From 8e5a1520cd5e901f514c382ad17e77d64727896a Mon Sep 17 00:00:00 2001
From: Michael Schwarz <michi.schwarz@gmail.com>
Date: Wed, 10 Dec 2014 22:13:34 +0100
Subject: Moved DXF export scripts to separate directory.

---
 support/dxf_export/__init__.py            |   0
 support/dxf_export/__main__.py            |  95 +++++
 support/dxf_export/better_dxf_outlines.py | 123 ++++++
 support/dxf_export/bezmisc.py             | 274 +++++++++++++
 support/dxf_export/cspsubdiv.py           |  37 ++
 support/dxf_export/cubicsuperpath.py      | 169 ++++++++
 support/dxf_export/dxf_templates.py       | 645 ++++++++++++++++++++++++++++++
 support/dxf_export/ffgeom.py              | 141 +++++++
 support/dxf_export/inkex.py               | 241 +++++++++++
 support/dxf_export/simplepath.py          | 212 ++++++++++
 support/dxf_export/simplestyle.py         | 244 +++++++++++
 support/dxf_export/simpletransform.py     | 241 +++++++++++
 12 files changed, 2422 insertions(+)
 create mode 100644 support/dxf_export/__init__.py
 create mode 100644 support/dxf_export/__main__.py
 create mode 100755 support/dxf_export/better_dxf_outlines.py
 create mode 100755 support/dxf_export/bezmisc.py
 create mode 100755 support/dxf_export/cspsubdiv.py
 create mode 100755 support/dxf_export/cubicsuperpath.py
 create mode 100755 support/dxf_export/dxf_templates.py
 create mode 100755 support/dxf_export/ffgeom.py
 create mode 100755 support/dxf_export/inkex.py
 create mode 100755 support/dxf_export/simplepath.py
 create mode 100755 support/dxf_export/simplestyle.py
 create mode 100755 support/dxf_export/simpletransform.py

(limited to 'support')

