From 8e5a1520cd5e901f514c382ad17e77d64727896a Mon Sep 17 00:00:00 2001 From: Michael Schwarz Date: Wed, 10 Dec 2014 22:13:34 +0100 Subject: Moved DXF export scripts to separate directory. --- Makefile | 3 +- dxf_export/__main__.py | 95 ----- dxf_export/better_dxf_outlines.py | 123 ------ dxf_export/bezmisc.py | 274 ------------- dxf_export/cspsubdiv.py | 37 -- dxf_export/cubicsuperpath.py | 169 -------- dxf_export/dxf_templates.py | 645 ------------------------------ dxf_export/ffgeom.py | 141 ------- dxf_export/inkex.py | 241 ----------- dxf_export/simplepath.py | 212 ---------- dxf_export/simplestyle.py | 244 ----------- dxf_export/simpletransform.py | 241 ----------- 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 +++++++++++ 24 files changed, 2424 insertions(+), 2423 deletions(-) delete mode 100644 dxf_export/__main__.py delete mode 100755 dxf_export/better_dxf_outlines.py delete mode 100755 dxf_export/bezmisc.py delete mode 100755 dxf_export/cspsubdiv.py delete mode 100755 dxf_export/cubicsuperpath.py delete mode 100755 dxf_export/dxf_templates.py delete mode 100755 dxf_export/ffgeom.py delete mode 100755 dxf_export/inkex.py delete mode 100755 dxf_export/simplepath.py delete mode 100755 dxf_export/simplestyle.py delete mode 100755 dxf_export/simpletransform.py 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 diff --git a/Makefile b/Makefile index 1b50f45..5a77c48 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ INKSCAPE ?= inkscape OPENSCAD ?= openscad +PYTHON ?= python2 # Used by dxf_export/main.sh export INKSCAPE @@ -36,7 +37,7 @@ $(foreach i,$(COMPILED_SCAD_FILES),$(eval $(i): $(filter $(dir $(i))%,$(LIBRARY_ # Rule to convert an SVG file to a DXF file. %.dxf: %.svg - python2 dxf_export $< $@ + PYTHONPATH="support:$$PYTHONPATH" $(PYTHON) -m dxf_export $< $@ # Rule to compile an OpenSCAD file to an STL file. %.stl: %.scad diff --git a/dxf_export/__main__.py b/dxf_export/__main__.py deleted file mode 100644 index ea6c7ad..0000000 --- a/dxf_export/__main__.py +++ /dev/null @@ -1,95 +0,0 @@ -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/dxf_export/better_dxf_outlines.py b/dxf_export/better_dxf_outlines.py deleted file mode 100755 index 19ec6c8..0000000 --- a/dxf_export/better_dxf_outlines.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/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/dxf_export/bezmisc.py b/dxf_export/bezmisc.py deleted file mode 100755 index e663fa6..0000000 --- a/dxf_export/bezmisc.py +++ /dev/null @@ -1,274 +0,0 @@ -#!/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 - -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 -"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/dxf_export/cspsubdiv.py b/dxf_export/cspsubdiv.py deleted file mode 100755 index c34236a..0000000 --- a/dxf_export/cspsubdiv.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/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/dxf_export/cubicsuperpath.py b/dxf_export/cubicsuperpath.py deleted file mode 100755 index af61acb..0000000 --- a/dxf_export/cubicsuperpath.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/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 start0 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/dxf_export/simplepath.py b/dxf_export/simplepath.py deleted file mode 100755 index f62b1b4..0000000 --- a/dxf_export/simplepath.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/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/dxf_export/simplestyle.py b/dxf_export/simplestyle.py deleted file mode 100755 index 3ec971e..0000000 --- a/dxf_export/simplestyle.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/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/dxf_export/simpletransform.py b/dxf_export/simpletransform.py deleted file mode 100755 index 47cc61e..0000000 --- a/dxf_export/simpletransform.py +++ /dev/null @@ -1,241 +0,0 @@ -#!/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 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 + +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 +"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 start0 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