inital default
12
README.md
|
@ -1,3 +1,13 @@
|
||||||
# svg2gcode
|
# 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("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
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import shapes as shapes_pkg
|
||||||
|
from shapes import point_generator
|
||||||
|
from config import *
|
||||||
|
|
||||||
|
def generate_gcode():
|
||||||
|
svg_shapes = set(['rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path'])
|
||||||
|
|
||||||
|
tree = ET.parse(sys.stdin)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
width = root.get('width')
|
||||||
|
height = root.get('height')
|
||||||
|
if width == None or height == None:
|
||||||
|
viewbox = root.get('viewBox')
|
||||||
|
if viewbox:
|
||||||
|
_, _, width, height = viewbox.split()
|
||||||
|
|
||||||
|
if width == None or height == None:
|
||||||
|
print("Unable to get width and height for the svg")
|
||||||
|
sys.exit(1)
|
||||||
|
width = float(width.split("mm")[0])
|
||||||
|
height = float(height.split("mm")[0])
|
||||||
|
print(";SVG: With:" + str(width) + " Height:" + str(height))
|
||||||
|
# Must keep the ratio of the svg, so offset will be largest between x/y
|
||||||
|
offset = max(x_offset, y_offset)
|
||||||
|
corrected_bed_max_x = bed_max_x - offset
|
||||||
|
corrected_bed_max_y = bed_max_y - offset
|
||||||
|
scale_x = 1 #corrected_bed_max_x / max(width, height)
|
||||||
|
scale_y = 1 #corrected_bed_max_y / max(width, height)
|
||||||
|
|
||||||
|
print(preamble)
|
||||||
|
# Iterator to lower printhead at first point
|
||||||
|
num_points = 0
|
||||||
|
|
||||||
|
for elem in root.iter():
|
||||||
|
|
||||||
|
try:
|
||||||
|
_, tag_suffix = elem.tag.split('}')
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if tag_suffix in svg_shapes:
|
||||||
|
shape_class = getattr(shapes_pkg, tag_suffix)
|
||||||
|
shape_obj = shape_class(elem)
|
||||||
|
d = shape_obj.d_path()
|
||||||
|
m = shape_obj.transformation_matrix()
|
||||||
|
|
||||||
|
if d:
|
||||||
|
print(shape_preamble)
|
||||||
|
p = point_generator(d, m, smoothness)
|
||||||
|
for x,y in p:
|
||||||
|
print(";X: " + str(x) + " Y: " + str(y))
|
||||||
|
if x > 0 and x < bed_max_x and y > 0 and y < bed_max_y:
|
||||||
|
print("G1 X%0.01f Y%0.01f" % ((scale_x*x)+x_offset, (scale_y*y)+y_offset))
|
||||||
|
num_points += 1
|
||||||
|
if num_points == 1:
|
||||||
|
print("M3 I S150 ;start laser")
|
||||||
|
else:
|
||||||
|
print("\n; Coordinates out of range:", "G1 X%0.01f Y%0.01f" % ((scale_x*x)+x_offset, (scale_y*y)+y_offset))
|
||||||
|
print("; Raw:", str(x), str(y), "\nScaled:", str(scale_x*x), str(scale_y*y), "\nScale Factors:", scale_x, scale_y, "\n")
|
||||||
|
print("exit")
|
||||||
|
sys.exit(-1)
|
||||||
|
print("M5 ;stop laser")
|
||||||
|
num_points = 0
|
||||||
|
print(shape_postamble)
|
||||||
|
|
||||||
|
print(postamble)
|
||||||
|
print("; Generated", num_points, "points")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("; " + str(sys.setrecursionlimit(20000)))
|
||||||
|
generate_gcode()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="10mm"
|
||||||
|
height="10mm"
|
||||||
|
viewBox="0 0 10 10"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||||
|
sodipodi:docname="10mmx10mm.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="12.397897"
|
||||||
|
inkscape:cx="-0.76625899"
|
||||||
|
inkscape:cy="19.842075"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="1680"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient2969"
|
||||||
|
inkscape:swatch="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop2967" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient2930"
|
||||||
|
inkscape:swatch="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop2928" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient2816"
|
||||||
|
inkscape:swatch="solid">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop2814" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient2930"
|
||||||
|
id="linearGradient2971"
|
||||||
|
x1="2.8073502"
|
||||||
|
y1="4.1158299"
|
||||||
|
x2="6.4509277"
|
||||||
|
y2="4.1158299"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(2.7154063,0,0,2.7800973,-7.5699926,-6.4424077)" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2971);stroke-width:0.106208;stroke-dasharray:none"
|
||||||
|
id="rect836"
|
||||||
|
width="9.8937922"
|
||||||
|
height="9.8937922"
|
||||||
|
x="0.053103756"
|
||||||
|
y="0.053103756" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 292 KiB |
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="61.2mm" height="31.200000000000006mm" viewBox="0 0 61.2 31.200000000000006" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||||
|
<g id="Sketch" transform="translate(30.600000,30.600000) scale(1,-1)">
|
||||||
|
<path id="Sketch_w0000" d="M -30.0 0.0 L 30.0 0.0 A 60 60 0 0 1 21.9615 30L -30.0 0.0 " stroke="#000000" stroke-width="0.35 px" style="stroke-width:0.35;stroke-miterlimit:4;stroke-dasharray:none;fill:none;fill-opacity:1;fill-rule: evenodd"/>
|
||||||
|
<title>b'Sketch'</title>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 609 B |
|
@ -0,0 +1,108 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="100mm"
|
||||||
|
height="100mm"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
|
||||||
|
sodipodi:docname="Test_H.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="1.7492917"
|
||||||
|
inkscape:cx="166.06721"
|
||||||
|
inkscape:cy="244.67046"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="1680"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="text221"
|
||||||
|
showguides="false">
|
||||||
|
<sodipodi:guide
|
||||||
|
position="30.40159,95.916204"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide20"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
aria-label="H"
|
||||||
|
id="text115"
|
||||||
|
style="font-size:50.8px;line-height:1.25;stroke-width:0.264583">
|
||||||
|
<g
|
||||||
|
aria-label="Hallo Jakob!"
|
||||||
|
id="text221"
|
||||||
|
style="font-size:10.5833px;stroke-width:0.0700042"
|
||||||
|
transform="matrix(1,0,0,-1,0,87.650687)">
|
||||||
|
<path
|
||||||
|
d="m 34.367619,26.146512 v 9.507547 h -9.491125 v -9.507547 h -2.495623 v 20.569076 h 2.495623 v -8.843573 h 9.491125 v 8.843573 h 2.482692 V 26.146512 Z"
|
||||||
|
id="path172"
|
||||||
|
style="stroke-width:0.183092" />
|
||||||
|
<path
|
||||||
|
d="m 49.548248,26.146512 q -0.232759,0.550959 -0.336199,1.610493 -0.672395,-0.776993 -1.680989,-1.342077 -1.008594,-0.550956 -2.301663,-0.550956 -2.146494,0 -3.439562,1.31382 -1.293069,1.313821 -1.293069,3.220982 0,2.458117 1.706851,3.729556 1.706849,1.285567 4.590394,1.285567 h 2.366316 v 1.214932 q 0,1.356203 -0.749981,2.161449 -0.737048,0.819371 -2.198216,0.819371 -1.357723,0 -2.198218,-0.734608 -0.827564,-0.720483 -0.827564,-1.681127 H 40.79417 q 0,1.638745 1.512893,3.079711 1.512889,1.440965 4.047305,1.440965 2.2758,0 3.736969,-1.271441 1.461168,-1.271439 1.461168,-3.842573 v -6.865778 q 0,-2.119066 0.491366,-3.362252 v -0.226028 z m -3.969722,2.00605 q 1.293069,0 2.23701,0.706355 0.95687,0.706357 1.344791,1.568111 v 3.150346 h -2.224079 q -4.034376,-0.08477 -4.034376,-2.825424 0,-1.087787 0.672397,-1.850651 0.672397,-0.748737 2.004257,-0.748737 z"
|
||||||
|
id="path174"
|
||||||
|
style="stroke-width:0.183092" />
|
||||||
|
<path
|
||||||
|
d="M 57.914403,47.845757 V 26.146512 h -2.405109 v 21.699245 z"
|
||||||
|
id="path176"
|
||||||
|
style="stroke-width:0.183092" />
|
||||||
|
<path
|
||||||
|
d="M 64.353885,47.845757 V 26.146512 h -2.405106 v 21.699245 z"
|
||||||
|
id="path178"
|
||||||
|
style="stroke-width:0.183092" />
|
||||||
|
<path
|
||||||
|
d="m 67.560698,33.944678 q 0,3.319871 1.706851,5.537829 1.706852,2.232083 4.642118,2.232083 2.935265,0 4.642117,-2.189701 1.706851,-2.175577 1.745644,-5.43894 v -0.466193 q 0,-3.319873 -1.719784,-5.537829 -1.706849,-2.217955 -4.642117,-2.217955 -2.948195,0 -4.667978,2.217955 -1.706851,2.217956 -1.706851,5.537829 z m 2.392177,-0.324922 q 0,-2.274465 0.982733,-3.941465 0.995663,-1.666999 2.999919,-1.666999 1.952535,0 2.948198,1.638746 0.995664,1.652871 1.008594,3.927336 v 0.367304 q 0,2.246212 -0.995663,3.927337 -0.995662,1.695252 -2.986989,1.695252 -1.978396,0 -2.974059,-1.695252 -0.982733,-1.681125 -0.982733,-3.927337 z"
|
||||||
|
id="path180"
|
||||||
|
style="stroke-width:0.183092" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<metadata
|
||||||
|
id="metadata59">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:License
|
||||||
|
rdf:about="http://artlibre.org/licence/lal">
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||||
|
<cc:permits
|
||||||
|
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||||
|
<cc:requires
|
||||||
|
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||||
|
<cc:requires
|
||||||
|
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||||
|
<cc:requires
|
||||||
|
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||||
|
</cc:License>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<cc:license
|
||||||
|
rdf:resource="http://artlibre.org/licence/lal" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,26 @@
|
||||||
|
; None
|
||||||
|
;SVG: With:10.0 Height:10.0
|
||||||
|
G90 ;Absolute programming
|
||||||
|
G21 ;Programming in millimeters (mm)
|
||||||
|
M5 ;Disable laser
|
||||||
|
; ------
|
||||||
|
; Draw shapes
|
||||||
|
;X: 0.053103756 Y: 0.053103756
|
||||||
|
G1 X0.1 Y0.1
|
||||||
|
M3 I S150 ;start laser
|
||||||
|
;X: 0.053103756 Y: 0.053103756
|
||||||
|
G1 X0.1 Y0.1
|
||||||
|
;X: 9.946895956 Y: 0.053103756
|
||||||
|
G1 X9.9 Y0.1
|
||||||
|
;X: 9.946895956 Y: 9.946895956
|
||||||
|
G1 X9.9 Y9.9
|
||||||
|
;X: 0.0531037560000005 Y: 9.946895956
|
||||||
|
G1 X0.1 Y9.9
|
||||||
|
;X: 0.053103756 Y: 0.053103756
|
||||||
|
G1 X0.1 Y0.1
|
||||||
|
M5 ;stop laser
|
||||||
|
; ------
|
||||||
|
; Shape completed
|
||||||
|
G1 X0.0 Y0.0; Display printbed
|
||||||
|
M02 ;End of program
|
||||||
|
; Generated 0 points
|