Creating an Inkscape Effect with Python & ElementTree

From XPUB & Lens-Based wiki


Inkscape

See also: Using argparse to create useful commandline scripts in Python

From the Inkscape wiki:

(interpreter)? your_script (--param=value)* /path/to/input/SVGfile | inkscape


Inkscape Effects: Hello World

To create/install an inkscape extension, you need to place it in the Inkscape extensions folder ($HOME/.config/inkscape/extensions/ or /usr/share/inkscape/extensions).

First thing you need is an "inx" file, an "inkscape-extension" XML file that describes your extension to Inkscape. This is pretty much the simplest form of an effect: NB The "submenu" item selects which Menu your effect will be presented under (in this case, we create a new menu called "Python"), and the "_name" element sets the name of the Effect itself.

helloworld.inx

<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
	<_name>Hello World</_name>
	<id>pzi.helloworld</id>
	<dependency type="executable" location="extensions">helloworld.py</dependency>
    <effect>
        <object-type>all</object-type>
        <effects-menu>
            <submenu _name="Python"/>
        </effects-menu>
    </effect>
    <script>
    	<command reldir="extensions" interpreter="python">helloworld.py</command>
    </script>
</inkscape-extension>

Inkscape reads the inx files when it starts, so Inkscape needs to be restarted when an inx file is added or changed. Luckily, changes to the script do not require that Inkscape get restarted.

Now, for some code, a "Hello World", that does nothing (yet)...

helloworld.py

import sys, codecs
from lxml import etree

f = codecs.open(sys.argv[-1], encoding="utf-8")
doc = etree.parse(f)
sys.stdout.write(etree.tostring(doc, encoding="utf-8", xml_declaration=True))

helloworld.py (with log)

import sys, codecs
from lxml import etree

log = open("helloworld.log", "w")
log.write("argv: " + str(sys.argv))
log.close()

f = codecs.open(sys.argv[-1], encoding="utf-8")
doc = etree.parse(f)
sys.stdout.write(etree.tostring(doc, encoding="utf-8", xml_declaration=True))

helloworld.py, reading minimal arguments from inkscape

import sys, codecs, argparse
from lxml import etree

parser = argparse.ArgumentParser(description="My first Inkscape effect")
parser.add_argument('--id', action="append", help="id(s) of selected elements")
parser.add_argument('path', help="path of svg file to open")

args = parser.parse_args()

f = codecs.open(args.path, encoding="utf-8")
doc = etree.parse(f)
sys.stdout.write(etree.tostring(doc, encoding="utf-8", xml_declaration=True))

Finally, a version that uses etree to modify the fill of path elements.

import sys, codecs, argparse
from lxml import etree

NS = { 'svg':"http://www.w3.org/2000/svg" }

parser = argparse.ArgumentParser(description="My first Inkscape effect")
parser.add_argument('--id', action="append", help="id(s) of selected elements")
parser.add_argument('path', help="path of svg file to open")

args = parser.parse_args()

# Open the file & parse to a "etree" object
f = codecs.open(args.path, encoding="utf-8")
t = etree.parse(f)

# Loop over all svg:path elements
for path in t.xpath("//svg:path", namespaces=NS):
    style = path.get("style")
    if style:
        path.set("style", style.replace("fill:none", "fill:#FF0000"))

# Output the (modified) tree
sys.stdout.write(etree.tostring(doc, encoding="utf-8", xml_declaration=True))

Example Inkscape filter, manipulating the SVG as text with search and replace:

import sys, codecs
from lxml import etree
 
# open the filename given on the commandline
f = codecs.open(sys.argv[-1], encoding="utf-8")
text = f.read()

text = text.replace("FOO", "this is some magic value")

# OUTPUT THE TREE
print text

Example showing 3 different kinds of manipulations:

import sys, codecs, argparse
from lxml import etree

NS = { 'svg':"http://www.w3.org/2000/svg" }

parser = argparse.ArgumentParser(description="My first Inkscape effect")
parser.add_argument('--id', action="append", help="id(s) of selected elements")
parser.add_argument('path', help="path of svg file to open")

args = parser.parse_args()

# Open the file & parse to a "etree" object
f = codecs.open(args.path, encoding="utf-8")
t = etree.parse(f)

# I. Select and alter existing elements
#
# Loop over all svg:path elements
for path in t.xpath("//svg:path ", namespaces=NS):
    style = path.get("style")
    if style:
        path.set("style", style.replace("fill:none", "fill:#FFFF00"))

## II. Create & Insert a new element
## l.append(thing)
## l.insert(index, thing)
r = etree.Element("rect")
r.attrib['x'] = "100"
r.attrib['y'] = "100"
r.attrib['width'] = "50"
r.attrib['height'] = "50"
r.attrib["style"] = "fill:#00FF00;"
g = t.xpath("//svg:g", namespaces=NS)[0]
g.append(r)


## III: Clone & insert an element
from copy import deepcopy
for id in args.id:
    p = t.xpath("//*[@id='{0}']".format(id))
    if len(p):
        p = p[0]
        p2 = deepcopy(p)
        del p2.attrib["id"]
        p.getparent().append(p2)


# Output the (modified) tree
sys.stdout.write(etree.tostring(t, encoding="utf-8", xml_declaration=True))

Resources