1 #!/usr/bin/env python
2 """
3 Copyright (C) 2009 Nick Drobchenko, nick@cnc-club.ru
4 based on gcode.py (C) 2007 hugomatic...
5 based on addnodes.py (C) 2005,2007 Aaron Spike, aaron@ekips.org
6 based on dots.py (C) 2005 Aaron Spike, aaron@ekips.org
7 based on interp.py (C) 2005 Aaron Spike, aaron@ekips.org
8 based on bezmisc.py (C) 2005 Aaron Spike, aaron@ekips.org
9 based on cubicsuperpath.py (C) 2005 Aaron Spike, aaron@ekips.org
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 """
26 ###
27 ### Gcodetools v 1.6.03
28 ###
29 gcodetools_current_version = "1.6.03"
31 import inkex, simplestyle, simplepath
32 import cubicsuperpath, simpletransform, bezmisc
34 import os
35 import math
36 import bezmisc
37 import re
38 import copy
39 import sys
40 import time
41 import cmath
42 import numpy
43 import codecs
44 import random
45 import gettext
46 _ = gettext.gettext
49 ### Check if inkex has errormsg (0.46 version doesnot have one.) Could be removed later.
50 if "errormsg" not in dir(inkex):
51 inkex.errormsg = lambda msg: sys.stderr.write((unicode(msg) + "\n").encode("UTF-8"))
54 def bezierslopeatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
55 ax,ay,bx,by,cx,cy,x0,y0=bezmisc.bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
56 dx=3*ax*(t**2)+2*bx*t+cx
57 dy=3*ay*(t**2)+2*by*t+cy
58 if dx==dy==0 :
59 dx = 6*ax*t+2*bx
60 dy = 6*ay*t+2*by
61 if dx==dy==0 :
62 dx = 6*ax
63 dy = 6*ay
64 if dx==dy==0 :
65 print_("Slope error x = %s*t^3+%s*t^2+%s*t+%s, y = %s*t^3+%s*t^2+%s*t+%s, t = %s, dx==dy==0" % (ax,bx,cx,dx,ay,by,cy,dy,t))
66 print_(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
67 dx, dy = 1, 1
69 return dx,dy
70 bezmisc.bezierslopeatt = bezierslopeatt
73 def ireplace(self,old,new,count=0):
74 pattern = re.compile(re.escape(old),re.I)
75 return re.sub(pattern,new,self,count)
77 ################################################################################
78 ###
79 ### Styles and additional parameters
80 ###
81 ################################################################################
83 math.pi2 = math.pi*2
84 straight_tolerance = 0.0001
85 straight_distance_tolerance = 0.0001
86 engraving_tolerance = 0.0001
87 loft_lengths_tolerance = 0.0000001
88 options = {}
89 defaults = {
90 'header': """%
91 (Header)
92 (Generated by gcodetools from Inkscape.)
93 (Using default header. To add your own header create file "header" in the output dir.)
94 M3
95 (Header end.)
96 """,
97 'footer': """
98 (Footer)
99 M5
100 G00 X0.0000 Y0.0000
101 M2
102 (Using default footer. To add your own footer create file "footer" in the output dir.)
103 (end)
104 %"""
105 }
107 intersection_recursion_depth = 10
108 intersection_tolerance = 0.00001
110 styles = {
111 "loft_style" : {
112 'main curve': simplestyle.formatStyle({ 'stroke': '#88f', 'fill': 'none', 'stroke-width':'1', 'marker-end':'url(#Arrow2Mend)' }),
113 },
114 "biarc_style" : {
115 'biarc0': simplestyle.formatStyle({ 'stroke': '#88f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
116 'biarc1': simplestyle.formatStyle({ 'stroke': '#8f8', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
117 'line': simplestyle.formatStyle({ 'stroke': '#f88', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
118 'area': simplestyle.formatStyle({ 'stroke': '#777', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
119 },
120 "biarc_style_dark" : {
121 'biarc0': simplestyle.formatStyle({ 'stroke': '#33a', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
122 'biarc1': simplestyle.formatStyle({ 'stroke': '#3a3', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
123 'line': simplestyle.formatStyle({ 'stroke': '#a33', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
124 'area': simplestyle.formatStyle({ 'stroke': '#222', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
125 },
126 "biarc_style_dark_area" : {
127 'biarc0': simplestyle.formatStyle({ 'stroke': '#33a', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
128 'biarc1': simplestyle.formatStyle({ 'stroke': '#3a3', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
129 'line': simplestyle.formatStyle({ 'stroke': '#a33', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
130 'area': simplestyle.formatStyle({ 'stroke': '#222', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
131 },
132 "biarc_style_i" : {
133 'biarc0': simplestyle.formatStyle({ 'stroke': '#880', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
134 'biarc1': simplestyle.formatStyle({ 'stroke': '#808', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
135 'line': simplestyle.formatStyle({ 'stroke': '#088', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
136 'area': simplestyle.formatStyle({ 'stroke': '#999', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
137 },
138 "biarc_style_dark_i" : {
139 'biarc0': simplestyle.formatStyle({ 'stroke': '#dd5', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
140 'biarc1': simplestyle.formatStyle({ 'stroke': '#d5d', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
141 'line': simplestyle.formatStyle({ 'stroke': '#5dd', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
142 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
143 },
144 "biarc_style_lathe_feed" : {
145 'biarc0': simplestyle.formatStyle({ 'stroke': '#07f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
146 'biarc1': simplestyle.formatStyle({ 'stroke': '#0f7', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
147 'line': simplestyle.formatStyle({ 'stroke': '#f44', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
148 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
149 },
150 "biarc_style_lathe_passing feed" : {
151 'biarc0': simplestyle.formatStyle({ 'stroke': '#07f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
152 'biarc1': simplestyle.formatStyle({ 'stroke': '#0f7', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
153 'line': simplestyle.formatStyle({ 'stroke': '#f44', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
154 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
155 },
156 "biarc_style_lathe_fine feed" : {
157 'biarc0': simplestyle.formatStyle({ 'stroke': '#7f0', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
158 'biarc1': simplestyle.formatStyle({ 'stroke': '#f70', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
159 'line': simplestyle.formatStyle({ 'stroke': '#744', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
160 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
161 },
162 "area artefact": simplestyle.formatStyle({ 'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width':'1' }),
163 "area artefact arrow": simplestyle.formatStyle({ 'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width':'1' }),
164 "dxf_points": simplestyle.formatStyle({ "stroke": "#ff0000", "fill": "#ff0000"}),
166 }
169 ################################################################################
170 ### Cubic Super Path additional functions
171 ################################################################################
173 def csp_simple_bound(csp):
174 minx,miny,maxx,maxy = None,None,None,None
175 for subpath in csp:
176 for sp in subpath :
177 for p in sp:
178 minx = min(minx,p[0]) if minx!=None else p[0]
179 miny = min(miny,p[1]) if miny!=None else p[1]
180 maxx = max(maxx,p[0]) if maxx!=None else p[0]
181 maxy = max(maxy,p[1]) if maxy!=None else p[1]
182 return minx,miny,maxx,maxy
185 def csp_segment_to_bez(sp1,sp2) :
186 return sp1[1:]+sp2[:2]
189 def bound_to_bound_distance(sp1,sp2,sp3,sp4) :
190 min_dist = 1e100
191 max_dist = 0
192 points1 = csp_segment_to_bez(sp1,sp2)
193 points2 = csp_segment_to_bez(sp3,sp4)
194 for i in range(4) :
195 for j in range(4) :
196 min_, max_ = line_to_line_min_max_distance_2(points1[i-1], points1[i], points2[j-1], points2[j])
197 min_dist = min(min_dist,min_)
198 max_dist = max(max_dist,max_)
199 print_("bound_to_bound", min_dist, max_dist)
200 return min_dist, max_dist
202 def csp_to_point_distance(csp, p, dist_bounds = [0,1e100], tolerance=.01) :
203 min_dist = [1e100,0,0,0]
204 for j in range(len(csp)) :
205 for i in range(1,len(csp[j])) :
206 d = csp_seg_to_point_distance(csp[j][i-1],csp[j][i],p,sample_points = 5, tolerance = .01)
207 if d[0] < dist_bounds[0] :
208 # draw_pointer( list(csp_at_t(subpath[dist[2]-1],subpath[dist[2]],dist[3]))
209 # +list(csp_at_t(csp[dist[4]][dist[5]-1],csp[dist[4]][dist[5]],dist[6])),"red","line", comment = math.sqrt(dist[0]))
210 return [d[0],j,i,d[1]]
211 else :
212 if d[0] < min_dist[0] : min_dist = [d[0],j,i,d[1]]
213 return min_dist
215 def csp_seg_to_point_distance(sp1,sp2,p,sample_points = 5, tolerance = .01) :
216 ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
217 dx, dy = dx-p[0], dy-p[1]
218 if sample_points < 2 : sample_points = 2
219 d = min( [(p[0]-sp1[1][0])**2 + (p[1]-sp1[1][1])**2,0.], [(p[0]-sp2[1][0])**2 + (p[1]-sp2[1][1])**2,1.] )
220 for k in range(sample_points) :
221 t = float(k)/(sample_points-1)
222 i = 0
223 while i==0 or abs(f)>0.000001 and i<20 :
224 t2,t3 = t**2,t**3
225 f = (ax*t3+bx*t2+cx*t+dx)*(3*ax*t2+2*bx*t+cx) + (ay*t3+by*t2+cy*t+dy)*(3*ay*t2+2*by*t+cy)
226 df = (6*ax*t+2*bx)*(ax*t3+bx*t2+cx*t+dx) + (3*ax*t2+2*bx*t+cx)**2 + (6*ay*t+2*by)*(ay*t3+by*t2+cy*t+dy) + (3*ay*t2+2*by*t+cy)**2
227 if df!=0 :
228 t = t - f/df
229 else :
230 break
231 i += 1
232 if 0<=t<=1 :
233 p1 = csp_at_t(sp1,sp2,t)
234 d1 = (p1[0]-p[0])**2 + (p1[1]-p[1])**2
235 if d1 < d[0] :
236 d = [d1,t]
237 return d
240 def csp_seg_to_csp_seg_distance(sp1,sp2,sp3,sp4, dist_bounds = [0,1e100], sample_points = 5, tolerance=.01) :
241 # check the ending points first
242 dist = csp_seg_to_point_distance(sp1,sp2,sp3[1],sample_points, tolerance)
243 dist += [0.]
244 if dist[0] <= dist_bounds[0] : return dist
245 d = csp_seg_to_point_distance(sp1,sp2,sp4[1],sample_points, tolerance)
246 if d[0]<dist[0] :
247 dist = d+[1.]
248 if dist[0] <= dist_bounds[0] : return dist
249 d = csp_seg_to_point_distance(sp3,sp4,sp1[1],sample_points, tolerance)
250 if d[0]<dist[0] :
251 dist = [d[0],0.,d[1]]
252 if dist[0] <= dist_bounds[0] : return dist
253 d = csp_seg_to_point_distance(sp3,sp4,sp2[1],sample_points, tolerance)
254 if d[0]<dist[0] :
255 dist = [d[0],1.,d[1]]
256 if dist[0] <= dist_bounds[0] : return dist
257 sample_points -= 2
258 if sample_points < 1 : sample_points = 1
259 ax1,ay1,bx1,by1,cx1,cy1,dx1,dy1 = csp_parameterize(sp1,sp2)
260 ax2,ay2,bx2,by2,cx2,cy2,dx2,dy2 = csp_parameterize(sp3,sp4)
261 # try to find closes points using Newtons method
262 for k in range(sample_points) :
263 for j in range(sample_points) :
264 t1,t2 = float(k+1)/(sample_points+1), float(j)/(sample_points+1)
265 t12, t13, t22, t23 = t1*t1, t1*t1*t1, t2*t2, t2*t2*t2
266 i = 0
267 F1, F2, F = [0,0], [[0,0],[0,0]], 1e100
268 x,y = ax1*t13+bx1*t12+cx1*t1+dx1 - (ax2*t23+bx2*t22+cx2*t2+dx2), ay1*t13+by1*t12+cy1*t1+dy1 - (ay2*t23+by2*t22+cy2*t2+dy2)
269 while i<2 or abs(F-Flast)>tolerance and i<30 :
270 #draw_pointer(csp_at_t(sp1,sp2,t1))
271 f1x = 3*ax1*t12+2*bx1*t1+cx1
272 f1y = 3*ay1*t12+2*by1*t1+cy1
273 f2x = 3*ax2*t22+2*bx2*t2+cx2
274 f2y = 3*ay2*t22+2*by2*t2+cy2
275 F1[0] = 2*f1x*x + 2*f1y*y
276 F1[1] = -2*f2x*x - 2*f2y*y
277 F2[0][0] = 2*(6*ax1*t1+2*bx1)*x + 2*f1x*f1x + 2*(6*ay1*t1+2*by1)*y +2*f1y*f1y
278 F2[0][1] = -2*f1x*f2x - 2*f1y*f2y
279 F2[1][0] = -2*f2x*f1x - 2*f2y*f1y
280 F2[1][1] = -2*(6*ax2*t2+2*bx2)*x + 2*f2x*f2x - 2*(6*ay2*t2+2*by2)*y + 2*f2y*f2y
281 F2 = inv_2x2(F2)
282 if F2!=None :
283 t1 -= ( F2[0][0]*F1[0] + F2[0][1]*F1[1] )
284 t2 -= ( F2[1][0]*F1[0] + F2[1][1]*F1[1] )
285 t12, t13, t22, t23 = t1*t1, t1*t1*t1, t2*t2, t2*t2*t2
286 x,y = ax1*t13+bx1*t12+cx1*t1+dx1 - (ax2*t23+bx2*t22+cx2*t2+dx2), ay1*t13+by1*t12+cy1*t1+dy1 - (ay2*t23+by2*t22+cy2*t2+dy2)
287 Flast = F
288 F = x*x+y*y
289 else :
290 break
291 i += 1
292 if F < dist[0] and 0<=t1<=1 and 0<=t2<=1:
293 dist = [F,t1,t2]
294 if dist[0] <= dist_bounds[0] :
295 return dist
296 return dist
299 def csp_to_csp_distance(csp1,csp2, dist_bounds = [0,1e100], tolerance=.01) :
300 dist = [1e100,0,0,0,0,0,0]
301 for i1 in range(len(csp1)) :
302 for j1 in range(1,len(csp1[i1])) :
303 for i2 in range(len(csp2)) :
304 for j2 in range(1,len(csp2[i2])) :
305 d = csp_seg_bound_to_csp_seg_bound_max_min_distance(csp1[i1][j1-1],csp1[i1][j1],csp2[i2][j2-1],csp2[i2][j2])
306 if d[0] >= dist_bounds[1] : continue
307 if d[1] < dist_bounds[0] : return [d[1],i1,j1,1,i2,j2,1]
308 d = csp_seg_to_csp_seg_distance(csp1[i1][j1-1],csp1[i1][j1],csp2[i2][j2-1],csp2[i2][j2], dist_bounds, tolerance=tolerance)
309 if d[0] < dist[0] :
310 dist = [d[0], i1,j1,d[1], i2,j2,d[2]]
311 if dist[0] <= dist_bounds[0] :
312 return dist
313 if dist[0] >= dist_bounds[1] :
314 return dist
315 return dist
316 # draw_pointer( list(csp_at_t(csp1[dist[1]][dist[2]-1],csp1[dist[1]][dist[2]],dist[3]))
317 # + list(csp_at_t(csp2[dist[4]][dist[5]-1],csp2[dist[4]][dist[5]],dist[6])), "#507","line")
320 def csp_split(sp1,sp2,t=.5) :
321 [x1,y1],[x2,y2],[x3,y3],[x4,y4] = sp1[1], sp1[2], sp2[0], sp2[1]
322 x12 = x1+(x2-x1)*t
323 y12 = y1+(y2-y1)*t
324 x23 = x2+(x3-x2)*t
325 y23 = y2+(y3-y2)*t
326 x34 = x3+(x4-x3)*t
327 y34 = y3+(y4-y3)*t
328 x1223 = x12+(x23-x12)*t
329 y1223 = y12+(y23-y12)*t
330 x2334 = x23+(x34-x23)*t
331 y2334 = y23+(y34-y23)*t
332 x = x1223+(x2334-x1223)*t
333 y = y1223+(y2334-y1223)*t
334 return [sp1[0],sp1[1],[x12,y12]], [[x1223,y1223],[x,y],[x2334,y2334]], [[x34,y34],sp2[1],sp2[2]]
336 def csp_true_bounds(csp) :
337 # Finds minx,miny,maxx,maxy of the csp and return their (x,y,i,j,t)
338 minx = [float("inf"), 0, 0, 0]
339 maxx = [float("-inf"), 0, 0, 0]
340 miny = [float("inf"), 0, 0, 0]
341 maxy = [float("-inf"), 0, 0, 0]
342 for i in range(len(csp)):
343 for j in range(1,len(csp[i])):
344 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]))
345 roots = cubic_solver(0, 3*ax, 2*bx, cx) + [0,1]
346 for root in roots :
347 if type(root) is complex and abs(root.imag)<1e-10:
348 root = root.real
349 if type(root) is not complex and 0<=root<=1:
350 y = ay*(root**3)+by*(root**2)+cy*root+y0
351 x = ax*(root**3)+bx*(root**2)+cx*root+x0
352 maxx = max([x,y,i,j,root],maxx)
353 minx = min([x,y,i,j,root],minx)
355 roots = cubic_solver(0, 3*ay, 2*by, cy) + [0,1]
356 for root in roots :
357 if type(root) is complex and root.imag==0:
358 root = root.real
359 if type(root) is not complex and 0<=root<=1:
360 y = ay*(root**3)+by*(root**2)+cy*root+y0
361 x = ax*(root**3)+bx*(root**2)+cx*root+x0
362 maxy = max([y,x,i,j,root],maxy)
363 miny = min([y,x,i,j,root],miny)
364 maxy[0],maxy[1] = maxy[1],maxy[0]
365 miny[0],miny[1] = miny[1],miny[0]
367 return minx,miny,maxx,maxy
370 ############################################################################
371 ### csp_segments_intersection(sp1,sp2,sp3,sp4)
372 ###
373 ### Returns array containig all intersections between two segmets of cubic
374 ### super path. Results are [ta,tb], or [ta0, ta1, tb0, tb1, "Overlap"]
375 ### where ta, tb are values of t for the intersection point.
376 ############################################################################
377 def csp_segments_intersection(sp1,sp2,sp3,sp4) :
378 a, b = csp_segment_to_bez(sp1,sp2), csp_segment_to_bez(sp3,sp4)
380 def polish_intersection(a,b,ta,tb, tolerance = intersection_tolerance) :
381 ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize(a)
382 ax1,ay1,bx1,by1,cx1,cy1,dx1,dy1 = bezmisc.bezierparameterize(b)
383 i = 0
384 F, F1 = [.0,.0], [[.0,.0],[.0,.0]]
385 while i==0 or (abs(F[0])**2+abs(F[1])**2 > tolerance and i<10):
386 ta3, ta2, tb3, tb2 = ta**3, ta**2, tb**3, tb**2
387 F[0] = ax*ta3+bx*ta2+cx*ta+dx-ax1*tb3-bx1*tb2-cx1*tb-dx1
388 F[1] = ay*ta3+by*ta2+cy*ta+dy-ay1*tb3-by1*tb2-cy1*tb-dy1
389 F1[0][0] = 3*ax *ta2 + 2*bx *ta + cx
390 F1[0][1] = -3*ax1*tb2 - 2*bx1*tb - cx1
391 F1[1][0] = 3*ay *ta2 + 2*by *ta + cy
392 F1[1][1] = -3*ay1*tb2 - 2*by1*tb - cy1
393 det = F1[0][0]*F1[1][1] - F1[0][1]*F1[1][0]
394 if det!=0 :
395 F1 = [ [ F1[1][1]/det, -F1[0][1]/det], [-F1[1][0]/det, F1[0][0]/det] ]
396 ta = ta - ( F1[0][0]*F[0] + F1[0][1]*F[1] )
397 tb = tb - ( F1[1][0]*F[0] + F1[1][1]*F[1] )
398 else: break
399 i += 1
401 return ta, tb
404 def recursion(a,b, ta0,ta1,tb0,tb1, depth_a,depth_b) :
405 global bezier_intersection_recursive_result
406 if a==b :
407 bezier_intersection_recursive_result += [[ta0,tb0,ta1,tb1,"Overlap"]]
408 return
409 tam, tbm = (ta0+ta1)/2, (tb0+tb1)/2
410 if depth_a>0 and depth_b>0 :
411 a1,a2 = bez_split(a,0.5)
412 b1,b2 = bez_split(b,0.5)
413 if bez_bounds_intersect(a1,b1) : recursion(a1,b1, ta0,tam,tb0,tbm, depth_a-1,depth_b-1)
414 if bez_bounds_intersect(a2,b1) : recursion(a2,b1, tam,ta1,tb0,tbm, depth_a-1,depth_b-1)
415 if bez_bounds_intersect(a1,b2) : recursion(a1,b2, ta0,tam,tbm,tb1, depth_a-1,depth_b-1)
416 if bez_bounds_intersect(a2,b2) : recursion(a2,b2, tam,ta1,tbm,tb1, depth_a-1,depth_b-1)
417 elif depth_a>0 :
418 a1,a2 = bez_split(a,0.5)
419 if bez_bounds_intersect(a1,b) : recursion(a1,b, ta0,tam,tb0,tb1, depth_a-1,depth_b)
420 if bez_bounds_intersect(a2,b) : recursion(a2,b, tam,ta1,tb0,tb1, depth_a-1,depth_b)
421 elif depth_b>0 :
422 b1,b2 = bez_split(b,0.5)
423 if bez_bounds_intersect(a,b1) : recursion(a,b1, ta0,ta1,tb0,tbm, depth_a,depth_b-1)
424 if bez_bounds_intersect(a,b2) : recursion(a,b2, ta0,ta1,tbm,tb1, depth_a,depth_b-1)
425 else : # Both segments have been subdevided enougth. Let's get some intersections :).
426 intersection, t1, t2 = straight_segments_intersection([a[0]]+[a[3]],[b[0]]+[b[3]])
427 if intersection :
428 if intersection == "Overlap" :
429 t1 = ( max(0,min(1,t1[0]))+max(0,min(1,t1[1])) )/2
430 t2 = ( max(0,min(1,t2[0]))+max(0,min(1,t2[1])) )/2
431 bezier_intersection_recursive_result += [[ta0+t1*(ta1-ta0),tb0+t2*(tb1-tb0)]]
433 global bezier_intersection_recursive_result
434 bezier_intersection_recursive_result = []
435 recursion(a,b,0.,1.,0.,1.,intersection_recursion_depth,intersection_recursion_depth)
436 intersections = bezier_intersection_recursive_result
437 for i in range(len(intersections)) :
438 if len(intersections[i])<5 or intersections[i][4] != "Overlap" :
439 intersections[i] = polish_intersection(a,b,intersections[i][0],intersections[i][1])
440 return intersections
443 def csp_segments_true_intersection(sp1,sp2,sp3,sp4) :
444 intersections = csp_segments_intersection(sp1,sp2,sp3,sp4)
445 res = []
446 for intersection in intersections :
447 if (
448 (len(intersection)==5 and intersection[4] == "Overlap" and (0<=intersection[0]<=1 or 0<=intersection[1]<=1) and (0<=intersection[2]<=1 or 0<=intersection[3]<=1) )
449 or ( 0<=intersection[0]<=1 and 0<=intersection[1]<=1 )
450 ) :
451 res += [intersection]
452 return res
455 def csp_get_t_at_curvature(sp1,sp2,c, sample_points = 16):
456 # returns a list containning [t1,t2,t3,...,tn], 0<=ti<=1...
457 if sample_points < 2 : sample_points = 2
458 tolerance = .0000000001
459 res = []
460 ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
461 for k in range(sample_points) :
462 t = float(k)/(sample_points-1)
463 i, F = 0, 1e100
464 while i<2 or abs(F)>tolerance and i<17 :
465 try : # some numerical calculation could exceed the limits
466 t2 = t*t
467 #slopes...
468 f1x = 3*ax*t2+2*bx*t+cx
469 f1y = 3*ay*t2+2*by*t+cy
470 f2x = 6*ax*t+2*bx
471 f2y = 6*ay*t+2*by
472 f3x = 6*ax
473 f3y = 6*ay
474 d = (f1x**2+f1y**2)**1.5
475 F1 = (
476 ( (f1x*f3y-f3x*f1y)*d - (f1x*f2y-f2x*f1y)*3.*(f2x*f1x+f2y*f1y)*((f1x**2+f1y**2)**.5) ) /
477 ((f1x**2+f1y**2)**3)
478 )
479 F = (f1x*f2y-f1y*f2x)/d - c
480 t -= F/F1
481 except:
482 break
483 i += 1
484 if 0<=t<=1 and F<=tolerance:
485 if len(res) == 0 :
486 res.append(t)
487 for i in res :
488 if abs(t-i)<=0.001 :
489 break
490 if not abs(t-i)<=0.001 :
491 res.append(t)
492 return res
495 def csp_max_curvature(sp1,sp2):
496 ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
497 tolerance = .0001
498 F = 0.
499 i = 0
500 while i<2 or F-Flast<tolerance and i<10 :
501 t = .5
502 f1x = 3*ax*t**2 + 2*bx*t + cx
503 f1y = 3*ay*t**2 + 2*by*t + cy
504 f2x = 6*ax*t + 2*bx
505 f2y = 6*ay*t + 2*by
506 f3x = 6*ax
507 f3y = 6*ay
508 d = pow(f1x**2+f1y**2,1.5)
509 if d != 0 :
510 Flast = F
511 F = (f1x*f2y-f1y*f2x)/d
512 F1 = (
513 ( d*(f1x*f3y-f3x*f1y) - (f1x*f2y-f2x*f1y)*3.*(f2x*f1x+f2y*f1y)*pow(f1x**2+f1y**2,.5) ) /
514 (f1x**2+f1y**2)**3
515 )
516 i+=1
517 if F1!=0:
518 t -= F/F1
519 else:
520 break
521 else: break
522 return t
525 def csp_curvature_at_t(sp1,sp2,t, depth = 3) :
526 ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize(csp_segment_to_bez(sp1,sp2))
528 #curvature = (x'y''-y'x'') / (x'^2+y'^2)^1.5
530 f1x = 3*ax*t**2 + 2*bx*t + cx
531 f1y = 3*ay*t**2 + 2*by*t + cy
532 f2x = 6*ax*t + 2*bx
533 f2y = 6*ay*t + 2*by
534 d = (f1x**2+f1y**2)**1.5
535 if d != 0 :
536 return (f1x*f2y-f1y*f2x)/d
537 else :
538 t1 = f1x*f2y-f1y*f2x
539 if t1 > 0 : return 1e100
540 if t1 < 0 : return -1e100
541 # Use the Lapitals rule to solve 0/0 problem for 2 times...
542 t1 = 2*(bx*ay-ax*by)*t+(ay*cx-ax*cy)
543 if t1 > 0 : return 1e100
544 if t1 < 0 : return -1e100
545 t1 = bx*ay-ax*by
546 if t1 > 0 : return 1e100
547 if t1 < 0 : return -1e100
548 if depth>0 :
549 # little hack ;^) hope it wont influence anything...
550 return csp_curvature_at_t(sp1,sp2,t*1.004, depth-1)
551 return 1e100
554 def csp_curvature_radius_at_t(sp1,sp2,t) :
555 c = csp_curvature_at_t(sp1,sp2,t)
556 if c == 0 : return 1e100
557 else: return 1/c
560 def csp_special_points(sp1,sp2) :
561 # special points = curvature == 0
562 ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize((sp1[1],sp1[2],sp2[0],sp2[1]))
563 a = 3*ax*by-3*ay*bx
564 b = 3*ax*cy-3*cx*ay
565 c = bx*cy-cx*by
566 roots = cubic_solver(0, a, b, c)
567 res = []
568 for i in roots :
569 if type(i) is complex and i.imag==0:
570 i = i.real
571 if type(i) is not complex and 0<=i<=1:
572 res.append(i)
573 return res
576 def csp_subpath_ccw(subpath):
577 # Remove all zerro length segments
578 s = 0
579 #subpath = subpath[:]
580 if (P(subpath[-1][1])-P(subpath[0][1])).l2() > 1e-10 :
581 subpath[-1][2] = subpath[-1][1]
582 subpath[0][0] = subpath[0][1]
583 subpath += [ [subpath[0][1],subpath[0][1],subpath[0][1]] ]
584 pl = subpath[-1][2]
585 for sp1 in subpath:
586 for p in sp1 :
587 s += (p[0]-pl[0])*(p[1]+pl[1])
588 pl = p
589 return s<0
592 def csp_at_t(sp1,sp2,t):
593 ax,bx,cx,dx = sp1[1][0], sp1[2][0], sp2[0][0], sp2[1][0]
594 ay,by,cy,dy = sp1[1][1], sp1[2][1], sp2[0][1], sp2[1][1]
596 x1, y1 = ax+(bx-ax)*t, ay+(by-ay)*t
597 x2, y2 = bx+(cx-bx)*t, by+(cy-by)*t
598 x3, y3 = cx+(dx-cx)*t, cy+(dy-cy)*t
600 x4,y4 = x1+(x2-x1)*t, y1+(y2-y1)*t
601 x5,y5 = x2+(x3-x2)*t, y2+(y3-y2)*t
603 x,y = x4+(x5-x4)*t, y4+(y5-y4)*t
604 return [x,y]
607 def csp_splitatlength(sp1, sp2, l = 0.5, tolerance = 0.01):
608 bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
609 t = bezmisc.beziertatlength(bez, l, tolerance)
610 return csp_split(sp1, sp2, t)
613 def cspseglength(sp1,sp2, tolerance = 0.001):
614 bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
615 return bezmisc.bezierlength(bez, tolerance)
618 def csplength(csp):
619 total = 0
620 lengths = []
621 for sp in csp:
622 for i in xrange(1,len(sp)):
623 l = cspseglength(sp[i-1],sp[i])
624 lengths.append(l)
625 total += l
626 return lengths, total
629 def csp_segments(csp):
630 l, seg = 0, [0]
631 for sp in csp:
632 for i in xrange(1,len(sp)):
633 l += cspseglength(sp[i-1],sp[i])
634 seg += [ l ]
636 if l>0 :
637 seg = [seg[i]/l for i in xrange(len(seg))]
638 return seg,l
641 def rebuild_csp (csp, segs, s=None):
642 # rebuild_csp() adds to csp control points making it's segments looks like segs
643 if s==None : s, l = csp_segments(csp)
645 if len(s)>len(segs) : return None
646 segs = segs[:]
647 segs.sort()
648 for i in xrange(len(s)):
649 d = None
650 for j in xrange(len(segs)):
651 d = min( [abs(s[i]-segs[j]),j], d) if d!=None else [abs(s[i]-segs[j]),j]
652 del segs[d[1]]
653 for i in xrange(len(segs)):
654 for j in xrange(0,len(s)):
655 if segs[i]<s[j] : break
656 if s[j]-s[j-1] != 0 :
657 t = (segs[i] - s[j-1])/(s[j]-s[j-1])
658 sp1,sp2,sp3 = csp_split(csp[j-1],csp[j], t)
659 csp = csp[:j-1] + [sp1,sp2,sp3] + csp[j+1:]
660 s = s[:j] + [ s[j-1]*(1-t)+s[j]*t ] + s[j:]
661 return csp, s
664 def csp_slope(sp1,sp2,t):
665 bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
666 return bezmisc.bezierslopeatt(bez,t)
669 def csp_line_intersection(l1,l2,sp1,sp2):
670 dd=l1[0]
671 cc=l2[0]-l1[0]
672 bb=l1[1]
673 aa=l2[1]-l1[1]
674 if aa==cc==0 : return []
675 if aa:
676 coef1=cc/aa
677 coef2=1
678 else:
679 coef1=1
680 coef2=aa/cc
681 bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
682 ax,ay,bx,by,cx,cy,x0,y0=bezmisc.bezierparameterize(bez)
683 a=coef1*ay-coef2*ax
684 b=coef1*by-coef2*bx
685 c=coef1*cy-coef2*cx
686 d=coef1*(y0-bb)-coef2*(x0-dd)
687 roots = cubic_solver(a,b,c,d)
688 retval = []
689 for i in roots :
690 if type(i) is complex and abs(i.imag)<1e-7:
691 i = i.real
692 if type(i) is not complex and -1e-10<=i<=1.+1e-10:
693 retval.append(i)
694 return retval
697 def csp_split_by_two_points(sp1,sp2,t1,t2) :
698 if t1>t2 : t1, t2 = t2, t1
699 if t1 == t2 :
700 sp1,sp2,sp3 = csp_split(sp1,sp2,t)
701 return [sp1,sp2,sp2,sp3]
702 elif t1 <= 1e-10 and t2 >= 1.-1e-10 :
703 return [sp1,sp1,sp2,sp2]
704 elif t1 <= 1e-10:
705 sp1,sp2,sp3 = csp_split(sp1,sp2,t2)
706 return [sp1,sp1,sp2,sp3]
707 elif t2 >= 1.-1e-10 :
708 sp1,sp2,sp3 = csp_split(sp1,sp2,t1)
709 return [sp1,sp2,sp3,sp3]
710 else:
711 sp1,sp2,sp3 = csp_split(sp1,sp2,t1)
712 sp2,sp3,sp4 = csp_split(sp2,sp3,(t2-t1)/(1-t1) )
713 return [sp1,sp2,sp3,sp4]
716 def csp_subpath_split_by_points(subpath, points) :
717 # points are [[i,t]...] where i-segment's number
718 points.sort()
719 points = [[1,0.]] + points + [[len(subpath)-1,1.]]
720 parts = []
721 for int1,int2 in zip(points,points[1:]) :
722 if int1==int2 :
723 continue
724 if int1[1] == 1. :
725 int1[0] += 1
726 int1[1] = 0.
727 if int1==int2 :
728 continue
729 if int2[1] == 0. :
730 int2[0] -= 1
731 int2[1] = 1.
732 if int1[0] == 0 and int2[0]==len(subpath)-1:# and small(int1[1]) and small(int2[1]-1) :
733 continue
734 if int1[0]==int2[0] : # same segment
735 sp = csp_split_by_two_points(subpath[int1[0]-1],subpath[int1[0]],int1[1], int2[1])
736 if sp[1]!=sp[2] :
737 parts += [ [sp[1],sp[2]] ]
738 else :
739 sp5,sp1,sp2 = csp_split(subpath[int1[0]-1],subpath[int1[0]],int1[1])
740 sp3,sp4,sp5 = csp_split(subpath[int2[0]-1],subpath[int2[0]],int2[1])
741 if int1[0]==int2[0]-1 :
742 parts += [ [sp1, [sp2[0],sp2[1],sp3[2]], sp4] ]
743 else :
744 parts += [ [sp1,sp2]+subpath[int1[0]+1:int2[0]-1]+[sp3,sp4] ]
745 return parts
748 def csp_from_arc(start, end, center, r, slope_st) :
749 # Creates csp that approximise specified arc
750 r = abs(r)
751 alpha = (atan2(end[0]-center[0],end[1]-center[1]) - atan2(start[0]-center[0],start[1]-center[1])) % math.pi2
753 sectors = int(abs(alpha)*2/math.pi)+1
754 alpha_start = atan2(start[0]-center[0],start[1]-center[1])
755 cos_,sin_ = math.cos(alpha_start), math.sin(alpha_start)
756 k = (4.*math.tan(alpha/sectors/4.)/3.)
757 if dot(slope_st , [- sin_*k*r, cos_*k*r]) < 0 :
758 if alpha>0 : alpha -= math.pi2
759 else: alpha += math.pi2
760 if abs(alpha*r)<0.001 :
761 return []
763 sectors = int(abs(alpha)*2/math.pi)+1
764 k = (4.*math.tan(alpha/sectors/4.)/3.)
765 result = []
766 for i in range(sectors+1) :
767 cos_,sin_ = math.cos(alpha_start + alpha*i/sectors), math.sin(alpha_start + alpha*i/sectors)
768 sp = [ [], [center[0] + cos_*r, center[1] + sin_*r], [] ]
769 sp[0] = [sp[1][0] + sin_*k*r, sp[1][1] - cos_*k*r ]
770 sp[2] = [sp[1][0] - sin_*k*r, sp[1][1] + cos_*k*r ]
771 result += [sp]
772 result[0][0] = result[0][1][:]
773 result[-1][2] = result[-1][1]
775 return result
778 def point_to_arc_distance(p, arc):
779 ### Distance calculattion from point to arc
780 P0,P2,c,a = arc
781 dist = None
782 p = P(p)
783 r = (P0-c).mag()
784 if r>0 :
785 i = c + (p-c).unit()*r
786 alpha = ((i-c).angle() - (P0-c).angle())
787 if a*alpha<0:
788 if alpha>0: alpha = alpha-math.pi2
789 else: alpha = math.pi2+alpha
790 if between(alpha,0,a) or min(abs(alpha),abs(alpha-a))<straight_tolerance :
791 return (p-i).mag(), [i.x, i.y]
792 else :
793 d1, d2 = (p-P0).mag(), (p-P2).mag()
794 if d1<d2 :
795 return (d1, [P0.x,P0.y])
796 else :
797 return (d2, [P2.x,P2.y])
800 def csp_to_arc_distance(sp1,sp2, arc1, arc2, tolerance = 0.01 ): # arc = [start,end,center,alpha]
801 n, i = 10, 0
802 d, d1, dl = (0,(0,0)), (0,(0,0)), 0
803 while i<1 or (abs(d1[0]-dl[0])>tolerance and i<4):
804 i += 1
805 dl = d1*1
806 for j in range(n+1):
807 t = float(j)/n
808 p = csp_at_t(sp1,sp2,t)
809 d = min(point_to_arc_distance(p,arc1), point_to_arc_distance(p,arc2))
810 d1 = max(d1,d)
811 n=n*2
812 return d1[0]
815 def csp_simple_bound_to_point_distance(p, csp):
816 minx,miny,maxx,maxy = None,None,None,None
817 for subpath in csp:
818 for sp in subpath:
819 for p_ in sp:
820 minx = min(minx,p_[0]) if minx!=None else p_[0]
821 miny = min(miny,p_[1]) if miny!=None else p_[1]
822 maxx = max(maxx,p_[0]) if maxx!=None else p_[0]
823 maxy = max(maxy,p_[1]) if maxy!=None else p_[1]
824 return math.sqrt(max(minx-p[0],p[0]-maxx,0)**2+max(miny-p[1],p[1]-maxy,0)**2)
827 def csp_point_inside_bound(sp1, sp2, p):
828 bez = [sp1[1],sp1[2],sp2[0],sp2[1]]
829 x,y = p
830 c = 0
831 for i in range(4):
832 [x0,y0], [x1,y1] = bez[i-1], bez[i]
833 if x0-x1!=0 and (y-y0)*(x1-x0)>=(x-x0)*(y1-y0) and x>min(x0,x1) and x<=max(x0,x1) :
834 c +=1
835 return c%2==0
838 def csp_bound_to_point_distance(sp1, sp2, p):
839 if csp_point_inside_bound(sp1, sp2, p) :
840 return 0.
841 bez = csp_segment_to_bez(sp1,sp2)
842 min_dist = 1e100
843 for i in range(0,4):
844 d = point_to_line_segment_distance_2(p, bez[i-1],bez[i])
845 if d <= min_dist : min_dist = d
846 return min_dist
849 def line_line_intersect(p1,p2,p3,p4) : # Return only true intersection.
850 if (p1[0]==p2[0] and p1[1]==p2[1]) or (p3[0]==p4[0] and p3[1]==p4[1]) : return False
851 x = (p2[0]-p1[0])*(p4[1]-p3[1]) - (p2[1]-p1[1])*(p4[0]-p3[0])
852 if x==0 : # Lines are parallel
853 if (p3[0]-p1[0])*(p2[1]-p1[1]) == (p3[1]-p1[1])*(p2[0]-p1[0]) :
854 if p3[0]!=p4[0] :
855 t11 = (p1[0]-p3[0])/(p4[0]-p3[0])
856 t12 = (p2[0]-p3[0])/(p4[0]-p3[0])
857 t21 = (p3[0]-p1[0])/(p2[0]-p1[0])
858 t22 = (p4[0]-p1[0])/(p2[0]-p1[0])
859 else:
860 t11 = (p1[1]-p3[1])/(p4[1]-p3[1])
861 t12 = (p2[1]-p3[1])/(p4[1]-p3[1])
862 t21 = (p3[1]-p1[1])/(p2[1]-p1[1])
863 t22 = (p4[1]-p1[1])/(p2[1]-p1[1])
864 return ("Overlap" if (0<=t11<=1 or 0<=t12<=1) and (0<=t21<=1 or 0<=t22<=1) else False)
865 else: return False
866 else :
867 return (
868 0<=((p4[0]-p3[0])*(p1[1]-p3[1]) - (p4[1]-p3[1])*(p1[0]-p3[0]))/x<=1 and
869 0<=((p2[0]-p1[0])*(p1[1]-p3[1]) - (p2[1]-p1[1])*(p1[0]-p3[0]))/x<=1 )
872 def line_line_intersection_points(p1,p2,p3,p4) : # Return only points [ (x,y) ]
873 if (p1[0]==p2[0] and p1[1]==p2[1]) or (p3[0]==p4[0] and p3[1]==p4[1]) : return []
874 x = (p2[0]-p1[0])*(p4[1]-p3[1]) - (p2[1]-p1[1])*(p4[0]-p3[0])
875 if x==0 : # Lines are parallel
876 if (p3[0]-p1[0])*(p2[1]-p1[1]) == (p3[1]-p1[1])*(p2[0]-p1[0]) :
877 if p3[0]!=p4[0] :
878 t11 = (p1[0]-p3[0])/(p4[0]-p3[0])
879 t12 = (p2[0]-p3[0])/(p4[0]-p3[0])
880 t21 = (p3[0]-p1[0])/(p2[0]-p1[0])
881 t22 = (p4[0]-p1[0])/(p2[0]-p1[0])
882 else:
883 t11 = (p1[1]-p3[1])/(p4[1]-p3[1])
884 t12 = (p2[1]-p3[1])/(p4[1]-p3[1])
885 t21 = (p3[1]-p1[1])/(p2[1]-p1[1])
886 t22 = (p4[1]-p1[1])/(p2[1]-p1[1])
887 res = []
888 if (0<=t11<=1 or 0<=t12<=1) and (0<=t21<=1 or 0<=t22<=1) :
889 if 0<=t11<=1 : res += [p1]
890 if 0<=t12<=1 : res += [p2]
891 if 0<=t21<=1 : res += [p3]
892 if 0<=t22<=1 : res += [p4]
893 return res
894 else: return []
895 else :
896 t1 = ((p4[0]-p3[0])*(p1[1]-p3[1]) - (p4[1]-p3[1])*(p1[0]-p3[0]))/x
897 t2 = ((p2[0]-p1[0])*(p1[1]-p3[1]) - (p2[1]-p1[1])*(p1[0]-p3[0]))/x
898 if 0<=t1<=1 and 0<=t2<=1 : return [ [p1[0]*(1-t1)+p2[0]*t1, p1[1]*(1-t1)+p2[1]*t1] ]
899 else : return []
902 def point_to_point_d2(a,b):
903 return (a[0]-b[0])**2 + (a[1]-b[1])**2
906 def point_to_point_d(a,b):
907 return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2)
910 def point_to_line_segment_distance_2(p1, p2,p3) :
911 # p1 - point, p2,p3 - line segment
912 #draw_pointer(p1)
913 w0 = [p1[0]-p2[0], p1[1]-p2[1]]
914 v = [p3[0]-p2[0], p3[1]-p2[1]]
915 c1 = w0[0]*v[0] + w0[1]*v[1]
916 if c1 <= 0 :
917 return w0[0]*w0[0]+w0[1]*w0[1]
918 c2 = v[0]*v[0] + v[1]*v[1]
919 if c2 <= c1 :
920 return (p1[0]-p3[0])**2 + (p1[1]-p3[1])**2
921 return (p1[0]- p2[0]-v[0]*c1/c2)**2 + (p1[1]- p2[1]-v[1]*c1/c2)
924 def line_to_line_distance_2(p1,p2,p3,p4):
925 if line_line_intersect(p1,p2,p3,p4) : return 0
926 return min(
927 point_to_line_segment_distance_2(p1,p3,p4),
928 point_to_line_segment_distance_2(p2,p3,p4),
929 point_to_line_segment_distance_2(p3,p1,p2),
930 point_to_line_segment_distance_2(p4,p1,p2))
933 def csp_seg_bound_to_csp_seg_bound_max_min_distance(sp1,sp2,sp3,sp4) :
934 bez1 = csp_segment_to_bez(sp1,sp2)
935 bez2 = csp_segment_to_bez(sp3,sp4)
936 min_dist = 1e100
937 max_dist = 0.
938 for i in range(4) :
939 if csp_point_inside_bound(sp1, sp2, bez2[i]) or csp_point_inside_bound(sp3, sp4, bez1[i]) :
940 min_dist = 0.
941 break
942 for i in range(4) :
943 for j in range(4) :
944 d = line_to_line_distance_2(bez1[i-1],bez1[i],bez2[j-1],bez2[j])
945 if d < min_dist : min_dist = d
946 d = (bez2[j][0]-bez1[i][0])**2 + (bez2[j][1]-bez1[i][1])**2
947 if max_dist < d : max_dist = d
948 return min_dist, max_dist
951 def csp_reverse(csp) :
952 for i in range(len(csp)) :
953 n = []
954 for j in csp[i] :
955 n = [ [j[2][:],j[1][:],j[0][:]] ] + n
956 csp[i] = n[:]
957 return csp
960 def csp_normalized_slope(sp1,sp2,t) :
961 ax,ay,bx,by,cx,cy,dx,dy=bezmisc.bezierparameterize((sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]))
962 if sp1[1]==sp2[1]==sp1[2]==sp2[0] : return [1.,0.]
963 f1x = 3*ax*t*t+2*bx*t+cx
964 f1y = 3*ay*t*t+2*by*t+cy
965 if abs(f1x*f1x+f1y*f1y) > 1e-20 :
966 l = math.sqrt(f1x*f1x+f1y*f1y)
967 return [f1x/l, f1y/l]
969 if t == 0 :
970 f1x = sp2[0][0]-sp1[1][0]
971 f1y = sp2[0][1]-sp1[1][1]
972 if abs(f1x*f1x+f1y*f1y) > 1e-20 :
973 l = math.sqrt(f1x*f1x+f1y*f1y)
974 return [f1x/l, f1y/l]
975 else :
976 f1x = sp2[1][0]-sp1[1][0]
977 f1y = sp2[1][1]-sp1[1][1]
978 if f1x*f1x+f1y*f1y != 0 :
979 l = math.sqrt(f1x*f1x+f1y*f1y)
980 return [f1x/l, f1y/l]
981 elif t == 1 :
982 f1x = sp2[1][0]-sp1[2][0]
983 f1y = sp2[1][1]-sp1[2][1]
984 if abs(f1x*f1x+f1y*f1y) > 1e-20 :
985 l = math.sqrt(f1x*f1x+f1y*f1y)
986 return [f1x/l, f1y/l]
987 else :
988 f1x = sp2[1][0]-sp1[1][0]
989 f1y = sp2[1][1]-sp1[1][1]
990 if f1x*f1x+f1y*f1y != 0 :
991 l = math.sqrt(f1x*f1x+f1y*f1y)
992 return [f1x/l, f1y/l]
993 else :
994 return [1.,0.]
997 def csp_normalized_normal(sp1,sp2,t) :
998 nx,ny = csp_normalized_slope(sp1,sp2,t)
999 return [-ny, nx]
1002 def csp_parameterize(sp1,sp2):
1003 return bezmisc.bezierparameterize(csp_segment_to_bez(sp1,sp2))
1006 def csp_concat_subpaths(*s):
1008 def concat(s1,s2) :
1009 if s1 == [] : return s2
1010 if s2 == [] : return s1
1011 if (s1[-1][1][0]-s2[0][1][0])**2 + (s1[-1][1][1]-s2[0][1][1])**2 > 0.00001 :
1012 return s1[:-1]+[ [s1[-1][0],s1[-1][1],s1[-1][1]], [s2[0][1],s2[0][1],s2[0][2]] ] + s2[1:]
1013 else :
1014 return s1[:-1]+[ [s1[-1][0],s2[0][1],s2[0][2]] ] + s2[1:]
1016 if len(s) == 0 : return []
1017 if len(s) ==1 : return s[0]
1018 result = s[0]
1019 for s1 in s[1:]:
1020 result = concat(result,s1)
1021 return result
1024 def csp_draw(csp, color="#05f", group = None, style="fill:none;", width = .1, comment = "") :
1025 if csp!=[] and csp!=[[]] :
1026 if group == None : group = options.doc_root
1027 style += "stroke:"+color+";"+ "stroke-width:%0.4fpx;"%width
1028 args = {"d": cubicsuperpath.formatPath(csp), "style":style}
1029 if comment!="" : args["comment"] = str(comment)
1030 inkex.etree.SubElement( group, inkex.addNS('path','svg'), args )
1033 def csp_subpaths_end_to_start_distance2(s1,s2):
1034 return (s1[-1][1][0]-s2[0][1][0])**2 + (s1[-1][1][1]-s2[0][1][1])**2
1037 def csp_clip_by_line(csp,l1,l2) :
1038 result = []
1039 for i in range(len(csp)):
1040 s = csp[i]
1041 intersections = []
1042 for j in range(1,len(s)) :
1043 intersections += [ [j,int_] for int_ in csp_line_intersection(l1,l2,s[j-1],s[j])]
1044 splitted_s = csp_subpath_split_by_points(s, intersections)
1045 for s in splitted_s[:] :
1046 clip = False
1047 for p in csp_true_bounds([s]) :
1048 if (l1[1]-l2[1])*p[0] + (l2[0]-l1[0])*p[1] + (l1[0]*l2[1]-l2[0]*l1[1])<-0.01 :
1049 clip = True
1050 break
1051 if clip :
1052 splitted_s.remove(s)
1053 result += splitted_s
1054 return result
1057 def csp_subpath_line_to(subpath, points) :
1058 # Appends subpath with line or polyline.
1059 if len(points)>0 :
1060 if len(subpath)>0:
1061 subpath[-1][2] = subpath[-1][1][:]
1062 if type(points[0]) == type([1,1]) :
1063 for p in points :
1064 subpath += [ [p[:],p[:],p[:]] ]
1065 else:
1066 subpath += [ [points,points,points] ]
1067 return subpath
1070 def csp_join_subpaths(csp) :
1071 result = csp[:]
1072 done_smf = True
1073 joined_result = []
1074 while done_smf :
1075 done_smf = False
1076 while len(result)>0:
1077 s1 = result[-1][:]
1078 del(result[-1])
1079 j = 0
1080 joined_smf = False
1081 while j<len(joined_result) :
1082 if csp_subpaths_end_to_start_distance2(joined_result[j],s1) <0.000001 :
1083 joined_result[j] = csp_concat_subpaths(joined_result[j],s1)
1084 done_smf = True
1085 joined_smf = True
1086 break
1087 if csp_subpaths_end_to_start_distance2(s1,joined_result[j]) <0.000001 :
1088 joined_result[j] = csp_concat_subpaths(s1,joined_result[j])
1089 done_smf = True
1090 joined_smf = True
1091 break
1092 j += 1
1093 if not joined_smf : joined_result += [s1[:]]
1094 if done_smf :
1095 result = joined_result[:]
1096 joined_result = []
1097 return joined_result
1100 def triangle_cross(a,b,c):
1101 return (a[0]-b[0])*(c[1]-b[1]) - (c[0]-b[0])*(a[1]-b[1])
1104 def csp_segment_convex_hull(sp1,sp2):
1105 a,b,c,d = sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]
1107 abc = triangle_cross(a,b,c)
1108 abd = triangle_cross(a,b,d)
1109 bcd = triangle_cross(b,c,d)
1110 cad = triangle_cross(c,a,d)
1111 if abc == 0 and abd == 0 : return [min(a,b,c,d), max(a,b,c,d)]
1112 if abc == 0 : return [d, min(a,b,c), max(a,b,c)]
1113 if abd == 0 : return [c, min(a,b,d), max(a,b,d)]
1114 if bcd == 0 : return [a, min(b,c,d), max(b,c,d)]
1115 if cad == 0 : return [b, min(c,a,d), max(c,a,d)]
1117 m1, m2, m3 = abc*abd>0, abc*bcd>0, abc*cad>0
1118 if m1 and m2 and m3 : return [a,b,c]
1119 if m1 and m2 and not m3 : return [a,b,c,d]
1120 if m1 and not m2 and m3 : return [a,b,d,c]
1121 if not m1 and m2 and m3 : return [a,d,b,c]
1122 if m1 and not (m2 and m3) : return [a,b,d]
1123 if not (m1 and m2) and m3 : return [c,a,d]
1124 if not (m1 and m3) and m2 : return [b,c,d]
1126 raise ValueError, "csp_segment_convex_hull happend something that shouldnot happen!"
1129 ################################################################################
1130 ### Bezier additional functions
1131 ################################################################################
1133 def bez_bounds_intersect(bez1, bez2) :
1134 return bounds_intersect(bez_bound(bez2), bez_bound(bez1))
1137 def bez_bound(bez) :
1138 return [
1139 min(bez[0][0], bez[1][0], bez[2][0], bez[3][0]),
1140 min(bez[0][1], bez[1][1], bez[2][1], bez[3][1]),
1141 max(bez[0][0], bez[1][0], bez[2][0], bez[3][0]),
1142 max(bez[0][1], bez[1][1], bez[2][1], bez[3][1]),
1143 ]
1146 def bounds_intersect(a, b) :
1147 return not ( (a[0]>b[2]) or (b[0]>a[2]) or (a[1]>b[3]) or (b[1]>a[3]) )
1150 def tpoint((x1,y1),(x2,y2),t):
1151 return [x1+t*(x2-x1),y1+t*(y2-y1)]
1154 def bez_to_csp_segment(bez) :
1155 return [bez[0],bez[0],bez[1]], [bez[2],bez[3],bez[3]]
1158 def bez_split(a,t=0.5) :
1159 a1 = tpoint(a[0],a[1],t)
1160 at = tpoint(a[1],a[2],t)
1161 b2 = tpoint(a[2],a[3],t)
1162 a2 = tpoint(a1,at,t)
1163 b1 = tpoint(b2,at,t)
1164 a3 = tpoint(a2,b1,t)
1165 return [a[0],a1,a2,a3], [a3,b1,b2,a[3]]
1168 def bez_at_t(bez,t) :
1169 return csp_at_t([bez[0],bez[0],bez[1]],[bez[2],bez[3],bez[3]],t)
1172 def bez_to_point_distance(bez,p,needed_dist=[0.,1e100]):
1173 # returns [d^2,t]
1174 return csp_seg_to_point_distance(bez_to_csp_segment(bez),p,needed_dist)
1177 def bez_normalized_slope(bez,t):
1178 return csp_normalized_slope([bez[0],bez[0],bez[1]], [bez[2],bez[3],bez[3]],t)
1180 ################################################################################
1181 ### Some vector functions
1182 ################################################################################
1184 def normalize((x,y)) :
1185 l = math.sqrt(x**2+y**2)
1186 if l == 0 : return [0.,0.]
1187 else : return [x/l, y/l]
1190 def cross(a,b) :
1191 return a[1] * b[0] - a[0] * b[1]
1194 def dot(a,b) :
1195 return a[0] * b[0] + a[1] * b[1]
1198 def rotate_ccw(d) :
1199 return [-d[1],d[0]]
1202 def vectors_ccw(a,b):
1203 return a[0]*b[1]-b[0]*a[1] < 0
1206 def vector_from_to_length(a,b):
1207 return math.sqrt((a[0]-b[0])*(a[0]-b[0]) + (a[1]-b[1])*(a[1]-b[1]))
1209 ################################################################################
1210 ### Common functions
1211 ################################################################################
1213 def matrix_mul(a,b) :
1214 return [ [ sum([a[i][k]*b[k][j] for k in range(len(a[0])) ]) for j in range(len(b[0]))] for i in range(len(a))]
1215 try :
1216 return [ [ sum([a[i][k]*b[k][j] for k in range(len(a[0])) ]) for j in range(len(b[0]))] for i in range(len(a))]
1217 except :
1218 return None
1221 def transpose(a) :
1222 try :
1223 return [ [ a[i][j] for i in range(len(a)) ] for j in range(len(a[0])) ]
1224 except :
1225 return None
1228 def det_3x3(a):
1229 return float(
1230 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]
1231 - 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]
1232 )
1235 def inv_3x3(a): # invert matrix 3x3
1236 det = det_3x3(a)
1237 if det==0: return None
1238 return [
1239 [ (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 ],
1240 [ -(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 ],
1241 [ (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 ]
1242 ]
1245 def inv_2x2(a): # invert matrix 2x2
1246 det = a[0][0]*a[1][1] - a[1][0]*a[0][1]
1247 if det==0: return None
1248 return [
1249 [a[1][1]/det, -a[0][1]/det],
1250 [-a[1][0]/det, a[0][0]/det]
1251 ]
1254 def small(a) :
1255 global small_tolerance
1256 return abs(a)<small_tolerance
1259 def atan2(*arg):
1260 if len(arg)==1 and ( type(arg[0]) == type([0.,0.]) or type(arg[0])==type((0.,0.)) ) :
1261 return (math.pi/2 - math.atan2(arg[0][0], arg[0][1]) ) % math.pi2
1262 elif len(arg)==2 :
1264 return (math.pi/2 - math.atan2(arg[0],arg[1]) ) % math.pi2
1265 else :
1266 raise ValueError, "Bad argumets for atan! (%s)" % arg
1269 def draw_text(text,x,y,style = None, font_size = 20) :
1270 if style == None :
1271 style = "font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;"
1272 style += "font-size:%fpx;"%font_size
1273 t = inkex.etree.SubElement( options.doc_root, inkex.addNS('text','svg'), {
1274 'x': str(x),
1275 inkex.addNS("space","xml"):"preserve",
1276 'y': str(y)
1277 })
1278 text = str(text).split("\n")
1279 for s in text :
1280 span = inkex.etree.SubElement( t, inkex.addNS('tspan','svg'),
1281 {
1282 'x': str(x),
1283 'y': str(+y),
1284 inkex.addNS("role","sodipodi"):"line",
1285 })
1286 y += font_size
1287 span.text = s
1290 def draw_pointer(x,color = "#f00", figure = "cross", comment = "", width = .1) :
1291 if figure == "line" :
1292 s = ""
1293 for i in range(1,len(x)/2) :
1294 s+= " %s, %s " %(x[i*2],x[i*2+1])
1295 inkex.etree.SubElement( options.doc_root, inkex.addNS('path','svg'), {"d": "M %s,%s L %s"%(x[0],x[1],s), "style":"fill:none;stroke:%s;stroke-width:%f;"%(color,width),"comment":str(comment)} )
1296 else :
1297 inkex.etree.SubElement( options.doc_root, inkex.addNS('path','svg'), {"d": "m %s,%s l 10,10 -20,-20 10,10 -10,10, 20,-20"%(x[0],x[1]), "style":"fill:none;stroke:%s;stroke-width:%f;"%(color,width),"comment":str(comment)} )
1300 def straight_segments_intersection(a,b, true_intersection = True) : # (True intersection means check ta and tb are in [0,1])
1301 ax,bx,cx,dx, ay,by,cy,dy = a[0][0],a[1][0],b[0][0],b[1][0], a[0][1],a[1][1],b[0][1],b[1][1]
1302 if (ax==bx and ay==by) or (cx==dx and cy==dy) : return False, 0, 0
1303 if (bx-ax)*(dy-cy)-(by-ay)*(dx-cx)==0 : # Lines are parallel
1304 ta = (ax-cx)/(dx-cx) if cx!=dx else (ay-cy)/(dy-cy)
1305 tb = (bx-cx)/(dx-cx) if cx!=dx else (by-cy)/(dy-cy)
1306 tc = (cx-ax)/(bx-ax) if ax!=bx else (cy-ay)/(by-ay)
1307 td = (dx-ax)/(bx-ax) if ax!=bx else (dy-ay)/(by-ay)
1308 return ("Overlap" if 0<=ta<=1 or 0<=tb<=1 or 0<=tc<=1 or 0<=td<=1 or not true_intersection else False), (ta,tb), (tc,td)
1309 else :
1310 ta = ( (ay-cy)*(dx-cx)-(ax-cx)*(dy-cy) ) / ( (bx-ax)*(dy-cy)-(by-ay)*(dx-cx) )
1311 tb = ( ax-cx+ta*(bx-ax) ) / (dx-cx) if dx!=cx else ( ay-cy+ta*(by-ay) ) / (dy-cy)
1312 return (0<=ta<=1 and 0<=tb<=1 or not true_intersection), ta, tb
1316 def isnan(x): return type(x) is float and x != x
1318 def isinf(x): inf = 1e5000; return x == inf or x == -inf
1320 def between(c,x,y):
1321 return x-straight_tolerance<=c<=y+straight_tolerance or y-straight_tolerance<=c<=x+straight_tolerance
1324 def cubic_solver(a,b,c,d):
1325 if a!=0:
1326 # Monics formula see http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots
1327 a,b,c = (b/a, c/a, d/a)
1328 m = 2*a**3 - 9*a*b + 27*c
1329 k = a**2 - 3*b
1330 n = m**2 - 4*k**3
1331 w1 = -.5 + .5*cmath.sqrt(3)*1j
1332 w2 = -.5 - .5*cmath.sqrt(3)*1j
1333 if n>=0 :
1334 t = m+math.sqrt(n)
1335 m1 = pow(t/2,1./3) if t>=0 else -pow(-t/2,1./3)
1336 t = m-math.sqrt(n)
1337 n1 = pow(t/2,1./3) if t>=0 else -pow(-t/2,1./3)
1338 else :
1339 m1 = pow(complex((m+cmath.sqrt(n))/2),1./3)
1340 n1 = pow(complex((m-cmath.sqrt(n))/2),1./3)
1341 x1 = -1./3 * (a + m1 + n1)
1342 x2 = -1./3 * (a + w1*m1 + w2*n1)
1343 x3 = -1./3 * (a + w2*m1 + w1*n1)
1344 return [x1,x2,x3]
1345 elif b!=0:
1346 det = c**2-4*b*d
1347 if det>0 :
1348 return [(-c+math.sqrt(det))/(2*b),(-c-math.sqrt(det))/(2*b)]
1349 elif d == 0 :
1350 return [-c/(b*b)]
1351 else :
1352 return [(-c+cmath.sqrt(det))/(2*b),(-c-cmath.sqrt(det))/(2*b)]
1353 elif c!=0 :
1354 return [-d/c]
1355 else : return []
1358 ################################################################################
1359 ### print_ prints any arguments into specified log file
1360 ################################################################################
1362 def print_(*arg):
1363 f = open(options.log_filename,"a")
1364 for s in arg :
1365 s = str(unicode(s).encode('unicode_escape'))+" "
1366 f.write( s )
1367 f.write("\n")
1368 f.close()
1371 ################################################################################
1372 ### Point (x,y) operations
1373 ################################################################################
1374 class P:
1375 def __init__(self, x, y=None):
1376 if not y==None:
1377 self.x, self.y = float(x), float(y)
1378 else:
1379 self.x, self.y = float(x[0]), float(x[1])
1380 def __add__(self, other): return P(self.x + other.x, self.y + other.y)
1381 def __sub__(self, other): return P(self.x - other.x, self.y - other.y)
1382 def __neg__(self): return P(-self.x, -self.y)
1383 def __mul__(self, other):
1384 if isinstance(other, P):
1385 return self.x * other.x + self.y * other.y
1386 return P(self.x * other, self.y * other)
1387 __rmul__ = __mul__
1388 def __div__(self, other): return P(self.x / other, self.y / other)
1389 def mag(self): return math.hypot(self.x, self.y)
1390 def unit(self):
1391 h = self.mag()
1392 if h: return self / h
1393 else: return P(0,0)
1394 def dot(self, other): return self.x * other.x + self.y * other.y
1395 def rot(self, theta):
1396 c = math.cos(theta)
1397 s = math.sin(theta)
1398 return P(self.x * c - self.y * s, self.x * s + self.y * c)
1399 def angle(self): return math.atan2(self.y, self.x)
1400 def __repr__(self): return '%f,%f' % (self.x, self.y)
1401 def pr(self): return "%.2f,%.2f" % (self.x, self.y)
1402 def to_list(self): return [self.x, self.y]
1403 def ccw(self): return P(-self.y,self.x)
1404 def l2(self): return self.x*self.x + self.y*self.y
1406 ################################################################################
1407 ###
1408 ### Offset function
1409 ###
1410 ### This function offsets given cubic super path.
1411 ### It's based on src/livarot/PathOutline.cpp from Inkscape's source code.
1412 ###
1413 ###
1414 ################################################################################
1415 def csp_offset(csp, r) :
1416 offset_tolerance = 0.05
1417 offset_subdivision_depth = 10
1418 time_ = time.time()
1419 time_start = time_
1420 print_("Offset start at %s"% time_)
1421 print_("Offset radius %s"% r)
1424 def csp_offset_segment(sp1,sp2,r) :
1425 result = []
1426 t = csp_get_t_at_curvature(sp1,sp2,1/r)
1427 if len(t) == 0 : t =[0.,1.]
1428 t.sort()
1429 if t[0]>.00000001 : t = [0.]+t
1430 if t[-1]<.99999999 : t.append(1.)
1431 for st,end in zip(t,t[1:]) :
1432 c = csp_curvature_at_t(sp1,sp2,(st+end)/2)
1433 sp = csp_split_by_two_points(sp1,sp2,st,end)
1434 if sp[1]!=sp[2]:
1435 if (c>1/r and r<0 or c<1/r and r>0) :
1436 offset = offset_segment_recursion(sp[1],sp[2],r, offset_subdivision_depth, offset_tolerance)
1437 else : # This part will be clipped for sure... TODO Optimize it...
1438 offset = offset_segment_recursion(sp[1],sp[2],r, offset_subdivision_depth, offset_tolerance)
1440 if result==[] :
1441 result = offset[:]
1442 else:
1443 if csp_subpaths_end_to_start_distance2(result,offset)<0.0001 :
1444 result = csp_concat_subpaths(result,offset)
1445 else:
1447 intersection = csp_get_subapths_last_first_intersection(result,offset)
1448 if intersection != [] :
1449 i,t1,j,t2 = intersection
1450 sp1_,sp2_,sp3_ = csp_split(result[i-1],result[i],t1)
1451 result = result[:i-1] + [ sp1_, sp2_ ]
1452 sp1_,sp2_,sp3_ = csp_split(offset[j-1],offset[j],t2)
1453 result = csp_concat_subpaths( result, [sp2_,sp3_] + offset[j+1:] )
1454 else :
1455 pass # ???
1456 #raise ValueError, "Offset curvature clipping error"
1457 #csp_draw([result])
1458 return result
1461 def create_offset_segment(sp1,sp2,r) :
1462 # See Gernot Hoffmann "Bezier Curves" p.34 -> 7.1 Bezier Offset Curves
1463 p0,p1,p2,p3 = P(sp1[1]),P(sp1[2]),P(sp2[0]),P(sp2[1])
1464 s0,s1,s3 = p1-p0,p2-p1,p3-p2
1465 n0 = s0.ccw().unit() if s0.l2()!=0 else P(csp_normalized_normal(sp1,sp2,0))
1466 n3 = s3.ccw().unit() if s3.l2()!=0 else P(csp_normalized_normal(sp1,sp2,1))
1467 n1 = s1.ccw().unit() if s1.l2()!=0 else (n0.unit()+n3.unit()).unit()
1469 q0,q3 = p0+r*n0, p3+r*n3
1470 c = csp_curvature_at_t(sp1,sp2,0)
1471 q1 = q0 + (p1-p0)*(1- (r*c if abs(c)<100 else 0) )
1472 c = csp_curvature_at_t(sp1,sp2,1)
1473 q2 = q3 + (p2-p3)*(1- (r*c if abs(c)<100 else 0) )
1476 return [[q0.to_list(), q0.to_list(), q1.to_list()],[q2.to_list(), q3.to_list(), q3.to_list()]]
1479 def csp_get_subapths_last_first_intersection(s1,s2):
1480 _break = False
1481 for i in range(1,len(s1)) :
1482 sp11, sp12 = s1[-i-1], s1[-i]
1483 for j in range(1,len(s2)) :
1484 sp21,sp22 = s2[j-1], s2[j]
1485 intersection = csp_segments_true_intersection(sp11,sp12,sp21,sp22)
1486 if intersection != [] :
1487 _break = True
1488 break
1489 if _break:break
1490 if _break :
1491 intersection = max(intersection)
1492 return [len(s1)-i,intersection[0], j,intersection[1]]
1493 else :
1494 return []
1497 def csp_join_offsets(prev,next,sp1,sp2,sp1_l,sp2_l,r):
1498 if len(next)>1 :
1499 if (P(prev[-1][1])-P(next[0][1])).l2()<0.001 :
1500 return prev,[],next
1501 intersection = csp_get_subapths_last_first_intersection(prev,next)
1502 if intersection != [] :
1503 i,t1,j,t2 = intersection
1504 sp1_,sp2_,sp3_ = csp_split(prev[i-1],prev[i],t1)
1505 sp3_,sp4_,sp5_ = csp_split(next[j-1], next[j],t2)
1506 return prev[:i-1] + [ sp1_, sp2_ ], [], [sp4_,sp5_] + next[j+1:]
1508 # Offsets do not intersect... will add an arc...
1509 start = (P(csp_at_t(sp1_l,sp2_l,1.)) + r*P(csp_normalized_normal(sp1_l,sp2_l,1.))).to_list()
1510 end = (P(csp_at_t(sp1,sp2,0.)) + r*P(csp_normalized_normal(sp1,sp2,0.))).to_list()
1511 arc = csp_from_arc(start, end, sp1[1], r, csp_normalized_slope(sp1_l,sp2_l,1.) )
1512 if arc == [] :
1513 return prev,[],next
1514 else:
1515 # Clip prev by arc
1516 if csp_subpaths_end_to_start_distance2(prev,arc)>0.00001 :
1517 intersection = csp_get_subapths_last_first_intersection(prev,arc)
1518 if intersection != [] :
1519 i,t1,j,t2 = intersection
1520 sp1_,sp2_,sp3_ = csp_split(prev[i-1],prev[i],t1)
1521 sp3_,sp4_,sp5_ = csp_split(arc[j-1],arc[j],t2)
1522 prev = prev[:i-1] + [ sp1_, sp2_ ]
1523 arc = [sp4_,sp5_] + arc[j+1:]
1524 #else : raise ValueError, "Offset curvature clipping error"
1525 # Clip next by arc
1526 if next == [] :
1527 return prev,[],arc
1528 if csp_subpaths_end_to_start_distance2(arc,next)>0.00001 :
1529 intersection = csp_get_subapths_last_first_intersection(arc,next)
1530 if intersection != [] :
1531 i,t1,j,t2 = intersection
1532 sp1_,sp2_,sp3_ = csp_split(arc[i-1],arc[i],t1)
1533 sp3_,sp4_,sp5_ = csp_split(next[j-1],next[j],t2)
1534 arc = arc[:i-1] + [ sp1_, sp2_ ]
1535 next = [sp4_,sp5_] + next[j+1:]
1536 #else : raise ValueError, "Offset curvature clipping error"
1538 return prev,arc,next
1541 def offset_segment_recursion(sp1,sp2,r, depth, tolerance) :
1542 sp1_r,sp2_r = create_offset_segment(sp1,sp2,r)
1543 err = max(
1544 csp_seg_to_point_distance(sp1_r,sp2_r, (P(csp_at_t(sp1,sp2,.25)) + P(csp_normalized_normal(sp1,sp2,.25))*r).to_list())[0],
1545 csp_seg_to_point_distance(sp1_r,sp2_r, (P(csp_at_t(sp1,sp2,.50)) + P(csp_normalized_normal(sp1,sp2,.50))*r).to_list())[0],
1546 csp_seg_to_point_distance(sp1_r,sp2_r, (P(csp_at_t(sp1,sp2,.75)) + P(csp_normalized_normal(sp1,sp2,.75))*r).to_list())[0],
1547 )
1549 if err>tolerance**2 and depth>0:
1550 #print_(csp_seg_to_point_distance(sp1_r,sp2_r, (P(csp_at_t(sp1,sp2,.25)) + P(csp_normalized_normal(sp1,sp2,.25))*r).to_list())[0], tolerance)
1551 if depth > offset_subdivision_depth-2 :
1552 t = csp_max_curvature(sp1,sp2)
1553 t = max(.1,min(.9 ,t))
1554 else :
1555 t = .5
1556 sp3,sp4,sp5 = csp_split(sp1,sp2,t)
1557 r1 = offset_segment_recursion(sp3,sp4,r, depth-1, tolerance)
1558 r2 = offset_segment_recursion(sp4,sp5,r, depth-1, tolerance)
1559 return r1[:-1]+ [[r1[-1][0],r1[-1][1],r2[0][2]]] + r2[1:]
1560 else :
1561 #csp_draw([[sp1_r,sp2_r]])
1562 #draw_pointer(sp1[1]+sp1_r[1], "#057", "line")
1563 #draw_pointer(sp2[1]+sp2_r[1], "#705", "line")
1564 return [sp1_r,sp2_r]
1567 ############################################################################
1568 # Some small definitions
1569 ############################################################################
1570 csp_len = len(csp)
1572 ############################################################################
1573 # Prepare the path
1574 ############################################################################
1575 # Remove all small segments (segment length < 0.001)
1577 for i in xrange(len(csp)) :
1578 for j in xrange(len(csp[i])) :
1579 sp = csp[i][j]
1580 if (P(sp[1])-P(sp[0])).mag() < 0.001 :
1581 csp[i][j][0] = sp[1]
1582 if (P(sp[2])-P(sp[0])).mag() < 0.001 :
1583 csp[i][j][2] = sp[1]
1584 for i in xrange(len(csp)) :
1585 for j in xrange(1,len(csp[i])) :
1586 if cspseglength(csp[i][j-1], csp[i][j])<0.001 :
1587 csp[i] = csp[i][:j] + csp[i][j+1:]
1588 if cspseglength(csp[i][-1],csp[i][0])>0.001 :
1589 csp[i][-1][2] = csp[i][-1][1]
1590 csp[i]+= [ [csp[i][0][1],csp[i][0][1],csp[i][0][1]] ]
1592 # TODO Get rid of self intersections.
1594 original_csp = csp[:]
1595 # Clip segments which has curvature>1/r. Because their offset will be selfintersecting and very nasty.
1597 print_("Offset prepared the path in %s"%(time.time()-time_))
1598 print_("Path length = %s"% sum([len(i)for i in csp] ) )
1599 time_ = time.time()
1601 ############################################################################
1602 # Offset
1603 ############################################################################
1604 # Create offsets for all segments in the path. And join them together inside each subpath.
1605 unclipped_offset = [[] for i in xrange(csp_len)]
1606 offsets_original = [[] for i in xrange(csp_len)]
1607 join_points = [[] for i in xrange(csp_len)]
1608 intersection = [[] for i in xrange(csp_len)]
1609 for i in xrange(csp_len) :
1610 subpath = csp[i]
1611 subpath_offset = []
1612 last_offset_len = 0
1613 for sp1,sp2 in zip(subpath, subpath[1:]) :
1614 segment_offset = csp_offset_segment(sp1,sp2,r)
1615 if subpath_offset == [] :
1616 subpath_offset = segment_offset
1618 prev_l = len(subpath_offset)
1619 else :
1620 prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:],segment_offset,sp1,sp2,sp1_l,sp2_l,r)
1621 #csp_draw([prev],"Blue")
1622 #csp_draw([arc],"Magenta")
1623 subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l+1],prev,arc,next)
1624 prev_l = len(next)
1625 sp1_l, sp2_l = sp1[:], sp2[:]
1627 # Join last and first offsets togother to close the curve
1629 prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:], subpath_offset[:2], subpath[0], subpath[1], sp1_l,sp2_l, r)
1630 subpath_offset[:2] = next[:]
1631 subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l+1],prev,arc)
1632 #csp_draw([prev],"Blue")
1633 #csp_draw([arc],"Red")
1634 #csp_draw([next],"Red")
1636 # Collect subpath's offset and save it to unclipped offset list.
1637 unclipped_offset[i] = subpath_offset[:]
1639 #for k,t in intersection[i]:
1640 # draw_pointer(csp_at_t(subpath_offset[k-1], subpath_offset[k], t))
1642 #inkex.etree.SubElement( options.doc_root, inkex.addNS('path','svg'), {"d": cubicsuperpath.formatPath(unclipped_offset), "style":"fill:none;stroke:#0f0;"} )
1643 print_("Offsetted path in %s"%(time.time()-time_))
1644 time_ = time.time()
1646 #for i in range(len(unclipped_offset)):
1647 # csp_draw([unclipped_offset[i]], color = ["Green","Red","Blue"][i%3], width = .1)
1648 #return []
1649 ############################################################################
1650 # Now to the clipping.
1651 ############################################################################
1652 # First of all find all intersection's between all segments of all offseted subpaths, including self intersections.
1654 #TODO define offset tolerance here
1655 global small_tolerance
1656 small_tolerance = 0.01
1657 summ = 0
1658 summ1 = 0
1659 for subpath_i in xrange(csp_len) :
1660 for subpath_j in xrange(subpath_i,csp_len) :
1661 subpath = unclipped_offset[subpath_i]
1662 subpath1 = unclipped_offset[subpath_j]
1663 for i in xrange(1,len(subpath)) :
1664 # If subpath_i==subpath_j we are looking for self intersections, so
1665 # we'll need search intersections only for xrange(i,len(subpath1))
1666 for j in ( xrange(i,len(subpath1)) if subpath_i==subpath_j else xrange(len(subpath1))) :
1667 if subpath_i==subpath_j and j==i :
1668 # Find self intersections of a segment
1669 sp1,sp2,sp3 = csp_split(subpath[i-1],subpath[i],.5)
1670 intersections = csp_segments_intersection(sp1,sp2,sp2,sp3)
1671 summ +=1
1672 for t in intersections :
1673 summ1 += 1
1674 if not ( small(t[0]-1) and small(t[1]) ) and 0<=t[0]<=1 and 0<=t[1]<=1 :
1675 intersection[subpath_i] += [ [i,t[0]/2],[j,t[1]/2+.5] ]
1676 else :
1677 intersections = csp_segments_intersection(subpath[i-1],subpath[i],subpath1[j-1],subpath1[j])
1678 summ +=1
1679 for t in intersections :
1680 summ1 += 1
1681 #TODO tolerance dependence to cpsp_length(t)
1682 if len(t) == 2 and 0<=t[0]<=1 and 0<=t[1]<=1 and not (
1683 subpath_i==subpath_j and (
1684 (j-i-1) % (len(subpath)-1) == 0 and small(t[0]-1) and small(t[1]) or
1685 (i-j-1) % (len(subpath)-1) == 0 and small(t[1]-1) and small(t[0]) ) ) :
1686 intersection[subpath_i] += [ [i,t[0]] ]
1687 intersection[subpath_j] += [ [j,t[1]] ]
1688 #draw_pointer(csp_at_t(subpath[i-1],subpath[i],t[0]),"#f00")
1689 #print_(t)
1690 #print_(i,j)
1691 elif len(t)==5 and t[4]=="Overlap":
1692 intersection[subpath_i] += [ [i,t[0]], [i,t[1]] ]
1693 intersection[subpath_j] += [ [j,t[1]], [j,t[3]] ]
1695 print_("Intersections found in %s"%(time.time()-time_))
1696 print_("Examined %s segments"%(summ))
1697 print_("found %s intersections"%(summ1))
1698 time_ = time.time()
1700 ########################################################################
1701 # Split unclipped offset by intersection points into splitted_offset
1702 ########################################################################
1703 splitted_offset = []
1704 for i in xrange(csp_len) :
1705 subpath = unclipped_offset[i]
1706 if len(intersection[i]) > 0 :
1707 parts = csp_subpath_split_by_points(subpath, intersection[i])
1708 # Close parts list to close path (The first and the last parts are joined together)
1709 if [1,0.] not in intersection[i] :
1710 parts[0][0][0] = parts[-1][-1][0]
1711 parts[0] = csp_concat_subpaths(parts[-1], parts[0])
1712 splitted_offset += parts[:-1]
1713 else:
1714 splitted_offset += parts[:]
1715 else :
1716 splitted_offset += [subpath[:]]
1718 #for i in range(len(splitted_offset)):
1719 # csp_draw([splitted_offset[i]], color = ["Green","Red","Blue"][i%3])
1720 print_("Splitted in %s"%(time.time()-time_))
1721 time_ = time.time()
1724 ########################################################################
1725 # Clipping
1726 ########################################################################
1727 result = []
1728 for subpath_i in range(len(splitted_offset)):
1729 clip = False
1730 s1 = splitted_offset[subpath_i]
1731 for subpath_j in range(len(splitted_offset)):
1732 s2 = splitted_offset[subpath_j]
1733 if (P(s1[0][1])-P(s2[-1][1])).l2()<0.0001 and ( (subpath_i+1) % len(splitted_offset) != subpath_j ):
1734 if dot(csp_normalized_normal(s2[-2],s2[-1],1.),csp_normalized_slope(s1[0],s1[1],0.))*r<-0.0001 :
1735 clip = True
1736 break
1737 if (P(s2[0][1])-P(s1[-1][1])).l2()<0.0001 and ( (subpath_j+1) % len(splitted_offset) != subpath_i ):
1738 if dot(csp_normalized_normal(s2[0],s2[1],0.),csp_normalized_slope(s1[-2],s1[-1],1.))*r>0.0001 :
1739 clip = True
1740 break
1742 if not clip :
1743 result += [s1[:]]
1744 elif options.offset_draw_clippend_path :
1745 csp_draw([s1],color="Red",width=.1)
1746 draw_pointer( csp_at_t(s2[-2],s2[-1],1.)+
1747 (P(csp_at_t(s2[-2],s2[-1],1.))+ P(csp_normalized_normal(s2[-2],s2[-1],1.))*10).to_list(),"Green", "line" )
1748 draw_pointer( csp_at_t(s1[0],s1[1],0.)+
1749 (P(csp_at_t(s1[0],s1[1],0.))+ P(csp_normalized_slope(s1[0],s1[1],0.))*10).to_list(),"Red", "line" )
1751 # Now join all together and check closure and orientation of result
1752 joined_result = csp_join_subpaths(result)
1753 # Check if each subpath from joined_result is closed
1754 #csp_draw(joined_result,color="Green",width=1)
1757 for s in joined_result[:] :
1758 if csp_subpaths_end_to_start_distance2(s,s) > 0.001 :
1759 # Remove open parts
1760 if options.offset_draw_clippend_path:
1761 csp_draw([s],color="Orange",width=1)
1762 draw_pointer(s[0][1], comment= csp_subpaths_end_to_start_distance2(s,s))
1763 draw_pointer(s[-1][1], comment = csp_subpaths_end_to_start_distance2(s,s))
1764 joined_result.remove(s)
1765 else :
1766 # Remove small parts
1767 minx,miny,maxx,maxy = csp_true_bounds([s])
1768 if (minx[0]-maxx[0])**2 + (miny[1]-maxy[1])**2 < 0.1 :
1769 joined_result.remove(s)
1770 print_("Clipped and joined path in %s"%(time.time()-time_))
1771 time_ = time.time()
1773 ########################################################################
1774 # Now to the Dummy cliping: remove parts from splitted offset if their
1775 # centers are closer to the original path than offset radius.
1776 ########################################################################
1778 r1,r2 = ( (0.99*r)**2, (1.01*r)**2 ) if abs(r*.01)<1 else ((abs(r)-1)**2, (abs(r)+1)**2)
1779 for s in joined_result[:]:
1780 dist = csp_to_point_distance(original_csp, s[int(len(s)/2)][1], dist_bounds = [r1,r2], tolerance = .000001)
1781 if not r1 < dist[0] < r2 :
1782 joined_result.remove(s)
1783 if options.offset_draw_clippend_path:
1784 csp_draw([s], comment = math.sqrt(dist[0]))
1785 draw_pointer(csp_at_t(csp[dist[1]][dist[2]-1],csp[dist[1]][dist[2]],dist[3])+s[int(len(s)/2)][1],"blue", "line", comment = [math.sqrt(dist[0]),i,j,sp] )
1787 print_("-----------------------------")
1788 print_("Total offset time %s"%(time.time()-time_start))
1789 print_()
1790 return joined_result
1796 ################################################################################
1797 ###
1798 ### Biarc function
1799 ###
1800 ### Calculates biarc approximation of cubic super path segment
1801 ### splits segment if needed or approximates it with straight line
1802 ###
1803 ################################################################################
1804 def biarc(sp1, sp2, z1, z2, depth=0):
1805 def biarc_split(sp1,sp2, z1, z2, depth):
1806 if depth<options.biarc_max_split_depth:
1807 sp1,sp2,sp3 = csp_split(sp1,sp2)
1808 l1, l2 = cspseglength(sp1,sp2), cspseglength(sp2,sp3)
1809 if l1+l2 == 0 : zm = z1
1810 else : zm = z1+(z2-z1)*l1/(l1+l2)
1811 return biarc(sp1,sp2,z1,zm,depth+1)+biarc(sp2,sp3,zm,z2,depth+1)
1812 else: return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
1814 P0, P4 = P(sp1[1]), P(sp2[1])
1815 TS, TE, v = (P(sp1[2])-P0), -(P(sp2[0])-P4), P0 - P4
1816 tsa, tea, va = TS.angle(), TE.angle(), v.angle()
1817 if TE.mag()<straight_distance_tolerance and TS.mag()<straight_distance_tolerance:
1818 # Both tangents are zerro - line straight
1819 return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
1820 if TE.mag() < straight_distance_tolerance:
1821 TE = -(TS+v).unit()
1822 r = TS.mag()/v.mag()*2
1823 elif TS.mag() < straight_distance_tolerance:
1824 TS = -(TE+v).unit()
1825 r = 1/( TE.mag()/v.mag()*2 )
1826 else:
1827 r=TS.mag()/TE.mag()
1828 TS, TE = TS.unit(), TE.unit()
1829 tang_are_parallel = ((tsa-tea)%math.pi<straight_tolerance or math.pi-(tsa-tea)%math.pi<straight_tolerance )
1830 if ( tang_are_parallel and
1831 ((v.mag()<straight_distance_tolerance or TE.mag()<straight_distance_tolerance or TS.mag()<straight_distance_tolerance) or
1832 1-abs(TS*v/(TS.mag()*v.mag()))<straight_tolerance) ):
1833 # Both tangents are parallel and start and end are the same - line straight
1834 # or one of tangents still smaller then tollerance
1836 # Both tangents and v are parallel - line straight
1837 return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
1839 c,b,a = v*v, 2*v*(r*TS+TE), 2*r*(TS*TE-1)
1840 if v.mag()==0:
1841 return biarc_split(sp1, sp2, z1, z2, depth)
1842 asmall, bsmall, csmall = abs(a)<10**-10,abs(b)<10**-10,abs(c)<10**-10
1843 if asmall and b!=0: beta = -c/b
1844 elif csmall and a!=0: beta = -b/a
1845 elif not asmall:
1846 discr = b*b-4*a*c
1847 if discr < 0: raise ValueError, (a,b,c,discr)
1848 disq = discr**.5
1849 beta1 = (-b - disq) / 2 / a
1850 beta2 = (-b + disq) / 2 / a
1851 if beta1*beta2 > 0 : raise ValueError, (a,b,c,disq,beta1,beta2)
1852 beta = max(beta1, beta2)
1853 elif asmall and bsmall:
1854 return biarc_split(sp1, sp2, z1, z2, depth)
1855 alpha = beta * r
1856 ab = alpha + beta
1857 P1 = P0 + alpha * TS
1858 P3 = P4 - beta * TE
1859 P2 = (beta / ab) * P1 + (alpha / ab) * P3
1862 def calculate_arc_params(P0,P1,P2):
1863 D = (P0+P2)/2
1864 if (D-P1).mag()==0: return None, None
1865 R = D - ( (D-P0).mag()**2/(D-P1).mag() )*(P1-D).unit()
1866 p0a, p1a, p2a = (P0-R).angle()%(2*math.pi), (P1-R).angle()%(2*math.pi), (P2-R).angle()%(2*math.pi)
1867 alpha = (p2a - p0a) % (2*math.pi)
1868 if (p0a<p2a and (p1a<p0a or p2a<p1a)) or (p2a<p1a<p0a) :
1869 alpha = -2*math.pi+alpha
1870 if abs(R.x)>1000000 or abs(R.y)>1000000 or (R-P0).mag<options.min_arc_radius :
1871 return None, None
1872 else :
1873 return R, alpha
1874 R1,a1 = calculate_arc_params(P0,P1,P2)
1875 R2,a2 = calculate_arc_params(P2,P3,P4)
1876 if R1==None or R2==None or (R1-P0).mag()<straight_tolerance or (R2-P2).mag()<straight_tolerance : return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
1878 d = csp_to_arc_distance(sp1,sp2, [P0,P2,R1,a1],[P2,P4,R2,a2])
1879 if d > options.biarc_tolerance and depth<options.biarc_max_split_depth : return biarc_split(sp1, sp2, z1, z2, depth)
1880 else:
1881 if R2.mag()*a2 == 0 : zm = z2
1882 else : zm = z1 + (z2-z1)*(abs(R1.mag()*a1))/(abs(R2.mag()*a2)+abs(R1.mag()*a1))
1883 return [ [ sp1[1], 'arc', [R1.x,R1.y], a1, [P2.x,P2.y], [z1,zm] ], [ [P2.x,P2.y], 'arc', [R2.x,R2.y], a2, [P4.x,P4.y], [zm,z2] ] ]
1886 def biarc_curve_segment_length(seg):
1887 if seg[1] == "arc" :
1888 return math.sqrt((seg[0][0]-seg[2][0])**2+(seg[0][1]-seg[2][1])**2)*seg[3]
1889 elif seg[1] == "line" :
1890 return math.sqrt((seg[0][0]-seg[4][0])**2+(seg[0][1]-seg[4][1])**2)
1891 else:
1892 return 0
1895 def biarc_curve_clip_at_l(curve, l, clip_type = "strict") :
1896 # get first subcurve and ceck it's length
1897 subcurve, subcurve_l, moved = [], 0, False
1898 for seg in curve:
1899 if seg[1] == "move" and moved or seg[1] == "end" :
1900 break
1901 if seg[1] == "move" : moved = True
1902 subcurve_l += biarc_curve_segment_length(seg)
1903 if seg[1] == "arc" or seg[1] == "line" :
1904 subcurve += [seg]
1906 if subcurve_l < l and clip_type == "strict" : return []
1907 lc = 0
1908 if (subcurve[-1][4][0]-subcurve[0][0][0])**2 + (subcurve[-1][4][1]-subcurve[0][0][1])**2 < 10**-7 : subcurve_closed = True
1909 i = 0
1910 reverse = False
1911 while lc<l :
1912 seg = subcurve[i]
1913 if reverse :
1914 if seg[1] == "line" :
1915 seg = [seg[4], "line", 0 , 0, seg[0], seg[5]] # Hmmm... Do we have to swap seg[5][0] and seg[5][1] (zstart and zend) or not?
1916 elif seg[1] == "arc" :
1917 seg = [seg[4], "arc", seg[2] , -seg[3], seg[0], seg[5]] # Hmmm... Do we have to swap seg[5][0] and seg[5][1] (zstart and zend) or not?
1918 ls = biarc_curve_segment_length(seg)
1919 if ls != 0 :
1920 if l-lc>ls :
1921 res += [seg]
1922 else :
1923 if seg[1] == "arc" :
1924 r = math.sqrt((seg[0][0]-seg[2][0])**2+(seg[0][1]-seg[2][1])**2)
1925 x,y = seg[0][0]-seg[2][0], seg[0][1]-seg[2][1]
1926 a = seg[3]/ls*(l-lc)
1927 x,y = x*math.cos(a) - y*math.sin(a), x*math.sin(a) + y*math.cos(a)
1928 x,y = x+seg[2][0], y+seg[2][1]
1929 res += [[ seg[0], "arc", seg[2], a, [x,y], [seg[5][0],seg[5][1]/ls*(l-lc)] ]]
1930 if seg[1] == "line" :
1931 res += [[ seg[0], "line", 0, 0, [(seg[4][0]-seg[0][0])/ls*(l-lc),(seg[4][1]-seg[0][1])/ls*(l-lc)], [seg[5][0],seg[5][1]/ls*(l-lc)] ]]
1932 i += 1
1933 if i >= len(subcurve) and not subcurve_closed:
1934 reverse = not reverse
1935 i = i%len(subcurve)
1936 return res
1940 class Postprocessor():
1941 def __init__(self, error_function_handler):
1942 self.error = error_function_handler
1943 self.functions = {
1944 "remap" : self.remap,
1945 "remapi" : self.remapi ,
1946 "scale" : self.scale,
1947 "move" : self.move,
1948 "flip" : self.flip_axis,
1949 "flip_axis" : self.flip_axis,
1950 "round" : self.round_coordinates,
1951 "parameterize" : self.parameterize,
1952 }
1955 def process(self,command):
1956 command = re.sub(r"\\\\",":#:#:slash:#:#:",command)
1957 command = re.sub(r"\\;",":#:#:semicolon:#:#:",command)
1958 command = command.split(";")
1959 for s in command:
1960 s = re.sub(":#:#:slash:#:#:","\\\\",s)
1961 s = re.sub(":#:#:semicolon:#:#:","\\;",s)
1962 s = s.strip()
1963 if s!="" :
1964 self.parse_command(s)
1967 def parse_command(self,command):
1968 r = re.match(r"([A-Za-z0-9_]+)\s*\(\s*(.*)\)",command)
1969 if not r:
1970 self.error("Parse error while postprocessing.\n(Command: '%s')"%(command), "error")
1971 function, parameters = r.group(1).lower(),r.group(2)
1972 if function in self.functions :
1973 print_("Postprocessor: executing function %s(%s)"%(function,parameters))
1974 self.functions[function](parameters)
1975 else :
1976 self.error("Unrecognized function '%s' while postprocessing.\n(Command: '%s')"%(function,command), "error")
1979 def re_sub_on_gcode_lines(self, pattern,replacemant):
1980 gcode = self.gcode.split("\n")
1981 self.gcode = ""
1982 for i in range(len(gcode)) :
1983 self.gcode += re.sub(pattern,replacement,gcode[i])
1986 def remapi(self,parameters):
1987 self.remap(parameters, case_sensitive = True)
1990 def remap(self,parameters, case_sensitive = False):
1991 # remap parameters should be like "x->y,y->x"
1992 parameters = parameters.replace("\,",":#:#:coma:#:#:")
1993 parameters = parameters.split(",")
1994 pattern, remap = [], []
1995 for s in parameters:
1996 s = s.replace(":#:#:coma:#:#:","\,")
1997 r = re.match("""\s*(\'|\")(.*)\\1\s*->\s*(\'|\")(.*)\\3\s*""",s)
1998 if not r :
1999 self.error("Bad parameters for remap.\n(Parameters: '%s')"%(parameters), "error")
2000 pattern +=[r.group(2)]
2001 remap +=[r.group(4)]
2005 for i in range(len(pattern)) :
2006 if case_sensitive :
2007 self.gcode = ireplace(self.gcode, pattern[i], ":#:#:remap_pattern%s:#:#:"%i )
2008 else :
2009 self.gcode = self.gcode.replace(pattern[i], ":#:#:remap_pattern%s:#:#:"%i)
2011 for i in range(len(remap)) :
2012 self.gcode = self.gcode.replace(":#:#:remap_pattern%s:#:#:"%i, remap[i])
2015 def transform(self, move, scale):
2016 axis = ["xi","yj","zk","a"]
2017 flip = scale[0]*scale[1]*scale[2] < 0
2018 gcode = ""
2019 warned = []
2020 r_scale = scale[0]
2021 plane = "g17"
2022 for s in self.gcode.split("\n"):
2023 # get plane selection:
2024 s_wo_comments = re.sub(r"\([^\)]*\)","",s)
2025 r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments)
2026 if r :
2027 plane = r.group(1).lower()
2028 if plane == "g17" : r_scale = scale[0] # plane XY -> scale x
2029 if plane == "g18" : r_scale = scale[0] # plane XZ -> scale x
2030 if plane == "g19" : r_scale = scale[1] # plane YZ -> scale y
2031 # Raise warning if scale factors are not the game for G02 and G03
2032 if plane not in warned:
2033 r = re.search(r"(?i)(G02|G03)", s_wo_comments)
2034 if r :
2035 if plane == "g17" and scale[0]!=scale[1]: self.error("Post-processor: Scale factors for X and Y axis are not the same. G02 and G03 codes will be corrupted.","warning")
2036 if plane == "g18" and scale[0]!=scale[2]: self.error("Post-processor: Scale factors for X and Z axis are not the same. G02 and G03 codes will be corrupted.","warning")
2037 if plane == "g19" and scale[1]!=scale[2]: self.error("Post-processor: Scale factors for Y and Z axis are not the same. G02 and G03 codes will be corrupted.","warning")
2038 warned += [plane]
2039 # Transform
2040 for i in range(len(axis)) :
2041 if move[i] != 0 or scale[i] != 1:
2042 for a in axis[i] :
2043 r = re.search(r"(?i)("+a+r")\s*(-?)\s*(\d*\.?\d*)", s)
2044 if r and r.group(3)!="":
2045 s = re.sub(r"(?i)("+a+r")\s*(-?)\s*(\d*\.?\d*)", r"\1 %f"%(float(r.group(2)+r.group(3))*scale[i]+(move[i] if a not in ["i","j","k"] else 0) ), s)
2046 #scale radius R
2047 if r_scale != 1 :
2048 r = re.search(r"(?i)(r)\s*(-?\s*(\d*\.?\d*))", s)
2049 if r and r.group(3)!="":
2050 try:
2051 s = re.sub(r"(?i)(r)\s*(-?)\s*(\d*\.?\d*)", r"\1 %f"%( float(r.group(2)+r.group(3))*r_scale ), s)
2052 except:
2053 pass
2055 gcode += s + "\n"
2057 self.gcode = gcode
2058 if flip :
2059 self.remapi("'G02'->'G03', 'G03'->'G02'")
2062 def parameterize(self,parameters) :
2063 planes = []
2064 feeds = {}
2065 coords = []
2066 gcode = ""
2067 coords_def = {"x":"x","y":"y","z":"z","i":"x","j":"y","k":"z","a":"a"}
2068 for s in self.gcode.split("\n"):
2069 s_wo_comments = re.sub(r"\([^\)]*\)","",s)
2070 # get Planes
2071 r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments)
2072 if r :
2073 plane = r.group(1).lower()
2074 if plane not in planes :
2075 planes += [plane]
2076 # get Feeds
2077 r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s_wo_comments)
2078 if r :
2079 feed = float (r.group(2)+r.group(3))
2080 if feed not in feeds :
2081 feeds[feed] = "#"+str(len(feeds)+20)
2083 #Coordinates
2084 for c in "xyzijka" :
2085 r = re.search(r"(?i)("+c+r")\s*(-?)\s*(\d*\.?\d*)", s_wo_comments)
2086 if r :
2087 c = coords_def[r.group(1).lower()]
2088 if c not in coords :
2089 coords += [c]
2090 # Add offset parametrization
2091 offset = {"x":"#6","y":"#7","z":"#8","a":"#9"}
2092 for c in coords:
2093 gcode += "%s = 0 (%s axis offset)\n" % (offset[c],c.upper())
2095 # Add scale parametrization
2096 if planes == [] : planes = ["g17"]
2097 if len(planes)>1 : # have G02 and G03 in several planes scale_x = scale_y = scale_z required
2098 gcode += "#10 = 1 (Scale factor)\n"
2099 scale = {"x":"#10","i":"#10","y":"#10","j":"#10","z":"#10","k":"#10","r":"#10"}
2100 else :
2101 gcode += "#10 = 1 (%s Scale factor)\n" % ({"g17":"XY","g18":"XZ","g19":"YZ"}[planes[0]])
2102 gcode += "#11 = 1 (%s Scale factor)\n" % ({"g17":"Z","g18":"Y","g19":"X"}[planes[0]])
2103 scale = {"x":"#10","i":"#10","y":"#10","j":"#10","z":"#10","k":"#10","r":"#10"}
2104 if "g17" in planes :
2105 scale["z"] = "#11"
2106 scale["k"] = "#11"
2107 if "g18" in planes :
2108 scale["y"] = "#11"
2109 scale["j"] = "#11"
2110 if "g19" in planes :
2111 scale["x"] = "#11"
2112 scale["i"] = "#11"
2113 # Add a scale
2114 if "a" in coords:
2115 gcode += "#12 = 1 (A axis scale)\n"
2116 scale["a"] = "#12"
2118 # Add feed parametrization
2119 for f in feeds :
2120 gcode += "%s = %f (Feed definition)\n" % (feeds[f],f)
2122 # Parameterize Gcode
2123 for s in self.gcode.split("\n"):
2124 #feed replace :
2125 r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s)
2126 if r and len(r.group(3))>0:
2127 s = re.sub(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", "F [%s]"%feeds[float(r.group(2)+r.group(3))], s)
2128 #Coords XYZA replace
2129 for c in "xyza" :
2130 r = re.search(r"(?i)(("+c+r")\s*(-?)\s*(\d*\.?\d*))", s)
2131 if r and len(r.group(4))>0:
2132 s = re.sub(r"(?i)("+c+r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*%s+%s]"%(scale[c],offset[c]), s)
2134 #Coords IJKR replace
2135 for c in "ijkr" :
2136 r = re.search(r"(?i)(("+c+r")\s*(-?)\s*(\d*\.?\d*))", s)
2137 if r and len(r.group(4))>0:
2138 s = re.sub(r"(?i)("+c+r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*%s]"%scale[c], s)
2140 gcode += s + "\n"
2142 self.gcode = gcode
2145 def round_coordinates(self,parameters) :
2146 try:
2147 round_ = int(parameters)
2148 except :
2149 self.error("Bad parameters for round. Round should be an integer! \n(Parameters: '%s')"%(parameters), "error")
2150 gcode = ""
2151 for s in self.gcode.split("\n"):
2152 for a in "xyzijkaf" :
2153 r = re.search(r"(?i)("+a+r")\s*(-?\s*(\d*\.?\d*))", s)
2154 if r :
2156 if r.group(2)!="":
2157 s = re.sub(
2158 r"(?i)("+a+r")\s*(-?)\s*(\d*\.?\d*)",
2159 (r"\1 %0."+str(round_)+"f" if round_>0 else r"\1 %d")%round(float(r.group(2)),round_),
2160 s)
2161 gcode += s + "\n"
2162 self.gcode = gcode
2165 def scale(self, parameters):
2166 parameters = parameters.split(",")
2167 scale = [1.,1.,1.,1.]
2168 try :
2169 for i in range(len(parameters)) :
2170 if float(parameters[i])==0 :
2171 self.error("Bad parameters for scale. Scale should not be 0 at any axis! \n(Parameters: '%s')"%(parameters), "error")
2172 scale[i] = float(parameters[i])
2173 except :
2174 self.error("Bad parameters for scale.\n(Parameters: '%s')"%(parameters), "error")
2175 self.transform([0,0,0,0],scale)
2178 def move(self, parameters):
2179 parameters = parameters.split(",")
2180 move = [0.,0.,0.,0.]
2181 try :
2182 for i in range(len(parameters)) :
2183 move[i] = float(parameters[i])
2184 except :
2185 self.error("Bad parameters for move.\n(Parameters: '%s')"%(parameters), "error")
2186 self.transform(move,[1.,1.,1.,1.])
2189 def flip_axis(self, parameters):
2190 parameters = parameters.lower()
2191 axis = {"x":1.,"y":1.,"z":1.,"a":1.}
2192 for p in parameters:
2193 if p in [","," "," ","\r","'",'"'] : continue
2194 if p not in ["x","y","z","a"] :
2195 self.error("Bad parameters for flip_axis. Parameter should be string consists of 'xyza' \n(Parameters: '%s')"%(parameters), "error")
2196 axis[p] = -axis[p]
2197 self.scale("%f,%f,%f,%f"%(axis["x"],axis["y"],axis["z"],axis["a"]))
2201 ################################################################################
2202 ### Polygon class
2203 ################################################################################
2204 class Polygon:
2205 def __init__(self, polygon=None):
2206 self.polygon = [] if polygon==None else polygon[:]
2209 def move(self, x, y) :
2210 for i in range(len(self.polygon)) :
2211 for j in range(len(self.polygon[i])) :
2212 self.polygon[i][j][0] += x
2213 self.polygon[i][j][1] += y
2216 def bounds(self) :
2217 minx,miny,maxx,maxy = 1e400, 1e400, -1e400, -1e400
2218 for poly in self.polygon :
2219 for p in poly :
2220 if minx > p[0] : minx = p[0]
2221 if miny > p[1] : miny = p[1]
2222 if maxx < p[0] : maxx = p[0]
2223 if maxy < p[1] : maxy = p[1]
2224 return minx*1,miny*1,maxx*1,maxy*1
2227 def width(self):
2228 b = self.bounds()
2229 return b[2]-b[0]
2232 def rotate_(self,sin,cos) :
2233 for i in range(len(self.polygon)) :
2234 for j in range(len(self.polygon[i])) :
2235 x,y = self.polygon[i][j][0], self.polygon[i][j][1]
2236 self.polygon[i][j][0] = x*cos - y*sin
2237 self.polygon[i][j][1] = x*sin + y*cos
2240 def rotate(self, a):
2241 cos, sin = math.cos(a), math.sin(a)
2242 self.rotate_(sin,cos)
2245 def drop_into_direction(self, direction, surface) :
2246 # Polygon is a list of simple polygons
2247 # Surface is a polygon + line y = 0
2248 # Direction is [dx,dy]
2249 if len(self.polygon) == 0 or len(self.polygon[0])==0 : return
2250 if direction[0]**2 + direction[1]**2 <1e-10 : return
2251 direction = normalize(direction)
2252 sin,cos = direction[0], -direction[1]
2253 self.rotate_(-sin,cos)
2254 surface.rotate_(-sin,cos)
2255 self.drop_down(surface, zerro_plane = False)
2256 self.rotate_(sin,cos)
2257 surface.rotate_(sin,cos)
2260 def centroid(self):
2261 centroids = []
2262 sa = 0
2263 for poly in self.polygon:
2264 cx,cy,a = 0,0,0
2265 for i in range(len(poly)):
2266 [x1,y1],[x2,y2] = poly[i-1],poly[i]
2267 cx += (x1+x2)*(x1*y2-x2*y1)
2268 cy += (y1+y2)*(x1*y2-x2*y1)
2269 a += (x1*y2-x2*y1)
2270 a *= 3.
2271 if abs(a)>0 :
2272 cx /= a
2273 cy /= a
2274 sa += abs(a)
2275 centroids += [ [cx,cy,a] ]
2276 if sa == 0 : return [0.,0.]
2277 cx,cy = 0.,0.
2278 for c in centroids :
2279 cx += c[0]*c[2]
2280 cy += c[1]*c[2]
2281 cx /= sa
2282 cy /= sa
2283 return [cx,cy]
2286 def drop_down(self, surface, zerro_plane = True) :
2287 # Polygon is a list of simple polygons
2288 # Surface is a polygon + line y = 0
2289 # Down means min y (0,-1)
2290 if len(self.polygon) == 0 or len(self.polygon[0])==0 : return
2291 # Get surface top point
2292 top = surface.bounds()[3]
2293 if zerro_plane : top = max(0, top)
2294 # Get polygon bottom point
2295 bottom = self.bounds()[1]
2296 self.move(0, top - bottom + 10)
2297 # Now get shortest distance from surface to polygon in positive x=0 direction
2298 # Such distance = min(distance(vertex, edge)...) where edge from surface and
2299 # vertex from polygon and vice versa...
2300 dist = 1e300
2301 for poly in surface.polygon :
2302 for i in range(len(poly)) :
2303 for poly1 in self.polygon :
2304 for i1 in range(len(poly1)) :
2305 st,end = poly[i-1], poly[i]
2306 vertex = poly1[i1]
2307 if st[0]<=vertex[0]<= end[0] or end[0]<=vertex[0]<=st[0] :
2308 if st[0]==end[0] : d = min(vertex[1]-st[1],vertex[1]-end[1])
2309 else : d = vertex[1] - st[1] - (end[1]-st[1])*(vertex[0]-st[0])/(end[0]-st[0])
2310 if dist > d : dist = d
2311 # and vice versa just change the sign because vertex now under the edge
2312 st,end = poly1[i1-1], poly1[i1]
2313 vertex = poly[i]
2314 if st[0]<=vertex[0]<=end[0] or end[0]<=vertex[0]<=st[0] :
2315 if st[0]==end[0] : d = min(- vertex[1]+st[1],-vertex[1]+end[1])
2316 else : d = - vertex[1] + st[1] + (end[1]-st[1])*(vertex[0]-st[0])/(end[0]-st[0])
2317 if dist > d : dist = d
2319 if zerro_plane and dist > 10 + top : dist = 10 + top
2320 #print_(dist, top, bottom)
2321 #self.draw()
2322 self.move(0, -dist)
2325 def draw(self,color="#075",width=.1) :
2326 for poly in self.polygon :
2327 csp_draw( [csp_subpath_line_to([],poly+[poly[0]])], color=color,width=width )
2330 def add(self, add) :
2331 if type(add) == type([]) :
2332 self.polygon += add[:]
2333 else :
2334 self.polygon += add.polygon[:]
2337 def point_inside(self,p) :
2338 inside = False
2339 for poly in self.polygon :
2340 for i in range(len(poly)):
2341 st,end = poly[i-1], poly[i]
2342 if p==st or p==end : return True # point is a vertex = point is on the edge
2343 if st[0]>end[0] : st, end = end, st # This will be needed to check that edge if open only at rigth end
2344 c = (p[1]-st[1])*(end[0]-st[0])-(end[1]-st[1])*(p[0]-st[0])
2345 #print_(c)
2346 if st[0]<=p[0]<end[0] :
2347 if c<0 :
2348 inside = not inside
2349 elif c == 0 : return True # point is on the edge
2350 elif st[0]==end[0]==p[0] and (st[1]<=p[1]<=end[1] or end[1]<=p[1]<=st[1]) : # point is on the edge
2351 return True
2352 return inside
2355 def hull(self) :
2356 # Add vertices at all self intersection points.
2357 hull = []
2358 for i1 in range(len(self.polygon)):
2359 poly1 = self.polygon[i1]
2360 poly_ = []
2361 for j1 in range(len(poly1)):
2362 s, e = poly1[j1-1],poly1[j1]
2363 poly_ += [s]
2365 # Check self intersections
2366 for j2 in range(j1+1,len(poly1)):
2367 s1, e1 = poly1[j2-1],poly1[j2]
2368 int_ = line_line_intersection_points(s,e,s1,e1)
2369 for p in int_ :
2370 if point_to_point_d2(p,s)>0.000001 and point_to_point_d2(p,e)>0.000001 :
2371 poly_ += [p]
2372 # Check self intersections with other polys
2373 for i2 in range(len(self.polygon)):
2374 if i1==i2 : continue
2375 poly2 = self.polygon[i2]
2376 for j2 in range(len(poly2)):
2377 s1, e1 = poly2[j2-1],poly2[j2]
2378 int_ = line_line_intersection_points(s,e,s1,e1)
2379 for p in int_ :
2380 if point_to_point_d2(p,s)>0.000001 and point_to_point_d2(p,e)>0.000001 :
2381 poly_ += [p]
2382 hull += [poly_]
2383 # Create the dictionary containing all edges in both directions
2384 edges = {}
2385 for poly in self.polygon :
2386 for i in range(len(poly)):
2387 s,e = tuple(poly[i-1]), tuple(poly[i])
2388 if (point_to_point_d2(e,s)<0.000001) : continue
2389 break_s, break_e = False, False
2390 for p in edges :
2391 if point_to_point_d2(p,s)<0.000001 :
2392 break_s = True
2393 s = p
2394 if point_to_point_d2(p,e)<0.000001 :
2395 break_e = True
2396 e = p
2397 if break_s and break_e : break
2398 l = point_to_point_d(s,e)
2399 if not break_s and not break_e :
2400 edges[s] = [ [s,e,l] ]
2401 edges[e] = [ [e,s,l] ]
2402 #draw_pointer(s+e,"red","line")
2403 #draw_pointer(s+e,"red","line")
2404 else :
2405 if e in edges :
2406 for edge in edges[e] :
2407 if point_to_point_d2(edge[1],s)<0.000001 :
2408 break
2409 if point_to_point_d2(edge[1],s)>0.000001 :
2410 edges[e] += [ [e,s,l] ]
2411 #draw_pointer(s+e,"red","line")
2413 else :
2414 edges[e] = [ [e,s,l] ]
2415 #draw_pointer(s+e,"green","line")
2416 if s in edges :
2417 for edge in edges[s] :
2418 if point_to_point_d2(edge[1],e)<0.000001 :
2419 break
2420 if point_to_point_d2(edge[1],e)>0.000001 :
2421 edges[s] += [ [s,e, l] ]
2422 #draw_pointer(s+e,"red","line")
2423 else :
2424 edges[s] = [ [s,e,l] ]
2425 #draw_pointer(s+e,"green","line")
2428 def angle_quadrant(sin,cos):
2429 # quadrants are (0,pi/2], (pi/2,pi], (pi,3*pi/2], (3*pi/2, 2*pi], i.e. 0 is in the 4-th quadrant
2430 if sin>0 and cos>=0 : return 1
2431 if sin>=0 and cos<0 : return 2
2432 if sin<0 and cos<=0 : return 3
2433 if sin<=0 and cos>0 : return 4
2436 def angle_is_less(sin,cos,sin1,cos1):
2437 # 0 = 2*pi is the largest angle
2438 if [sin1, cos1] == [0,1] : return True
2439 if [sin, cos] == [0,1] : return False
2440 if angle_quadrant(sin,cos)>angle_quadrant(sin1,cos1) :
2441 return False
2442 if angle_quadrant(sin,cos)<angle_quadrant(sin1,cos1) :
2443 return True
2444 if sin>=0 and cos>0 : return sin<sin1
2445 if sin>0 and cos<=0 : return sin>sin1
2446 if sin<=0 and cos<0 : return sin>sin1
2447 if sin<0 and cos>=0 : return sin<sin1
2450 def get_closes_edge_by_angle(edges, last):
2451 # Last edge is normalized vector of the last edge.
2452 min_angle = [0,1]
2453 next = last
2454 last_edge = [(last[0][0]-last[1][0])/last[2], (last[0][1]-last[1][1])/last[2]]
2455 for p in edges:
2456 #draw_pointer(list(p[0])+[p[0][0]+last_edge[0]*40,p[0][1]+last_edge[1]*40], "Red", "line", width=1)
2457 #print_("len(edges)=",len(edges))
2458 cur = [(p[1][0]-p[0][0])/p[2],(p[1][1]-p[0][1])/p[2]]
2459 cos, sin = dot(cur,last_edge), cross(cur,last_edge)
2460 #draw_pointer(list(p[0])+[p[0][0]+cur[0]*40,p[0][1]+cur[1]*40], "Orange", "line", width=1, comment = [sin,cos])
2461 #print_("cos, sin=",cos,sin)
2462 #print_("min_angle_before=",min_angle)
2464 if angle_is_less(sin,cos,min_angle[0],min_angle[1]) :
2465 min_angle = [sin,cos]
2466 next = p
2467 #print_("min_angle=",min_angle)
2469 return next
2471 # Join edges together into new polygon cutting the vertexes inside new polygon
2472 self.polygon = []
2473 len_edges = sum([len(edges[p]) for p in edges])
2474 loops = 0
2476 while len(edges)>0 :
2477 poly = []
2478 if loops > len_edges : raise ValueError, "Hull error"
2479 loops+=1
2480 # Find left most vertex.
2481 start = (1e100,1)
2482 for edge in edges :
2483 start = min(start, min(edges[edge]))
2484 last = [(start[0][0]-1,start[0][1]),start[0],1]
2485 first_run = True
2486 loops1 = 0
2487 while (last[1]!=start[0] or first_run) :
2488 first_run = False
2489 if loops1 > len_edges : raise ValueError, "Hull error"
2490 loops1 += 1
2491 next = get_closes_edge_by_angle(edges[last[1]],last)
2492 #draw_pointer(next[0]+next[1],"Green","line", comment=i, width= 1)
2493 #print_(next[0],"-",next[1])
2495 last = next
2496 poly += [ list(last[0]) ]
2497 self.polygon += [ poly ]
2498 # Remove all edges that are intersects new poly (any vertex inside new poly)
2499 poly_ = Polygon([poly])
2500 for p in edges.keys()[:] :
2501 if poly_.point_inside(list(p)) : del edges[p]
2502 self.draw(color="Green", width=1)
2505 class Arangement_Genetic:
2506 # gene = [fittness, order, rotation, xposition]
2507 # spieces = [gene]*shapes count
2508 # population = [spieces]
2509 def __init__(self, polygons, material_width):
2510 self.population = []
2511 self.genes_count = len(polygons)
2512 self.polygons = polygons
2513 self.width = material_width
2514 self.mutation_factor = 0.1
2515 self.order_mutate_factor = 1.
2516 self.move_mutate_factor = 1.
2519 def add_random_species(self,count):
2520 for i in range(count):
2521 specimen = []
2522 order = range(self.genes_count)
2523 random.shuffle(order)
2524 for j in order:
2525 specimen += [ [j, random.random(), random.random()] ]
2526 self.population += [ [None,specimen] ]
2529 def species_distance2(self,sp1,sp2) :
2530 # retun distance, each component is normalized
2531 s = 0
2532 for j in range(self.genes_count) :
2533 s += ((sp1[j][0]-sp2[j][0])/self.genes_count)**2 + (( sp1[j][1]-sp2[j][1]))**2 + ((sp1[j][2]-sp2[j][2]))**2
2534 return s
2537 def similarity(self,sp1,top) :
2538 # Define similarity as a simple distance between two points in len(gene)*len(spiece) -th dimentions
2539 # for sp2 in top_spieces sum(|sp1-sp2|)/top_count
2540 sim = 0
2541 for sp2 in top :
2542 sim += math.sqrt(species_distance2(sp1,sp2[1]))
2543 return sim/len(top)
2546 def leave_top_species(self,count):
2547 self.population.sort()
2548 res = [ copy.deepcopy(self.population[0]) ]
2549 del self.population[0]
2550 for i in range(count-1) :
2551 t = []
2552 for j in range(20) :
2553 i1 = random.randint(0,len(self.population)-1)
2554 t += [ [self.population[i1][0],i1] ]
2555 t.sort()
2556 res += [ copy.deepcopy(self.population[t[0][1]]) ]
2557 del self.population[t[0][1]]
2558 self.population = res
2559 #del self.population[0]
2560 #for c in range(count-1) :
2561 # rank = []
2562 # for i in range(len(self.population)) :
2563 # sim = self.similarity(self.population[i][1],res)
2564 # rank += [ [self.population[i][0] / sim if sim>0 else 1e100,i] ]
2565 # rank.sort()
2566 # res += [ copy.deepcopy(self.population[rank[0][1]]) ]
2567 # print_(rank[0],self.population[rank[0][1]][0])
2568 # print_(res[-1])
2569 # del self.population[rank[0][1]]
2571 self.population = res
2574 def populate_species(self,count, parent_count):
2575 self.population.sort()
2576 self.inc = 0
2577 for c in range(count):
2578 parent1 = random.randint(0,parent_count-1)
2579 parent2 = random.randint(0,parent_count-1)
2580 if parent1==parent2 : parent2 = (parent2+1) % parent_count
2581 parent1, parent2 = self.population[parent1][1], self.population[parent2][1]
2582 i1,i2 = 0, 0
2583 genes_order = []
2584 specimen = [ [0,0.,0.] for i in range(self.genes_count) ]
2586 self.incest_mutation_multiplyer = 1.
2587 self.incest_mutation_count_multiplyer = 1.
2589 if self.species_distance2(parent1, parent2) <= .01/self.genes_count :
2590 # OMG it's a incest :O!!!
2591 # Damn you bastards!
2592 self.inc +=1
2593 self.incest_mutation_multiplyer = 2.
2594 self.incest_mutation_count_multiplyer = 2.
2595 else :
2596 if random.random()<.01 : print_(self.species_distance2(parent1, parent2))
2597 start_gene = random.randint(0,self.genes_count)
2598 end_gene = (max(1,random.randint(0,self.genes_count),int(self.genes_count/4))+start_gene) % self.genes_count
2599 if end_gene<start_gene :
2600 end_gene, start_gene = start_gene, end_gene
2601 parent1, parent2 = parent2, parent1
2602 for i in range(start_gene,end_gene) :
2603 #rotation_mutate_param = random.random()/100
2604 #xposition_mutate_param = random.random()/100
2605 tr = 1. #- rotation_mutate_param
2606 tp = 1. #- xposition_mutate_param
2607 specimen[i] = [parent1[i][0], parent1[i][1]*tr+parent2[i][1]*(1-tr),parent1[i][2]*tp+parent2[i][2]*(1-tp)]
2608 genes_order += [ parent1[i][0] ]
2610 for i in range(0,start_gene)+range(end_gene,self.genes_count) :
2611 tr = 0. #rotation_mutate_param
2612 tp = 0. #xposition_mutate_param
2613 j = i
2614 while parent2[j][0] in genes_order :
2615 j = (j+1)%self.genes_count
2616 specimen[i] = [parent2[j][0], parent1[i][1]*tr+parent2[i][1]*(1-tr),parent1[i][2]*tp+parent2[i][2]*(1-tp)]
2617 genes_order += [ parent2[j][0] ]
2620 for i in range(random.randint(self.mutation_genes_count[0],self.mutation_genes_count[0]*self.incest_mutation_count_multiplyer )) :
2621 if random.random() < self.order_mutate_factor * self.incest_mutation_multiplyer :
2622 i1,i2 = random.randint(0,self.genes_count-1),random.randint(0,self.genes_count-1)
2623 specimen[i1][0], specimen[i2][0] = specimen[i2][0], specimen[i1][0]
2624 if random.random() < self.move_mutation_factor * self.incest_mutation_multiplyer:
2625 i1 = random.randint(0,self.genes_count-1)
2626 specimen[i1][1] = (specimen[i1][1]+random.random()*math.pi2*self.move_mutation_multiplier)%1.
2627 specimen[i1][2] = (specimen[i1][2]+random.random()*self.move_mutation_multiplier)%1.
2628 self.population += [ [None,specimen] ]
2631 def test_spiece_drop_down(self,spiece) :
2632 surface = Polygon()
2633 for p in spiece :
2634 time_ = time.time()
2635 poly = Polygon(copy.deepcopy(self.polygons[p[0]].polygon))
2636 poly.rotate(p[1]*math.pi2)
2637 w = poly.width()
2638 left = poly.bounds()[0]
2639 poly.move( -left + (self.width-w)*p[2],0)
2640 poly.drop_down(surface)
2641 surface.add(poly)
2642 return surface
2645 def test(self,test_function):
2646 for i in range(len(self.population)) :
2647 if self.population[i][0] == None :
2648 surface = test_function(self.population[i][1])
2649 b = surface.bounds()
2650 self.population[i][0] = (b[3]-b[1])*(b[2]-b[0])
2651 self.population.sort()
2654 def test_spiece_centroid(self,spiece) :
2655 poly = Polygon(copy.deepcopy(self.polygons[spiece[0][0]].polygon))
2656 poly.rotate(spiece[0][2]*math.pi2)
2657 surface = Polygon(poly.polygon)
2658 i = 0
2659 for p in spiece[1:] :
2660 i += 1
2661 poly = Polygon(copy.deepcopy(self.polygons[p[0]].polygon))
2662 poly.rotate(p[2]*math.pi2)
2663 c = surface.centroid()
2664 c1 = poly.centroid()
2665 direction = [math.cos(p[1]*math.pi2), -math.sin(p[1]*math.pi2)]
2666 poly.move(c[0]-c1[0]-direction[0]*100,c[1]-c1[1]-direction[1]*100)
2667 poly.drop_into_direction(direction,surface)
2668 surface.add(poly)
2669 return surface
2673 #surface.draw()
2676 ################################################################################
2677 ###
2678 ### Gcodetools class
2679 ###
2680 ################################################################################
2682 class Gcodetools(inkex.Effect):
2684 def export_gcode(self,gcode) :
2685 if self.options.postprocessor != "" or self.options.postprocessor_custom != "" :
2686 postprocessor = Postprocessor(self.error)
2687 postprocessor.gcode = gcode
2688 if self.options.postprocessor != "" :
2689 postprocessor.process(self.options.postprocessor)
2690 if self.options.postprocessor_custom != "" :
2691 postprocessor.process(self.options.postprocessor_custom)
2692 postprocessor.gcode = self.header + postprocessor.gcode + self.footer
2693 f = open(self.options.directory+self.options.file, "w")
2694 f.write(postprocessor.gcode)
2695 f.close()
2698 ################################################################################
2699 ### Arrangement: arranges paths by givven params
2700 ### TODO move it to the bottom
2701 ################################################################################
2702 def arrangement(self) :
2703 paths = self.selected_paths
2704 surface = Polygon()
2705 polygons = []
2706 time_ = time.time()
2707 print_("Arrangement start at %s"%(time_))
2708 original_paths = []
2709 for layer in self.layers :
2710 if layer in paths :
2711 for path in paths[layer] :
2712 csp = cubicsuperpath.parsePath(path.get("d"))
2713 polygon = Polygon()
2714 for subpath in csp :
2715 for sp1, sp2 in zip(subpath,subpath[1:]) :
2716 polygon.add([csp_segment_convex_hull(sp1,sp2)])
2717 #print_("Redused edges count from", sum([len(poly) for poly in polygon.polygon ]) )
2718 polygon.hull()
2719 original_paths += [path]
2720 polygons += [polygon]
2722 print_("Paths hull computed in %s sec."%(time.time()-time_))
2723 print_("Got %s polygons having average %s edges each."% ( len(polygons), float(sum([ sum([len(poly) for poly in polygon.polygon]) for polygon in polygons ])) / len(polygons) ) )
2724 time_ = time.time()
2726 # material_width = self.options.arrangement_material_width
2727 # population = Arangement_Genetic(polygons, material_width)
2728 # population.add_random_species(1)
2729 # population.test_population_centroid()
2730 ## return
2731 material_width = self.options.arrangement_material_width
2732 population = Arangement_Genetic(polygons, material_width)
2735 print_("Genetic alhorithm start at %s"%(time_))
2736 time_ = time.time()
2740 population.add_random_species(50)
2741 population.test(population.test_spiece_centroid)
2742 print_("Initial population done in %s"%(time.time()-time_))
2743 time_ = time.time()
2744 pop = copy.deepcopy(population)
2745 population_count = self.options.arrangement_population_count
2746 last_champ = []
2747 champions_count = 0
2752 for i in range(population_count):
2753 population.leave_top_species(20)
2754 population.move_mutation_multiplier = random.random()/2
2756 population.order_mutation_factor = .2
2757 population.move_mutation_factor = 1.
2758 population.mutation_genes_count = [1,2]
2759 population.populate_species(250, 20)
2760 """
2761 randomize = i%100 < 40
2762 if i%100 < 40 :
2763 population.add_random_species(250)
2764 if 40<= i%100 < 100 :
2765 population.mutation_genes_count = [1,max(2,int(population.genes_count/4))] #[1,max(2,int(population.genes_count/2))] if 40<=i%100<60 else [1,max(2,int(population.genes_count/10))]
2766 population.move_mutation_multiplier = 1. if 40<=i%100<80 else .1
2767 population.move_mutation_factor = (-(i%100)/30+10/3) if 50<=i%100<100 else .5
2768 population.order_mutation_factor = 1./(i%100-79) if 80<=i%100<100 else 1.
2769 population.populate_species(250, 10)
2770 """
2771 population.test(population.test_spiece_centroid)
2772 draw_new_champ = False
2773 print_()
2774 for x in population.population[:10]:
2775 print_(x[0])
2777 if population.population[0][0]!= last_champ :
2778 draw_new_champ = True
2779 last_champ = population.population[0][0]*1
2782 k = ""
2783 #for j in range(10) :
2784 # k += "%s " % population.population[j][0]
2785 print_("Cicle %s done in %s"%(i,time.time()-time_))
2786 time_ = time.time()
2787 print_("%s incests been found"%population.inc)
2788 print_()
2789 #print_(k)
2790 #print_()
2791 if i == 0 or i == population_count-1 or draw_new_champ :
2792 colors = ["blue"]
2794 surface = population.test_spiece_centroid(population.population[0][1])
2795 b = surface.bounds()
2796 x,y = 400* (champions_count%10), 700*int(champions_count/10)
2797 surface.move(x-b[0],y-b[1])
2798 surface.draw(width=2, color=colors[0])
2799 draw_text("Step = %s\nSquare = %f"%(i,(b[2]-b[0])*(b[3]-b[1])),x,y-40)
2800 champions_count += 1
2802 spiece = population.population[0][1]
2803 poly = Polygon(copy.deepcopy(population.polygons[spiece[0][0]].polygon))
2804 poly.rotate(spiece[0][2]*math.pi2)
2805 surface = Polygon(poly.polygon)
2806 poly.draw(width = 2, color= "Violet")
2807 for p in spiece[1:] :
2808 poly = Polygon(copy.deepcopy(population.polygons[p[0]].polygon))
2809 poly.rotate(p[2]*math.pi2)
2810 direction = [math.cos(p[1]*math.pi2), -math.sin(p[1]*math.pi2)]
2811 normalize(direction)
2812 c = surface.centroid()
2813 c1 = poly.centroid()
2814 poly.move(c[0]-c1[0]-direction[0]*400,c[1]-c1[1]-direction[1]*400)
2815 c = surface.centroid()
2816 c1 = poly.centroid()
2817 poly.draw(width = 5, color= "Violet")
2818 draw_pointer(c+c1,"Green","line")
2819 direction = normalize(direction)
2822 sin,cos = direction[0], direction[1]
2823 poly.rotate_(-sin,cos)
2824 surface.rotate_(-sin,cos)
2825 # poly.draw(color = "Violet",width=4)
2826 surface.draw(color = "Orange",width=4)
2827 poly.rotate_(sin,cos)
2828 surface.rotate_(sin,cos)
2831 poly.drop_into_direction(direction,surface)
2832 surface.add(poly)
2835 # Now we'll need apply transforms to original paths
2838 def __init__(self):
2839 inkex.Effect.__init__(self)
2840 self.OptionParser.add_option("-d", "--directory", action="store", type="string", dest="directory", default="/home/", help="Directory for gcode file")
2841 self.OptionParser.add_option("-f", "--filename", action="store", type="string", dest="file", default="-1.0", help="File name")
2842 self.OptionParser.add_option("", "--add-numeric-suffix-to-filename", action="store", type="inkbool", dest="add_numeric_suffix_to_filename", default=True,help="Add numeric suffix to filename")
2843 self.OptionParser.add_option("", "--Zscale", action="store", type="float", dest="Zscale", default="1.0", help="Scale factor Z")
2844 self.OptionParser.add_option("", "--Zoffset", action="store", type="float", dest="Zoffset", default="0.0", help="Offset along Z")
2845 self.OptionParser.add_option("-s", "--Zsafe", action="store", type="float", dest="Zsafe", default="0.5", help="Z above all obstacles")
2846 self.OptionParser.add_option("-z", "--Zsurface", action="store", type="float", dest="Zsurface", default="0.0", help="Z of the surface")
2847 self.OptionParser.add_option("-c", "--Zdepth", action="store", type="float", dest="Zdepth", default="-0.125", help="Z depth of cut")
2848 self.OptionParser.add_option("", "--Zstep", action="store", type="float", dest="Zstep", default="-0.125", help="Z step of cutting")
2849 self.OptionParser.add_option("-p", "--feed", action="store", type="float", dest="feed", default="4.0", help="Feed rate in unit/min")
2851 self.OptionParser.add_option("", "--biarc-tolerance", action="store", type="float", dest="biarc_tolerance", default="1", help="Tolerance used when calculating biarc interpolation.")
2852 self.OptionParser.add_option("", "--biarc-max-split-depth", action="store", type="int", dest="biarc_max_split_depth", default="4", help="Defines maximum depth of splitting while approximating using biarcs.")
2854 self.OptionParser.add_option("", "--tool-diameter", action="store", type="float", dest="tool_diameter", default="3", help="Tool diameter used for area cutting")
2855 self.OptionParser.add_option("", "--max-area-curves", action="store", type="int", dest="max_area_curves", default="100", help="Maximum area curves for each area")
2856 self.OptionParser.add_option("", "--area-inkscape-radius", action="store", type="float", dest="area_inkscape_radius", default="-10", help="Radius for preparing curves using inkscape")
2857 self.OptionParser.add_option("", "--unit", action="store", type="string", dest="unit", default="G21 (All units in mm)", help="Units")
2858 self.OptionParser.add_option("", "--active-tab", action="store", type="string", dest="active_tab", default="", help="Defines which tab is active")
2860 self.OptionParser.add_option("", "--area-find-artefacts-diameter",action="store", type="float", dest="area_find_artefacts_diameter", default="1", help="artefacts seeking radius")
2861 self.OptionParser.add_option("", "--area-find-artefacts-action", action="store", type="string", dest="area_find_artefacts_action", default="mark with an arrow", help="artefacts action type")
2863 self.OptionParser.add_option("", "--auto_select_paths", action="store", type="inkbool", dest="auto_select_paths", default=True, help="Select all paths if nothing is selected.")
2865 self.OptionParser.add_option("", "--loft-distances", action="store", type="string", dest="loft_distances", default="10", help="Distances between paths.")
2866 self.OptionParser.add_option("", "--loft-direction", action="store", type="string", dest="loft_direction", default="crosswise", help="Direction of loft's interpolation.")
2867 self.OptionParser.add_option("", "--loft-interpolation-degree", action="store", type="float", dest="loft_interpolation_degree", default="2", help="Which interpolation use to loft the paths smooth interpolation or staright.")
2869 self.OptionParser.add_option("", "--min-arc-radius", action="store", type="float", dest="min_arc_radius", default=".1", help="All arc having radius less than minimum will be considered as straight line")
2871 self.OptionParser.add_option("", "--engraving-sharp-angle-tollerance",action="store", type="float", dest="engraving_sharp_angle_tollerance", default="150", help="All angles thar are less than engraving-sharp-angle-tollerance will be thought sharp")
2872 self.OptionParser.add_option("", "--engraving-max-dist", action="store", type="float", dest="engraving_max_dist", default="10", help="Distanse from original path where engraving is not needed (usualy it's cutting tool diameter)")
2873 self.OptionParser.add_option("", "--engraving-newton-iterations", action="store", type="int", dest="engraving_newton_iterations", default="4", help="Number of sample points used to calculate distance")
2874 self.OptionParser.add_option("", "--engraving-draw-calculation-paths",action="store", type="inkbool", dest="engraving_draw_calculation_paths", default=False, help="Draw additional graphics to debug engraving path")
2875 self.OptionParser.add_option("", "--engraving-cutter-shape-function",action="store", type="string", dest="engraving_cutter_shape_function", default="w", help="Cutter shape function z(w). Ex. cone: w. ")
2877 self.OptionParser.add_option("", "--lathe-width", action="store", type="float", dest="lathe_width", default=10., help="Lathe width")
2878 self.OptionParser.add_option("", "--lathe-fine-cut-width", action="store", type="float", dest="lathe_fine_cut_width", default=1., help="Fine cut width")
2879 self.OptionParser.add_option("", "--lathe-fine-cut-count", action="store", type="int", dest="lathe_fine_cut_count", default=1., help="Fine cut count")
2880 self.OptionParser.add_option("", "--lathe-create-fine-cut-using", action="store", type="string", dest="lathe_create_fine_cut_using", default="Move path", help="Create fine cut using")
2881 self.OptionParser.add_option("", "--lathe-x-axis-remap", action="store", type="string", dest="lathe_x_axis_remap", default="X", help="Lathe X axis remap")
2882 self.OptionParser.add_option("", "--lathe-z-axis-remap", action="store", type="string", dest="lathe_z_axis_remap", default="Z", help="Lathe Z axis remap")
2884 self.OptionParser.add_option("", "--create-log", action="store", type="inkbool", dest="log_create_log", default=False, help="Create log files")
2885 self.OptionParser.add_option("", "--log-filename", action="store", type="string", dest="log_filename", default='', help="Create log files")
2887 self.OptionParser.add_option("", "--orientation-points-count", action="store", type="int", dest="orientation_points_count", default='2', help="Orientation points count")
2888 self.OptionParser.add_option("", "--tools-library-type", action="store", type="string", dest="tools_library_type", default='cylinder cutter', help="Create tools defention")
2890 self.OptionParser.add_option("", "--dxfpoints-action", action="store", type="string", dest="dxfpoints_action", default='replace', help="dxfpoint sign toggle")
2892 self.OptionParser.add_option("", "--help-language", action="store", type="string", dest="help_language", default='http://www.cnc-club.ru/forum/viewtopic.php?f=33&t=35', help="Open help page in webbrowser.")
2894 self.OptionParser.add_option("", "--offset-radius", action="store", type="float", dest="offset_radius", default=10., help="Offset radius")
2895 self.OptionParser.add_option("", "--offset-step", action="store", type="float", dest="offset_step", default=10., help="Offset step")
2896 self.OptionParser.add_option("", "--offset-draw-clippend-path", action="store", type="inkbool", dest="offset_draw_clippend_path", default=False, help="Draw clipped path")
2897 self.OptionParser.add_option("", "--offset-just-get-distance", action="store", type="inkbool", dest="offset_just_get_distance", default=False, help="Don't do offset just get distance")
2899 self.OptionParser.add_option("", "--arrangement-material-width", action="store", type="float", dest="arrangement_material_width", default=500, help="Materials width for arrangement")
2900 self.OptionParser.add_option("", "--arrangement-population-count",action="store", type="int", dest="arrangement_population_count", default=100, help="Genetic algorithm populations count")
2902 self.OptionParser.add_option("", "--postprocessor", action="store", type="string", dest="postprocessor", default='', help="Postprocessor command.")
2903 self.OptionParser.add_option("", "--postprocessor-custom", action="store", type="string", dest="postprocessor_custom", default='', help="Postprocessor custom command.")
2907 self.default_tool = {
2908 "name": "Default tool",
2909 "id": "default tool",
2910 "diameter":10.,
2911 "shape": "10",
2912 "penetration angle":90.,
2913 "penetration feed":100.,
2914 "depth step":1.,
2915 "feed":400.,
2916 "in trajectotry":"",
2917 "out trajectotry":"",
2918 "gcode before path":"",
2919 "gcode after path":"",
2920 "sog":"",
2921 "spinlde rpm":"",
2922 "CW or CCW":"",
2923 "tool change gcode":" ",
2924 "4th axis meaning": " ",
2925 "4th axis scale": 1.,
2926 "4th axis offset": 0.,
2927 "passing feed":"800",
2928 "fine feed":"800",
2929 }
2930 self.tools_field_order = [
2931 'name',
2932 'id',
2933 'diameter',
2934 'feed',
2935 'shape',
2936 'penetration angle',
2937 'penetration feed',
2938 "passing feed",
2939 'depth step',
2940 "in trajectotry",
2941 "out trajectotry",
2942 "gcode before path",
2943 "gcode after path",
2944 "sog",
2945 "spinlde rpm",
2946 "CW or CCW",
2947 "tool change gcode",
2948 ]
2951 def parse_curve(self, p, layer, w = None, f = None):
2952 c = []
2953 if len(p)==0 :
2954 return []
2955 p = self.transform_csp(p, layer)
2958 ### Sort to reduce Rapid distance
2959 k = range(1,len(p))
2960 keys = [0]
2961 while len(k)>0:
2962 end = p[keys[-1]][-1][1]
2963 dist = None
2964 for i in range(len(k)):
2965 start = p[k[i]][0][1]
2966 dist = max( ( -( ( end[0]-start[0])**2+(end[1]-start[1])**2 ) ,i) , dist )
2967 keys += [k[dist[1]]]
2968 del k[dist[1]]
2969 for k in keys:
2970 subpath = p[k]
2971 c += [ [ [subpath[0][1][0],subpath[0][1][1]] , 'move', 0, 0] ]
2972 for i in range(1,len(subpath)):
2973 sp1 = [ [subpath[i-1][j][0], subpath[i-1][j][1]] for j in range(3)]
2974 sp2 = [ [subpath[i ][j][0], subpath[i ][j][1]] for j in range(3)]
2975 c += biarc(sp1,sp2,0,0) if w==None else biarc(sp1,sp2,-f(w[k][i-1]),-f(w[k][i]))
2976 # l1 = biarc(sp1,sp2,0,0) if w==None else biarc(sp1,sp2,-f(w[k][i-1]),-f(w[k][i]))
2977 # print_((-f(w[k][i-1]),-f(w[k][i]), [i1[5] for i1 in l1]) )
2978 c += [ [ [subpath[-1][1][0],subpath[-1][1][1]] ,'end',0,0] ]
2979 return c
2982 def draw_curve(self, curve, layer, group=None, style=styles["biarc_style"]):
2984 self.get_defs()
2985 # Add marker to defs if it doesnot exists
2986 if "DrawCurveMarker" not in self.defs :
2987 defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
2988 marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"DrawCurveMarker","orient":"auto","refX":"-8","refY":"-2.41063","style":"overflow:visible"})
2989 inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
2990 { "d":"m -6.55552,-2.41063 0,0 L -13.11104,0 c 1.0473,-1.42323 1.04126,-3.37047 0,-4.82126",
2991 "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" }
2992 )
2993 if "DrawCurveMarker_r" not in self.defs :
2994 defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
2995 marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"DrawCurveMarker_r","orient":"auto","refX":"8","refY":"-2.41063","style":"overflow:visible"})
2996 inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
2997 { "d":"m 6.55552,-2.41063 0,0 L 13.11104,0 c -1.0473,-1.42323 -1.04126,-3.37047 0,-4.82126",
2998 "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" }
2999 )
3000 for i in [0,1]:
3001 style['biarc%s_r'%i] = simplestyle.parseStyle(style['biarc%s'%i])
3002 style['biarc%s_r'%i]["marker-start"] = "url(#DrawCurveMarker_r)"
3003 del(style['biarc%s_r'%i]["marker-end"])
3004 style['biarc%s_r'%i] = simplestyle.formatStyle(style['biarc%s_r'%i])
3006 if group==None:
3007 group = inkex.etree.SubElement( self.layers[min(1,len(self.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} )
3008 s, arcn = '', 0
3011 a,b,c = [0.,0.], [1.,0.], [0.,1.]
3012 k = (b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1])
3013 a,b,c = self.transform(a, layer, True), self.transform(b, layer, True), self.transform(c, layer, True)
3014 if ((b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1]))*k > 0 : reverse_angle = 1
3015 else : reverse_angle = -1
3016 for sk in curve:
3017 si = sk[:]
3018 si[0], si[2] = self.transform(si[0], layer, True), (self.transform(si[2], layer, True) if type(si[2])==type([]) and len(si[2])==2 else si[2])
3020 if s!='':
3021 if s[1] == 'line':
3022 inkex.etree.SubElement( group, inkex.addNS('path','svg'),
3023 {
3024 'style': style['line'],
3025 'd':'M %s,%s L %s,%s' % (s[0][0], s[0][1], si[0][0], si[0][1]),
3026 "gcodetools": "Preview",
3027 }
3028 )
3029 elif s[1] == 'arc':
3030 arcn += 1
3031 sp = s[0]
3032 c = s[2]
3033 s[3] = s[3]*reverse_angle
3035 a = ( (P(si[0])-P(c)).angle() - (P(s[0])-P(c)).angle() )%math.pi2 #s[3]
3036 if s[3]*a<0:
3037 if a>0: a = a-math.pi2
3038 else: a = math.pi2+a
3039 r = math.sqrt( (sp[0]-c[0])**2 + (sp[1]-c[1])**2 )
3040 a_st = ( math.atan2(sp[0]-c[0],- (sp[1]-c[1])) - math.pi/2 ) % (math.pi*2)
3041 st = style['biarc%s' % (arcn%2)][:]
3042 if a>0:
3043 a_end = a_st+a
3044 st = style['biarc%s'%(arcn%2)]
3045 else:
3046 a_end = a_st*1
3047 a_st = a_st+a
3048 st = style['biarc%s_r'%(arcn%2)]
3049 inkex.etree.SubElement( group, inkex.addNS('path','svg'),
3050 {
3051 'style': st,
3052 inkex.addNS('cx','sodipodi'): str(c[0]),
3053 inkex.addNS('cy','sodipodi'): str(c[1]),
3054 inkex.addNS('rx','sodipodi'): str(r),
3055 inkex.addNS('ry','sodipodi'): str(r),
3056 inkex.addNS('start','sodipodi'): str(a_st),
3057 inkex.addNS('end','sodipodi'): str(a_end),
3058 inkex.addNS('open','sodipodi'): 'true',
3059 inkex.addNS('type','sodipodi'): 'arc',
3060 "gcodetools": "Preview",
3061 })
3062 s = si
3065 def check_dir(self):
3066 if self.options.directory[-1] not in ["/","\\"]:
3067 if "\\" in self.options.directory :
3068 self.options.directory += "\\"
3069 else :
3070 self.options.directory += "/"
3071 print_("Checking direcrory: '%s'"%self.options.directory)
3072 if (os.path.isdir(self.options.directory)):
3073 if (os.path.isfile(self.options.directory+'header')):
3074 f = open(self.options.directory+slash+'header', 'r')
3075 self.header = f.read()
3076 f.close()
3077 else:
3078 self.header = defaults['header']
3079 if (os.path.isfile(self.options.directory+'footer')):
3080 f = open(self.options.directory+'footer','r')
3081 self.footer = f.read()
3082 f.close()
3083 else:
3084 self.footer = defaults['footer']
3085 self.header += self.options.unit + "\n"
3086 else:
3087 self.error(_("Directory does not exist! Please specify existing directory at Preferences tab!"),"error")
3088 return False
3090 if self.options.add_numeric_suffix_to_filename :
3091 dir_list = os.listdir(self.options.directory)
3092 if "." in self.options.file :
3093 r = re.match(r"^(.*)(\..*)$",self.options.file)
3094 ext = r.group(2)
3095 name = r.group(1)
3096 else:
3097 ext = ""
3098 name = self.options.file
3099 max_n = 0
3100 for s in dir_list :
3101 r = re.match(r"^%s_0*(\d+)%s$"%(re.escape(name),re.escape(ext) ), s)
3102 if r :
3103 max_n = max(max_n,int(r.group(1)))
3104 filename = name + "_" + ( "0"*(4-len(str(max_n+1))) + str(max_n+1) ) + ext
3105 self.options.file = filename
3107 print_("Testing writing rights on '%s'"%(self.options.directory+self.options.file))
3108 try:
3109 f = open(self.options.directory+self.options.file, "w")
3110 f.close()
3111 except:
3112 self.error(_("Can not write to specified file!\n%s"%(self.options.directory+self.options.file)),"error")
3113 return False
3114 return True
3118 ################################################################################
3119 ###
3120 ### Generate Gcode
3121 ### Generates Gcode on given curve.
3122 ###
3123 ### Crve defenitnion [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]]
3124 ###
3125 ################################################################################
3126 def generate_gcode(self, curve, layer, depth):
3127 Zauto_scale = self.Zauto_scale[layer]
3128 tool = self.tools[layer][0]
3130 def c(c):
3131 c = [c[i] if i<len(c) else None for i in range(6)]
3132 if c[5] == 0 : c[5]=None
3133 s,s1 = [" X", " Y", " Z", " I", " J", " K"], ["","","","","",""]
3134 m,a = [1,1,self.options.Zscale*Zauto_scale,1,1,self.options.Zscale*Zauto_scale], [0,0,self.options.Zoffset,0,0,0]
3135 r = ''
3136 for i in range(6):
3137 if c[i]!=None:
3138 r += s[i] + ("%f" % (c[i]*m[i]+a[i])) + s1[i]
3139 return r
3141 def calculate_angle(a, current_a):
3142 return min(
3143 [abs(a-current_a%math.pi2+math.pi2), a+current_a-current_a%math.pi2+math.pi2],
3144 [abs(a-current_a%math.pi2-math.pi2), a+current_a-current_a%math.pi2-math.pi2],
3145 [abs(a-current_a%math.pi2), a+current_a-current_a%math.pi2])[1]
3146 if len(curve)==0 : return ""
3148 try :
3149 self.last_used_tool == None
3150 except :
3151 self.last_used_tool = None
3152 print_("working on curve")
3153 print_(curve)
3154 g = tool['tool change gcode'] +"\n" if tool != self.last_used_tool else "\n"
3156 lg, zs, f = 'G00', self.options.Zsafe, " F%f"%tool['feed']
3157 current_a = 0
3158 go_to_safe_distance = "G00" + c([None,None,zs]) + "\n"
3159 penetration_feed = " F%s"%tool['penetration feed']
3160 for i in range(1,len(curve)):
3161 # Creating Gcode for curve between s=curve[i-1] and si=curve[i] start at s[0] end at s[4]=si[0]
3162 s, si = curve[i-1], curve[i]
3163 feed = f if lg not in ['G01','G02','G03'] else ''
3164 if s[1] == 'move':
3165 g += go_to_safe_distance + "G00" + c(si[0]) + "\n" + tool['gcode before path'] + "\n"
3166 lg = 'G00'
3167 elif s[1] == 'end':
3168 g += go_to_safe_distance + tool['gcode after path'] + "\n"
3169 lg = 'G00'
3170 elif s[1] == 'line':
3171 if tool['4th axis meaning'] == "tangent knife" :
3172 a = atan2(si[0][0]-s[0][0],si[0][1]-s[0][1])
3173 a = calculate_angle(a, current_a)
3174 g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
3175 current_a = a
3176 if lg=="G00": g += "G01" + c([None,None,s[5][0]+depth]) + penetration_feed +"\n"
3177 g += "G01" +c(si[0]+[s[5][1]+depth]) + feed + "\n"
3178 lg = 'G01'
3179 elif s[1] == 'arc':
3180 r = [(s[2][0]-s[0][0]), (s[2][1]-s[0][1])]
3181 if tool['4th axis meaning'] == "tangent knife" :
3182 if s[3]<0 : # CW
3183 a1 = atan2(s[2][1]-s[0][1],-s[2][0]+s[0][0]) + math.pi
3184 else: #CCW
3185 a1 = atan2(-s[2][1]+s[0][1],s[2][0]-s[0][0]) + math.pi
3186 a = calculate_angle(a1, current_a)
3187 g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
3188 current_a = a
3189 axis4 = " A%s"%((current_a+s[3])*tool['4th axis scale']+tool['4th axis offset'])
3190 current_a = current_a+s[3]
3191 else : axis4 = ""
3192 if lg=="G00": g += "G01" + c([None,None,s[5][0]+depth]) + penetration_feed + "\n"
3193 if (r[0]**2 + r[1]**2)>self.options.min_arc_radius:
3194 r1, r2 = (P(s[0])-P(s[2])), (P(si[0])-P(s[2]))
3195 if abs(r1.mag()-r2.mag()) < 0.001 :
3196 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]) ]) + feed + axis4 + "\n"
3197 else:
3198 r = (r1.mag()+r2.mag())/2
3199 g += ("G02" if s[3]<0 else "G03") + c(si[0]+[s[5][1]+depth]) + " R%f" % (r) + feed + axis4 + "\n"
3200 lg = 'G02'
3201 else:
3202 if tool['4th axis meaning'] == "tangent knife" :
3203 a = atan2(si[0][0]-s[0][0],si[0][1]-s[0][1]) + math.pi
3204 a = calculate_angle(a, current_a)
3205 g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
3206 current_a = a
3207 g += "G01" +c(si[0]+[s[5][1]+depth]) + feed + "\n"
3208 lg = 'G01'
3209 if si[1] == 'end':
3210 g += go_to_safe_distance + tool['gcode after path'] + "\n"
3211 return g
3214 def get_transforms(self,g):
3215 root = self.document.getroot()
3216 trans = []
3217 while (g!=root):
3218 if 'transform' in g.keys():
3219 t = g.get('transform')
3220 t = simpletransform.parseTransform(t)
3221 trans = simpletransform.composeTransform(t,trans) if trans != [] else t
3222 print_(trans)
3223 g=g.getparent()
3224 return trans
3227 def apply_transforms(self,g,csp):
3228 trans = self.get_transforms(g)
3229 if trans != []:
3230 simpletransform.applyTransformToPath(trans, csp)
3231 return csp
3234 def transform(self,source_point, layer, reverse=False):
3235 if layer not in self.transform_matrix:
3236 for i in range(self.layers.index(layer),-1,-1):
3237 if self.layers[i] in self.orientation_points :
3238 break
3239 if self.layers[i] not in self.orientation_points :
3240 self.error(_("Orientation points for '%s' layer have not been found! Please add orientation points using Orientation tab!") % layer.get(inkex.addNS('label','inkscape')),"no_orientation_points")
3241 elif self.layers[i] in self.transform_matrix :
3242 self.transform_matrix[layer] = self.transform_matrix[self.layers[i]]
3243 else :
3244 orientation_layer = self.layers[i]
3245 if len(self.orientation_points[orientation_layer])>1 :
3246 self.error(_("There are more than one orientation point groups in '%s' layer") % orientation_layer.get(inkex.addNS('label','inkscape')),"more_than_one_orientation_point_groups")
3247 points = self.orientation_points[orientation_layer][0]
3248 if len(points)==2:
3249 points += [ [ [(points[1][0][1]-points[0][0][1])+points[0][0][0], -(points[1][0][0]-points[0][0][0])+points[0][0][1]], [-(points[1][1][1]-points[0][1][1])+points[0][1][0], points[1][1][0]-points[0][1][0]+points[0][1][1]] ] ]
3250 if len(points)==3:
3251 print_("Layer '%s' Orientation points: " % orientation_layer.get(inkex.addNS('label','inkscape')))
3252 for point in points:
3253 print_(point)
3254 # Zcoordinates definition taken from Orientatnion point 1 and 2
3255 self.Zcoordinates[layer] = [max(points[0][1][2],points[1][1][2]), min(points[0][1][2],points[1][1][2])]
3256 matrix = numpy.array([
3257 [points[0][0][0], points[0][0][1], 1, 0, 0, 0, 0, 0, 0],
3258 [0, 0, 0, points[0][0][0], points[0][0][1], 1, 0, 0, 0],
3259 [0, 0, 0, 0, 0, 0, points[0][0][0], points[0][0][1], 1],
3260 [points[1][0][0], points[1][0][1], 1, 0, 0, 0, 0, 0, 0],
3261 [0, 0, 0, points[1][0][0], points[1][0][1], 1, 0, 0, 0],
3262 [0, 0, 0, 0, 0, 0, points[1][0][0], points[1][0][1], 1],
3263 [points[2][0][0], points[2][0][1], 1, 0, 0, 0, 0, 0, 0],
3264 [0, 0, 0, points[2][0][0], points[2][0][1], 1, 0, 0, 0],
3265 [0, 0, 0, 0, 0, 0, points[2][0][0], points[2][0][1], 1]
3266 ])
3268 if numpy.linalg.det(matrix)!=0 :
3269 m = numpy.linalg.solve(matrix,
3270 numpy.array(
3271 [[points[0][1][0]], [points[0][1][1]], [1], [points[1][1][0]], [points[1][1][1]], [1], [points[2][1][0]], [points[2][1][1]], [1]]
3272 )
3273 ).tolist()
3274 self.transform_matrix[layer] = [[m[j*3+i][0] for i in range(3)] for j in range(3)]
3276 else :
3277 self.error(_("Orientation points are wrong! (if there are two orientation points they sould not be the same. If there are three orientation points they should not be in a straight line.)"),"wrong_orientation_points")
3278 else :
3279 self.error(_("Orientation points are wrong! (if there are two orientation points they sould not be the same. If there are three orientation points they should not be in a straight line.)"),"wrong_orientation_points")
3281 self.transform_matrix_reverse[layer] = numpy.linalg.inv(self.transform_matrix[layer]).tolist()
3282 print_("\n Layer '%s' transformation matrixes:" % layer.get(inkex.addNS('label','inkscape')) )
3283 print_(self.transform_matrix)
3284 print_(self.transform_matrix_reverse)
3286 ###self.Zauto_scale[layer] = math.sqrt( (self.transform_matrix[layer][0][0]**2 + self.transform_matrix[layer][1][1]**2)/2 )
3287 ### Zautoscale is absolete
3288 self.Zauto_scale[layer] = 1
3289 print_("Z automatic scale = %s (computed according orientation points)" % self.Zauto_scale[layer])
3291 x,y = source_point[0], source_point[1]
3292 if not reverse :
3293 t = self.transform_matrix[layer]
3294 else :
3295 t = self.transform_matrix_reverse[layer]
3296 return [t[0][0]*x+t[0][1]*y+t[0][2], t[1][0]*x+t[1][1]*y+t[1][2]]
3299 def transform_csp(self, csp_, layer, reverse = False):
3300 csp = [ [ [csp_[i][j][0][:],csp_[i][j][1][:],csp_[i][j][2][:]] for j in range(len(csp_[i])) ] for i in range(len(csp_)) ]
3301 for i in xrange(len(csp)):
3302 for j in xrange(len(csp[i])):
3303 for k in xrange(len(csp[i][j])):
3304 csp[i][j][k] = self.transform(csp[i][j][k],layer, reverse)
3305 return csp
3308 ################################################################################
3309 ### Errors handling function, notes are just printed into Logfile,
3310 ### warnings are printed into log file and warning message is displayed but
3311 ### extension continues working, errors causes log and execution is halted
3312 ### Notes, warnings adn errors could be assigned to space or comma or dot
3313 ### sepparated strings (case is ignoreg).
3314 ################################################################################
3315 def error(self, s, type_= "Warning"):
3316 notes = "Note "
3317 warnings = """
3318 Warning tools_warning
3319 bad_orientation_points_in_some_layers
3320 more_than_one_orientation_point_groups
3321 more_than_one_tool
3322 orientation_have_not_been_defined
3323 tool_have_not_been_defined
3324 selection_does_not_contain_paths
3325 selection_does_not_contain_paths_will_take_all
3326 selection_is_empty_will_comupe_drawing
3327 selection_contains_objects_that_are_not_paths
3328 """
3329 errors = """
3330 Error
3331 wrong_orientation_points
3332 area_tools_diameter_error
3333 no_tool_error
3334 active_layer_already_has_tool
3335 active_layer_already_has_orientation_points
3336 """
3337 if type_.lower() in re.split("[\s\n,\.]+", errors.lower()) :
3338 print_(s)
3339 inkex.errormsg(s+"\n")
3340 sys.exit()
3341 elif type_.lower() in re.split("[\s\n,\.]+", warnings.lower()) :
3342 print_(s)
3343 inkex.errormsg(s+"\n")
3344 elif type_.lower() in re.split("[\s\n,\.]+", notes.lower()) :
3345 print_(s)
3346 else :
3347 print_(s)
3348 inkex.errormsg(s)
3349 sys.exit()
3352 ################################################################################
3353 ### Get defs from svg
3354 ################################################################################
3355 def get_defs(self) :
3356 self.defs = {}
3357 def recursive(g) :
3358 for i in g:
3359 if i.tag == inkex.addNS("defs","svg") :
3360 for j in i:
3361 self.defs[j.get("id")] = i
3362 if i.tag ==inkex.addNS("g",'svg') :
3363 recursive(i)
3364 recursive(self.document.getroot())
3367 ################################################################################
3368 ###
3369 ### Get Gcodetools info from the svg
3370 ###
3371 ################################################################################
3372 def get_info(self):
3373 self.selected_paths = {}
3374 self.paths = {}
3375 self.tools = {}
3376 self.orientation_points = {}
3377 self.layers = [self.document.getroot()]
3378 self.Zcoordinates = {}
3379 self.transform_matrix = {}
3380 self.transform_matrix_reverse = {}
3381 self.Zauto_scale = {}
3383 def recursive_search(g, layer, selected=False):
3384 items = g.getchildren()
3385 items.reverse()
3386 for i in items:
3387 if selected:
3388 self.selected[i.get("id")] = i
3389 if i.tag == inkex.addNS("g",'svg') and i.get(inkex.addNS('groupmode','inkscape')) == 'layer':
3390 self.layers += [i]
3391 recursive_search(i,i)
3392 elif i.get('gcodetools') == "Gcodetools orientation group" :
3393 points = self.get_orientation_points(i)
3394 if points != None :
3395 self.orientation_points[layer] = self.orientation_points[layer]+[points[:]] if layer in self.orientation_points else [points[:]]
3396 print_("Found orientation points in '%s' layer: %s" % (layer.get(inkex.addNS('label','inkscape')), points))
3397 else :
3398 self.error(_("Warning! Found bad orientation points in '%s' layer. Resulting Gcode could be corrupt!") % layer.get(inkex.addNS('label','inkscape')), "bad_orientation_points_in_some_layers")
3399 elif i.get("gcodetools") == "Gcodetools tool defenition" :
3400 tool = self.get_tool(i)
3401 self.tools[layer] = self.tools[layer] + [tool.copy()] if layer in self.tools else [tool.copy()]
3402 print_("Found tool in '%s' layer: %s" % (layer.get(inkex.addNS('label','inkscape')), tool))
3403 elif i.tag == inkex.addNS('path','svg'):
3404 if "gcodetools" not in i.keys() :
3405 self.paths[layer] = self.paths[layer] + [i] if layer in self.paths else [i]
3406 if i.get("id") in self.selected :
3407 self.selected_paths[layer] = self.selected_paths[layer] + [i] if layer in self.selected_paths else [i]
3408 elif i.tag == inkex.addNS("g",'svg'):
3409 recursive_search(i,layer, (i.get("id") in self.selected) )
3410 elif i.get("id") in self.selected :
3411 # xgettext:no-pango-format
3412 self.error(_("This extension works with Paths and Dynamic Offsets and groups of them only! All other objects will be ignored!\nSolution 1: press Path->Object to path or Shift+Ctrl+C.\nSolution 2: Path->Dynamic offset or Ctrl+J.\nSolution 3: export all contours to PostScript level 2 (File->Save As->.ps) and File->Import this file."),"selection_contains_objects_that_are_not_paths")
3415 recursive_search(self.document.getroot(),self.document.getroot())
3418 def get_orientation_points(self,g):
3419 items = g.getchildren()
3420 items.reverse()
3421 p2, p3 = [], []
3422 p = None
3423 for i in items:
3424 if i.tag == inkex.addNS("g",'svg') and i.get("gcodetools") == "Gcodetools orientation point (2 points)":
3425 p2 += [i]
3426 if i.tag == inkex.addNS("g",'svg') and i.get("gcodetools") == "Gcodetools orientation point (3 points)":
3427 p3 += [i]
3428 if len(p2)==2 : p=p2
3429 elif len(p3)==3 : p=p3
3430 if p==None : return None
3431 points = []
3432 for i in p :
3433 point = [[],[]]
3434 for node in i :
3435 if node.get('gcodetools') == "Gcodetools orientation point arrow":
3436 point[0] = self.apply_transforms(node,cubicsuperpath.parsePath(node.get("d")))[0][0][1]
3437 if node.get('gcodetools') == "Gcodetools orientation point text":
3438 r = re.match(r'(?i)\s*\(\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*\)\s*',node.text)
3439 point[1] = [float(r.group(1)),float(r.group(2)),float(r.group(3))]
3440 if point[0]!=[] and point[1]!=[]: points += [point]
3441 if len(points)==len(p2)==2 or len(points)==len(p3)==3 : return points
3442 else : return None
3445 def get_tool(self, g):
3446 tool = self.default_tool.copy()
3447 tool["self_group"] = g
3448 for i in g:
3449 # Get parameters
3450 if i.get("gcodetools") == "Gcodetools tool background" :
3451 tool["style"] = simplestyle.parseStyle(i.get("style"))
3452 elif i.get("gcodetools") == "Gcodetools tool parameter" :
3453 key = None
3454 value = None
3455 for j in i:
3456 if j.get("gcodetools") == "Gcodetools tool defention field name":
3457 key = j.text
3458 if j.get("gcodetools") == "Gcodetools tool defention field value":
3459 for k in j :
3460 if k.tag == inkex.addNS('tspan','svg') and k.get("gcodetools") == "Gcodetools tool defention field value":
3461 if k.text!=None : value = value +"\n" + k.text if value != None else k.text
3462 if value == None or key == None: continue
3463 #print_("Found tool parameter '%s':'%s'" % (key,value))
3464 if key in self.default_tool.keys() :
3465 try :
3466 tool[key] = type(self.default_tool[key])(value)
3467 except :
3468 tool[key] = self.default_tool[key]
3469 self.error(_("Warning! Tool's and default tool's parameter's (%s) types are not the same ( type('%s') != type('%s') ).") % (key, value, self.default_tool[key]), "tools_warning")
3470 else :
3471 tool[key] = value
3472 self.error(_("Warning! Tool has parameter that default tool has not ( '%s': '%s' ).") % (key, value), "tools_warning" )
3473 return tool
3476 def set_tool(self,layer):
3477 # print_(("index(layer)=",self.layers.index(layer),"set_tool():layer=",layer,"self.tools=",self.tools))
3478 # for l in self.layers:
3479 # print_(("l=",l))
3480 for i in range(self.layers.index(layer),-1,-1):
3481 # print_(("processing layer",i))
3482 if self.layers[i] in self.tools :
3483 break
3484 if self.layers[i] in self.tools :
3485 if self.layers[i] != layer : self.tools[layer] = self.tools[self.layers[i]]
3486 if len(self.tools[layer])>1 : self.error(_("Layer '%s' contains more than one tool!") % self.layers[i].get(inkex.addNS('label','inkscape')), "more_than_one_tool")
3487 return self.tools[layer]
3488 else :
3489 self.error(_("Can not find tool for '%s' layer! Please add one with Tools library tab!") % layer.get(inkex.addNS('label','inkscape')), "no_tool_error")
3492 ################################################################################
3493 ###
3494 ### Path to Gcode
3495 ###
3496 ################################################################################
3497 def path_to_gcode(self) :
3499 def get_boundaries(points):
3500 minx,miny,maxx,maxy=None,None,None,None
3501 out=[[],[],[],[]]
3502 for p in points:
3503 if minx==p[0]:
3504 out[0]+=[p]
3505 if minx==None or p[0]<minx:
3506 minx=p[0]
3507 out[0]=[p]
3509 if miny==p[1]:
3510 out[1]+=[p]
3511 if miny==None or p[1]<miny:
3512 miny=p[1]
3513 out[1]=[p]
3515 if maxx==p[0]:
3516 out[2]+=[p]
3517 if maxx==None or p[0]>maxx:
3518 maxx=p[0]
3519 out[2]=[p]
3521 if maxy==p[1]:
3522 out[3]+=[p]
3523 if maxy==None or p[1]>maxy:
3524 maxy=p[1]
3525 out[3]=[p]
3526 return out
3529 def remove_duplicates(points):
3530 i=0
3531 out=[]
3532 for p in points:
3533 for j in xrange(i,len(points)):
3534 if p==points[j]: points[j]=[None,None]
3535 if p!=[None,None]: out+=[p]
3536 i+=1
3537 return(out)
3540 def get_way_len(points):
3541 l=0
3542 for i in xrange(1,len(points)):
3543 l+=math.sqrt((points[i][0]-points[i-1][0])**2 + (points[i][1]-points[i-1][1])**2)
3544 return l
3547 def sort_dxfpoints(points):
3548 points=remove_duplicates(points)
3549 # print_(get_boundaries(get_boundaries(points)[2])[1])
3550 ways=[
3551 # l=0, d=1, r=2, u=3
3552 [3,0], # ul
3553 [3,2], # ur
3554 [1,0], # dl
3555 [1,2], # dr
3556 [0,3], # lu
3557 [0,1], # ld
3558 [2,3], # ru
3559 [2,1], # rd
3560 ]
3561 # print_(("points=",points))
3562 minimal_way=[]
3563 minimal_len=None
3564 minimal_way_type=None
3565 for w in ways:
3566 tpoints=points[:]
3567 cw=[]
3568 # print_(("tpoints=",tpoints))
3569 for j in xrange(0,len(points)):
3570 p=get_boundaries(get_boundaries(tpoints)[w[0]])[w[1]]
3571 # print_(p)
3572 tpoints.remove(p[0])
3573 cw+=p
3574 curlen = get_way_len(cw)
3575 if minimal_len==None or curlen < minimal_len:
3576 minimal_len=curlen
3577 minimal_way=cw
3578 minimal_way_type=w
3580 return minimal_way
3583 def print_dxfpoints(points):
3584 gcode=""
3585 for point in points:
3586 gcode +="(drilling dxfpoint)\nG00 Z%f\nG00 X%f Y%f\nG01 Z%f F%f\nG04 P%f\nG00 Z%f\n" % (self.options.Zsafe,point[0],point[1],self.Zcoordinates[layer][1],self.tools[layer][0]["penetration feed"],0.2,self.options.Zsafe)
3587 # print_(("got dxfpoints array=",points))
3588 return gcode
3590 if self.selected_paths == {} and self.options.auto_select_paths:
3591 paths=self.paths
3592 self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
3593 else :
3594 paths = self.selected_paths
3595 self.check_dir()
3596 gcode = ""
3598 biarc_group = inkex.etree.SubElement( self.selected_paths.keys()[0] if len(self.selected_paths.keys())>0 else self.layers[0], inkex.addNS('g','svg') )
3599 print_(("self.layers=",self.layers))
3600 print_(("paths=",paths))
3601 for layer in self.layers :
3602 # print_(("processing layer",layer," of layers:",self.layers))
3603 if layer in paths :
3604 # print_(("layer ",layer, " is in paths:",paths))
3605 print_(("layer",layer))
3606 self.set_tool(layer)
3607 p = []
3608 dxfpoints = []
3609 for path in paths[layer] :
3610 if "d" not in path.keys() :
3611 self.error(_("Warning: One or more paths dont have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!"),"selection_contains_objects_that_are_not_paths")
3612 continue
3613 csp = cubicsuperpath.parsePath(path.get("d"))
3614 csp = self.apply_transforms(path, csp)
3615 if path.get("dxfpoint") == "1":
3616 tmp_curve=self.transform_csp(csp, layer)
3617 x=tmp_curve[0][0][0][0]
3618 y=tmp_curve[0][0][0][1]
3619 print_("got dxfpoint (scaled) at (%f,%f)" % (x,y))
3620 dxfpoints += [[x,y]]
3621 else:
3622 p += csp
3623 dxfpoints=sort_dxfpoints(dxfpoints)
3624 gcode+=print_dxfpoints(dxfpoints)
3625 curve = self.parse_curve(p, layer)
3626 self.draw_curve(curve, layer, biarc_group)
3627 if self.tools[layer][0]["depth step"] == 0 : self.tools[layer][0]["depth step"] = 1
3628 for step in range( 0, int(math.ceil( abs( (self.Zcoordinates[layer][1]-self.Zcoordinates[layer][0])/self.tools[layer][0]["depth step"] )) ) ):
3629 Zpos = max( self.Zcoordinates[layer][1], self.Zcoordinates[layer][0] - abs(self.tools[layer][0]["depth step"]*(step+1)) )
3630 gcode += self.generate_gcode(curve, layer, Zpos)
3632 self.export_gcode(gcode)
3634 ################################################################################
3635 ###
3636 ### dxfpoints
3637 ###
3638 ################################################################################
3639 def dxfpoints(self):
3640 if self.selected_paths == {}:
3641 self.error(_("Noting is selected. Please select something to convert to drill point (dxfpoint) or clear point sign."),"warning")
3642 for layer in self.layers :
3643 if layer in self.selected_paths :
3644 for path in self.selected_paths[layer]:
3645 # print_(("processing path",path.get('d')))
3646 if self.options.dxfpoints_action == 'replace':
3647 # print_("trying to set as dxfpoint")
3649 path.set("dxfpoint","1")
3650 r = re.match("^\s*.\s*(\S+)",path.get("d"))
3651 if r!=None:
3652 print_(("got path=",r.group(1)))
3653 path.set("d","m %s 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z" % r.group(1))
3654 path.set("style",styles["dxf_points"])
3656 if self.options.dxfpoints_action == 'save':
3657 path.set("dxfpoint","1")
3659 if self.options.dxfpoints_action == 'clear' and path.get("dxfpoint") == "1":
3660 path.set("dxfpoint","0")
3661 # for id, node in self.selected.iteritems():
3662 # print_((id,node,node.attrib))
3665 ################################################################################
3666 ###
3667 ### Artefacts
3668 ###
3669 ################################################################################
3670 def area_artefacts(self) :
3671 if self.selected_paths == {} and self.options.auto_select_paths:
3672 paths=self.paths
3673 self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
3674 else :
3675 paths = self.selected_paths
3676 for layer in paths :
3677 # paths[layer].reverse() # Reverse list of paths to leave their order
3678 for path in paths[layer] :
3679 parent = path.getparent()
3680 style = path.get("style") if "style" in path.keys() else ""
3681 if "d" not in path.keys() :
3682 self.error(_("Warning: One or more paths dont have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!"),"selection_contains_objects_that_are_not_paths")
3683 continue
3684 csp = cubicsuperpath.parsePath(path.get("d"))
3685 csp = self.apply_transforms(path, csp)
3686 for subpath in csp :
3687 bounds = csp_simple_bound([subpath])
3688 if (bounds[2]-bounds[0])**2+(bounds[3]-bounds[1])**2 < self.options.area_find_artefacts_diameter**2:
3689 if self.options.area_find_artefacts_action == "mark with an arrow" :
3690 inkex.etree.SubElement(parent, inkex.addNS('path','svg'),
3691 {
3692 'd': 'm %s,%s 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z' % (subpath[0][1][0],subpath[0][1][1]),
3693 'style': styles["area artefact arrow"],
3694 'gcodetools': 'area artefact arrow',
3695 })
3696 inkex.etree.SubElement(parent, inkex.addNS('path','svg'), {'d': cubicsuperpath.formatPath([subpath]), 'style': style, "gcodetools_parameter":"area artefact"})
3697 elif self.options.area_find_artefacts_action == "mark with style" :
3698 inkex.etree.SubElement(parent, inkex.addNS('path','svg'), {'d': cubicsuperpath.formatPath([subpath]), 'style': styles["area artefact"]})
3699 elif self.options.area_find_artefacts_action == "delete" :
3700 print_("Deleted artifact %s" % subpath )
3701 else :
3702 inkex.etree.SubElement(parent, inkex.addNS('path','svg'), {'d': cubicsuperpath.formatPath([subpath]), 'style': style})
3703 parent.remove(path)
3704 return
3707 ################################################################################
3708 ###
3709 ### Calculate area curves
3710 ###
3711 ################################################################################
3712 def area(self) :
3713 if len(self.selected_paths)<=0:
3714 self.error(_("This extension requires at least one selected path."),"warning")
3715 return
3716 for layer in self.layers :
3717 if layer in self.selected_paths :
3718 self.set_tool(layer)
3719 if self.tools[layer][0]['diameter']<=0 :
3720 self.error(_("Tool diameter must be > 0 but tool's diameter on '%s' layer is not!") % layer.get(inkex.addNS('label','inkscape')),"area_tools_diameter_error")
3722 for path in self.selected_paths[layer]:
3723 print_(("doing path", path.get("style"), path.get("d")))
3725 area_group = inkex.etree.SubElement( path.getparent(), inkex.addNS('g','svg') )
3727 d = path.get('d')
3728 print_(d)
3729 if d==None:
3730 print_("omitting non-path")
3731 self.error(_("Warning: omitting non-path"),"selection_contains_objects_that_are_not_paths")
3732 continue
3733 csp = cubicsuperpath.parsePath(d)
3735 if path.get(inkex.addNS('type','sodipodi'))!="inkscape:offset":
3736 print_("Path %s is not an offset. Preparation started." % path.get("id"))
3737 # Path is not offset. Preparation will be needed.
3738 # Finding top most point in path (min y value)
3740 min_x,min_y,min_i,min_j,min_t = csp_true_bounds(csp)[1]
3742 # Reverse path if needed.
3743 if min_y!=float("-inf") :
3744 # Move outline subpath to the begining of csp
3745 subp = csp[min_i]
3746 del csp[min_i]
3747 j = min_j
3748 # Split by the topmost point and join again
3749 if min_t in [0,1]:
3750 if min_t == 0: j=j-1
3751 subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1]
3752 subp = [ [subp[j][1], subp[j][1], subp[j][2]] ] + subp[j+1:] + subp[:j] + [ [subp[j][0], subp[j][1], subp[j][1]] ]
3753 else:
3754 sp1,sp2,sp3 = csp_split(subp[j-1],subp[j],min_t)
3755 subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1]
3756 subp = [ [ sp2[1], sp2[1],sp2[2] ] ] + [sp3] + subp[j+1:] + subp[:j-1] + [sp1] + [[ sp2[0], sp2[1],sp2[1] ]]
3757 csp = [subp] + csp
3758 # reverse path if needed
3759 if csp_subpath_ccw(csp[0]) :
3760 for i in range(len(csp)):
3761 n = []
3762 for j in csp[i]:
3763 n = [ [j[2][:],j[1][:],j[0][:]] ] + n
3764 csp[i] = n[:]
3767 d = cubicsuperpath.formatPath(csp)
3768 print_(("original d=",d))
3769 d = re.sub(r'(?i)(m[^mz]+)',r'\1 Z ',d)
3770 d = re.sub(r'(?i)\s*z\s*z\s*',r' Z ',d)
3771 d = re.sub(r'(?i)\s*([A-Za-z])\s*',r' \1 ',d)
3772 print_(("formatted d=",d))
3773 # scale = sqrt(Xscale**2 + Yscale**2) / sqrt(1**2 + 1**2)
3774 p0 = self.transform([0,0],layer)
3775 p1 = self.transform([0,1],layer)
3776 scale = (P(p0)-P(p1)).mag()
3777 if scale == 0 : scale = 1.
3778 else : scale = 1./scale
3779 print_(scale)
3780 tool_d = self.tools[layer][0]['diameter']*scale
3781 r = self.options.area_inkscape_radius * scale
3782 sign=1 if r>0 else -1
3783 print_("Tool diameter = %s, r = %s" % (tool_d, r))
3785 for i in range(self.options.max_area_curves):
3786 radius = - tool_d * (i+0.5) * sign
3787 if abs(radius)>abs(r):
3788 radius = -r
3790 inkex.etree.SubElement( area_group, inkex.addNS('path','svg'),
3791 {
3792 inkex.addNS('type','sodipodi'): 'inkscape:offset',
3793 inkex.addNS('radius','inkscape'): str(radius),
3794 inkex.addNS('original','inkscape'): d,
3795 'style': styles["biarc_style_i"]['area']
3796 })
3797 print_(("adding curve",area_group,d,styles["biarc_style_i"]['area']))
3798 if radius == -r : break
3801 ################################################################################
3802 ###
3803 ### Engraving
3804 ###
3805 ################################################################################
3806 def engraving(self) :
3807 if len(self.selected_paths)<=0:
3808 self.error(_("This extension requires at least one selected path."),"warning")
3809 return
3810 if not self.check_dir() : return
3811 gcode = ''
3813 def find_cutter_center((x1,y1),(nx1,ny1), sp1,sp2, tool, t3 = .5):
3814 ####################################################################
3815 ### To find center of cutter a system of non linear equations
3816 ### will be solved using Newton's method
3817 ####################################################################
3818 bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
3819 ax,ay,bx,by,cx,cy,dx,dy=bezmisc.bezierparameterize(bez)
3820 fx=ax*(t3*t3*t3)+bx*(t3*t3)+cx*t3+dx
3821 fy=ay*(t3*t3*t3)+by*(t3*t3)+cy*t3+dy
3823 nx2,ny2 = csp_normalized_normal(sp1,sp2,t3)
3824 intersection, t1, t2 = straight_segments_intersection([[x1,y1],[x1+nx1,y1+ny1]],[[fx,fy],[fx+nx2,fy+ny2]], False)
3825 if not intersection or intersection == "Overlap" :
3826 if nx1!=0 :
3827 t1 = t2 = (x1-fx)/nx1
3828 else :
3829 t1 = t2 = (y1-fy)/ny1
3831 t = [ t1, t2, t3 ]
3832 i = 0
3833 F = [0.,0.,0.]
3834 F1 = [[0.,0.,0.],[0.,0.,0.],[0.,0.,0.]]
3835 while i==0 or abs(F[0])+abs(F[1])+math.sqrt(abs(F[2])) >engraving_tolerance and i<10:
3836 t1,t2,t3 = t[0],t[1],t[2]
3837 fx=ax*(t3*t3*t3)+bx*(t3*t3)+cx*t3+dx
3838 fy=ay*(t3*t3*t3)+by*(t3*t3)+cy*t3+dy
3839 f1x=3*ax*(t3*t3)+2*bx*t3+cx
3840 f1y=3*ay*(t3*t3)+2*by*t3+cy
3841 i+=1
3843 tx = fx-x1-nx1*t1
3844 ty = fy-y1-ny1*t1
3846 F[0] = x1+nx1*t1-fx+t2*f1y
3847 F[1] = y1+ny1*t1-fy-t2*f1x
3848 F[2] = t1*t1 - tx*tx -ty*ty
3850 F1[0][0] = nx1
3851 F1[0][1] = f1y
3852 F1[0][2] = -f1x+t2*(6*ay*t3+2*by)
3854 F1[1][0] = ny1
3855 F1[1][1] = -f1x
3856 F1[1][2] = -f1y-t2*(6*ax*t3+2*bx)
3858 F1[2][0] = 2*t1+2*nx1*tx +2*ny1*ty
3859 F1[2][1] = 0
3860 F1[2][2] = -2*f1x*tx -2*f1y*ty
3862 F1 = inv_3x3(F1)
3864 if ( isnan(F[0]) or isnan(F[1]) or isnan(F[2]) or
3865 isinf(F[0]) or isinf(F[1]) or isinf(F[2]) ):
3866 return t+[1e100,i]
3868 if F1!= None :
3869 t[0] -= F1[0][0]*F[0] + F1[0][1]*F[1] + F1[0][2]*F[2]
3870 t[1] -= F1[1][0]*F[0] + F1[1][1]*F[1] + F1[1][2]*F[2]
3871 t[2] -= F1[2][0]*F[0] + F1[2][1]*F[1] + F1[2][2]*F[2]
3872 else: break
3874 return t+[abs(F[0])+abs(F[1])+math.sqrt(abs(F[2])),i]
3875 cspe =[]
3876 we = []
3877 for layer in self.layers :
3878 if layer in self.selected_paths :
3879 self.set_tool(layer)
3880 engraving_group = inkex.etree.SubElement( self.selected_paths[layer][0].getparent(), inkex.addNS('g','svg') )
3881 for node in self.selected_paths[layer] :
3882 if node.tag == inkex.addNS('path','svg'):
3883 cspi = cubicsuperpath.parsePath(node.get('d'))
3885 for j in xrange(len(cspi)):
3886 # Remove zerro length segments
3887 i = 1
3888 while i<len(cspi[j]):
3889 if abs(cspi[j][i-1][1][0]-cspi[j][i][1][0])<engraving_tolerance and abs(cspi[j][i-1][1][1]-cspi[j][i][1][1])<engraving_tolerance:
3890 cspi[j][i-1][2] = cspi[j][i][2]
3891 del cspi[j][i]
3892 else:
3893 i += 1
3894 for csp in cspi:
3895 # Create list containing normlas and points
3896 nl = []
3897 for i in range(1,len(csp)):
3898 n, n1 = [], []
3899 sp1, sp2 = csp[i-1], csp[i]
3900 for ti in [.0,.25,.75,1.]:
3901 # Is following string is nedded or not??? (It makes t depend on form of the curve)
3902 #ti = bezmisc.beziertatlength(bez,ti)
3903 x1,y1 = csp_at_t(sp1,sp2,ti)
3904 nx,ny = csp_normalized_normal(sp1,sp2,ti)
3905 n+=[ [ [x1,y1], [nx,ny], False, False, i] ] # [point coordinates, normal, is an inner corner, is an outer corner, csp's index]
3906 if ti==1 and i<len(csp)-1:
3907 nx2, ny2 = csp_normalized_slope(csp[i],csp[i+1],0)
3908 nx2,ny2 = -ny2,nx2
3909 ang = ny2*nx-ny*nx2
3910 ang1 = 180-math.acos(max(-1,min(1,nx*nx2+ny*ny2)))*180/math.pi
3911 if ang > 0 and ang1 < self.options.engraving_sharp_angle_tollerance : # inner angle
3912 n[-1][2] = True
3913 elif ang < 0 and ang1 < self.options.engraving_sharp_angle_tollerance : # outer angle
3914 a = -math.acos(nx*nx2+ny*ny2)
3915 for t in [.0,.25,.75,1.]:
3916 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 ] ]
3917 nl += [ n ] + ([ n1 ] if n1!=[] else [])
3918 # Modify first/last points if curve is closed
3919 if abs(csp[-1][1][0]-csp[0][1][0])<engraving_tolerance and abs(csp[-1][1][1]-csp[0][1][1])<engraving_tolerance :
3920 x1,y1 = csp_at_t(csp[-2],csp[-1],1)
3921 nx,ny = csp_normalized_slope(csp[-2],csp[-1],1)
3922 nx,ny = -ny,nx
3923 nx2,ny2 = csp_normalized_slope(csp[0],csp[1],0)
3924 nx2,ny2 = -ny2,nx2
3925 ang = ny2*nx-ny*nx2
3926 if ang > 0 and 180-math.acos(nx*nx2+ny*ny2)*180/math.pi < self.options.engraving_sharp_angle_tollerance : # inner angle
3927 nl[-1][-1][2] = True
3928 elif ang < 0 and 180-math.acos(nx*nx2+ny*ny2)*180/math.pi < self.options.engraving_sharp_angle_tollerance : # outer angle
3929 a = -math.acos(nx*nx2+ny*ny2)
3930 n1 = []
3931 for t in [.0,.25,.75,1.]:
3932 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 ] ]
3933 nl += [ n1 ]
3936 print_(("engraving_draw_calculation_paths=",self.options.engraving_draw_calculation_paths))
3937 if self.options.engraving_draw_calculation_paths==True:
3938 for i in nl:
3939 for p in i:
3940 inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
3941 {
3942 "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),
3943 'style': "stroke:#0000ff; stroke-opacity:0.46; stroke-width:0.1; fill:none",
3944 "gcodetools": "Engraving calculation paths"
3945 })
3948 # Calculate offset points
3949 csp_points = []
3950 for ki in xrange(len(nl)):
3951 p = []
3952 for ti in xrange(3) if ki!=len(nl)-1 else xrange(4):
3953 n = nl[ki][ti]
3954 x1,y1 = n[0]
3955 nx,ny = n[1]
3956 d, r = 0, float("inf")
3957 if ti==0 and nl[ki-1][-1][2] == True or ti==3 and nl[ki][ti][2] == True:
3958 # Point is a sharp angle r=0p
3959 r = 0
3960 else :
3961 for j in xrange(0,len(cspi)):
3962 for i in xrange(1,len(cspi[j])):
3963 d = csp_bound_to_point_distance(cspi[j][i-1], cspi[j][i], [x1,y1])
3964 if d >= self.options.engraving_max_dist*2 :
3965 r = min(math.sqrt(d/2),r)
3966 continue
3967 for n1 in xrange(self.options.engraving_newton_iterations):
3968 t = find_cutter_center((x1,y1),(nx,ny), cspi[j][i-1], cspi[j][i], self.tools[layer][0], float(n1)/(self.options.engraving_newton_iterations-1))
3969 print_(t)
3970 if t[0] > engraving_tolerance and 0<=t[2]<=1 and abs(t[3])<engraving_tolerance:
3971 print_("!@#!@#!@#!@#!@",t)
3972 t3 = t[2]
3973 ax,ay,bx,by,cx,cy,dx,dy=bezmisc.bezierparameterize((cspi[j][i-1][1],cspi[j][i-1][2],cspi[j][i][0],cspi[j][i][1]))
3974 x2=ax*(t3*t3*t3)+bx*(t3*t3)+cx*t3+dx
3975 y2=ay*(t3*t3*t3)+by*(t3*t3)+cy*t3+dy
3976 if abs(x2-x1)<engraving_tolerance and abs(y2-y1)<engraving_tolerance:
3977 f1x = 3*ax*(t3*t3)+2*bx*t3+cx
3978 f1y = 3*ay*(t3*t3)+2*by*t3+cy
3979 f2x = 6*ax*t3+2*bx
3980 f2y = 6*ay*t3+2*by
3981 d = f1x*f2y-f1y*f2x
3982 # d = curvature
3983 if d!=0 :
3984 d = math.sqrt((f1x*f1x+f1y*f1y)**3)/d
3985 if d>0:
3986 r = min( d,r) if r!=None else d
3987 else :
3988 r = min(r,self.options.engraving_max_dist) if r!=None else self.options.engraving_max_dist
3989 else:
3990 r = min(t[0],r) if r!=None else t[0]
3991 for j in xrange(0,len(cspi)):
3992 for i in xrange(0,len(cspi[j])):
3993 x2,y2 = cspi[j][i][1]
3994 if (abs(x1-x2)>engraving_tolerance or abs(y1-y2)>engraving_tolerance ) and (x2*nx - x1*nx + y2*ny - y1*ny) != 0:
3995 t1 = .5 * ( (x1-x2)**2+(y1-y2)**2 ) / (x2*nx - x1*nx + y2*ny - y1*ny)
3996 if t1>0 : r = min(t1,r) if r!=None else t1
3997 if self.options.engraving_draw_calculation_paths==True:
3998 inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
3999 {"gcodetools": "Engraving calculation paths", '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'})
4000 inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
4001 {"gcodetools": "Engraving calculation paths", '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'})
4002 r = min(r, self.options.engraving_max_dist)
4003 w = min(r, self.tools[layer][0]['diameter'])
4004 p += [ [x1+nx*w,y1+ny*w,r,w] ]
4008 if len(csp_points)>0 : csp_points[-1] += [p[0]]
4009 csp_points += [ p ]
4010 # Splitting path to pieces each of them not further from path more than engraving_max_dist
4011 engraving_path = [ [] ]
4012 for p_ in csp_points :
4013 for p in p_:
4014 if p[2]<self.options.engraving_max_dist : break
4015 if p[2]<self.options.engraving_max_dist: engraving_path[-1] += [p_]
4016 else :
4017 if engraving_path[-1] != [] : engraving_path += [ [] ]
4018 if engraving_path[-1] == [] : del engraving_path[-1]
4021 for csp_points in engraving_path :
4022 # Create Path that goes through this points
4023 cspm = []
4024 w = []
4025 m = [[0.0, 0.0, 0.0, 1.0], [0.015625, 0.140625, 0.421875, 0.421875], [0.421875, 0.421875, 0.140625, 0.015625], [1.0, 0.0, 0.0, 0.0]]
4026 for p in csp_points:
4027 m = numpy.array(m)
4028 xi = numpy.array( [p[i][:2] for i in range(4)])
4029 sp1,sp2 = [[0.,0.],[0.,0.],[0.,0.]], [[0.,0.],[0.,0.],[0.,0.]]
4030 a,b,c,d = numpy.linalg.solve(m, xi).tolist()
4031 sp1[1], sp1[0] = d, d
4032 sp1[2] = c
4033 sp2[0] = b
4034 sp2[1], sp2[2] = a, a
4035 sp3,sp4,sp5 = csp_split(sp1, sp2, .25)
4036 l = cspseglength(sp3,sp4)
4037 sp1,sp2,sp4 = csp_split(sp1, sp2, .75)
4038 l1 = cspseglength(sp1,sp2)
4039 if l1!=0:
4040 sp1,sp2,sp3 = csp_splitatlength(sp1, sp2, l/l1)
4041 if len(cspm)>0 :
4042 cspm[-1][2] = sp1[2]
4043 cspm += [sp2[:], sp3[:], sp4[:]]
4044 w += [p[i][3] for i in range(1,4)]
4045 else :
4046 cspm += [sp1[:], sp2[:], sp3[:], sp4[:]]
4047 w += [p[i][3] for i in range(4)]
4048 if self.options.engraving_draw_calculation_paths==True:
4049 node = inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'), {
4050 "d": cubicsuperpath.formatPath([cspm]),
4051 'style': styles["biarc_style_i"]['biarc1'],
4052 "gcodetools": "Engraving calculation paths",
4053 })
4054 for i in xrange(len(cspm)):
4055 inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
4056 {"gcodetools": "Engraving calculation paths", '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'})
4057 cspe += [cspm]
4058 we += [w]
4060 if self.tools[layer][0]['shape'] != "":
4061 f = eval('lambda w: ' + self.tools[layer][0]['shape'].strip('"'))
4062 else:
4063 self.error(_("Tool '%s' has no shape!") % self.tools[layer][0]['name'],"engraving_tools_shape_error")
4064 f = lambda w: w
4066 if cspe!=[]:
4067 curve = self.parse_curve(cspe, layer, we, f)
4068 self.draw_curve(curve, layer, engraving_group)
4069 gcode += self.generate_gcode(curve, layer, self.options.Zsurface)
4071 if gcode!='' :
4072 self.export_gcode(gcode)
4073 else : self.error(_("No need to engrave sharp angles."),"warning")
4076 ################################################################################
4077 ###
4078 ### Orientation
4079 ###
4080 ################################################################################
4081 def orientation(self, layer=None) :
4082 print_("entering orientations")
4083 if layer == None :
4084 layer = self.current_layer if self.current_layer is not None else self.document.getroot()
4085 if layer in self.orientation_points:
4086 self.error(_("Active layer already has orientation points! Remove them or select another layer!"),"active_layer_already_has_orientation_points")
4088 orientation_group = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), {"gcodetools":"Gcodetools orientation group"})
4089 doc_height = inkex.unittouu(self.document.getroot().get('height'))
4090 if self.document.getroot().get('height') == "100%" :
4091 doc_height = 1052.3622047
4092 print_("Overruding height from 100 percents to %s" % doc_height)
4093 if self.options.unit == "G21 (All units in mm)" :
4094 points = [[0.,0.,self.options.Zsurface],[100.,0.,self.options.Zdepth],[0.,100.,0.]]
4095 orientation_scale = 3.5433070660
4096 print_("orientation_scale < 0 ===> switching to mm units=%0.10f"%orientation_scale )
4097 elif self.options.unit == "G20 (All units in inches)" :
4098 points = [[0.,0.,self.options.Zsurface],[5.,0.,self.options.Zdepth],[0.,5.,0.]]
4099 orientation_scale = 90
4100 print_("orientation_scale < 0 ===> switching to inches units=%0.10f"%orientation_scale )
4101 if self.options.orientation_points_count == 2 :
4102 points = points[:2]
4103 print_(("using orientation scale",orientation_scale,"i=",points))
4104 for i in points :
4105 si = [i[0]*orientation_scale, i[1]*orientation_scale]
4106 g = inkex.etree.SubElement(orientation_group, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools orientation point (%s points)" % self.options.orientation_points_count})
4107 inkex.etree.SubElement( g, inkex.addNS('path','svg'),
4108 {
4109 'style': "stroke:none;fill:#000000;",
4110 'd':'m %s,%s 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z z' % (si[0], -si[1]+doc_height),
4111 'gcodetools': "Gcodetools orientation point arrow"
4112 })
4113 t = inkex.etree.SubElement( g, inkex.addNS('text','svg'),
4114 {
4115 'style': "font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;",
4116 inkex.addNS("space","xml"):"preserve",
4117 'x': str(si[0]+10),
4118 'y': str(-si[1]-10+doc_height),
4119 'gcodetools': "Gcodetools orientation point text"
4120 })
4121 t.text = "(%s; %s; %s)" % (i[0],i[1],i[2])
4124 ################################################################################
4125 ###
4126 ### Tools library
4127 ###
4128 ################################################################################
4129 def tools_library(self, layer=None) :
4130 # Add a tool to the drawing
4131 if layer == None :
4132 layer = self.current_layer if self.current_layer is not None else self.document.getroot()
4133 if layer in self.tools:
4134 self.error(_("Active layer already has a tool! Remove it or select another layer!"),"active_layer_already_has_tool")
4136 if self.options.tools_library_type == "cylinder cutter" :
4137 tool = {
4138 "name": "Cylindrical cutter",
4139 "id": "Cylindrical cutter 0001",
4140 "diameter":10,
4141 "penetration angle":90,
4142 "feed":"400",
4143 "penetration feed":"100",
4144 "depth step":"1",
4145 "tool change gcode":" "
4146 }
4147 elif self.options.tools_library_type == "lathe cutter" :
4148 tool = {
4149 "name": "Lathe cutter",
4150 "id": "Lathe cutter 0001",
4151 "diameter":10,
4152 "penetration angle":90,
4153 "feed":"400",
4154 "passing feed":"800",
4155 "fine feed":"100",
4156 "penetration feed":"100",
4157 "depth step":"1",
4158 "tool change gcode":" "
4159 }
4160 elif self.options.tools_library_type == "cone cutter":
4161 tool = {
4162 "name": "Cone cutter",
4163 "id": "Cone cutter 0001",
4164 "diameter":10,
4165 "shape":"w",
4166 "feed":"400",
4167 "penetration feed":"100",
4168 "depth step":"1",
4169 "tool change gcode":" "
4170 }
4171 elif self.options.tools_library_type == "tangent knife":
4172 tool = {
4173 "name": "Tangent knife",
4174 "id": "Tangent knife 0001",
4175 "feed":"400",
4176 "penetration feed":"100",
4177 "depth step":"100",
4178 "4th axis meaning": "tangent knife",
4179 "4th axis scale": 1.,
4180 "4th axis offset": 0,
4181 "tool change gcode":" "
4182 }
4184 elif self.options.tools_library_type == "plasma cutter":
4185 tool = {
4186 "name": "Plasma cutter",
4187 "id": "Plasma cutter 0001",
4188 "diameter":10,
4189 "penetration feed":100,
4190 "feed":400,
4191 "gcode before path":"""G31 Z-100 F500 (find metal)
4192 G92 Z0 (zerro z)
4193 G00 Z10 F500 (going up)
4194 M03 (turn on plasma)
4195 G04 P0.2 (pause)
4196 G01 Z1 (going to cutting z)\n""",
4197 "gcode after path":"M05 (turn off plasma)\n",
4198 }
4199 else :
4200 tool = self.default_tool
4202 tool_num = sum([len(self.tools[i]) for i in self.tools])
4203 colors = ["00ff00","0000ff","ff0000","fefe00","00fefe", "fe00fe", "fe7e00", "7efe00", "00fe7e", "007efe", "7e00fe", "fe007e"]
4205 tools_group = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools tool defenition"})
4206 bg = inkex.etree.SubElement( tools_group, inkex.addNS('path','svg'),
4207 {'style': "fill:#%s;fill-opacity:0.5;stroke:#444444; stroke-width:1px;"%colors[tool_num%len(colors)], "gcodetools":"Gcodetools tool background"})
4209 y = 0
4210 keys = []
4211 for key in self.tools_field_order:
4212 if key in tool: keys += [key]
4213 for key in tool:
4214 if key not in keys: keys += [key]
4215 for key in keys :
4216 g = inkex.etree.SubElement(tools_group, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools tool parameter"})
4218 t = inkex.etree.SubElement( g, inkex.addNS('text','svg'),
4219 {
4220 'style': ("font-size:10px;" if key!="name" else "font-size:20px;") + "font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;",
4221 inkex.addNS("space","xml"):"preserve",
4222 'x': str(0),
4223 'y': str(y),
4224 'gcodetools': "Gcodetools tool defention field name"
4225 })
4226 t.text = str(key)
4227 v = str(tool[key]).split("\n")
4228 t = inkex.etree.SubElement( g, inkex.addNS('text','svg'),
4229 {
4230 'style': ("font-size:10px;" if key!="name" else "font-size:20px;") + "font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;",
4231 'x': str(150),
4232 inkex.addNS("space","xml"):"preserve",
4233 'y': str(y),
4234 'gcodetools': "Gcodetools tool defention field value"
4235 })
4236 for s in v :
4237 span = inkex.etree.SubElement( t, inkex.addNS('tspan','svg'),
4238 {
4239 'x': str(150),
4240 'y': str(+y),
4241 inkex.addNS("role","sodipodi"):"line",
4242 'gcodetools': "Gcodetools tool defention field value"
4243 })
4244 y += 15 if key!='name' else 20
4245 span.text = s
4246 bg.set('d',"m -20,-20 l 400,0 0,%f -400,0 z " % (y+50))
4247 tool = []
4248 tools_group.set("transform", simpletransform.formatTransform([ [1,0,self.view_center[0]-150 ], [0,1,self.view_center[1]] ] ))
4251 ################################################################################
4252 ###
4253 ### Check tools and OP asignment
4254 ###
4255 ################################################################################
4256 def check_tools_and_op(self):
4257 if len(self.selected)<=0 :
4258 self.error(_("Selection is empty! Will compute whole drawing."),"selection_is_empty_will_comupe_drawing")
4259 paths = self.paths
4260 else :
4261 paths = self.selected_paths
4262 # Set group
4263 group = inkex.etree.SubElement( self.selected_paths.keys()[0] if len(self.selected_paths.keys())>0 else self.layers[0], inkex.addNS('g','svg') )
4264 trans_ = [[1,0.3,0],[0,0.5,0]]
4265 self.get_defs()
4266 # Add marker to defs if it doesnot exists
4267 if "CheckToolsAndOPMarker" not in self.defs :
4268 defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
4269 marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"CheckToolsAndOPMarker","orient":"auto","refX":"-8","refY":"-2.41063","style":"overflow:visible"})
4270 inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
4271 { "d":"m -6.55552,-2.41063 0,0 L -13.11104,0 c 1.0473,-1.42323 1.04126,-3.37047 0,-4.82126",
4272 "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" }
4273 )
4274 bounds = [float('inf'),float('inf'),float('-inf'),float('-inf')]
4275 tools_bounds = {}
4276 for layer in self.layers :
4277 if layer in paths :
4278 self.set_tool(layer)
4279 tool = self.tools[layer][0]
4280 tools_bounds[layer] = tools_bounds[layer] if layer in tools_bounds else [float("inf"),float("-inf")]
4281 style = simplestyle.formatStyle(tool["style"])
4282 for path in paths[layer] :
4283 style = "fill:%s; fill-opacity:%s; stroke:#000044; stroke-width:1; marker-mid:url(#CheckToolsAndOPMarker);" % (
4284 tool["style"]["fill"] if "fill" in tool["style"] else "#00ff00",
4285 tool["style"]["fill-opacity"] if "fill-opacity" in tool["style"] else "0.5")
4286 group.insert( 0, inkex.etree.Element(path.tag, path.attrib))
4287 new = group.getchildren()[0]
4288 new.set("style", style)
4290 trans = self.get_transforms(path)
4291 trans = simpletransform.composeTransform( trans_, trans if trans != [] else [[1.,0.,0.],[0.,1.,0.]])
4292 csp = cubicsuperpath.parsePath(path.get("d"))
4293 simpletransform.applyTransformToPath(trans,csp)
4294 path_bounds = csp_simple_bound(csp)
4295 trans = simpletransform.formatTransform(trans)
4296 bounds = [min(bounds[0],path_bounds[0]), min(bounds[1],path_bounds[1]), max(bounds[2],path_bounds[2]), max(bounds[3],path_bounds[3])]
4297 tools_bounds[layer] = [min(tools_bounds[layer][0], path_bounds[1]), max(tools_bounds[layer][1], path_bounds[3])]
4299 new.set("transform", trans)
4300 trans_[1][2] += 20
4301 trans_[1][2] += 100
4303 for layer in self.layers :
4304 if layer in self.tools :
4305 if layer in tools_bounds :
4306 tool = self.tools[layer][0]
4307 g = copy.deepcopy(tool["self_group"])
4308 g.attrib["gcodetools"] = "Check tools and OP asignment"
4309 trans = [[1,0.3,bounds[2]],[0,0.5,tools_bounds[layer][0]]]
4310 g.set("transform",simpletransform.formatTransform(trans))
4311 group.insert( 0, g )
4314 ################################################################################
4315 ### TODO Launch browser on help tab
4316 ################################################################################
4317 def help(self):
4318 self.error(_("""Tutorials, manuals and support can be found at\nEnglish support forum:\n http://www.cnc-club.ru/gcodetools\nand Russian support forum:\n http://www.cnc-club.ru/gcodetoolsru"""),"warning")
4319 return
4322 ################################################################################
4323 ### Lathe
4324 ################################################################################
4325 def generate_lathe_gcode(self, subpath, layer, feed_type) :
4326 if len(subpath) <2 : return ""
4327 feed = " F %f" % self.tool[feed_type]
4328 x,z = self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap
4329 flip_angle = -1 if x.lower()+z.lower() in ["xz", "yx", "zy"] else 1
4330 alias = {"X":"I", "Y":"J", "Z":"K", "x":"i", "y":"j", "z":"k"}
4331 i_, k_ = alias[x], alias[z]
4332 c = [ [subpath[0][1], "move", 0, 0, 0] ]
4333 #csp_draw(self.transform_csp([subpath],layer,True), color = "Orange", width = .1)
4334 for sp1,sp2 in zip(subpath,subpath[1:]) :
4335 c += biarc(sp1,sp2,0,0)
4336 for i in range(1,len(c)) : # Just in case check end point of each segment
4337 c[i-1][4] = c[i][0][:]
4338 c += [ [subpath[-1][1], "end", 0, 0, 0] ]
4339 self.draw_curve(c, layer, style = styles["biarc_style_lathe_%s" % feed_type])
4341 gcode = ("G01 %s %f %s %f" % (x, c[0][4][0], z, c[0][4][1]) ) + feed + "\n" # Just in case move to the start...
4342 for s in c :
4343 if s[1] == 'line':
4344 gcode += ("G01 %s %f %s %f" % (x, s[4][0], z, s[4][1]) ) + feed + "\n"
4345 elif s[1] == 'arc':
4346 r = [(s[2][0]-s[0][0]), (s[2][1]-s[0][1])]
4347 if (r[0]**2 + r[1]**2)>self.options.min_arc_radius:
4348 r1, r2 = (P(s[0])-P(s[2])), (P(s[4])-P(s[2]))
4349 if abs(r1.mag()-r2.mag()) < 0.001 :
4350 gcode += ("G02" if s[3]*flip_angle<0 else "G03") + (" %s %f %s %f %s %f %s %f" % (x,s[4][0],z,s[4][1],i_,(s[2][0]-s[0][0]), k_, (s[2][1]-s[0][1]) ) ) + feed + "\n"
4351 else:
4352 r = (r1.mag()+r2.mag())/2
4353 gcode += ("G02" if s[3]*flip_angle<0 else "G03") + (" %s %f %s %f" % (x,s[4][0],z,y[4][1]) ) + " R%f"%r + feed + "\n"
4354 return gcode
4357 def lathe(self):
4358 if not self.check_dir() : return
4359 x,z = self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap
4360 x = re.sub("^\s*([XYZxyz])\s*$",r"\1",x)
4361 z = re.sub("^\s*([XYZxyz])\s*$",r"\1",z)
4362 if x not in ["X", "Y", "Z", "x", "y", "z"] or z not in ["X", "Y", "Z", "x", "y", "z"] :
4363 self.error(_("Lathe X and Z axis remap should be 'X', 'Y' or 'Z'. Exiting..."),"warning")
4364 return
4365 if x.lower() == z.lower() :
4366 self.error(_("Lathe X and Z axis remap should be the same. Exiting..."),"warning")
4367 return
4368 if x.lower()+z.lower() in ["xy","yx"] : gcode_plane_selection = "G17 (Using XY plane)\n"
4369 if x.lower()+z.lower() in ["xz","zx"] : gcode_plane_selection = "G18 (Using XZ plane)\n"
4370 if x.lower()+z.lower() in ["zy","yz"] : gcode_plane_selection = "G19 (Using YZ plane)\n"
4371 self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap = x, z
4373 paths = self.selected_paths
4374 self.tool = []
4375 gcode = ""
4376 for layer in self.layers :
4377 if layer in paths :
4378 self.set_tool(layer)
4379 if self.tool != self.tools[layer][0] :
4380 self.tool = self.tools[layer][0]
4381 self.tool["passing feed"] = float(self.tool["passing feed"] if "passing feed" in self.tool else self.tool["feed"])
4382 self.tool["feed"] = float(self.tool["feed"])
4383 self.tool["fine feed"] = float(self.tool["fine feed"] if "fine feed" in self.tool else self.tool["feed"])
4385 gcode += ( "(Change tool to %s)\n" % re.sub("\"'\(\)\\\\"," ",self.tool["name"]) ) + self.tool["tool change gcode"] + "\n"
4387 for path in paths[layer]:
4388 csp = self.transform_csp(cubicsuperpath.parsePath(path.get("d")),layer)
4390 for subpath in csp :
4391 # Offset the path if fine cut is defined.
4392 fine_cut = subpath[:]
4393 if self.options.lathe_fine_cut_width>0 :
4394 r = self.options.lathe_fine_cut_width
4395 # Close the path to make offset correct
4396 bound = csp_simple_bound([subpath])
4397 minx,miny,maxx,maxy = csp_true_bounds([subpath])
4398 offsetted_subpath = csp_subpath_line_to(subpath[:], [ [subpath[-1][1][0], miny[1]-r*10 ], [subpath[0][1][0], miny[1]-r*10 ], [subpath[0][1][0], subpath[0][1][1] ] ])
4399 left,right = subpath[-1][1][0], subpath[0][1][0]
4400 if left>right : left, right = right,left
4401 offsetted_subpath = csp_offset([offsetted_subpath], r if not csp_subpath_ccw(offsetted_subpath) else -r )
4402 offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left,10], [left,0] )
4403 offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right,0], [right,10] )
4404 offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1]-r], [10, miny[1]-r] )
4405 #csp_draw(self.transform_csp(offsetted_subpath,layer,True), color = "Green", width = 1)
4406 # Join offsetted_subpath together
4407 # Hope there wont be any cicles
4408 subpath = csp_join_subpaths(offsetted_subpath)[0]
4410 # Create solid object from path and lathe_width
4411 bound = csp_simple_bound([subpath])
4412 top_start, top_end = [subpath[0][1][0], self.options.lathe_width+self.options.Zsafe+self.options.lathe_fine_cut_width], [subpath[-1][1][0], self.options.lathe_width+self.options.Zsafe+self.options.lathe_fine_cut_width]
4414 gcode += ("G01 %s %f F %f \n" % (z, top_start[1], self.tool["passing feed"]) )
4415 gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
4417 subpath = csp_concat_subpaths(csp_subpath_line_to([],[top_start,subpath[0][1]]), subpath)
4418 subpath = csp_subpath_line_to(subpath,[top_end,top_start])
4421 width = max(0, self.options.lathe_width - max(0, bound[1]) )
4422 step = self.tool['depth step']
4423 steps = int(math.ceil(width/step))
4424 for i in range(steps+1):
4425 current_width = self.options.lathe_width - step*i
4426 intersections = []
4427 for j in range(1,len(subpath)) :
4428 sp1,sp2 = subpath[j-1], subpath[j]
4429 intersections += [[j,k] for k in csp_line_intersection([bound[0]-10,current_width], [bound[2]+10,current_width], sp1, sp2)]
4430 intersections += [[j,k] for k in csp_line_intersection([bound[0]-10,current_width+step], [bound[2]+10,current_width+step], sp1, sp2)]
4431 parts = csp_subpath_split_by_points(subpath,intersections)
4432 for part in parts :
4433 minx,miny,maxx,maxy = csp_true_bounds([part])
4434 y = (maxy[1]+miny[1])/2
4435 if y > current_width+step :
4436 gcode += self.generate_lathe_gcode(part,layer,"passing feed")
4437 elif current_width <= y <= current_width+step :
4438 gcode += self.generate_lathe_gcode(part,layer,"feed")
4439 else :
4440 # full step cut
4441 part = csp_subpath_line_to([], [part[0][1], part[-1][1]] )
4442 gcode += self.generate_lathe_gcode(part,layer,"feed")
4444 top_start, top_end = [fine_cut[0][1][0], self.options.lathe_width+self.options.Zsafe+self.options.lathe_fine_cut_width], [fine_cut[-1][1][0], self.options.lathe_width+self.options.Zsafe+self.options.lathe_fine_cut_width]
4445 gcode += "\n(Fine cutting start)\n(Calculating fine cut using %s)\n"%self.options.lathe_create_fine_cut_using
4446 for i in range(self.options.lathe_fine_cut_count) :
4447 width = self.options.lathe_fine_cut_width*(1-float(i+1)/self.options.lathe_fine_cut_count )
4448 if width == 0 :
4449 current_pass = fine_cut
4450 else :
4451 if self.options.lathe_create_fine_cut_using == "Move path" :
4452 current_pass = [ [ [i2[0],i2[1]+width] for i2 in i1] for i1 in fine_cut]
4453 else :
4454 minx,miny,maxx,maxy = csp_true_bounds([fine_cut])
4455 offsetted_subpath = csp_subpath_line_to(fine_cut[:], [ [fine_cut[-1][1][0], miny[1]-r*10 ], [fine_cut[0][1][0], miny[1]-r*10 ], [fine_cut[0][1][0], fine_cut[0][1][1] ] ])
4456 left,right = fine_cut[-1][1][0], fine_cut[0][1][0]
4457 if left>right : left, right = right,left
4458 offsetted_subpath = csp_offset([offsetted_subpath], width if not csp_subpath_ccw(offsetted_subpath) else -width )
4459 offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left,10], [left,0] )
4460 offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right,0], [right,10] )
4461 offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1]-r], [10, miny[1]-r] )
4462 current_pass = csp_join_subpaths(offsetted_subpath)[0]
4465 gcode += "\n(Fine cut %i-th cicle start)\n"%(i+1)
4466 gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
4467 gcode += ("G01 %s %f %s %f F %f \n" % (x, current_pass[0][1][0], z, current_pass[0][1][1]+self.options.lathe_fine_cut_width, self.tool["passing feed"]) )
4468 gcode += ("G01 %s %f %s %f F %f \n" % (x, current_pass[0][1][0], z, current_pass[0][1][1], self.tool["fine feed"]) )
4470 gcode += self.generate_lathe_gcode(current_pass,layer,"fine feed")
4471 gcode += ("G01 %s %f F %f \n" % (z, top_start[1], self.tool["passing feed"]) )
4472 gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
4476 self.export_gcode(gcode)
4479 def update(self) :
4480 try :
4481 import urllib
4482 f = urllib.urlopen("http://www.cnc-club.ru/gcodetools_latest_version", proxies = urllib.getproxies())
4483 a = f.read()
4484 for s in a.split("\n") :
4485 r = re.search(r"Gcodetools\s+latest\s+version\s*=\s*(.*)",s)
4486 if r :
4487 ver = r.group(1).strip()
4488 if ver != gcodetools_current_version :
4489 self.error("There is a newer version of Gcodetools you can get it at: \nhttp://www.cnc-club.ru/gcodetools (English version). \nhttp://www.cnc-club.ru/gcodetools_ru (Russian version). ","Warning")
4490 else :
4491 self.error("You are currently using latest stable version of Gcodetools.","Warning")
4492 return
4493 self.error("Can not check the latest version. You can check it manualy at \nhttp://www.cnc-club.ru/gcodetools (English version). \nhttp://www.cnc-club.ru/gcodetools_ru (Russian version). \nCurrent version is Gcodetools %s"%gcodetools_current_version,"Warning")
4494 except :
4495 self.error("Can not check the latest version. You can check it manualy at \nhttp://www.cnc-club.ru/gcodetools (English version). \nhttp://www.cnc-club.ru/gcodetools_ru (Russian version). \nCurrent version is Gcodetools %s"%gcodetools_current_version,"Warning")
4498 ################################################################################
4499 ###
4500 ### Effect
4501 ###
4502 ### Main function of Gcodetools class
4503 ###
4504 ################################################################################
4505 def effect(self) :
4506 global options
4507 options = self.options
4508 options.self = self
4509 options.doc_root = self.document.getroot()
4511 # define print_ function
4512 global print_
4513 if self.options.log_create_log :
4514 try :
4515 if os.path.isfile(self.options.log_filename) : os.remove(self.options.log_filename)
4516 f = open(self.options.log_filename,"a")
4517 f.write("Gcodetools log file.\nStarted at %s.\n%s\n" % (time.strftime("%d.%m.%Y %H:%M:%S"),options.log_filename))
4518 f.write("%s tab is active.\n" % self.options.active_tab)
4519 f.close()
4520 except :
4521 print_ = lambda *x : None
4522 else : print_ = lambda *x : None
4523 if self.options.active_tab == '"help"' :
4524 self.help()
4525 return
4526 elif self.options.active_tab not in ['"dxfpoints"','"path-to-gcode"', '"area"', '"area_artefacts"', '"engraving"', '"orientation"', '"tools_library"', '"lathe"', '"offset"', '"arrangement"', '"update"']:
4527 self.error(_("Select one of the active tabs - Path to Gcode, Area, Engraving, DXF points, Orientation, Offset, Lathe or Tools library."),"error")
4528 else:
4529 # Get all Gcodetools data from the scene.
4530 self.get_info()
4531 if self.options.active_tab in ['"dxfpoints"','"path-to-gcode"', '"area"', '"area_artefacts"', '"engraving"', '"lathe"']:
4532 if self.orientation_points == {} :
4533 self.error(_("Orientation points have not been defined! A default set of orientation points has been automatically added."),"warning")
4534 self.orientation( self.layers[min(1,len(self.layers)-1)] )
4535 self.get_info()
4536 if self.tools == {} :
4537 self.error(_("Cutting tool has not been defined! A default tool has been automatically added."),"warning")
4538 self.options.tools_library_type = "default"
4539 self.tools_library( self.layers[min(1,len(self.layers)-1)] )
4540 self.get_info()
4541 if self.options.active_tab == '"path-to-gcode"':
4542 self.path_to_gcode()
4543 elif self.options.active_tab == '"area"':
4544 self.area()
4545 elif self.options.active_tab == '"area_artefacts"':
4546 self.area_artefacts()
4547 elif self.options.active_tab == '"dxfpoints"':
4548 self.dxfpoints()
4549 elif self.options.active_tab == '"engraving"':
4550 self.engraving()
4551 elif self.options.active_tab == '"orientation"':
4552 self.orientation()
4553 elif self.options.active_tab == '"tools_library"':
4554 if self.options.tools_library_type != "check":
4555 self.tools_library()
4556 else :
4557 self.check_tools_and_op()
4558 elif self.options.active_tab == '"lathe"':
4559 self.lathe()
4560 elif self.options.active_tab == '"update"':
4561 self.update()
4562 elif self.options.active_tab == '"offset"':
4563 if self.options.offset_just_get_distance :
4564 for layer in self.selected_paths :
4565 if len(self.selected_paths[layer]) == 2 :
4566 csp1, csp2 = cubicsuperpath.parsePath(self.selected_paths[layer][0].get("d")), cubicsuperpath.parsePath(self.selected_paths[layer][1].get("d"))
4567 dist = csp_to_csp_distance(csp1,csp2)
4568 print_(dist)
4569 draw_pointer( list(csp_at_t(csp1[dist[1]][dist[2]-1],csp1[dist[1]][dist[2]],dist[3]))
4570 +list(csp_at_t(csp2[dist[4]][dist[5]-1],csp2[dist[4]][dist[5]],dist[6])),"red","line", comment = math.sqrt(dist[0]))
4571 return
4572 if self.options.offset_step == 0 : self.options.offset_step = self.options.offset_radius
4573 if self.options.offset_step*self.options.offset_radius <0 : self.options.offset_step *= -1
4574 time_ = time.time()
4575 offsets_count = 0
4576 for layer in self.selected_paths :
4577 for path in self.selected_paths[layer] :
4579 offset = self.options.offset_step/2
4580 while abs(offset) <= abs(self.options.offset_radius) :
4581 offset_ = csp_offset(cubicsuperpath.parsePath(path.get("d")), offset)
4582 offsets_count += 1
4583 if offset_ != [] :
4584 for iii in offset_ :
4585 csp_draw([iii], color="Green", width=1)
4586 #print_(offset_)
4587 else :
4588 print_("------------Reached empty offset at radius %s"% offset )
4589 break
4590 offset += self.options.offset_step
4591 print_()
4592 print_("-----------------------------------------------------------------------------------")
4593 print_("-----------------------------------------------------------------------------------")
4594 print_("-----------------------------------------------------------------------------------")
4595 print_()
4596 print_("Done in %s"%(time.time()-time_))
4597 print_("Total offsets count %s"%offsets_count)
4598 elif self.options.active_tab == '"arrangement"':
4599 self.arrangement()
4600 #
4601 e = Gcodetools()
4602 e.affect()