diff --git a/support/dxf_export/__init__.py b/support/dxf_export/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/support/dxf_export/__main__.py b/support/dxf_export/__main__.py
new file mode 100644
index 0000000..ea6c7ad
--- /dev/null
+++ b/support/dxf_export/__main__.py
@@ -0,0 +1,95 @@
+import sys, os, xml.etree.ElementTree, subprocess, tempfile, contextlib, shutil
+import better_dxf_outlines
+
+
+@contextlib.contextmanager
+def TemporaryDirectory():
+	dir = tempfile.mkdtemp()
+	
+	try:
+		yield dir
+	finally:
+		shutil.rmtree(dir)
+
+
+def _export_dxf(in_path, out_path):
+	dxf_export = better_dxf_outlines.MyEffect()
+	dxf_export.affect(args = [in_path], output = False)
+	
+	with open(out_path, 'w') as file:
+		file.write(dxf_export.dxf)
+
+
+def _get_inkscape_layer_count(svg_path):
+	document = xml.etree.ElementTree.parse(svg_path)
+	layers = document.findall(
+		'{http://www.w3.org/2000/svg}g[@{http://www.inkscape.org/namespaces/inkscape}groupmode="layer"]')
+	
+	return len(layers)
+
+
+def _command(args):
+	process = subprocess.Popen(args)
+	process.wait()
+	
+	assert not process.returncode
+
+
+def _inkscape(svg_path, verbs):
+	def iter_args():
+		yield os.environ['INKSCAPE']
+		
+		for i in verbs:
+			yield '--verb'
+			yield i
+		
+		yield svg_path
+	
+	_command(list(iter_args()))
+
+
+def _unfuck_svg_document(temp_svg_path):
+	"""
+	Unfucks an SVG document so is can be processed by the better_dxf_export plugin.
+	"""
+	
+	layers_count = _get_inkscape_layer_count(temp_svg_path)
+
+	def iter_inkscape_verbs():
+		yield 'LayerUnlockAll'
+		yield 'LayerShowAll'
+
+		# Go to the first layer
+		for _ in range(layers_count):
+			yield 'LayerPrev'
+
+		for _ in range(layers_count):
+			yield 'EditSelectAll'
+			yield 'ObjectToPath'
+			yield 'EditSelectAll'
+			yield 'SelectionUnGroup'
+			yield 'EditSelectAll'
+			yield 'StrokeToPath'
+			yield 'EditSelectAll'
+			yield 'SelectionUnion'
+			yield 'LayerNext'
+
+		yield 'FileSave'
+		yield 'FileClose'
+		yield 'FileQuit'
+
+	_inkscape(temp_svg_path, list(iter_inkscape_verbs()))
+
+
+def main(in_path, out_path):
+	with TemporaryDirectory() as temp_dir:
+		temp_svg_path = os.path.join(temp_dir, 'temp.svg')
+		
+		shutil.copyfile(in_path, temp_svg_path)
+
+		_unfuck_svg_document(temp_svg_path)
+		
+		_export_dxf(temp_svg_path, out_path)
+
+
+main(*sys.argv[1:])
diff --git a/support/dxf_export/better_dxf_outlines.py b/support/dxf_export/better_dxf_outlines.py
new file mode 100755
index 0000000..19ec6c8
--- /dev/null
+++ b/support/dxf_export/better_dxf_outlines.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python 
+'''
+Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org
+- template dxf_outlines.dxf added Feb 2008 by Alvin Penner, penner@vaxxine.com
+- layers, transformation, flattening added April 2008 by Bob Cook, bob@bobcookdev.com
+- bug fix for xpath() calls added February 2009 by Bob Cook, bob@bobcookdev.com
+- max value of 10 on path flattening, August 2011 by Bob Cook, bob@bobcookdev.com
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+'''
+import inkex, simplepath, simpletransform, cubicsuperpath, cspsubdiv, dxf_templates, re
+
+class MyEffect(inkex.Effect):
+
+    def __init__(self):
+    
+        inkex.Effect.__init__(self)
+        self.dxf = ''
+        self.handle = 255
+        self.flatness = 0.1
+        
+    def output(self):
+        print self.dxf
+        
+    def dxf_add(self, str):
+        self.dxf += str
+        
+    def dxf_insert_code(self, code, value):
+        self.dxf += code + "\n" + value + "\n"
+        
+    def dxf_line(self,layer,csp):
+        self.dxf_insert_code(   '0', 'LINE' )
+        self.dxf_insert_code(   '8', layer )
+        self.dxf_insert_code(  '62', '4' )
+        self.dxf_insert_code(   '5', '%x' % self.handle )
+        self.dxf_insert_code( '100', 'AcDbEntity' )
+        self.dxf_insert_code( '100', 'AcDbLine' )
+        self.dxf_insert_code(  '10', '%f' % csp[0][0] )
+        self.dxf_insert_code(  '20', '%f' % csp[0][1] )
+        self.dxf_insert_code(  '30', '0.0' )
+        self.dxf_insert_code(  '11', '%f' % csp[1][0] )
+        self.dxf_insert_code(  '21', '%f' % csp[1][1] )
+        self.dxf_insert_code(  '31', '0.0' )
+        
+    def dxf_point(self,layer,x,y):
+        self.dxf_insert_code(   '0', 'POINT' )
+        self.dxf_insert_code(   '8', layer )
+        self.dxf_insert_code(  '62', '4' )
+        self.dxf_insert_code(   '5', '%x' % self.handle )
+        self.dxf_insert_code( '100', 'AcDbEntity' )
+        self.dxf_insert_code( '100', 'AcDbPoint' )
+        self.dxf_insert_code(  '10', '%f' % x )
+        self.dxf_insert_code(  '20', '%f' % y )
+        self.dxf_insert_code(  '30', '0.0' )
+        
+    def dxf_path_to_lines(self,layer,p):
+        f = self.flatness
+        is_flat = 0
+        while is_flat < 1:
+            if f > 10:
+                break
+            try:
+                cspsubdiv.cspsubdiv(p, f)
+                is_flat = 1
+            except:
+                f += 0.1
+        
+        for sub in p:
+            for i in range(len(sub)-1):
+                self.handle += 1
+                s = sub[i]
+                e = sub[i+1]
+                self.dxf_line(layer,[s[1],e[1]])
+    
+    def dxf_path_to_point(self,layer,p):
+        bbox = simpletransform.roughBBox(p)
+        x = (bbox[0] + bbox[1]) / 2
+        y = (bbox[2] + bbox[3]) / 2
+        self.dxf_point(layer,x,y)
+        
+    def effect(self):
+        self.dxf_insert_code( '999', 'Inkscape export via "Better DXF Output" (bob@bobcookdev.com)' )
+        self.dxf_add( dxf_templates.r14_header )
+        
+        scale = 25.4/90.0
+        h = inkex.unittouu(self.document.getroot().xpath('@height',namespaces=inkex.NSS)[0])
+        
+        path = '//svg:path'
+        for node in self.document.getroot().xpath(path,namespaces=inkex.NSS):
+        
+            layer = node.getparent().get(inkex.addNS('label','inkscape'))
+            if layer == None:
+                layer = 'Layer 1'
+            
+            d = node.get('d')
+            p = cubicsuperpath.parsePath(d)
+            
+            t = node.get('transform')
+            if t != None:
+                m = simpletransform.parseTransform(t)
+                simpletransform.applyTransformToPath(m,p)
+            
+            m = [[scale,0,0],[0,-scale,h*scale]]
+            simpletransform.applyTransformToPath(m,p)
+    
+            if re.search('drill$',layer,re.I) == None:
+                self.dxf_path_to_lines(layer,p)
+            else:    
+                self.dxf_path_to_point(layer,p)
+                    
+        self.dxf_add( dxf_templates.r14_footer )
diff --git a/support/dxf_export/bezmisc.py b/support/dxf_export/bezmisc.py
new file mode 100755
index 0000000..e663fa6
--- /dev/null
+++ b/support/dxf_export/bezmisc.py
@@ -0,0 +1,274 @@
+#!/usr/bin/env python
+'''
+Copyright (C) 2010 Nick Drobchenko, nick@cnc-club.ru
+Copyright (C) 2005 Aaron Spike, aaron@ekips.org
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+'''
+
+import math, cmath
+
+def rootWrapper(a,b,c,d):
+    if a:
+        # Monics formula see http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots
+        a,b,c = (b/a, c/a, d/a)
+        m = 2.0*a**3 - 9.0*a*b + 27.0*c
+        k = a**2 - 3.0*b
+        n = m**2 - 4.0*k**3
+        w1 = -.5 + .5*cmath.sqrt(-3.0)
+        w2 = -.5 - .5*cmath.sqrt(-3.0)
+        if n < 0:
+            m1 = pow(complex((m+cmath.sqrt(n))/2),1./3)
+            n1 = pow(complex((m-cmath.sqrt(n))/2),1./3)
+        else:
+            if m+math.sqrt(n) < 0:
+                m1 = -pow(-(m+math.sqrt(n))/2,1./3)
+            else:
+                m1 = pow((m+math.sqrt(n))/2,1./3)
+            if m-math.sqrt(n) < 0:
+                n1 = -pow(-(m-math.sqrt(n))/2,1./3)
+            else:
+                n1 = pow((m-math.sqrt(n))/2,1./3)
+        x1 = -1./3 * (a + m1 + n1)
+        x2 = -1./3 * (a + w1*m1 + w2*n1)
+        x3 = -1./3 * (a + w2*m1 + w1*n1)
+        return (x1,x2,x3)
+    elif b:
+        det=c**2.0-4.0*b*d
+        if det:
+            return (-c+cmath.sqrt(det))/(2.0*b),(-c-cmath.sqrt(det))/(2.0*b)
+        else:
+            return -c/(2.0*b),
+    elif c:
+        return 1.0*(-d/c),
+    return ()
+
+def bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3))):
+    #parametric bezier
+    x0=bx0
+    y0=by0
+    cx=3*(bx1-x0)
+    bx=3*(bx2-bx1)-cx
+    ax=bx3-x0-cx-bx
+    cy=3*(by1-y0)
+    by=3*(by2-by1)-cy
+    ay=by3-y0-cy-by
+
+    return ax,ay,bx,by,cx,cy,x0,y0
+    #ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+
+def linebezierintersect(((lx1,ly1),(lx2,ly2)),((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3))):
+    #parametric line
+    dd=lx1
+    cc=lx2-lx1
+    bb=ly1
+    aa=ly2-ly1
+
+    if aa:
+        coef1=cc/aa
+        coef2=1
+    else:
+        coef1=1
+        coef2=aa/cc
+
+    ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+    #cubic intersection coefficients
+    a=coef1*ay-coef2*ax
+    b=coef1*by-coef2*bx
+    c=coef1*cy-coef2*cx
+    d=coef1*(y0-bb)-coef2*(x0-dd)
+
+    roots = rootWrapper(a,b,c,d)
+    retval = []
+    for i in roots:
+        if type(i) is complex and i.imag==0:
+            i = i.real
+        if type(i) is not complex and 0<=i<=1:
+            retval.append(bezierpointatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),i))
+    return retval
+
+def bezierpointatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
+    ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+    x=ax*(t**3)+bx*(t**2)+cx*t+x0
+    y=ay*(t**3)+by*(t**2)+cy*t+y0
+    return x,y
+
+def bezierslopeatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
+    ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+    dx=3*ax*(t**2)+2*bx*t+cx
+    dy=3*ay*(t**2)+2*by*t+cy
+    return dx,dy
+
+def beziertatslope(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),(dy,dx)):
+    ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+    #quadratic coefficents of slope formula
+    if dx:
+        slope = 1.0*(dy/dx)
+        a=3*ay-3*ax*slope
+        b=2*by-2*bx*slope
+        c=cy-cx*slope
+    elif dy:
+        slope = 1.0*(dx/dy)
+        a=3*ax-3*ay*slope
+        b=2*bx-2*by*slope
+        c=cx-cy*slope
+    else:
+        return []
+
+    roots = rootWrapper(0,a,b,c)
+    retval = []
+    for i in roots:
+        if type(i) is complex and i.imag==0:
+            i = i.real
+        if type(i) is not complex and 0<=i<=1:
+            retval.append(i)
+    return retval
+
+def tpoint((x1,y1),(x2,y2),t):
+    return x1+t*(x2-x1),y1+t*(y2-y1)
+def beziersplitatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
+    m1=tpoint((bx0,by0),(bx1,by1),t)
+    m2=tpoint((bx1,by1),(bx2,by2),t)
+    m3=tpoint((bx2,by2),(bx3,by3),t)
+    m4=tpoint(m1,m2,t)
+    m5=tpoint(m2,m3,t)
+    m=tpoint(m4,m5,t)
+    
+    return ((bx0,by0),m1,m4,m),(m,m5,m3,(bx3,by3))
+
+'''
+Approximating the arc length of a bezier curve
+according to <http://www.cit.gu.edu.au/~anthony/info/graphics/bezier.curves>
+
+if:
+    L1 = |P0 P1| +|P1 P2| +|P2 P3| 
+    L0 = |P0 P3|
+then: 
+    L = 1/2*L0 + 1/2*L1
+    ERR = L1-L0
+ERR approaches 0 as the number of subdivisions (m) increases
+    2^-4m
+
+Reference:
+Jens Gravesen <gravesen@mat.dth.dk>
+"Adaptive subdivision and the length of Bezier curves"
+mat-report no. 1992-10, Mathematical Institute, The Technical
+University of Denmark. 
+'''
+def pointdistance((x1,y1),(x2,y2)):
+    return math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2))
+def Gravesen_addifclose(b, len, error = 0.001):
+    box = 0
+    for i in range(1,4):
+        box += pointdistance(b[i-1], b[i])
+    chord = pointdistance(b[0], b[3])
+    if (box - chord) > error:
+        first, second = beziersplitatt(b, 0.5)
+        Gravesen_addifclose(first, len, error)
+        Gravesen_addifclose(second, len, error)
+    else:
+        len[0] += (box / 2.0) + (chord / 2.0)
+def bezierlengthGravesen(b, error = 0.001):
+    len = [0]
+    Gravesen_addifclose(b, len, error)
+    return len[0]
+
+# balf = Bezier Arc Length Function
+balfax,balfbx,balfcx,balfay,balfby,balfcy = 0,0,0,0,0,0
+def balf(t):
+    retval = (balfax*(t**2) + balfbx*t + balfcx)**2 + (balfay*(t**2) + balfby*t + balfcy)**2
+    return math.sqrt(retval)
+
+def Simpson(f, a, b, n_limit, tolerance):
+    n = 2
+    multiplier = (b - a)/6.0
+    endsum = f(a) + f(b)
+    interval = (b - a)/2.0
+    asum = 0.0
+    bsum = f(a + interval)
+    est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum))
+    est0 = 2.0 * est1
+    #print multiplier, endsum, interval, asum, bsum, est1, est0
+    while n < n_limit and abs(est1 - est0) > tolerance:
+        n *= 2
+        multiplier /= 2.0
+        interval /= 2.0
+        asum += bsum
+        bsum = 0.0
+        est0 = est1
+        for i in xrange(1, n, 2):
+            bsum += f(a + (i * interval))
+            est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum))
+    #print multiplier, endsum, interval, asum, bsum, est1, est0
+    return est1
+
+def bezierlengthSimpson(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)), tolerance = 0.001):
+    global balfax,balfbx,balfcx,balfay,balfby,balfcy
+    ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+    balfax,balfbx,balfcx,balfay,balfby,balfcy = 3*ax,2*bx,cx,3*ay,2*by,cy
+    return Simpson(balf, 0.0, 1.0, 4096, tolerance)
+
+def beziertatlength(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)), l = 0.5, tolerance = 0.001):
+    global balfax,balfbx,balfcx,balfay,balfby,balfcy
+    ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
+    balfax,balfbx,balfcx,balfay,balfby,balfcy = 3*ax,2*bx,cx,3*ay,2*by,cy
+    t = 1.0
+    tdiv = t
+    curlen = Simpson(balf, 0.0, t, 4096, tolerance)
+    targetlen = l * curlen
+    diff = curlen - targetlen
+    while abs(diff) > tolerance:
+        tdiv /= 2.0
+        if diff < 0:
+            t += tdiv
+        else:
+            t -= tdiv            
+        curlen = Simpson(balf, 0.0, t, 4096, tolerance)
+        diff = curlen - targetlen
+    return t
+
+#default bezier length method
+bezierlength = bezierlengthSimpson
+
+if __name__ == '__main__':
+    import timing
+    #print linebezierintersect(((,),(,)),((,),(,),(,),(,)))
+    #print linebezierintersect(((0,1),(0,-1)),((-1,0),(-.5,0),(.5,0),(1,0)))
+    tol = 0.00000001
+    curves = [((0,0),(1,5),(4,5),(5,5)),
+            ((0,0),(0,0),(5,0),(10,0)),
+            ((0,0),(0,0),(5,1),(10,0)),
+            ((-10,0),(0,0),(10,0),(10,10)),
+            ((15,10),(0,0),(10,0),(-5,10))]
+    '''
+    for curve in curves:
+        timing.start()
+        g = bezierlengthGravesen(curve,tol)
+        timing.finish()
+        gt = timing.micro()
+
+        timing.start()
+        s = bezierlengthSimpson(curve,tol)
+        timing.finish()
+        st = timing.micro()
+
+        print g, gt
+        print s, st
+    '''
+    for curve in curves:
+        print beziertatlength(curve,0.5)
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
diff --git a/support/dxf_export/cspsubdiv.py b/support/dxf_export/cspsubdiv.py
new file mode 100755
index 0000000..c34236a
--- /dev/null
+++ b/support/dxf_export/cspsubdiv.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+from bezmisc import *
+from ffgeom import *
+
+def maxdist(((p0x,p0y),(p1x,p1y),(p2x,p2y),(p3x,p3y))):
+    p0 = Point(p0x,p0y)
+    p1 = Point(p1x,p1y)
+    p2 = Point(p2x,p2y)
+    p3 = Point(p3x,p3y)
+
+    s1 = Segment(p0,p3)
+    return max(s1.distanceToPoint(p1),s1.distanceToPoint(p2))
+    
+
+def cspsubdiv(csp,flat):
+    for sp in csp:
+        subdiv(sp,flat)
+
+def subdiv(sp,flat,i=1):
+    while i < len(sp):
+        p0 = sp[i-1][1]
+        p1 = sp[i-1][2]
+        p2 = sp[i][0]
+        p3 = sp[i][1]
+        
+        b = (p0,p1,p2,p3)
+        m = maxdist(b)
+        if m <= flat:
+            i += 1
+        else:
+            one, two = beziersplitatt(b,0.5)
+            sp[i-1][2] = one[1]
+            sp[i][0] = two[2]
+            p = [one[2],one[3],two[1]]
+            sp[i:1] = [p]
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
diff --git a/support/dxf_export/cubicsuperpath.py b/support/dxf_export/cubicsuperpath.py
new file mode 100755
index 0000000..af61acb
--- /dev/null
+++ b/support/dxf_export/cubicsuperpath.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python
+"""
+cubicsuperpath.py
+
+Copyright (C) 2005 Aaron Spike, aaron@ekips.org
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""
+import simplepath 
+from math import *
+
+def matprod(mlist):
+    prod=mlist[0]
+    for m in mlist[1:]:
+        a00=prod[0][0]*m[0][0]+prod[0][1]*m[1][0]
+        a01=prod[0][0]*m[0][1]+prod[0][1]*m[1][1]
+        a10=prod[1][0]*m[0][0]+prod[1][1]*m[1][0]
+        a11=prod[1][0]*m[0][1]+prod[1][1]*m[1][1]
+        prod=[[a00,a01],[a10,a11]]
+    return prod
+def rotmat(teta):
+    return [[cos(teta),-sin(teta)],[sin(teta),cos(teta)]]
+def applymat(mat, pt):
+    x=mat[0][0]*pt[0]+mat[0][1]*pt[1]
+    y=mat[1][0]*pt[0]+mat[1][1]*pt[1]
+    pt[0]=x
+    pt[1]=y
+def norm(pt):
+    return sqrt(pt[0]*pt[0]+pt[1]*pt[1])
+
+def ArcToPath(p1,params):
+    A=p1[:]
+    rx,ry,teta,longflag,sweepflag,x2,y2=params[:]
+    teta = teta*pi/180.0
+    B=[x2,y2]
+    if rx==0 or ry==0:
+        return([[A,A,A],[B,B,B]])
+    mat=matprod((rotmat(teta),[[1/rx,0],[0,1/ry]],rotmat(-teta)))
+    applymat(mat, A)
+    applymat(mat, B)
+    k=[-(B[1]-A[1]),B[0]-A[0]]
+    d=k[0]*k[0]+k[1]*k[1]
+    k[0]/=sqrt(d)
+    k[1]/=sqrt(d)
+    d=sqrt(max(0,1-d/4))
+    if longflag==sweepflag:
+        d*=-1
+    O=[(B[0]+A[0])/2+d*k[0],(B[1]+A[1])/2+d*k[1]]
+    OA=[A[0]-O[0],A[1]-O[1]]
+    OB=[B[0]-O[0],B[1]-O[1]]
+    start=acos(OA[0]/norm(OA))
+    if OA[1]<0:
+        start*=-1
+    end=acos(OB[0]/norm(OB))
+    if OB[1]<0:
+        end*=-1
+
+    if sweepflag and start>end:
+        end +=2*pi
+    if (not sweepflag) and start<end:
+        end -=2*pi
+
+    NbSectors=int(abs(start-end)*2/pi)+1
+    dTeta=(end-start)/NbSectors
+    #v=dTeta*2/pi*0.552
+    #v=dTeta*2/pi*4*(sqrt(2)-1)/3
+    v = 4*tan(dTeta/4)/3
+    #if not sweepflag:
+    #    v*=-1
+    p=[]
+    for i in range(0,NbSectors+1,1):
+        angle=start+i*dTeta
+        v1=[O[0]+cos(angle)-(-v)*sin(angle),O[1]+sin(angle)+(-v)*cos(angle)]
+        pt=[O[0]+cos(angle)                ,O[1]+sin(angle)                ]
+        v2=[O[0]+cos(angle)-  v *sin(angle),O[1]+sin(angle)+  v *cos(angle)]
+        p.append([v1,pt,v2])
+    p[ 0][0]=p[ 0][1][:]
+    p[-1][2]=p[-1][1][:]
+
+    mat=matprod((rotmat(teta),[[rx,0],[0,ry]],rotmat(-teta)))
+    for pts in p:
+        applymat(mat, pts[0])
+        applymat(mat, pts[1])
+        applymat(mat, pts[2])
+    return(p)
+    
+def CubicSuperPath(simplepath):
+    csp = []
+    subpath = -1
+    subpathstart = []
+    last = []
+    lastctrl = []
+    for s in simplepath:
+        cmd, params = s        
+        if cmd == 'M':
+            if last:
+                csp[subpath].append([lastctrl[:],last[:],last[:]])
+            subpath += 1
+            csp.append([])
+            subpathstart =  params[:]
+            last = params[:]
+            lastctrl = params[:]
+        elif cmd == 'L':
+            csp[subpath].append([lastctrl[:],last[:],last[:]])
+            last = params[:]
+            lastctrl = params[:]
+        elif cmd == 'C':
+            csp[subpath].append([lastctrl[:],last[:],params[:2]])
+            last = params[-2:]
+            lastctrl = params[2:4]
+        elif cmd == 'Q':
+            q0=last[:]
+            q1=params[0:2]
+            q2=params[2:4]
+            x0=     q0[0]
+            x1=1./3*q0[0]+2./3*q1[0]
+            x2=           2./3*q1[0]+1./3*q2[0]
+            x3=                           q2[0]
+            y0=     q0[1]
+            y1=1./3*q0[1]+2./3*q1[1]
+            y2=           2./3*q1[1]+1./3*q2[1]
+            y3=                           q2[1]
+            csp[subpath].append([lastctrl[:],[x0,y0],[x1,y1]])
+            last = [x3,y3]
+            lastctrl = [x2,y2]
+        elif cmd == 'A':
+            arcp=ArcToPath(last[:],params[:])
+            arcp[ 0][0]=lastctrl[:]
+            last=arcp[-1][1]
+            lastctrl = arcp[-1][0]
+            csp[subpath]+=arcp[:-1]
+        elif cmd == 'Z':
+            csp[subpath].append([lastctrl[:],last[:],last[:]])
+            last = subpathstart[:]
+            lastctrl = subpathstart[:]
+    #append final superpoint
+    csp[subpath].append([lastctrl[:],last[:],last[:]])
+    return csp    
+
+def unCubicSuperPath(csp):
+    a = []
+    for subpath in csp:
+        if subpath:
+            a.append(['M',subpath[0][1][:]])
+            for i in range(1,len(subpath)):
+                a.append(['C',subpath[i-1][2][:] + subpath[i][0][:] + subpath[i][1][:]])
+    return a
+
+def parsePath(d):
+    return CubicSuperPath(simplepath.parsePath(d))
+
+def formatPath(p):
+    return simplepath.formatPath(unCubicSuperPath(p))
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
diff --git a/support/dxf_export/dxf_templates.py b/support/dxf_export/dxf_templates.py
new file mode 100755
index 0000000..fb26632
--- /dev/null
+++ b/support/dxf_export/dxf_templates.py
@@ -0,0 +1,645 @@
+r14_header = '''  0
+SECTION
+  2
+HEADER
+  9
+$ACADVER
+  1
+AC1014
+  9
+$HANDSEED
+  5
+FFFF
+  0
+ENDSEC
+  0
+SECTION
+  2
+TABLES
+  0
+TABLE
+  2
+VPORT
+  5
+8
+330
+0
+100
+AcDbSymbolTable
+ 70
+     4
+  0
+VPORT
+  5
+2E
+330
+8
+100
+AcDbSymbolTableRecord
+100
+AcDbViewportTableRecord
+  2
+*ACTIVE
+ 70
+     0
+ 10
+0.0
+ 20
+0.0
+ 11
+1.0
+ 21
+1.0
+ 12
+4.25
+ 22
+5.5
+ 13
+0.0
+ 23
+0.0
+ 14
+10.0
+ 24
+10.0
+ 15
+10.0
+ 25
+10.0
+ 16
+0.0
+ 26
+0.0
+ 36
+1.0
+ 17
+0.0
+ 27
+0.0
+ 37
+0.0
+ 40
+11
+ 41
+1.24
+ 42
+50.0
+ 43
+0.0
+ 44
+0.0
+ 50
+0.0
+ 51
+0.0
+ 71
+     0
+ 72
+   100
+ 73
+     1
+ 74
+     3
+ 75
+     0
+ 76
+     0
+ 77
+     0
+ 78
+     0
+  0
+ENDTAB
+  0
+TABLE
+  2
+LTYPE
+  5
+5
+330
+0
+100
+AcDbSymbolTable
+ 70
+     1
+  0
+LTYPE
+  5
+14
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+  2
+BYBLOCK
+ 70
+     0
+  3
+
+ 72
+    65
+ 73
+     0
+ 40
+0.0
+  0
+LTYPE
+  5
+15
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+  2
+BYLAYER
+ 70
+     0
+  3
+
+ 72
+    65
+ 73
+     0
+ 40
+0.0
+  0
+LTYPE
+  5
+16
+330
+5
+100
+AcDbSymbolTableRecord
+100
+AcDbLinetypeTableRecord
+  2
+CONTINUOUS
+ 70
+     0
+  3
+Solid line
+ 72
+    65
+ 73
+     0
+ 40
+0.0
+  0
+ENDTAB
+  0
+TABLE
+  2
+LAYER
+  5
+2
+330
+0
+100
+AcDbSymbolTable
+ 70
+1
+  0
+LAYER
+  5
+10
+330
+2
+100
+AcDbSymbolTableRecord
+100
+AcDbLayerTableRecord
+  2
+0
+ 70
+     0
+ 62
+     7
+  6
+CONTINUOUS
+  0
+ENDTAB
+  0
+TABLE
+  2
+STYLE
+  5
+3
+330
+0
+100
+AcDbSymbolTable
+ 70
+     1
+  0
+STYLE
+  5
+11
+330
+3
+100
+AcDbSymbolTableRecord
+100
+AcDbTextStyleTableRecord
+  2
+STANDARD
+ 70
+     0
+ 40
+0.0
+ 41
+1.0
+ 50
+0.0
+ 71
+     0
+ 42
+2.5
+  3
+txt
+  4
+
+  0
+ENDTAB
+  0
+TABLE
+  2
+VIEW
+  5
+6
+330
+0
+100
+AcDbSymbolTable
+ 70
+     0
+  0
+ENDTAB
+  0
+TABLE
+  2
+UCS
+  5
+7
+330
+0
+100
+AcDbSymbolTable
+ 70
+     0
+  0
+ENDTAB
+  0
+TABLE
+  2
+APPID
+  5
+9
+330
+0
+100
+AcDbSymbolTable
+ 70
+     2
+  0
+APPID
+  5
+12
+330
+9
+100
+AcDbSymbolTableRecord
+100
+AcDbRegAppTableRecord
+  2
+ACAD
+ 70
+     0
+  0
+ENDTAB
+  0
+TABLE
+  2
+DIMSTYLE
+  5
+A
+330
+0
+100
+AcDbSymbolTable
+ 70
+     1
+  0
+DIMSTYLE
+105
+27
+330
+A
+100
+AcDbSymbolTableRecord
+100
+AcDbDimStyleTableRecord
+  2
+ISO-25
+ 70
+     0
+  3
+
+  4
+
+  5
+
+  6
+
+  7
+
+ 40
+1.0
+ 41
+2.5
+ 42
+0.625
+ 43
+3.75
+ 44
+1.25
+ 45
+0.0
+ 46
+0.0
+ 47
+0.0
+ 48
+0.0
+140
+2.5
+141
+2.5
+142
+0.0
+143
+0.03937007874016
+144
+1.0
+145
+0.0
+146
+1.0
+147
+0.625
+ 71
+     0
+ 72
+     0
+ 73
+     0
+ 74
+     0
+ 75
+     0
+ 76
+     0
+ 77
+     1
+ 78
+     8
+170
+     0
+171
+     3
+172
+     1
+173
+     0
+174
+     0
+175
+     0
+176
+     0
+177
+     0
+178
+     0
+270
+     2
+271
+     2
+272
+     2
+273
+     2
+274
+     3
+340
+11
+275
+     0
+280
+     0
+281
+     0
+282
+     0
+283
+     0
+284
+     8
+285
+     0
+286
+     0
+287
+     3
+288
+     0
+  0
+ENDTAB
+  0
+TABLE
+  2
+BLOCK_RECORD
+  5
+1
+330
+0
+100
+AcDbSymbolTable
+ 70
+     1
+  0
+BLOCK_RECORD
+  5
+1F
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+  2
+*MODEL_SPACE
+  0
+BLOCK_RECORD
+  5
+1B
+330
+1
+100
+AcDbSymbolTableRecord
+100
+AcDbBlockTableRecord
+  2
+*PAPER_SPACE
+  0
+ENDTAB
+  0
+ENDSEC
+  0
+SECTION
+  2
+BLOCKS
+  0
+BLOCK
+  5
+20
+330
+1F
+100
+AcDbEntity
+  8
+0
+100
+AcDbBlockBegin
+  2
+*MODEL_SPACE
+ 70
+     0
+ 10
+0.0
+ 20
+0.0
+ 30
+0.0
+  3
+*MODEL_SPACE
+  1
+
+  0
+ENDBLK
+  5
+21
+330
+1F
+100
+AcDbEntity
+  8
+0
+100
+AcDbBlockEnd
+  0
+BLOCK
+  5
+1C
+330
+1B
+100
+AcDbEntity
+ 67
+     1
+  8
+0
+100
+AcDbBlockBegin
+  2
+*PAPER_SPACE
+  1
+
+  0
+ENDBLK
+  5
+1D
+330
+1B
+100
+AcDbEntity
+ 67
+     1
+  8
+0
+100
+AcDbBlockEnd
+  0
+ENDSEC
+  0
+SECTION
+  2
+ENTITIES
+'''
+
+
+r14_footer = '''  0
+ENDSEC
+  0
+SECTION
+  2
+OBJECTS
+  0
+DICTIONARY
+  5
+C
+330
+0
+100
+AcDbDictionary
+  3
+ACAD_GROUP
+350
+D
+  3
+ACAD_MLINESTYLE
+350
+17
+  0
+DICTIONARY
+  5
+D
+330
+C
+100
+AcDbDictionary
+  0
+DICTIONARY
+  5
+1A
+330
+C
+100
+AcDbDictionary
+  0
+DICTIONARY
+  5
+17
+330
+C
+100
+AcDbDictionary
+  3
+STANDARD
+350
+18
+  0
+DICTIONARY
+  5
+19
+330
+C
+100
+AcDbDictionary
+  0
+ENDSEC
+  0
+EOF'''
diff --git a/support/dxf_export/ffgeom.py b/support/dxf_export/ffgeom.py
new file mode 100755
index 0000000..1983586
--- /dev/null
+++ b/support/dxf_export/ffgeom.py
@@ -0,0 +1,141 @@
+#!/usr/bin/env python
+"""
+    ffgeom.py
+    Copyright (C) 2005 Aaron Cyril Spike, aaron@ekips.org
+
+    This file is part of FretFind 2-D.
+
+    FretFind 2-D is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    FretFind 2-D is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with FretFind 2-D; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+"""
+import math
+try:
+    NaN = float('NaN')
+except ValueError:
+    PosInf = 1e300000
+    NaN = PosInf/PosInf
+
+class Point:
+    precision = 5
+    def __init__(self, x, y):
+        self.__coordinates = {'x' : float(x), 'y' : float(y)}
+    def __getitem__(self, key):
+        return self.__coordinates[key]
+    def __setitem__(self, key, value):
+        self.__coordinates[key] = float(value)
+    def __repr__(self):
+        return '(%s, %s)' % (round(self['x'],self.precision),round(self['y'],self.precision))
+    def copy(self):
+        return Point(self['x'],self['y'])
+    def translate(self, x, y):
+        self['x'] += x
+        self['y'] += y
+    def move(self, x, y):
+        self['x'] = float(x)
+        self['y'] = float(y)
+
+class Segment:
+    def __init__(self, e0, e1):
+        self.__endpoints = [e0, e1]
+    def __getitem__(self, key):
+        return self.__endpoints[key]
+    def __setitem__(self, key, value):
+        self.__endpoints[key] = value
+    def __repr__(self):
+        return repr(self.__endpoints)
+    def copy(self):
+        return Segment(self[0],self[1])
+    def translate(self, x, y):
+        self[0].translate(x,y)
+        self[1].translate(x,y)
+    def move(self,e0,e1):
+        self[0] = e0
+        self[1] = e1
+    def delta_x(self):
+        return self[1]['x'] - self[0]['x']
+    def delta_y(self):
+        return self[1]['y'] - self[0]['y']
+    #alias functions
+    run = delta_x
+    rise = delta_y
+    def slope(self):
+        if self.delta_x() != 0:
+            return self.delta_x() / self.delta_y()
+        return NaN
+    def intercept(self):
+        if self.delta_x() != 0:
+            return self[1]['y'] - (self[0]['x'] * self.slope())
+        return NaN
+    def distanceToPoint(self, p):
+        s2 = Segment(self[0],p)
+        c1 = dot(s2,self)
+        if c1 <= 0:
+            return Segment(p,self[0]).length()
+        c2 = dot(self,self)
+        if c2 <= c1:
+            return Segment(p,self[1]).length()
+        return self.perpDistanceToPoint(p)
+    def perpDistanceToPoint(self, p):
+        len = self.length()
+        if len == 0: return NaN
+        return math.fabs(((self[1]['x'] - self[0]['x']) * (self[0]['y'] - p['y'])) - \
+            ((self[0]['x'] - p['x']) * (self[1]['y'] - self[0]['y']))) / len
+    def angle(self):
+        return math.pi * (math.atan2(self.delta_y(), self.delta_x())) / 180
+    def length(self):
+        return math.sqrt((self.delta_x() ** 2) + (self.delta_y() ** 2))
+    def pointAtLength(self, len):
+        if self.length() == 0: return Point(NaN, NaN)
+        ratio = len / self.length()
+        x = self[0]['x'] + (ratio * self.delta_x())
+        y = self[0]['y'] + (ratio * self.delta_y())
+        return Point(x, y)
+    def pointAtRatio(self, ratio):
+        if self.length() == 0: return Point(NaN, NaN)
+        x = self[0]['x'] + (ratio * self.delta_x())
+        y = self[0]['y'] + (ratio * self.delta_y())
+        return Point(x, y)
+    def createParallel(self, p):
+        return Segment(Point(p['x'] + self.delta_x(), p['y'] + self.delta_y()), p)
+    def intersect(self, s):
+        return intersectSegments(self, s)
+
+def intersectSegments(s1, s2):
+    x1 = s1[0]['x']
+    x2 = s1[1]['x']
+    x3 = s2[0]['x']
+    x4 = s2[1]['x']
+    
+    y1 = s1[0]['y']
+    y2 = s1[1]['y']
+    y3 = s2[0]['y']
+    y4 = s2[1]['y']
+    
+    denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1))
+    num1 = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3))
+    num2 = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3))
+
+    num = num1
+
+    if denom != 0: 
+        x = x1 + ((num / denom) * (x2 - x1))
+        y = y1 + ((num / denom) * (y2 - y1))
+        return Point(x, y)
+    return Point(NaN, NaN)
+
+def dot(s1, s2):
+    return s1.delta_x() * s2.delta_x() + s1.delta_y() * s2.delta_y()
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
diff --git a/support/dxf_export/inkex.py b/support/dxf_export/inkex.py
new file mode 100755
index 0000000..e487822
--- /dev/null
+++ b/support/dxf_export/inkex.py
@@ -0,0 +1,241 @@
+#!/usr/bin/env python
+"""
+inkex.py
+A helper module for creating Inkscape extensions
+
+Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+"""
+import sys, copy, optparse, random, re
+import gettext
+from math import *
+_ = gettext.gettext
+
+#a dictionary of all of the xmlns prefixes in a standard inkscape doc
+NSS = {
+u'sodipodi' :u'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
+u'cc'       :u'http://creativecommons.org/ns#',
+u'ccOLD'    :u'http://web.resource.org/cc/',
+u'svg'      :u'http://www.w3.org/2000/svg',
+u'dc'       :u'http://purl.org/dc/elements/1.1/',
+u'rdf'      :u'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+u'inkscape' :u'http://www.inkscape.org/namespaces/inkscape',
+u'xlink'    :u'http://www.w3.org/1999/xlink',
+u'xml'      :u'http://www.w3.org/XML/1998/namespace'
+}
+
+#a dictionary of unit to user unit conversion factors
+uuconv = {'in':90.0, 'pt':1.25, 'px':1, 'mm':3.5433070866, 'cm':35.433070866, 'm':3543.3070866,
+          'km':3543307.0866, 'pc':15.0, 'yd':3240 , 'ft':1080}
+def unittouu(string):
+    '''Returns userunits given a string representation of units in another system'''
+    unit = re.compile('(%s)$' % '|'.join(uuconv.keys()))
+    param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
+
+    p = param.match(string)
+    u = unit.search(string)    
+    if p:
+        retval = float(p.string[p.start():p.end()])
+    else:
+        retval = 0.0
+    if u:
+        try:
+            return retval * uuconv[u.string[u.start():u.end()]]
+        except KeyError:
+            pass
+    return retval
+
+def uutounit(val, unit):
+    return val/uuconv[unit]
+
+try:
+    from lxml import etree
+except:
+    sys.exit(_('The fantastic lxml wrapper for libxml2 is required by inkex.py and therefore this extension. Please download and install the latest version from http://cheeseshop.python.org/pypi/lxml/, or install it through your package manager by a command like: sudo apt-get install python-lxml'))
+
+def debug(what):
+    sys.stderr.write(str(what) + "\n")
+    return what
+
+def errormsg(msg):
+    """Intended for end-user-visible error messages.
+    
+       (Currently just writes to stderr with an appended newline, but could do
+       something better in future: e.g. could add markup to distinguish error
+       messages from status messages or debugging output.)
+      
+       Note that this should always be combined with translation:
+
+         import gettext
+         _ = gettext.gettext
+         ...
+         inkex.errormsg(_("This extension requires two selected paths."))
+    """
+    sys.stderr.write((unicode(msg) + "\n").encode("UTF-8"))
+
+def check_inkbool(option, opt, value):
+    if str(value).capitalize() == 'True':
+        return True
+    elif str(value).capitalize() == 'False':
+        return False
+    else:
+        raise optparse.OptionValueError("option %s: invalid inkbool value: %s" % (opt, value))
+
+def addNS(tag, ns=None):
+    val = tag
+    if ns!=None and len(ns)>0 and NSS.has_key(ns) and len(tag)>0 and tag[0]!='{':
+        val = "{%s}%s" % (NSS[ns], tag)
+    return val
+
+class InkOption(optparse.Option):
+    TYPES = optparse.Option.TYPES + ("inkbool",)
+    TYPE_CHECKER = copy.copy(optparse.Option.TYPE_CHECKER)
+    TYPE_CHECKER["inkbool"] = check_inkbool
+
+class Effect:
+    """A class for creating Inkscape SVG Effects"""
+
+    def __init__(self, *args, **kwargs):
+        self.document=None
+        self.original_document=None
+        self.ctx=None
+        self.selected={}
+        self.doc_ids={}
+        self.options=None
+        self.args=None
+        self.OptionParser = optparse.OptionParser(usage="usage: %prog [options] SVGfile",option_class=InkOption)
+        self.OptionParser.add_option("--id",
+                        action="append", type="string", dest="ids", default=[], 
+                        help="id attribute of object to manipulate")
+
+    def effect(self):
+        pass
+
+    def getoptions(self,args=sys.argv[1:]):
+        """Collect command line arguments"""
+        self.options, self.args = self.OptionParser.parse_args(args)
+
+    def parse(self,file=None):
+        """Parse document in specified file or on stdin"""
+        try:
+            try:
+                stream = open(file,'r')
+            except:
+                stream = open(self.svg_file,'r')
+        except:
+            stream = sys.stdin
+        p = etree.XMLParser(huge_tree=True)
+        self.document = etree.parse(stream, parser=p)
+        self.original_document = copy.deepcopy(self.document)
+        stream.close()
+
+    def getposinlayer(self):
+        #defaults
+        self.current_layer = self.document.getroot()
+        self.view_center = (0.0,0.0)
+
+        layerattr = self.document.xpath('//sodipodi:namedview/@inkscape:current-layer', namespaces=NSS)
+        if layerattr:
+            layername = layerattr[0]
+            layer = self.document.xpath('//svg:g[@id="%s"]' % layername, namespaces=NSS)
+            if layer:
+                self.current_layer = layer[0]
+
+        xattr = self.document.xpath('//sodipodi:namedview/@inkscape:cx', namespaces=NSS)
+        yattr = self.document.xpath('//sodipodi:namedview/@inkscape:cy', namespaces=NSS)
+        doc_height = unittouu(self.document.getroot().get('height'))
+        if xattr and yattr:
+            x = xattr[0]
+            y = yattr[0]
+            if x and y:
+                self.view_center = (float(x), doc_height - float(y)) # FIXME: y-coordinate flip, eliminate it when it's gone in Inkscape
+
+    def getselected(self):
+        """Collect selected nodes"""
+        for i in self.options.ids:
+            path = '//*[@id="%s"]' % i
+            for node in self.document.xpath(path, namespaces=NSS):
+                self.selected[i] = node
+
+    def getElementById(self, id):
+        path = '//*[@id="%s"]' % id
+        el_list = self.document.xpath(path, namespaces=NSS)
+        if el_list:
+          return el_list[0]
+        else:
+          return None
+
+    def getParentNode(self, node):
+        for parent in self.document.getiterator():
+            if node in parent.getchildren():
+                return parent
+                break
+
+
+    def getdocids(self):
+        docIdNodes = self.document.xpath('//@id', namespaces=NSS)
+        for m in docIdNodes:
+            self.doc_ids[m] = 1
+
+    def getNamedView(self):
+        return self.document.xpath('//sodipodi:namedview', namespaces=NSS)[0]
+
+    def createGuide(self, posX, posY, angle):
+        atts = {
+          'position': str(posX)+','+str(posY),
+          'orientation': str(sin(radians(angle)))+','+str(-cos(radians(angle)))
+          }
+        guide = etree.SubElement(
+                  self.getNamedView(),
+                  addNS('guide','sodipodi'), atts )
+        return guide
+
+    def output(self):
+        """Serialize document into XML on stdout"""
+        original = etree.tostring(self.original_document)        
+        result = etree.tostring(self.document)        
+        if original != result:
+            self.document.write(sys.stdout)
+
+    def affect(self, args=sys.argv[1:], output=True):
+        """Affect an SVG document with a callback effect"""
+        self.svg_file = args[-1]
+        self.getoptions(args)
+        self.parse()
+        self.getposinlayer()
+        self.getselected()
+        self.getdocids()
+        self.effect()
+        if output: self.output()
+
+    def uniqueId(self, old_id, make_new_id = True):
+        new_id = old_id
+        if make_new_id:
+            while new_id in self.doc_ids:
+                new_id += random.choice('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
+            self.doc_ids[new_id] = 1
+        return new_id
+
+    def xpathSingle(self, path):
+        try:
+            retval = self.document.xpath(path, namespaces=NSS)[0]
+        except:
+            errormsg(_("No matching node for expression: %s") % path)
+            retval = None
+        return retval
+            
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
diff --git a/support/dxf_export/simplepath.py b/support/dxf_export/simplepath.py
new file mode 100755
index 0000000..f62b1b4
--- /dev/null
+++ b/support/dxf_export/simplepath.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python
+"""
+simplepath.py
+functions for digesting paths into a simple list structure
+
+Copyright (C) 2005 Aaron Spike, aaron@ekips.org
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""
+import re, math
+
+def lexPath(d):
+    """
+    returns and iterator that breaks path data 
+    identifies command and parameter tokens
+    """
+    offset = 0
+    length = len(d)
+    delim = re.compile(r'[ \t\r\n,]+')
+    command = re.compile(r'[MLHVCSQTAZmlhvcsqtaz]')
+    parameter = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
+    while 1:
+        m = delim.match(d, offset)
+        if m:
+            offset = m.end()
+        if offset >= length:
+            break
+        m = command.match(d, offset)
+        if m:
+            yield [d[offset:m.end()], True]
+            offset = m.end()
+            continue
+        m = parameter.match(d, offset)
+        if m:
+            yield [d[offset:m.end()], False]
+            offset = m.end()
+            continue
+        #TODO: create new exception
+        raise Exception, 'Invalid path data!'
+'''
+pathdefs = {commandfamily:
+    [
+    implicitnext,
+    #params,
+    [casts,cast,cast],
+    [coord type,x,y,0]
+    ]}
+'''
+pathdefs = {
+    'M':['L', 2, [float, float], ['x','y']], 
+    'L':['L', 2, [float, float], ['x','y']], 
+    'H':['H', 1, [float], ['x']], 
+    'V':['V', 1, [float], ['y']], 
+    'C':['C', 6, [float, float, float, float, float, float], ['x','y','x','y','x','y']], 
+    'S':['S', 4, [float, float, float, float], ['x','y','x','y']], 
+    'Q':['Q', 4, [float, float, float, float], ['x','y','x','y']], 
+    'T':['T', 2, [float, float], ['x','y']], 
+    'A':['A', 7, [float, float, float, int, int, float, float], ['r','r','a',0,'s','x','y']], 
+    'Z':['L', 0, [], []]
+    }
+def parsePath(d):
+    """
+    Parse SVG path and return an array of segments.
+    Removes all shorthand notation.
+    Converts coordinates to absolute.
+    """
+    retval = []
+    lexer = lexPath(d)
+
+    pen = (0.0,0.0)
+    subPathStart = pen
+    lastControl = pen
+    lastCommand = ''
+    
+    while 1:
+        try:
+            token, isCommand = lexer.next()
+        except StopIteration:
+            break
+        params = []
+        needParam = True
+        if isCommand:
+            if not lastCommand and token.upper() != 'M':
+                raise Exception, 'Invalid path, must begin with moveto.'    
+            else:                
+                command = token
+        else:
+            #command was omited
+            #use last command's implicit next command
+            needParam = False
+            if lastCommand:
+                if lastCommand.isupper():
+                    command = pathdefs[lastCommand][0]
+                else:
+                    command = pathdefs[lastCommand.upper()][0].lower()
+            else:
+                raise Exception, 'Invalid path, no initial command.'    
+        numParams = pathdefs[command.upper()][1]
+        while numParams > 0:
+            if needParam:
+                try: 
+                    token, isCommand = lexer.next()
+                    if isCommand:
+                        raise Exception, 'Invalid number of parameters'
+                except StopIteration:
+                    raise Exception, 'Unexpected end of path'
+            cast = pathdefs[command.upper()][2][-numParams]
+            param = cast(token)
+            if command.islower():
+                if pathdefs[command.upper()][3][-numParams]=='x':
+                    param += pen[0]
+                elif pathdefs[command.upper()][3][-numParams]=='y':
+                    param += pen[1]
+            params.append(param)
+            needParam = True
+            numParams -= 1
+        #segment is now absolute so
+        outputCommand = command.upper()
+    
+        #Flesh out shortcut notation    
+        if outputCommand in ('H','V'):
+            if outputCommand == 'H':
+                params.append(pen[1])
+            if outputCommand == 'V':
+                params.insert(0,pen[0])
+            outputCommand = 'L'
+        if outputCommand in ('S','T'):
+            params.insert(0,pen[1]+(pen[1]-lastControl[1]))
+            params.insert(0,pen[0]+(pen[0]-lastControl[0]))
+            if outputCommand == 'S':
+                outputCommand = 'C'
+            if outputCommand == 'T':
+                outputCommand = 'Q'
+
+        #current values become "last" values
+        if outputCommand == 'M':
+            subPathStart = tuple(params[0:2])
+            pen = subPathStart
+        if outputCommand == 'Z':
+            pen = subPathStart
+        else:
+            pen = tuple(params[-2:])
+
+        if outputCommand in ('Q','C'):
+            lastControl = tuple(params[-4:-2])
+        else:
+            lastControl = pen
+        lastCommand = command
+
+        retval.append([outputCommand,params])
+    return retval
+
+def formatPath(a):
+    """Format SVG path data from an array"""
+    return "".join([cmd + " ".join([str(p) for p in params]) for cmd, params in a])
+
+def translatePath(p, x, y):
+    for cmd,params in p:
+        defs = pathdefs[cmd]
+        for i in range(defs[1]):
+            if defs[3][i] == 'x':
+                params[i] += x
+            elif defs[3][i] == 'y':
+                params[i] += y
+
+def scalePath(p, x, y):
+    for cmd,params in p:
+        defs = pathdefs[cmd]
+        for i in range(defs[1]):
+            if defs[3][i] == 'x':
+                params[i] *= x
+            elif defs[3][i] == 'y':
+                params[i] *= y
+            elif defs[3][i] == 'r':         # radius parameter
+                params[i] *= x
+            elif defs[3][i] == 's':         # sweep-flag parameter
+                if x*y < 0:
+                    params[i] = 1 - params[i]
+            elif defs[3][i] == 'a':         # x-axis-rotation angle
+                if y < 0:
+                    params[i] = - params[i]
+
+def rotatePath(p, a, cx = 0, cy = 0):
+    if a == 0:
+        return p
+    for cmd,params in p:
+        defs = pathdefs[cmd]
+        for i in range(defs[1]):
+            if defs[3][i] == 'x':
+                x = params[i] - cx
+                y = params[i + 1] - cy
+                r = math.sqrt((x**2) + (y**2))
+                if r != 0:
+                    theta = math.atan2(y, x) + a
+                    params[i] = (r * math.cos(theta)) + cx
+                    params[i + 1] = (r * math.sin(theta)) + cy
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
diff --git a/support/dxf_export/simplestyle.py b/support/dxf_export/simplestyle.py
new file mode 100755
index 0000000..3ec971e
--- /dev/null
+++ b/support/dxf_export/simplestyle.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python
+"""
+simplestyle.py
+Two simple functions for working with inline css
+and some color handling on top.
+
+Copyright (C) 2005 Aaron Spike, aaron@ekips.org
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+"""
+
+svgcolors={
+    'aliceblue':'#f0f8ff',
+    'antiquewhite':'#faebd7',
+    'aqua':'#00ffff',
+    'aquamarine':'#7fffd4',
+    'azure':'#f0ffff',
+    'beige':'#f5f5dc',
+    'bisque':'#ffe4c4',
+    'black':'#000000',
+    'blanchedalmond':'#ffebcd',
+    'blue':'#0000ff',
+    'blueviolet':'#8a2be2',
+    'brown':'#a52a2a',
+    'burlywood':'#deb887',
+    'cadetblue':'#5f9ea0',
+    'chartreuse':'#7fff00',
+    'chocolate':'#d2691e',
+    'coral':'#ff7f50',
+    'cornflowerblue':'#6495ed',
+    'cornsilk':'#fff8dc',
+    'crimson':'#dc143c',
+    'cyan':'#00ffff',
+    'darkblue':'#00008b',
+    'darkcyan':'#008b8b',
+    'darkgoldenrod':'#b8860b',
+    'darkgray':'#a9a9a9',
+    'darkgreen':'#006400',
+    'darkgrey':'#a9a9a9',
+    'darkkhaki':'#bdb76b',
+    'darkmagenta':'#8b008b',
+    'darkolivegreen':'#556b2f',
+    'darkorange':'#ff8c00',
+    'darkorchid':'#9932cc',
+    'darkred':'#8b0000',
+    'darksalmon':'#e9967a',
+    'darkseagreen':'#8fbc8f',
+    'darkslateblue':'#483d8b',
+    'darkslategray':'#2f4f4f',
+    'darkslategrey':'#2f4f4f',
+    'darkturquoise':'#00ced1',
+    'darkviolet':'#9400d3',
+    'deeppink':'#ff1493',
+    'deepskyblue':'#00bfff',
+    'dimgray':'#696969',
+    'dimgrey':'#696969',
+    'dodgerblue':'#1e90ff',
+    'firebrick':'#b22222',
+    'floralwhite':'#fffaf0',
+    'forestgreen':'#228b22',
+    'fuchsia':'#ff00ff',
+    'gainsboro':'#dcdcdc',
+    'ghostwhite':'#f8f8ff',
+    'gold':'#ffd700',
+    'goldenrod':'#daa520',
+    'gray':'#808080',
+    'grey':'#808080',
+    'green':'#008000',
+    'greenyellow':'#adff2f',
+    'honeydew':'#f0fff0',
+    'hotpink':'#ff69b4',
+    'indianred':'#cd5c5c',
+    'indigo':'#4b0082',
+    'ivory':'#fffff0',
+    'khaki':'#f0e68c',
+    'lavender':'#e6e6fa',
+    'lavenderblush':'#fff0f5',
+    'lawngreen':'#7cfc00',
+    'lemonchiffon':'#fffacd',
+    'lightblue':'#add8e6',
+    'lightcoral':'#f08080',
+    'lightcyan':'#e0ffff',
+    'lightgoldenrodyellow':'#fafad2',
+    'lightgray':'#d3d3d3',
+    'lightgreen':'#90ee90',
+    'lightgrey':'#d3d3d3',
+    'lightpink':'#ffb6c1',
+    'lightsalmon':'#ffa07a',
+    'lightseagreen':'#20b2aa',
+    'lightskyblue':'#87cefa',
+    'lightslategray':'#778899',
+    'lightslategrey':'#778899',
+    'lightsteelblue':'#b0c4de',
+    'lightyellow':'#ffffe0',
+    'lime':'#00ff00',
+    'limegreen':'#32cd32',
+    'linen':'#faf0e6',
+    'magenta':'#ff00ff',
+    'maroon':'#800000',
+    'mediumaquamarine':'#66cdaa',
+    'mediumblue':'#0000cd',
+    'mediumorchid':'#ba55d3',
+    'mediumpurple':'#9370db',
+    'mediumseagreen':'#3cb371',
+    'mediumslateblue':'#7b68ee',
+    'mediumspringgreen':'#00fa9a',
+    'mediumturquoise':'#48d1cc',
+    'mediumvioletred':'#c71585',
+    'midnightblue':'#191970',
+    'mintcream':'#f5fffa',
+    'mistyrose':'#ffe4e1',
+    'moccasin':'#ffe4b5',
+    'navajowhite':'#ffdead',
+    'navy':'#000080',
+    'oldlace':'#fdf5e6',
+    'olive':'#808000',
+    'olivedrab':'#6b8e23',
+    'orange':'#ffa500',
+    'orangered':'#ff4500',
+    'orchid':'#da70d6',
+    'palegoldenrod':'#eee8aa',
+    'palegreen':'#98fb98',
+    'paleturquoise':'#afeeee',
+    'palevioletred':'#db7093',
+    'papayawhip':'#ffefd5',
+    'peachpuff':'#ffdab9',
+    'peru':'#cd853f',
+    'pink':'#ffc0cb',
+    'plum':'#dda0dd',
+    'powderblue':'#b0e0e6',
+    'purple':'#800080',
+    'red':'#ff0000',
+    'rosybrown':'#bc8f8f',
+    'royalblue':'#4169e1',
+    'saddlebrown':'#8b4513',
+    'salmon':'#fa8072',
+    'sandybrown':'#f4a460',
+    'seagreen':'#2e8b57',
+    'seashell':'#fff5ee',
+    'sienna':'#a0522d',
+    'silver':'#c0c0c0',
+    'skyblue':'#87ceeb',
+    'slateblue':'#6a5acd',
+    'slategray':'#708090',
+    'slategrey':'#708090',
+    'snow':'#fffafa',
+    'springgreen':'#00ff7f',
+    'steelblue':'#4682b4',
+    'tan':'#d2b48c',
+    'teal':'#008080',
+    'thistle':'#d8bfd8',
+    'tomato':'#ff6347',
+    'turquoise':'#40e0d0',
+    'violet':'#ee82ee',
+    'wheat':'#f5deb3',
+    'white':'#ffffff',
+    'whitesmoke':'#f5f5f5',
+    'yellow':'#ffff00',
+    'yellowgreen':'#9acd32'
+}
+
+def parseStyle(s):
+    """Create a dictionary from the value of an inline style attribute"""
+    if s is None:
+      return {}
+    else:
+      return dict([[x.strip() for x in i.split(":")] for i in s.split(";") if len(i.strip())])
+
+def formatStyle(a):
+    """Format an inline style attribute from a dictionary"""
+    return ";".join([att+":"+str(val) for att,val in a.iteritems()])
+
+def isColor(c):
+    """Determine if its a color we can use. If not, leave it unchanged."""
+    if c.startswith('#') and (len(c)==4 or len(c)==7):
+        return True
+    if c.lower() in svgcolors.keys():
+        return True
+    #might be "none" or some undefined color constant or rgb()
+    #however, rgb() shouldnt occur at this point
+    return False
+
+def parseColor(c):
+    """Creates a rgb int array"""
+    tmp = svgcolors.get(c.lower())
+    if tmp is not None:
+        c = tmp
+    elif c.startswith('#') and len(c)==4:
+        c='#'+c[1:2]+c[1:2]+c[2:3]+c[2:3]+c[3:]+c[3:]
+    elif c.startswith('rgb('):
+        # remove the rgb(...) stuff
+        tmp = c.strip()[4:-1]
+        numbers = [number.strip() for number in tmp.split(',')]
+        converted_numbers = []
+        if len(numbers) == 3:
+            for num in numbers:
+                if num.endswith(r'%'):
+                    converted_numbers.append(int(float(num[0:-1])*255/100))
+                else:
+                    converted_numbers.append(int(num))
+            return tuple(converted_numbers)
+        else:    
+            return (0,0,0)
+    try:
+        r=int(c[1:3],16)
+        g=int(c[3:5],16)
+        b=int(c[5:],16)
+    except:
+        # unknown color ...
+        # Return a default color. Maybe not the best thing to do but probably
+        # better than raising an exception. 
+       return(0,0,0)
+    return (r,g,b)
+
+def formatColoria(a):
+    """int array to #rrggbb"""
+    return '#%02x%02x%02x' % (a[0],a[1],a[2])
+
+def formatColorfa(a):
+    """float array to #rrggbb"""
+    return '#%02x%02x%02x' % (int(round(a[0]*255)),int(round(a[1]*255)),int(round(a[2]*255)))
+
+def formatColor3i(r,g,b):
+    """3 ints to #rrggbb"""
+    return '#%02x%02x%02x' % (r,g,b)
+
+def formatColor3f(r,g,b):
+    """3 floats to #rrggbb"""
+    return '#%02x%02x%02x' % (int(round(r*255)),int(round(g*255)),int(round(b*255)))
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
diff --git a/support/dxf_export/simpletransform.py b/support/dxf_export/simpletransform.py
new file mode 100755
index 0000000..47cc61e
--- /dev/null
+++ b/support/dxf_export/simpletransform.py
@@ -0,0 +1,241 @@
+#!/usr/bin/env python
+'''
+Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr
+Copyright (C) 2010 Alvin Penner, penner@vaxxine.com
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+barraud@math.univ-lille1.fr
+
+This code defines several functions to make handling of transform
+attribute easier.
+'''
+import inkex, cubicsuperpath, bezmisc, simplestyle
+import copy, math, re
+
+def parseTransform(transf,mat=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]):
+    if transf=="" or transf==None:
+        return(mat)
+    stransf = transf.strip()
+    result=re.match("(translate|scale|rotate|skewX|skewY|matrix)\s*\(([^)]*)\)\s*,?",stransf)
+#-- translate --
+    if result.group(1)=="translate":
+        args=result.group(2).replace(',',' ').split()
+        dx=float(args[0])
+        if len(args)==1:
+            dy=0.0
+        else:
+            dy=float(args[1])
+        matrix=[[1,0,dx],[0,1,dy]]
+#-- scale --
+    if result.group(1)=="scale":
+        args=result.group(2).replace(',',' ').split()
+        sx=float(args[0])
+        if len(args)==1:
+            sy=sx
+        else:
+            sy=float(args[1])
+        matrix=[[sx,0,0],[0,sy,0]]
+#-- rotate --
+    if result.group(1)=="rotate":
+        args=result.group(2).replace(',',' ').split()
+        a=float(args[0])*math.pi/180
+        if len(args)==1:
+            cx,cy=(0.0,0.0)
+        else:
+            cx,cy=map(float,args[1:])
+        matrix=[[math.cos(a),-math.sin(a),cx],[math.sin(a),math.cos(a),cy]]
+        matrix=composeTransform(matrix,[[1,0,-cx],[0,1,-cy]])
+#-- skewX --
+    if result.group(1)=="skewX":
+        a=float(result.group(2))*math.pi/180
+        matrix=[[1,math.tan(a),0],[0,1,0]]
+#-- skewY --
+    if result.group(1)=="skewY":
+        a=float(result.group(2))*math.pi/180
+        matrix=[[1,0,0],[math.tan(a),1,0]]
+#-- matrix --
+    if result.group(1)=="matrix":
+        a11,a21,a12,a22,v1,v2=result.group(2).replace(',',' ').split()
+        matrix=[[float(a11),float(a12),float(v1)], [float(a21),float(a22),float(v2)]]
+
+    matrix=composeTransform(mat,matrix)
+    if result.end() < len(stransf):
+        return(parseTransform(stransf[result.end():], matrix))
+    else:
+        return matrix
+
+def formatTransform(mat):
+    return ("matrix(%f,%f,%f,%f,%f,%f)" % (mat[0][0], mat[1][0], mat[0][1], mat[1][1], mat[0][2], mat[1][2]))
+
+def composeTransform(M1,M2):
+    a11 = M1[0][0]*M2[0][0] + M1[0][1]*M2[1][0]
+    a12 = M1[0][0]*M2[0][1] + M1[0][1]*M2[1][1]
+    a21 = M1[1][0]*M2[0][0] + M1[1][1]*M2[1][0]
+    a22 = M1[1][0]*M2[0][1] + M1[1][1]*M2[1][1]
+
+    v1 = M1[0][0]*M2[0][2] + M1[0][1]*M2[1][2] + M1[0][2]
+    v2 = M1[1][0]*M2[0][2] + M1[1][1]*M2[1][2] + M1[1][2]
+    return [[a11,a12,v1],[a21,a22,v2]]
+
+def composeParents(node, mat):
+    trans = node.get('transform')
+    if trans:
+        mat = composeTransform(parseTransform(trans), mat)
+    if node.getparent().tag == inkex.addNS('g','svg'):
+        mat = composeParents(node.getparent(), mat)
+    return mat
+
+def applyTransformToNode(mat,node):
+    m=parseTransform(node.get("transform"))
+    newtransf=formatTransform(composeTransform(mat,m))
+    node.set("transform", newtransf)
+
+def applyTransformToPoint(mat,pt):
+    x = mat[0][0]*pt[0] + mat[0][1]*pt[1] + mat[0][2]
+    y = mat[1][0]*pt[0] + mat[1][1]*pt[1] + mat[1][2]
+    pt[0]=x
+    pt[1]=y
+
+def applyTransformToPath(mat,path):
+    for comp in path:
+        for ctl in comp:
+            for pt in ctl:
+                applyTransformToPoint(mat,pt)
+
+def fuseTransform(node):
+    if node.get('d')==None:
+        #FIXME: how do you raise errors?
+        raise AssertionError, 'can not fuse "transform" of elements that have no "d" attribute'
+    t = node.get("transform")
+    if t == None:
+        return
+    m = parseTransform(t)
+    d = node.get('d')
+    p = cubicsuperpath.parsePath(d)
+    applyTransformToPath(m,p)
+    node.set('d', cubicsuperpath.formatPath(p))
+    del node.attrib["transform"]
+
+####################################################################
+##-- Some functions to compute a rough bbox of a given list of objects.
+##-- this should be shipped out in an separate file...
+
+def boxunion(b1,b2):
+    if b1 is None:
+        return b2
+    elif b2 is None:
+        return b1    
+    else:
+        return((min(b1[0],b2[0]), max(b1[1],b2[1]), min(b1[2],b2[2]), max(b1[3],b2[3])))
+
+def roughBBox(path):
+    xmin,xMax,ymin,yMax = path[0][0][0][0],path[0][0][0][0],path[0][0][0][1],path[0][0][0][1]
+    for pathcomp in path:
+        for ctl in pathcomp:
+            for pt in ctl:
+                xmin = min(xmin,pt[0])
+                xMax = max(xMax,pt[0])
+                ymin = min(ymin,pt[1])
+                yMax = max(yMax,pt[1])
+    return xmin,xMax,ymin,yMax
+
+def refinedBBox(path):
+    xmin,xMax,ymin,yMax = path[0][0][1][0],path[0][0][1][0],path[0][0][1][1],path[0][0][1][1]
+    for pathcomp in path:
+        for i in range(1, len(pathcomp)):
+            cmin, cmax = cubicExtrema(pathcomp[i-1][1][0], pathcomp[i-1][2][0], pathcomp[i][0][0], pathcomp[i][1][0])
+            xmin = min(xmin, cmin)
+            xMax = max(xMax, cmax)
+            cmin, cmax = cubicExtrema(pathcomp[i-1][1][1], pathcomp[i-1][2][1], pathcomp[i][0][1], pathcomp[i][1][1])
+            ymin = min(ymin, cmin)
+            yMax = max(yMax, cmax)
+    return xmin,xMax,ymin,yMax
+
+def cubicExtrema(y0, y1, y2, y3):
+    cmin = min(y0, y3)
+    cmax = max(y0, y3)
+    d1 = y1 - y0
+    d2 = y2 - y1
+    d3 = y3 - y2
+    if (d1 - 2*d2 + d3):
+        if (d2*d2 > d1*d3):
+            t = (d1 - d2 + math.sqrt(d2*d2 - d1*d3))/(d1 - 2*d2 + d3)
+            if (t > 0) and (t < 1):
+                y = y0*(1-t)*(1-t)*(1-t) + 3*y1*t*(1-t)*(1-t) + 3*y2*t*t*(1-t) + y3*t*t*t
+                cmin = min(cmin, y)
+                cmax = max(cmax, y)
+            t = (d1 - d2 - math.sqrt(d2*d2 - d1*d3))/(d1 - 2*d2 + d3)
+            if (t > 0) and (t < 1):
+                y = y0*(1-t)*(1-t)*(1-t) + 3*y1*t*(1-t)*(1-t) + 3*y2*t*t*(1-t) + y3*t*t*t
+                cmin = min(cmin, y)
+                cmax = max(cmax, y)
+    elif (d3 - d1):
+        t = -d1/(d3 - d1)
+        if (t > 0) and (t < 1):
+            y = y0*(1-t)*(1-t)*(1-t) + 3*y1*t*(1-t)*(1-t) + 3*y2*t*t*(1-t) + y3*t*t*t
+            cmin = min(cmin, y)
+            cmax = max(cmax, y)
+    return cmin, cmax
+
+def computeBBox(aList,mat=[[1,0,0],[0,1,0]]):
+    bbox=None
+    for node in aList:
+        m = parseTransform(node.get('transform'))
+        m = composeTransform(mat,m)
+        #TODO: text not supported!
+        d = None
+        if node.get("d"):
+            d = node.get('d')
+        elif node.get('points'):
+            d = 'M' + node.get('points')
+        elif node.tag in [ inkex.addNS('rect','svg'), 'rect', inkex.addNS('image','svg'), 'image' ]:
+            d = 'M' + node.get('x', '0') + ',' + node.get('y', '0') + \
+                'h' + node.get('width') + 'v' + node.get('height') + \
+                'h-' + node.get('width')
+        elif node.tag in [ inkex.addNS('line','svg'), 'line' ]:
+            d = 'M' + node.get('x1') + ',' + node.get('y1') + \
+                ' ' + node.get('x2') + ',' + node.get('y2')
+        elif node.tag in [ inkex.addNS('circle','svg'), 'circle', \
+                            inkex.addNS('ellipse','svg'), 'ellipse' ]:
+            rx = node.get('r')
+            if rx is not None:
+                ry = rx
+            else:
+                rx = node.get('rx')
+                ry = node.get('ry')
+            cx = float(node.get('cx', '0'))
+            cy = float(node.get('cy', '0'))
+            x1 = cx - float(rx)
+            x2 = cx + float(rx)
+            d = 'M %f %f ' % (x1, cy) + \
+                'A' + rx + ',' + ry + ' 0 1 0 %f,%f' % (x2, cy) + \
+                'A' + rx + ',' + ry + ' 0 1 0 %f,%f' % (x1, cy)
+ 
+        if d is not None:
+            p = cubicsuperpath.parsePath(d)
+            applyTransformToPath(m,p)
+            bbox=boxunion(refinedBBox(p),bbox)
+
+        elif node.tag == inkex.addNS('use','svg') or node.tag=='use':
+            refid=node.get(inkex.addNS('href','xlink'))
+            path = '//*[@id="%s"]' % refid[1:]
+            refnode = node.xpath(path)
+            bbox=boxunion(computeBBox(refnode,m),bbox)
+            
+        bbox=boxunion(computeBBox(node,m),bbox)
+    return bbox
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
-- 
cgit