#!/usr/bin/env python """ Copyright (C) 2009 Nick Drobchenko, nick@cnc-club.ru based on gcode.py (C) 2007 hugomatic... based on addnodes.py (C) 2005,2007 Aaron Spike, aaron@ekips.org based on dots.py (C) 2005 Aaron Spike, aaron@ekips.org based on interp.py (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 """ ### ### Gcode tools v 1.2 ### import inkex, simplestyle, simplepath import cubicsuperpath, simpletransform, bezmisc import os import math import bezmisc import re import copy import sys import time import cmath import numpy import sys _ = inkex._ def bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3))): #parametric bezier ax,ay,bx,by,cx,cy,x0,y0 = 0, 0, 0, 0, 0, 0, 0, 0 if ( (bx0,by0)==(bx1,by1) and (bx2,by2)==(bx3,by3) or (bx0,by0)==(bx1,by1)==(bx2,by2) or (bx1,by1)==(bx2,by2)==(bx3,by3) ): x0=bx0 cx=bx3-bx0 y0=by0 cy=by3-by0 elif (bx2,by2)==(bx3,by3) : x0=bx0 cx = (bx1-bx0)*2 bx = bx0-2*bx1+bx2 y0=by0 cy = (by1-by0)*2 by = by0-2*by1+by2 elif (bx0,by0)==(bx1,by1) : x0=bx1 cx = (bx2-bx1)*2 bx = bx1-2*bx2+bx3 y0=by1 cy = (by2-by1)*2 by = by1-2*by2+by3 else: 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 bezmisc.bezierparameterize = bezierparameterize ################################################################################ ### ### Styles and additional parameters ### ################################################################################ math.pi2 = math.pi*2 straight_tolerance = 0.0001 straight_distance_tolerance = 0.0001 engraving_tolerance = 0.0001 loft_lengths_tolerance = 0.0000001 options = {} defaults = { 'header': '%\n(Generated by gcode_tools from inkscape.) \nM3 \n', 'footer': 'M5 \nG00 X0.0000 Y0.0000 \nM2 \n(end)\n%' } loft_style = { 'main curve': simplestyle.formatStyle({ 'stroke': '#88f', 'fill': 'none', 'stroke-width':'1', 'marker-end':'url(#Arrow2Mend)' }), } biarc_style = { 'biarc0': simplestyle.formatStyle({ 'stroke': '#88f', 'fill': 'none', 'stroke-width':'1' }), 'biarc1': simplestyle.formatStyle({ 'stroke': '#8f8', 'fill': 'none', 'stroke-width':'1' }), 'line': simplestyle.formatStyle({ 'stroke': '#f88', 'fill': 'none', 'stroke-width':'1' }), 'area': simplestyle.formatStyle({ 'stroke': '#777', 'fill': 'none', 'stroke-width':'0.1' }), } biarc_style_dark = { 'biarc0': simplestyle.formatStyle({ 'stroke': '#33a', 'fill': 'none', 'stroke-width':'1' }), 'biarc1': simplestyle.formatStyle({ 'stroke': '#3a3', 'fill': 'none', 'stroke-width':'1' }), 'line': simplestyle.formatStyle({ 'stroke': '#a33', 'fill': 'none', 'stroke-width':'1' }), 'area': simplestyle.formatStyle({ 'stroke': '#222', 'fill': 'none', 'stroke-width':'0.3' }), } biarc_style_dark_area = { 'biarc0': simplestyle.formatStyle({ 'stroke': '#33a', 'fill': 'none', 'stroke-width':'0.1' }), 'biarc1': simplestyle.formatStyle({ 'stroke': '#3a3', 'fill': 'none', 'stroke-width':'0.1' }), 'line': simplestyle.formatStyle({ 'stroke': '#a33', 'fill': 'none', 'stroke-width':'0.1' }), 'area': simplestyle.formatStyle({ 'stroke': '#222', 'fill': 'none', 'stroke-width':'0.3' }), } biarc_style_i = { 'biarc0': simplestyle.formatStyle({ 'stroke': '#880', 'fill': 'none', 'stroke-width':'1' }), 'biarc1': simplestyle.formatStyle({ 'stroke': '#808', 'fill': 'none', 'stroke-width':'1' }), 'line': simplestyle.formatStyle({ 'stroke': '#088', 'fill': 'none', 'stroke-width':'1' }), 'area': simplestyle.formatStyle({ 'stroke': '#999', 'fill': 'none', 'stroke-width':'0.3' }), } biarc_style_dark_i = { 'biarc0': simplestyle.formatStyle({ 'stroke': '#dd5', 'fill': 'none', 'stroke-width':'1' }), 'biarc1': simplestyle.formatStyle({ 'stroke': '#d5d', 'fill': 'none', 'stroke-width':'1' }), 'line': simplestyle.formatStyle({ 'stroke': '#5dd', 'fill': 'none', 'stroke-width':'1' }), 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', 'stroke-width':'0.3' }), } ################################################################################ ### ### Common functions ### ################################################################################ def isnan(x): return type(x) is float and x != x def isinf(x): inf = 1e5000; return x == inf or x == -inf ### ### Just simple output function for better debugging ### #if os.path.isfile("/home/nick/output.txt") :os.remove("/home/nick/output.txt") def print_(s=''): f = open("c:\output.txt","a") f.write(str(s)) f.write("\n") f.close() #if os.path.isfile("c:\output.txt") :os.remove("c:\output.txt") #def print_(s=''): # f = open("c:\output.txt","a") # f.write(str(s)) # f.write("\n") # f.close() ### ### Point (x,y) operations ### class P: def __init__(self, x, y=None): if not y==None: self.x, self.y = float(x), float(y) else: self.x, self.y = float(x[0]), float(x[1]) def __add__(self, other): return P(self.x + other.x, self.y + other.y) def __sub__(self, other): return P(self.x - other.x, self.y - other.y) def __neg__(self): return P(-self.x, -self.y) def __mul__(self, other): if isinstance(other, P): return self.x * other.x + self.y * other.y return P(self.x * other, self.y * other) __rmul__ = __mul__ def __div__(self, other): return P(self.x / other, self.y / other) def mag(self): return math.hypot(self.x, self.y) def unit(self): h = self.mag() if h: return self / h else: return P(0,0) def dot(self, other): return self.x * other.x + self.y * other.y def rot(self, theta): c = math.cos(theta) s = math.sin(theta) return P(self.x * c - self.y * s, self.x * s + self.y * c) def angle(self): return math.atan2(self.y, self.x) def __repr__(self): return '%f,%f' % (self.x, self.y) def pr(self): return "%.2f,%.2f" % (self.x, self.y) def to_list(self): return [self.x, self.y] ### ### Functions to operate with CubicSuperPath ### def point_to_csp_simple_bound_dist(p, csp): minx,miny,maxx,maxy = None,None,None,None for subpath in csp: for sp in subpath: for p_ in sp: minx = min(minx,p_[0]) if minx!=None else p_[0] miny = min(miny,p_[1]) if miny!=None else p_[1] maxx = max(maxx,p_[0]) if maxx!=None else p_[0] maxy = max(maxy,p_[1]) if maxy!=None else p_[1] return math.sqrt(max(minx-p[0],p[0]-maxx,0)**2+max(miny-p[1],p[1]-maxy,0)**2) def point_to_csp_bound_dist(p, sp1, sp2 , max_needed_distance): d = point_to_csp_simple_bound_dist(p, [[ sp1,sp2]] ) if max_needed_distance<=d : return d sp = sp1[1:]+sp2[:2] d = None int_count = 0 for i in xrange(4): x1, y1, dx, dy = sp[i-1][0],sp[i-1][1], sp[i][0]-sp[i-1][0], sp[i][1]-sp[i-1][1] if (dx**2+dy**2)>0 : d1 = min( (p[0]-x1)**2 + (p[1]-y1)**2, (p[0]-sp[i][0])**2 + (p[1]-sp[i][1])**2 ) if 0<=((p[1]-y1)*dy+(p[0]-x1)*dx)/(dx**2+dy**2)<=1: d1 = min( d1, ((p[0]-x1)*dy+(y1-p[1])*dx)**2/(dx**2+dy**2) ) else : d1 = (p[0]-x1)**2 + (p[1]-y1)**2 d = min(d,d1) if d!=None else d1 # Get intersections with horisontal or vertical lines that goes throught p and x if dx!=0 and 0<=(p[0]-x1)/dx<1 and (p[0]-x1)/dx*dy+y1>=p[1] : if p[0]-x1/dx*dy+y1==p[1]: return 0 int_count +=1 elif x1==p[0] : if y1<=p[1]<=sp[i][1] or sp[i][1]<=p[1]<=y1: return 0 elif y1<=p[1]: x2, y2, dx2, dy2 = sp[i-1][0],sp[i-1][1], sp[i][0]-sp[i-1][0], sp[i][1]-sp[i-1][1] if dx2!=0 and dy!=0: t1 = (x1-x2)/dx2 if not (0<=t1<=1 and 0<=(y2-y1+t1*dy2)/dy<=1): int_count +=1 elif x1!=x2: int_count +=1 if int_count%2 == 0 : return math.sqrt(d) else: return -math.sqrt(d) def csp_at_t(sp1,sp2,t): bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]) return bezmisc.bezierpointatt(bez,t) def cspbezsplit(sp1, sp2, t = 0.5): s1,s2 = bezmisc.beziersplitatt((sp1[1],sp1[2],sp2[0],sp2[1]),t) return [ [sp1[0][:], sp1[1][:], list(s1[1])], [list(s1[2]), list(s1[3]), list(s2[1])], [list(s2[2]), sp2[1][:], sp2[2][:]] ] def cspbezsplitatlength(sp1, sp2, l = 0.5, tolerance = 0.01): bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]) t = bezmisc.beziertatlength(bez, l, tolerance) return cspbezsplit(sp1, sp2, t) def cspseglength(sp1,sp2, tolerance = 0.001): bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]) return bezmisc.bezierlength(bez, tolerance) def csplength(csp): total = 0 lengths = [] for sp in csp: for i in xrange(1,len(sp)): l = cspseglength(sp[i-1],sp[i]) lengths.append(l) total += l return lengths, total def csp_segments(csp): l, seg = 0, [0] for sp in csp: for i in xrange(1,len(sp)): l += cspseglength(sp[i-1],sp[i]) seg += [ l ] if l>0 : seg = [seg[i]/l for i in xrange(len(seg))] return seg,l # rebuild_csp Adds to csp control points makin it's segments looks like segs def rebuild_csp (csp, segs, s=None): if s==None : s, l = csp_segments(csp) if len(s)>len(segs) : return None segs = segs[:] segs.sort() for i in xrange(len(s)): d = None for j in xrange(len(segs)): d = min( [abs(s[i]-segs[j]),j], d) if d!=None else [abs(s[i]-segs[j]),j] del segs[d[1]] for i in xrange(len(segs)): for j in xrange(0,len(s)): if segs[i]0 : i = c + (p-c).unit()*r alpha = ((i-c).angle() - (P0-c).angle()) if a*alpha<0: if alpha>0: alpha = alpha-math.pi2 else: alpha = math.pi2+alpha if between(alpha,0,a) or min(abs(alpha),abs(alpha-a))tolerance and i<4): i += 1 dl = d1*1 for j in range(n+1): t = float(j)/n p = csp_at_t(sp1,sp2,t) d = min(distance_from_point_to_arc(p,arc1), distance_from_point_to_arc(p,arc2)) d1 = max(d1,d) n=n*2 return d1[0] def get_distance_from_point_to_csp(p,sp1,sp2, tolerance = 0.01 ): n, i = 10, 0 d, dl = [None,(0,0)],[0,(0,0)] while i<2 or (abs(d[0]-dl[0])>tolerance and i<4): i += 1 dl = d[:] for j in range(n+1): t = float(j)/n cp = csp_at_t(sp1,sp2,t) d = min( [(P(cp)-P(p)).mag(),t], d) if d[0]!=None else [(P(cp)-P(p)).mag(),t] n=n*2 return d def reverce_csp (csp): for i in range(len(csp)): n = [] for j in csp[i]: n = [ [j[2][:],j[1][:],j[0][:]] ] + n csp[i] = n[:] return csp def cubic_solver(a,b,c,d): if a!=0: # 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*a**3 - 9*a*b + 27*c k = a**2 - 3*b n = m**2 - 4*k**3 w1 = -.5 + .5*cmath.sqrt(3)*1j w2 = -.5 - .5*cmath.sqrt(3)*1j m1 = pow(complex((m+cmath.sqrt(n))/2),1./3) n1 = pow(complex((m-cmath.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!=0: det = c**2-4*b*d if det>0 : return [(-c+math.sqrt(det))/(2*b),(-c-math.sqrt(det))/(2*b)] elif d == 0 : return [-c/(b*b)] else : return [(-c+cmath.sqrt(det))/(2*b),(-c-cmath.sqrt(det))/(2*b)] elif c!=0 : return [-d/c] else : return [] def csp_line_intersection(l1,l2,sp1,sp2): dd=l1[0] cc=l2[0]-l1[0] bb=l1[1] aa=l2[1]-l1[1] if aa==cc==0 : return [] if aa: coef1=cc/aa coef2=1 else: coef1=1 coef2=aa/cc bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]) ax,ay,bx,by,cx,cy,x0,y0=bezmisc.bezierparameterize(bez) a=coef1*ay-coef2*ax b=coef1*by-coef2*bx c=coef1*cy-coef2*cx d=coef1*(y0-bb)-coef2*(x0-dd) roots = cubic_solver(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(i) return retval ################################################################################ ### ### Biarc function ### ### Calculates biarc approximation of cubic super path segment ### splits segment if needed or approximates it with straight line ### ################################################################################ def biarc(sp1, sp2, z1, z2, depth=0,): def biarc_split(sp1,sp2, z1, z2, depth): if depth 0 : raise ValueError, (a,b,c,disq,beta1,beta2) beta = max(beta1, beta2) elif asmall and bsmall: return biarc_split(sp1, sp2, z1, z2, depth) alpha = beta * r ab = alpha + beta P1 = P0 + alpha * TS P3 = P4 - beta * TE P2 = (beta / ab) * P1 + (alpha / ab) * P3 def calculate_arc_params(P0,P1,P2): D = (P0+P2)/2 if (D-P1).mag()==0: return None, None R = D - ( (D-P0).mag()**2/(D-P1).mag() )*(P1-D).unit() p0a, p1a, p2a = (P0-R).angle()%(2*math.pi), (P1-R).angle()%(2*math.pi), (P2-R).angle()%(2*math.pi) alpha = (p2a - p0a) % (2*math.pi) if (p0a1000000 or abs(R.y)>1000000 or (R-P0).mag options.biarc_tolerance and depth0: # end = np[-1][-1][1] # dist = None # for i in range(len(p)): # start = p[i][0][1] # dist = max( ( -( ( end[0]-start[0])**2+(end[1]-start[1])**2 ) ,i) , dist ) # np += [p[dist[1]][:]] # del p[dist[1]] # p = np[:] # for subpath in p: # c += [ [ [subpath[0][1][0]*xs,subpath[0][1][1]*ys] , 'move', 0, 0] ] # for i in range(1,len(subpath)): # sp1 = [ [subpath[i-1][j][0]*xs, subpath[i-1][j][1]*ys] for j in range(3)] # sp2 = [ [subpath[i ][j][0]*xs, subpath[i ][j][1]*ys] for j in range(3)] # c += biarc(sp1,sp2,0,0) # c += [ [ [subpath[-1][1][0]*xs,subpath[-1][1][1]*ys] ,'end',0,0] ] # return c def parse_curve(self, p, w = None, f = None): c = [] if self.options.Xscale!=self.options.Yscale: xs,ys = self.options.Xscale,self.options.Yscale self.options.Xscale,self.options.Yscale = 1.0, 1.0 else : xs,ys = 1.0,1.0 ### Sort to reduce Rapid distance k = range(1,len(p)) keys = [0] while len(k)>0: end = p[keys[-1]][-1][1] dist = None for i in range(len(k)): start = p[k[i]][0][1] dist = max( ( -( ( end[0]-start[0])**2+(end[1]-start[1])**2 ) ,i) , dist ) keys += [k[dist[1]]] del k[dist[1]] for k in keys: subpath = p[k] c += [ [ [subpath[0][1][0]*xs,subpath[0][1][1]*ys] , 'move', 0, 0] ] for i in range(1,len(subpath)): sp1 = [ [subpath[i-1][j][0]*xs, subpath[i-1][j][1]*ys] for j in range(3)] sp2 = [ [subpath[i ][j][0]*xs, subpath[i ][j][1]*ys] for j in range(3)] # print_(w) c += biarc(sp1,sp2,0,0) if w==None else biarc(sp1,sp2,-f(w[k][i-1]),-f(w[k][i])) c += [ [ [subpath[-1][1][0]*xs,subpath[-1][1][1]*ys] ,'end',0,0] ] return c def draw_curve(self, curve, group=None, style=biarc_style): if group==None: group = inkex.etree.SubElement( self.biarcGroup, inkex.addNS('g','svg') ) s, arcn = '', 0 for si in curve: if s!='': if s[1] == 'line': inkex.etree.SubElement( group, inkex.addNS('path','svg'), { 'style': style['line'], 'd':'M %s,%s L %s,%s' % (s[0][0], s[0][1], si[0][0], si[0][1]), 'comment': str(s) } ) elif s[1] == 'arc': arcn += 1 sp = s[0] c = s[2] a = ( (P(si[0])-P(c)).angle() - (P(s[0])-P(c)).angle() )%math.pi2 #s[3] if s[3]*a<0: if a>0: a = a-math.pi2 else: a = math.pi2+a r = math.sqrt( (sp[0]-c[0])**2 + (sp[1]-c[1])**2 ) a_st = ( math.atan2(sp[0]-c[0],- (sp[1]-c[1])) - math.pi/2 ) % (math.pi*2) if a>0: a_end = a_st+a else: a_end = a_st*1 a_st = a_st+a inkex.etree.SubElement( group, inkex.addNS('path','svg'), { 'style': style['biarc%s' % (arcn%2)], inkex.addNS('cx','sodipodi'): str(c[0]), inkex.addNS('cy','sodipodi'): str(c[1]), inkex.addNS('rx','sodipodi'): str(r), inkex.addNS('ry','sodipodi'): str(r), inkex.addNS('start','sodipodi'): str(a_st), inkex.addNS('end','sodipodi'): str(a_end), inkex.addNS('open','sodipodi'): 'true', inkex.addNS('type','sodipodi'): 'arc', 'comment': str(s) }) s = si def check_dir(self): if (os.path.isdir(self.options.directory)): if (os.path.isfile(self.options.directory+'/header')): f = open(self.options.directory+'/header', 'r') self.header = f.read() f.close() else: self.header = defaults['header'] if (os.path.isfile(self.options.directory+'/footer')): f = open(self.options.directory+'/footer','r') self.footer = f.read() f.close() else: self.footer = defaults['footer'] self.header += self.options.unit + ( """#4 = %f (Feed) #5 = %f (Scale xy) #7 = %f (Scale z) #8 = %f (Offset x) #9 = %f (Offset y) #10 = %f (Offset z) #11 = %f (Safe distanse)\n""" % (self.options.feed, self.options.Xscale if self.options.Xscale==self.options.Yscale else 1, self.options.Zscale, self.options.Xoffset, self.options.Yoffset, self.options.Zoffset, self.options.Zsafe) if not self.options.generate_not_parametric_code else "" ) return True else: inkex.errormsg(_("Directory does not exist!")) return False def generate_gcode(self, curve, depth): def c(c): c = [c[i] if iself.options.min_arc_radius: r1, r2 = (P(s[0])-P(s[2])), (P(si[0])-P(s[2])) if abs(r1.mag()-r2.mag()) < 0.001 : if lg=="G00": g += "G01" + c([None,None,s[5][0]+depth]) + feed + "\n" # g += ("G02" if s[3]>0 else "G03") + c(si[0]+[ s[5][1]+depth, (s[2][0]-s[0][0]),(s[2][1]-s[0][1]), (s[5][1]-s[5][0])/2 ]) + feed + "\n" g += ("G02" if s[3]>0 else "G03") + c(si[0]+[ s[5][1]+depth, (s[2][0]-s[0][0]) ]) + feed + "\n" else: r = (r1.mag()+r2.mag())/2 g += ("G02" if s[3]>0 else "G03") + c(si[0]+[s[5][1]+depth]) + " R%f" % (r*self.options.Xscale) + feed + "\n" lg = 'G02' else: if lg=="G00": g += "G01" + c([None,None,s[5][0]+depth]) + "\n" g += "G01" +c(si[0]+[s[5][1]+depth]) + feed + "\n" lg = 'G01' if si[1] == 'end': g += "G00" + c([None,None,zs]) + "\n" return g def effect(self): global options options = self.options if len(self.options.ids)<=0: inkex.errormsg(_("This extension requires at least one selected path.")) return ################################################################################ ### ### Curve to Gcode ### ################################################################################ if self.options.function == 'Curve': if not self.check_dir() : return gcode = self.header # Set group if len(self.options.ids)>0: self.biarcGroup = inkex.etree.SubElement( self.selected[self.options.ids[0]].getparent(), inkex.addNS('g','svg') ) options.Group = self.biarcGroup p = [] for id, node in self.selected.iteritems(): if node.tag == inkex.addNS('path','svg'): csp = cubicsuperpath.parsePath(node.get("d")) if 'transform' in node.keys(): trans = node.get('transform') trans = simpletransform.parseTransform(trans) simpletransform.applyTransformToPath(trans, csp) p += csp # gcode += '(Found path %s)\n' % node.get('id').replace('(','').replace(')','').replace('\n','') curve = self.parse_curve(p) self.draw_curve(curve) if self.options.Zstep == 0 : Zstep = 1 for step in range( 0, int(math.ceil( abs( (self.options.Zdepth-self.options.Zsurface)/self.options.Zstep )) ) ): Zpos = max( self.options.Zdepth, self.options.Zsurface - abs(self.options.Zstep*(step+1)) ) gcode += self.generate_gcode(curve,Zpos) gcode += self.footer try: f = open(self.options.directory+'/'+self.options.file, "w") f.write(gcode) f.close() except: inkex.errormsg(_("Can not write to specified file!")) return ################################################################################ ### ### Calculate area curves ### ################################################################################ elif self.options.function=='Area curves' : if self.options.tool_diameter<=0 : inkex.errormsg(_("Tool diameter must be > 0!")) return for id, node in self.selected.iteritems(): self.biarcGroup = inkex.etree.SubElement( node.getparent(), inkex.addNS('g','svg') ) break for id, node in self.selected.iteritems(): if node.tag == inkex.addNS('path','svg'): d = node.get('d') csp = cubicsuperpath.parsePath(d) # Finding top most point in path (min y value) my = (None, 0, 0, 0) for i in range(len(csp)): for j in range(1,len(csp[i])): ax,ay,bx,by,cx,cy,x0,y0 = bezmisc.bezierparameterize((csp[i][j-1][1],csp[i][j-1][2],csp[i][j][0],csp[i][j][1])) if ay == 0 : roots = [ -cy/(2*by) ] if by !=0 else [] else: det = (2.0*by)**2 - 4.0*(3*ay*cy) roots = [ (-2*by+math.sqrt(det))/(6*ay), (-2*by+math.sqrt(det))/(6*ay) ] if det>=0 else [] roots += [1,0] for t in roots : if 0<=t<=1: y = ay*(t**3)+by*(t**2)+cy*t+y0 x = ax*(t**3)+bx*(t**2)+cx*t+x0 if my[0]>y or my[0] == None : my = (y,i,j,t,x) elif my[0]==y and x>my[4] : my = (y,i,j,t,x) if my[0]!=None : subp = csp[my[1]] del csp[my[1]] j = my[2] if my[3] in [0,1]: if my[3] == 0: j=j-1 subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1] subp = [ [subp[j][1], subp[j][1], subp[j][2]] ] + subp[j+1:] + subp[:j] + [ [subp[j][0], subp[j][1], subp[j][1]] ] else: sp1,sp2,sp3 = cspbezsplit(subp[j-1],subp[j],my[3]) subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1] subp = [ [ sp2[1], sp2[1],sp2[2] ] ] + [sp3] + subp[j+1:] + subp[:j-1] + [sp1] + [[ sp2[0], sp2[1],sp2[1] ]] csp = [subp] + csp # Reverce path if needed st,end = [],[] for i in range(1,len(subp)): pst = P( csp_at_t(subp[i-1],subp[i],.1) ) pend = P( csp_at_t(subp[-i-1],subp[-i],.9) ) if st==[] and (pst-P(subp[0][1])).mag()>straight_tolerance: st = pst - P(subp[0][1]) if end==[] and (pend-P(subp[0][1])).mag()>straight_tolerance: end = pend - P(subp[0][1]) if st!=[] and end!=[]: break if math.atan2(st.x,st.y) % math.pi2 < math.atan2(end.x,end.y) % math.pi2 : for i in range(len(csp)): n = [] for j in csp[i]: n = [ [j[2][:],j[1][:],j[0][:]] ] + n csp[i] = n[:] d = cubicsuperpath.formatPath(csp) d = re.sub(r'(?i)(m[^mz]+)',r'\1 Z ',d) d = re.sub(r'(?i)\s*z\s*z\s*',r' Z ',d) d = re.sub(r'(?i)\s*([A-Za-z])\s*',r' \1 ',d) r = self.options.area_inkscape_radius sign=1 if r>0 else -1 tool_d = self.options.tool_diameter for i in range(self.options.max_area_curves): radius = - tool_d * (i+0.5) * sign if abs(radius)>abs(r): radius = -r inkex.etree.SubElement( self.biarcGroup, inkex.addNS('path','svg'), { inkex.addNS('type','sodipodi'): 'inkscape:offset', inkex.addNS('radius','inkscape'): str(radius), inkex.addNS('original','inkscape'): d, 'style': biarc_style_i['area'] }) if radius == -r : break ################################################################################ ### ### Engraving ### ################################################################################ elif self.options.function=='Engraving' : ################################################################################ ### ### To find center of cutter a system of non linear equations will be solved ### using Newton's method ### ################################################################################ if not self.check_dir() : return gcode = self.header def inv(a): # invert matrix 3x3 det = float(a[0][0]*a[1][1]*a[2][2] + a[0][1]*a[1][2]*a[2][0] + a[1][0]*a[2][1]*a[0][2] - a[0][2]*a[1][1]*a[2][0] - a[0][0]*a[2][1]*a[1][2] - a[0][1]*a[2][2]*a[1][0]) if det==0: return None return [ [ (a[1][1]*a[2][2] - a[2][1]*a[1][2])/det, -(a[0][1]*a[2][2] - a[2][1]*a[0][2])/det, (a[0][1]*a[1][2] - a[1][1]*a[0][2])/det ], [ -(a[1][0]*a[2][2] - a[2][0]*a[1][2])/det, (a[0][0]*a[2][2] - a[2][0]*a[0][2])/det, -(a[0][0]*a[1][2] - a[1][0]*a[0][2])/det ], [ (a[1][0]*a[2][1] - a[2][0]*a[1][1])/det, -(a[0][0]*a[2][1] - a[2][0]*a[0][1])/det, (a[0][0]*a[1][1] - a[1][0]*a[0][1])/det ] ] def find_cutter_center((x1,y1),(nx1,ny1), sp1,sp2,t3 = .5): bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]) ax,ay,bx,by,cx,cy,dx,dy=bezmisc.bezierparameterize(bez) fx=ax*(t3*t3*t3)+bx*(t3*t3)+cx*t3+dx fy=ay*(t3*t3*t3)+by*(t3*t3)+cy*t3+dy f1x = -(3*ay*(t3*t3)+2*by*t3+cy) f1y = 3*ax*(t3*t3)+2*bx*t3+cx if (ny*f1x-nx*f1y) != 0 : t1 = ((fy-y1)*f1x - (fx-x1)*f1y)/(ny*f1x-nx*f1y) t2 = (x1-fx-t1*nx)/f1x if f1x != 0 else (y1-fy-t1*ny)/f1y if (ny*f1x-nx*f1y)==0 or t1<0 or t2<0 : t1 = self.options.tool_diameter t2 = self.options.tool_diameter/math.sqrt(f1x*f1x+f1y*f1y) t = [ t1, t2, t3 ] i = 0 F = [0.,0.,0.] F1 = [[0.,0.,0.],[0.,0.,0.],[0.,0.,0.]] print_(( F[0], F[1], F[2] )) print_(( abs(F[0]),abs(F[1]),abs(F[2]) )) while i==0 or abs(F[0])+abs(F[1])+math.sqrt(abs(F[2])) >engraving_tolerance and i<10: t1,t2,t3 = t[0],t[1],t[2] fx=ax*(t3*t3*t3)+bx*(t3*t3)+cx*t3+dx fy=ay*(t3*t3*t3)+by*(t3*t3)+cy*t3+dy f1x=3*ax*(t3*t3)+2*bx*t3+cx f1y=3*ay*(t3*t3)+2*by*t3+cy i+=1 tx = fx-x1-nx1*t1 ty = fy-y1-ny1*t1 F[0] = x1+nx1*t1-fx+t2*f1y F[1] = y1+ny1*t1-fy-t2*f1x F[2] = t1*t1 - tx*tx -ty*ty F1[0][0] = nx1 F1[0][1] = f1y F1[0][2] = -f1x+t2*(6*ay*t3+2*by) F1[1][0] = ny1 F1[1][1] = -f1x F1[1][2] = -f1y-t2*(6*ax*t3+2*bx) F1[2][0] = 2*t1+2*nx1*tx +2*ny1*ty F1[2][1] = 0 F1[2][2] = -2*f1x*tx -2*f1y*ty F1 = inv(F1) print_(("ver:",sys.version)) # if ( math.isnan(F[0]) or math.isnan(F[1]) or math.isnan(F[2]) or math.isinf(F[0]) or math.isinf(F[1]) or math.isinf(F[2]) ): # return t+[1e100,i] if ( isnan(F[0]) or isnan(F[1]) or isnan(F[2]) or isinf(F[0]) or isinf(F[1]) or isinf(F[2]) ): return t+[1e100,i] if F1!= None: t[0] -= F1[0][0]*F[0] + F1[0][1]*F[1] + F1[0][2]*F[2] t[1] -= F1[1][0]*F[0] + F1[1][1]*F[1] + F1[1][2]*F[2] t[2] -= F1[2][0]*F[0] + F1[2][1]*F[1] + F1[2][2]*F[2] else: break print_(( F[0], F[1], F[2] )) print_(( abs(F[0]),abs(F[1]),abs(F[2]) )) return t+[abs(F[0])+abs(F[1])+math.sqrt(abs(F[2])),i] def csp_simpe_bound(csp): minx,miny,maxx,maxy = None,None,None for subpath in csp: for sp in subpath : for p in sp: minx = min(minx,p[0]) if minx!=None else p[0] miny = min(miny,p[1]) if miny!=None else p[1] maxx = max(maxx,p[0]) if maxx!=None else p[0] maxy = max(maxy,p[1]) if maxy!=None else p[1] return minx,miny,maxx,maxy self.Group = inkex.etree.SubElement( self.selected[self.options.ids[0]].getparent(), inkex.addNS('g','svg') ) cspe =[] we = [] for id, node in self.selected.iteritems(): if node.tag == inkex.addNS('path','svg'): cspi = cubicsuperpath.parsePath(node.get('d')) for csp in cspi: # Remove zerro length segments i = 1 while i 0 and ang1 < self.options.engraving_sharp_angle_tollerance : # inner angle n[-1][2] = True elif ang < 0 and ang1 < self.options.engraving_sharp_angle_tollerance : # outer angle a = -math.acos(nx*nx2+ny*ny2) for t in [.0,.25,.75,1.]: n1 += [ [ [x1,y1], [nx*math.cos(a*t)-ny*math.sin(a*t),nx*math.sin(a*t)+ny*math.cos(a*t)], False, True, i ] ] nl += [ n ] + ([ n1 ] if n1!=[] else []) # Modify first/last points if curve is closed if abs(csp[-1][1][0]-csp[0][1][0]) 0 and 180-math.acos(nx*nx2+ny*ny2)*180/math.pi < self.options.engraving_sharp_angle_tollerance : # inner angle nl[-1][-1][2] = True elif ang < 0 and 180-math.acos(nx*nx2+ny*ny2)*180/math.pi < self.options.engraving_sharp_angle_tollerance : # outer angle a = -math.acos(nx*nx2+ny*ny2) n1 = [] for t in [.0,.25,.75,1.]: n1 += [ [ [x1,y1], [nx*math.cos(a*t)-ny*math.sin(a*t),nx*math.sin(a*t)+ny*math.cos(a*t)], False, True, i ] ] nl += [ n1 ] if self.options.engraving_draw_calculation_paths==True: for i in nl: for p in i: inkex.etree.SubElement( self.Group, inkex.addNS('path','svg'), { "d": "M %f,%f L %f,%f" %(p[0][0],p[0][1],p[0][0]+p[1][0]*10,p[0][1]+p[1][1]*10), 'style': "stroke:#0000ff; stroke-opacity:0.46; stroke-width:0.1; fill:none", }) # Calculate offset points csp_points = [] for ki in xrange(len(nl)): p = [] for ti in xrange(3) if ki!=len(nl)-1 else xrange(4): n = nl[ki][ti] # print_((ki,ti,"@@@@@@@")) x1,y1 = n[0] nx,ny = n[1] d, r = 0, None if ti==0 and nl[ki-1][-1][2] == True or ti==3 and nl[ki][ti][2] == True: # Point is a sharp angle r=0p r = 0 else : # print_((ki,ti,"@@@@@@@!!!!!!!!!!")) for j in xrange(0,len(cspi)): for i in xrange(1,len(cspi[j])): # print_(i) d = point_to_csp_bound_dist([x1,y1], cspi[j][i-1], cspi[j][i], self.options.engraving_max_dist*2) # print_(d) if d>=self.options.engraving_max_dist*2 : r = min(d/2,r) if r!=None else d/2 continue for n1 in xrange(self.options.engraving_newton_iterations): # print_(n1) t = find_cutter_center((x1,y1),(nx,ny), cspi[j][i-1], cspi[j][i], float(n1)/(self.options.engraving_newton_iterations-1)) if t[0] > engraving_tolerance and 0<=t[2]<=1 and abs(t[3])0: r = min( d,r) if r!=None else d else : r = min(r,self.options.engraving_max_dist) if r!=None else self.options.engraving_max_dist # print_((r,d)) else: r = min(t[0],r) if r!=None else t[0] for j in xrange(0,len(cspi)): for i in xrange(0,len(cspi[j])): x2,y2 = cspi[j][i][1] if (abs(x1-x2)>engraving_tolerance or abs(y1-y2)>engraving_tolerance ) and (x2*nx - x1*nx + y2*ny - y1*ny) != 0: t1 = .5 * ( (x1-x2)**2+(y1-y2)**2 ) / (x2*nx - x1*nx + y2*ny - y1*ny) if t1>0 : r = min(t1,r) if r!=None else t1 if self.options.engraving_draw_calculation_paths==True: inkex.etree.SubElement( self.Group, inkex.addNS('path','svg'), {'style': "fill:#ff00ff; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;", inkex.addNS('cx','sodipodi'): str(x1+nx*r), inkex.addNS('cy','sodipodi'): str(y1+ny*r), inkex.addNS('rx','sodipodi'): str(1), inkex.addNS('ry','sodipodi'): str(1), inkex.addNS('type','sodipodi'): 'arc'}) inkex.etree.SubElement( self.Group, inkex.addNS('path','svg'), {'style': "fill:none; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;", inkex.addNS('cx','sodipodi'): str(x1+nx*r), inkex.addNS('cy','sodipodi'): str(y1+ny*r),inkex.addNS('rx','sodipodi'): str(r), inkex.addNS('ry','sodipodi'): str(r), inkex.addNS('type','sodipodi'): 'arc'}) r = min(r, self.options.engraving_max_dist) p += [ [x1+nx*r,y1+ny*r,r] ] if len(csp_points)>0 : csp_points[-1] += [p[0]] csp_points += [ p ] # Splitting path to pieces each of them not further from path more than engraving_max_dist engraving_path = [ [] ] for p_ in csp_points : for p in p_: if p[2]0 : cspm[-1][2] = sp1[2] cspm += [sp2[:], sp3[:], sp4[:]] w += [p[i][2] for i in range(1,4)] else : cspm += [sp1[:], sp2[:], sp3[:], sp4[:]] w += [p[i][2] for i in range(4)] node = inkex.etree.SubElement( self.Group, inkex.addNS('path','svg'), { "d": cubicsuperpath.formatPath([cspm]), 'style': biarc_style_i['biarc1'] }) for i in xrange(len(cspm)): inkex.etree.SubElement( self.Group, inkex.addNS('path','svg'), {'style': "fill:none; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;", inkex.addNS('cx','sodipodi'): str(cspm[i][1][0]), inkex.addNS('cy','sodipodi'): str(cspm[i][1][1]),inkex.addNS('rx','sodipodi'): str(w[i]), inkex.addNS('ry','sodipodi'): str(w[i]), inkex.addNS('type','sodipodi'): 'arc'}) cspe += [cspm] we += [w] # print_(we) if self.options.engraving_cutter_shape_function != "": f = eval('lambda w: ' + self.options.engraving_cutter_shape_function.strip('"')) else: f = lambda w: w curve = self.parse_curve(cspe,we,f) self.draw_curve(curve,self.Group) gcode += self.generate_gcode(curve,self.options.Zsurface) gcode += self.footer try: f = open(self.options.directory+'/'+self.options.file, "w") f.write(gcode) f.close() except: inkex.errormsg(_("Can not write to specified file!")) return e = Gcode_tools() e.affect()