30 Commits
Author SHA1 Message Date
Vishal Patil 124c379447 Fixed the README 2014-01-13 11:14:07 -05:00
Vishal Patil 728a4ca63d Fixed the README 2014-01-08 13:27:57 -05:00
Vishal Patil cb7bf5328d Enabled all of the raster gcode 2014-01-07 10:59:15 -05:00
Vishal Patil cae5ccef3a fixed the transform problem for rasterization as well 2014-01-07 10:25:06 -05:00
Vishal Patil c0b6e032d9 Changed the plastibot template 2014-01-07 07:27:33 -05:00
Vishal Patil e9ccf106a3 Added dependency of the simpletransform script 2014-01-06 20:43:21 -05:00
Vishal Patil b47371f04b Added matrix transformation 2014-01-06 20:39:13 -05:00
Vishal Patil cb3b71f389 some more improvements to rasterization 2014-01-06 15:33:04 -05:00
Vishal Patil 0c4101fbf3 Few more improvements 2014-01-06 14:24:06 -05:00
Vishal Patil dcbc3c75d8 Added a template for laser drawing 2014-01-06 14:12:56 -05:00
Vishal Patil 6d09b0d724 Added a Plastibot laser template 2014-01-06 14:09:44 -05:00
Vishal Patil 2d5e7d3926 Looks like raster is complete 2014-01-05 23:36:01 -05:00
Vishal Patil 42e692df58 raster checkpoint 2014-01-05 23:07:45 -05:00
Vishal Patil 792c98d03d Raster checkpoint 2014-01-05 22:54:26 -05:00
Vishal Patil 1d90a378bf checkpoint 2014-01-05 19:16:17 -05:00
Vishal Patil e308bcf80c Added raster files 2014-01-05 12:39:59 -05:00
Vishal Patil f5da12b158 Merge branch 'inkscape_raster' of http://github.com/vishpat/svg2gcode into inkscape_raster 2014-01-05 11:59:22 -05:00
Vishal Patil 25cadfa1fa Added submenu for vector 2014-01-05 11:57:30 -05:00
Vishal Patil 516f4701e2 Added submenu for laser vector 2014-01-05 11:47:55 -05:00
Vishal Patil 8f7d9886eb More changes to the readme 2013-12-19 14:50:12 -05:00
Vishal Patil 644f512e4d Fixed the README 2013-12-19 10:18:20 -05:00
Vishal Patil 825ec8bbfe Add instructions to the README file 2013-12-19 10:15:40 -05:00
Vishal Patil 8aed5dca6a Made the code generic 2013-12-18 17:11:46 -05:00
Vishal Patil f43b3e5234 Final changes 2013-12-18 16:55:09 -05:00
Vishal Patil 7e8a4943b9 Able to generate g-code 2013-12-18 16:29:39 -05:00
Vishal Patil 9eb93d547d checkpoint 2013-12-18 15:38:25 -05:00
Vishal Patil 357c8a9df9 Got simple version working 2013-12-18 14:20:03 -05:00
Vishal Patil 008d748988 Add instructions to install lxml on MacOSX 2013-12-18 14:19:45 -05:00
Vishal Patil 291c3a7f57 Sample effect 2013-12-18 13:45:05 -05:00
Vishal Patil 1632c17d22 Added inkscape extension 2013-12-18 13:26:41 -05:00
15 changed files with 351 additions and 915 deletions

No files matched your search

