parent
b8e4f21fbc
commit
eff60aa3ee
@ -1,3 +1,13 @@ |
||||
# svg2gcode |
||||
|
||||
Convert vector images (SVG) to gcode for usage with a laser plotter. |
||||
Convert vector images (SVG) to gcode for usage with a laser plotter. |
||||
|
||||
Based on the vector to gcode implementation from [Vishal Patil](https://github.com/vishpat/svg2gcode) |
||||
|
||||
# Requirements |
||||
|
||||
`pip install inkex` |
||||
|
||||
# Usage |
||||
|
||||
`clear && cat test_data/10mmx10mm.svg | python3 svg2gcode.py > test_data/test.gcode` |
||||
|
@ -0,0 +1,290 @@ |
||||
#!/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 |
||||
''' |
||||
|
||||
from __future__ import absolute_import |
||||
from __future__ import print_function |
||||
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(xxx_todo_changeme): |
||||
#parametric bezier |
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme |
||||
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(xxx_todo_changeme1, xxx_todo_changeme2): |
||||
#parametric line |
||||
((lx1,ly1),(lx2,ly2)) = xxx_todo_changeme1 |
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme2 |
||||
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(xxx_todo_changeme3,t): |
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme3 |
||||
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(xxx_todo_changeme4,t): |
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme4 |
||||
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(xxx_todo_changeme5, xxx_todo_changeme6): |
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme5 |
||||
(dy,dx) = xxx_todo_changeme6 |
||||
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(xxx_todo_changeme7, xxx_todo_changeme8,t): |
||||
(x1,y1) = xxx_todo_changeme7 |
||||
(x2,y2) = xxx_todo_changeme8 |
||||
return x1+t*(x2-x1),y1+t*(y2-y1) |
||||
def beziersplitatt(xxx_todo_changeme9,t): |
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme9 |
||||
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(xxx_todo_changeme10, xxx_todo_changeme11): |
||||
(x1,y1) = xxx_todo_changeme10 |
||||
(x2,y2) = xxx_todo_changeme11 |
||||
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 range(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(xxx_todo_changeme12, tolerance = 0.001): |
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme12 |
||||
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(xxx_todo_changeme13, l = 0.5, tolerance = 0.001): |
||||
((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)) = xxx_todo_changeme13 |
||||
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 |
@ -0,0 +1,45 @@ |
||||
|
||||
"""Z position when tool is touching surface in mm""" |
||||
z_touching = 0.0 |
||||
|
||||
"""Z position where tool should travel in mm""" |
||||
z_travel = 0.0 |
||||
|
||||
"""Distance between pen and Y=0.0 in mm""" |
||||
y_offset = 0.0 |
||||
|
||||
"""Distance between pen and X=0.0 in mm""" |
||||
x_offset = 0.0 |
||||
|
||||
"""Print bed width in mm""" |
||||
bed_max_x = 300 |
||||
|
||||
"""Print bed height in mm""" |
||||
bed_max_y = 300 |
||||
|
||||
"""X/Y speed in mm/minute""" |
||||
xy_speed = 700 |
||||
|
||||
"""Wait time between phases in milliseconds""" |
||||
wait_time = 1000 |
||||
|
||||
"""G-code emitted at the start of processing the SVG file""" |
||||
preamble = "G90 ;Absolute programming\nG21 ;Programming in millimeters (mm)\nM5 ;Disable laser" |
||||
|
||||
"""G-code emitted at the end of processing the SVG file""" |
||||
postamble = "G1 X0.0 Y0.0; Display printbed\nM02 ;End of program" |
||||
|
||||
"""G-code emitted before processing a SVG shape""" |
||||
shape_preamble = "; ------\n; Draw shapes" |
||||
|
||||
"""G-code emitted after processing a SVG shape""" |
||||
shape_postamble = "; ------\n; Shape completed" |
||||
|
||||
""" |
||||
Used to control the smoothness/sharpness of the curves. |
||||
Smaller the value greater the sharpness. Make sure the |
||||
value is greater than 0.1 |
||||
""" |
||||
smoothness = 0.5 |
||||
|
||||
|
@ -0,0 +1,8 @@ |
||||
from __future__ import absolute_import |
||||
from __future__ import print_function |
||||
import os |
||||
|
||||
for filename in os.listdir('.'): |
||||
if filename.endswith('.py'): |
||||
os.system("modernize -w " + filename + " --no-six") |
||||
print(("Converting", filename + "...")) |
@ -0,0 +1,40 @@ |
||||
#!/usr/bin/env python |
||||
from __future__ import absolute_import |
||||
from bezmisc import * |
||||
from ffgeom import * |
||||
|
||||
def maxdist(xxx_todo_changeme): |
||||
((p0x,p0y),(p1x,p1y),(p2x,p2y),(p3x,p3y)) = xxx_todo_changeme |
||||
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): |
||||
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: |
||||
try: |
||||
subdiv(sp,flat,i+1) |
||||
except IndexError: |
||||
pass |
||||
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] |
||||
subdiv(sp,flat,i) |
@ -0,0 +1,170 @@ |
||||
#!/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 |
||||
|
||||
""" |
||||
from __future__ import absolute_import |
||||
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 fileencoding=utf-8 textwidth=99 |
@ -0,0 +1,142 @@ |
||||
#!/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 |
||||
""" |
||||
from __future__ import absolute_import |
||||
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 fileencoding=utf-8 textwidth=99 |
@ -0,0 +1,190 @@ |
||||
#!/usr/bin/env python |
||||
|
||||
from __future__ import absolute_import |
||||
import logging |
||||
import traceback |
||||
import xml.etree.ElementTree as ET |
||||
import simplepath |
||||
import simpletransform |
||||
import cubicsuperpath |
||||
import cspsubdiv |
||||
from bezmisc import beziersplitatt |
||||
|
||||
|
||||
class svgshape(object): |
||||
|
||||
def __init__(self, xml_node): |
||||
self.xml_node = xml_node |
||||
|
||||
def d_path(self): |
||||
raise NotImplementedError |
||||
|
||||
def transformation_matrix(self): |
||||
t = self.xml_node.get('transform') |
||||
return simpletransform.parseTransform(t) if t is not None else None |
||||
|
||||
def svg_path(self): |
||||
return "<path d=\"" + self.d_path() + "\"/>" |
||||
|
||||
def __str__(self): |
||||
return self.xml_node |
||||
|
||||
class path(svgshape): |
||||
def __init__(self, xml_node): |
||||
super(path, self).__init__(xml_node) |
||||
|
||||
if not self.xml_node == None: |
||||
path_el = self.xml_node |
||||
self.d = path_el.get('d') |
||||
else: |
||||
self.d = None |
||||
logging.error("path: Unable to get the attributes for %s", self.xml_node) |
||||
|
||||
def d_path(self): |
||||
return self.d |
||||
|
||||
class rect(svgshape): |
||||
|
||||
def __init__(self, xml_node): |
||||
super(rect, self).__init__(xml_node) |
||||
|
||||
if not self.xml_node == None: |
||||
rect_el = self.xml_node |
||||
self.x = float(rect_el.get('x')) if rect_el.get('x') else 0 |
||||
self.y = float(rect_el.get('y')) if rect_el.get('y') else 0 |
||||
self.rx = float(rect_el.get('rx')) if rect_el.get('rx') else 0 |
||||
self.ry = float(rect_el.get('ry')) if rect_el.get('ry') else 0 |
||||
self.width = float(rect_el.get('width')) if rect_el.get('width') else 0 |
||||
self.height = float(rect_el.get('height')) if rect_el.get('height') else 0 |
||||
else: |
||||
self.x = self.y = self.rx = self.ry = self.width = self.height = 0 |
||||
logging.error("rect: Unable to get the attributes for %s", self.xml_node) |
||||
|
||||
def d_path(self): |
||||
a = list() |
||||
a.append( ['M ', [self.x, self.y]] ) |
||||
a.append( [' l ', [self.width, 0]] ) |
||||
a.append( [' l ', [0, self.height]] ) |
||||
a.append( [' l ', [-self.width, 0]] ) |
||||
a.append( [' Z', []] ) |
||||
return simplepath.formatPath(a) |
||||
|
||||
class ellipse(svgshape): |
||||
|
||||
def __init__(self, xml_node): |
||||
super(ellipse, self).__init__(xml_node) |
||||
|
||||
if not self.xml_node == None: |
||||
ellipse_el = self.xml_node |
||||
self.cx = float(ellipse_el.get('cx')) if ellipse_el.get('cx') else 0 |
||||
self.cy = float(ellipse_el.get('cy')) if ellipse_el.get('cy') else 0 |
||||
self.rx = float(ellipse_el.get('rx')) if ellipse_el.get('rx') else 0 |
||||
self.ry = float(ellipse_el.get('ry')) if ellipse_el.get('ry') else 0 |
||||
else: |
||||
self.cx = self.cy = self.rx = self.ry = 0 |
||||
logging.error("ellipse: Unable to get the attributes for %s", self.xml_node) |
||||
|
||||
def d_path(self): |
||||
x1 = self.cx - self.rx |
||||
x2 = self.cx + self.rx |
||||
p = 'M %f,%f ' % ( x1, self.cy ) + \ |
||||
'A %f,%f ' % ( self.rx, self.ry ) + \ |
||||
'0 1 0 %f,%f ' % ( x2, self.cy ) + \ |
||||
'A %f,%f ' % ( self.rx, self.ry ) + \ |
||||
'0 1 0 %f,%f' % ( x1, self.cy ) |
||||
return p |
||||
|
||||
class circle(ellipse): |
||||
def __init__(self, xml_node): |
||||
super(ellipse, self).__init__(xml_node) |
||||
|
||||
if not self.xml_node == None: |
||||
circle_el = self.xml_node |
||||
self.cx = float(circle_el.get('cx')) if circle_el.get('cx') else 0 |
||||
self.cy = float(circle_el.get('cy')) if circle_el.get('cy') else 0 |
||||
self.rx = float(circle_el.get('r')) if circle_el.get('r') else 0 |
||||
self.ry = self.rx |
||||
else: |
||||
self.cx = self.cy = self.r = 0 |
||||
logging.error("Circle: Unable to get the attributes for %s", self.xml_node) |
||||
|
||||
class line(svgshape): |
||||
|
||||
def __init__(self, xml_node): |
||||
super(line, self).__init__(xml_node) |
||||
|
||||
if not self.xml_node == None: |
||||
line_el = self.xml_node |
||||
self.x1 = float(line_el.get('x1')) if line_el.get('x1') else 0 |
||||
self.y1 = float(line_el.get('y1')) if line_el.get('y1') else 0 |
||||
self.x2 = float(line_el.get('x2')) if line_el.get('x2') else 0 |
||||
self.y2 = float(line_el.get('y2')) if line_el.get('y2') else 0 |
||||
else: |
||||
self.x1 = self.y1 = self.x2 = self.y2 = 0 |
||||
logging.error("line: Unable to get the attributes for %s", self.xml_node) |
||||
|
||||
def d_path(self): |
||||
a = [] |
||||
a.append( ['M ', [self.x1, self.y1]] ) |
||||
a.append( ['L ', [self.x2, self.y2]] ) |
||||
return simplepath.formatPath(a) |
||||
|
||||
class polycommon(svgshape): |
||||
|
||||
def __init__(self, xml_node, polytype): |
||||
super(polycommon, self).__init__(xml_node) |
||||
self.points = list() |
||||
|
||||
if not self.xml_node == None: |
||||
polycommon_el = self.xml_node |
||||
points = polycommon_el.get('points') if polycommon_el.get('points') else list() |
||||
for pa in points.split(): |
||||
self.points.append(pa) |
||||
else: |
||||
logging.error("polycommon: Unable to get the attributes for %s", self.xml_node) |
||||
|
||||
|
||||
class polygon(polycommon): |
||||
|
||||
def __init__(self, xml_node): |
||||
super(polygon, self).__init__(xml_node, 'polygon') |
||||
|
||||
def d_path(self): |
||||
d = "M " + self.points[0] |
||||
for i in range( 1, len(self.points) ): |
||||
d += " L " + self.points[i] |
||||
d += " Z" |
||||
return d |
||||
|
||||
class polyline(polycommon): |
||||
|
||||
def __init__(self, xml_node): |
||||
super(polyline, self).__init__(xml_node, 'polyline') |
||||
|
||||
def d_path(self): |
||||
d = "M " + self.points[0] |
||||
for i in range( 1, len(self.points) ): |
||||
d += " L " + self.points[i] |
||||
return d |
||||
|
||||
def point_generator(path, mat, flatness): |
||||
|
||||
if len(simplepath.parsePath(path)) == 0: |
||||
return |
||||
|
||||
simple_path = simplepath.parsePath(path) |
||||
startX,startY = float(simple_path[0][1][0]), float(simple_path[0][1][1]) |
||||
yield startX, startY |
||||
|
||||
p = cubicsuperpath.parsePath(path) |
||||
|
||||
if mat: |
||||
simpletransform.applyTransformToPath(mat, p) |
||||
|
||||
for sp in p: |
||||
cspsubdiv.subdiv( sp, flatness) |
||||
for csp in sp: |
||||
ctrl_pt1 = csp[0] |
||||
ctrl_pt2 = csp[1] |
||||
end_pt = csp[2] |
||||
yield end_pt[0], end_pt[1], |
@ -0,0 +1,203 @@ |
||||
#!/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 |
||||
|
||||
""" |
||||
from __future__ import absolute_import |
||||
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], [0,0,0,0,0,'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 = next(lexer) |
||||
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 = next(lexer) |
||||
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 |
||||
|
||||
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 |
||||
|
@ -0,0 +1,242 @@ |
||||
#!/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. |
||||
''' |
||||
from __future__ import absolute_import |
||||
import cubicsuperpath, bezmisc |
||||
import copy, math, re, inkex |
||||
|
||||
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(" |