diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 47 | ||||
-rwxr-xr-x | dxf_export/better_dxf_outlines.py | 127 | ||||
-rwxr-xr-x | dxf_export/bezmisc.py | 274 | ||||
-rwxr-xr-x | dxf_export/cspsubdiv.py | 37 | ||||
-rwxr-xr-x | dxf_export/cubicsuperpath.py | 169 | ||||
-rwxr-xr-x | dxf_export/dxf_templates.py | 645 | ||||
-rwxr-xr-x | dxf_export/ffgeom.py | 141 | ||||
-rwxr-xr-x | dxf_export/inkex.py | 241 | ||||
-rwxr-xr-x | dxf_export/main.sh | 36 | ||||
-rwxr-xr-x | dxf_export/simplepath.py | 212 | ||||
-rwxr-xr-x | dxf_export/simplestyle.py | 244 | ||||
-rwxr-xr-x | dxf_export/simpletransform.py | 241 | ||||
-rwxr-xr-x | generate_sources.sh | 9 | ||||
-rw-r--r-- | readme.creole | 60 | ||||
-rw-r--r-- | src/example.scad | 17 | ||||
-rw-r--r-- | src/example.svg | 122 |
17 files changed, 2626 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6a369a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/config.mk +*.pyc +*.dxf +*.stl diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..37a6547 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +INKSCAPE ?= inkscape +OPENSCAD ?= openscad + +# Used by dxf_export/main.sh +export INKSCAPE + +# Run generate_scad.sh to get the names of all OpenSCAD files that should be generated using that same script. +GENERATED_FILES := $(addsuffix .scad,$(basename $(shell ./generate_sources.sh))) +GENERATED_SVG_FILES := $(filter %.svg, $(GENERATED_FILES)) +GENERATED_SCAD_FILES := $(filter %.scad, $(GENERATED_FILES)) + +# Source SVG files. +SVG_FILES := $(shell find src -name '*.svg') $(GENERATED_SVG_FILES) + +# Only OpenSCAD files whose names do not start with `_' are compiled to STL. +SCAD_FILES := $(shell find src -name '*.scad') $(GENERATED_SCAD_FILES) +LIBRARY_SCAD_FILES := $(foreach i,$(SCAD_FILES),$(if $(filter _%,$(notdir $(i))),$(i))) +COMPILED_SCAD_FILES := $(filter-out $(LIBRARY_SCAD_FILES),$(SCAD_FILES)) + +# All files that may be generated from the source files. +STL_FILES := $(patsubst %.scad,%.stl,$(COMPILED_SCAD_FILES)) +DXF_FILES := $(patsubst %.svg,%.dxf,$(SVG_FILES)) + +# Everything. +all: $(STL_FILES) + +# Everything^-1. +clean: + rm -rf $(DXF_FILES) $(STL_FILES) $(GENERATED_SCAD_FILES) + +# Needs to be included after target all has been defined. +-include config.mk + +# Assume that any compiled OpenSCAD file may depend on any non-compiled OpenSCAD file in the same directory. +$(foreach i,$(COMPILED_SCAD_FILES),$(eval $(i): $(filter $(dir $(i))%,$(LIBRARY_SCAD_FILES) $(DXF_FILES)))) + +# Rule to convert an SVG file to a DXF file. +%.dxf: %.svg + dxf_export/main.sh $< $@ + +# Rule to compile an OpenSCAD file to an STL file. +%.stl: %.scad + $(OPENSCAD) -o $@ $< + +# Rule for automaticlaly generated OpenSCAD files. +$(GENERATED_FILES): generate_sources.sh + ./generate_sources.sh $@ > $@ diff --git a/dxf_export/better_dxf_outlines.py b/dxf_export/better_dxf_outlines.py new file mode 100755 index 0000000..142dc8f --- /dev/null +++ b/dxf_export/better_dxf_outlines.py @@ -0,0 +1,127 @@ +#!/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 ) + +e = MyEffect() +e.affect() + diff --git a/dxf_export/bezmisc.py b/dxf_export/bezmisc.py new file mode 100755 index 0000000..e663fa6 --- /dev/null +++ b/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/dxf_export/cspsubdiv.py b/dxf_export/cspsubdiv.py new file mode 100755 index 0000000..c34236a --- /dev/null +++ b/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/dxf_export/cubicsuperpath.py b/dxf_export/cubicsuperpath.py new file mode 100755 index 0000000..af61acb --- /dev/null +++ b/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/dxf_export/dxf_templates.py b/dxf_export/dxf_templates.py new file mode 100755 index 0000000..fb26632 --- /dev/null +++ b/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/dxf_export/ffgeom.py b/dxf_export/ffgeom.py new file mode 100755 index 0000000..1983586 --- /dev/null +++ b/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/dxf_export/inkex.py b/dxf_export/inkex.py new file mode 100755 index 0000000..e487822 --- /dev/null +++ b/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/dxf_export/main.sh b/dxf_export/main.sh new file mode 100755 index 0000000..ef888b0 --- /dev/null +++ b/dxf_export/main.sh @@ -0,0 +1,36 @@ +#! /usr/bin/env bash + +in_file=$1 +out_file=$2 + +# If environment variable DXF_EXPORT_DEBUG is set, the temporary file that is modified using Inkscape is saved in the same directory as the source file and not removed. +if [ "$DXF_EXPORT_DEBUG" ]; then + temp_file="$(dirname "$in_file")/$(basename "$in_file" '.svg')~temp.svg" +else + temp_dir=$(mktemp -d) + temp_file="$temp_dir/temp.svg" +fi + +script_path=$(dirname "$BASH_SOURCE") + +cp "$in_file" "$temp_file" + +# Run a few commands using Inkscape on the SVG file to get in into a shape that makes a successful conversion to DXF more likely. +"$INKSCAPE" \ + --verb UnlockAllInAllLayers \ + --verb EditSelectAllInAllLayers \ + --verb ObjectToPath \ + --verb EditSelectAllInAllLayers \ + --verb SelectionUnGroup \ + --verb EditSelectAllInAllLayers \ + --verb StrokeToPath \ + --verb FileSave \ + --verb FileClose \ + "$temp_file" + +# Convert the SVG to a DXF file. +python2 "$script_path/better_dxf_outlines.py" "$temp_file" > "$out_file" + +if ! [ "$DXF_EXPORT_DEBUG" ]; then + rm -rf "$temp_dir" +fi diff --git a/dxf_export/simplepath.py b/dxf_export/simplepath.py new file mode 100755 index 0000000..f62b1b4 --- /dev/null +++ b/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/dxf_export/simplestyle.py b/dxf_export/simplestyle.py new file mode 100755 index 0000000..3ec971e --- /dev/null +++ b/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/dxf_export/simpletransform.py b/dxf_export/simpletransform.py new file mode 100755 index 0000000..47cc61e --- /dev/null +++ b/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 diff --git a/generate_sources.sh b/generate_sources.sh new file mode 100755 index 0000000..687f91a --- /dev/null +++ b/generate_sources.sh @@ -0,0 +1,9 @@ +#! /usr/bin/env bash + +if [ "$1" ]; then + # Print the content of the generated source named $1 here. + true +else + # Print a list of names of the files that should be generated using this script here. + true +fi diff --git a/readme.creole b/readme.creole new file mode 100644 index 0000000..55c4542 --- /dev/null +++ b/readme.creole @@ -0,0 +1,60 @@ += Readme + +== Prerequisites + +* Inkscape +** Used to export DXF files to SVG. + +* OpenSCAD +** The currently newest version (2014.03) has trouble with 2D shapes containing holes. The current development version solves these problems. +** It is assumed that the 'openscad' binary is on $PATH. + +If any of the required binaries ('inkscape' for Inkscape and 'openscad' for OpenSCAD) is not available on $PATH, paths to these binaries can be configured by creating a file called 'config.mk' in the same directory as the makefile. There, the variables 'OPENSCAD' and 'INKSCAPE' can be set to the appropriate paths like this: + +{{{ +OPENSCAD := /Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD +}}} + + +== Supported file types + +=== SVG files + +Any file whose name ends in `.svg` may be used from an OpenSCAD file like this: + +{{{ +import("file.dxf"); +}}} + +The makefile will automatically convert the SVG file to a DXF file. + + +=== OpenSCAD files + +Any file whose name ends in `.scad` but does not start with `_` will be compiled to STL file using OpenSCAD. OpenSCAD files whose names start with `_` can be used as "library" files and can be used from other OpenSCAD files using one of the following commands: + +{{{ +include <filename> +use <filename> +}}} + +Please see the [http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Print_version|manual] for details. + + +== Compiling + +To compile the whole project, run `make` from the directory in which this readme is. This will process all necessary SVG files and produce an STL file for each OpenSCAD source file. Individual files may be created or updated by passing their names to the make command, as ussual. + +To remove all generated files, run `make clean`. + + +=== Dependency tracking + +OpenSCAD actually does have the ability to produce dependency files which can be read by `make` but does this in a way that makes using them in a project with multiple directories near-impossible. Because of this, dependencies between files are generated from a few assumptions based on simple naming-conventions: + +* Only files in the `src` directory are compiled. +* Each generated STL file solely depends on the OpenSCAD file with the same name. +* Each generated DXF files solely depends on the SVG files with the same name. +* Each OpenSCAD file depends whose name does not begin with `_` depends on two sets of files: +** All OpenSCAD files in the same directory whose names do begin with `_`. +** All DXF files in the same directory. diff --git a/src/example.scad b/src/example.scad new file mode 100644 index 0000000..e4e5022 --- /dev/null +++ b/src/example.scad @@ -0,0 +1,17 @@ +module extrude_layer(layer, height) { + linear_extrude(height = height) + import("example.dxf", layer = layer); +} + +render(convexity = 10) { + union() { + difference() { + extrude_layer("base", 2); + + translate([0, 0, -1e6]) + extrude_layer("text", 2e6); + } + + extrude_layer("struts", 1); + } +} diff --git a/src/example.svg b/src/example.svg new file mode 100644 index 0000000..8d90ed7 --- /dev/null +++ b/src/example.svg @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Ebene_2" + x="0px" + y="0px" + width="626.03119" + height="670.09412" + viewBox="0 0 626.03119 670.09412" + enable-background="new 0 0 4856 968" + xml:space="preserve" + inkscape:version="0.48.5 r10040" + sodipodi:docname="example.svg"><metadata + id="metadata41"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs + id="defs39"><inkscape:path-effect + is_visible="true" + id="path-effect3785" + effect="spiro" /><inkscape:path-effect + is_visible="true" + id="path-effect3777" + effect="spiro" /><inkscape:path-effect + effect="spiro" + id="path-effect3773" + is_visible="true" /><inkscape:path-effect + effect="spiro" + id="path-effect3769" + is_visible="true" /><inkscape:path-effect + effect="spiro" + id="path-effect3779" + is_visible="true" /></defs><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1275" + inkscape:window-height="1002" + id="namedview37" + showgrid="true" + fit-margin-top="20" + fit-margin-left="20" + fit-margin-right="20" + fit-margin-bottom="20" + inkscape:zoom="2.4630583" + inkscape:cx="100.04789" + inkscape:cy="-9.3088588" + inkscape:window-x="0" + inkscape:window-y="22" + inkscape:window-maximized="0" + inkscape:current-layer="layer1" + inkscape:document-units="mm" + units="mm" + inkscape:object-paths="true" + inkscape:snap-nodes="true" + inkscape:snap-grids="true" + inkscape:snap-to-guides="false" + inkscape:snap-intersection-paths="false"><inkscape:grid + type="xygrid" + id="grid2988" + empspacing="10" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + spacingx="1mm" + spacingy="1mm" + dotted="false" + units="mm" /></sodipodi:namedview><g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="base" + style="display:inline"><rect + style="color:#000000;fill:#000000;fill-opacity:0.39215686;fill-rule:nonzero;stroke:none;stroke-width:14.17322826;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect2989" + width="141.73228" + height="70.866119" + x="-1.2523757e-06" + y="599.22797" /></g><g + inkscape:groupmode="layer" + id="layer1" + inkscape:label="text" + style="display:inline"><text + xml:space="preserve" + style="font-size:60px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#0000ff;fill-opacity:0.39215686;stroke:none;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans" + x="17.716534" + y="655.9209" + id="text3779" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3781" + x="17.716534" + y="655.9209">Abc</tspan></text> +</g><g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="struts" + style="display:inline"><path + style="fill:none;stroke:#ff0000;stroke-width:2.83464575;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.39215686;stroke-dasharray:none" + d="m 38.164379,606.53629 0,45.8413" + id="path3767" + inkscape:path-effect="#path-effect3769" + inkscape:original-d="m 38.164379,606.53629 0,45.8413" + inkscape:connector-curvature="0" /><path + inkscape:connector-curvature="0" + inkscape:original-d="m 79.060068,617.16621 0,45.8413" + inkscape:path-effect="#path-effect3785" + id="path3783" + d="m 79.060068,617.16621 0,45.8413" + style="fill:none;stroke:#ff0000;stroke-width:2.83464575;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.39215686;stroke-dasharray:none" /></g></svg>
\ No newline at end of file |