+21 -4
View File
@@ -1,7 +1,24 @@
A fast svg to gcode compiler.
An Inkscape extension to convert a SVG drawing to G-code for reprap machines. This plugin has been designed to work with Plastibot laser cutter. The gcode can be
generated for two modes __vector__ and __raster__ laser scan.
cat svgfile | python svg2gcode.py
Installation
------------
Copy the __svg2gcode.py__, __svg2gcode.inx__ and __shapes.py__ to Inkscape __extensions__ directory
The compiler is based on the eggbot project and it basically converts all of the SVG shapes into bezier curves. The bezier curves are then recursively sub divided until desired smoothness is achieved. The sub curves are then approximated as lines which are then converted into g-code.
* on Windows : "C:\Program Files\Inkscape\share\extensions"
* on Linux: "/usr/share/inkscape/extensions"
* on OS X: "/Applications/Inkscape.app/Contents/Resources/extensions"
Some of the characteristics of the compiler can be changed by editing the config.py
Copy the __plastibot_laser.svg__ to Inkscape __templates__ directory
* on Windows : "C:\Program Files\Inkscape\share\template"
* on Linux: "/usr/share/inkscape/template"
* on OS X: "/Applications/Inkscape.app/Contents/Resources/template"
Parameters
----------
The size of the bed is controlled via the __bed-width__ and __bed-height__ parameters in __svg2gcode_*.inx__ file. Please note that the size of the SVG image should match with the size of the printer/plotter bed. This can be achived using the __File -> New -> plastibot_laser__ type when creating a new SVG in inkscape.
Output
------
By default the gcode is stored in a file called __vector.gcode__ (for vector laser) or __raster.gcode__ (for raster laser) in your home directory. The file name can be changed using the options provided in the extension pop-up.
View File
Whitespace-only changes.
-274
View File
@@ -1,274 +0,0 @@
#!/usr/bin/env python
'''
Copyright (C) 2010 Nick Drobchenko, nick@cnc-club.ru
Copyright (C) 2005 Aaron Spike, aaron@ekips.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
import math, cmath
def rootWrapper(a,b,c,d):
if a:
# Monics formula see http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots
a,b,c = (b/a, c/a, d/a)
m = 2.0*a**3 - 9.0*a*b + 27.0*c
k = a**2 - 3.0*b
n = m**2 - 4.0*k**3
w1 = -.5 + .5*cmath.sqrt(-3.0)
w2 = -.5 - .5*cmath.sqrt(-3.0)
if n < 0:
m1 = pow(complex((m+cmath.sqrt(n))/2),1./3)
n1 = pow(complex((m-cmath.sqrt(n))/2),1./3)
else:
if m+math.sqrt(n) < 0:
m1 = -pow(-(m+math.sqrt(n))/2,1./3)
else:
m1 = pow((m+math.sqrt(n))/2,1./3)
if m-math.sqrt(n) < 0:
n1 = -pow(-(m-math.sqrt(n))/2,1./3)
else:
n1 = pow((m-math.sqrt(n))/2,1./3)
x1 = -1./3 * (a + m1 + n1)
x2 = -1./3 * (a + w1*m1 + w2*n1)
x3 = -1./3 * (a + w2*m1 + w1*n1)
return (x1,x2,x3)
elif b:
det=c**2.0-4.0*b*d
if det:
return (-c+cmath.sqrt(det))/(2.0*b),(-c-cmath.sqrt(det))/(2.0*b)
else:
return -c/(2.0*b),
elif c:
return 1.0*(-d/c),
return ()
def bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3))):
#parametric bezier
x0=bx0
y0=by0
cx=3*(bx1-x0)
bx=3*(bx2-bx1)-cx
ax=bx3-x0-cx-bx
cy=3*(by1-y0)
by=3*(by2-by1)-cy
ay=by3-y0-cy-by
return ax,ay,bx,by,cx,cy,x0,y0
#ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
def linebezierintersect(((lx1,ly1),(lx2,ly2)),((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3))):
#parametric line
dd=lx1
cc=lx2-lx1
bb=ly1
aa=ly2-ly1
if aa:
coef1=cc/aa
coef2=1
else:
coef1=1
coef2=aa/cc
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
#cubic intersection coefficients
a=coef1*ay-coef2*ax
b=coef1*by-coef2*bx
c=coef1*cy-coef2*cx
d=coef1*(y0-bb)-coef2*(x0-dd)
roots = rootWrapper(a,b,c,d)
retval = []
for i in roots:
if type(i) is complex and i.imag==0:
i = i.real
if type(i) is not complex and 0<=i<=1:
retval.append(bezierpointatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),i))
return retval
def bezierpointatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
x=ax*(t**3)+bx*(t**2)+cx*t+x0
y=ay*(t**3)+by*(t**2)+cy*t+y0
return x,y
def bezierslopeatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
dx=3*ax*(t**2)+2*bx*t+cx
dy=3*ay*(t**2)+2*by*t+cy
return dx,dy
def beziertatslope(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),(dy,dx)):
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
#quadratic coefficents of slope formula
if dx:
slope = 1.0*(dy/dx)
a=3*ay-3*ax*slope
b=2*by-2*bx*slope
c=cy-cx*slope
elif dy:
slope = 1.0*(dx/dy)
a=3*ax-3*ay*slope
b=2*bx-2*by*slope
c=cx-cy*slope
else:
return []
roots = rootWrapper(0,a,b,c)
retval = []
for i in roots:
if type(i) is complex and i.imag==0:
i = i.real
if type(i) is not complex and 0<=i<=1:
retval.append(i)
return retval
def tpoint((x1,y1),(x2,y2),t):
return x1+t*(x2-x1),y1+t*(y2-y1)
def beziersplitatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
m1=tpoint((bx0,by0),(bx1,by1),t)
m2=tpoint((bx1,by1),(bx2,by2),t)
m3=tpoint((bx2,by2),(bx3,by3),t)
m4=tpoint(m1,m2,t)
m5=tpoint(m2,m3,t)
m=tpoint(m4,m5,t)
return ((bx0,by0),m1,m4,m),(m,m5,m3,(bx3,by3))
'''
Approximating the arc length of a bezier curve
according to <http://www.cit.gu.edu.au/~anthony/info/graphics/bezier.curves>
if:
L1 = |P0 P1| +|P1 P2| +|P2 P3|
L0 = |P0 P3|
then:
L = 1/2*L0 + 1/2*L1
ERR = L1-L0
ERR approaches 0 as the number of subdivisions (m) increases
2^-4m
Reference:
Jens Gravesen <gravesen@mat.dth.dk>
"Adaptive subdivision and the length of Bezier curves"
mat-report no. 1992-10, Mathematical Institute, The Technical
University of Denmark.
'''
def pointdistance((x1,y1),(x2,y2)):
return math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2))
def Gravesen_addifclose(b, len, error = 0.001):
box = 0
for i in range(1,4):
box += pointdistance(b[i-1], b[i])
chord = pointdistance(b[0], b[3])
if (box - chord) > error:
first, second = beziersplitatt(b, 0.5)
Gravesen_addifclose(first, len, error)
Gravesen_addifclose(second, len, error)
else:
len[0] += (box / 2.0) + (chord / 2.0)
def bezierlengthGravesen(b, error = 0.001):
len = [0]
Gravesen_addifclose(b, len, error)
return len[0]
# balf = Bezier Arc Length Function
balfax,balfbx,balfcx,balfay,balfby,balfcy = 0,0,0,0,0,0
def balf(t):
retval = (balfax*(t**2) + balfbx*t + balfcx)**2 + (balfay*(t**2) + balfby*t + balfcy)**2
return math.sqrt(retval)
def Simpson(f, a, b, n_limit, tolerance):
n = 2
multiplier = (b - a)/6.0
endsum = f(a) + f(b)
interval = (b - a)/2.0
asum = 0.0
bsum = f(a + interval)
est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum))
est0 = 2.0 * est1
#print multiplier, endsum, interval, asum, bsum, est1, est0
while n < n_limit and abs(est1 - est0) > tolerance:
n *= 2
multiplier /= 2.0
interval /= 2.0
asum += bsum
bsum = 0.0
est0 = est1
for i in xrange(1, n, 2):
bsum += f(a + (i * interval))
est1 = multiplier * (endsum + (2.0 * asum) + (4.0 * bsum))
#print multiplier, endsum, interval, asum, bsum, est1, est0
return est1
def bezierlengthSimpson(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)), tolerance = 0.001):
global balfax,balfbx,balfcx,balfay,balfby,balfcy
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
balfax,balfbx,balfcx,balfay,balfby,balfcy = 3*ax,2*bx,cx,3*ay,2*by,cy
return Simpson(balf, 0.0, 1.0, 4096, tolerance)
def beziertatlength(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)), l = 0.5, tolerance = 0.001):
global balfax,balfbx,balfcx,balfay,balfby,balfcy
ax,ay,bx,by,cx,cy,x0,y0=bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
balfax,balfbx,balfcx,balfay,balfby,balfcy = 3*ax,2*bx,cx,3*ay,2*by,cy
t = 1.0
tdiv = t
curlen = Simpson(balf, 0.0, t, 4096, tolerance)
targetlen = l * curlen
diff = curlen - targetlen
while abs(diff) > tolerance:
tdiv /= 2.0
if diff < 0:
t += tdiv
else:
t -= tdiv
curlen = Simpson(balf, 0.0, t, 4096, tolerance)
diff = curlen - targetlen
return t
#default bezier length method
bezierlength = bezierlengthSimpson
if __name__ == '__main__':
# import timing
#print linebezierintersect(((,),(,)),((,),(,),(,),(,)))
#print linebezierintersect(((0,1),(0,-1)),((-1,0),(-.5,0),(.5,0),(1,0)))
tol = 0.00000001
curves = [((0,0),(1,5),(4,5),(5,5)),
((0,0),(0,0),(5,0),(10,0)),
((0,0),(0,0),(5,1),(10,0)),
((-10,0),(0,0),(10,0),(10,10)),
((15,10),(0,0),(10,0),(-5,10))]
'''
for curve in curves:
timing.start()
g = bezierlengthGravesen(curve,tol)
timing.finish()
gt = timing.micro()
timing.start()
s = bezierlengthSimpson(curve,tol)
timing.finish()
st = timing.micro()
print g, gt
print s, st
'''
for curve in curves:
print beziertatlength(curve,0.5)
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99
-26
View File
@@ -1,26 +0,0 @@
"""G-code emitted at the start of processing the SVG file"""
preamble = "G28\nG1 Z5.0"
"""G-code emitted at the end of processing the SVG file"""
postamble = "G28"
"""G-code emitted before processing a SVG shape"""
shape_preamble = "G4 P200"
"""G-code emitted after processing a SVG shape"""
shape_postamble = "G4 P200"
"""Print bed width in mm"""
bed_max_x = 200
"""Print bed height in mm"""
bed_max_y = 200
"""
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.2
-38
View File
@@ -1,38 +0,0 @@
#!/usr/bin/env python
from bezmisc import *
from ffgeom import *
def maxdist(((p0x,p0y),(p1x,p1y),(p2x,p2y),(p3x,p3y))):
p0 = Point(p0x,p0y)
p1 = Point(p1x,p1y)
p2 = Point(p2x,p2y)
p3 = Point(p3x,p3y)
s1 = Segment(p0,p3)
return max(s1.distanceToPoint(p1),s1.distanceToPoint(p2))
def cspsubdiv(csp,flat):
for sp in csp:
subdiv(sp,flat)
def subdiv(sp,flat,i=1):
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)
-169
View File
@@ -1,169 +0,0 @@
#!/usr/bin/env python
"""
cubicsuperpath.py
Copyright (C) 2005 Aaron Spike, aaron@ekips.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import simplepath
from math import *
def matprod(mlist):
prod=mlist[0]
for m in mlist[1:]:
a00=prod[0][0]*m[0][0]+prod[0][1]*m[1][0]
a01=prod[0][0]*m[0][1]+prod[0][1]*m[1][1]
a10=prod[1][0]*m[0][0]+prod[1][1]*m[1][0]
a11=prod[1][0]*m[0][1]+prod[1][1]*m[1][1]
prod=[[a00,a01],[a10,a11]]
return prod
def rotmat(teta):
return [[cos(teta),-sin(teta)],[sin(teta),cos(teta)]]
def applymat(mat, pt):
x=mat[0][0]*pt[0]+mat[0][1]*pt[1]
y=mat[1][0]*pt[0]+mat[1][1]*pt[1]
pt[0]=x
pt[1]=y
def norm(pt):
return sqrt(pt[0]*pt[0]+pt[1]*pt[1])
def ArcToPath(p1,params):
A=p1[:]
rx,ry,teta,longflag,sweepflag,x2,y2=params[:]
teta = teta*pi/180.0
B=[x2,y2]
if rx==0 or ry==0:
return([[A,A,A],[B,B,B]])
mat=matprod((rotmat(teta),[[1/rx,0],[0,1/ry]],rotmat(-teta)))
applymat(mat, A)
applymat(mat, B)
k=[-(B[1]-A[1]),B[0]-A[0]]
d=k[0]*k[0]+k[1]*k[1]
k[0]/=sqrt(d)
k[1]/=sqrt(d)
d=sqrt(max(0,1-d/4))
if longflag==sweepflag:
d*=-1
O=[(B[0]+A[0])/2+d*k[0],(B[1]+A[1])/2+d*k[1]]
OA=[A[0]-O[0],A[1]-O[1]]
OB=[B[0]-O[0],B[1]-O[1]]
start=acos(OA[0]/norm(OA))
if OA[1]<0:
start*=-1
end=acos(OB[0]/norm(OB))
if OB[1]<0:
end*=-1
if sweepflag and start>end:
end +=2*pi
if (not sweepflag) and 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
-141
View File
@@ -1,141 +0,0 @@
#!/usr/bin/env python
"""
ffgeom.py
Copyright (C) 2005 Aaron Cyril Spike, aaron@ekips.org
This file is part of FretFind 2-D.
FretFind 2-D is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
FretFind 2-D is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FretFind 2-D; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import math
try:
NaN = float('NaN')
except ValueError:
PosInf = 1e300000
NaN = PosInf/PosInf
class Point:
precision = 5
def __init__(self, x, y):
self.__coordinates = {'x' : float(x), 'y' : float(y)}
def __getitem__(self, key):
return self.__coordinates[key]
def __setitem__(self, key, value):
self.__coordinates[key] = float(value)
def __repr__(self):
return '(%s, %s)' % (round(self['x'],self.precision),round(self['y'],self.precision))
def copy(self):
return Point(self['x'],self['y'])
def translate(self, x, y):
self['x'] += x
self['y'] += y
def move(self, x, y):
self['x'] = float(x)
self['y'] = float(y)
class Segment:
def __init__(self, e0, e1):
self.__endpoints = [e0, e1]
def __getitem__(self, key):
return self.__endpoints[key]
def __setitem__(self, key, value):
self.__endpoints[key] = value
def __repr__(self):
return repr(self.__endpoints)
def copy(self):
return Segment(self[0],self[1])
def translate(self, x, y):
self[0].translate(x,y)
self[1].translate(x,y)
def move(self,e0,e1):
self[0] = e0
self[1] = e1
def delta_x(self):
return self[1]['x'] - self[0]['x']
def delta_y(self):
return self[1]['y'] - self[0]['y']
#alias functions
run = delta_x
rise = delta_y
def slope(self):
if self.delta_x() != 0:
return self.delta_x() / self.delta_y()
return NaN
def intercept(self):
if self.delta_x() != 0:
return self[1]['y'] - (self[0]['x'] * self.slope())
return NaN
def distanceToPoint(self, p):
s2 = Segment(self[0],p)
c1 = dot(s2,self)
if c1 <= 0:
return Segment(p,self[0]).length()
c2 = dot(self,self)
if c2 <= c1:
return Segment(p,self[1]).length()
return self.perpDistanceToPoint(p)
def perpDistanceToPoint(self, p):
len = self.length()
if len == 0: return NaN
return math.fabs(((self[1]['x'] - self[0]['x']) * (self[0]['y'] - p['y'])) - \
((self[0]['x'] - p['x']) * (self[1]['y'] - self[0]['y']))) / len
def angle(self):
return math.pi * (math.atan2(self.delta_y(), self.delta_x())) / 180
def length(self):
return math.sqrt((self.delta_x() ** 2) + (self.delta_y() ** 2))
def pointAtLength(self, len):
if self.length() == 0: return Point(NaN, NaN)
ratio = len / self.length()
x = self[0]['x'] + (ratio * self.delta_x())
y = self[0]['y'] + (ratio * self.delta_y())
return Point(x, y)
def pointAtRatio(self, ratio):
if self.length() == 0: return Point(NaN, NaN)
x = self[0]['x'] + (ratio * self.delta_x())
y = self[0]['y'] + (ratio * self.delta_y())
return Point(x, y)
def createParallel(self, p):
return Segment(Point(p['x'] + self.delta_x(), p['y'] + self.delta_y()), p)
def intersect(self, s):
return intersectSegments(self, s)
def intersectSegments(s1, s2):
x1 = s1[0]['x']
x2 = s1[1]['x']
x3 = s2[0]['x']
x4 = s2[1]['x']
y1 = s1[0]['y']
y2 = s1[1]['y']
y3 = s2[0]['y']
y4 = s2[1]['y']
denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1))
num1 = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3))
num2 = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3))
num = num1
if denom != 0:
x = x1 + ((num / denom) * (x2 - x1))
y = y1 + ((num / denom) * (y2 - y1))
return Point(x, y)
return Point(NaN, NaN)
def dot(s1, s2):
return s1.delta_x() * s2.delta_x() + s1.delta_y() * s2.delta_y()
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://web.resource.org/cc/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
width="200"
height="200">
<defs />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="375"
inkscape:cy="520"
inkscape:document-units="mm"
inkscape:current-layer="layer1" />
<metadata>
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+11 -2
View File
@@ -4,6 +4,7 @@ import logging
import traceback
import xml.etree.ElementTree as ET
import simplepath
import simpletransform
import cubicsuperpath
import cspsubdiv
from bezmisc import beziersplitatt
@@ -13,10 +14,14 @@ 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() + "\"/>"
@@ -161,7 +166,7 @@ class polyline(polycommon):
d += " L " + self.points[i]
return d
def point_generator(path, flatness):
def point_generator(path, mat, flatness):
if len(simplepath.parsePath(path)) == 0:
return
@@ -171,6 +176,10 @@ def point_generator(path, flatness):
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:
-202
View File
@@ -1,202 +0,0 @@
#!/usr/bin/env python
"""
simplepath.py
functions for digesting paths into a simple list structure
Copyright (C) 2005 Aaron Spike, aaron@ekips.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import re, math
def lexPath(d):
"""
returns and iterator that breaks path data
identifies command and parameter tokens
"""
offset = 0
length = len(d)
delim = re.compile(r'[ \t\r\n,]+')
command = re.compile(r'[MLHVCSQTAZmlhvcsqtaz]')
parameter = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
while 1:
m = delim.match(d, offset)
if m:
offset = m.end()
if offset >= length:
break
m = command.match(d, offset)
if m:
yield [d[offset:m.end()], True]
offset = m.end()
continue
m = parameter.match(d, offset)
if m:
yield [d[offset:m.end()], False]
offset = m.end()
continue
#TODO: create new exception
raise Exception, 'Invalid path data!'
'''
pathdefs = {commandfamily:
[
implicitnext,
#params,
[casts,cast,cast],
[coord type,x,y,0]
]}
'''
pathdefs = {
'M':['L', 2, [float, float], ['x','y']],
'L':['L', 2, [float, float], ['x','y']],
'H':['H', 1, [float], ['x']],
'V':['V', 1, [float], ['y']],
'C':['C', 6, [float, float, float, float, float, float], ['x','y','x','y','x','y']],
'S':['S', 4, [float, float, float, float], ['x','y','x','y']],
'Q':['Q', 4, [float, float, float, float], ['x','y','x','y']],
'T':['T', 2, [float, float], ['x','y']],
'A':['A', 7, [float, float, float, int, int, float, float], [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 = lexer.next()
except StopIteration:
break
params = []
needParam = True
if isCommand:
if not lastCommand and token.upper() != 'M':
raise Exception, 'Invalid path, must begin with moveto.'
else:
command = token
else:
#command was omited
#use last command's implicit next command
needParam = False
if lastCommand:
if lastCommand.isupper():
command = pathdefs[lastCommand][0]
else:
command = pathdefs[lastCommand.upper()][0].lower()
else:
raise Exception, 'Invalid path, no initial command.'
numParams = pathdefs[command.upper()][1]
while numParams > 0:
if needParam:
try:
token, isCommand = lexer.next()
if isCommand:
raise Exception, 'Invalid number of parameters'
except StopIteration:
raise Exception, 'Unexpected end of path'
cast = pathdefs[command.upper()][2][-numParams]
param = cast(token)
if command.islower():
if pathdefs[command.upper()][3][-numParams]=='x':
param += pen[0]
elif pathdefs[command.upper()][3][-numParams]=='y':
param += pen[1]
params.append(param)
needParam = True
numParams -= 1
#segment is now absolute so
outputCommand = command.upper()
#Flesh out shortcut notation
if outputCommand in ('H','V'):
if outputCommand == 'H':
params.append(pen[1])
if outputCommand == 'V':
params.insert(0,pen[0])
outputCommand = 'L'
if outputCommand in ('S','T'):
params.insert(0,pen[1]+(pen[1]-lastControl[1]))
params.insert(0,pen[0]+(pen[0]-lastControl[0]))
if outputCommand == 'S':
outputCommand = 'C'
if outputCommand == 'T':
outputCommand = 'Q'
#current values become "last" values
if outputCommand == 'M':
subPathStart = tuple(params[0:2])
pen = subPathStart
if outputCommand == 'Z':
pen = subPathStart
else:
pen = tuple(params[-2:])
if outputCommand in ('Q','C'):
lastControl = tuple(params[-4:-2])
else:
lastControl = pen
lastCommand = command
retval.append([outputCommand,params])
return retval
def formatPath(a):
"""Format SVG path data from an array"""
return "".join([cmd + " ".join([str(p) for p in params]) for cmd, params in a])
def translatePath(p, x, y):
for cmd,params in p:
defs = pathdefs[cmd]
for i in range(defs[1]):
if defs[3][i] == 'x':
params[i] += x
elif defs[3][i] == 'y':
params[i] += y
def scalePath(p, x, y):
for cmd,params in p:
defs = pathdefs[cmd]
for i in range(defs[1]):
if defs[3][i] == 'x':
params[i] *= x
elif defs[3][i] == 'y':
params[i] *= y
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
-59
View File
@@ -1,59 +0,0 @@
#!/usr/bin/env python
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)
height = float(height)
scale_x = bed_max_x / max(width, height)
scale_y = bed_max_y / max(width, height)
print preamble
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()
if d:
print shape_preamble
p = point_generator(d, smoothness)
for x,y in p:
if x > 0 and x < bed_max_x and y > 0 and y < bed_max_y:
print "G1 X%0.1f Y%0.1f" % (scale_x*x, scale_y*y)
print shape_postamble
print postamble
if __name__ == "__main__":
generate_gcode()
+27
View File
@@ -0,0 +1,27 @@
<inkscape-extension>
<_name>Raster</_name>
<id>com.plastibot.laser.svg2gcode.raster</id>
<dependency type="executable" location="extensions">inkex.py</dependency>
<dependency type="executable" location="extensions">bezmisc.py</dependency>
<dependency type="executable" location="extensions">cspsubdiv.py</dependency>
<dependency type="executable" location="extensions">cubicsuperpath.py</dependency>
<dependency type="executable" location="extensions">ffgeom.py</dependency>
<dependency type="executable" location="extensions">shapes.py</dependency>
<dependency type="executable" location="extensions">simplepath.py</dependency>
<dependency type="executable" location="extensions">svg2gcode_raster.py</dependency>
<param name="bed-width" type="int" min="200" max="200" _gui-text="Bed Width">200</param>
<param name="bed-height" type="int" min="200" max="200" _gui-text="Bed Height">200</param>
<param name="gcode-file" type="string" _gui-text="G-code path">raster.gcode</param>
<param name="preamble" type="string" _gui-text="Preamble">G28 \r\n G1 Z5.0</param>
<param name="postamble" type="string" _gui-text="Postamble">G28</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu _name="Plastibot Laser"/>
</effects-menu>
</effect>
<script>
<command reldir="extensions" interpreter="python">svg2gcode_raster.py</command>
</script>
</inkscape-extension>
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env python
import sys
import os
import bisect
import inkex
import shapes as shapes_pkg
from shapes import point_generator
class svg2gcode_raster(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option('-w', '--bed-width', action = 'store',
type = 'int', dest = 'bed_width', default = 200,
help = 'Bed Width')
self.OptionParser.add_option('-x', '--bed-height', action = 'store',
type = 'int', dest = 'bed_height', default = 200,
help = 'Bed Height')
self.OptionParser.add_option('-o', '--gcode-file', action = 'store',
type = 'string', dest = 'gcode_file', default = 'plastibot.gcode',
help = 'The generated gcode file path')
self.OptionParser.add_option('-p', '--preamble', action = 'store',
type = 'string', dest = 'preamble', default = 'G28\nG1 Z5.0\n',
help = 'Preamble G-code')
self.OptionParser.add_option('-q', '--postamble', action = 'store',
type = 'string', dest = 'postamble', default = 'G28\nG1 Z5.0\n',
help = 'Postamble G-code')
def generate_gcode(self, svg):
gcode_file = os.path.join(os.path.expanduser('~'), self.options.gcode_file)
try:
os.remove(gcode_file)
except OSError:
pass
bed_width = self.options.bed_width
bed_height = self.options.bed_height
width = svg.get('width')
height = svg.get('height')
width = inkex.unittouu(width)
height = inkex.unittouu(height)
if width > bed_width or height > bed_height:
raise ValueError(('The document size (%d x %d) is greater than the bedsize' %
(round(width, 1), round(height, 1))))
points = []
threshold = 0.1
intermediate_points = 5
svg_shapes = set(['rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path'])
min_x = min_y = sys.maxint
max_x = max_y = 0
for elem in svg.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:
prev_x = prev_y = None
p = point_generator(d, m, threshold)
for x, y in p:
if prev_x is not None and prev_y is not None:
intermediate_points = \
(5 * max(abs(x - prev_x), abs(y - prev_y)))/threshold
intermediate_points = int(intermediate_points)
if intermediate_points > 0:
x_incr = (x - prev_x)/intermediate_points
y_incr = (y - prev_y)/intermediate_points
for i in range(1, intermediate_points):
bisect.insort(points, (round(prev_x + i*x_incr, 1) ,
round(prev_y + i*y_incr, 1)))
min_x = x if x < min_x else min_x
min_y = y if y < min_y else min_y
max_x = x if x > max_x else max_x
max_y = y if y > max_y else max_y
bisect.insort(points, (round(x, 1) , round(y, 1)))
prev_x = x
prev_y = y
with open(gcode_file, 'w') as gcode:
gcode.write(self.options.preamble + '\n')
laser_high = False
y = min_y
while y <= max_y:
x = min_x
while x <= max_x:
i = bisect.bisect_left(points, (round(x, 1), round(y, 1)))
if i != len(points) and cmp(points[i], (round(x, 1), round(y, 1))) == 0:
if not laser_high:
gcode.write("M104 S255\n")
laser_high = True
gcode.write("G1 X%0.1f Y%0.1f\n" % (x, y))
else:
if laser_high:
gcode.write("M104 S0\n")
laser_high = False
gcode.write("G1 X%0.1f Y%0.1f\n" % (x, y))
x += threshold
y += threshold
gcode.write(self.options.postamble + '\n')
def effect(self):
svg = self.document.getroot()
self.generate_gcode(svg)
effect = svg2gcode_raster();
effect.affect();
+30
View File
@@ -0,0 +1,30 @@
<inkscape-extension>
<_name>Vector</_name>
<id>com.plastibot.laser.svg2gcode.vector</id>
<dependency type="executable" location="extensions">inkex.py</dependency>
<dependency type="executable" location="extensions">bezmisc.py</dependency>
<dependency type="executable" location="extensions">cspsubdiv.py</dependency>
<dependency type="executable" location="extensions">cubicsuperpath.py</dependency>
<dependency type="executable" location="extensions">ffgeom.py</dependency>
<dependency type="executable" location="extensions">shapes.py</dependency>
<dependency type="executable" location="extensions">simplepath.py</dependency>
<dependency type="executable" location="extensions">simpletransform.py</dependency>
<dependency type="executable" location="extensions">svg2gcode_vector.py</dependency>
<param name="bed-width" type="int" min="200" max="200" _gui-text="Bed Width">200</param>
<param name="bed-height" type="int" min="200" max="200" _gui-text="Bed Height">200</param>
<param name="gcode-file" type="string" _gui-text="G-code path">vector.gcode</param>
<param name="preamble" type="string" _gui-text="Preamble">G28\nG1 Z5.0</param>
<param name="postamble" type="string" _gui-text="Postamble">G28</param>
<param name="shape-preamble" type="string" _gui-text="Shape Preamble">G4 P200</param>
<param name="shape-postamble" type="string" _gui-text="Shape Postamble">G4 P200</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu _name="Plastibot Laser"/>
</effects-menu>
</effect>
<script>
<command reldir="extensions" interpreter="python">svg2gcode_vector.py</command>
</script>
</inkscape-extension>
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python
import sys
import os
import inkex
import shapes as shapes_pkg
from shapes import point_generator
class svg2gcode_vector(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option('-w', '--bed-width', action = 'store',
type = 'int', dest = 'bed_width', default = 200,
help = 'Bed Width')
self.OptionParser.add_option('-x', '--bed-height', action = 'store',
type = 'int', dest = 'bed_height', default = 200,
help = 'Bed Height')
self.OptionParser.add_option('-o', '--gcode-file', action = 'store',
type = 'string', dest = 'gcode_file', default = 'plastibot.gcode',
help = 'The generated gcode file path')
self.OptionParser.add_option('-p', '--preamble', action = 'store',
type = 'string', dest = 'preamble', default = 'G28\nG1 Z5.0\n',
help = 'Preamble G-code')
self.OptionParser.add_option('-q', '--postamble', action = 'store',
type = 'string', dest = 'postamble', default = 'G28\nG1 Z5.0\n',
help = 'Postamble G-code')
self.OptionParser.add_option('-s', '--shape-preamble', action = 'store',
type = 'string', dest = 'shape_preamble', default = 'G4 P200\n',
help = 'Shape Preamble G-code')
self.OptionParser.add_option('-t', '--shape-postamble', action = 'store',
type = 'string', dest = 'shape_postamble', default = 'G4 P200\n',
help = 'Shape Postamble G-code')
def generate_gcode(self, svg):
gcode_file = os.path.join(os.path.expanduser('~'), self.options.gcode_file)
try:
os.remove(gcode_file)
except OSError:
pass
bed_width = self.options.bed_width
bed_height = self.options.bed_height
width = svg.get('width')
height = svg.get('height')
width = inkex.unittouu(width)
height = inkex.unittouu(height)
if width > bed_width or height > bed_height:
raise ValueError(('The document size (%d x %d) is greater than the bedsize' %
(round(width, 1), round(height, 1))))
with open(gcode_file, 'w') as gcode:
gcode.write(self.options.preamble + '\n')
svg_shapes = set(['rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path'])
for elem in svg.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:
gcode.write(self.options.shape_preamble + '\n')
p = point_generator(d, m, 0.05)
for x,y in p:
gcode.write("G1 X%0.1f Y%0.1f\n" % (x, y))
gcode.write(self.options.shape_postamble + '\n')
gcode.write(self.options.postamble + '\n')
def effect(self):
svg = self.document.getroot()
self.generate_gcode(svg)
effect = svg2gcode_vector();
effect.affect();