Code

Translations. French translation minor update.
[inkscape.git] / share / extensions / gcodetools.py
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
48  
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
68                                 
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 %"""
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"}),
165                 
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
201         
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
214                         
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")
318                                         
319                                         
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]]
335         
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      
493                 
494         
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                        
523         
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))
527         
528         #curvature = (x'y''-y'x'') / (x'^2+y'^2)^1.5 
529         
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
553                 
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
575         
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     
599         
600         x4,y4 = x1+(x2-x1)*t, y1+(y2-y1)*t 
601         x5,y5 = x2+(x3-x2)*t, y2+(y3-y2)*t 
602         
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)   
612         
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)
644         
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]
774                 
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 )
870                                         
871                                         
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.]
996                                 
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):
1007         
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:]               
1015                         
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 )                                                        
1032         
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
1069         
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])
1102         
1104 def csp_segment_convex_hull(sp1,sp2):
1105         a,b,c,d = sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]
1106         
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)]
1116         
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]
1125         
1126         raise ValueError, "csp_segment_convex_hull happend something that shouldnot happen!"    
1128         
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]]
1167         
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 ################################################################################
1183         
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
1258                                         
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 :
1263                 
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
1288                         
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
1313         
1314         
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
1405         
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)
1422         
1423         
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) 
1439                                         
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:
1446                                                 
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                                   
1459         
1460                                                 
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) ) 
1475                 
1476                 return [[q0.to_list(), q0.to_list(), q1.to_list()],[q2.to_list(), q3.to_list(), q3.to_list()]]
1477         
1478         
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 []
1495         
1496                 
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:] 
1507                         
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
1539                 
1540                 
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]
1565                 
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]] ]
1591         
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.
1596                                         
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
1617                                 
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[:]
1626                                                 
1627                 # Join last and first offsets togother to close the curve
1628                 
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))
1641                         
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()
1645         
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()
1699                                                                 
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[:]]
1717         
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()
1723         
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
1741                         
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"  )
1750                                 
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)
1756         
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()
1772                         
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         ########################################################################                
1777         
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
1791         
1792         
1793                 
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]] ]
1877         
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      
1938         
1939         
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                                         }
1953         
1954                         
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)           
1965                         
1966         
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")
1977         
1978         
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])
1984                 
1985         
1986         def remapi(self,parameters):
1987                 self.remap(parameters, case_sensitive = True)
1988         
1989         
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)]    
2002                 
2003                 
2004                 
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)
2010                         
2011                 for i in range(len(remap)) :
2012                         self.gcode = self.gcode.replace(":#:#:remap_pattern%s:#:#:"%i, remap[i])
2013         
2014         
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"
2056                         
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)
2082                                         
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())
2094                         
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"
2117                 
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"
2141         
2142                 self.gcode = gcode      
2144         
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 : 
2155                                         
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
2163         
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)         
2177         
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"]))     
2199         
2200                         
2201 ################################################################################
2202 ###             Polygon class
2203 ################################################################################
2204 class Polygon:
2205         def __init__(self, polygon=None):
2206                 self.polygon = [] if polygon==None else polygon[:]
2207         
2208         
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
2214         
2215         
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              
2225         
2226         
2227         def width(self):
2228                 b = self.bounds()
2229                 return b[2]-b[0]
2230         
2231         
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
2238                 
2239         
2240         def rotate(self, a):
2241                 cos, sin = math.cos(a), math.sin(a)
2242                 self.rotate_(sin,cos)
2243         
2244                         
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)
2258                 
2259                         
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]
2285                         
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
2318                 
2319                 if zerro_plane and dist > 10 + top : dist = 10 + top 
2320                 #print_(dist, top, bottom)
2321                 #self.draw()
2322                 self.move(0, -dist)             
2323                 
2324                                         
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 )
2328                         
2329         
2330         def add(self, add) :
2331                 if type(add) == type([]) :
2332                         self.polygon += add[:]
2333                 else :  
2334                         self.polygon += add.polygon[:]
2336         
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                   
2354         
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]
2364                                 
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")
2412                                                         
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")
2427                 
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
2434                         
2435                 
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
2449                         
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             
2470                         
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 
2475                 
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.
2518         
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] ]
2528         
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
2536         
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)
2544                 
2545         
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]]
2570                         
2571                 self.population = res
2572                         
2573                         
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) ]
2585                         
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] ]                                                
2618                                 
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] ]  
2630         
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
2644         
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
2670                 
2671                 
2672                 
2673                 #surface.draw()
2675                 
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]
2721                                         
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()
2725                 
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)
2733                 
2734                 
2735                 print_("Genetic alhorithm start at %s"%(time_))
2736                 time_ = time.time()
2737                 
2738         
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
2748                 
2749                 
2750                 
2751                 
2752                 for i in range(population_count):
2753                         population.leave_top_species(20)
2754                         population.move_mutation_multiplier = random.random()/2
2755                         
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])
2776                         
2777                         if population.population[0][0]!= last_champ : 
2778                                 draw_new_champ = True
2779                                 last_champ = population.population[0][0]*1
2780                         
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"]
2793                                 
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)
2820                                         
2821                                         
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)
2833                                 
2834                                 
2835                 # Now we'll need apply transforms to original paths
2836                 
2837                 
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")
2891                                                                                                                                                                                                                   
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")               
2898         
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.")
2904         
2905                 
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)
2956                         
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"]):
2983         
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])
3005                 
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
3009                 
3010                 
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])
3019                         
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
3034                                                 
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
3063         
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
3106                 
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
3115                         
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 ""    
3147                                 
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"
3155                 
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 
3225                 
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                                                         ])
3267                                                                 
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)]
3275                                         
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
3306         
3307                 
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()
3350         
3351         
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 = {}
3382                 
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")
3413                                 
3414                                         
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
3474                 
3475                 
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)
3538         
3539         
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
3546         
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
3579                         
3580                         return minimal_way
3582                 
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
3589                         
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)
3631                         
3632                 self.export_gcode(gcode)
3633         
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")
3648                                                 
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
3705                         
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")
3721         
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') )
3726                                 
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)
3734                                 
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)
3739                                                 
3740                                                 min_x,min_y,min_i,min_j,min_t = csp_true_bounds(csp)[1]
3741                                                 
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[:]
3766                                                         
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
3789                                                 
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
3822                 
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
3830                                         
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
3842                                 
3843                                 tx = fx-x1-nx1*t1
3844                                 ty = fy-y1-ny1*t1
3845                                 
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                                     
3849                                 
3850                                 F1[0][0] = nx1
3851                                 F1[0][1] = f1y
3852                                 F1[0][2] = -f1x+t2*(6*ay*t3+2*by)
3853                                 
3854                                 F1[1][0] = ny1
3855                                 F1[1][1] = -f1x
3856                                 F1[1][2] = -f1y-t2*(6*ax*t3+2*bx)
3857                                 
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)
3863                         
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]      
3867                         
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     
3873                                 
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] ]
4005                                                         
4006                                                 
4007                                                         
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] 
4019                                         
4020                                         
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
4065                 
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")
4087                 
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])
4122         
4123                 
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                         }
4183                         
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
4201                 
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"]
4204                 
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"})
4217                 
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)
4289                                         
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])
4340                 
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
4372         
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"]) 
4384                                         
4385                                         gcode += ( "(Change tool to %s)\n" % re.sub("\"'\(\)\\\\"," ",self.tool["name"]) ) + self.tool["tool change gcode"] + "\n"
4386                                         
4387                                 for path in paths[layer]:
4388                                         csp = self.transform_csp(cubicsuperpath.parsePath(path.get("d")),layer)
4389                                         
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]
4409                                                         
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])
4419         
4420                                                 
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")
4443                                                                         
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"]) )
4469                                                 
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"]) )
4473         
4474                 
4475                 
4476                 self.export_gcode(gcode)
4478         
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")                                    
4496                                 
4497                                                                                 
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] :
4578                                                                                 
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()