inital default

This commit is contained in:
Hendrik Schutter 2022-06-13 12:45:47 +02:00
parent b8e4f21fbc
commit eff60aa3ee
21 changed files with 1744 additions and 1 deletions

View File

@ -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
__init__.py Executable file
View File

290
bezmisc.py Executable file
View File

@ -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

45
config.py Normal file
View File

@ -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

8
convert.py Normal file
View File

@ -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 + "..."))

40
cspsubdiv.py Executable file
View File

@ -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)

170
cubicsuperpath.py Executable file
View File

@ -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

142
ffgeom.py Normal file
View File

@ -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

190
shapes.py Normal file
View File

@ -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],

203
simplepath.py Executable file
View File

@ -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

242
simpletransform.py Executable file
View File

@ -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

8
svg2gcode.code-workspace Normal file
View File

@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

81
svg2gcode.py Executable file
View File

@ -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()

86
test_data/10mmx10mm.svg Normal file
View File

@ -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

43
test_data/Pezi.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 292 KiB

View File

@ -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

Binary file not shown.

Binary file not shown.

108
test_data/Test_H.svg Normal file
View File

@ -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

43
test_data/Zeichnung.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

26
test_data/test.gcode Normal file
View File

@ -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