Difference between revisions of "PythonEffectTutorial"

From Inkscape Wiki
Jump to navigation Jump to search
m
m (→‎Effect Extension Script: provide link to documentation of inkex.py and simplestyle.py)
(15 intermediate revisions by 4 users not shown)
Line 1: Line 1:
Effect extensions in Inkscape means a simple programs or scripts that reads an SVG file from standard input, transforms it somehow and prints it to the standart output. Usually Inkscape sits on both ends, providing the file with some parameters as input first and finally reading the output, which is then used for further work.
Effect extensions in Inkscape means a simple programs or scripts that reads an SVG file from standard input, transforms it somehow and prints it to the standard output. Usually Inkscape sits on both ends, providing the file with some parameters as input first and finally reading the output, which is then used for further work.
[[Image:Effect_flow.svg|thumb|right|400px]]
[[Image:Effect_flow.svg|thumb|right|400px]]


We will write a simple effect extension script in Python that will put "Hello World!" or "Hello <value of --what option>!" string in the center of document and inside a new layer.
We will write a simple effect extension script in [http://docs.python.org Python] that will create a new "Hello World!" or "Hello <value of --what option>!" string in the center of document and inside a new layer.


== Effect Extension Script ==
== Effect Extension Script ==


First of all create a file ''hello_world.py'' and make it executable with the Python interpreter with the well-known directive:
First of all create a file ''hello_world.py'' and make it executable with the Python interpreter with the well-known directive:
<pre>
<pre>
#!/usr/bin/env python
#!/usr/bin/env python
</pre>
</pre>


If you're going to put the file somewhere else than into inkscape's installation directory, we need to add a path so that python can find the necessary modules:
If you're going to put the file somewhere else than into Inkscape's installation directory, we need to add a path so that python can find the necessary modules:
 
<pre>
<pre>
import sys
import sys
Line 19: Line 17:
</pre>
</pre>


Import ''inkex.py'' file with ''Effect'' base class that will do most of work for us and ''simplestyle.py'' module with support functions for working with CSS styles. We will use just the ''formatStyle'' function from this module.:
Import the ''inkex.py'' file with the ''Effect'' base class that will do most of the work for us and the ''simplestyle.py'' module with support functions for working with CSS styles, for more information on these modules see [[Python modules for extensions]]. We will use just the ''formatStyle'' function from this module:
 
<pre>
<pre>
import inkex
import inkex
Line 26: Line 23:
</pre>
</pre>


Declare ''HelloWordEffect'' class and write a constructor where the base class is initialized and script options for the option parser are defined:
Declare a ''HelloWordEffect'' class that inherits from ''Effect'' and write a constructor where the base class is initialized and script options for the option parser are defined:
 
<pre>
<pre>
class HelloWorldEffect(inkex.Effect):
class HelloWorldEffect(inkex.Effect):
Line 37: Line 33:
</pre>
</pre>


The complete documentation for the ''OptionParser'' class can be found at http://docs.python.org/lib/module-optparse.html. Here we just use the ''add_option'' method which has as first argument a short option name, as second argument a long option name and then a few other arguments with this meaning:
The complete documentation for the ''OptionParser'' class can be found at [https://docs.python.org/2/library/optparse.html docs.python.org]. Here we just use the ''add_option'' method which has as first argument a short option name, as second argument a long option name and then a few other arguments with this meaning:


* ''action'' - An action which should be done with option value. In this case we use action ''store'' which will store option value in ''self.options.<destination>'' attribute.
* ''action'' - An action which should be done with option value. In this case we use action ''store'' which will store option value in ''self.options.<destination>'' attribute.
Line 45: Line 41:
* ''help'' - A help string that will be displayed if script will be given no arguments or some option or argument will have wrong syntax.
* ''help'' - A help string that will be displayed if script will be given no arguments or some option or argument will have wrong syntax.


Inkscape will create a GUI form with widgets for all specified options and prefill them with specified default values using ''.inx''. file for this extenstion which we will write later.  
Inkscape will create a GUI form with widgets for all specified options and prefill them with the default values specified using the ''.inx'' file for this extension which we will write later.  


We need to override only one ''Effect'' class method to provide effect functionality:  
We need to override only one ''Effect'' class method to provide the desired effect functionality:  
<pre>
<pre>
     def effect(self):
     def effect(self):
         what = self.options.what
         what = self.options.what
</pre>
</pre>
As you can imagine we just stored the ''--what'' option value to the ''what'' variable.


As you can mention we just stored ''--what'' option value to ''what'' variable.
Now we will finally start to do something. We will have to work with the XML representation of the SVG document that we can access via ''Effect'''s ''self.document'' attribute.
It is of lxml's '' _ElementTree'' class type.  Complete documentation for the lxml package can be found at [http://lxml.de/ lxml.de].


Now we will finally start to do something. We will work with XML representation of SVG document via ''self.document'' attribute. It is of ''Document'' class type from ''xml.dom'' module. Complete documentation for this module can be found at http://docs.python.org/lib/module-xml.dom.html.
First we get SVG document's ''svg'' element and its dimensions:
 
<pre>
First get SVG document ''svg'' element and its dimensions:
        svg = self.document.getroot()
        # or alternatively
        # svg = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0]


<pre>
         # Again, there are two ways to get the attibutes:
         svg = self.document.getElementsByTagName('svg')[0]
         width = self.unittouu(svg.get('width'))
         width = inkex.unittouu(svg.getAttribute('width'))
         height = self.unittouu(svg.attrib['height'])
         height = inkex.unittouu(svg.getAttribute('height'))
</pre>
</pre>
The ''xpath'' function returns a list of all matching elements so we just use the first one of them.


Function ''getElementsByTagName'' returns list of all found elements of this name so we just use first of them.
We now create an SVG group element ('' 'g' '') and "mark" it as a layer using Inkscape' SVG extensions:
 
Create SVG group element and "convert" it to layer using Inkscape SVG extenstions:
 
<pre>
<pre>
         layer = self.document.createElement('g')
         layer = inkex.etree.SubElement(svg, 'g')
         layer.setAttribute('inkscape:label', 'Hello %s Layer' % (what))
         layer.set(inkex.addNS('label', 'inkscape'), 'Hello %s Layer' % (what))
         layer.setAttribute('inkscape:groupmode', 'layer')
         layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
</pre>
</pre>
This creates ''inkscape:label'' and ''inkscape:groupmode'' attributes, which will only be read by Inkscape or compatible applications.  To all other viewers, this new element looks just like a plain SVG group.


Create SVG text element and its value containing "Hello World"" string:
Now we create an SVG text element with a text value containing the "Hello World" string:
 
<pre>
<pre>
         text = self.document.createElement('text')
         text = inkex.etree.Element(inkex.addNS('text','svg'))
         value = self.document.createTextNode('Hello %s!' % (what))
         text.text = 'Hello %s!' % (what)
</pre>
</pre>


Set position of text to center of SVG document:
Set the position of the text to the center of SVG document:
 
<pre>
<pre>
         text.setAttribute('x', str(width / 2))
         text.set('x', str(width / 2))
         text.setAttribute('y', str(height / 2))
         text.set('y', str(height / 2))
</pre>
</pre>


If we want center text on its position we will define CSS style of SVG ''text'' element. Actually use ''text-anchor'' SVG extension to CSS styles to do that work:
If we want to center the text on its position we need to define the CSS style of the SVG ''text'' element. Actually we use the ''text-anchor'' SVG extension to CSS styles to do that work:
 
<pre>
<pre>
         style = {'text-align' : 'center', 'text-anchor' : 'middle'}
         style = {'text-align' : 'center', 'text-anchor' : 'middle'}
         text.setAttribute('style', formatStyle(style))
         text.set('style', formatStyle(style))
</pre>
</pre>


Finally connect all created elements together and put them in SVG document:
Finally we connect all created elements together and put them into the SVG document:
 
<pre>
<pre>
        text.appendChild(value)
         layer.append(text)
         layer.appendChild(text)
        svg.appendChild(layer)
</pre>
</pre>


We just defined a class of our effect extension so we have to create an instance of it and execute it in main control flow:
We just defined a class that inherited from the original effect extension so we have to create an instance of it and execute it in the main control flow:
 
<pre>
<pre>
effect = HelloWorldEffect()
effect = HelloWorldEffect()
Line 116: Line 107:


<pre>
<pre>
<inkscape-extension>
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
   <_name>Hello World!</_name>
   <_name>Hello World!</_name>
   <id>org.ekips.filter.hello_world</id>
   <id>org.ekips.filter.hello_world</id>
Line 140: Line 132:
== Installation ==
== Installation ==


To install a new extenstion just put ''hello_world.py'' and ''hello_world.inx'' files with all dependency modules to ''<path_to_inkscape>/extensions'' or ''~/.inkscape/extensions'' directory and start Inkscape. A new menu item ''Hello World!'' in ''Effects->Examples'' menu should appear.
To install a new extenstion just put ''hello_world.py'' and ''hello_world.inx'' files with all dependency modules to the ''<path_to_inkscape>/extensions'' or ''~/.config/inkscape/extensions'' directory.  On Linux you will probably have to make the python script executable first if you haven't done this yet.  This is usually done by the usual command (or in your preferred file manager):
<pre>
$ chmod a+x hello_world.py
</pre>
 
Now start Inkscape. A new menu item ''Hello World!'' in ''Extensions->Examples'' menu should appear.


== Complete Source Code ==
== Complete Source Code ==
Line 152: Line 149:
# the installation directory
# the installation directory
import sys
import sys
sys.path.append('/usr/share/inkscape/extensions') # or another path, as necessary
sys.path.append('/usr/share/inkscape/extensions')


# We will use inex module with predefined effect base class.
# We will use the inkex module with the predefined Effect base class.
import inkex
import inkex
# simplestyle module provides functions for style parsing.
# The simplestyle module provides functions for style parsing.
from simplestyle import *
from simplestyle import *


""" Example Inkscape effect extension.
Creates a new layer with "Hello World!" text centered in middle of document."""
class HelloWorldEffect(inkex.Effect):
class HelloWorldEffect(inkex.Effect):
     """ Constructor.
     """
     Defines "--what" option of a script."""
    Example Inkscape effect extension.
     Creates a new layer with a "Hello World!" text centered in the middle of the document.
    """
     def __init__(self):
     def __init__(self):
         # Call base class construtor.
        """
        Constructor.
        Defines the "--what" option of a script.
        """
         # Call the base class constructor.
         inkex.Effect.__init__(self)
         inkex.Effect.__init__(self)


Line 173: Line 174:
           help = 'What would you like to greet?')
           help = 'What would you like to greet?')


    """ Effect behaviour.
    Overrides base class' method and insert "Hello World" text in SVG document. """
     def effect(self):
     def effect(self):
         # Get script "--what" option value.
        """
        Effect behaviour.
        Overrides base class' method and inserts "Hello World" text into SVG document.
        """
         # Get script's "--what" option value.
         what = self.options.what
         what = self.options.what


         # Get access to main SVG document element and get its dimensions.
         # Get access to main SVG document element and get its dimensions.
         svg = self.document.getElementsByTagName('svg')[0]
         svg = self.document.getroot()
         width = inkex.unittouu(svg.getAttribute('width'))
        # or alternatively
         height = inkex.unittouu(svg.getAttribute('height'))
        # svg = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0]
 
        # Again, there are two ways to get the attibutes:
         width = self.unittouu(svg.get('width'))
         height = self.unittouu(svg.attrib['height'])


         # Create a new layer.
         # Create a new layer.
         layer = self.document.createElement('g')
         layer = inkex.etree.SubElement(svg, 'g')
         layer.setAttribute('inkscape:label', 'Hello %s Layer' % (what))
         layer.set(inkex.addNS('label', 'inkscape'), 'Hello %s Layer' % (what))
         layer.setAttribute('inkscape:groupmode', 'layer')
         layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')


         # Create text element
         # Create text element
         text = self.document.createElement('text')
         text = inkex.etree.Element(inkex.addNS('text','svg'))
         value = self.document.createTextNode('Hello %s!' % (what))
         text.text = 'Hello %s!' % (what)


         # Set text position to center of document.
         # Set text position to center of document.
         text.setAttribute('x', str(width / 2))
         text.set('x', str(width / 2))
         text.setAttribute('y', str(height / 2))
         text.set('y', str(height / 2))


         # Center text horizontally with CSS style.
         # Center text horizontally with CSS style.
         style = {'text-align' : 'center', 'text-anchor': 'middle'}
         style = {'text-align' : 'center', 'text-anchor': 'middle'}
         text.setAttribute('style', formatStyle(style))
         text.set('style', formatStyle(style))


         # Connect elements together.
         # Connect elements together.
        text.appendChild(value)
         layer.append(text)
         layer.appendChild(text)
        svg.appendChild(layer)


# Create effect instance and apply it.
# Create effect instance and apply it.
Line 211: Line 216:
</pre>
</pre>


Last edited by --[[User:Rubikcube|Rubikcube]] 21:18, 7 August 2008 (UTC), based on a version by
[[User:Blackhex|Blackhex]] 11:59, 26 April 2007 (UTC)
[[User:Blackhex|Blackhex]] 11:59, 26 April 2007 (UTC)
[[Category:Developer Documentation]]
[[Category:Extensions]]

Revision as of 20:53, 6 November 2017

Effect extensions in Inkscape means a simple programs or scripts that reads an SVG file from standard input, transforms it somehow and prints it to the standard output. Usually Inkscape sits on both ends, providing the file with some parameters as input first and finally reading the output, which is then used for further work.

Effect flow.svg

We will write a simple effect extension script in Python that will create a new "Hello World!" or "Hello <value of --what option>!" string in the center of document and inside a new layer.

Effect Extension Script

First of all create a file hello_world.py and make it executable with the Python interpreter with the well-known directive:

#!/usr/bin/env python

If you're going to put the file somewhere else than into Inkscape's installation directory, we need to add a path so that python can find the necessary modules:

import sys
sys.path.append('/usr/share/inkscape/extensions') # or another path, as necessary

Import the inkex.py file with the Effect base class that will do most of the work for us and the simplestyle.py module with support functions for working with CSS styles, for more information on these modules see Python modules for extensions. We will use just the formatStyle function from this module:

import inkex
from simplestyle import *

Declare a HelloWordEffect class that inherits from Effect and write a constructor where the base class is initialized and script options for the option parser are defined:

class HelloWorldEffect(inkex.Effect):
    def __init__(self):
        inkex.Effect.__init__(self)
        self.OptionParser.add_option('-w', '--what', action = 'store',
          type = 'string', dest = 'what', default = 'World',
          help = 'What would you like to greet?')

The complete documentation for the OptionParser class can be found at docs.python.org. Here we just use the add_option method which has as first argument a short option name, as second argument a long option name and then a few other arguments with this meaning:

  • action - An action which should be done with option value. In this case we use action store which will store option value in self.options.<destination> attribute.
  • type - Type of option value. We use string here.
  • dest - Destination of option action specified by action argument. Using what value we say that we want to store option value to self.options.what attribute.
  • default - Defalut value for this option if it is not specified.
  • help - A help string that will be displayed if script will be given no arguments or some option or argument will have wrong syntax.

Inkscape will create a GUI form with widgets for all specified options and prefill them with the default values specified using the .inx file for this extension which we will write later.

We need to override only one Effect class method to provide the desired effect functionality:

    def effect(self):
        what = self.options.what

As you can imagine we just stored the --what option value to the what variable.

Now we will finally start to do something. We will have to work with the XML representation of the SVG document that we can access via Effect's self.document attribute. It is of lxml's _ElementTree class type. Complete documentation for the lxml package can be found at lxml.de.

First we get SVG document's svg element and its dimensions:

        svg = self.document.getroot()
        # or alternatively
        # svg = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0]

        # Again, there are two ways to get the attibutes:
        width  = self.unittouu(svg.get('width'))
        height = self.unittouu(svg.attrib['height'])

The xpath function returns a list of all matching elements so we just use the first one of them.

We now create an SVG group element ( 'g' ) and "mark" it as a layer using Inkscape' SVG extensions:

        layer = inkex.etree.SubElement(svg, 'g')
        layer.set(inkex.addNS('label', 'inkscape'), 'Hello %s Layer' % (what))
        layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')

This creates inkscape:label and inkscape:groupmode attributes, which will only be read by Inkscape or compatible applications. To all other viewers, this new element looks just like a plain SVG group.

Now we create an SVG text element with a text value containing the "Hello World" string:

        text = inkex.etree.Element(inkex.addNS('text','svg'))
        text.text = 'Hello %s!' % (what)

Set the position of the text to the center of SVG document:

        text.set('x', str(width / 2))
        text.set('y', str(height / 2))

If we want to center the text on its position we need to define the CSS style of the SVG text element. Actually we use the text-anchor SVG extension to CSS styles to do that work:

        style = {'text-align' : 'center', 'text-anchor' : 'middle'}
        text.set('style', formatStyle(style))

Finally we connect all created elements together and put them into the SVG document:

        layer.append(text)

We just defined a class that inherited from the original effect extension so we have to create an instance of it and execute it in the main control flow:

effect = HelloWorldEffect()
effect.affect()

Extension Description File

To include script in Inkscape's main menu create hello_world.inx file describing script evokation.

<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
  <_name>Hello World!</_name>
  <id>org.ekips.filter.hello_world</id>
  <dependency type="executable" location="extensions">hello_world.py</dependency>
  <dependency type="executable" location="extensions">inkex.py</dependency>
  <param name="what" type="string" _gui-text="What would you like to greet?">World</param>
  <effect>
    <object-type>all</object-type>
    <effects-menu>
       <submenu _name="Examples"/>
    </effects-menu>
  </effect>
  <script>
    <command reldir="extensions" interpreter="python">hello_world.py</command>
  </script>
</inkscape-extension>

Create <param> element for every option of a script and <dependency> for every included module which is not from Python standard library. Inkscape will search for this modules in directory with script. <effect> element and its descendants defines name of menu item evoking our new "Hello World!" extension.

If the inx file isn't well formed or if any of the dependencies wasn't met, the extension won't show up in the menu. If your extension doesn't show up, take a look at extension-errors.log, which may give you a hint why it wasn't loaded.

Installation

To install a new extenstion just put hello_world.py and hello_world.inx files with all dependency modules to the <path_to_inkscape>/extensions or ~/.config/inkscape/extensions directory. On Linux you will probably have to make the python script executable first if you haven't done this yet. This is usually done by the usual command (or in your preferred file manager):

$ chmod a+x hello_world.py

Now start Inkscape. A new menu item Hello World! in Extensions->Examples menu should appear.

Complete Source Code

Here is a complete commented source pre of hello_world.py script file:

#!/usr/bin/env python

# These two lines are only needed if you don't put the script directly into
# the installation directory
import sys
sys.path.append('/usr/share/inkscape/extensions')

# We will use the inkex module with the predefined Effect base class.
import inkex
# The simplestyle module provides functions for style parsing.
from simplestyle import *

class HelloWorldEffect(inkex.Effect):
    """
    Example Inkscape effect extension.
    Creates a new layer with a "Hello World!" text centered in the middle of the document.
    """
    def __init__(self):
        """
        Constructor.
        Defines the "--what" option of a script.
        """
        # Call the base class constructor.
        inkex.Effect.__init__(self)

        # Define string option "--what" with "-w" shortcut and default value "World".
        self.OptionParser.add_option('-w', '--what', action = 'store',
          type = 'string', dest = 'what', default = 'World',
          help = 'What would you like to greet?')

    def effect(self):
        """
        Effect behaviour.
        Overrides base class' method and inserts "Hello World" text into SVG document.
        """
        # Get script's "--what" option value.
        what = self.options.what

        # Get access to main SVG document element and get its dimensions.
        svg = self.document.getroot()
        # or alternatively
        # svg = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0]

        # Again, there are two ways to get the attibutes:
        width  = self.unittouu(svg.get('width'))
        height = self.unittouu(svg.attrib['height'])

        # Create a new layer.
        layer = inkex.etree.SubElement(svg, 'g')
        layer.set(inkex.addNS('label', 'inkscape'), 'Hello %s Layer' % (what))
        layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')

        # Create text element
        text = inkex.etree.Element(inkex.addNS('text','svg'))
        text.text = 'Hello %s!' % (what)

        # Set text position to center of document.
        text.set('x', str(width / 2))
        text.set('y', str(height / 2))

        # Center text horizontally with CSS style.
        style = {'text-align' : 'center', 'text-anchor': 'middle'}
        text.set('style', formatStyle(style))

        # Connect elements together.
        layer.append(text)

# Create effect instance and apply it.
effect = HelloWorldEffect()
effect.affect()

Last edited by --Rubikcube 21:18, 7 August 2008 (UTC), based on a version by Blackhex 11:59, 26 April 2007 (UTC)