Advertisement
Guest User

gcodetools piece

a guest
Jul 9th, 2019
618
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 271.21 KB | None | 0 0
  1. #!/usr/bin/env python
  2. """
  3. Comments starting "#LT" or "#CLT" are by Chris Lusby Taylor who rewrote the engraving function in 2011.
  4. History of CLT changes to engraving and other functions it uses:
  5. 9 May 2011 Changed test of tool diameter to square it
  6. 10 May Note that there are many unused functions, including:
  7.      bound_to_bound_distance, csp_curvature_radius_at_t,
  8.    csp_special_points, csplength, rebuild_csp, csp_slope,
  9.    csp_simple_bound_to_point_distance, csp_bound_to_point_distance,
  10.    bez_at_t, bez_to_point_distance, bez_normalized_slope, matrix_mul, transpose
  11.    Fixed csp_point_inside_bound() to work if x outside bounds
  12. 20 May Now encoding the bisectors of angles.
  13. 23 May Using r/cos(a) instead of normalised normals for bisectors of angles.
  14. 23 May Note that Z values generated for engraving are in pixels, not mm.
  15.    Removed the biarc curves - straight lines are better.
  16. 24 May Changed Bezier slope calculation to be less sensitive to tiny differences in points.
  17.    Added use of self.options.engraving_newton_iterations to control accuracy
  18. 25 May Big restructure and new recursive function.
  19.       Changed the way I treat corners - I now find if the centre of a proposed circle is
  20.        within the area bounded by the line being tested and the two angle bisectors at
  21.           its ends. See get_radius_to_line().
  22. 29 May Eliminating redundant points. If A,B,C colinear, drop B
  23. 30 May Eliminating redundant lines in divided Beziers. Changed subdivision of lines
  24. 7Jun Try to show engraving in 3D
  25. 8 Jun Displaying in stereo 3D.
  26.    Fixed a bug in bisect - it could go wrong due to rounding errors if
  27.             1+x1.x2+y1.y2<0 which should never happen. BTW, I spotted a non-normalised normal
  28.             returned by csp_normalized_normal. Need to check for that.
  29.  9 Jun Corrected spelling of 'definition' but still match previous 'defention' and   'defenition' if found in file
  30.      Changed get_tool to find 1.6.04 tools or new tools with corrected spelling
  31. 10 Jun Put 3D into a separate layer called 3D, created unless it already exists
  32.    Changed csp_normalized_slope to reject lines shorter than 1e-9.
  33. 10 Jun Changed all dimensions seen by user to be mm/inch, not pixels. This includes
  34.      tool diameter, maximum engraving distance, tool shape and all Z values.
  35. 12 Jun ver 208 Now scales correctly if orientation points moved or stretched.
  36. 12 Jun ver 209. Now detect if engraving toolshape not a function of radius
  37.        Graphics now indicate Gcode toolpath, limited by min(tool diameter/2,max-dist)
  38. TODO Change line division to be recursive, depending on what line is touched. See line_divide
  39.  
  40.  
  41. engraving() functions (c) 2011 Chris Lusby Taylor, [email protected]
  42. Copyright (C) 2009 Nick Drobchenko, [email protected]
  43. based on gcode.py (C) 2007 hugomatic...
  44. based on addnodes.py (C) 2005,2007 Aaron Spike, [email protected]
  45. based on dots.py (C) 2005 Aaron Spike, [email protected]
  46. based on interp.py (C) 2005 Aaron Spike, [email protected]
  47. based on bezmisc.py (C) 2005 Aaron Spike, [email protected]
  48. based on cubicsuperpath.py (C) 2005 Aaron Spike, [email protected]
  49.  
  50. This program is free software; you can redistribute it and/or modify
  51. it under the terms of the GNU General Public License as published by
  52. the Free Software Foundation; either version 2 of the License, or
  53. (at your option) any later version.
  54.  
  55. This program is distributed in the hope that it will be useful,
  56. but WITHOUT ANY WARRANTY; without even the implied warranty of
  57. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  58. GNU General Public License for more details.
  59.  
  60. You should have received a copy of the GNU General Public License
  61. along with this program; if not, write to the Free Software
  62. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  63. """
  64.  
  65. ###
  66. ###     Gcodetools v 1.7
  67. ###
  68.  
  69. gcodetools_current_version = "1.7"
  70.  
  71. # standard library
  72. import os
  73. import math
  74. import bezmisc
  75. import re
  76. import copy
  77. import sys
  78. import time
  79. import cmath
  80. import numpy
  81. import codecs
  82. import random
  83. # local library
  84. import inkex
  85. import simplestyle
  86. import simplepath
  87. import cubicsuperpath
  88. import simpletransform
  89. import bezmisc
  90.  
  91. inkex.localize()
  92.  
  93. ### Check if inkex has errormsg (0.46 version does not have one.) Could be removed later.
  94. if "errormsg" not in dir(inkex):
  95.     inkex.errormsg = lambda msg: sys.stderr.write((unicode(msg) + "\n").encode("UTF-8"))
  96.  
  97.  
  98. def bezierslopeatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
  99.     ax,ay,bx,by,cx,cy,x0,y0=bezmisc.bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
  100.     dx=3*ax*(t**2)+2*bx*t+cx
  101.     dy=3*ay*(t**2)+2*by*t+cy
  102.     if dx==dy==0 :
  103.         dx = 6*ax*t+2*bx
  104.         dy = 6*ay*t+2*by
  105.         if dx==dy==0 :
  106.             dx = 6*ax
  107.             dy = 6*ay
  108.             if dx==dy==0 :
  109.                 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))
  110.                 print_(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
  111.                 dx, dy = 1, 1
  112.                
  113.     return dx,dy
  114. bezmisc.bezierslopeatt = bezierslopeatt
  115.  
  116.  
  117. def ireplace(self,old,new,count=0):
  118.     pattern = re.compile(re.escape(old),re.I)
  119.     return re.sub(pattern,new,self,count)
  120.  
  121. def isset(variable):
  122.     # VARIABLE NAME SHOULD BE A STRING! Like isset("foobar")
  123.     return variable in locals() or variable in globals()
  124.    
  125.    
  126. ################################################################################
  127. ###
  128. ###     Styles and additional parameters
  129. ###
  130. ################################################################################
  131.  
  132. math.pi2 = math.pi*2
  133. straight_tolerance = 0.0001
  134. straight_distance_tolerance = 0.0001
  135. engraving_tolerance = 0.0001
  136. loft_lengths_tolerance = 0.0000001
  137.  
  138. EMC_TOLERANCE_EQUAL = 0.00001
  139.  
  140. options = {}
  141. defaults = {
  142. 'header': """%
  143. (Header)
  144. (Generated by gcodetools from Inkscape.)
  145. (Using default header. To add your own header create file "header" in the output dir.)
  146. M3
  147. (Header end.)
  148. """,
  149. 'footer': """
  150. (Footer)
  151. M5
  152. G00 X0.0000 Y0.0000
  153. M2
  154. (Using default footer. To add your own footer create file "footer" in the output dir.)
  155. (end)
  156. %"""
  157. }
  158.  
  159. intersection_recursion_depth = 10
  160. intersection_tolerance = 0.00001
  161.  
  162. styles = {
  163.         "in_out_path_style" : simplestyle.formatStyle({ 'stroke': '#0072a7', 'fill': 'none', 'stroke-width':'1', 'marker-mid':'url(#InOutPathMarker)' }),
  164.        
  165.         "loft_style" : {
  166.                 'main curve':   simplestyle.formatStyle({ 'stroke': '#88f', 'fill': 'none', 'stroke-width':'1', 'marker-end':'url(#Arrow2Mend)' }),
  167.             },
  168.         "biarc_style" : {
  169.                 'biarc0':   simplestyle.formatStyle({ 'stroke': '#88f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  170.                 'biarc1':   simplestyle.formatStyle({ 'stroke': '#8f8', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  171.                 'line':     simplestyle.formatStyle({ 'stroke': '#f88', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  172.                 'area':     simplestyle.formatStyle({ 'stroke': '#777', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
  173.             },
  174.         "biarc_style_dark" : {
  175.                 'biarc0':   simplestyle.formatStyle({ 'stroke': '#33a', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  176.                 'biarc1':   simplestyle.formatStyle({ 'stroke': '#3a3', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  177.                 'line':     simplestyle.formatStyle({ 'stroke': '#a33', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  178.                 'area':     simplestyle.formatStyle({ 'stroke': '#222', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
  179.             },
  180.         "biarc_style_dark_area" : {
  181.                 'biarc0':   simplestyle.formatStyle({ 'stroke': '#33a', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
  182.                 'biarc1':   simplestyle.formatStyle({ 'stroke': '#3a3', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
  183.                 'line':     simplestyle.formatStyle({ 'stroke': '#a33', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
  184.                 'area':     simplestyle.formatStyle({ 'stroke': '#222', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
  185.             },
  186.         "biarc_style_i" : {
  187.                 'biarc0':   simplestyle.formatStyle({ 'stroke': '#880', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  188.                 'biarc1':   simplestyle.formatStyle({ 'stroke': '#808', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  189.                 'line':     simplestyle.formatStyle({ 'stroke': '#088', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  190.                 'area':     simplestyle.formatStyle({ 'stroke': '#999', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
  191.             },
  192.         "biarc_style_dark_i" : {
  193.                 'biarc0':   simplestyle.formatStyle({ 'stroke': '#dd5', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  194.                 'biarc1':   simplestyle.formatStyle({ 'stroke': '#d5d', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  195.                 'line':     simplestyle.formatStyle({ 'stroke': '#5dd', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
  196.                 'area':     simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
  197.             },
  198.         "biarc_style_lathe_feed" : {
  199.                 'biarc0':   simplestyle.formatStyle({ 'stroke': '#07f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
  200.                 'biarc1':   simplestyle.formatStyle({ 'stroke': '#0f7', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
  201.                 'line':     simplestyle.formatStyle({ 'stroke': '#f44', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
  202.                 'area':     simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
  203.             },
  204.         "biarc_style_lathe_passing feed" : {
  205.                 'biarc0':   simplestyle.formatStyle({ 'stroke': '#07f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
  206.                 'biarc1':   simplestyle.formatStyle({ 'stroke': '#0f7', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
  207.                 'line':     simplestyle.formatStyle({ 'stroke': '#f44', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
  208.                 'area':     simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
  209.             },
  210.         "biarc_style_lathe_fine feed" : {
  211.                 'biarc0':   simplestyle.formatStyle({ 'stroke': '#7f0', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
  212.                 'biarc1':   simplestyle.formatStyle({ 'stroke': '#f70', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
  213.                 'line':     simplestyle.formatStyle({ 'stroke': '#744', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
  214.                 'area':     simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
  215.             },
  216.         "area artefact":        simplestyle.formatStyle({ 'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width':'1' }),
  217.         "area artefact arrow":  simplestyle.formatStyle({ 'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width':'1' }),
  218.         "dxf_points":           simplestyle.formatStyle({ "stroke": "#ff0000", "fill": "#ff0000"}),
  219.        
  220.     }
  221.  
  222.  
  223.  
  224. ################################################################################
  225. ###     Gcode additional functions
  226. ################################################################################
  227.  
  228. def gcode_comment_str(s, replace_new_line = False):
  229.     if replace_new_line :
  230.         s = re.sub(r"[\n\r]+", ".", s)
  231.     res = ""
  232.     if s[-1] == "\n" : s = s[:-1]
  233.     for a in s.split("\n") :
  234.         if a != "" :
  235.             res += ";(" + re.sub(r"[\(\)\\\n\r]", ".", a) + ")\n"
  236.         else :  
  237.             res += "\n"
  238.     return res 
  239.  
  240.  
  241. ################################################################################
  242. ###     Cubic Super Path additional functions
  243. ################################################################################
  244.  
  245.  
  246. def csp_from_polyline(line) :
  247.     return [ [ [point[:] for k in range(3) ] for point in subline ] for subline in line ]
  248.    
  249. def csp_remove_zerro_segments(csp, tolerance = 1e-7):  
  250.     res = []
  251.     for subpath in csp:
  252.         if len(subpath) > 0 :
  253.             res.append([subpath[0]])
  254.             for sp1,sp2 in zip(subpath,subpath[1:]) :
  255.                 if point_to_point_d2(sp1[1],sp2[1])<=tolerance and point_to_point_d2(sp1[2],sp2[1])<=tolerance and point_to_point_d2(sp1[1],sp2[0])<=tolerance :
  256.                     res[-1][-1][2] = sp2[2]
  257.                 else :
  258.                     res[-1].append(sp2)
  259.     return res
  260.            
  261.  
  262.    
  263.  
  264.  
  265. def point_inside_csp(p,csp, on_the_path = True) :
  266.     # we'll do the raytracing and see how many intersections are there on the ray's way.
  267.     # if number of intersections is even then point is outside.
  268.     # ray will be x=p.x and y=>p.y
  269.     # you can assing any value to on_the_path, by dfault if point is on the path
  270.     # function will return thai it's inside the path.
  271.     x,y = p
  272.     ray_intersections_count = 0
  273.     for subpath in csp :
  274.        
  275.         for i in range(1, len(subpath)) :
  276.             sp1, sp2 = subpath[i-1], subpath[i]
  277.             ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
  278.             if ax==0 and bx==0 and cx==0 and dx==x :
  279.                 #we've got a special case here
  280.                 b = csp_true_bounds( [[sp1,sp2]])
  281.                 if b[1][1]<=y<=b[3][1] :
  282.                     # points is on the path
  283.                     return on_the_path
  284.                 else :
  285.                     # we can skip this segment because it wont influence the answer.
  286.                     pass   
  287.             else:
  288.                 for t in csp_line_intersection([x,y],[x,y+5],sp1,sp2) :
  289.                     if t == 0 or t == 1 :
  290.                         #we've got another special case here
  291.                         x1,y1 = csp_at_t(sp1,sp2,t)
  292.                         if y1==y :
  293.                             # the point is on the path
  294.                             return on_the_path
  295.                         # if t == 0 we sould have considered this case previously.
  296.                         if t == 1 :
  297.                             # we have to check the next segmant if it is on the same side of the ray
  298.                             st_d = csp_normalized_slope(sp1,sp2,1)[0]
  299.                             if st_d == 0 : st_d = csp_normalized_slope(sp1,sp2,0.99)[0]
  300.                            
  301.                             for j in range(1, len(subpath)+1):
  302.                                 if (i+j) % len(subpath) == 0 : continue # skip the closing segment
  303.                                 sp11,sp22 = subpath[(i-1+j) % len(subpath)], subpath[(i+j) % len(subpath)]
  304.                                 ax1,ay1,bx1,by1,cx1,cy1,dx1,dy1 = csp_parameterize(sp1,sp2)
  305.                                 if ax1==0 and bx1==0 and cx1==0 and dx1==x : continue # this segment parallel to the ray, so skip it
  306.                                 en_d = csp_normalized_slope(sp11,sp22,0)[0]
  307.                                 if en_d == 0 : en_d = csp_normalized_slope(sp11,sp22,0.01)[0]
  308.                                 if st_d*en_d <=0 :
  309.                                     ray_intersections_count += 1
  310.                                     break
  311.                     else
  312.                         x1,y1 = csp_at_t(sp1,sp2,t)
  313.                         if y1==y :
  314.                             # the point is on the path
  315.                             return on_the_path
  316.                         else :
  317.                             if y1>y and 3*ax*t**2 + 2*bx*t + cx !=0 : # if it's 0 the path only touches the ray
  318.                                 ray_intersections_count += 1    
  319.     return ray_intersections_count%2 == 1              
  320.  
  321. def csp_close_all_subpaths(csp, tolerance = 0.000001):
  322.     for i in range(len(csp)):
  323.         if point_to_point_d2(csp[i][0][1] , csp[i][-1][1])> tolerance**2 :
  324.             csp[i][-1][2] = csp[i][-1][1][:]
  325.             csp[i] += [ [csp[i][0][1][:] for j in range(3)] ]
  326.         else:
  327.             if csp[i][0][1] != csp[i][-1][1] :
  328.                 csp[i][-1][1] = csp[i][0][1][:]
  329.     return csp
  330.  
  331. def csp_simple_bound(csp):
  332.     minx,miny,maxx,maxy = None,None,None,None
  333.     for subpath in csp:
  334.         for sp in subpath :
  335.             for p in sp:
  336.                 minx = min(minx,p[0]) if minx!=None else p[0]
  337.                 miny = min(miny,p[1]) if miny!=None else p[1]
  338.                 maxx = max(maxx,p[0]) if maxx!=None else p[0]
  339.                 maxy = max(maxy,p[1]) if maxy!=None else p[1]
  340.     return minx,miny,maxx,maxy     
  341.  
  342.  
  343. def csp_segment_to_bez(sp1,sp2) :
  344.     return sp1[1:]+sp2[:2]
  345.  
  346.  
  347. def bound_to_bound_distance(sp1,sp2,sp3,sp4) :
  348.     min_dist = 1e100
  349.     max_dist = 0
  350.     points1 = csp_segment_to_bez(sp1,sp2)
  351.     points2 = csp_segment_to_bez(sp3,sp4)
  352.     for i in range(4) :
  353.         for j in range(4) :
  354.             min_, max_ = line_to_line_min_max_distance_2(points1[i-1], points1[i], points2[j-1], points2[j])
  355.             min_dist = min(min_dist,min_)
  356.             max_dist = max(max_dist,max_)
  357.             print_("bound_to_bound", min_dist, max_dist)
  358.     return min_dist, max_dist
  359.    
  360. def csp_to_point_distance(csp, p, dist_bounds = [0,1e100], tolerance=.01) :
  361.     min_dist = [1e100,0,0,0]
  362.     for j in range(len(csp)) :
  363.         for i in range(1,len(csp[j])) :
  364.             d = csp_seg_to_point_distance(csp[j][i-1],csp[j][i],p,sample_points = 5, tolerance = .01)
  365.             if d[0] < dist_bounds[0] :
  366. #               draw_pointer( list(csp_at_t(subpath[dist[2]-1],subpath[dist[2]],dist[3]))
  367. #                   +list(csp_at_t(csp[dist[4]][dist[5]-1],csp[dist[4]][dist[5]],dist[6])),"red","line", comment = math.sqrt(dist[0]))
  368.                 return [d[0],j,i,d[1]]
  369.             else :
  370.                 if d[0] < min_dist[0] : min_dist = [d[0],j,i,d[1]]
  371.     return min_dist
  372.            
  373. def csp_seg_to_point_distance(sp1,sp2,p,sample_points = 5, tolerance = .01) :
  374.     ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
  375.     dx, dy = dx-p[0], dy-p[1]
  376.     if sample_points < 2 : sample_points = 2
  377.     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.] )  
  378.     for k in range(sample_points) :
  379.         t = float(k)/(sample_points-1)
  380.         i = 0
  381.         while i==0 or abs(f)>0.000001 and i<20 :
  382.             t2,t3 = t**2,t**3
  383.             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)
  384.             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
  385.             if df!=0 :
  386.                 t = t - f/df
  387.             else
  388.                 break
  389.             i += 1 
  390.         if 0<=t<=1 :
  391.             p1 = csp_at_t(sp1,sp2,t)
  392.             d1 = (p1[0]-p[0])**2 + (p1[1]-p[1])**2
  393.             if d1 < d[0] :
  394.                 d = [d1,t]
  395.     return d   
  396.  
  397.  
  398. def csp_seg_to_csp_seg_distance(sp1,sp2,sp3,sp4, dist_bounds = [0,1e100], sample_points = 5, tolerance=.01) :
  399.     # check the ending points first
  400.     dist =  csp_seg_to_point_distance(sp1,sp2,sp3[1],sample_points, tolerance)
  401.     dist += [0.]
  402.     if dist[0] <= dist_bounds[0] : return dist
  403.     d = csp_seg_to_point_distance(sp1,sp2,sp4[1],sample_points, tolerance)
  404.     if d[0]<dist[0] :
  405.         dist = d+[1.]
  406.         if dist[0] <= dist_bounds[0] : return dist
  407.     d = csp_seg_to_point_distance(sp3,sp4,sp1[1],sample_points, tolerance)
  408.     if d[0]<dist[0] :
  409.         dist = [d[0],0.,d[1]]
  410.         if dist[0] <= dist_bounds[0] : return dist
  411.     d = csp_seg_to_point_distance(sp3,sp4,sp2[1],sample_points, tolerance)
  412.     if d[0]<dist[0] :
  413.         dist = [d[0],1.,d[1]]
  414.         if dist[0] <= dist_bounds[0] : return dist
  415.     sample_points -= 2
  416.     if sample_points < 1 : sample_points = 1
  417.     ax1,ay1,bx1,by1,cx1,cy1,dx1,dy1 = csp_parameterize(sp1,sp2)
  418.     ax2,ay2,bx2,by2,cx2,cy2,dx2,dy2 = csp_parameterize(sp3,sp4)
  419.     #   try to find closes points using Newtons method
  420.     for k in range(sample_points) :
  421.         for j in range(sample_points) :
  422.             t1,t2 = float(k+1)/(sample_points+1), float(j)/(sample_points+1)
  423.             t12, t13, t22, t23 = t1*t1, t1*t1*t1, t2*t2, t2*t2*t2
  424.             i = 0
  425.             F1, F2, F = [0,0], [[0,0],[0,0]], 1e100
  426.             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)        
  427.             while i<2 or abs(F-Flast)>tolerance and i<30 :
  428.                 #draw_pointer(csp_at_t(sp1,sp2,t1))
  429.                 f1x = 3*ax1*t12+2*bx1*t1+cx1
  430.                 f1y = 3*ay1*t12+2*by1*t1+cy1
  431.                 f2x = 3*ax2*t22+2*bx2*t2+cx2
  432.                 f2y = 3*ay2*t22+2*by2*t2+cy2
  433.                 F1[0] = 2*f1x*x + 2*f1y*y
  434.                 F1[1] = -2*f2x*x - 2*f2y*y
  435.                 F2[0][0] = 2*(6*ax1*t1+2*bx1)*x + 2*f1x*f1x + 2*(6*ay1*t1+2*by1)*y +2*f1y*f1y
  436.                 F2[0][1] = -2*f1x*f2x - 2*f1y*f2y
  437.                 F2[1][0] = -2*f2x*f1x - 2*f2y*f1y
  438.                 F2[1][1] = -2*(6*ax2*t2+2*bx2)*x + 2*f2x*f2x - 2*(6*ay2*t2+2*by2)*y + 2*f2y*f2y
  439.                 F2 = inv_2x2(F2)
  440.                 if F2!=None :
  441.                     t1 -= ( F2[0][0]*F1[0] + F2[0][1]*F1[1] )
  442.                     t2 -= ( F2[1][0]*F1[0] + F2[1][1]*F1[1] )
  443.                     t12, t13, t22, t23 = t1*t1, t1*t1*t1, t2*t2, t2*t2*t2
  444.                     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)
  445.                     Flast = F
  446.                     F = x*x+y*y
  447.                 else :
  448.                     break
  449.                 i += 1
  450.             if F < dist[0] and 0<=t1<=1 and 0<=t2<=1:
  451.                 dist = [F,t1,t2]
  452.                 if dist[0] <= dist_bounds[0] :
  453.                     return dist
  454.     return dist        
  455.  
  456.  
  457. def csp_to_csp_distance(csp1,csp2, dist_bounds = [0,1e100], tolerance=.01) :
  458.     dist = [1e100,0,0,0,0,0,0]
  459.     for i1 in range(len(csp1)) :
  460.         for j1 in range(1,len(csp1[i1])) :
  461.             for i2 in range(len(csp2)) :
  462.                 for j2 in range(1,len(csp2[i2])) :
  463.                     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])
  464.                     if d[0] >= dist_bounds[1] : continue
  465.                     if d[1] < dist_bounds[0] : return [d[1],i1,j1,1,i2,j2,1]
  466.                     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)
  467.                     if d[0] < dist[0] :
  468.                         dist = [d[0], i1,j1,d[1], i2,j2,d[2]]
  469.                     if dist[0] <= dist_bounds[0]
  470.                         return dist
  471.             if dist[0] >= dist_bounds[1]
  472.                 return dist
  473.     return dist
  474. #   draw_pointer( list(csp_at_t(csp1[dist[1]][dist[2]-1],csp1[dist[1]][dist[2]],dist[3]))
  475. #               + list(csp_at_t(csp2[dist[4]][dist[5]-1],csp2[dist[4]][dist[5]],dist[6])), "#507","line")
  476.                    
  477.                    
  478. def csp_split(sp1,sp2,t=.5) :
  479.     [x1,y1],[x2,y2],[x3,y3],[x4,y4] = sp1[1], sp1[2], sp2[0], sp2[1]
  480.     x12 = x1+(x2-x1)*t
  481.     y12 = y1+(y2-y1)*t
  482.     x23 = x2+(x3-x2)*t
  483.     y23 = y2+(y3-y2)*t
  484.     x34 = x3+(x4-x3)*t
  485.     y34 = y3+(y4-y3)*t
  486.     x1223 = x12+(x23-x12)*t
  487.     y1223 = y12+(y23-y12)*t
  488.     x2334 = x23+(x34-x23)*t
  489.     y2334 = y23+(y34-y23)*t
  490.     x = x1223+(x2334-x1223)*t
  491.     y = y1223+(y2334-y1223)*t
  492.     return [sp1[0],sp1[1],[x12,y12]], [[x1223,y1223],[x,y],[x2334,y2334]], [[x34,y34],sp2[1],sp2[2]]
  493.    
  494.    
  495. def csp_true_bounds(csp) :
  496.     # Finds minx,miny,maxx,maxy of the csp and return their (x,y,i,j,t)
  497.     minx = [float("inf"), 0, 0, 0]                                                                                                                                                                                                                                                                                                                                                                                                                             
  498.     maxx = [float("-inf"), 0, 0, 0]
  499.     miny = [float("inf"), 0, 0, 0]
  500.     maxy = [float("-inf"), 0, 0, 0]
  501.     for i in range(len(csp)):
  502.         for j in range(1,len(csp[i])):
  503.             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]))
  504.             roots = cubic_solver(0, 3*ax, 2*bx, cx)  + [0,1]
  505.             for root in roots :
  506.                 if type(root) is complex and abs(root.imag)<1e-10:
  507.                     root = root.real
  508.                 if type(root) is not complex and 0<=root<=1:
  509.                     y = ay*(root**3)+by*(root**2)+cy*root+y0
  510.                     x = ax*(root**3)+bx*(root**2)+cx*root+x0
  511.                     maxx = max([x,y,i,j,root],maxx)
  512.                     minx = min([x,y,i,j,root],minx)
  513.  
  514.             roots = cubic_solver(0, 3*ay, 2*by, cy)  + [0,1]
  515.             for root in roots :
  516.                 if type(root) is complex and root.imag==0:
  517.                     root = root.real
  518.                 if type(root) is not complex and 0<=root<=1:
  519.                     y = ay*(root**3)+by*(root**2)+cy*root+y0
  520.                     x = ax*(root**3)+bx*(root**2)+cx*root+x0
  521.                     maxy = max([y,x,i,j,root],maxy)
  522.                     miny = min([y,x,i,j,root],miny)
  523.     maxy[0],maxy[1] = maxy[1],maxy[0]
  524.     miny[0],miny[1] = miny[1],miny[0]
  525.  
  526.     return minx,miny,maxx,maxy
  527.  
  528.  
  529. ############################################################################
  530. ### csp_segments_intersection(sp1,sp2,sp3,sp4)
  531. ###
  532. ### Returns array containig all intersections between two segmets of cubic
  533. ### super path. Results are [ta,tb], or [ta0, ta1, tb0, tb1, "Overlap"]
  534. ### where ta, tb are values of t for the intersection point.
  535. ############################################################################
  536. def csp_segments_intersection(sp1,sp2,sp3,sp4) :
  537.     a, b = csp_segment_to_bez(sp1,sp2), csp_segment_to_bez(sp3,sp4)
  538.  
  539.     def polish_intersection(a,b,ta,tb, tolerance = intersection_tolerance) :
  540.         ax,ay,bx,by,cx,cy,dx,dy         = bezmisc.bezierparameterize(a)
  541.         ax1,ay1,bx1,by1,cx1,cy1,dx1,dy1 = bezmisc.bezierparameterize(b)
  542.         i = 0
  543.         F, F1 = [.0,.0], [[.0,.0],[.0,.0]]
  544.         while i==0 or (abs(F[0])**2+abs(F[1])**2 > tolerance and i<10):
  545.             ta3, ta2, tb3, tb2 = ta**3, ta**2, tb**3, tb**2
  546.             F[0] = ax*ta3+bx*ta2+cx*ta+dx-ax1*tb3-bx1*tb2-cx1*tb-dx1
  547.             F[1] = ay*ta3+by*ta2+cy*ta+dy-ay1*tb3-by1*tb2-cy1*tb-dy1
  548.             F1[0][0] = 3*ax *ta2 + 2*bx *ta + cx
  549.             F1[0][1] = -3*ax1*tb2 - 2*bx1*tb - cx1
  550.             F1[1][0] = 3*ay *ta2 + 2*by *ta + cy
  551.             F1[1][1] = -3*ay1*tb2 - 2*by1*tb - cy1 
  552.             det = F1[0][0]*F1[1][1] - F1[0][1]*F1[1][0]
  553.             if det!=0 :
  554.                 F1 = [  [ F1[1][1]/det, -F1[0][1]/det], [-F1[1][0]/det, F1[0][0]/det] ]
  555.                 ta = ta - ( F1[0][0]*F[0] + F1[0][1]*F[1] )
  556.                 tb = tb - ( F1[1][0]*F[0] + F1[1][1]*F[1] )
  557.             else: break
  558.             i += 1
  559.  
  560.         return ta, tb          
  561.  
  562.  
  563.     def recursion(a,b, ta0,ta1,tb0,tb1, depth_a,depth_b) :
  564.         global bezier_intersection_recursive_result
  565.         if a==b :
  566.             bezier_intersection_recursive_result += [[ta0,tb0,ta1,tb1,"Overlap"]]
  567.             return
  568.         tam, tbm = (ta0+ta1)/2, (tb0+tb1)/2
  569.         if depth_a>0 and depth_b>0 :
  570.             a1,a2 = bez_split(a,0.5)
  571.             b1,b2 = bez_split(b,0.5)
  572.             if bez_bounds_intersect(a1,b1) : recursion(a1,b1, ta0,tam,tb0,tbm, depth_a-1,depth_b-1)
  573.             if bez_bounds_intersect(a2,b1) : recursion(a2,b1, tam,ta1,tb0,tbm, depth_a-1,depth_b-1)
  574.             if bez_bounds_intersect(a1,b2) : recursion(a1,b2, ta0,tam,tbm,tb1, depth_a-1,depth_b-1)
  575.             if bez_bounds_intersect(a2,b2) : recursion(a2,b2, tam,ta1,tbm,tb1, depth_a-1,depth_b-1)
  576.         elif depth_a>0 :
  577.             a1,a2 = bez_split(a,0.5)
  578.             if bez_bounds_intersect(a1,b) : recursion(a1,b, ta0,tam,tb0,tb1, depth_a-1,depth_b)
  579.             if bez_bounds_intersect(a2,b) : recursion(a2,b, tam,ta1,tb0,tb1, depth_a-1,depth_b)
  580.         elif depth_b>0 :
  581.             b1,b2 = bez_split(b,0.5)
  582.             if bez_bounds_intersect(a,b1) : recursion(a,b1, ta0,ta1,tb0,tbm, depth_a,depth_b-1)
  583.             if bez_bounds_intersect(a,b2) : recursion(a,b2, ta0,ta1,tbm,tb1, depth_a,depth_b-1)
  584.         else : # Both segments have been subdevided enougth. Let's get some intersections :).
  585.             intersection, t1, t2 = straight_segments_intersection([a[0]]+[a[3]],[b[0]]+[b[3]])
  586.             if intersection :
  587.                 if intersection == "Overlap" :
  588.                     t1 = ( max(0,min(1,t1[0]))+max(0,min(1,t1[1])) )/2
  589.                     t2 = ( max(0,min(1,t2[0]))+max(0,min(1,t2[1])) )/2
  590.                 bezier_intersection_recursive_result += [[ta0+t1*(ta1-ta0),tb0+t2*(tb1-tb0)]]
  591.  
  592.     global bezier_intersection_recursive_result
  593.     bezier_intersection_recursive_result = []
  594.     recursion(a,b,0.,1.,0.,1.,intersection_recursion_depth,intersection_recursion_depth)
  595.     intersections = bezier_intersection_recursive_result
  596.     for i in range(len(intersections)) :           
  597.         if len(intersections[i])<5 or intersections[i][4] != "Overlap" :
  598.             intersections[i] = polish_intersection(a,b,intersections[i][0],intersections[i][1])
  599.     return intersections
  600.  
  601.  
  602. def csp_segments_true_intersection(sp1,sp2,sp3,sp4) :
  603.     intersections = csp_segments_intersection(sp1,sp2,sp3,sp4)
  604.     res = []
  605.     for intersection in intersections :
  606.         if (
  607.                 (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) )
  608.              or ( 0<=intersection[0]<=1 and 0<=intersection[1]<=1 )
  609.             ) :
  610.             res += [intersection]
  611.     return res
  612.  
  613.  
  614. def csp_get_t_at_curvature(sp1,sp2,c, sample_points = 16):
  615.     # returns a list containning [t1,t2,t3,...,tn], 0<=ti<=1...
  616.     if sample_points < 2 : sample_points = 2
  617.     tolerance = .0000000001
  618.     res = []
  619.     ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
  620.     for k in range(sample_points) :
  621.         t = float(k)/(sample_points-1)
  622.         i, F = 0, 1e100
  623.         while i<2 or abs(F)>tolerance and i<17 :
  624.             try : # some numerical calculation could exceed the limits
  625.                 t2 = t*t
  626.                 #slopes...
  627.                 f1x = 3*ax*t2+2*bx*t+cx
  628.                 f1y = 3*ay*t2+2*by*t+cy
  629.                 f2x = 6*ax*t+2*bx
  630.                 f2y = 6*ay*t+2*by
  631.                 f3x = 6*ax
  632.                 f3y = 6*ay
  633.                 d = (f1x**2+f1y**2)**1.5
  634.                 F1 = (
  635.                          (  (f1x*f3y-f3x*f1y)*d - (f1x*f2y-f2x*f1y)*3.*(f2x*f1x+f2y*f1y)*((f1x**2+f1y**2)**.5) )    /
  636.                                 ((f1x**2+f1y**2)**3)
  637.                      )
  638.                 F = (f1x*f2y-f1y*f2x)/d - c
  639.                 t -= F/F1
  640.             except:
  641.                 break
  642.             i += 1
  643.         if 0<=t<=1 and F<=tolerance:
  644.             if len(res) == 0 :
  645.                 res.append(t)
  646.             for i in res :
  647.                 if abs(t-i)<=0.001 :
  648.                     break
  649.             if not abs(t-i)<=0.001 :
  650.                 res.append(t)
  651.     return res 
  652.        
  653.    
  654. def csp_max_curvature(sp1,sp2):
  655.     ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
  656.     tolerance = .0001
  657.     F = 0.
  658.     i = 0
  659.     while i<2 or F-Flast<tolerance and i<10 :
  660.         t = .5
  661.         f1x = 3*ax*t**2 + 2*bx*t + cx
  662.         f1y = 3*ay*t**2 + 2*by*t + cy
  663.         f2x = 6*ax*t + 2*bx
  664.         f2y = 6*ay*t + 2*by
  665.         f3x = 6*ax
  666.         f3y = 6*ay
  667.         d = pow(f1x**2+f1y**2,1.5)
  668.         if d != 0 :
  669.             Flast = F
  670.             F = (f1x*f2y-f1y*f2x)/d
  671.             F1 =    (
  672.                          (  d*(f1x*f3y-f3x*f1y) - (f1x*f2y-f2x*f1y)*3.*(f2x*f1x+f2y*f1y)*pow(f1x**2+f1y**2,.5) )    /
  673.                                 (f1x**2+f1y**2)**3
  674.                     )
  675.             i+=1   
  676.             if F1!=0:
  677.                 t -= F/F1
  678.             else:
  679.                 break
  680.         else: break
  681.     return t           
  682.    
  683.  
  684. def csp_curvature_at_t(sp1,sp2,t, depth = 3) :
  685.     ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize(csp_segment_to_bez(sp1,sp2))
  686.    
  687.     #curvature = (x'y''-y'x'') / (x'^2+y'^2)^1.5
  688.    
  689.     f1x = 3*ax*t**2 + 2*bx*t + cx
  690.     f1y = 3*ay*t**2 + 2*by*t + cy
  691.     f2x = 6*ax*t + 2*bx
  692.     f2y = 6*ay*t + 2*by
  693.     d = (f1x**2+f1y**2)**1.5
  694.     if d != 0 :
  695.         return (f1x*f2y-f1y*f2x)/d
  696.     else :
  697.         t1 = f1x*f2y-f1y*f2x
  698.         if t1 > 0 : return 1e100
  699.         if t1 < 0 : return -1e100
  700.         # Use the Lapitals rule to solve 0/0 problem for 2 times...
  701.         t1 = 2*(bx*ay-ax*by)*t+(ay*cx-ax*cy)
  702.         if t1 > 0 : return 1e100
  703.         if t1 < 0 : return -1e100
  704.         t1 = bx*ay-ax*by
  705.         if t1 > 0 : return 1e100
  706.         if t1 < 0 : return -1e100
  707.         if depth>0 :
  708.             # little hack ;^) hope it wont influence anything...
  709.             return csp_curvature_at_t(sp1,sp2,t*1.004, depth-1)
  710.         return 1e100
  711.  
  712.        
  713. def csp_curvature_radius_at_t(sp1,sp2,t) :
  714.     c = csp_curvature_at_t(sp1,sp2,t)
  715.     if c == 0 : return 1e100
  716.     else: return 1/c
  717.  
  718.  
  719. def csp_special_points(sp1,sp2) :
  720.     # special points = curvature == 0
  721.     ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize((sp1[1],sp1[2],sp2[0],sp2[1]))
  722.     a = 3*ax*by-3*ay*bx
  723.     b = 3*ax*cy-3*cx*ay
  724.     c = bx*cy-cx*by
  725.     roots = cubic_solver(0, a, b, c)   
  726.     res = []
  727.     for i in roots :
  728.         if type(i) is complex and i.imag==0:
  729.             i = i.real
  730.         if type(i) is not complex and 0<=i<=1:
  731.             res.append(i)
  732.     return res
  733.  
  734.    
  735. def csp_subpath_ccw(subpath):
  736.     # Remove all zerro length segments
  737.     s = 0
  738.     #subpath = subpath[:]
  739.     if (P(subpath[-1][1])-P(subpath[0][1])).l2() > 1e-10 :
  740.         subpath[-1][2] = subpath[-1][1]
  741.         subpath[0][0] = subpath[0][1]
  742.         subpath += [ [subpath[0][1],subpath[0][1],subpath[0][1]] ]
  743.     pl = subpath[-1][2]
  744.     for sp1 in subpath:
  745.         for p in sp1 :
  746.             s += (p[0]-pl[0])*(p[1]+pl[1])
  747.             pl = p
  748.     return s<0
  749.  
  750.  
  751. def csp_at_t(sp1,sp2,t):
  752.     ax,bx,cx,dx = sp1[1][0], sp1[2][0], sp2[0][0], sp2[1][0]
  753.     ay,by,cy,dy = sp1[1][1], sp1[2][1], sp2[0][1], sp2[1][1]
  754.  
  755.     x1, y1 = ax+(bx-ax)*t, ay+(by-ay)*t
  756.     x2, y2 = bx+(cx-bx)*t, by+(cy-by)*t
  757.     x3, y3 = cx+(dx-cx)*t, cy+(dy-cy)*t
  758.    
  759.     x4,y4 = x1+(x2-x1)*t, y1+(y2-y1)*t
  760.     x5,y5 = x2+(x3-x2)*t, y2+(y3-y2)*t
  761.    
  762.     x,y = x4+(x5-x4)*t, y4+(y5-y4)*t
  763.     return [x,y]
  764.  
  765. def csp_at_length(sp1,sp2,l=0.5, tolerance = 0.01):
  766.     bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
  767.     t = bezmisc.beziertatlength(bez, l, tolerance)
  768.     return csp_at_t(sp1,sp2,t)
  769.    
  770.  
  771. def csp_splitatlength(sp1, sp2, l = 0.5, tolerance = 0.01):
  772.     bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
  773.     t = bezmisc.beziertatlength(bez, l, tolerance)
  774.     return csp_split(sp1, sp2, t)  
  775.  
  776.    
  777. def cspseglength(sp1,sp2, tolerance = 0.01):
  778.     bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
  779.     return bezmisc.bezierlength(bez, tolerance)
  780.  
  781.  
  782. def csplength(csp):
  783.     total = 0
  784.     lengths = []
  785.     for sp in csp:
  786.         for i in xrange(1,len(sp)):
  787.             l = cspseglength(sp[i-1],sp[i])
  788.             lengths.append(l)
  789.             total += l         
  790.     return lengths, total
  791.  
  792.  
  793. def csp_segments(csp):
  794.     l, seg = 0, [0]
  795.     for sp in csp:
  796.         for i in xrange(1,len(sp)):
  797.             l += cspseglength(sp[i-1],sp[i])
  798.             seg += [ l ]
  799.  
  800.     if l>0 :
  801.         seg = [seg[i]/l for i in xrange(len(seg))]
  802.     return seg,l
  803.  
  804.  
  805. def rebuild_csp (csp, segs, s=None):
  806.     # rebuild_csp() adds to csp control points making it's segments looks like segs
  807.     if s==None : s, l = csp_segments(csp)
  808.    
  809.     if len(s)>len(segs) : return None
  810.     segs = segs[:]
  811.     segs.sort()
  812.     for i in xrange(len(s)):
  813.         d = None
  814.         for j in xrange(len(segs)):
  815.             d = min( [abs(s[i]-segs[j]),j], d) if d!=None else [abs(s[i]-segs[j]),j]
  816.         del segs[d[1]]
  817.     for i in xrange(len(segs)):
  818.         for j in xrange(0,len(s)):
  819.             if segs[i]<s[j] : break
  820.         if s[j]-s[j-1] != 0 :
  821.             t = (segs[i] - s[j-1])/(s[j]-s[j-1])
  822.             sp1,sp2,sp3 = csp_split(csp[j-1],csp[j], t)
  823.             csp = csp[:j-1] + [sp1,sp2,sp3] + csp[j+1:]
  824.             s = s[:j] + [ s[j-1]*(1-t)+s[j]*t  ] + s[j:]
  825.     return csp, s
  826.  
  827.  
  828. def csp_slope(sp1,sp2,t):
  829.     bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
  830.     return bezmisc.bezierslopeatt(bez,t)
  831.  
  832.  
  833. def csp_line_intersection(l1,l2,sp1,sp2):
  834.     dd=l1[0]
  835.     cc=l2[0]-l1[0]
  836.     bb=l1[1]
  837.     aa=l2[1]-l1[1]
  838.     if aa==cc==0 : return []
  839.     if aa:
  840.         coef1=cc/aa
  841.         coef2=1
  842.     else:
  843.         coef1=1
  844.         coef2=aa/cc
  845.     bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
  846.     ax,ay,bx,by,cx,cy,x0,y0=bezmisc.bezierparameterize(bez)
  847.     a=coef1*ay-coef2*ax
  848.     b=coef1*by-coef2*bx
  849.     c=coef1*cy-coef2*cx
  850.     d=coef1*(y0-bb)-coef2*(x0-dd)
  851.     roots = cubic_solver(a,b,c,d)
  852.     retval = []
  853.     for i in roots :
  854.         if type(i) is complex and abs(i.imag)<1e-7:
  855.             i = i.real
  856.         if type(i) is not complex and -1e-10<=i<=1.+1e-10:
  857.             retval.append(i)
  858.     return retval
  859.  
  860.  
  861. def csp_split_by_two_points(sp1,sp2,t1,t2) :
  862.     if t1>t2 : t1, t2 = t2, t1
  863.     if t1 == t2 :
  864.         sp1,sp2,sp3 = csp_split(sp1,sp2,t)
  865.         return [sp1,sp2,sp2,sp3]
  866.     elif t1 <= 1e-10 and t2 >= 1.-1e-10 :
  867.         return [sp1,sp1,sp2,sp2]
  868.     elif t1 <= 1e-10:  
  869.         sp1,sp2,sp3 = csp_split(sp1,sp2,t2)
  870.         return [sp1,sp1,sp2,sp3]
  871.     elif t2 >= 1.-1e-10 :
  872.         sp1,sp2,sp3 = csp_split(sp1,sp2,t1)
  873.         return [sp1,sp2,sp3,sp3]
  874.     else:
  875.         sp1,sp2,sp3 = csp_split(sp1,sp2,t1)
  876.         sp2,sp3,sp4 = csp_split(sp2,sp3,(t2-t1)/(1-t1) )
  877.         return [sp1,sp2,sp3,sp4]
  878.  
  879. def csp_seg_split(sp1,sp2, points):
  880.     # points is float=t or list [t1, t2, ..., tn]
  881.     if type(points) is float :
  882.         points = [points]
  883.     points.sort()
  884.     res = [sp1,sp2]
  885.     last_t = 0
  886.     for t in points:
  887.         if 1e-10<t<1.-1e-10 :
  888.             sp3,sp4,sp5 = csp_split(res[-2],res[-1], (t-last_t)/(1-last_t))
  889.             last_t = t
  890.             res[-2:] = [sp3,sp4,sp5]
  891.     return res     
  892.  
  893.  
  894. def csp_subpath_split_by_points(subpath, points) :
  895.     # points are [[i,t]...] where i-segment's number
  896.     points.sort()
  897.     points = [[1,0.]] + points + [[len(subpath)-1,1.]]
  898.     parts = []
  899.     for int1,int2 in zip(points,points[1:]) :
  900.         if int1==int2 :
  901.             continue
  902.         if int1[1] == 1. :
  903.             int1[0] += 1
  904.             int1[1] = 0.
  905.         if int1==int2 :
  906.             continue
  907.         if int2[1] == 0. :
  908.             int2[0] -= 1
  909.             int2[1] = 1.
  910.         if int1[0] == 0 and int2[0]==len(subpath)-1:# and small(int1[1]) and small(int2[1]-1) :
  911.             continue
  912.         if int1[0]==int2[0] :   # same segment
  913.             sp = csp_split_by_two_points(subpath[int1[0]-1],subpath[int1[0]],int1[1], int2[1])
  914.             if sp[1]!=sp[2] :
  915.                 parts += [  [sp[1],sp[2]]   ]
  916.         else :
  917.             sp5,sp1,sp2 = csp_split(subpath[int1[0]-1],subpath[int1[0]],int1[1])
  918.             sp3,sp4,sp5 = csp_split(subpath[int2[0]-1],subpath[int2[0]],int2[1])
  919.             if int1[0]==int2[0]-1 :
  920.                 parts += [  [sp1, [sp2[0],sp2[1],sp3[2]], sp4] ]
  921.             else :
  922.                 parts += [ [sp1,sp2]+subpath[int1[0]+1:int2[0]-1]+[sp3,sp4] ]
  923.     return parts
  924.  
  925.  
  926. def arc_from_s_r_n_l(s,r,n,l) :
  927.     if abs(n[0]**2+n[1]**2 - 1) > 1e-10 : n = normalize(n)
  928.     return arc_from_c_s_l([s[0]+n[0]*r, s[1]+n[1]*r],s,l)
  929.  
  930.  
  931. def arc_from_c_s_l(c,s,l) :
  932.     r = point_to_point_d(c,s)
  933.     if r == 0 : return []
  934.     alpha = l/r
  935.     cos_, sin_ = math.cos(alpha), math.sin(alpha)
  936.     e = [ c[0] + (s[0]-c[0])*cos_ - (s[1]-c[1])*sin_, c[1] + (s[0]-c[0])*sin_ + (s[1]-c[1])*cos_]
  937.     n = [c[0]-s[0],c[1]-s[1]]
  938.     slope = rotate_cw(n) if l>0 else rotate_ccw(n)
  939.     return csp_from_arc(s, e, c, r, slope)
  940.  
  941.  
  942. def csp_from_arc(start, end, center, r, slope_st) :
  943.     # Creates csp that approximise specified arc
  944.     r = abs(r)
  945.     alpha = (atan2(end[0]-center[0],end[1]-center[1]) - atan2(start[0]-center[0],start[1]-center[1])) % math.pi2
  946.  
  947.     sectors = int(abs(alpha)*2/math.pi)+1
  948.     alpha_start = atan2(start[0]-center[0],start[1]-center[1])
  949.     cos_,sin_ = math.cos(alpha_start), math.sin(alpha_start)
  950.     k = (4.*math.tan(alpha/sectors/4.)/3.)
  951.     if dot(slope_st , [- sin_*k*r, cos_*k*r]) < 0 :
  952.         if alpha>0 : alpha -= math.pi2
  953.         else: alpha += math.pi2
  954.     if abs(alpha*r)<0.001 :
  955.         return []
  956.  
  957.     sectors = int(abs(alpha)*2/math.pi)+1
  958.     k = (4.*math.tan(alpha/sectors/4.)/3.)
  959.     result = []
  960.     for i in range(sectors+1) :
  961.         cos_,sin_ = math.cos(alpha_start + alpha*i/sectors), math.sin(alpha_start + alpha*i/sectors)
  962.         sp = [ [], [center[0] + cos_*r, center[1] + sin_*r], [] ]
  963.         sp[0] = [sp[1][0] + sin_*k*r, sp[1][1] - cos_*k*r ]
  964.         sp[2] = [sp[1][0] - sin_*k*r, sp[1][1] + cos_*k*r ]
  965.         result += [sp]
  966.     result[0][0] = result[0][1][:]
  967.     result[-1][2] = result[-1][1]
  968.        
  969.     return result
  970.  
  971.  
  972. def point_to_arc_distance(p, arc):
  973.     ###     Distance calculattion from point to arc
  974.     P0,P2,c,a = arc
  975.     dist = None
  976.     p = P(p)
  977.     r = (P0-c).mag()
  978.     if r>0 :
  979.         i = c + (p-c).unit()*r
  980.         alpha = ((i-c).angle() - (P0-c).angle())
  981.         if a*alpha<0:
  982.             if alpha>0: alpha = alpha-math.pi2
  983.             else: alpha = math.pi2+alpha
  984.         if between(alpha,0,a) or min(abs(alpha),abs(alpha-a))<straight_tolerance :
  985.             return (p-i).mag(), [i.x, i.y]
  986.         else :
  987.             d1, d2 = (p-P0).mag(), (p-P2).mag()
  988.             if d1<d2 :
  989.                 return (d1, [P0.x,P0.y])
  990.             else :
  991.                 return (d2, [P2.x,P2.y])
  992.  
  993.  
  994. def csp_to_arc_distance(sp1,sp2, arc1, arc2, tolerance = 0.01 ): # arc = [start,end,center,alpha]
  995.     n, i = 10, 0
  996.     d, d1, dl = (0,(0,0)), (0,(0,0)), 0
  997.     while i<1 or (abs(d1[0]-dl[0])>tolerance and i<4):
  998.         i += 1
  999.         dl = d1*1  
  1000.         for j in range(n+1):
  1001.             t = float(j)/n
  1002.             p = csp_at_t(sp1,sp2,t)
  1003.             d = min(point_to_arc_distance(p,arc1), point_to_arc_distance(p,arc2))
  1004.             d1 = max(d1,d)
  1005.         n=n*2
  1006.     return d1[0]
  1007.  
  1008.  
  1009. def csp_simple_bound_to_point_distance(p, csp):
  1010.     minx,miny,maxx,maxy = None,None,None,None
  1011.     for subpath in csp:
  1012.         for sp in subpath:
  1013.             for p_ in sp:
  1014.                 minx = min(minx,p_[0]) if minx!=None else p_[0]
  1015.                 miny = min(miny,p_[1]) if miny!=None else p_[1]
  1016.                 maxx = max(maxx,p_[0]) if maxx!=None else p_[0]
  1017.                 maxy = max(maxy,p_[1]) if maxy!=None else p_[1]
  1018.     return math.sqrt(max(minx-p[0],p[0]-maxx,0)**2+max(miny-p[1],p[1]-maxy,0)**2)
  1019.  
  1020.  
  1021. def csp_point_inside_bound(sp1, sp2, p):
  1022.     bez = [sp1[1],sp1[2],sp2[0],sp2[1]]
  1023.     x,y = p
  1024.     c = 0
  1025.     #CLT added test of x in range
  1026.     xmin=1e100
  1027.     xmax=-1e100
  1028.     for i in range(4):
  1029.         [x0,y0], [x1,y1] = bez[i-1], bez[i]
  1030.         xmin=min(xmin,x0)
  1031.         xmax=max(xmax,x0)
  1032.         if x0-x1!=0 and (y-y0)*(x1-x0)>=(x-x0)*(y1-y0) and x>min(x0,x1) and x<=max(x0,x1) :
  1033.             c +=1
  1034.     return xmin<=x<=xmax and c%2==0
  1035.  
  1036.  
  1037. def csp_bound_to_point_distance(sp1, sp2, p):
  1038.     if csp_point_inside_bound(sp1, sp2, p) :
  1039.         return 0.
  1040.     bez = csp_segment_to_bez(sp1,sp2)
  1041.     min_dist = 1e100
  1042.     for i in range(0,4):
  1043.         d = point_to_line_segment_distance_2(p, bez[i-1],bez[i])
  1044.         if d <= min_dist : min_dist = d
  1045.     return min_dist
  1046.  
  1047.  
  1048. def line_line_intersect(p1,p2,p3,p4) : # Return only true intersection.
  1049.     if (p1[0]==p2[0] and p1[1]==p2[1]) or (p3[0]==p4[0] and p3[1]==p4[1]) : return False
  1050.     x = (p2[0]-p1[0])*(p4[1]-p3[1]) - (p2[1]-p1[1])*(p4[0]-p3[0])
  1051.     if x==0 : # Lines are parallel
  1052.         if (p3[0]-p1[0])*(p2[1]-p1[1]) == (p3[1]-p1[1])*(p2[0]-p1[0]) :
  1053.             if p3[0]!=p4[0] :
  1054.                 t11 = (p1[0]-p3[0])/(p4[0]-p3[0])
  1055.                 t12 = (p2[0]-p3[0])/(p4[0]-p3[0])
  1056.                 t21 = (p3[0]-p1[0])/(p2[0]-p1[0])
  1057.                 t22 = (p4[0]-p1[0])/(p2[0]-p1[0])
  1058.             else:
  1059.                 t11 = (p1[1]-p3[1])/(p4[1]-p3[1])
  1060.                 t12 = (p2[1]-p3[1])/(p4[1]-p3[1])
  1061.                 t21 = (p3[1]-p1[1])/(p2[1]-p1[1])
  1062.                 t22 = (p4[1]-p1[1])/(p2[1]-p1[1])
  1063.             return ("Overlap" if (0<=t11<=1 or 0<=t12<=1) and (0<=t21<=1 or 0<=t22<=1) else False)
  1064.         else: return False 
  1065.     else :
  1066.         return (
  1067.                     0<=((p4[0]-p3[0])*(p1[1]-p3[1]) - (p4[1]-p3[1])*(p1[0]-p3[0]))/x<=1 and
  1068.                     0<=((p2[0]-p1[0])*(p1[1]-p3[1]) - (p2[1]-p1[1])*(p1[0]-p3[0]))/x<=1 )
  1069.                    
  1070.                    
  1071. def line_line_intersection_points(p1,p2,p3,p4) : # Return only points [ (x,y) ]
  1072.     if (p1[0]==p2[0] and p1[1]==p2[1]) or (p3[0]==p4[0] and p3[1]==p4[1]) : return []
  1073.     x = (p2[0]-p1[0])*(p4[1]-p3[1]) - (p2[1]-p1[1])*(p4[0]-p3[0])
  1074.     if x==0 : # Lines are parallel
  1075.         if (p3[0]-p1[0])*(p2[1]-p1[1]) == (p3[1]-p1[1])*(p2[0]-p1[0]) :
  1076.             if p3[0]!=p4[0] :
  1077.                 t11 = (p1[0]-p3[0])/(p4[0]-p3[0])
  1078.                 t12 = (p2[0]-p3[0])/(p4[0]-p3[0])
  1079.                 t21 = (p3[0]-p1[0])/(p2[0]-p1[0])
  1080.                 t22 = (p4[0]-p1[0])/(p2[0]-p1[0])
  1081.             else:
  1082.                 t11 = (p1[1]-p3[1])/(p4[1]-p3[1])
  1083.                 t12 = (p2[1]-p3[1])/(p4[1]-p3[1])
  1084.                 t21 = (p3[1]-p1[1])/(p2[1]-p1[1])
  1085.                 t22 = (p4[1]-p1[1])/(p2[1]-p1[1])
  1086.             res = []
  1087.             if (0<=t11<=1 or 0<=t12<=1) and (0<=t21<=1 or 0<=t22<=1) :
  1088.                 if 0<=t11<=1 : res += [p1] 
  1089.                 if 0<=t12<=1 : res += [p2] 
  1090.                 if 0<=t21<=1 : res += [p3] 
  1091.                 if 0<=t22<=1 : res += [p4] 
  1092.             return res
  1093.         else: return []
  1094.     else :
  1095.         t1 = ((p4[0]-p3[0])*(p1[1]-p3[1]) - (p4[1]-p3[1])*(p1[0]-p3[0]))/x
  1096.         t2 = ((p2[0]-p1[0])*(p1[1]-p3[1]) - (p2[1]-p1[1])*(p1[0]-p3[0]))/x
  1097.         if 0<=t1<=1 and 0<=t2<=1 : return [ [p1[0]*(1-t1)+p2[0]*t1, p1[1]*(1-t1)+p2[1]*t1] ]
  1098.         else : return []                   
  1099.  
  1100.  
  1101. def point_to_point_d2(a,b):
  1102.     return (a[0]-b[0])**2 + (a[1]-b[1])**2
  1103.  
  1104.  
  1105. def point_to_point_d(a,b):
  1106.     return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2)
  1107.  
  1108.  
  1109. def point_to_line_segment_distance_2(p1, p2,p3) :
  1110.     # p1 - point, p2,p3 - line segment
  1111.     #draw_pointer(p1)
  1112.     w0 = [p1[0]-p2[0], p1[1]-p2[1]]
  1113.     v = [p3[0]-p2[0], p3[1]-p2[1]]
  1114.     c1 = w0[0]*v[0] + w0[1]*v[1]
  1115.     if c1 <= 0 :
  1116.         return w0[0]*w0[0]+w0[1]*w0[1]
  1117.     c2 = v[0]*v[0] + v[1]*v[1]
  1118.     if c2 <= c1 :
  1119.         return (p1[0]-p3[0])**2 + (p1[1]-p3[1])**2
  1120.     return (p1[0]- p2[0]-v[0]*c1/c2)**2 + (p1[1]- p2[1]-v[1]*c1/c2)
  1121.  
  1122.  
  1123. def line_to_line_distance_2(p1,p2,p3,p4):
  1124.     if line_line_intersect(p1,p2,p3,p4) : return 0
  1125.     return min(
  1126.             point_to_line_segment_distance_2(p1,p3,p4),
  1127.             point_to_line_segment_distance_2(p2,p3,p4),
  1128.             point_to_line_segment_distance_2(p3,p1,p2),
  1129.             point_to_line_segment_distance_2(p4,p1,p2))
  1130.  
  1131.  
  1132. def csp_seg_bound_to_csp_seg_bound_max_min_distance(sp1,sp2,sp3,sp4) :
  1133.     bez1 = csp_segment_to_bez(sp1,sp2)
  1134.     bez2 = csp_segment_to_bez(sp3,sp4)
  1135.     min_dist = 1e100
  1136.     max_dist = 0.
  1137.     for i in range(4) :
  1138.         if csp_point_inside_bound(sp1, sp2, bez2[i]) or csp_point_inside_bound(sp3, sp4, bez1[i]) :
  1139.             min_dist = 0.
  1140.             break  
  1141.     for i in range(4) :
  1142.         for j in range(4) :
  1143.             d = line_to_line_distance_2(bez1[i-1],bez1[i],bez2[j-1],bez2[j])
  1144.             if d < min_dist : min_dist = d
  1145.             d = (bez2[j][0]-bez1[i][0])**2 + (bez2[j][1]-bez1[i][1])**2
  1146.             if max_dist < d : max_dist = d
  1147.     return min_dist, max_dist
  1148.  
  1149.  
  1150. def csp_reverse(csp) :
  1151.     for i in range(len(csp)) :
  1152.         n = []
  1153.         for j in csp[i] :
  1154.             n = [ [j[2][:],j[1][:],j[0][:]] ] + n
  1155.         csp[i] = n[:]
  1156.     return csp         
  1157.  
  1158.  
  1159. def csp_normalized_slope(sp1,sp2,t) :
  1160.     ax,ay,bx,by,cx,cy,dx,dy=bezmisc.bezierparameterize((sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]))
  1161.     if sp1[1]==sp2[1]==sp1[2]==sp2[0] : return [1.,0.]
  1162.     f1x = 3*ax*t*t+2*bx*t+cx
  1163.     f1y = 3*ay*t*t+2*by*t+cy
  1164.     if abs(f1x*f1x+f1y*f1y) > 1e-9 : #LT changed this from 1e-20, which caused problems
  1165.         l = math.sqrt(f1x*f1x+f1y*f1y)
  1166.         return [f1x/l, f1y/l]
  1167.  
  1168.     if t == 0 :
  1169.         f1x = sp2[0][0]-sp1[1][0]
  1170.         f1y = sp2[0][1]-sp1[1][1]
  1171.         if abs(f1x*f1x+f1y*f1y) > 1e-9 : #LT changed this from 1e-20, which caused problems
  1172.             l = math.sqrt(f1x*f1x+f1y*f1y)
  1173.             return [f1x/l, f1y/l]
  1174.         else :
  1175.             f1x = sp2[1][0]-sp1[1][0]
  1176.             f1y = sp2[1][1]-sp1[1][1]
  1177.             if f1x*f1x+f1y*f1y != 0 :
  1178.                 l = math.sqrt(f1x*f1x+f1y*f1y)
  1179.                 return [f1x/l, f1y/l]
  1180.     elif t == 1 :
  1181.         f1x = sp2[1][0]-sp1[2][0]
  1182.         f1y = sp2[1][1]-sp1[2][1]
  1183.         if abs(f1x*f1x+f1y*f1y) > 1e-9 :
  1184.             l = math.sqrt(f1x*f1x+f1y*f1y)
  1185.             return [f1x/l, f1y/l]
  1186.         else :
  1187.             f1x = sp2[1][0]-sp1[1][0]
  1188.             f1y = sp2[1][1]-sp1[1][1]
  1189.             if f1x*f1x+f1y*f1y != 0 :
  1190.                 l = math.sqrt(f1x*f1x+f1y*f1y)
  1191.                 return [f1x/l, f1y/l]
  1192.     else :
  1193.         return [1.,0.]
  1194.  
  1195.                
  1196. def csp_normalized_normal(sp1,sp2,t) :
  1197.     nx,ny = csp_normalized_slope(sp1,sp2,t)
  1198.     return [-ny, nx]
  1199.  
  1200.  
  1201. def csp_parameterize(sp1,sp2):
  1202.     return bezmisc.bezierparameterize(csp_segment_to_bez(sp1,sp2))
  1203.  
  1204.  
  1205. def csp_concat_subpaths(*s):
  1206.    
  1207.     def concat(s1,s2) :
  1208.         if s1 == [] : return s2
  1209.         if s2 == [] : return s1
  1210.         if (s1[-1][1][0]-s2[0][1][0])**2 + (s1[-1][1][1]-s2[0][1][1])**2 > 0.00001 :
  1211.             return s1[:-1]+[ [s1[-1][0],s1[-1][1],s1[-1][1]], [s2[0][1],s2[0][1],s2[0][2]] ] + s2[1:]      
  1212.         else :
  1213.             return s1[:-1]+[ [s1[-1][0],s2[0][1],s2[0][2]] ] + s2[1:]      
  1214.            
  1215.     if len(s) == 0 : return []
  1216.     if len(s) ==1 : return s[0]
  1217.     result = s[0]
  1218.     for s1 in s[1:]:
  1219.         result = concat(result,s1)
  1220.     return result
  1221.  
  1222. def csp_subpaths_end_to_start_distance2(s1,s2):
  1223.     return (s1[-1][1][0]-s2[0][1][0])**2 + (s1[-1][1][1]-s2[0][1][1])**2
  1224.  
  1225.  
  1226. def csp_clip_by_line(csp,l1,l2) :
  1227.     result = []
  1228.     for i in range(len(csp)):
  1229.         s = csp[i]
  1230.         intersections = []
  1231.         for j in range(1,len(s)) :
  1232.             intersections += [ [j,int_] for int_ in csp_line_intersection(l1,l2,s[j-1],s[j])]
  1233.         splitted_s = csp_subpath_split_by_points(s, intersections)
  1234.         for s in splitted_s[:] :
  1235.             clip = False
  1236.             for p in csp_true_bounds([s]) :
  1237.                 if (l1[1]-l2[1])*p[0] + (l2[0]-l1[0])*p[1] + (l1[0]*l2[1]-l2[0]*l1[1])<-0.01 :
  1238.                     clip = True
  1239.                     break
  1240.             if clip :
  1241.                 splitted_s.remove(s)
  1242.         result += splitted_s
  1243.     return result
  1244.  
  1245.  
  1246. def csp_subpath_line_to(subpath, points, prepend = False) :
  1247.     # Appends subpath with line or polyline.
  1248.     if len(points)>0 :
  1249.         if not prepend :
  1250.             if len(subpath)>0:
  1251.                 subpath[-1][2] = subpath[-1][1][:]
  1252.             if type(points[0]) == type([1,1]) :
  1253.                 for p in points :
  1254.                     subpath += [ [p[:],p[:],p[:]] ]
  1255.             else:
  1256.                 subpath += [ [points,points,points] ]
  1257.         else :     
  1258.             if len(subpath)>0:
  1259.                 subpath[0][0] = subpath[0][1][:]
  1260.             if type(points[0]) == type([1,1]) :
  1261.                 for p in points :
  1262.                     subpath = [ [p[:],p[:],p[:]] ] + subpath
  1263.             else:
  1264.                 subpath = [ [points,points,points] ] + subpath
  1265.     return subpath
  1266.  
  1267.    
  1268. def csp_join_subpaths(csp) :
  1269.     result = csp[:]
  1270.     done_smf = True
  1271.     joined_result = []
  1272.     while done_smf :
  1273.         done_smf = False
  1274.         while len(result)>0:
  1275.             s1 = result[-1][:]
  1276.             del(result[-1])
  1277.             j = 0
  1278.             joined_smf = False
  1279.             while j<len(joined_result) :
  1280.                 if csp_subpaths_end_to_start_distance2(joined_result[j],s1) <0.000001 :
  1281.                     joined_result[j] = csp_concat_subpaths(joined_result[j],s1)
  1282.                     done_smf = True
  1283.                     joined_smf = True
  1284.                     break              
  1285.                 if csp_subpaths_end_to_start_distance2(s1,joined_result[j]) <0.000001 :
  1286.                     joined_result[j] = csp_concat_subpaths(s1,joined_result[j])
  1287.                     done_smf = True
  1288.                     joined_smf = True
  1289.                     break              
  1290.                 j += 1
  1291.             if not joined_smf : joined_result += [s1[:]]
  1292.         if done_smf :
  1293.             result = joined_result[:]
  1294.             joined_result = []
  1295.     return joined_result
  1296.  
  1297.  
  1298. def triangle_cross(a,b,c):
  1299.     return (a[0]-b[0])*(c[1]-b[1]) - (c[0]-b[0])*(a[1]-b[1])
  1300.    
  1301.  
  1302. def csp_segment_convex_hull(sp1,sp2):
  1303.     a,b,c,d = sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]
  1304.    
  1305.     abc = triangle_cross(a,b,c)
  1306.     abd = triangle_cross(a,b,d)
  1307.     bcd = triangle_cross(b,c,d)
  1308.     cad = triangle_cross(c,a,d)
  1309.     if abc == 0 and abd == 0 : return [min(a,b,c,d), max(a,b,c,d)]
  1310.     if abc == 0 : return [d, min(a,b,c), max(a,b,c)]
  1311.     if abd == 0 : return [c, min(a,b,d), max(a,b,d)]
  1312.     if bcd == 0 : return [a, min(b,c,d), max(b,c,d)]
  1313.     if cad == 0 : return [b, min(c,a,d), max(c,a,d)]
  1314.    
  1315.     m1, m2, m3 = abc*abd>0, abc*bcd>0, abc*cad>0
  1316.     if m1 and m2 and m3 : return [a,b,c]
  1317.     if   m1 and   m2 and not m3 : return [a,b,c,d]
  1318.     if   m1 and not m2 and   m3 : return [a,b,d,c]
  1319.     if not m1 and   m2 and   m3 : return [a,d,b,c]
  1320.     if m1 and not (m2 and m3) : return [a,b,d]
  1321.     if not (m1 and m2) and m3 : return [c,a,d]
  1322.     if not (m1 and m3) and m2 : return [b,c,d]
  1323.    
  1324.     raise ValueError, "csp_segment_convex_hull happend something that shouldnot happen!"   
  1325.  
  1326.    
  1327. ################################################################################
  1328. ###     Bezier additional functions
  1329. ################################################################################
  1330.  
  1331. def bez_bounds_intersect(bez1, bez2) :
  1332.     return bounds_intersect(bez_bound(bez2), bez_bound(bez1))
  1333.  
  1334.  
  1335. def bez_bound(bez) :
  1336.     return [
  1337.                 min(bez[0][0], bez[1][0], bez[2][0], bez[3][0]),
  1338.                 min(bez[0][1], bez[1][1], bez[2][1], bez[3][1]),
  1339.                 max(bez[0][0], bez[1][0], bez[2][0], bez[3][0]),
  1340.                 max(bez[0][1], bez[1][1], bez[2][1], bez[3][1]),
  1341.             ]
  1342.  
  1343.  
  1344. def bounds_intersect(a, b) :
  1345.     return not ( (a[0]>b[2]) or (b[0]>a[2]) or (a[1]>b[3]) or (b[1]>a[3]) )
  1346.  
  1347.  
  1348. def tpoint((x1,y1),(x2,y2),t):
  1349.     return [x1+t*(x2-x1),y1+t*(y2-y1)]
  1350.  
  1351.  
  1352. def bez_to_csp_segment(bez) :
  1353.     return [bez[0],bez[0],bez[1]], [bez[2],bez[3],bez[3]]
  1354.  
  1355.  
  1356. def bez_split(a,t=0.5) :
  1357.      a1 = tpoint(a[0],a[1],t)
  1358.      at = tpoint(a[1],a[2],t)
  1359.      b2 = tpoint(a[2],a[3],t)
  1360.      a2 = tpoint(a1,at,t)
  1361.      b1 = tpoint(b2,at,t)
  1362.      a3 = tpoint(a2,b1,t)
  1363.      return [a[0],a1,a2,a3], [a3,b1,b2,a[3]]
  1364.  
  1365.    
  1366. def bez_at_t(bez,t) :
  1367.     return csp_at_t([bez[0],bez[0],bez[1]],[bez[2],bez[3],bez[3]],t)
  1368.  
  1369.  
  1370. def bez_to_point_distance(bez,p,needed_dist=[0.,1e100]):
  1371.     # returns [d^2,t]
  1372.     return csp_seg_to_point_distance(bez_to_csp_segment(bez),p,needed_dist)
  1373.  
  1374.  
  1375. def bez_normalized_slope(bez,t):
  1376.     return csp_normalized_slope([bez[0],bez[0],bez[1]], [bez[2],bez[3],bez[3]],t)  
  1377.  
  1378. ################################################################################
  1379. ### Some vector functions
  1380. ################################################################################
  1381.    
  1382. def normalize((x,y)) :
  1383.     l = math.sqrt(x**2+y**2)
  1384.     if l == 0 : return [0.,0.]
  1385.     else :      return [x/l, y/l]
  1386.  
  1387.  
  1388. def cross(a,b) :
  1389.     return a[1] * b[0] - a[0] * b[1]
  1390.  
  1391.  
  1392. def dot(a,b) :
  1393.     return a[0] * b[0] + a[1] * b[1]
  1394.  
  1395.  
  1396. def rotate_ccw(d) :
  1397.     return [-d[1],d[0]]
  1398.  
  1399. def rotate_cw(d) :
  1400.     return [d[1],-d[0]]
  1401.  
  1402.  
  1403. def vectors_ccw(a,b):
  1404.     return a[0]*b[1]-b[0]*a[1] < 0
  1405.  
  1406. def vector_add(a,b) :
  1407.     return [a[0]+b[0],a[1]+b[1]]
  1408.  
  1409. def vector_mul(a,b) :
  1410.     return [a[0]*b,a[1]*b]
  1411.  
  1412.  
  1413. def vector_from_to_length(a,b):
  1414.     return math.sqrt((a[0]-b[0])*(a[0]-b[0]) + (a[1]-b[1])*(a[1]-b[1]))
  1415.  
  1416. ################################################################################
  1417. ### Common functions
  1418. ################################################################################
  1419.  
  1420. def matrix_mul(a,b) :
  1421.     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))]
  1422.     try :
  1423.         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))]
  1424.     except :
  1425.         return None
  1426.  
  1427.  
  1428. def transpose(a) :
  1429.     try :
  1430.         return [ [ a[i][j] for i in range(len(a)) ] for j in range(len(a[0])) ]
  1431.     except :
  1432.         return None
  1433.  
  1434.  
  1435. def det_3x3(a):
  1436.     return float(
  1437.         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]
  1438.         - 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]
  1439.         )
  1440.  
  1441.  
  1442. def inv_3x3(a): # invert matrix 3x3
  1443.     det = det_3x3(a)
  1444.     if det==0: return None
  1445.     return  [
  1446.         [ (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 ],
  1447.         [ -(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 ],
  1448.         [ (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 ]
  1449.     ]
  1450.  
  1451.  
  1452. def inv_2x2(a): # invert matrix 2x2
  1453.     det = a[0][0]*a[1][1] - a[1][0]*a[0][1]
  1454.     if det==0: return None
  1455.     return [
  1456.             [a[1][1]/det, -a[0][1]/det],
  1457.             [-a[1][0]/det, a[0][0]/det]
  1458.             ]
  1459.  
  1460.  
  1461. def small(a) :
  1462.     global small_tolerance
  1463.     return abs(a)<small_tolerance
  1464.  
  1465.                    
  1466. def atan2(*arg):   
  1467.     if len(arg)==1 and ( type(arg[0]) == type([0.,0.]) or type(arg[0])==type((0.,0.)) ) :
  1468.         return (math.pi/2 - math.atan2(arg[0][0], arg[0][1]) ) % math.pi2
  1469.     elif len(arg)==2 :
  1470.        
  1471.         return (math.pi/2 - math.atan2(arg[0],arg[1]) ) % math.pi2
  1472.     else :
  1473.         raise ValueError, "Bad argumets for atan! (%s)" % arg
  1474.  
  1475. def get_text(node) :
  1476.     value = None
  1477.     if node.text!=None : value = value +"\n" + node.text if value != None else node.text
  1478.     for k in node :
  1479.         if k.tag == inkex.addNS('tspan','svg'):
  1480.             if k.text!=None : value = value +"\n" + k.text if value != None else k.text
  1481.     return value
  1482.  
  1483.  
  1484.  
  1485. def draw_text(text,x,y, group = None, style = None, font_size = 10, gcodetools_tag = None) :
  1486.     if style == None :
  1487.         style = "font-family:DejaVu Sans;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:DejaVu Sans;fill:#000000;fill-opacity:1;stroke:none;"
  1488.     style += "font-size:%fpx;"%font_size
  1489.     attributes = {          'x':    str(x),
  1490.                             inkex.addNS("space","xml"):"preserve",
  1491.                             'y':    str(y),
  1492.                             'style' : style
  1493.                         }
  1494.     if gcodetools_tag!=None :
  1495.         attributes["gcodetools"] = str(gcodetools_tag)
  1496.  
  1497.     if group == None:
  1498.         group = options.doc_root
  1499.  
  1500.     t = inkex.etree.SubElement( group, inkex.addNS('text','svg'), attributes)
  1501.     text = str(text).split("\n")
  1502.     for s in text :
  1503.         span = inkex.etree.SubElement( t, inkex.addNS('tspan','svg'),
  1504.                         {
  1505.                             'x':    str(x),
  1506.                             'y':    str(y),
  1507.                             inkex.addNS("role","sodipodi"):"line",
  1508.                         })                 
  1509.         y += font_size
  1510.         span.text = s.encode('utf-8')
  1511.  
  1512. def draw_csp(csp, stroke = "#f00", fill = "none", comment = "", width = 0.354, group = None, style = None, gcodetools_tag = None) :
  1513.     if style == None :
  1514.         style = "fill:%s;fill-opacity:1;stroke:%s;stroke-width:%s"%(fill,stroke,width)
  1515.     attributes = {          'd':    cubicsuperpath.formatPath(csp),
  1516.                             'style' : style
  1517.                 }
  1518.     if comment != '':
  1519.         attributes['comment'] = comment
  1520.     if group == None :
  1521.         group = options.doc_root
  1522.        
  1523.     return inkex.etree.SubElement( group, inkex.addNS('path','svg'), attributes)
  1524.    
  1525. def draw_pointer(x,color = "#f00", figure = "cross", group = None, comment = "", fill=None, width = .1, size = 10., text = None, font_size=None, pointer_type=None, attrib = None) :
  1526.     size = size/2
  1527.     if attrib == None : attrib = {}
  1528.     if pointer_type == None:
  1529.         pointer_type = "Pointer"
  1530.     attrib["gcodetools"] = pointer_type
  1531.     if group == None:
  1532.         group = options.self.current_layer
  1533.     if text != None :
  1534.         if font_size == None : font_size = 7
  1535.         group = inkex.etree.SubElement( group, inkex.addNS('g','svg'), {"gcodetools": pointer_type+" group"} )     
  1536.         draw_text(text,x[0]+size*2.2,x[1]-size, group = group, font_size = font_size)
  1537.     if figure == "line" :
  1538.         s = ""
  1539.         for i in range(1,len(x)/2) :
  1540.             s+= " %s, %s " %(x[i*2],x[i*2+1])
  1541.         attrib.update({"d": "M %s,%s L %s"%(x[0],x[1],s), "style":"fill:none;stroke:%s;stroke-width:%f;"%(color,width),"comment":str(comment)})        
  1542.         inkex.etree.SubElement( group, inkex.addNS('path','svg'), attrib)
  1543.     elif figure == "arrow" :
  1544.         if fill == None : fill = "#12b3ff"
  1545.         fill_opacity = "0.8"
  1546.         d = "m %s,%s " % (x[0],x[1]) + re.sub("([0-9\-.e]+)",(lambda match: str(float(match.group(1))*size*2.)), "0.88464,-0.40404 c -0.0987,-0.0162 -0.186549,-0.0589 -0.26147,-0.1173 l 0.357342,-0.35625 c 0.04631,-0.039 0.0031,-0.13174 -0.05665,-0.12164 -0.0029,-1.4e-4 -0.0058,-1.4e-4 -0.0087,0 l -2.2e-5,2e-5 c -0.01189,0.004 -0.02257,0.0119 -0.0305,0.0217 l -0.357342,0.35625 c -0.05818,-0.0743 -0.102813,-0.16338 -0.117662,-0.26067 l -0.409636,0.88193 z")
  1547.         attrib.update({"d": d, "style":"fill:%s;stroke:none;fill-opacity:%s;"%(fill,fill_opacity),"comment":str(comment)})         
  1548.         inkex.etree.SubElement( group, inkex.addNS('path','svg'), attrib)
  1549.     else :
  1550.         attrib.update({"d": "m %s,%s l %f,%f %f,%f %f,%f %f,%f , %f,%f"%(x[0],x[1], size,size, -2*size,-2*size, size,size, size,-size, -2*size,2*size ), "style":"fill:none;stroke:%s;stroke-width:%f;"%(color,width),"comment":str(comment)})
  1551.         inkex.etree.SubElement( group, inkex.addNS('path','svg'), attrib)
  1552.  
  1553.  
  1554. def straight_segments_intersection(a,b, true_intersection = True) : # (True intersection means check ta and tb are in [0,1])
  1555.     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]
  1556.     if (ax==bx and ay==by) or (cx==dx and cy==dy) : return False, 0, 0
  1557.     if (bx-ax)*(dy-cy)-(by-ay)*(dx-cx)==0 : # Lines are parallel
  1558.         ta = (ax-cx)/(dx-cx) if cx!=dx else (ay-cy)/(dy-cy)
  1559.         tb = (bx-cx)/(dx-cx) if cx!=dx else (by-cy)/(dy-cy)
  1560.         tc = (cx-ax)/(bx-ax) if ax!=bx else (cy-ay)/(by-ay)
  1561.         td = (dx-ax)/(bx-ax) if ax!=bx else (dy-ay)/(by-ay)
  1562.         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)
  1563.     else :
  1564.         ta = ( (ay-cy)*(dx-cx)-(ax-cx)*(dy-cy) ) / ( (bx-ax)*(dy-cy)-(by-ay)*(dx-cx) )
  1565.         tb = ( ax-cx+ta*(bx-ax) ) / (dx-cx) if dx!=cx else ( ay-cy+ta*(by-ay) ) / (dy-cy)
  1566.         return (0<=ta<=1 and 0<=tb<=1 or not true_intersection), ta, tb
  1567.    
  1568.    
  1569.  
  1570. def isnan(x): return type(x) is float and x != x
  1571.  
  1572. def isinf(x): inf = 1e5000; return x == inf or x == -inf
  1573.  
  1574. def between(c,x,y):
  1575.         return x-straight_tolerance<=c<=y+straight_tolerance or y-straight_tolerance<=c<=x+straight_tolerance
  1576.  
  1577. def cubic_solver_real(a,b,c,d):
  1578.     # returns only real roots of a cubic equation.
  1579.     roots = cubic_solver(a,b,c,d)
  1580.     res = []
  1581.     for root in roots :
  1582.         if type(root) is complex
  1583.             if -1e-10<root.imag<1e-10 :
  1584.                 res.append(root.real)
  1585.         else :
  1586.             res.append(root)
  1587.     return res
  1588.    
  1589.    
  1590. def cubic_solver(a,b,c,d)
  1591.     if a!=0:
  1592.         #   Monics formula see http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots
  1593.         a,b,c = (b/a, c/a, d/a)
  1594.         m = 2*a**3 - 9*a*b + 27*c
  1595.         k = a**2 - 3*b
  1596.         n = m**2 - 4*k**3
  1597.         w1 = -.5 + .5*cmath.sqrt(3)*1j
  1598.         w2 = -.5 - .5*cmath.sqrt(3)*1j
  1599.         if n>=0 :
  1600.             t = m+math.sqrt(n)
  1601.             m1 = pow(t/2,1./3) if t>=0 else -pow(-t/2,1./3)
  1602.             t = m-math.sqrt(n)
  1603.             n1 = pow(t/2,1./3) if t>=0 else -pow(-t/2,1./3)
  1604.         else :
  1605.             m1 = pow(complex((m+cmath.sqrt(n))/2),1./3)
  1606.             n1 = pow(complex((m-cmath.sqrt(n))/2),1./3)
  1607.         x1 = -1./3 * (a + m1 + n1)
  1608.         x2 = -1./3 * (a + w1*m1 + w2*n1)
  1609.         x3 = -1./3 * (a + w2*m1 + w1*n1)
  1610.         return [x1,x2,x3]
  1611.     elif b!=0:
  1612.         det = c**2-4*b*d
  1613.         if det>0 :
  1614.             return [(-c+math.sqrt(det))/(2*b),(-c-math.sqrt(det))/(2*b)]
  1615.         elif d == 0 :
  1616.             return [-c/(b*b)]  
  1617.         else :
  1618.             return [(-c+cmath.sqrt(det))/(2*b),(-c-cmath.sqrt(det))/(2*b)]
  1619.     elif c!=0 :
  1620.         return [-d/c]
  1621.     else : return []
  1622.  
  1623.  
  1624. ################################################################################
  1625. ###     print_ prints any arguments into specified log file
  1626. ################################################################################
  1627.  
  1628. def print_(*arg):
  1629.     f = open(options.log_filename,"a")
  1630.     for s in arg :
  1631.         s = str(unicode(s).encode('unicode_escape'))+" "
  1632.         f.write( s )
  1633.     f.write("\n")
  1634.     f.close()
  1635.  
  1636.  
  1637. ################################################################################
  1638. ###     Point (x,y) operations
  1639. ################################################################################
  1640. class P:
  1641.     def __init__(self, x, y=None):
  1642.         if not y==None:
  1643.             self.x, self.y = float(x), float(y)
  1644.         else:
  1645.             self.x, self.y = float(x[0]), float(x[1])
  1646.     def __add__(self, other): return P(self.x + other.x, self.y + other.y)
  1647.     def __sub__(self, other): return P(self.x - other.x, self.y - other.y)
  1648.     def __neg__(self): return P(-self.x, -self.y)
  1649.     def __mul__(self, other):
  1650.         if isinstance(other, P):
  1651.             return self.x * other.x + self.y * other.y
  1652.         return P(self.x * other, self.y * other)
  1653.     __rmul__ = __mul__
  1654.     def __div__(self, other): return P(self.x / other, self.y / other)
  1655.     def mag(self): return math.hypot(self.x, self.y)
  1656.     def unit(self):
  1657.         h = self.mag()
  1658.         if h: return self / h
  1659.         else: return P(0,0)
  1660.     def dot(self, other): return self.x * other.x + self.y * other.y
  1661.     def rot(self, theta):
  1662.         c = math.cos(theta)
  1663.         s = math.sin(theta)
  1664.         return P(self.x * c - self.y * s, self.x * s + self.y * c)
  1665.     def angle(self): return math.atan2(self.y, self.x)
  1666.     def __repr__(self): return '%f,%f' % (self.x, self.y)
  1667.     def pr(self): return "%.2f,%.2f" % (self.x, self.y)
  1668.     def to_list(self): return [self.x, self.y] 
  1669.     def ccw(self): return P(-self.y,self.x)
  1670.     def l2(self): return self.x*self.x + self.y*self.y
  1671.  
  1672.  
  1673. class Arc():
  1674.     def __init__(self,st,end,c,a):
  1675.         self.st = P(st)
  1676.         self.end = P(end)
  1677.         self.c = P(c)
  1678.         self.r = (P(st)-P(c)).mag()
  1679.         self.a = ( (self.st-self.c).angle() - (self.end-self.c).angle() ) % math.pi2
  1680.         if a<0 : self.a -= math.pi2
  1681.  
  1682.     def offset(self, r):
  1683.         if self.a>0 :
  1684.             r += self.r
  1685.         else :
  1686.             r = self.r - r
  1687.        
  1688.         if self.r != 0 :
  1689.             self.st = self.c + (self.st-self.c)*r/self.r
  1690.             self.end = self.c + (self.end-self.c)*r/self.r
  1691.             self.r = r
  1692.    
  1693.     def length(self):
  1694.         return abs(self.a*self.r)
  1695.    
  1696.  
  1697.     def draw(self, group, style, layer, transform, num = 0, reverse_angle = 1):
  1698.         st = P(gcodetools.transform(self.st.to_list(), layer, True))
  1699.         c = P(gcodetools.transform(self.c.to_list(), layer, True))
  1700.         a = self.a * reverse_angle
  1701.         r = (st-c)
  1702.         a_st = (math.atan2(r.x,-r.y) - math.pi/2) % (math.pi*2)
  1703.         r = r.mag()
  1704.         if a<0:
  1705.             a_end = a_st+a
  1706.             style = style['biarc%s'%(num%2)]
  1707.         else:
  1708.             a_end = a_st
  1709.             a_st = a_st+a
  1710.             style = style['biarc%s_r'%(num%2)]
  1711.        
  1712.         attr = {
  1713.                 'style': style,
  1714.                  inkex.addNS('cx','sodipodi'):      str(c.x),
  1715.                  inkex.addNS('cy','sodipodi'):      str(c.y),
  1716.                  inkex.addNS('rx','sodipodi'):      str(r),
  1717.                  inkex.addNS('ry','sodipodi'):      str(r),
  1718.                  inkex.addNS('start','sodipodi'):   str(a_st),
  1719.                  inkex.addNS('end','sodipodi'):     str(a_end),
  1720.                  inkex.addNS('open','sodipodi'):    'true',
  1721.                  inkex.addNS('type','sodipodi'):    'arc',
  1722.                  "gcodetools": "Preview",
  1723.                 }  
  1724.         if transform != [] :
  1725.             attr["transform"] = transform  
  1726.         inkex.etree.SubElement( group, inkex.addNS('path','svg'), attr)
  1727.        
  1728.     def intersect(self,b) :
  1729.         return []
  1730.  
  1731.  
  1732. class Line():
  1733.     def __init__(self,st,end):
  1734.         if st.__class__ == P :
  1735.             st = st.to_list()
  1736.         if end.__class__ == P :
  1737.             end = end.to_list()
  1738.         self.st = P(st)
  1739.         self.end = P(end)
  1740.         self.l = self.length()
  1741.         if self.l != 0 :
  1742.             self.n = ((self.end-self.st)/self.l).ccw()
  1743.         else:
  1744.             self.n = [0,1]
  1745.                
  1746.     def offset(self, r):
  1747.         self.st -= self.n*r
  1748.         self.end -= self.n*r
  1749.        
  1750.     def l2(self): return (self.st-self.end).l2()
  1751.     def length(self): return (self.st-self.end).mag()
  1752.    
  1753.     def draw(self, group, style, layer, transform, num = 0, reverse_angle = 1):
  1754.         st = gcodetools.transform(self.st.to_list(), layer, True)
  1755.         end = gcodetools.transform(self.end.to_list(), layer, True)
  1756.  
  1757.  
  1758.         attr = {    'style': style['line'],
  1759.                     'd':'M %s,%s L %s,%s' % (st[0],st[1],end[0],end[1]),
  1760.                     "gcodetools": "Preview",
  1761.                 }
  1762.         if transform != [] :
  1763.             attr["transform"] = transform      
  1764.         inkex.etree.SubElement( group, inkex.addNS('path','svg'), attr  )
  1765.    
  1766.     def intersect(self,b) :
  1767.         if b.__class__ == Line :
  1768.             if self.l < 10e-8 or b.l < 10e-8 : return []
  1769.             v1 = self.end - self.st
  1770.             v2 = b.end - b.st
  1771.             x = v1.x*v2.y - v2.x*v1.y
  1772.             if x == 0 :
  1773.                 # lines are parallel
  1774.                 res = []
  1775.  
  1776.                 if (self.st.x-b.st.x)*v1.y - (self.st.y-b.st.y)*v1.x == 0:
  1777.                     # lines are the same
  1778.                     if v1.x != 0 :
  1779.                         if 0<=(self.st.x-b.st.x)/v2.x<=1 : res.append(self.st)
  1780.                         if 0<=(self.end.x-b.st.x)/v2.x<=1 : res.append(self.end)
  1781.                         if 0<=(b.st.x-self.st.x)/v1.x<=1 : res.append(b.st)
  1782.                         if 0<=(b.end.x-b.st.x)/v1.x<=1 : res.append(b.end)
  1783.                     else :
  1784.                         if 0<=(self.st.y-b.st.y)/v2.y<=1 : res.append(self.st)
  1785.                         if 0<=(self.end.y-b.st.y)/v2.y<=1 : res.append(self.end)
  1786.                         if 0<=(b.st.y-self.st.y)/v1.y<=1 : res.append(b.st)
  1787.                         if 0<=(b.end.y-b.st.y)/v1.y<=1 : res.append(b.end)
  1788.                 return res
  1789.             else :
  1790.                 t1 = ( -v1.x*(b.end.y-self.end.y) + v1.y*(b.end.x-self.end.x) ) / x
  1791.                 t2 = ( -v1.y*(self.st.x-b.st.x) + v1.x*(self.st.y-b.st.y) ) / x
  1792.  
  1793.                 gcodetools.error((x,t1,t2), "warning")
  1794.                 if 0<=t1<=1 and 0<=t2<=1 : return [ self.st+v1*t1 ]
  1795.                 else : return []                   
  1796.         else: return []
  1797.            
  1798.            
  1799.    
  1800.    
  1801. class Biarc:
  1802.     def __init__(self, items=None):
  1803.         if items == None :
  1804.             self.items = []
  1805.         else:  
  1806.             self.items = items
  1807.            
  1808.     def l(self) :
  1809.         return sum([i.length() for i in items])
  1810.    
  1811.     def close(self) :
  1812.         for subitems in self.items:
  1813.             if (subitems[0].st-subitems[-1].end).l2()>10e-16 :
  1814.                 subitems.append(Line(subitems[-1].end,subitems[0].st))
  1815.    
  1816.     def offset(self,r) :
  1817.         # offset each element
  1818.         self.close()
  1819.         for subitems in self.items :
  1820.             for item in subitems :
  1821.                 item.offset(r)
  1822.         self.connect(r)
  1823.        
  1824.     def connect(self, r) :             
  1825.         for subitems in self.items :
  1826.             for a,b in zip(subitems, subitems[1:]) :
  1827.                 i = a.intersect(b)
  1828.                 for p in i :
  1829.                     draw_pointer(p.to_list())
  1830.                
  1831.                        
  1832.                
  1833.                        
  1834.     def clip_offset(self):
  1835.         pass   
  1836.        
  1837.     def draw(self, layer, group=None, style=styles["biarc_style"]):
  1838.         global gcodetools
  1839.         gcodetools.set_markers()
  1840.  
  1841.         for i in [0,1]:
  1842.             style['biarc%s_r'%i] = simplestyle.parseStyle(style['biarc%s'%i])
  1843.             style['biarc%s_r'%i]["marker-start"] = "url(#DrawCurveMarker_r)"
  1844.             del(style['biarc%s_r'%i]["marker-end"])
  1845.             style['biarc%s_r'%i] = simplestyle.formatStyle(style['biarc%s_r'%i])
  1846.        
  1847.         if group==None:
  1848.             if "preview_groups" not in dir(options.self) :
  1849.                 gcodetools.preview_groups = { layer: inkex.etree.SubElement( gcodetools.layers[min(1,len(gcodetools.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} ) }
  1850.             elif layer not in gcodetools.preview_groups :
  1851.                 gcodetools.preview_groups[layer] = inkex.etree.SubElement( gcodetools.layers[min(1,len(gcodetools.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} )
  1852.             group = gcodetools.preview_groups[layer]
  1853.        
  1854.         transform = gcodetools.get_transforms(group)
  1855.         if transform != [] :
  1856.             transform = gcodetools.reverse_transform(transform)
  1857.             transform = simpletransform.formatTransform(transform)
  1858.  
  1859.         a,b,c = [0.,0.], [1.,0.], [0.,1.]
  1860.         k = (b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1])
  1861.         a,b,c = gcodetools.transform(a, layer, True), gcodetools.transform(b, layer, True), gcodetools.transform(c, layer, True)
  1862.         if ((b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1]))*k > 0 : reverse_angle = -1
  1863.         else : reverse_angle = 1
  1864.  
  1865.  
  1866.         num = 0
  1867.         for subitems in self.items :
  1868.             for item in subitems :
  1869.                 num += 1
  1870.                 #if num>1 : break
  1871.                 item.draw(group, style, layer, transform, num, reverse_angle)
  1872.  
  1873.     def from_old_style(self, curve) :
  1874.         #Crve defenitnion [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]]      
  1875.         self.items = []
  1876.         for sp in curve:
  1877.             print_(sp)
  1878.             if sp[1] == 'move':            
  1879.                 self.items.append([])
  1880.             if sp[1] == 'arc':
  1881.                 self.items[-1].append(Arc(sp[0],sp[4],sp[2],sp[3]))
  1882.             if sp[1] == 'line':
  1883.                 self.items[-1].append(Line(sp[0],sp[4]))
  1884.        
  1885.            
  1886.        
  1887.            
  1888.    
  1889. ################################################################################
  1890. ###
  1891. ### Offset function
  1892. ###
  1893. ### This function offsets given cubic super path.
  1894. ### It's based on src/livarot/PathOutline.cpp from Inkscape's source code.
  1895. ###
  1896. ###
  1897. ################################################################################
  1898. def csp_offset(csp, r) :
  1899.     offset_tolerance = 0.05
  1900.     offset_subdivision_depth = 10
  1901.     time_ = time.time()
  1902.     time_start = time_
  1903.     print_("Offset start at %s"% time_)
  1904.     print_("Offset radius %s"% r)
  1905.    
  1906.    
  1907.     def csp_offset_segment(sp1,sp2,r) :
  1908.         result = []
  1909.         t = csp_get_t_at_curvature(sp1,sp2,1/r)
  1910.         if len(t) == 0 : t =[0.,1.]
  1911.         t.sort()
  1912.         if t[0]>.00000001 : t = [0.]+t
  1913.         if t[-1]<.99999999 : t.append(1.)
  1914.         for st,end in zip(t,t[1:]) :   
  1915.             c = csp_curvature_at_t(sp1,sp2,(st+end)/2)
  1916.             sp = csp_split_by_two_points(sp1,sp2,st,end)
  1917.             if sp[1]!=sp[2]:
  1918.                 if (c>1/r and r<0 or c<1/r and r>0) :
  1919.                     offset = offset_segment_recursion(sp[1],sp[2],r, offset_subdivision_depth, offset_tolerance)
  1920.                 else : # This part will be clipped for sure... TODO Optimize it...
  1921.                     offset = offset_segment_recursion(sp[1],sp[2],r, offset_subdivision_depth, offset_tolerance)
  1922.                    
  1923.                 if result==[] :
  1924.                     result = offset[:]
  1925.                 else:
  1926.                     if csp_subpaths_end_to_start_distance2(result,offset)<0.0001 :
  1927.                         result = csp_concat_subpaths(result,offset)
  1928.                     else:
  1929.                        
  1930.                         intersection = csp_get_subapths_last_first_intersection(result,offset)
  1931.                         if intersection != [] :
  1932.                             i,t1,j,t2 = intersection
  1933.                             sp1_,sp2_,sp3_ = csp_split(result[i-1],result[i],t1)
  1934.                             result = result[:i-1] + [ sp1_, sp2_ ]
  1935.                             sp1_,sp2_,sp3_ = csp_split(offset[j-1],offset[j],t2)
  1936.                             result = csp_concat_subpaths( result, [sp2_,sp3_] + offset[j+1:] )
  1937.                         else :
  1938.                             pass # ???                     
  1939.                             #raise ValueError, "Offset curvature clipping error"
  1940.         #draw_csp([result])                        
  1941.         return result                  
  1942.    
  1943.                        
  1944.     def create_offset_segment(sp1,sp2,r) :
  1945.         # See   Gernot Hoffmann "Bezier Curves" p.34 -> 7.1 Bezier Offset Curves
  1946.         p0,p1,p2,p3 = P(sp1[1]),P(sp1[2]),P(sp2[0]),P(sp2[1])
  1947.         s0,s1,s3 = p1-p0,p2-p1,p3-p2
  1948.         n0 = s0.ccw().unit() if s0.l2()!=0 else P(csp_normalized_normal(sp1,sp2,0))
  1949.         n3 = s3.ccw().unit() if s3.l2()!=0 else P(csp_normalized_normal(sp1,sp2,1))
  1950.         n1 = s1.ccw().unit() if s1.l2()!=0 else (n0.unit()+n3.unit()).unit()
  1951.  
  1952.         q0,q3 = p0+r*n0, p3+r*n3
  1953.         c = csp_curvature_at_t(sp1,sp2,0)
  1954.         q1 = q0 + (p1-p0)*(1- (r*c if abs(c)<100 else 0) )
  1955.         c = csp_curvature_at_t(sp1,sp2,1)
  1956.         q2 = q3 + (p2-p3)*(1- (r*c if abs(c)<100 else 0) )
  1957.  
  1958.        
  1959.         return [[q0.to_list(), q0.to_list(), q1.to_list()],[q2.to_list(), q3.to_list(), q3.to_list()]]
  1960.    
  1961.    
  1962.     def csp_get_subapths_last_first_intersection(s1,s2):
  1963.         _break = False
  1964.         for i in range(1,len(s1)) :
  1965.             sp11, sp12 = s1[-i-1], s1[-i]
  1966.             for j in range(1,len(s2)) :
  1967.                 sp21,sp22 = s2[j-1], s2[j]
  1968.                 intersection = csp_segments_true_intersection(sp11,sp12,sp21,sp22)
  1969.                 if intersection != [] :
  1970.                     _break = True
  1971.                     break
  1972.             if _break:break
  1973.         if _break :
  1974.             intersection = max(intersection)
  1975.             return [len(s1)-i,intersection[0], j,intersection[1]]
  1976.         else :
  1977.             return []
  1978.    
  1979.        
  1980.     def csp_join_offsets(prev,next,sp1,sp2,sp1_l,sp2_l,r):
  1981.         if len(next)>1 :
  1982.             if (P(prev[-1][1])-P(next[0][1])).l2()<0.001 :
  1983.                 return prev,[],next
  1984.             intersection = csp_get_subapths_last_first_intersection(prev,next)
  1985.             if intersection != [] :
  1986.                 i,t1,j,t2 = intersection
  1987.                 sp1_,sp2_,sp3_ = csp_split(prev[i-1],prev[i],t1)
  1988.                 sp3_,sp4_,sp5_ = csp_split(next[j-1], next[j],t2)
  1989.                 return prev[:i-1] + [ sp1_, sp2_ ], [], [sp4_,sp5_] + next[j+1:]
  1990.            
  1991.         # Offsets do not intersect... will add an arc...
  1992.         start = (P(csp_at_t(sp1_l,sp2_l,1.)) + r*P(csp_normalized_normal(sp1_l,sp2_l,1.))).to_list()
  1993.         end  = (P(csp_at_t(sp1,sp2,0.)) + r*P(csp_normalized_normal(sp1,sp2,0.))).to_list()
  1994.         arc = csp_from_arc(start, end, sp1[1], r, csp_normalized_slope(sp1_l,sp2_l,1.) )
  1995.         if arc == [] :
  1996.             return prev,[],next
  1997.         else:
  1998.             # Clip prev by arc
  1999.             if csp_subpaths_end_to_start_distance2(prev,arc)>0.00001 :
  2000.                 intersection = csp_get_subapths_last_first_intersection(prev,arc)
  2001.                 if intersection != [] :
  2002.                     i,t1,j,t2 = intersection
  2003.                     sp1_,sp2_,sp3_ = csp_split(prev[i-1],prev[i],t1)
  2004.                     sp3_,sp4_,sp5_ = csp_split(arc[j-1],arc[j],t2)
  2005.                     prev = prev[:i-1] + [ sp1_, sp2_ ]
  2006.                     arc = [sp4_,sp5_] + arc[j+1:]
  2007.                 #else : raise ValueError, "Offset curvature clipping error"
  2008.             # Clip next by arc
  2009.             if next == [] :
  2010.                 return prev,[],arc
  2011.             if csp_subpaths_end_to_start_distance2(arc,next)>0.00001 :
  2012.                 intersection = csp_get_subapths_last_first_intersection(arc,next)
  2013.                 if intersection != [] :
  2014.                     i,t1,j,t2 = intersection
  2015.                     sp1_,sp2_,sp3_ = csp_split(arc[i-1],arc[i],t1)
  2016.                     sp3_,sp4_,sp5_ = csp_split(next[j-1],next[j],t2)
  2017.                     arc = arc[:i-1] + [ sp1_, sp2_ ]
  2018.                     next = [sp4_,sp5_] + next[j+1:]
  2019.                 #else : raise ValueError, "Offset curvature clipping error"
  2020.  
  2021.             return prev,arc,next
  2022.        
  2023.        
  2024.     def offset_segment_recursion(sp1,sp2,r, depth, tolerance) :
  2025.         sp1_r,sp2_r = create_offset_segment(sp1,sp2,r)
  2026.         err = max(
  2027.                 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],
  2028.                 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],
  2029.                 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],
  2030.                 )
  2031.  
  2032.         if err>tolerance**2 and depth>0:
  2033.             #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)
  2034.             if depth > offset_subdivision_depth-2 :
  2035.                 t = csp_max_curvature(sp1,sp2)
  2036.                 t = max(.1,min(.9 ,t))
  2037.             else :
  2038.                 t = .5
  2039.             sp3,sp4,sp5 = csp_split(sp1,sp2,t)
  2040.             r1 = offset_segment_recursion(sp3,sp4,r, depth-1, tolerance)
  2041.             r2 = offset_segment_recursion(sp4,sp5,r, depth-1, tolerance)
  2042.             return r1[:-1]+ [[r1[-1][0],r1[-1][1],r2[0][2]]] + r2[1:]
  2043.         else :
  2044.             #draw_csp([[sp1_r,sp2_r]])
  2045.             #draw_pointer(sp1[1]+sp1_r[1], "#057", "line")
  2046.             #draw_pointer(sp2[1]+sp2_r[1], "#705", "line")
  2047.             return [sp1_r,sp2_r]
  2048.        
  2049.  
  2050.     ############################################################################
  2051.     # Some small definitions
  2052.     ############################################################################
  2053.     csp_len = len(csp)
  2054.  
  2055.     ############################################################################
  2056.     # Prepare the path
  2057.     ############################################################################
  2058.     # Remove all small segments (segment length < 0.001)
  2059.  
  2060.     for i in xrange(len(csp)) :
  2061.         for j in xrange(len(csp[i])) :
  2062.             sp = csp[i][j]
  2063.             if (P(sp[1])-P(sp[0])).mag() < 0.001 :
  2064.                 csp[i][j][0] = sp[1]
  2065.             if (P(sp[2])-P(sp[0])).mag() < 0.001 :
  2066.                 csp[i][j][2] = sp[1]
  2067.     for i in xrange(len(csp)) :
  2068.         for j in xrange(1,len(csp[i])) :
  2069.             if cspseglength(csp[i][j-1], csp[i][j])<0.001 :
  2070.                 csp[i] = csp[i][:j] + csp[i][j+1:]
  2071.         if cspseglength(csp[i][-1],csp[i][0])>0.001 :
  2072.             csp[i][-1][2] = csp[i][-1][1]
  2073.             csp[i]+= [ [csp[i][0][1],csp[i][0][1],csp[i][0][1]] ]
  2074.    
  2075.     # TODO Get rid of self intersections.
  2076.  
  2077.     original_csp = csp[:]
  2078.     # Clip segments which has curvature>1/r. Because their offset will be selfintersecting and very nasty.
  2079.                    
  2080.     print_("Offset prepared the path in %s"%(time.time()-time_))
  2081.     print_("Path length = %s"% sum([len(i)for i in csp] ) )
  2082.     time_ = time.time()
  2083.  
  2084.     ############################################################################
  2085.     # Offset
  2086.     ############################################################################
  2087.     # Create offsets for all segments in the path. And join them together inside each subpath.     
  2088.     unclipped_offset = [[] for i in xrange(csp_len)]
  2089.     offsets_original = [[] for i in xrange(csp_len)]
  2090.     join_points = [[] for i in xrange(csp_len)]
  2091.     intersection = [[] for i in xrange(csp_len)]
  2092.     for i in xrange(csp_len) :
  2093.         subpath = csp[i]
  2094.         subpath_offset = []
  2095.         last_offset_len = 0
  2096.         for sp1,sp2 in zip(subpath, subpath[1:]) :
  2097.             segment_offset = csp_offset_segment(sp1,sp2,r)
  2098.             if subpath_offset == [] :
  2099.                 subpath_offset = segment_offset
  2100.                
  2101.                 prev_l = len(subpath_offset)
  2102.             else :
  2103.                 prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:],segment_offset,sp1,sp2,sp1_l,sp2_l,r)
  2104.                 #draw_csp([prev],"Blue")
  2105.                 #draw_csp([arc],"Magenta")
  2106.                 subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l+1],prev,arc,next)
  2107.                 prev_l = len(next)             
  2108.             sp1_l, sp2_l = sp1[:], sp2[:]
  2109.                        
  2110.         # Join last and first offsets togother to close the curve
  2111.        
  2112.         prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:], subpath_offset[:2], subpath[0], subpath[1], sp1_l,sp2_l, r)
  2113.         subpath_offset[:2] = next[:]
  2114.         subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l+1],prev,arc)
  2115.         #draw_csp([prev],"Blue")
  2116.         #draw_csp([arc],"Red")
  2117.         #draw_csp([next],"Red")
  2118.  
  2119.         # Collect subpath's offset and save it to unclipped offset list.    
  2120.         unclipped_offset[i] = subpath_offset[:]
  2121.  
  2122.         #for k,t in intersection[i]:
  2123.         #   draw_pointer(csp_at_t(subpath_offset[k-1], subpath_offset[k], t))
  2124.            
  2125.     #inkex.etree.SubElement( options.doc_root, inkex.addNS('path','svg'), {"d": cubicsuperpath.formatPath(unclipped_offset), "style":"fill:none;stroke:#0f0;"} )   
  2126.     print_("Offsetted path in %s"%(time.time()-time_))
  2127.     time_ = time.time()
  2128.    
  2129.     #for i in range(len(unclipped_offset)):
  2130.     #   draw_csp([unclipped_offset[i]], color = ["Green","Red","Blue"][i%3], width = .1)
  2131.     #return []
  2132.     ############################################################################
  2133.     # Now to the clipping.
  2134.     ############################################################################
  2135.     # First of all find all intersection's between all segments of all offseted subpaths, including self intersections.
  2136.  
  2137.     #TODO define offset tolerance here
  2138.     global small_tolerance
  2139.     small_tolerance = 0.01
  2140.     summ = 0
  2141.     summ1 = 0    
  2142.     for subpath_i in xrange(csp_len) :
  2143.         for subpath_j in xrange(subpath_i,csp_len) :
  2144.             subpath = unclipped_offset[subpath_i]
  2145.             subpath1 = unclipped_offset[subpath_j]
  2146.             for i in xrange(1,len(subpath)) :
  2147.                 # If subpath_i==subpath_j we are looking for self intersections, so
  2148.                 # we'll need search intersections only for xrange(i,len(subpath1))
  2149.                 for j in ( xrange(i,len(subpath1)) if subpath_i==subpath_j else xrange(len(subpath1))) :
  2150.                     if subpath_i==subpath_j and j==i :
  2151.                         # Find self intersections of a segment
  2152.                         sp1,sp2,sp3 = csp_split(subpath[i-1],subpath[i],.5)
  2153.                         intersections = csp_segments_intersection(sp1,sp2,sp2,sp3)
  2154.                         summ +=1
  2155.                         for t in intersections :
  2156.                             summ1 += 1
  2157.                             if not ( small(t[0]-1) and small(t[1]) ) and 0<=t[0]<=1 and 0<=t[1]<=1 :
  2158.                                 intersection[subpath_i] += [ [i,t[0]/2],[j,t[1]/2+.5] ]
  2159.                     else :
  2160.                         intersections = csp_segments_intersection(subpath[i-1],subpath[i],subpath1[j-1],subpath1[j])
  2161.                         summ +=1
  2162.                         for t in intersections :
  2163.                             summ1 += 1
  2164.                             #TODO tolerance dependence to cpsp_length(t)
  2165.                             if len(t) == 2 and 0<=t[0]<=1 and 0<=t[1]<=1 and not (
  2166.                                     subpath_i==subpath_j and (
  2167.                                     (j-i-1) % (len(subpath)-1) == 0 and small(t[0]-1) and small(t[1]) or
  2168.                                     (i-j-1) % (len(subpath)-1) == 0 and small(t[1]-1) and small(t[0]) ) ) :
  2169.                                 intersection[subpath_i] += [ [i,t[0]] ]
  2170.                                 intersection[subpath_j] += [ [j,t[1]] ]
  2171.                                 #draw_pointer(csp_at_t(subpath[i-1],subpath[i],t[0]),"#f00")
  2172.                                 #print_(t)
  2173.                                 #print_(i,j)
  2174.                             elif len(t)==5 and t[4]=="Overlap":
  2175.                                 intersection[subpath_i] += [ [i,t[0]], [i,t[1]] ]
  2176.                                 intersection[subpath_j] += [ [j,t[1]], [j,t[3]] ]
  2177.  
  2178.     print_("Intersections found in %s"%(time.time()-time_))
  2179.     print_("Examined %s segments"%(summ))
  2180.     print_("found %s intersections"%(summ1))
  2181.     time_ = time.time()
  2182.                                
  2183.     ########################################################################
  2184.     # Split unclipped offset by intersection points into splitted_offset
  2185.     ########################################################################
  2186.     splitted_offset = []
  2187.     for i in xrange(csp_len) :
  2188.         subpath = unclipped_offset[i]
  2189.         if len(intersection[i]) > 0 :
  2190.             parts = csp_subpath_split_by_points(subpath, intersection[i])
  2191.             # Close parts list to close path (The first and the last parts are joined together)    
  2192.             if [1,0.] not in intersection[i] :
  2193.                 parts[0][0][0] = parts[-1][-1][0]
  2194.                 parts[0] = csp_concat_subpaths(parts[-1], parts[0])
  2195.                 splitted_offset += parts[:-1]
  2196.             else:
  2197.                 splitted_offset += parts[:]
  2198.         else :
  2199.             splitted_offset += [subpath[:]]
  2200.    
  2201.     #for i in range(len(splitted_offset)):
  2202.     #   draw_csp([splitted_offset[i]], color = ["Green","Red","Blue"][i%3])
  2203.     print_("Splitted in %s"%(time.time()-time_))
  2204.     time_ = time.time()
  2205.  
  2206.    
  2207.     ########################################################################
  2208.     # Clipping
  2209.     ########################################################################       
  2210.     result = []
  2211.     for subpath_i in range(len(splitted_offset)):
  2212.         clip = False
  2213.         s1 = splitted_offset[subpath_i]
  2214.         for subpath_j in range(len(splitted_offset)):
  2215.             s2 = splitted_offset[subpath_j]
  2216.             if (P(s1[0][1])-P(s2[-1][1])).l2()<0.0001 and ( (subpath_i+1) % len(splitted_offset) != subpath_j ):
  2217.                 if dot(csp_normalized_normal(s2[-2],s2[-1],1.),csp_normalized_slope(s1[0],s1[1],0.))*r<-0.0001 :
  2218.                     clip = True
  2219.                     break
  2220.             if (P(s2[0][1])-P(s1[-1][1])).l2()<0.0001 and ( (subpath_j+1) % len(splitted_offset) != subpath_i ):
  2221.                 if dot(csp_normalized_normal(s2[0],s2[1],0.),csp_normalized_slope(s1[-2],s1[-1],1.))*r>0.0001 :
  2222.                     clip = True
  2223.                     break
  2224.            
  2225.         if not clip :
  2226.             result += [s1[:]]
  2227.         elif options.offset_draw_clippend_path :
  2228.             draw_csp([s1],color="Red",width=.1)
  2229.             draw_pointer( csp_at_t(s2[-2],s2[-1],1.)+
  2230.                 (P(csp_at_t(s2[-2],s2[-1],1.))+ P(csp_normalized_normal(s2[-2],s2[-1],1.))*10).to_list(),"Green", "line" )
  2231.             draw_pointer( csp_at_t(s1[0],s1[1],0.)+
  2232.                 (P(csp_at_t(s1[0],s1[1],0.))+ P(csp_normalized_slope(s1[0],s1[1],0.))*10).to_list(),"Red", "line" )
  2233.                
  2234.     # Now join all together and check closure and orientation of result
  2235.     joined_result = csp_join_subpaths(result)
  2236.     # Check if each subpath from joined_result is closed
  2237.     #draw_csp(joined_result,color="Green",width=1)
  2238.  
  2239.    
  2240.     for s in joined_result[:] :
  2241.         if csp_subpaths_end_to_start_distance2(s,s) > 0.001 :
  2242.             # Remove open parts
  2243.             if options.offset_draw_clippend_path:
  2244.                 draw_csp([s],color="Orange",width=1)
  2245.                 draw_pointer(s[0][1], comment= csp_subpaths_end_to_start_distance2(s,s))
  2246.                 draw_pointer(s[-1][1], comment = csp_subpaths_end_to_start_distance2(s,s))
  2247.             joined_result.remove(s)
  2248.         else :         
  2249.             # Remove small parts
  2250.             minx,miny,maxx,maxy = csp_true_bounds([s])
  2251.             if (minx[0]-maxx[0])**2 + (miny[1]-maxy[1])**2 < 0.1 :
  2252.                 joined_result.remove(s)
  2253.     print_("Clipped and joined path in %s"%(time.time()-time_))
  2254.     time_ = time.time()
  2255.            
  2256.     ########################################################################
  2257.     # Now to the Dummy cliping: remove parts from splitted offset if their
  2258.     # centers are closer to the original path than offset radius.
  2259.     ########################################################################       
  2260.    
  2261.     r1,r2 = ( (0.99*r)**2, (1.01*r)**2 ) if abs(r*.01)<1 else ((abs(r)-1)**2, (abs(r)+1)**2)
  2262.     for s in joined_result[:]:
  2263.         dist = csp_to_point_distance(original_csp, s[int(len(s)/2)][1], dist_bounds = [r1,r2], tolerance = .000001)
  2264.         if not r1 < dist[0] < r2 :
  2265.             joined_result.remove(s)
  2266.             if options.offset_draw_clippend_path:
  2267.                 draw_csp([s], comment = math.sqrt(dist[0]))
  2268.                 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] )
  2269.  
  2270.     print_("-----------------------------")
  2271.     print_("Total offset time %s"%(time.time()-time_start))
  2272.     print_()
  2273.     return joined_result
  2274.    
  2275.    
  2276.        
  2277.  
  2278.  
  2279. ################################################################################
  2280. ###
  2281. ###     Biarc function
  2282. ###
  2283. ###     Calculates biarc approximation of cubic super path segment
  2284. ###     splits segment if needed or approximates it with straight line
  2285. ###
  2286. ################################################################################
  2287. def biarc(sp1, sp2, z1, z2, depth=0):
  2288.     def biarc_split(sp1,sp2, z1, z2, depth):
  2289.         if depth<options.biarc_max_split_depth:
  2290.             sp1,sp2,sp3 = csp_split(sp1,sp2)
  2291.             l1, l2 = cspseglength(sp1,sp2), cspseglength(sp2,sp3)
  2292.             if l1+l2 == 0 : zm = z1
  2293.             else : zm = z1+(z2-z1)*l1/(l1+l2)
  2294.             return biarc(sp1,sp2,z1,zm,depth+1)+biarc(sp2,sp3,zm,z2,depth+1)
  2295.         else: return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
  2296.  
  2297.     P0, P4 = P(sp1[1]), P(sp2[1])
  2298.     TS, TE, v = (P(sp1[2])-P0), -(P(sp2[0])-P4), P0 - P4
  2299.     tsa, tea, va = TS.angle(), TE.angle(), v.angle()
  2300.     if TE.mag()<straight_distance_tolerance and TS.mag()<straight_distance_tolerance:  
  2301.         # Both tangents are zerro - line straight
  2302.         return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
  2303.     if TE.mag() < straight_distance_tolerance:
  2304.         TE = -(TS+v).unit()
  2305.         r = TS.mag()/v.mag()*2
  2306.     elif TS.mag() < straight_distance_tolerance:
  2307.         TS = -(TE+v).unit()
  2308.         r = 1/( TE.mag()/v.mag()*2 )
  2309.     else:  
  2310.         r=TS.mag()/TE.mag()
  2311.     TS, TE = TS.unit(), TE.unit()
  2312.     tang_are_parallel = ((tsa-tea)%math.pi<straight_tolerance or math.pi-(tsa-tea)%math.pi<straight_tolerance )
  2313.     if ( tang_are_parallel and
  2314.                 ((v.mag()<straight_distance_tolerance or TE.mag()<straight_distance_tolerance or TS.mag()<straight_distance_tolerance) or
  2315.                     1-abs(TS*v/(TS.mag()*v.mag()))<straight_tolerance)  ):
  2316.                 # Both tangents are parallel and start and end are the same - line straight
  2317.                 # or one of tangents still smaller then tollerance
  2318.  
  2319.                 # Both tangents and v are parallel - line straight
  2320.         return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
  2321.  
  2322.     c,b,a = v*v, 2*v*(r*TS+TE), 2*r*(TS*TE-1)
  2323.     if v.mag()==0:
  2324.         return biarc_split(sp1, sp2, z1, z2, depth)
  2325.     asmall, bsmall, csmall = abs(a)<10**-10,abs(b)<10**-10,abs(c)<10**-10
  2326.     if      asmall and b!=0:    beta = -c/b
  2327.     elif    csmall and a!=0:    beta = -b/a
  2328.     elif not asmall:     
  2329.         discr = b*b-4*a*c
  2330.         if discr < 0:   raise ValueError, (a,b,c,discr)
  2331.         disq = discr**.5
  2332.         beta1 = (-b - disq) / 2 / a
  2333.         beta2 = (-b + disq) / 2 / a
  2334.         if beta1*beta2 > 0 :    raise ValueError, (a,b,c,disq,beta1,beta2)
  2335.         beta = max(beta1, beta2)
  2336.     elif    asmall and bsmall: 
  2337.         return biarc_split(sp1, sp2, z1, z2, depth)
  2338.     alpha = beta * r
  2339.     ab = alpha + beta
  2340.     P1 = P0 + alpha * TS
  2341.     P3 = P4 - beta * TE
  2342.     P2 = (beta / ab) * P1 + (alpha / ab) * P3
  2343.  
  2344.  
  2345.     def calculate_arc_params(P0,P1,P2):
  2346.         D = (P0+P2)/2
  2347.         if (D-P1).mag()==0: return None, None
  2348.         R = D - ( (D-P0).mag()**2/(D-P1).mag() )*(P1-D).unit()
  2349.         p0a, p1a, p2a = (P0-R).angle()%(2*math.pi), (P1-R).angle()%(2*math.pi), (P2-R).angle()%(2*math.pi)
  2350.         alpha = (p2a - p0a) % (2*math.pi)                  
  2351.         if (p0a<p2a and (p1a<p0a or p2a<p1a))   or  (p2a<p1a<p0a) :
  2352.             alpha = -2*math.pi+alpha
  2353.         if abs(R.x)>1000000 or abs(R.y)>1000000 or (R-P0).mag<options.min_arc_radius**2 :
  2354.             return None, None
  2355.         else
  2356.             return R, alpha
  2357.     R1,a1 = calculate_arc_params(P0,P1,P2)
  2358.     R2,a2 = calculate_arc_params(P2,P3,P4)
  2359.     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]] ]
  2360.    
  2361.     d = csp_to_arc_distance(sp1,sp2, [P0,P2,R1,a1],[P2,P4,R2,a2])
  2362.     if d > options.biarc_tolerance and depth<options.biarc_max_split_depth   : return biarc_split(sp1, sp2, z1, z2, depth)
  2363.     else:
  2364.         if R2.mag()*a2 == 0 : zm = z2
  2365.         else : zm = z1 + (z2-z1)*(abs(R1.mag()*a1))/(abs(R2.mag()*a2)+abs(R1.mag()*a1))
  2366.  
  2367.         l = (P0-P2).l2()
  2368.         if l < EMC_TOLERANCE_EQUAL**2 or l<EMC_TOLERANCE_EQUAL**2 * R1.l2() /100 :
  2369.             # arc should be straight otherwise it could be threated as full circle
  2370.             arc1 = [ sp1[1], 'line', 0, 0, [P2.x,P2.y], [z1,zm] ]
  2371.         else :
  2372.             arc1 = [ sp1[1], 'arc', [R1.x,R1.y], a1, [P2.x,P2.y], [z1,zm] ]
  2373.  
  2374.         l = (P4-P2).l2()
  2375.         if l < EMC_TOLERANCE_EQUAL**2 or l<EMC_TOLERANCE_EQUAL**2 * R2.l2() /100 :
  2376.             # arc should be straight otherwise it could be threated as full circle
  2377.             arc2 = [ [P2.x,P2.y], 'line', 0, 0, [P4.x,P4.y], [zm,z2] ]
  2378.         else :
  2379.             arc2 = [ [P2.x,P2.y], 'arc', [R2.x,R2.y], a2, [P4.x,P4.y], [zm,z2] ]
  2380.        
  2381.         return [ arc1, arc2 ]
  2382.  
  2383.  
  2384. def biarc_curve_segment_length(seg):
  2385.     if seg[1] == "arc" :
  2386.         return math.sqrt((seg[0][0]-seg[2][0])**2+(seg[0][1]-seg[2][1])**2)*seg[3]
  2387.     elif seg[1] == "line" :
  2388.         return math.sqrt((seg[0][0]-seg[4][0])**2+(seg[0][1]-seg[4][1])**2)
  2389.     else:
  2390.         return 0   
  2391.  
  2392.  
  2393. def biarc_curve_clip_at_l(curve, l, clip_type = "strict") :
  2394.     # get first subcurve and ceck it's length
  2395.     subcurve, subcurve_l, moved = [], 0, False
  2396.     for seg in curve:
  2397.         if seg[1] == "move" and moved or seg[1] == "end"
  2398.             break
  2399.         if seg[1] == "move" : moved = True
  2400.         subcurve_l += biarc_curve_segment_length(seg)
  2401.         if seg[1] == "arc" or seg[1] == "line" :
  2402.             subcurve += [seg]
  2403.  
  2404.     if subcurve_l < l and clip_type == "strict" : return []
  2405.     lc = 0
  2406.     if (subcurve[-1][4][0]-subcurve[0][0][0])**2 + (subcurve[-1][4][1]-subcurve[0][0][1])**2 < 10**-7 : subcurve_closed = True
  2407.     i = 0
  2408.     reverse = False
  2409.     while lc<l :
  2410.         seg = subcurve[i]
  2411.         if reverse :
  2412.             if seg[1] == "line" :
  2413.                 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?
  2414.             elif seg[1] == "arc" :
  2415.                 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?
  2416.         ls = biarc_curve_segment_length(seg)
  2417.         if ls != 0 :
  2418.             if l-lc>ls :
  2419.                 res += [seg]
  2420.             else :
  2421.                 if seg[1] == "arc" :
  2422.                     r = math.sqrt((seg[0][0]-seg[2][0])**2+(seg[0][1]-seg[2][1])**2)
  2423.                     x,y = seg[0][0]-seg[2][0], seg[0][1]-seg[2][1]
  2424.                     a = seg[3]/ls*(l-lc)
  2425.                     x,y = x*math.cos(a) - y*math.sin(a), x*math.sin(a) + y*math.cos(a)
  2426.                     x,y = x+seg[2][0], y+seg[2][1]
  2427.                     res += [[ seg[0], "arc", seg[2], a, [x,y], [seg[5][0],seg[5][1]/ls*(l-lc)] ]]
  2428.                 if seg[1] == "line" :
  2429.                     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)] ]]
  2430.         i += 1
  2431.         if i >= len(subcurve) and not subcurve_closed:
  2432.             reverse = not reverse
  2433.         i = i%len(subcurve)
  2434.     return res 
  2435.  
  2436.    
  2437.    
  2438. class Postprocessor():
  2439.     def __init__(self, error_function_handler):
  2440.         self.error = error_function_handler
  2441.         self.functions = {
  2442.                     "remap"     : self.remap,
  2443.                     "remapi"    : self.remapi ,
  2444.                     "scale"     : self.scale,
  2445.                     "move"      : self.move,
  2446.                     "flip"      : self.flip_axis,
  2447.                     "flip_axis" : self.flip_axis,
  2448.                     "round"     : self.round_coordinates,
  2449.                     "parameterize"  : self.parameterize,
  2450.                     "regex"         : self.re_sub_on_gcode_lines
  2451.                     }
  2452.    
  2453.            
  2454.     def process(self,command):
  2455.         command = re.sub(r"\\\\",":#:#:slash:#:#:",command)
  2456.         command = re.sub(r"\\;",":#:#:semicolon:#:#:",command)
  2457.         command = command.split(";")
  2458.         for s in command:
  2459.             s = re.sub(":#:#:slash:#:#:","\\\\",s)
  2460.             s = re.sub(":#:#:semicolon:#:#:","\\;",s)
  2461.             s = s.strip()
  2462.             if s!="" :
  2463.                 self.parse_command(s)      
  2464.            
  2465.    
  2466.     def parse_command(self,command):
  2467.         r = re.match(r"([A-Za-z0-9_]+)\s*\(\s*(.*)\)",command)
  2468.         if not r:
  2469.             self.error("Parse error while postprocessing.\n(Command: '%s')"%(command), "error")
  2470.         function, parameters = r.group(1).lower(),r.group(2)
  2471.         if function in self.functions :
  2472.             print_("Postprocessor: executing function %s(%s)"%(function,parameters))
  2473.             self.functions[function](parameters)
  2474.         else :
  2475.             self.error("Unrecognized function '%s' while postprocessing.\n(Command: '%s')"%(function,command), "error")
  2476.    
  2477.    
  2478.     def re_sub_on_gcode_lines(self, parameters):
  2479.         gcode = self.gcode.split("\n")
  2480.         self.gcode = ""
  2481.         try :
  2482.             for line in gcode :
  2483.                 self.gcode += eval( "re.sub(%s,line)"%parameters) +"\n"
  2484.  
  2485.         except Exception as ex :   
  2486.             self.error("Bad parameters for regexp. They should be as re.sub pattern and replacement parameters! For example: r\"G0(\d)\", r\"G\\1\" \n(Parameters: '%s')\n %s"%(parameters, ex), "error")              
  2487.        
  2488.    
  2489.     def remapi(self,parameters):
  2490.         self.remap(parameters, case_sensitive = True)
  2491.    
  2492.    
  2493.     def remap(self,parameters, case_sensitive = False):
  2494.         # remap parameters should be like "x->y,y->x"
  2495.         parameters = parameters.replace("\,",":#:#:coma:#:#:")
  2496.         parameters = parameters.split(",")
  2497.         pattern, remap = [], []
  2498.         for s in parameters:
  2499.             s = s.replace(":#:#:coma:#:#:","\,")
  2500.             r = re.match("""\s*(\'|\")(.*)\\1\s*->\s*(\'|\")(.*)\\3\s*""",s)
  2501.             if not r :
  2502.                 self.error("Bad parameters for remap.\n(Parameters: '%s')"%(parameters), "error")
  2503.             pattern +=[r.group(2)] 
  2504.             remap +=[r.group(4)]   
  2505.        
  2506.        
  2507.        
  2508.         for i in range(len(pattern)) :
  2509.             if case_sensitive :
  2510.                 self.gcode = ireplace(self.gcode, pattern[i], ":#:#:remap_pattern%s:#:#:"%i )
  2511.             else :
  2512.                 self.gcode = self.gcode.replace(pattern[i], ":#:#:remap_pattern%s:#:#:"%i)
  2513.            
  2514.         for i in range(len(remap)) :
  2515.             self.gcode = self.gcode.replace(":#:#:remap_pattern%s:#:#:"%i, remap[i])
  2516.    
  2517.    
  2518.     def transform(self, move, scale):
  2519.         axis = ["xi","yj","zk","a"]
  2520.         flip = scale[0]*scale[1]*scale[2] < 0
  2521.         gcode = ""
  2522.         warned = []
  2523.         r_scale = scale[0]
  2524.         plane = "g17"
  2525.         for s in self.gcode.split("\n"):
  2526.             # get plane selection:
  2527.             s_wo_comments = re.sub(r"\([^\)]*\)","",s)
  2528.             r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments)
  2529.             if r :
  2530.                 plane = r.group(1).lower()
  2531.                 if plane == "g17" : r_scale = scale[0] # plane XY -> scale x
  2532.                 if plane == "g18" : r_scale = scale[0] # plane XZ -> scale x
  2533.                 if plane == "g19" : r_scale = scale[1] # plane YZ -> scale y
  2534.             # Raise warning if scale factors are not the game for G02 and G03  
  2535.             if plane not in warned:
  2536.                 r = re.search(r"(?i)(G02|G03)", s_wo_comments)
  2537.                 if r :
  2538.                     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")
  2539.                     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")
  2540.                     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")
  2541.                     warned += [plane]
  2542.             # Transform    
  2543.             for i in range(len(axis)) :
  2544.                 if move[i] != 0 or scale[i] != 1:
  2545.                     for a in axis[i] :
  2546.                         r = re.search(r"(?i)("+a+r")\s*(-?)\s*(\d*\.?\d*)", s)
  2547.                         if r and r.group(3)!="":
  2548.                             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)
  2549.             #scale radius R
  2550.             if r_scale != 1 :
  2551.                 r = re.search(r"(?i)(r)\s*(-?\s*(\d*\.?\d*))", s)
  2552.                 if r and r.group(3)!="":
  2553.                     try:
  2554.                         s = re.sub(r"(?i)(r)\s*(-?)\s*(\d*\.?\d*)", r"\1 %f"%( float(r.group(2)+r.group(3))*r_scale ), s)
  2555.                     except:
  2556.                         pass   
  2557.  
  2558.             gcode += s + "\n"
  2559.            
  2560.         self.gcode = gcode
  2561.         if flip :
  2562.             self.remapi("'G02'->'G03', 'G03'->'G02'")
  2563.  
  2564.  
  2565.     def parameterize(self,parameters) :
  2566.         planes = []
  2567.         feeds = {}
  2568.         coords = []
  2569.         gcode = ""
  2570.         coords_def = {"x":"x","y":"y","z":"z","i":"x","j":"y","k":"z","a":"a"}
  2571.         for s in self.gcode.split("\n"):
  2572.             s_wo_comments = re.sub(r"\([^\)]*\)","",s)
  2573.             # get Planes
  2574.             r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments)
  2575.             if r :
  2576.                 plane = r.group(1).lower()
  2577.                 if plane not in planes :
  2578.                     planes += [plane]
  2579.             # get Feeds
  2580.             r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s_wo_comments)
  2581.             if r :
  2582.                 feed = float (r.group(2)+r.group(3))
  2583.                 if feed not in feeds :
  2584.                     feeds[feed] = "#"+str(len(feeds)+20)
  2585.                    
  2586.             #Coordinates
  2587.             for c in "xyzijka" :
  2588.                 r = re.search(r"(?i)("+c+r")\s*(-?)\s*(\d*\.?\d*)", s_wo_comments)
  2589.                 if r :
  2590.                     c = coords_def[r.group(1).lower()]
  2591.                     if c not in coords :
  2592.                         coords += [c]
  2593.         # Add offset parametrization
  2594.         offset = {"x":"#6","y":"#7","z":"#8","a":"#9"}
  2595.         for c in coords:
  2596.             gcode += "%s = 0 (%s axis offset)\n" % (offset[c],c.upper())
  2597.            
  2598.         # Add scale parametrization
  2599.         if planes == [] : planes = ["g17"]
  2600.         if len(planes)>1 : # have G02 and G03 in several planes scale_x = scale_y = scale_z required
  2601.             gcode += "#10 = 1 (Scale factor)\n"
  2602.             scale = {"x":"#10","i":"#10","y":"#10","j":"#10","z":"#10","k":"#10","r":"#10"}
  2603.         else :
  2604.             gcode += "#10 = 1 (%s Scale factor)\n" % ({"g17":"XY","g18":"XZ","g19":"YZ"}[planes[0]])
  2605.             gcode += "#11 = 1 (%s Scale factor)\n" % ({"g17":"Z","g18":"Y","g19":"X"}[planes[0]])
  2606.             scale = {"x":"#10","i":"#10","y":"#10","j":"#10","z":"#10","k":"#10","r":"#10"}
  2607.             if "g17" in planes :
  2608.                 scale["z"] = "#11"             
  2609.                 scale["k"] = "#11"             
  2610.             if "g18" in planes :
  2611.                 scale["y"] = "#11"             
  2612.                 scale["j"] = "#11"             
  2613.             if "g19" in planes :
  2614.                 scale["x"] = "#11"             
  2615.                 scale["i"] = "#11"             
  2616.         # Add a scale
  2617.         if "a" in coords:
  2618.             gcode += "#12 = 1 (A axis scale)\n"
  2619.             scale["a"] = "#12"
  2620.        
  2621.         # Add feed parametrization
  2622.         for f in feeds :
  2623.             gcode += "%s = %f (Feed definition)\n" % (feeds[f],f)
  2624.  
  2625.         # Parameterize Gcode       
  2626.         for s in self.gcode.split("\n"):
  2627.             #feed replace :
  2628.             r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s)
  2629.             if r and len(r.group(3))>0:
  2630.                 s = re.sub(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", "F [%s]"%feeds[float(r.group(2)+r.group(3))], s)
  2631.             #Coords XYZA replace
  2632.             for c in "xyza" :
  2633.                 r = re.search(r"(?i)(("+c+r")\s*(-?)\s*(\d*\.?\d*))", s)
  2634.                 if r and len(r.group(4))>0:
  2635.                     s = re.sub(r"(?i)("+c+r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*%s+%s]"%(scale[c],offset[c]), s)
  2636.  
  2637.             #Coords IJKR replace
  2638.             for c in "ijkr" :
  2639.                 r = re.search(r"(?i)(("+c+r")\s*(-?)\s*(\d*\.?\d*))", s)
  2640.                 if r and len(r.group(4))>0:
  2641.                     s = re.sub(r"(?i)("+c+r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*%s]"%scale[c], s)
  2642.  
  2643.             gcode += s + "\n"
  2644.    
  2645.         self.gcode = gcode 
  2646.  
  2647.     #post processing processor code where a space appears somehow
  2648.     def round_coordinates(self,parameters) :
  2649.         try:
  2650.             round_ = int(parameters)
  2651.         except :   
  2652.             self.error("Bad parameters for round. Round should be an integer! \n(Parameters: '%s')"%(parameters), "error")     
  2653.         gcode = ""
  2654.         for s in self.gcode.split("\n"):
  2655.             for a in "xyzijkaf" :
  2656.                 r = re.search(r"(?i)("+a+r")\s*(-?\s*(\d*\.?\d*))", s)
  2657.                 if r :
  2658.                    
  2659.                     if r.group(2)!="":
  2660.                         s = re.sub(
  2661.                                     r"(?i)("+a+r")\s*(-?)\s*(\d*\.?\d*)",
  2662.                                     (r"\1 %0."+str(round_)+"f" if round_>0 else r"\1 %d")%round(float(r.group(2)),round_),
  2663.                                     s)
  2664.             gcode += s + "\n"
  2665.         self.gcode = gcode
  2666.    
  2667.  
  2668.     def scale(self, parameters):
  2669.         parameters = parameters.split(",")
  2670.         scale = [1.,1.,1.,1.]
  2671.         try :
  2672.             for i in range(len(parameters)) :
  2673.                 if float(parameters[i])==0 :
  2674.                     self.error("Bad parameters for scale. Scale should not be 0 at any axis! \n(Parameters: '%s')"%(parameters), "error")      
  2675.                 scale[i] = float(parameters[i])
  2676.         except :
  2677.             self.error("Bad parameters for scale.\n(Parameters: '%s')"%(parameters), "error")
  2678.         self.transform([0,0,0,0],scale)    
  2679.  
  2680.    
  2681.     def move(self, parameters):
  2682.         parameters = parameters.split(",")
  2683.         move = [0.,0.,0.,0.]
  2684.         try :
  2685.             for i in range(len(parameters)) :
  2686.                 move[i] = float(parameters[i])
  2687.         except :
  2688.             self.error("Bad parameters for move.\n(Parameters: '%s')"%(parameters), "error")
  2689.         self.transform(move,[1.,1.,1.,1.])     
  2690.  
  2691.  
  2692.     def flip_axis(self, parameters):
  2693.         parameters = parameters.lower()
  2694.         axis = {"x":1.,"y":1.,"z":1.,"a":1.}   
  2695.         for p in parameters:
  2696.             if p in [","," ","  ","\r","'",'"'] : continue
  2697.             if p not in ["x","y","z","a"] :
  2698.                 self.error("Bad parameters for flip_axis. Parameter should be string consists of 'xyza' \n(Parameters: '%s')"%(parameters), "error")
  2699.             axis[p] = -axis[p]
  2700.         self.scale("%f,%f,%f,%f"%(axis["x"],axis["y"],axis["z"],axis["a"]))
  2701.  
  2702.    
  2703.            
  2704. ################################################################################
  2705. ###     Polygon class
  2706. ################################################################################
  2707. class Polygon:
  2708.     def __init__(self, polygon=None):
  2709.         self.polygon = [] if polygon==None else polygon[:]
  2710.    
  2711.    
  2712.     def move(self, x, y) :
  2713.         for i in range(len(self.polygon)) :
  2714.             for j in range(len(self.polygon[i])) :
  2715.                 self.polygon[i][j][0] += x
  2716.                 self.polygon[i][j][1] += y
  2717.    
  2718.    
  2719.     def bounds(self) :
  2720.         minx,miny,maxx,maxy = 1e400, 1e400, -1e400, -1e400
  2721.         for poly in self.polygon :
  2722.             for p in poly :
  2723.                 if minx > p[0] : minx = p[0]
  2724.                 if miny > p[1] : miny = p[1]
  2725.                 if maxx < p[0] : maxx = p[0]
  2726.                 if maxy < p[1] : maxy = p[1]
  2727.         return minx*1,miny*1,maxx*1,maxy*1     
  2728.    
  2729.    
  2730.     def width(self):
  2731.         b = self.bounds()
  2732.         return b[2]-b[0]
  2733.    
  2734.    
  2735.     def rotate_(self,sin,cos) :
  2736.         self.polygon = [
  2737.                 [
  2738.                     [point[0]*cos - point[1]*sin,point[0]*sin + point[1]*cos] for point in subpoly
  2739.                 ]          
  2740.                 for subpoly in self.polygon
  2741.             ]
  2742.  
  2743.    
  2744.     def rotate(self, a):
  2745.         cos, sin = math.cos(a), math.sin(a)
  2746.         self.rotate_(sin,cos)
  2747.    
  2748.            
  2749.     def drop_into_direction(self, direction, surface) :
  2750.         # Polygon is a list of simple polygons
  2751.         # Surface is a polygon + line y = 0
  2752.         # Direction is [dx,dy]
  2753.         if len(self.polygon) == 0 or len(self.polygon[0])==0 : return
  2754.         if direction[0]**2 + direction[1]**2 <1e-10 : return
  2755.         direction = normalize(direction)
  2756.         sin,cos = direction[0], -direction[1]
  2757.         self.rotate_(-sin,cos)
  2758.         surface.rotate_(-sin,cos)
  2759.         self.drop_down(surface, zerro_plane = False)
  2760.         self.rotate_(sin,cos)
  2761.         surface.rotate_(sin,cos)
  2762.        
  2763.            
  2764.     def centroid(self):
  2765.         centroids = []
  2766.         sa = 0
  2767.         for poly in self.polygon:
  2768.             cx,cy,a = 0,0,0
  2769.             for i in range(len(poly)):
  2770.                 [x1,y1],[x2,y2] = poly[i-1],poly[i]
  2771.                 cx += (x1+x2)*(x1*y2-x2*y1)
  2772.                 cy += (y1+y2)*(x1*y2-x2*y1)
  2773.                 a += (x1*y2-x2*y1)
  2774.             a *= 3.
  2775.             if abs(a)>0 :
  2776.                 cx /= a
  2777.                 cy /= a
  2778.                 sa += abs(a)
  2779.                 centroids += [ [cx,cy,a] ]
  2780.         if sa == 0 : return [0.,0.]
  2781.         cx,cy = 0.,0.
  2782.         for c in centroids :
  2783.             cx += c[0]*c[2]
  2784.             cy += c[1]*c[2]
  2785.         cx /= sa
  2786.         cy /= sa
  2787.         return [cx,cy]
  2788.  
  2789.            
  2790.     def drop_down(self, surface, zerro_plane = True) :
  2791.         # Polygon is a list of simple polygons
  2792.         # Surface is a polygon + line y = 0
  2793.         # Down means min y (0,-1)
  2794.         if len(self.polygon) == 0 or len(self.polygon[0])==0 : return
  2795.         # Get surface top point
  2796.         top = surface.bounds()[3]
  2797.         if zerro_plane : top = max(0, top)
  2798.         # Get polygon bottom point
  2799.         bottom = self.bounds()[1]
  2800.         self.move(0, top - bottom + 10)    
  2801.         # Now get shortest distance from surface to polygon in positive x=0 direction
  2802.         # Such distance = min(distance(vertex, edge)...) where edge from surface and
  2803.         # vertex from polygon and vice versa...
  2804.         dist = 1e300
  2805.         for poly in surface.polygon :
  2806.             for i in range(len(poly)) :
  2807.                 for poly1 in self.polygon :
  2808.                     for i1 in range(len(poly1)) :
  2809.                         st,end = poly[i-1], poly[i]
  2810.                         vertex = poly1[i1]
  2811.                         if st[0]<=vertex[0]<= end[0] or end[0]<=vertex[0]<=st[0] :
  2812.                             if st[0]==end[0] : d = min(vertex[1]-st[1],vertex[1]-end[1])
  2813.                             else : d = vertex[1] - st[1] - (end[1]-st[1])*(vertex[0]-st[0])/(end[0]-st[0])
  2814.                             if dist > d : dist = d
  2815.                         # and vice versa just change the sign because vertex now under the edge
  2816.                         st,end = poly1[i1-1], poly1[i1]
  2817.                         vertex = poly[i]
  2818.                         if st[0]<=vertex[0]<=end[0] or end[0]<=vertex[0]<=st[0] :
  2819.                             if st[0]==end[0] : d = min(- vertex[1]+st[1],-vertex[1]+end[1])
  2820.                             else : d = - vertex[1] + st[1] + (end[1]-st[1])*(vertex[0]-st[0])/(end[0]-st[0])
  2821.                             if dist > d : dist = d
  2822.        
  2823.         if zerro_plane and dist > 10 + top : dist = 10 + top
  2824.         #print_(dist, top, bottom)
  2825.         #self.draw()
  2826.         self.move(0, -dist)    
  2827.        
  2828.                    
  2829.     def draw(self,color="#075",width=.1, group = None) :
  2830.         csp = [csp_subpath_line_to([],poly+[poly[0]]) for poly in self.polygon]
  2831.         draw_csp( csp, color=color,width=width, group = group)
  2832.            
  2833.            
  2834.    
  2835.     def add(self, add) :
  2836.         if type(add) == type([]) :
  2837.             self.polygon += add[:]
  2838.         else
  2839.             self.polygon += add.polygon[:]
  2840.  
  2841.    
  2842.     def point_inside(self,p) :
  2843.         inside = False
  2844.         for poly in self.polygon :
  2845.             for i in range(len(poly)):
  2846.                 st,end = poly[i-1], poly[i]
  2847.                 if p==st or p==end : return True # point is a vertex = point is on the edge
  2848.                 if st[0]>end[0] : st, end = end, st # This will be needed to check that edge if open only at rigth end
  2849.                 c = (p[1]-st[1])*(end[0]-st[0])-(end[1]-st[1])*(p[0]-st[0])
  2850.                 #print_(c)
  2851.                 if st[0]<=p[0]<end[0] :
  2852.                     if c<0 :
  2853.                         inside = not inside
  2854.                     elif c == 0 : return True # point is on the edge
  2855.                 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
  2856.                     return True
  2857.         return inside          
  2858.  
  2859.    
  2860.     def hull(self) :
  2861.         # Add vertices at all self intersection points.
  2862.         hull = []
  2863.         for i1 in range(len(self.polygon)):
  2864.             poly1 = self.polygon[i1]
  2865.             poly_ = []
  2866.             for j1 in range(len(poly1)):
  2867.                 s, e = poly1[j1-1],poly1[j1]
  2868.                 poly_ += [s]
  2869.                
  2870.                 # Check self intersections
  2871.                 for j2 in range(j1+1,len(poly1)):
  2872.                     s1, e1 = poly1[j2-1],poly1[j2]
  2873.                     int_ = line_line_intersection_points(s,e,s1,e1)
  2874.                     for p in int_ :
  2875.                         if point_to_point_d2(p,s)>0.000001 and point_to_point_d2(p,e)>0.000001 :
  2876.                             poly_ += [p]
  2877.                 # Check self intersections with other polys
  2878.                 for i2 in range(len(self.polygon)):
  2879.                     if i1==i2 : continue
  2880.                     poly2 = self.polygon[i2]
  2881.                     for j2 in range(len(poly2)):
  2882.                         s1, e1 = poly2[j2-1],poly2[j2]
  2883.                         int_ = line_line_intersection_points(s,e,s1,e1)
  2884.                         for p in int_ :
  2885.                             if point_to_point_d2(p,s)>0.000001 and point_to_point_d2(p,e)>0.000001 :
  2886.                                 poly_ += [p]
  2887.             hull += [poly_]
  2888.         # Create the dictionary containing all edges in both directions
  2889.         edges = {}
  2890.         for poly in self.polygon :
  2891.             for i in range(len(poly)):
  2892.                 s,e = tuple(poly[i-1]), tuple(poly[i])
  2893.                 if (point_to_point_d2(e,s)<0.000001) : continue
  2894.                 break_s, break_e = False, False
  2895.                 for p in edges :
  2896.                     if point_to_point_d2(p,s)<0.000001 :
  2897.                         break_s = True
  2898.                         s = p
  2899.                     if point_to_point_d2(p,e)<0.000001 :
  2900.                         break_e = True
  2901.                         e = p
  2902.                     if break_s and break_e : break
  2903.                 l = point_to_point_d(s,e)
  2904.                 if not break_s and not break_e :
  2905.                     edges[s] = [ [s,e,l] ]
  2906.                     edges[e] = [ [e,s,l] ]
  2907.                     #draw_pointer(s+e,"red","line")
  2908.                     #draw_pointer(s+e,"red","line")
  2909.                 else :
  2910.                     if e in edges :
  2911.                         for edge in edges[e]
  2912.                             if point_to_point_d2(edge[1],s)<0.000001 :
  2913.                                 break
  2914.                         if point_to_point_d2(edge[1],s)>0.000001 :
  2915.                             edges[e] += [ [e,s,l] ]
  2916.                             #draw_pointer(s+e,"red","line")
  2917.                            
  2918.                     else :
  2919.                         edges[e] = [ [e,s,l] ]
  2920.                         #draw_pointer(s+e,"green","line")
  2921.                     if s in edges :
  2922.                         for edge in edges[s]
  2923.                             if point_to_point_d2(edge[1],e)<0.000001 :
  2924.                                 break
  2925.                         if point_to_point_d2(edge[1],e)>0.000001 :
  2926.                             edges[s] += [ [s,e, l] ]
  2927.                             #draw_pointer(s+e,"red","line")
  2928.                     else :
  2929.                         edges[s] = [ [s,e,l] ]
  2930.                         #draw_pointer(s+e,"green","line")
  2931.  
  2932.        
  2933.         def angle_quadrant(sin,cos):
  2934.             # 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
  2935.             if sin>0 and cos>=0 : return 1
  2936.             if sin>=0 and cos<0 : return 2
  2937.             if sin<0 and cos<=0 : return 3
  2938.             if sin<=0 and cos>0 : return 4
  2939.            
  2940.        
  2941.         def angle_is_less(sin,cos,sin1,cos1):
  2942.             # 0 = 2*pi is the largest angle
  2943.             if [sin1, cos1] == [0,1] : return True
  2944.             if [sin, cos] == [0,1] : return False
  2945.             if angle_quadrant(sin,cos)>angle_quadrant(sin1,cos1) :
  2946.                 return False
  2947.             if angle_quadrant(sin,cos)<angle_quadrant(sin1,cos1) :
  2948.                 return True
  2949.             if sin>=0 and cos>0 : return sin<sin1
  2950.             if sin>0 and cos<=0 : return sin>sin1
  2951.             if sin<=0 and cos<0 : return sin>sin1
  2952.             if sin<0 and cos>=0 : return sin<sin1
  2953.  
  2954.            
  2955.         def get_closes_edge_by_angle(edges, last):
  2956.             # Last edge is normalized vector of the last edge.
  2957.             min_angle = [0,1]
  2958.             next = last
  2959.             last_edge = [(last[0][0]-last[1][0])/last[2], (last[0][1]-last[1][1])/last[2]]
  2960.             for p in edges:
  2961.                 #draw_pointer(list(p[0])+[p[0][0]+last_edge[0]*40,p[0][1]+last_edge[1]*40], "Red", "line", width=1)
  2962.                 #print_("len(edges)=",len(edges))
  2963.                 cur = [(p[1][0]-p[0][0])/p[2],(p[1][1]-p[0][1])/p[2]]
  2964.                 cos, sin = dot(cur,last_edge), cross(cur,last_edge)
  2965.                 #draw_pointer(list(p[0])+[p[0][0]+cur[0]*40,p[0][1]+cur[1]*40], "Orange", "line", width=1, comment = [sin,cos])
  2966.                 #print_("cos, sin=",cos,sin)
  2967.                 #print_("min_angle_before=",min_angle)
  2968.  
  2969.                 if  angle_is_less(sin,cos,min_angle[0],min_angle[1]) :
  2970.                     min_angle = [sin,cos]
  2971.                     next = p
  2972.                 #print_("min_angle=",min_angle)
  2973.  
  2974.             return next    
  2975.            
  2976.         # Join edges together into new polygon cutting the vertexes inside new polygon
  2977.         self.polygon = []
  2978.         len_edges = sum([len(edges[p]) for p in edges])
  2979.         loops = 0
  2980.        
  2981.         while len(edges)>0 :
  2982.             poly = []
  2983.             if loops > len_edges : raise ValueError, "Hull error"
  2984.             loops+=1
  2985.             # Find left most vertex.
  2986.             start = (1e100,1)
  2987.             for edge in edges :
  2988.                 start = min(start, min(edges[edge]))
  2989.             last = [(start[0][0]-1,start[0][1]),start[0],1]
  2990.             first_run = True
  2991.             loops1 = 0
  2992.             while (last[1]!=start[0] or first_run) :    
  2993.                 first_run = False
  2994.                 if loops1 > len_edges : raise ValueError, "Hull error"
  2995.                 loops1 += 1
  2996.                 next = get_closes_edge_by_angle(edges[last[1]],last)
  2997.                 #draw_pointer(next[0]+next[1],"Green","line", comment=i, width= 1)
  2998.                 #print_(next[0],"-",next[1])
  2999.  
  3000.                 last = next
  3001.                 poly += [ list(last[0]) ]              
  3002.             self.polygon += [ poly ]
  3003.             # Remove all edges that are intersects new poly (any vertex inside new poly)
  3004.             poly_ = Polygon([poly])
  3005.             for p in edges.keys()[:] :
  3006.                 if poly_.point_inside(list(p)) : del edges[p]
  3007.         self.draw(color="Green", width=1)  
  3008.  
  3009.  
  3010. class Arangement_Genetic:
  3011.     # gene = [fittness, order, rotation, xposition]
  3012.     # spieces = [gene]*shapes count
  3013.     # population = [spieces]
  3014.     def __init__(self, polygons, material_width):
  3015.         self.population = []
  3016.         self.genes_count = len(polygons)
  3017.         self.polygons = polygons
  3018.         self.width = material_width
  3019.         self.mutation_factor = 0.1
  3020.         self.order_mutate_factor = 1.
  3021.         self.move_mutate_factor = 1.
  3022.  
  3023.    
  3024.     def add_random_species(self,count):
  3025.         for i in range(count):
  3026.             specimen = []
  3027.             order = range(self.genes_count)
  3028.             random.shuffle(order)
  3029.             for j in order:
  3030.                 specimen += [ [j, random.random(), random.random()] ]
  3031.             self.population += [ [None,specimen] ]
  3032.  
  3033.    
  3034.     def species_distance2(self,sp1,sp2) :
  3035.         # retun distance, each component is normalized
  3036.         s = 0
  3037.         for j in range(self.genes_count) :
  3038.             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
  3039.         return s
  3040.  
  3041.    
  3042.     def similarity(self,sp1,top) :
  3043.         # Define similarity as a simple distance between two points in len(gene)*len(spiece) -th dimentions
  3044.         # for sp2 in top_spieces sum(|sp1-sp2|)/top_count
  3045.         sim = 0
  3046.         for sp2 in top :
  3047.             sim += math.sqrt(species_distance2(sp1,sp2[1]))
  3048.         return sim/len(top)
  3049.        
  3050.    
  3051.     def leave_top_species(self,count):
  3052.         self.population.sort()
  3053.         res = [ copy.deepcopy(self.population[0]) ]
  3054.         del self.population[0]
  3055.         for i in range(count-1) :
  3056.             t = []
  3057.             for j in range(20) :
  3058.                 i1 = random.randint(0,len(self.population)-1)
  3059.                 t += [ [self.population[i1][0],i1] ]
  3060.             t.sort()
  3061.             res += [ copy.deepcopy(self.population[t[0][1]]) ]
  3062.             del self.population[t[0][1]]
  3063.         self.population = res      
  3064.         #del self.population[0]
  3065.         #for c in range(count-1) :
  3066.         #   rank = []
  3067.         #   for i in range(len(self.population)) : 
  3068.         #       sim = self.similarity(self.population[i][1],res)
  3069.         #       rank += [ [self.population[i][0] / sim if sim>0 else 1e100,i] ]
  3070.         #   rank.sort()
  3071.         #   res += [ copy.deepcopy(self.population[rank[0][1]]) ]
  3072.         #   print_(rank[0],self.population[rank[0][1]][0])
  3073.         #   print_(res[-1])
  3074.         #   del self.population[rank[0][1]]
  3075.            
  3076.         self.population = res
  3077.            
  3078.            
  3079.     def populate_species(self,count, parent_count):
  3080.         self.population.sort()
  3081.         self.inc = 0
  3082.         for c in range(count):
  3083.             parent1 = random.randint(0,parent_count-1)
  3084.             parent2 = random.randint(0,parent_count-1)
  3085.             if parent1==parent2 : parent2 = (parent2+1) % parent_count
  3086.             parent1, parent2 = self.population[parent1][1], self.population[parent2][1]
  3087.             i1,i2 = 0, 0
  3088.             genes_order = []
  3089.             specimen = [ [0,0.,0.] for i in range(self.genes_count) ]
  3090.            
  3091.             self.incest_mutation_multiplyer = 1.
  3092.             self.incest_mutation_count_multiplyer = 1.
  3093.  
  3094.             if self.species_distance2(parent1, parent2) <= .01/self.genes_count :
  3095.                 # OMG it's a incest :O!!!
  3096.                 # Damn you bastards!
  3097.                 self.inc +=1
  3098.                 self.incest_mutation_multiplyer = 2.
  3099.                 self.incest_mutation_count_multiplyer = 2.
  3100.             else :
  3101.                 pass
  3102. #               if random.random()<.01 : print_(self.species_distance2(parent1, parent2))  
  3103.             start_gene = random.randint(0,self.genes_count)
  3104.             end_gene = (max(1,random.randint(0,self.genes_count),int(self.genes_count/4))+start_gene) % self.genes_count
  3105.             if end_gene<start_gene :
  3106.                 end_gene, start_gene = start_gene, end_gene
  3107.                 parent1, parent2 = parent2, parent1
  3108.             for i in range(start_gene,end_gene) :
  3109.                 #rotation_mutate_param = random.random()/100
  3110.                 #xposition_mutate_param = random.random()/100
  3111.                 tr = 1. #- rotation_mutate_param
  3112.                 tp = 1. #- xposition_mutate_param
  3113.                 specimen[i] = [parent1[i][0], parent1[i][1]*tr+parent2[i][1]*(1-tr),parent1[i][2]*tp+parent2[i][2]*(1-tp)]
  3114.                 genes_order += [ parent1[i][0] ]
  3115.  
  3116.             for i in range(0,start_gene)+range(end_gene,self.genes_count) :
  3117.                 tr = 0. #rotation_mutate_param
  3118.                 tp = 0. #xposition_mutate_param
  3119.                 j = i
  3120.                 while parent2[j][0] in genes_order :
  3121.                     j = (j+1)%self.genes_count
  3122.                 specimen[i] = [parent2[j][0], parent1[i][1]*tr+parent2[i][1]*(1-tr),parent1[i][2]*tp+parent2[i][2]*(1-tp)]
  3123.                 genes_order += [ parent2[j][0] ]                       
  3124.                
  3125.  
  3126.             for i in range(random.randint(self.mutation_genes_count[0],self.mutation_genes_count[0]*self.incest_mutation_count_multiplyer )) :
  3127.                 if random.random() < self.order_mutate_factor * self.incest_mutation_multiplyer :
  3128.                     i1,i2 = random.randint(0,self.genes_count-1),random.randint(0,self.genes_count-1)
  3129.                     specimen[i1][0], specimen[i2][0] = specimen[i2][0], specimen[i1][0]
  3130.                 if random.random() < self.move_mutation_factor * self.incest_mutation_multiplyer:
  3131.                     i1 = random.randint(0,self.genes_count-1)
  3132.                     specimen[i1][1] = (specimen[i1][1]+random.random()*math.pi2*self.move_mutation_multiplier)%1.
  3133.                     specimen[i1][2] = (specimen[i1][2]+random.random()*self.move_mutation_multiplier)%1.
  3134.             self.population += [ [None,specimen] ] 
  3135.  
  3136.    
  3137.     def test_spiece_drop_down(self,spiece) :
  3138.         surface = Polygon()
  3139.         for p in spiece :
  3140.             time_ = time.time()
  3141.             poly = Polygon(copy.deepcopy(self.polygons[p[0]].polygon))
  3142.             poly.rotate(p[1]*math.pi2)
  3143.             w = poly.width()
  3144.             left = poly.bounds()[0]
  3145.             poly.move( -left + (self.width-w)*p[2],0)
  3146.             poly.drop_down(surface)
  3147.             surface.add(poly)
  3148.         return surface
  3149.  
  3150.    
  3151.     def test(self,test_function):
  3152.         time_ = time.time()
  3153.         for i in range(len(self.population)) :
  3154.             if self.population[i][0] == None :
  3155.                 surface = test_function(self.population[i][1])
  3156.                 b = surface.bounds()
  3157.                 self.population[i][0] = (b[3]-b[1])*(b[2]-b[0])
  3158.         self.population.sort()             
  3159.  
  3160.     def test_spiece_centroid(self,spiece) :
  3161.         poly = Polygon( self.polygons[spiece[0][0]].polygon[:])
  3162.         poly.rotate(spiece[0][1]*math.pi2)
  3163.         surface = Polygon(poly.polygon)
  3164.         for p in spiece[1:] :
  3165.             poly = Polygon(self.polygons[p[0]].polygon[:])
  3166.             c = surface.centroid()
  3167.             surface.move(-c[0],-c[1])
  3168.             c1 = poly.centroid()
  3169.             poly.move(-c1[0],-c1[1])
  3170.             poly.rotate(p[1]*math.pi2+p[2]*math.pi2)
  3171.             surface.rotate(p[2]*math.pi2)
  3172.             poly.drop_down(surface)
  3173.             surface.add(poly)
  3174.             surface.rotate(-p[2]*math.pi2)
  3175.         return surface
  3176.        
  3177.                
  3178.     def test_inline(self) :
  3179.         ###
  3180.         ### Fast test function using weave's from scipy inline function
  3181.         ###
  3182.         try :
  3183.             converters is None
  3184.         except :   
  3185.             try:
  3186.                 from scipy import weave
  3187.                 from scipy.weave import converters
  3188.             except:
  3189.                 options.self.error("For this function Scipy is needed. See http://www.cnc-club.ru/gcodetools for details.","error")    
  3190.    
  3191.         # Prepare vars
  3192.         poly_, subpoly_, points_ = [], [], []
  3193.         for poly in self.polygons :
  3194.             p = poly.polygon
  3195.             poly_ += [len(subpoly_), len(subpoly_)+len(p)*2]
  3196.             for subpoly in p :
  3197.                 subpoly_ += [len(points_), len(points_)+len(subpoly)*2+2]
  3198.                 for point in subpoly :
  3199.                     points_ += point
  3200.                 points_ += subpoly[0] # Close subpolygon
  3201.  
  3202.         test_ = []
  3203.         population_ = []
  3204.         for spiece in self.population:
  3205.             test_.append( spiece[0] if spiece[0] != None else -1)
  3206.             for sp in spiece[1]:
  3207.                 population_ += sp
  3208.            
  3209.         lp_, ls_, l_, lt_ = len(poly_), len(subpoly_), len(points_), len(test_)
  3210.          
  3211.         f = open('inline_test.c', 'r')
  3212.         code = f.read()
  3213.         f.close()
  3214.        
  3215.         f = open('inline_test_functions.c', 'r')
  3216.         functions = f.read()
  3217.         f.close()
  3218.        
  3219.         stdout_ = sys.stdout
  3220.         s = ''     
  3221.         sys.stdout = s
  3222.  
  3223.         test = weave.inline(
  3224.                             code,
  3225.                             ['points_','subpoly_','poly_', 'lp_', 'ls_', 'l_', 'lt_','test_', 'population_'],
  3226.                             compiler='gcc',
  3227.                             support_code = functions,
  3228.                             )
  3229.         if s!='' : options.self.error(s,"warning")     
  3230.         sys.stdout = stdout_
  3231.        
  3232.         for i in range(len(test_)):
  3233.             self.population[i][0] = test_[i]
  3234.        
  3235.        
  3236.        
  3237.        
  3238.         #surface.draw()
  3239.  
  3240.        
  3241. ################################################################################
  3242. ###
  3243. ###     Gcodetools class
  3244. ###
  3245. ################################################################################
  3246.  
  3247. class Gcodetools(inkex.Effect):
  3248.  
  3249.     def export_gcode(self,gcode, no_headers = False) :
  3250.         if self.options.postprocessor != "" or self.options.postprocessor_custom != "" :
  3251.             postprocessor = Postprocessor(self.error)
  3252.             postprocessor.gcode = gcode
  3253.             if self.options.postprocessor != "" :
  3254.                 postprocessor.process(self.options.postprocessor)
  3255.             if self.options.postprocessor_custom != "" :
  3256.                 postprocessor.process(self.options.postprocessor_custom)
  3257.  
  3258.         if not no_headers :
  3259.             postprocessor.gcode = self.header + postprocessor.gcode + self.footer
  3260.  
  3261.         f = open(self.options.directory+self.options.file, "w")
  3262.         f.write(postprocessor.gcode)
  3263.         f.close()                          
  3264.  
  3265.  
  3266. ################################################################################
  3267. ###     In/out paths:
  3268. ###     TODO move it to the bottom
  3269. ################################################################################
  3270.     def plasma_prepare_path(self) :
  3271.    
  3272.         def add_arc(sp1,sp2,end = False,l=10.,r=10.) :
  3273.             if not end :
  3274.                 n = csp_normalized_normal(sp1,sp2,0.)
  3275.                 return csp_reverse([arc_from_s_r_n_l(sp1[1],r,n,-l)])[0]
  3276.             else:
  3277.                 n = csp_normalized_normal(sp1,sp2,1.)
  3278.                 return arc_from_s_r_n_l(sp2[1],r,n,l)
  3279.                
  3280.         def add_normal(sp1,sp2,end = False,l=10.,r=10.) :
  3281.             # r is needed only for be compatible with add_arc
  3282.             if not end :
  3283.                 n = csp_normalized_normal(sp1,sp2,0.)
  3284.                 p = [n[0]*l+sp1[1][0],n[1]*l+sp1[1][1]]
  3285.                 return csp_subpath_line_to([], [p,sp1[1]])
  3286.             else:
  3287.                 n = csp_normalized_normal(sp1,sp2,1.)
  3288.                 p = [n[0]*l+sp2[1][0],n[1]*l+sp2[1][1]]
  3289.                 return csp_subpath_line_to([], [sp2[1],p])
  3290.                
  3291.         def add_tangent(sp1,sp2,end = False,l=10.,r=10.) :
  3292.             # r is needed only for be compatible with add_arc
  3293.             if not end :
  3294.                 n = csp_normalized_slope(sp1,sp2,0.)
  3295.                 p = [-n[0]*l+sp1[1][0],-n[1]*l+sp1[1][1]]
  3296.                 return csp_subpath_line_to([], [p,sp1[1]])
  3297.             else:
  3298.                 n = csp_normalized_slope(sp1,sp2,1.)
  3299.                 p = [n[0]*l+sp2[1][0],n[1]*l+sp2[1][1]]
  3300.                 return csp_subpath_line_to([], [sp2[1],p])
  3301.    
  3302.         if not self.options.in_out_path and not self.options.plasma_prepare_corners and self.options.in_out_path_do_not_add_reference_point:
  3303.             self.error("Warning! Extenstion is not said to do anything! Enable one of Create in-out paths or Prepare corners checkboxes or disable Do not add in-out referense point!")
  3304.             return
  3305.  
  3306.         # Add in-out-reference point if there is no one yet.       
  3307.         if ( (len(self.in_out_reference_points)==0 and self.options.in_out_path
  3308.             or not self.options.in_out_path and not self.options.plasma_prepare_corners )
  3309.              and not self.options.in_out_path_do_not_add_reference_point) :
  3310.                     self.options.orientation_points_count = "in-out reference point"       
  3311.                     self.orientation()
  3312.            
  3313.         if self.options.in_out_path or self.options.plasma_prepare_corners:
  3314.             self.set_markers()
  3315.             add_func = {"Round":add_arc, "Perpendicular": add_normal, "Tangent": add_tangent}[self.options.in_out_path_type]
  3316.             if self.options.in_out_path_type == "Round" and self.options.in_out_path_len > self.options.in_out_path_radius*3/2*math.pi :
  3317.                 self.error("In-out len is to big for in-out radius will cropp it to be r*3/2*pi!", "warning")
  3318.            
  3319.             if self.selected_paths == {} and self.options.auto_select_paths:
  3320.                 self.selected_paths = self.paths
  3321.                 self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
  3322.  
  3323.             if self.selected_paths == {}:
  3324.                 self.error(_("Nothing is selected. Please select something."),"warning")
  3325.             a = self.options.plasma_prepare_corners_tolerance
  3326.             corner_tolerance = cross([1.,0.], [math.cos(a),math.sin(a)])
  3327.  
  3328.             for layer in self.layers :
  3329.                 if layer in self.selected_paths :
  3330.                     max_dist =  self.transform_scalar(self.options.in_out_path_point_max_dist, layer, reverse=True)
  3331.                     l =         self.transform_scalar(self.options.in_out_path_len, layer, reverse=True)
  3332.                     plasma_l =  self.transform_scalar(self.options.plasma_prepare_corners_distance, layer, reverse=True)
  3333.                     r =         self.transform_scalar(self.options.in_out_path_radius, layer, reverse=True)
  3334.                     l = min(l,r*3/2*math.pi)
  3335.  
  3336.                     for path in self.selected_paths[layer]:
  3337.                         csp = self.apply_transforms( path, cubicsuperpath.parsePath(path.get("d")) )
  3338.                         csp = csp_remove_zerro_segments(csp)
  3339.                         res = []
  3340.  
  3341.                         for subpath in csp :
  3342.                         # Find closes point to in-out reference point
  3343.                         # If subpath is open skip this step
  3344.                             if self.options.in_out_path :
  3345.                                 # split and reverse path for further add in-out points
  3346.                                 if point_to_point_d2(subpath[0][1], subpath[-1][1]) < 1.e-10 :
  3347.                                     d = [1e100,1,1,1.]
  3348.                                     for p in self.in_out_reference_points :
  3349.                                         d1 = csp_to_point_distance([subpath], p, dist_bounds = [0,max_dist], tolerance=.01)
  3350.                                         if d1[0] < d[0] :
  3351.                                             d = d1[:]
  3352.                                             p_ = p
  3353.                                     if d[0] < max_dist**2 :
  3354.                                         # Lets find is there any angles near this point to put in-out path in
  3355.                                         # the angle if it's possible
  3356.                                         # remove last node to make iterations easier
  3357.                                         subpath[0][0] = subpath[-1][0]
  3358.                                         del subpath[-1]
  3359.                                         max_cross = [-1e100, None]
  3360.                                         for j in range(len(subpath)) :
  3361.                                             sp1,sp2,sp3 = subpath[j-2],subpath[j-1],subpath[j]
  3362.                                             if point_to_point_d2(sp2[1],p_)<max_dist**2:
  3363.                                                 s1,s2 = csp_normalized_slope(sp1,sp2,1.), csp_normalized_slope(sp2,sp3,0.)
  3364.                                                 max_cross = max(max_cross,[cross(s1,s2),j-1])
  3365.                                         # return back last point       
  3366.                                         subpath.append(subpath[0])
  3367.                                         if max_cross[1] !=None and max_cross[0]>corner_tolerance :
  3368.                                             # there's an angle near the point
  3369.                                             j = max_cross[1]
  3370.                                             if j<0 : j -= 1
  3371.                                             if j!=0 :
  3372.                                                 subpath = csp_concat_subpaths(subpath[j:],subpath[:j+1])
  3373.                                         else :
  3374.                                             # have to cut path's segment
  3375.                                             d,i,j,t = d
  3376.                                             sp1,sp2,sp3 = csp_split(subpath[j-1],subpath[j],t)
  3377.                                             subpath = csp_concat_subpaths([sp2,sp3], subpath[j:], subpath[:j], [sp1,sp2])                          
  3378.  
  3379.                             if self.options.plasma_prepare_corners :
  3380.                                 # prepare corners
  3381.                                 # find corners and add some nodes
  3382.                                 # corner at path's start/end is ignored
  3383.                                 res_ = [subpath[0]]
  3384.                                 for sp2, sp3 in zip(subpath[1:],subpath[2:]) :
  3385.                                     sp1 = res_[-1]
  3386.                                     s1,s2 = csp_normalized_slope(sp1,sp2,1.), csp_normalized_slope(sp2,sp3,0.)
  3387.                                     if cross(s1,s2) > corner_tolerance :
  3388.                                         # got a corner to process
  3389.                                         S1,S2 = P(s1),P(s2)
  3390.                                         N = (S1-S2).unit()*plasma_l
  3391.                                         SP2= P(sp2[1])
  3392.                                         P1 = (SP2 + N)
  3393.                                         res_ += [
  3394.                                                     [sp2[0],sp2[1], (SP2+S1*plasma_l).to_list() ],
  3395.                                                     [ (P1-N.ccw()/2 ).to_list(), P1.to_list(), (P1+N.ccw()/2).to_list()],
  3396.                                                     [(SP2-S2*plasma_l).to_list(), sp2[1],sp2[2]]
  3397.                                                 ]
  3398.                                     else:
  3399.                                         res_ += [sp2]
  3400.                                 res_ += [sp3]
  3401.                                 subpath = res_
  3402.                             if self.options.in_out_path :
  3403.                                 # finally add let's add in-out paths...
  3404.                                 subpath = csp_concat_subpaths(
  3405.                                                     add_func(subpath[0],subpath[1],False,l,r),
  3406.                                                     subpath,
  3407.                                                     add_func(subpath[-2],subpath[-1],True,l,r)
  3408.                                                     )
  3409.  
  3410.                            
  3411.                             res += [ subpath ]
  3412.                            
  3413.                            
  3414.                         if self.options.in_out_path_replace_original_path :
  3415.                             path.set("d", cubicsuperpath.formatPath( self.apply_transforms(path,res,True) ))
  3416.                         else:  
  3417.                             draw_csp(res, width=1, style=styles["in_out_path_style"] )
  3418.        
  3419. ################################################################################
  3420. ###     Arrangement: arranges paths by givven params
  3421. ###     TODO move it to the bottom
  3422. ################################################################################
  3423.     def arrangement(self) :
  3424.         paths = self.selected_paths
  3425.         surface = Polygon()
  3426.         polygons = []
  3427.         time_ = time.time()
  3428.         print_("Arrangement start at %s"%(time_))
  3429.         original_paths = []
  3430.         for layer in self.layers :
  3431.             if layer in paths :
  3432.                 for path in paths[layer] :
  3433.                     csp = cubicsuperpath.parsePath(path.get("d"))
  3434.                     polygon = Polygon()
  3435.                     for subpath in csp :
  3436.                         for sp1, sp2 in zip(subpath,subpath[1:]) :
  3437.                             polygon.add([csp_segment_convex_hull(sp1,sp2)])
  3438.                     #print_("Redused edges count from", sum([len(poly) for poly in polygon.polygon ]) )
  3439.                     polygon.hull()
  3440.                     original_paths += [path]
  3441.                     polygons += [polygon]
  3442.                    
  3443.         print_("Paths hull computed in %s sec."%(time.time()-time_))
  3444.         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) ) )
  3445.         time_ = time.time()
  3446.        
  3447. #       material_width = self.options.arrangement_material_width
  3448. #       population = Arangement_Genetic(polygons, material_width)
  3449. #       population.add_random_species(1)
  3450. #       population.test_population_centroid()
  3451. ##      return
  3452.         material_width = self.options.arrangement_material_width
  3453.         population = Arangement_Genetic(polygons, material_width)
  3454.        
  3455.        
  3456.         print_("Genetic algorithm start at %s"%(time_))
  3457.         start_time = time.time()
  3458.         time_ = time.time()
  3459.        
  3460.    
  3461.  
  3462.         population.add_random_species(50)
  3463.         #population.test(population.test_spiece_centroid)
  3464.         print_("Initial population done in %s"%(time.time()-time_))
  3465.         time_ = time.time()
  3466.         pop = copy.deepcopy(population)
  3467.         population_count = self.options.arrangement_population_count
  3468.         last_champ = -1
  3469.         champions_count = 0
  3470.        
  3471.        
  3472.        
  3473.        
  3474.         for i in range(population_count):
  3475.             population.leave_top_species(20)
  3476.             population.move_mutation_multiplier = random.random()/2
  3477.            
  3478.             population.order_mutation_factor = .2
  3479.             population.move_mutation_factor = 1.
  3480.             population.mutation_genes_count = [1,2]
  3481.             population.populate_species(250, 20)
  3482.             print_("Populate done at %s"%(time.time()-time_))          
  3483.             """
  3484.             randomize = i%100 < 40
  3485.             if  i%100 < 40 :
  3486.                 population.add_random_species(250)
  3487.             if 40<= i%100 < 100 :
  3488.                 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))]
  3489.                 population.move_mutation_multiplier = 1. if 40<=i%100<80 else .1
  3490.                 population.move_mutation_factor = (-(i%100)/30+10/3) if 50<=i%100<100 else .5
  3491.                 population.order_mutation_factor = 1./(i%100-79) if 80<=i%100<100 else 1.
  3492.                 population.populate_species(250, 10)
  3493.             """
  3494.             if self.options.arrangement_inline_test :
  3495.                 population.test_inline()
  3496.             else:      
  3497.                 population.test(population.test_spiece_centroid)
  3498.                
  3499.             print_("Test done at %s"%(time.time()-time_))
  3500.             draw_new_champ = False
  3501.             print_()
  3502.  
  3503.            
  3504.             if population.population[0][0]!= last_champ :
  3505.                 draw_new_champ = True
  3506.                 improve = last_champ-population.population[0][0]
  3507.                 last_champ = population.population[0][0]*1
  3508.  
  3509.            
  3510.             print_("Cicle %s done in %s"%(i,time.time()-time_))
  3511.             time_ = time.time()
  3512.             print_("%s incests been found"%population.inc)
  3513.             print_()
  3514.  
  3515.             if i == 0 or i == population_count-1 or draw_new_champ :
  3516.                 colors = ["blue"]
  3517.                
  3518.                 surface = population.test_spiece_centroid(population.population[0][1])
  3519.                 b = surface.bounds()
  3520.                 x,y = 400* (champions_count%10), 700*int(champions_count/10)
  3521.                 surface.move(x-b[0],y-b[1])
  3522.                 surface.draw(width=2, color=colors[0])
  3523.                 draw_text("Step = %s\nSquare = %f\nSquare improvement = %f\nTime from start = %f"%(i,(b[2]-b[0])*(b[3]-b[1]),improve,time.time()-start_time),x,y-50)
  3524.                 champions_count += 1
  3525.                 """
  3526.                 spiece = population.population[0][1]
  3527.                 poly = Polygon(copy.deepcopy(population.polygons[spiece[0][0]].polygon))
  3528.                 poly.rotate(spiece[0][2]*math.pi2)
  3529.                 surface = Polygon(poly.polygon)
  3530.                 poly.draw(width = 2, color= "Violet")
  3531.                 for p in spiece[1:] :
  3532.                     poly = Polygon(copy.deepcopy(population.polygons[p[0]].polygon))
  3533.                     poly.rotate(p[2]*math.pi2)
  3534.                     direction = [math.cos(p[1]*math.pi2), -math.sin(p[1]*math.pi2)]
  3535.                     normalize(direction)
  3536.                     c = surface.centroid()
  3537.                     c1 = poly.centroid()
  3538.                     poly.move(c[0]-c1[0]-direction[0]*400,c[1]-c1[1]-direction[1]*400)
  3539.                     c = surface.centroid()
  3540.                     c1 = poly.centroid()
  3541.                     poly.draw(width = 5, color= "Violet")
  3542.                     draw_pointer(c+c1,"Green","line")
  3543.                     direction = normalize(direction)
  3544.                    
  3545.                    
  3546.                     sin,cos = direction[0], direction[1]
  3547.                     poly.rotate_(-sin,cos)
  3548.                     surface.rotate_(-sin,cos)
  3549. #                   poly.draw(color = "Violet",width=4)                
  3550.                     surface.draw(color = "Orange",width=4)                 
  3551.                     poly.rotate_(sin,cos)
  3552.                     surface.rotate_(sin,cos)
  3553.  
  3554.  
  3555.                     poly.drop_into_direction(direction,surface)
  3556.                     surface.add(poly)
  3557.                
  3558.                 """
  3559.         # Now we'll need apply transforms to original paths
  3560.        
  3561.        
  3562.     def __init__(self):
  3563.         inkex.Effect.__init__(self)
  3564.         self.OptionParser.add_option("-d", "--directory",                   action="store", type="string",      dest="directory", default="/home/",                 help="Directory for gcode file")
  3565.         self.OptionParser.add_option("-f", "--filename",                    action="store", type="string",      dest="file", default="-1.0",                        help="File name")          
  3566.         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")         
  3567.         self.OptionParser.add_option("",  "--Zscale",                       action="store", type="float",       dest="Zscale", default="1.0",                       help="Scale factor Z")             
  3568.         self.OptionParser.add_option("",  "--Zoffset",                      action="store", type="float",       dest="Zoffset", default="0.0",                      help="Offset along Z")
  3569.         self.OptionParser.add_option("-s", "--Zsafe",                       action="store", type="float",       dest="Zsafe", default="0.5",                        help="Z above all obstacles")
  3570.         self.OptionParser.add_option("-z", "--Zsurface",                    action="store", type="float",       dest="Zsurface", default="0.0",                     help="Z of the surface")
  3571.         self.OptionParser.add_option("-c", "--Zdepth",                      action="store", type="float",       dest="Zdepth", default="-0.125",                    help="Z depth of cut")
  3572.         self.OptionParser.add_option("",  "--Zstep",                        action="store", type="float",       dest="Zstep", default="-0.125",                     help="Z step of cutting")      
  3573.         self.OptionParser.add_option("-p", "--feed",                        action="store", type="float",       dest="feed", default="4.0",                         help="Feed rate in unit/min")
  3574.  
  3575.         self.OptionParser.add_option("",  "--biarc-tolerance",              action="store", type="float",       dest="biarc_tolerance", default="1",                help="Tolerance used when calculating biarc interpolation.")               
  3576.         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.")               
  3577.         self.OptionParser.add_option("",  "--path-to-gcode-order",          action="store", type="string",      dest="path_to_gcode_order", default="path by path", help="Defines cutting order path by path or layer by layer.")              
  3578.         self.OptionParser.add_option("",  "--path-to-gcode-depth-function",action="store", type="string",       dest="path_to_gcode_depth_function", default="zd",  help="Path to gcode depth function.")              
  3579.         self.OptionParser.add_option("",  "--path-to-gcode-sort-paths", action="store", type="inkbool",     dest="path_to_gcode_sort_paths", default=True,      help="Sort paths to reduce rapid distance.")       
  3580.         self.OptionParser.add_option("",  "--comment-gcode",                action="store", type="string",      dest="comment_gcode", default="",                   help="Comment Gcode")              
  3581.         self.OptionParser.add_option("",  "--comment-gcode-from-properties",action="store", type="inkbool",     dest="comment_gcode_from_properties", default=False,help="Get additional comments from Object Properties")             
  3582.  
  3583.  
  3584.  
  3585.         self.OptionParser.add_option("",  "--tool-diameter",                action="store", type="float",       dest="tool_diameter", default="3",                  help="Tool diameter used for area cutting")    
  3586.         self.OptionParser.add_option("",  "--max-area-curves",              action="store", type="int",         dest="max_area_curves", default="100",              help="Maximum area curves for each area")
  3587.         self.OptionParser.add_option("",  "--area-inkscape-radius",     action="store", type="float",       dest="area_inkscape_radius", default="0",           help="Area curves overlaping (depends on tool diameter [0,0.9])")
  3588.         self.OptionParser.add_option("",  "--area-tool-overlap",            action="store", type="float",       dest="area_tool_overlap", default="-10",            help="Radius for preparing curves using inkscape")
  3589.         self.OptionParser.add_option("",  "--unit",                     action="store", type="string",      dest="unit", default="G21 (All units in mm)",       help="Units")
  3590.         self.OptionParser.add_option("",  "--active-tab",                   action="store", type="string",      dest="active_tab", default="",                      help="Defines which tab is active")
  3591.  
  3592.         self.OptionParser.add_option("",  "--area-fill-angle",              action="store", type="float",       dest="area_fill_angle", default="0",                    help="Fill area with lines heading this angle")
  3593.         self.OptionParser.add_option("",  "--area-fill-shift",              action="store", type="float",       dest="area_fill_shift", default="0",                    help="Shift the lines by tool d * shift")
  3594.         self.OptionParser.add_option("",  "--area-fill-method",         action="store", type="string",      dest="area_fill_method", default="zig-zag",                 help="Filling method either zig-zag or spiral")
  3595.  
  3596.         self.OptionParser.add_option("",  "--area-find-artefacts-diameter",action="store", type="float",        dest="area_find_artefacts_diameter", default="1",                   help="Artefacts seeking radius")
  3597.         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")
  3598.  
  3599.         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.")       
  3600.  
  3601.         self.OptionParser.add_option("",  "--loft-distances",               action="store", type="string",      dest="loft_distances", default="10",                help="Distances between paths.")
  3602.         self.OptionParser.add_option("",  "--loft-direction",               action="store", type="string",      dest="loft_direction", default="crosswise",         help="Direction of loft's interpolation.")
  3603.         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.")
  3604.  
  3605.         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")    
  3606.  
  3607.         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")       
  3608.         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)")      
  3609.         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")     
  3610.         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")       
  3611.         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. ")
  3612.  
  3613.         self.OptionParser.add_option("",  "--lathe-width",                  action="store", type="float",       dest="lathe_width", default=10.,                            help="Lathe width")
  3614.         self.OptionParser.add_option("",  "--lathe-fine-cut-width",     action="store", type="float",       dest="lathe_fine_cut_width", default=1.,                    help="Fine cut width")
  3615.         self.OptionParser.add_option("",  "--lathe-fine-cut-count",     action="store", type="int",         dest="lathe_fine_cut_count", default=1.,                    help="Fine cut count")
  3616.         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")
  3617.         self.OptionParser.add_option("",  "--lathe-x-axis-remap",           action="store", type="string",      dest="lathe_x_axis_remap", default="X",                     help="Lathe X axis remap")
  3618.         self.OptionParser.add_option("",  "--lathe-z-axis-remap",           action="store", type="string",      dest="lathe_z_axis_remap", default="Z",                     help="Lathe Z axis remap")
  3619.  
  3620.         self.OptionParser.add_option("",  "--lathe-rectangular-cutter-width",action="store", type="float",  dest="lathe_rectangular_cutter_width", default="4",     help="Rectangular cutter width")
  3621.  
  3622.         self.OptionParser.add_option("",  "--create-log",                   action="store", type="inkbool",     dest="log_create_log", default=False,               help="Create log files")
  3623.         self.OptionParser.add_option("",  "--log-filename",             action="store", type="string",      dest="log_filename", default='',                    help="Create log files")
  3624.  
  3625.         self.OptionParser.add_option("",  "--orientation-points-count", action="store", type="string",      dest="orientation_points_count", default="2",           help="Orientation points count")
  3626.         self.OptionParser.add_option("",  "--tools-library-type",           action="store", type="string",      dest="tools_library_type", default='default tool',  help="Create tools definition")
  3627.  
  3628.         self.OptionParser.add_option("",  "--dxfpoints-action",         action="store", type="string",      dest="dxfpoints_action", default='replace',         help="dxfpoint sign toggle")
  3629.                                                                                                          
  3630.         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.")
  3631.  
  3632.         self.OptionParser.add_option("",  "--offset-radius",                action="store", type="float",       dest="offset_radius", default=10.,      help="Offset radius")
  3633.         self.OptionParser.add_option("",  "--offset-step",                  action="store", type="float",       dest="offset_step", default=10.,        help="Offset step")
  3634.         self.OptionParser.add_option("",  "--offset-draw-clippend-path",    action="store", type="inkbool",     dest="offset_draw_clippend_path", default=False,        help="Draw clipped path")      
  3635.         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")      
  3636.    
  3637.         self.OptionParser.add_option("",  "--arrangement-material-width",   action="store", type="float",       dest="arrangement_material_width", default=500,     help="Materials width for arrangement")    
  3638.         self.OptionParser.add_option("",  "--arrangement-population-count",action="store", type="int",          dest="arrangement_population_count", default=100,   help="Genetic algorithm populations count")    
  3639.         self.OptionParser.add_option("",  "--arrangement-inline-test",      action="store", type="inkbool",     dest="arrangement_inline_test", default=False,  help="Use C-inline test (some additional packets will be needed)")
  3640.  
  3641.  
  3642.         self.OptionParser.add_option("",  "--postprocessor",                action="store", type="string",      dest="postprocessor", default='',           help="Postprocessor command.")
  3643.         self.OptionParser.add_option("",  "--postprocessor-custom",     action="store", type="string",      dest="postprocessor_custom", default='',    help="Postprocessor custom command.")
  3644.    
  3645.         self.OptionParser.add_option("",  "--graffiti-max-seg-length",      action="store", type="float",       dest="graffiti_max_seg_length", default=1., help="Graffiti maximum segment length.")
  3646.         self.OptionParser.add_option("",  "--graffiti-min-radius",          action="store", type="float",       dest="graffiti_min_radius", default=10.,    help="Graffiti minimal connector's radius.")
  3647.         self.OptionParser.add_option("",  "--graffiti-start-pos",           action="store", type="string",      dest="graffiti_start_pos", default="(0;0)", help="Graffiti Start position (x;y).")
  3648.         self.OptionParser.add_option("",  "--graffiti-create-linearization-preview",    action="store", type="inkbool",     dest="graffiti_create_linearization_preview", default=True, help="Graffiti create linearization preview.")
  3649.         self.OptionParser.add_option("",  "--graffiti-create-preview",      action="store", type="inkbool",     dest="graffiti_create_preview", default=True,   help="Graffiti create preview.")
  3650.         self.OptionParser.add_option("",  "--graffiti-preview-size",        action="store", type="int",         dest="graffiti_preview_size", default=800,  help="Graffiti preview's size.")
  3651.         self.OptionParser.add_option("",  "--graffiti-preview-emmit",       action="store", type="int",         dest="graffiti_preview_emmit", default=800, help="Preview's paint emmit (pts/s).")
  3652.  
  3653.  
  3654.         self.OptionParser.add_option("",  "--in-out-path",                  action="store", type="inkbool",     dest="in_out_path", default=True,           help="Create in-out paths")
  3655.         self.OptionParser.add_option("",  "--in-out-path-do-not-add-reference-point",   action="store", type="inkbool", dest="in_out_path_do_not_add_reference_point", default=False,   help="Just add reference in-out point")
  3656.         self.OptionParser.add_option("",  "--in-out-path-point-max-dist",   action="store", type="float",       dest="in_out_path_point_max_dist", default=10., help="In-out path max distance to reference point")
  3657.         self.OptionParser.add_option("",  "--in-out-path-type",         action="store", type="string",      dest="in_out_path_type", default="Round",   help="In-out path type")
  3658.         self.OptionParser.add_option("",  "--in-out-path-len",              action="store", type="float",       dest="in_out_path_len", default=10.,        help="In-out path length")
  3659.         self.OptionParser.add_option("",  "--in-out-path-replace-original-path",action="store", type="inkbool", dest="in_out_path_replace_original_path", default=False,    help="Replace original path")
  3660.         self.OptionParser.add_option("",  "--in-out-path-radius",           action="store", type="float",       dest="in_out_path_radius", default=10.,     help="In-out path radius for round path")
  3661.  
  3662.         self.OptionParser.add_option("",  "--plasma-prepare-corners",       action="store", type="inkbool",     dest="plasma_prepare_corners", default=True,    help="Prepare corners")
  3663.         self.OptionParser.add_option("",  "--plasma-prepare-corners-distance", action="store", type="float",    dest="plasma_prepare_corners_distance", default=10.,help="Stepout distance for corners")
  3664.         self.OptionParser.add_option("",  "--plasma-prepare-corners-tolerance", action="store", type="float",   dest="plasma_prepare_corners_tolerance", default=10.,help="Maximum angle for corner (0-180 deg)")
  3665.  
  3666.         self.default_tool = {
  3667.                     "name": "Default tool",
  3668.                     "id": "default tool",
  3669.                     "diameter":10.,
  3670.                     "shape": "10",
  3671.                     "penetration angle":90.,
  3672.                     "penetration feed":3000.,
  3673.                     "depth step":1.,
  3674.                     "feed":1200.,
  3675.                     "in trajectotry":"",
  3676.                     "out trajectotry":"",
  3677.                     "gcode before path":"M280 P0 S80",
  3678.                     "gcode after path":"M280 P0 S50",
  3679.                     "sog":"",
  3680.                     "spinlde rpm":"",
  3681.                     "CW or CCW":"",
  3682.                     "tool change gcode":"",
  3683.                     "4th axis meaning": " ",
  3684.                     "4th axis scale": 1.,
  3685.                     "4th axis offset": 0.,
  3686.                     "passing feed":"4800",                 
  3687.                     "fine feed":"1200",                
  3688.                 }          
  3689.         self.tools_field_order = [
  3690.                     'name',
  3691.                     'id',
  3692.                     'diameter',
  3693.                     'feed',
  3694.                     'shape',
  3695.                     'penetration angle',
  3696.                     'penetration feed',
  3697.                     "passing feed",
  3698.                     'depth step',
  3699.                     "in trajectotry",
  3700.                     "out trajectotry",
  3701.                     "gcode before path",
  3702.                     "gcode after path",
  3703.                     "sog",
  3704.                     "spinlde rpm",
  3705.                     "CW or CCW",
  3706.                     "tool change gcode",
  3707.                 ]
  3708.  
  3709.  
  3710.     def parse_curve(self, p, layer, w = None, f = None):
  3711.             c = []
  3712.             if len(p)==0 :
  3713.                 return []
  3714.             p = self.transform_csp(p, layer)
  3715.            
  3716.  
  3717.             ### Sort to reduce Rapid distance  
  3718.             k = range(1,len(p))
  3719.             keys = [0]
  3720.             while len(k)>0:
  3721.                 end = p[keys[-1]][-1][1]
  3722.                 dist = None
  3723.                 for i in range(len(k)):
  3724.                     start = p[k[i]][0][1]
  3725.                     dist = max(  ( -( ( end[0]-start[0])**2+(end[1]-start[1])**2 ) ,i)  ,  dist )
  3726.                 keys += [k[dist[1]]]
  3727.                 del k[dist[1]]
  3728.             for k in keys:
  3729.                 subpath = p[k]
  3730.                 c += [ [    [subpath[0][1][0],subpath[0][1][1]]  , 'move', 0, 0] ]
  3731.                 for i in range(1,len(subpath)):
  3732.                     sp1 = [ [subpath[i-1][j][0], subpath[i-1][j][1]] for j in range(3)]
  3733.                     sp2 = [ [subpath[i ][j][0], subpath[i ][j][1]] for j in range(3)]
  3734.                     c += biarc(sp1,sp2,0,0) if w==None else biarc(sp1,sp2,-f(w[k][i-1]),-f(w[k][i]))
  3735. #                   l1 = biarc(sp1,sp2,0,0) if w==None else biarc(sp1,sp2,-f(w[k][i-1]),-f(w[k][i]))
  3736. #                   print_((-f(w[k][i-1]),-f(w[k][i]), [i1[5] for i1 in l1]) )
  3737.                 c += [ [ [subpath[-1][1][0],subpath[-1][1][1]] ,'end',0,0] ]
  3738.             return c
  3739.  
  3740.  
  3741. ################################################################################
  3742. ###     Draw csp
  3743. ################################################################################
  3744.  
  3745.     def draw_csp(self, csp, layer=None, group=None, fill='none', stroke='#178ade', width=0.354, style=None):
  3746.         if layer!=None :
  3747.             csp = self.transform_csp(csp,layer,reverse=True)
  3748.         if group==None and layer==None:
  3749.             group = self.document.getroot()
  3750.         elif group==None and layer!=None :
  3751.             group = layer
  3752.         csp = self.apply_transforms(group,csp, reverse=True)
  3753.         if style!=None :
  3754.             return draw_csp(csp, group=group, style=style)
  3755.         else :
  3756.             return draw_csp(csp, group=group, fill=fill, stroke=stroke, width=width)
  3757.                
  3758.  
  3759.  
  3760.  
  3761.     def draw_curve(self, curve, layer, group=None, style=styles["biarc_style"]):
  3762.         self.set_markers()
  3763.  
  3764.         for i in [0,1]:
  3765.             style['biarc%s_r'%i] = simplestyle.parseStyle(style['biarc%s'%i])
  3766.             style['biarc%s_r'%i]["marker-start"] = "url(#DrawCurveMarker_r)"
  3767.             del(style['biarc%s_r'%i]["marker-end"])
  3768.             style['biarc%s_r'%i] = simplestyle.formatStyle(style['biarc%s_r'%i])
  3769.        
  3770.         if group==None:
  3771.             if "preview_groups" not in dir(self) :
  3772.                 self.preview_groups = { layer: inkex.etree.SubElement( self.layers[min(1,len(self.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} ) }
  3773.             elif layer not in self.preview_groups :
  3774.                 self.preview_groups[layer] = inkex.etree.SubElement( self.layers[min(1,len(self.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} )
  3775.             group = self.preview_groups[layer]
  3776.  
  3777.         s, arcn = '', 0
  3778.        
  3779.         transform = self.get_transforms(group)
  3780.         if transform != [] :
  3781.             transform = self.reverse_transform(transform)
  3782.             transform = simpletransform.formatTransform(transform)
  3783.        
  3784.         a,b,c = [0.,0.], [1.,0.], [0.,1.]
  3785.         k = (b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1])
  3786.         a,b,c = self.transform(a, layer, True), self.transform(b, layer, True), self.transform(c, layer, True)
  3787.         if ((b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1]))*k > 0 : reverse_angle = 1
  3788.         else : reverse_angle = -1
  3789.         for sk in curve:
  3790.             si = sk[:]
  3791.             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])
  3792.            
  3793.             if s!='':
  3794.                 if s[1] == 'line':
  3795.                     attr = {    'style': style['line'],
  3796.                                 'd':'M %s,%s L %s,%s' % (s[0][0], s[0][1], si[0][0], si[0][1]),
  3797.                                 "gcodetools": "Preview",
  3798.                             }
  3799.                     if transform != [] :
  3800.                         attr["transform"] = transform      
  3801.                     inkex.etree.SubElement( group, inkex.addNS('path','svg'), attr  )
  3802.                 elif s[1] == 'arc':
  3803.                     arcn += 1
  3804.                     sp = s[0]
  3805.                     c = s[2]
  3806.                     s[3] = s[3]*reverse_angle
  3807.                        
  3808.                     a = ( (P(si[0])-P(c)).angle() - (P(s[0])-P(c)).angle() )%math.pi2 #s[3]
  3809.                     if s[3]*a<0:
  3810.                             if a>0: a = a-math.pi2
  3811.                             else: a = math.pi2+a
  3812.                     r = math.sqrt( (sp[0]-c[0])**2 + (sp[1]-c[1])**2 )
  3813.                     a_st = ( math.atan2(sp[0]-c[0],- (sp[1]-c[1])) - math.pi/2 ) % (math.pi*2)
  3814.                     st = style['biarc%s' % (arcn%2)][:]
  3815.                     if a>0:
  3816.                         a_end = a_st+a
  3817.                         st = style['biarc%s'%(arcn%2)]
  3818.                     else:
  3819.                         a_end = a_st*1
  3820.                         a_st = a_st+a
  3821.                         st = style['biarc%s_r'%(arcn%2)]
  3822.                    
  3823.                     attr = {
  3824.                             'style': st,
  3825.                              inkex.addNS('cx','sodipodi'):      str(c[0]),
  3826.                              inkex.addNS('cy','sodipodi'):      str(c[1]),
  3827.                              inkex.addNS('rx','sodipodi'):      str(r),
  3828.                              inkex.addNS('ry','sodipodi'):      str(r),
  3829.                              inkex.addNS('start','sodipodi'):   str(a_st),
  3830.                              inkex.addNS('end','sodipodi'):     str(a_end),
  3831.                              inkex.addNS('open','sodipodi'):    'true',
  3832.                              inkex.addNS('type','sodipodi'):    'arc',
  3833.                              "gcodetools": "Preview",
  3834.                             }  
  3835.                        
  3836.                     if transform != [] :
  3837.                         attr["transform"] = transform  
  3838.                     inkex.etree.SubElement( group, inkex.addNS('path','svg'), attr)
  3839.             s = si
  3840.    
  3841.  
  3842.     def check_dir(self):
  3843.         if self.options.directory[-1] not in ["/","\\"]:
  3844.             if "\\" in self.options.directory :
  3845.                 self.options.directory += "\\"
  3846.             else :
  3847.                 self.options.directory += "/"
  3848.         print_("Checking directory: '%s'"%self.options.directory)
  3849.         if (os.path.isdir(self.options.directory)):
  3850.             if (os.path.isfile(self.options.directory+'header')):
  3851.                 f = open(self.options.directory+'header', 'r')
  3852.                 self.header = f.read()
  3853.                 f.close()
  3854.             else:
  3855.                 self.header = defaults['header']
  3856.             if (os.path.isfile(self.options.directory+'footer')):
  3857.                 f = open(self.options.directory+'footer','r')
  3858.                 self.footer = f.read()
  3859.                 f.close()
  3860.             else:
  3861.                 self.footer = defaults['footer']
  3862.             self.header += self.options.unit + "\n"
  3863.         else:
  3864.             self.error(_("Directory does not exist! Please specify existing directory at Preferences tab!"),"error")
  3865.             return False
  3866.  
  3867.         if self.options.add_numeric_suffix_to_filename :
  3868.             dir_list = os.listdir(self.options.directory)
  3869.             if "." in self.options.file :
  3870.                 r = re.match(r"^(.*)(\..*)$",self.options.file)
  3871.                 ext = r.group(2)
  3872.                 name = r.group(1)
  3873.             else:  
  3874.                 ext = ""
  3875.                 name = self.options.file
  3876.             max_n = 0
  3877.             for s in dir_list :
  3878.                 r = re.match(r"^%s_0*(\d+)%s$"%(re.escape(name),re.escape(ext) ), s)
  3879.                 if r :
  3880.                     max_n = max(max_n,int(r.group(1)))
  3881.             filename = name + "_" + ( "0"*(4-len(str(max_n+1))) + str(max_n+1) ) + ext
  3882.             self.options.file = filename
  3883.  
  3884.         if self.options.directory[-1] not in ["/","\\"]:
  3885.             if "\\" in self.options.directory :
  3886.                 self.options.directory += "\\"
  3887.             else :
  3888.                 self.options.directory += "/"
  3889.  
  3890.         try:    
  3891.             f = open(self.options.directory+self.options.file, "w")
  3892.             f.close()                          
  3893.         except:
  3894.             self.error(_("Can not write to specified file!\n%s"%(self.options.directory+self.options.file)),"error")
  3895.             return False
  3896.         return True
  3897.            
  3898.  
  3899.  
  3900. ################################################################################
  3901. ###
  3902. ###     Generate Gcode
  3903. ###     Generates Gcode on given curve.
  3904. ###
  3905. ###     Curve definition [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]]       
  3906. ###
  3907. ################################################################################
  3908.     def generate_gcode(self, curve, layer, depth):
  3909.         Zauto_scale = self.Zauto_scale[layer]
  3910.         tool = self.tools[layer][0]
  3911.         g = ""
  3912.  
  3913.         def c(c):
  3914.             c = [c[i] if i<len(c) else None for i in range(6)]
  3915.             if c[5] == 0 : c[5]=None
  3916.             s,s1 = [" X", " Y", " Z", " I", " J", " K"], ["","","","","",""]
  3917.             m,a = [1,1,self.options.Zscale*Zauto_scale,1,1,self.options.Zscale*Zauto_scale], [0,0,self.options.Zoffset,0,0,0]
  3918.             r = '' 
  3919.             for i in range(6):
  3920.                 if c[i]!=None:
  3921.                     r += s[i] + ("%f" % (c[i]*m[i]+a[i])) + s1[i]
  3922.             return r
  3923.  
  3924.         def calculate_angle(a, current_a):
  3925.             return min(                
  3926.                         [abs(a-current_a%math.pi2+math.pi2), a+current_a-current_a%math.pi2+math.pi2],
  3927.                         [abs(a-current_a%math.pi2-math.pi2), a+current_a-current_a%math.pi2-math.pi2],
  3928.                         [abs(a-current_a%math.pi2),          a+current_a-current_a%math.pi2])[1]
  3929.         if len(curve)==0 : return ""   
  3930.                
  3931.         try :
  3932.             self.last_used_tool == None
  3933.         except :
  3934.             self.last_used_tool = None
  3935.         print_("working on curve")
  3936.         print_(curve)
  3937.        
  3938.         if tool != self.last_used_tool :
  3939.             g += ( ";(Change tool to %s)\n" % re.sub("\"'\(\)\\\\"," ",tool["name"]) ) + tool["tool change gcode"] + "\n"
  3940.  
  3941.         lg, zs, f = 'G00', self.options.Zsafe, " F%f"%tool['feed']
  3942.         current_a = 0
  3943.         go_to_safe_distance = "G00" + c([None,None,zs]) + "\n"
  3944.         penetration_feed = " F%s"%tool['penetration feed']
  3945.         for i in range(1,len(curve)):
  3946.         #   Creating Gcode for curve between s=curve[i-1] and si=curve[i] start at s[0] end at s[4]=si[0]
  3947.             s, si = curve[i-1], curve[i]
  3948.             feed = f if lg not in ['G01','G02','G03'] else ''
  3949.             if s[1] == 'move':
  3950.                 g += go_to_safe_distance + "G00" + c(si[0]) + "\nM400\n" + tool['gcode before path'] + "\nM400\n"
  3951.                 lg = 'G00'
  3952.             elif s[1] == 'end':
  3953.                 g += go_to_safe_distance + "\nM400\n" + tool['gcode after path'] + "\nM400\n"
  3954.                 lg = 'G00'
  3955.             elif s[1] == 'line':
  3956.                 if tool['4th axis meaning'] == "tangent knife" :
  3957.                     a = atan2(si[0][0]-s[0][0],si[0][1]-s[0][1])
  3958.                     a = calculate_angle(a, current_a)
  3959.                     g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
  3960.                     current_a = a
  3961.                 if lg=="G00": g += "G01" + c([None,None,s[5][0]+depth]) + penetration_feed +";(Penetrate)\n"   
  3962.                 g += "G01" +c(si[0]+[s[5][1]+depth]) + feed + "\n"
  3963.                 lg = 'G01'
  3964.             elif s[1] == 'arc':
  3965.                 r = [(s[2][0]-s[0][0]), (s[2][1]-s[0][1])]
  3966.                 if tool['4th axis meaning'] == "tangent knife" :
  3967.                     if s[3]<0 : # CW
  3968.                         a1 = atan2(s[2][1]-s[0][1],-s[2][0]+s[0][0]) + math.pi
  3969.                     else: #CCW
  3970.                         a1 = atan2(-s[2][1]+s[0][1],s[2][0]-s[0][0]) + math.pi
  3971.                     a = calculate_angle(a1, current_a)
  3972.                     g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
  3973.                     current_a = a
  3974.                     axis4 = " A%s"%((current_a+s[3])*tool['4th axis scale']+tool['4th axis offset'])
  3975.                     current_a = current_a+s[3]
  3976.                 else : axis4 = ""
  3977.                 if lg=="G00": g += "G01" + c([None,None,s[5][0]+depth]) + penetration_feed + ";(Penetrate)\n"              
  3978.                 if (r[0]**2 + r[1]**2)>self.options.min_arc_radius**2:
  3979.                     r1, r2 = (P(s[0])-P(s[2])), (P(si[0])-P(s[2]))
  3980.                     if abs(r1.mag()-r2.mag()) < 0.001 :
  3981.                         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"
  3982.                     else:
  3983.                         r = (r1.mag()+r2.mag())/2
  3984.                         g += ("G02" if s[3]<0 else "G03") + c(si[0]+[s[5][1]+depth]) + " R%f" % (r) + feed + axis4 + "\n"
  3985.                     lg = 'G02'
  3986.                 else:
  3987.                     if tool['4th axis meaning'] == "tangent knife" :
  3988.                         a = atan2(si[0][0]-s[0][0],si[0][1]-s[0][1]) + math.pi
  3989.                         a = calculate_angle(a, current_a)
  3990.                         g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
  3991.                         current_a = a
  3992.                     g += "G01" +c(si[0]+[s[5][1]+depth]) + feed + "\n"
  3993.                     lg = 'G01'
  3994.         if si[1] == 'end':
  3995.             g += go_to_safe_distance + "\nM400\n" + tool['gcode after path'] + "\nM400\n"
  3996.         return g
  3997.  
  3998.  
  3999.     def get_transforms(self,g):
  4000.         root = self.document.getroot()
  4001.         trans = []
  4002.         while (g!=root):
  4003.             if 'transform' in g.keys():
  4004.                 t = g.get('transform')
  4005.                 t = simpletransform.parseTransform(t)
  4006.                 trans = simpletransform.composeTransform(t,trans) if trans != [] else t
  4007.                 print_(trans)
  4008.             g=g.getparent()
  4009.         return trans
  4010.    
  4011.     def reverse_transform(self,transform):
  4012.         trans = numpy.array(transform + [[0,0,1]])
  4013.         if numpy.linalg.det(trans)!=0 :
  4014.             trans = numpy.linalg.inv(trans).tolist()[:2]       
  4015.             return trans
  4016.         else :
  4017.          return transform
  4018.        
  4019.  
  4020.     def apply_transforms(self,g,csp, reverse=False):
  4021.         trans = self.get_transforms(g)
  4022.         if trans != []:
  4023.             if not reverse :
  4024.                 simpletransform.applyTransformToPath(trans, csp)
  4025.             else :
  4026.                 simpletransform.applyTransformToPath(self.reverse_transform(trans), csp)
  4027.         return csp
  4028.        
  4029.        
  4030.  
  4031.     def transform_scalar(self,x,layer,reverse=False):
  4032.         return self.transform([x,0],layer,reverse)[0] - self.transform([0,0],layer,reverse)[0]
  4033.  
  4034.     def transform(self,source_point, layer, reverse=False):
  4035.         if layer not in self.transform_matrix:
  4036.             for i in range(self.layers.index(layer),-1,-1):
  4037.                 if self.layers[i] in self.orientation_points :
  4038.                     break
  4039.             if self.layers[i] not in self.orientation_points :
  4040.                 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")
  4041.             elif self.layers[i] in self.transform_matrix :
  4042.                 self.transform_matrix[layer] = self.transform_matrix[self.layers[i]]
  4043.                 self.Zcoordinates[layer] = self.Zcoordinates[self.layers[i]]
  4044.             else :
  4045.                 orientation_layer = self.layers[i]
  4046.                 if len(self.orientation_points[orientation_layer])>1 :
  4047.                     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")
  4048.                 points = self.orientation_points[orientation_layer][0]
  4049.                 if len(points)==2:
  4050.                     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]] ] ]
  4051.                 if len(points)==3:
  4052.                     print_("Layer '%s' Orientation points: " % orientation_layer.get(inkex.addNS('label','inkscape')))
  4053.                     for point in points:
  4054.                         print_(point)
  4055.                     #   Zcoordinates definition taken from Orientatnion point 1 and 2
  4056.                     self.Zcoordinates[layer] = [max(points[0][1][2],points[1][1][2]), min(points[0][1][2],points[1][1][2])]
  4057.                     matrix = numpy.array([
  4058.                                 [points[0][0][0], points[0][0][1], 1, 0, 0, 0, 0, 0, 0],
  4059.                                 [0, 0, 0, points[0][0][0], points[0][0][1], 1, 0, 0, 0],
  4060.                                 [0, 0, 0, 0, 0, 0, points[0][0][0], points[0][0][1], 1],
  4061.                                 [points[1][0][0], points[1][0][1], 1, 0, 0, 0, 0, 0, 0],
  4062.                                 [0, 0, 0, points[1][0][0], points[1][0][1], 1, 0, 0, 0],
  4063.                                 [0, 0, 0, 0, 0, 0, points[1][0][0], points[1][0][1], 1],
  4064.                                 [points[2][0][0], points[2][0][1], 1, 0, 0, 0, 0, 0, 0],
  4065.                                 [0, 0, 0, points[2][0][0], points[2][0][1], 1, 0, 0, 0],
  4066.                                 [0, 0, 0, 0, 0, 0, points[2][0][0], points[2][0][1], 1]
  4067.                             ])
  4068.                                
  4069.                     if numpy.linalg.det(matrix)!=0 :
  4070.                         m = numpy.linalg.solve(matrix,
  4071.                             numpy.array(
  4072.                                 [[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]]  
  4073.                                         )
  4074.                             ).tolist()
  4075.                         self.transform_matrix[layer] = [[m[j*3+i][0] for i in range(3)] for j in range(3)]
  4076.                    
  4077.                     else :
  4078.                         self.error(_("Orientation points are wrong! (if there are two orientation points they should not be the same. If there are three orientation points they should not be in a straight line.)"),"wrong_orientation_points")
  4079.                 else :
  4080.                     self.error(_("Orientation points are wrong! (if there are two orientation points they should not be the same. If there are three orientation points they should not be in a straight line.)"),"wrong_orientation_points")
  4081.  
  4082.             self.transform_matrix_reverse[layer] = numpy.linalg.inv(self.transform_matrix[layer]).tolist()     
  4083.             print_("\n Layer '%s' transformation matrixes:" % layer.get(inkex.addNS('label','inkscape')) )
  4084.             print_(self.transform_matrix)
  4085.             print_(self.transform_matrix_reverse)
  4086.  
  4087.             ###self.Zauto_scale[layer] = math.sqrt( (self.transform_matrix[layer][0][0]**2 + self.transform_matrix[layer][1][1]**2)/2 )
  4088.             ### Zautoscale is absolete
  4089.             self.Zauto_scale[layer] = 1
  4090.             print_("Z automatic scale = %s (computed according orientation points)" % self.Zauto_scale[layer])
  4091.  
  4092.         x,y = source_point[0], source_point[1]
  4093.         if not reverse :
  4094.             t = self.transform_matrix[layer]
  4095.         else :
  4096.             t = self.transform_matrix_reverse[layer]
  4097.         return [t[0][0]*x+t[0][1]*y+t[0][2], t[1][0]*x+t[1][1]*y+t[1][2]]
  4098.  
  4099.  
  4100.     def transform_csp(self, csp_, layer, reverse = False):
  4101.         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_)) ]
  4102.         for i in xrange(len(csp)):
  4103.             for j in xrange(len(csp[i])):
  4104.                 for k in xrange(len(csp[i][j])):
  4105.                     csp[i][j][k] = self.transform(csp[i][j][k],layer, reverse)
  4106.         return csp
  4107.    
  4108.        
  4109. ################################################################################
  4110. ###     Errors handling function, notes are just printed into Logfile,
  4111. ###     warnings are printed into log file and warning message is displayed but
  4112. ###     extension continues working, errors causes log and execution is halted
  4113. ###     Notes, warnings adn errors could be assigned to space or comma or dot
  4114. ###     sepparated strings (case is ignoreg).
  4115. ################################################################################
  4116.     def error(self, s, type_= "Warning"):
  4117.         notes = "Note "
  4118.         warnings = """
  4119.                         Warning tools_warning
  4120.                         orientation_warning
  4121.                         bad_orientation_points_in_some_layers
  4122.                         more_than_one_orientation_point_groups
  4123.                         more_than_one_tool
  4124.                         orientation_have_not_been_defined
  4125.                         tool_have_not_been_defined
  4126.                         selection_does_not_contain_paths
  4127.                         selection_does_not_contain_paths_will_take_all
  4128.                         selection_is_empty_will_comupe_drawing
  4129.                         selection_contains_objects_that_are_not_paths
  4130.                         Continue
  4131.                         """
  4132.         errors = """
  4133.                         Error  
  4134.                         wrong_orientation_points   
  4135.                         area_tools_diameter_error
  4136.                         no_tool_error
  4137.                         active_layer_already_has_tool
  4138.                         active_layer_already_has_orientation_points
  4139.                     """
  4140.         s = s.encode('utf-8')
  4141.         if type_.lower() in re.split("[\s\n,\.]+", errors.lower()) :
  4142.             print_(s)
  4143.             inkex.errormsg(s+"\n")     
  4144.             sys.exit()
  4145.         elif type_.lower() in re.split("[\s\n,\.]+", warnings.lower()) :
  4146.             print_(s)
  4147.             inkex.errormsg(s+"\n")     
  4148.         elif type_.lower() in re.split("[\s\n,\.]+", notes.lower()) :
  4149.             print_(s)
  4150.         else :
  4151.             print_(s)
  4152.             inkex.errormsg(s)      
  4153.             sys.exit()
  4154.    
  4155.  
  4156. ################################################################################
  4157. ###     Set markers
  4158. ################################################################################
  4159.     def set_markers(self) :
  4160.         self.get_defs()
  4161.         # Add marker to defs if it doesnot exists
  4162.         if "CheckToolsAndOPMarker" not in self.defs :
  4163.             defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
  4164.             marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"CheckToolsAndOPMarker","orient":"auto","refX":"-4","refY":"-1.687441","style":"overflow:visible"})
  4165.             inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
  4166.    
  4167.                     {   "d":"   m -4.588864,-1.687441 0.0,0.0 L -9.177728,0.0 c 0.73311,-0.996261 0.728882,-2.359329 0.0,-3.374882",
  4168.                         "style": "fill:#000044; fill-rule:evenodd;9oke:none;"   }
  4169.                 )
  4170.  
  4171.         if "DrawCurveMarker" not in self.defs :
  4172.             defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
  4173.             marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"DrawCurveMarker","orient":"auto","refX":"-4","refY":"-1.687441","style":"overflow:visible"})
  4174.             inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
  4175.                     {   "d":"m -4.588864,-1.687441 0.0,0.0 L -9.177728,0.0 c 0.73311,-0.996261 0.728882,-2.359329 0.0,-3.374882",
  4176.                         "style": "fill:#000044; fill-rule:evenodd;stroke:none;" }
  4177.                 )
  4178.  
  4179.         if "DrawCurveMarker_r" not in self.defs :
  4180.             defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
  4181.             marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"DrawCurveMarker_r","orient":"auto","refX":"4","refY":"-1.687441","style":"overflow:visible"})
  4182.             inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
  4183.                     {   "d":"m 4.588864,-1.687441 0.0,0.0 L 9.177728,0.0 c -0.73311,-0.996261 -0.728882,-2.359329 0.0,-3.374882",
  4184.                         "style": "fill:#000044; fill-rule:evenodd;stroke:none;" }
  4185.                 )
  4186.  
  4187.         if "InOutPathMarker" not in self.defs :
  4188.             defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
  4189.             marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"InOutPathMarker","orient":"auto","refX":"-4","refY":"-1.687441","style":"overflow:visible"})
  4190.             inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
  4191.                     {   "d":"m -4.588864,-1.687441 0.0,0.0 L -9.177728,0.0 c 0.73311,-0.996261 0.728882,-2.359329 0.0,-3.374882",
  4192.                         "style": "fill:#0072a7; fill-rule:evenodd;stroke:none;" }
  4193.                 )
  4194.  
  4195.  
  4196.    
  4197. ################################################################################
  4198. ###     Get defs from svg
  4199. ################################################################################
  4200.     def get_defs(self) :
  4201.         self.defs = {}
  4202.         def recursive(g) :
  4203.             for i in g:
  4204.                 if i.tag == inkex.addNS("defs","svg") :
  4205.                     for j in i:
  4206.                         self.defs[j.get("id")] = i
  4207.                 if i.tag ==inkex.addNS("g",'svg') :
  4208.                     recursive(i)
  4209.         recursive(self.document.getroot())
  4210.  
  4211.  
  4212. ################################################################################
  4213. ###
  4214. ###     Get Gcodetools info from the svg
  4215. ###
  4216. ################################################################################
  4217.     def get_info(self):
  4218.         self.selected_paths = {}
  4219.         self.paths = {}    
  4220.         self.tools = {}
  4221.         self.orientation_points = {}
  4222.         self.graffiti_reference_points = {}
  4223.         self.layers = [self.document.getroot()]
  4224.         self.Zcoordinates = {}
  4225.         self.transform_matrix = {}
  4226.         self.transform_matrix_reverse = {}
  4227.         self.Zauto_scale = {}
  4228.         self.in_out_reference_points = []
  4229.         self.my3Dlayer = None
  4230.  
  4231.         def recursive_search(g, layer, selected=False):
  4232.             items = g.getchildren()
  4233.             items.reverse()
  4234.             for i in items:
  4235.                 if selected:
  4236.                     self.selected[i.get("id")] = i
  4237.                 if i.tag == inkex.addNS("g",'svg') and i.get(inkex.addNS('groupmode','inkscape')) == 'layer':
  4238.                     if i.get(inkex.addNS('label','inkscape')) == '3D' :
  4239.                         self.my3Dlayer=i
  4240.                     else :
  4241.                         self.layers += [i]
  4242.                         recursive_search(i,i)
  4243.  
  4244.                 elif i.get('gcodetools') == "Gcodetools orientation group" :
  4245.                     points = self.get_orientation_points(i)
  4246.                     if points != None :
  4247.                         self.orientation_points[layer] = self.orientation_points[layer]+[points[:]] if layer in self.orientation_points else [points[:]]
  4248.                         print_("Found orientation points in '%s' layer: %s" % (layer.get(inkex.addNS('label','inkscape')), points))
  4249.                     else :
  4250.                         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")
  4251.  
  4252.                 #Need to recognise old files ver 1.6.04 and earlier
  4253.                 elif i.get("gcodetools") == "Gcodetools tool definition" or i.get("gcodetools") == "Gcodetools tool defenition" :
  4254.                     tool = self.get_tool(i)
  4255.                     self.tools[layer] = self.tools[layer] + [tool.copy()] if layer in self.tools else [tool.copy()]
  4256.                     print_("Found tool in '%s' layer: %s" % (layer.get(inkex.addNS('label','inkscape')), tool))
  4257.  
  4258.                 elif i.get("gcodetools") == "Gcodetools graffiti reference point" :
  4259.                     point = self.get_graffiti_reference_points(i)
  4260.                     if point != [] :
  4261.                         self.graffiti_reference_points[layer] = self.graffiti_reference_points[layer]+[point[:]] if layer in self.graffiti_reference_points else [point]
  4262.                     else :
  4263.                         self.error(_("Warning! Found bad graffiti reference point in '%s' layer. Resulting Gcode could be corrupt!") % layer.get(inkex.addNS('label','inkscape')), "bad_orientation_points_in_some_layers")
  4264.                
  4265.                 elif i.tag == inkex.addNS('path','svg'):
  4266.                     if "gcodetools" not in i.keys() :
  4267.                         self.paths[layer] = self.paths[layer] + [i] if layer in self.paths else [i]
  4268.                         if i.get("id") in self.selected :
  4269.                             self.selected_paths[layer] = self.selected_paths[layer] + [i] if layer in self.selected_paths else [i]
  4270.  
  4271.                 elif i.get("gcodetools") == "In-out reference point group" :
  4272.                     items_ = i.getchildren()
  4273.                     items_.reverse()
  4274.                     for j in items_ :
  4275.                         if j.get("gcodetools") == "In-out reference point" :
  4276.                             self.in_out_reference_points.append( self.apply_transforms(j,cubicsuperpath.parsePath(j.get("d")))[0][0][1] )
  4277.  
  4278.  
  4279.                 elif i.tag == inkex.addNS("g",'svg'):
  4280.                     recursive_search(i,layer, (i.get("id") in self.selected) )
  4281.  
  4282.                 elif i.get("id") in self.selected :
  4283. # xgettext:no-pango-format
  4284.                     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")
  4285.                
  4286.                    
  4287.         recursive_search(self.document.getroot(),self.document.getroot())
  4288.  
  4289.         if len(self.layers) == 1 :
  4290.             self.error(_("Document has no layers! Add at least one layer using layers panel (Ctrl+Shift+L)"),"Error")
  4291.         root = self.document.getroot()
  4292.  
  4293.         if root in self.selected_paths or root in self.paths :
  4294.             self.error(_("Warning! There are some paths in the root of the document, but not in any layer! Using bottom-most layer for them."), "tools_warning" )
  4295.  
  4296.         if root in self.selected_paths :
  4297.             if self.layers[-1] in self.selected_paths :
  4298.                 self.selected_paths[self.layers[-1]] += self.selected_paths[root][:]
  4299.             else
  4300.                 self.selected_paths[self.layers[-1]] = self.selected_paths[root][:]
  4301.             del self.selected_paths[root]
  4302.            
  4303.         if root in self.paths :
  4304.             if self.layers[-1] in self.paths :
  4305.                 self.paths[self.layers[-1]] += self.paths[root][:]
  4306.             else :
  4307.                 self.paths[self.layers[-1]] = self.paths[root][:]
  4308.             del self.paths[root]
  4309.  
  4310.  
  4311.     def get_orientation_points(self,g):
  4312.         items = g.getchildren()
  4313.         items.reverse()
  4314.         p2, p3 = [], []
  4315.         p = None
  4316.         for i in items:
  4317.             if i.tag == inkex.addNS("g",'svg') and i.get("gcodetools") == "Gcodetools orientation point (2 points)":
  4318.                 p2 += [i]
  4319.             if i.tag == inkex.addNS("g",'svg') and i.get("gcodetools") == "Gcodetools orientation point (3 points)":
  4320.                 p3 += [i]
  4321.         if len(p2)==2 : p=p2
  4322.         elif len(p3)==3 : p=p3
  4323.         if p==None : return None
  4324.         points = []
  4325.         for i in p :   
  4326.             point = [[],[]]
  4327.             for node in i :
  4328.                 if node.get('gcodetools') == "Gcodetools orientation point arrow":
  4329.                     point[0] = self.apply_transforms(node,cubicsuperpath.parsePath(node.get("d")))[0][0][1]
  4330.                 if node.get('gcodetools') == "Gcodetools orientation point text":
  4331.                     r = re.match(r'(?i)\s*\(\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*;\s*(-?\s*\d*(?:,|\.)*\d*)\s*\)\s*',get_text(node))
  4332.                     point[1] = [float(r.group(1)),float(r.group(2)),float(r.group(3))]
  4333.             if point[0]!=[] and point[1]!=[]:   points += [point]
  4334.         if len(points)==len(p2)==2 or len(points)==len(p3)==3 : return points
  4335.         else : return None
  4336.    
  4337.     def get_graffiti_reference_points(self,g):
  4338.             point = [[], '']
  4339.             for node in g :
  4340.                 if node.get('gcodetools') == "Gcodetools graffiti reference point arrow":
  4341.                     point[0] = self.apply_transforms(node,cubicsuperpath.parsePath(node.get("d")))[0][0][1]
  4342.                 if node.get('gcodetools') == "Gcodetools graffiti reference point text":
  4343.                     point[1] = get_text(node)
  4344.             if point[0]!=[] and point[1]!='' : return point
  4345.             else : return []
  4346.  
  4347.     def get_tool(self, g):
  4348.         tool = self.default_tool.copy()
  4349.         tool["self_group"] = g
  4350.         for i in g:
  4351.             #   Get parameters
  4352.             if i.get("gcodetools") == "Gcodetools tool background" :
  4353.                 tool["style"] = simplestyle.parseStyle(i.get("style"))
  4354.             elif i.get("gcodetools") == "Gcodetools tool parameter" :
  4355.                 key = None
  4356.                 value = None
  4357.                 for j in i:
  4358.                     #need to recognise old tools from ver 1.6.04
  4359.                     if j.get("gcodetools") == "Gcodetools tool definition field name" or j.get("gcodetools") == "Gcodetools tool defention field name":
  4360.                         key = get_text(j)
  4361.                     if j.get("gcodetools") == "Gcodetools tool definition field value" or j.get("gcodetools") == "Gcodetools tool defention field value":
  4362.                         value = get_text(j)
  4363.                         if value == "(None)": value = ""
  4364.                 if value == None or key == None: continue
  4365.                 #print_("Found tool parameter '%s':'%s'" % (key,value))
  4366.                 if key in self.default_tool.keys() :
  4367.                      try :
  4368.                         tool[key] = type(self.default_tool[key])(value)
  4369.                      except :
  4370.                         tool[key] = self.default_tool[key]
  4371.                         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")
  4372.                 else :
  4373.                     tool[key] = value
  4374.                     self.error(_("Warning! Tool has parameter that default tool has not ( '%s': '%s' ).") % (key, value), "tools_warning" )
  4375.         return tool
  4376.        
  4377.        
  4378.     def set_tool(self,layer):
  4379. #       print_(("index(layer)=",self.layers.index(layer),"set_tool():layer=",layer,"self.tools=",self.tools))
  4380. #       for l in self.layers:
  4381. #           print_(("l=",l))
  4382.         for i in range(self.layers.index(layer),-1,-1):
  4383. #           print_(("processing layer",i))
  4384.             if self.layers[i] in self.tools :
  4385.                 break
  4386.         if self.layers[i] in self.tools :
  4387.             if self.layers[i] != layer : self.tools[layer] = self.tools[self.layers[i]]
  4388.             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")
  4389.             return self.tools[layer]
  4390.         else :
  4391.             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")
  4392.  
  4393.  
  4394. ################################################################################
  4395. ###
  4396. ###     Path to Gcode
  4397. ###
  4398. ################################################################################
  4399.     def path_to_gcode(self) :
  4400.         from functools import partial
  4401.         def get_boundaries(points):
  4402.             minx,miny,maxx,maxy=None,None,None,None
  4403.             out=[[],[],[],[]]
  4404.             for p in points:
  4405.                 if minx==p[0]:
  4406.                     out[0]+=[p]
  4407.                 if minx==None or p[0]<minx:
  4408.                     minx=p[0]
  4409.                     out[0]=[p]
  4410.  
  4411.                 if miny==p[1]:
  4412.                     out[1]+=[p]
  4413.                 if miny==None or p[1]<miny:
  4414.                     miny=p[1]
  4415.                     out[1]=[p]
  4416.  
  4417.                 if maxx==p[0]:
  4418.                     out[2]+=[p]
  4419.                 if maxx==None or p[0]>maxx:
  4420.                     maxx=p[0]
  4421.                     out[2]=[p]
  4422.  
  4423.                 if maxy==p[1]:
  4424.                     out[3]+=[p]
  4425.                 if maxy==None or p[1]>maxy:
  4426.                     maxy=p[1]
  4427.                     out[3]=[p]
  4428.             return out
  4429.  
  4430.  
  4431.         def remove_duplicates(points):
  4432.             i=0    
  4433.             out=[]
  4434.             for p in points:
  4435.                 for j in xrange(i,len(points)):
  4436.                     if p==points[j]: points[j]=[None,None] 
  4437.                 if p!=[None,None]: out+=[p]
  4438.             i+=1
  4439.             return(out)
  4440.    
  4441.    
  4442.         def get_way_len(points):
  4443.             l=0
  4444.             for i in xrange(1,len(points)):
  4445.                 l+=math.sqrt((points[i][0]-points[i-1][0])**2 + (points[i][1]-points[i-1][1])**2)
  4446.             return l
  4447.  
  4448.    
  4449.         def sort_dxfpoints(points):
  4450.             points=remove_duplicates(points)
  4451. #           print_(get_boundaries(get_boundaries(points)[2])[1])
  4452.             ways=[
  4453.                          # l=0, d=1, r=2, u=3
  4454.              [3,0], # ul
  4455.              [3,2], # ur
  4456.              [1,0], # dl
  4457.              [1,2], # dr
  4458.              [0,3], # lu
  4459.              [0,1], # ld
  4460.              [2,3], # ru
  4461.              [2,1], # rd
  4462.             ]
  4463. #           print_(("points=",points))
  4464.             minimal_way=[]
  4465.             minimal_len=None
  4466.             minimal_way_type=None
  4467.             for w in ways:
  4468.                 tpoints=points[:]
  4469.                 cw=[]
  4470. #               print_(("tpoints=",tpoints))
  4471.                 for j in xrange(0,len(points)):
  4472.                     p=get_boundaries(get_boundaries(tpoints)[w[0]])[w[1]]
  4473. #                   print_(p)
  4474.                     tpoints.remove(p[0])
  4475.                     cw+=p
  4476.                 curlen = get_way_len(cw)
  4477.                 if minimal_len==None or curlen < minimal_len:
  4478.                     minimal_len=curlen
  4479.                     minimal_way=cw
  4480.                     minimal_way_type=w
  4481.            
  4482.             return minimal_way
  4483.  
  4484.         def sort_lines(lines):
  4485.             if len(lines) == 0 : return []
  4486.             lines = [ [key]+lines[key] for key in range(len(lines))]           
  4487.             keys = [0]
  4488.             end_point = lines[0][3:]
  4489.             print_("!!!",lines,"\n",end_point)
  4490.             del lines[0]
  4491.             while len(lines)>0:
  4492.                 dist = [ [point_to_point_d2(end_point,lines[i][1:3]),i] for i in range(len(lines))]
  4493.                 i = min(dist)[1]
  4494.                 keys.append(lines[i][0])
  4495.                 end_point = lines[i][3:]
  4496.                 del lines[i]
  4497.             return keys
  4498.            
  4499.         def sort_curves(curves):
  4500.             lines = []
  4501.             for curve in curves:
  4502.                 lines += [curve[0][0][0] + curve[-1][-1][0]]
  4503.             return sort_lines(lines)
  4504.        
  4505.         def print_dxfpoints(points):
  4506.             gcode=""
  4507.             for point in points:
  4508.                 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)
  4509. #           print_(("got dxfpoints array=",points))
  4510.             return gcode
  4511.        
  4512.         def get_path_properties(node, recursive=True, tags={inkex.addNS('desc','svg'):"Description",inkex.addNS('title','svg'):"Title"} ) :
  4513.             res = {}
  4514.             done = False
  4515.             root = self.document.getroot()
  4516.             while not done and node != root :
  4517.                 for i in node.getchildren():
  4518.                     if i.tag in tags:
  4519.                         res[tags[i.tag]] = i.text
  4520.                     done = True
  4521.                 node =  node.getparent()
  4522.             return res
  4523.  
  4524.         if self.selected_paths == {} and self.options.auto_select_paths:
  4525.             paths=self.paths
  4526.             self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
  4527.         else :
  4528.             paths = self.selected_paths
  4529.         self.check_dir()
  4530.         gcode = ""
  4531.        
  4532.         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') )
  4533.         print_(("self.layers=",self.layers))
  4534.         print_(("paths=",paths))
  4535.         colors = {}
  4536.         for layer in self.layers :
  4537.             if layer in paths :
  4538.                 print_(("layer",layer))
  4539.                 # transform simple path to get all var about orientation
  4540.                 self.transform_csp([ [ [[0,0],[0,0],[0,0]], [[0,0],[0,0],[0,0]] ] ], layer)
  4541.            
  4542.                 self.set_tool(layer)
  4543.                 curves = []
  4544.                 dxfpoints = []
  4545.  
  4546.                 try :
  4547.                     depth_func = eval('lambda c,d,s: ' + self.options.path_to_gcode_depth_function.strip('"'))             
  4548.                 except:
  4549.                     self.error("Bad depth function! Enter correct function at Path to Gcode tab!")
  4550.  
  4551.                 for path in paths[layer] :
  4552.                     if "d" not in path.keys() :
  4553.                         self.error(_("Warning: One or more paths do not have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!"),"selection_contains_objects_that_are_not_paths")
  4554.                         continue                   
  4555.                     csp = cubicsuperpath.parsePath(path.get("d"))
  4556.                     csp = self.apply_transforms(path, csp)
  4557.                     id_ = path.get("id")
  4558.                    
  4559.                     def set_comment(match, path):
  4560.                         if match.group(1) in path.keys() :
  4561.                             return path.get(match.group(1))
  4562.                         else:
  4563.                             return "None"
  4564.                     if self.options.comment_gcode != "" :
  4565.                         comment = re.sub("\[([A-Za-z_\-\:]+)\]", partial(set_comment, path=path), self.options.comment_gcode)
  4566.                         comment = comment.replace(":newline:","\n")
  4567.                         comment = gcode_comment_str(comment)
  4568.                     else:
  4569.                         comment = ""
  4570.                     if self.options.comment_gcode_from_properties :
  4571.                         tags = get_path_properties(path)
  4572.                         for tag in tags :
  4573.                             comment += gcode_comment_str("%s: %s"%(tag,tags[tag]))
  4574.  
  4575.                     style = simplestyle.parseStyle(path.get("style"))
  4576.                     colors[id_] = simplestyle.parseColor(style['stroke'] if "stroke" in style and style['stroke']!='none' else "#000")
  4577.                     if path.get("dxfpoint") == "1":
  4578.                         tmp_curve=self.transform_csp(csp, layer)
  4579.                         x=tmp_curve[0][0][0][0]
  4580.                         y=tmp_curve[0][0][0][1]
  4581.                         print_("got dxfpoint (scaled) at (%f,%f)" % (x,y))
  4582.                         dxfpoints += [[x,y]]
  4583.                     else:
  4584.                        
  4585.                         zd,zs = self.Zcoordinates[layer][1],    self.Zcoordinates[layer][0]
  4586.                         c = 1. - float(sum(colors[id_]))/255/3
  4587.                         curves +=   [
  4588.                                          [
  4589.                                             [id_, depth_func(c,zd,zs), comment],
  4590.                                             [ self.parse_curve([subpath], layer) for subpath in csp ]
  4591.                                          ]
  4592.                                     ]
  4593. #               for c in curves :
  4594. #                   print_(c)
  4595.                 dxfpoints=sort_dxfpoints(dxfpoints)
  4596.                 gcode+=print_dxfpoints(dxfpoints)
  4597.                
  4598.                
  4599.                 for curve in curves :
  4600.                     for subcurve in curve[1] :
  4601.                         self.draw_curve(subcurve, layer)
  4602.                    
  4603.                 if self.options.path_to_gcode_order == 'subpath by subpath':
  4604.                     curves_ = []
  4605.                     for curve in curves :
  4606.                         curves_ += [ [curve[0],[subcurve]] for subcurve in curve[1] ]
  4607.                     curves = curves_   
  4608.  
  4609.                     self.options.path_to_gcode_order = 'path by path'
  4610.                    
  4611.                 if self.options.path_to_gcode_order == 'path by path':
  4612.                     if self.options.path_to_gcode_sort_paths :
  4613.                         keys = sort_curves( [curve[1] for curve in curves] )
  4614.                     else :
  4615.                         keys = range(len(curves))
  4616.                     for key in keys:
  4617.                         d = curves[key][0][1]
  4618.                         for step in range( 0, int(math.ceil( abs((zs-d)/self.tools[layer][0]["depth step"] )) ) ):
  4619.                             z = max(d, zs - abs(self.tools[layer][0]["depth step"]*(step+1)))
  4620.                            
  4621.                             gcode += gcode_comment_str("\nStart cutting path id: %s"%curves[key][0][0])
  4622.                             if curves[key][0][2] != "()" :
  4623.                                 gcode += curves[key][0][2] # add comment
  4624.                                
  4625.                             for curve in curves[key][1]:
  4626.                                 gcode += self.generate_gcode(curve, layer, z)
  4627.                                
  4628.                             gcode += gcode_comment_str("End cutting path id: %s\n\n"%curves[key][0][0])
  4629.                            
  4630.                 else:   # pass by pass
  4631.                     mind = min( [curve[0][1] for curve in curves] )
  4632.                     for step in range( 0, int(math.ceil( abs((zs-mind)/self.tools[layer][0]["depth step"] )) ) ):
  4633.                         z = zs - abs(self.tools[layer][0]["depth step"]*(step))
  4634.                         curves_ = []
  4635.                         for curve in curves:
  4636.                             if curve[0][1]<z :
  4637.                                 curves_.append(curve)
  4638.  
  4639.                         z = zs - abs(self.tools[layer][0]["depth step"]*(step+1))
  4640.                         gcode += "\n(Pass at depth %s)\n"%z
  4641.  
  4642.                         if self.options.path_to_gcode_sort_paths :
  4643.                             keys = sort_curves( [curve[1] for curve in curves_] )      
  4644.                         else :
  4645.                             keys = range(len(curves_))
  4646.                         for key in keys:
  4647.                
  4648.                             gcode += gcode_comment_str("Start cutting path id: %s"%curves[key][0][0])
  4649.                             if curves[key][0][2] != "()" :
  4650.                                 gcode += curves[key][0][2] # add comment
  4651.  
  4652.                             for subcurve in curves_[key][1]:
  4653.                                 gcode += self.generate_gcode(subcurve, layer, max(z,curves_[key][0][1]))
  4654.                
  4655.                             gcode += gcode_comment_str("End cutting path id: %s\n\n"%curves[key][0][0])
  4656.  
  4657.                            
  4658.         self.export_gcode(gcode)
  4659.    
  4660. ################################################################################
  4661. ###
  4662. ###     dxfpoints
  4663. ###
  4664. ################################################################################
  4665.     def dxfpoints(self):
  4666.         if self.selected_paths == {}:
  4667.             self.error(_("Nothing is selected. Please select something to convert to drill point (dxfpoint) or clear point sign."),"warning")
  4668.         for layer in self.layers :
  4669.             if layer in self.selected_paths :
  4670.                 for path in self.selected_paths[layer]:
  4671. #                   print_(("processing path",path.get('d')))
  4672.                     if self.options.dxfpoints_action == 'replace':
  4673. #                       print_("trying to set as dxfpoint")
  4674.                        
  4675.                         path.set("dxfpoint","1")
  4676.                         r = re.match("^\s*.\s*(\S+)",path.get("d"))
  4677.                         if r!=None:
  4678.                             print_(("got path=",r.group(1)))
  4679.                             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))
  4680.                             path.set("style",styles["dxf_points"])
  4681.  
  4682.                     if self.options.dxfpoints_action == 'save':
  4683.                         path.set("dxfpoint","1")
  4684.  
  4685.                     if self.options.dxfpoints_action == 'clear' and path.get("dxfpoint") == "1":
  4686.                         path.set("dxfpoint","0")
  4687. #                       for id, node in self.selected.iteritems():
  4688. #                           print_((id,node,node.attrib))
  4689.  
  4690.  
  4691. ################################################################################
  4692. ###
  4693. ###     Artefacts
  4694. ###
  4695. ################################################################################
  4696.     def area_artefacts(self) :
  4697.             if self.selected_paths == {} and self.options.auto_select_paths:
  4698.                 paths=self.paths
  4699.                 self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
  4700.             else :
  4701.                 paths = self.selected_paths
  4702.             for layer in paths :
  4703. #               paths[layer].reverse() # Reverse list of paths to leave their order
  4704.                 for path in paths[layer] :
  4705.                     parent = path.getparent()
  4706.                     style = path.get("style") if "style" in path.keys() else ""
  4707.                     if "d" not in path.keys() :
  4708.                         self.error(_("Warning: One or more paths do not have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!"),"selection_contains_objects_that_are_not_paths")
  4709.                         continue       
  4710.                     csp = cubicsuperpath.parsePath(path.get("d"))
  4711.                     remove = []
  4712.                     for i in range(len(csp)) :
  4713.                         subpath = [ [point[:] for point in points] for points in csp[i]]
  4714.                         subpath = self.apply_transforms(path,[subpath])[0]
  4715.                         bounds = csp_simple_bound([subpath])
  4716.                         if (bounds[2]-bounds[0])**2+(bounds[3]-bounds[1])**2 < self.options.area_find_artefacts_diameter**2:
  4717.                             if self.options.area_find_artefacts_action == "mark with an arrow" :
  4718.                                 arrow = cubicsuperpath.parsePath( '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]) )
  4719.                                 arrow = self.apply_transforms(path,arrow,True)
  4720.                                 inkex.etree.SubElement(parent, inkex.addNS('path','svg'),
  4721.                                         {
  4722.                                             'd': cubicsuperpath.formatPath(arrow),
  4723.                                             'style': styles["area artefact arrow"],
  4724.                                             'gcodetools': 'area artefact arrow',
  4725.                                         })
  4726.                             elif self.options.area_find_artefacts_action == "mark with style" :
  4727.                                 inkex.etree.SubElement(parent, inkex.addNS('path','svg'), {'d': cubicsuperpath.formatPath(csp[i]), 'style': styles["area artefact"]})
  4728.                                 remove.append(i)
  4729.                             elif self.options.area_find_artefacts_action == "delete" :
  4730.                                 remove.append(i)
  4731.                                 print_("Deleted artefact %s" % subpath )
  4732.                     remove.reverse()
  4733.                     for i in remove :
  4734.                         del csp[i]
  4735.                     if len(csp) == 0
  4736.                         parent.remove(path)
  4737.                     else :
  4738.                         path.set("d", cubicsuperpath.formatPath(csp))
  4739.                            
  4740.             return
  4741.            
  4742.  
  4743. ################################################################################
  4744. ###
  4745. ###     Calculate area curves
  4746. ###
  4747. ################################################################################
  4748.     def area(self) :
  4749.         if len(self.selected_paths)<=0:
  4750.             self.error(_("This extension requires at least one selected path."),"warning")
  4751.             return
  4752.         for layer in self.layers :
  4753.             if layer in self.selected_paths :
  4754.                 self.set_tool(layer)
  4755.                 if self.tools[layer][0]['diameter']<=0 :
  4756.                     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")
  4757.    
  4758.                 for path in self.selected_paths[layer]:
  4759.                     print_(("doing path",   path.get("style"), path.get("d")))
  4760.  
  4761.                     area_group = inkex.etree.SubElement( path.getparent(), inkex.addNS('g','svg') )
  4762.                
  4763.                     d = path.get('d')
  4764.                     print_(d)
  4765.                     if d==None:
  4766.                         print_("omitting non-path")
  4767.                         self.error(_("Warning: omitting non-path"),"selection_contains_objects_that_are_not_paths")
  4768.                         continue
  4769.                     csp = cubicsuperpath.parsePath(d)
  4770.                
  4771.                     if path.get(inkex.addNS('type','sodipodi'))!="inkscape:offset":
  4772.                         print_("Path %s is not an offset. Preparation started." % path.get("id"))
  4773.                         # Path is not offset. Preparation will be needed.
  4774.                         # Finding top most point in path (min y value)
  4775.                        
  4776.                         min_x,min_y,min_i,min_j,min_t = csp_true_bounds(csp)[1]
  4777.                        
  4778.                         # Reverse path if needed.
  4779.                         if min_y!=float("-inf") :
  4780.                             # Move outline subpath to the begining of csp
  4781.                             subp = csp[min_i]
  4782.                             del csp[min_i]
  4783.                             j = min_j
  4784.                             # Split by the topmost point and join again
  4785.                             if min_t in [0,1]:
  4786.                                 if min_t == 0: j=j-1
  4787.                                 subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1]
  4788.                                 subp = [ [subp[j][1], subp[j][1], subp[j][2]] ] + subp[j+1:] + subp[:j] + [ [subp[j][0], subp[j][1], subp[j][1]] ]
  4789.                             else:                          
  4790.                                 sp1,sp2,sp3 = csp_split(subp[j-1],subp[j],min_t)
  4791.                                 subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1]
  4792.                                 subp = [ [ sp2[1], sp2[1],sp2[2] ] ] + [sp3] + subp[j+1:] + subp[:j-1] + [sp1] + [[ sp2[0], sp2[1],sp2[1] ]]                         
  4793.                             csp = [subp] + csp
  4794.                             # reverse path if needed
  4795.                             if csp_subpath_ccw(csp[0]) :
  4796.                                 for i in range(len(csp)):
  4797.                                     n = []
  4798.                                     for j in csp[i]:
  4799.                                         n = [ [j[2][:],j[1][:],j[0][:]] ] + n
  4800.                                     csp[i] = n[:]
  4801.  
  4802.                            
  4803.                         d = cubicsuperpath.formatPath(csp)
  4804.                         print_(("original d=",d))
  4805.                         d = re.sub(r'(?i)(m[^mz]+)',r'\1 Z ',d)
  4806.                         d = re.sub(r'(?i)\s*z\s*z\s*',r' Z ',d)
  4807.                         d = re.sub(r'(?i)\s*([A-Za-z])\s*',r' \1 ',d)
  4808.                         print_(("formatted d=",d))
  4809.                     # scale = sqrt(Xscale**2 + Yscale**2) / sqrt(1**2 + 1**2)
  4810.                     p0 = self.transform([0,0],layer)
  4811.                     p1 = self.transform([0,1],layer)
  4812.                     scale = (P(p0)-P(p1)).mag()
  4813.                     if scale == 0 : scale = 1.
  4814.                     else : scale = 1./scale
  4815.                     print_(scale)
  4816.                     tool_d = self.tools[layer][0]['diameter']*scale
  4817.                     r = self.options.area_inkscape_radius * scale
  4818.                     sign=1 if r>0 else -1
  4819.                     print_("Tool diameter = %s, r = %s" % (tool_d, r))
  4820.                    
  4821.                     # avoiding infinite loops
  4822.                     if self.options.area_tool_overlap>0.9 : self.options.area_tool_overlap = .9
  4823.            
  4824.                     for i in range(self.options.max_area_curves):
  4825.                         radius = - tool_d * (i*(1-self.options.area_tool_overlap)+0.5) * sign
  4826.                         if abs(radius)>abs(r):
  4827.                             radius = -r
  4828.                        
  4829.                         inkex.etree.SubElement( area_group, inkex.addNS('path','svg'),
  4830.                                         {
  4831.                                              inkex.addNS('type','sodipodi'):    'inkscape:offset',
  4832.                                              inkex.addNS('radius','inkscape')str(radius),
  4833.                                              inkex.addNS('original','inkscape'):    d,
  4834.                                             'style': styles["biarc_style_i"]['area']
  4835.                                         })
  4836.                         print_(("adding curve",area_group,d,styles["biarc_style_i"]['area']))
  4837.                         if radius == -r : break
  4838.  
  4839.  
  4840. ################################################################################
  4841. ###
  4842. ###     Polyline to biarc
  4843. ###
  4844. ###     Converts Polyline to Biarc
  4845. ################################################################################
  4846.     def polyline_to_biarc(self):
  4847.        
  4848.        
  4849.        
  4850.         def biarc(sm, depth=0):
  4851.             def biarc_split(sp1,sp2, z1, z2, depth):
  4852.                 if depth<options.biarc_max_split_depth:
  4853.                     sp1,sp2,sp3 = csp_split(sp1,sp2)
  4854.                     l1, l2 = cspseglength(sp1,sp2), cspseglength(sp2,sp3)
  4855.                     if l1+l2 == 0 : zm = z1
  4856.                     else : zm = z1+(z2-z1)*l1/(l1+l2)
  4857.                     return biarc(sp1,sp2,z1,zm,depth+1)+biarc(sp2,sp3,zm,z2,depth+1)
  4858.                 else: return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
  4859.  
  4860.             P0, P4 = P(sp1[1]), P(sp2[1])
  4861.             TS, TE, v = (P(sp1[2])-P0), -(P(sp2[0])-P4), P0 - P4
  4862.             tsa, tea, va = TS.angle(), TE.angle(), v.angle()
  4863.             if TE.mag()<straight_distance_tolerance and TS.mag()<straight_distance_tolerance:  
  4864.                 # Both tangents are zerro - line straight
  4865.                 return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
  4866.             if TE.mag() < straight_distance_tolerance:
  4867.                 TE = -(TS+v).unit()
  4868.                 r = TS.mag()/v.mag()*2
  4869.             elif TS.mag() < straight_distance_tolerance:
  4870.                 TS = -(TE+v).unit()
  4871.                 r = 1/( TE.mag()/v.mag()*2 )
  4872.             else:  
  4873.                 r=TS.mag()/TE.mag()
  4874.             TS, TE = TS.unit(), TE.unit()
  4875.             tang_are_parallel = ((tsa-tea)%math.pi<straight_tolerance or math.pi-(tsa-tea)%math.pi<straight_tolerance )
  4876.             if ( tang_are_parallel and
  4877.                         ((v.mag()<straight_distance_tolerance or TE.mag()<straight_distance_tolerance or TS.mag()<straight_distance_tolerance) or
  4878.                             1-abs(TS*v/(TS.mag()*v.mag()))<straight_tolerance)  ):
  4879.                         # Both tangents are parallel and start and end are the same - line straight
  4880.                         # or one of tangents still smaller then tollerance
  4881.  
  4882.                         # Both tangents and v are parallel - line straight
  4883.                 return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
  4884.  
  4885.             c,b,a = v*v, 2*v*(r*TS+TE), 2*r*(TS*TE-1)
  4886.             if v.mag()==0:
  4887.                 return biarc_split(sp1, sp2, z1, z2, depth)
  4888.             asmall, bsmall, csmall = abs(a)<10**-10,abs(b)<10**-10,abs(c)<10**-10
  4889.             if      asmall and b!=0:    beta = -c/b
  4890.             elif    csmall and a!=0:    beta = -b/a
  4891.             elif not asmall:     
  4892.                 discr = b*b-4*a*c
  4893.                 if discr < 0:   raise ValueError, (a,b,c,discr)
  4894.                 disq = discr**.5
  4895.                 beta1 = (-b - disq) / 2 / a
  4896.                 beta2 = (-b + disq) / 2 / a
  4897.                 if beta1*beta2 > 0 :    raise ValueError, (a,b,c,disq,beta1,beta2)
  4898.                 beta = max(beta1, beta2)
  4899.             elif    asmall and bsmall: 
  4900.                 return biarc_split(sp1, sp2, z1, z2, depth)
  4901.             alpha = beta * r
  4902.             ab = alpha + beta
  4903.             P1 = P0 + alpha * TS
  4904.             P3 = P4 - beta * TE
  4905.             P2 = (beta / ab) * P1 + (alpha / ab) * P3
  4906.  
  4907.  
  4908.             def calculate_arc_params(P0,P1,P2):
  4909.                 D = (P0+P2)/2
  4910.                 if (D-P1).mag()==0: return None, None
  4911.                 R = D - ( (D-P0).mag()**2/(D-P1).mag() )*(P1-D).unit()
  4912.                 p0a, p1a, p2a = (P0-R).angle()%(2*math.pi), (P1-R).angle()%(2*math.pi), (P2-R).angle()%(2*math.pi)
  4913.                 alpha = (p2a - p0a) % (2*math.pi)                  
  4914.                 if (p0a<p2a and (p1a<p0a or p2a<p1a))   or  (p2a<p1a<p0a) :
  4915.                     alpha = -2*math.pi+alpha
  4916.                 if abs(R.x)>1000000 or abs(R.y)>1000000 or (R-P0).mag<options.min_arc_radius**2 :
  4917.                     return None, None
  4918.                 else
  4919.                     return R, alpha
  4920.             R1,a1 = calculate_arc_params(P0,P1,P2)
  4921.             R2,a2 = calculate_arc_params(P2,P3,P4)
  4922.             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]] ]
  4923.    
  4924.             d = csp_to_arc_distance(sp1,sp2, [P0,P2,R1,a1],[P2,P4,R2,a2])
  4925.             if d > options.biarc_tolerance and depth<options.biarc_max_split_depth   : return biarc_split(sp1, sp2, z1, z2, depth)
  4926.             else:
  4927.                 if R2.mag()*a2 == 0 : zm = z2
  4928.                 else : zm = z1 + (z2-z1)*(abs(R1.mag()*a1))/(abs(R2.mag()*a2)+abs(R1.mag()*a1))
  4929.  
  4930.                 l = (P0-P2).l2()
  4931.                 if l < EMC_TOLERANCE_EQUAL**2 or l<EMC_TOLERANCE_EQUAL**2 * R1.l2() /100 :
  4932.                     # arc should be straight otherwise it could be threated as full circle
  4933.                     arc1 = [ sp1[1], 'line', 0, 0, [P2.x,P2.y], [z1,zm] ]
  4934.                 else :
  4935.                     arc1 = [ sp1[1], 'arc', [R1.x,R1.y], a1, [P2.x,P2.y], [z1,zm] ]
  4936.  
  4937.                 l = (P4-P2).l2()
  4938.                 if l < EMC_TOLERANCE_EQUAL**2 or l<EMC_TOLERANCE_EQUAL**2 * R2.l2() /100 :
  4939.                     # arc should be straight otherwise it could be threated as full circle
  4940.                     arc2 = [ [P2.x,P2.y], 'line', 0, 0, [P4.x,P4.y], [zm,z2] ]
  4941.                 else :
  4942.                     arc2 = [ [P2.x,P2.y], 'arc', [R2.x,R2.y], a2, [P4.x,P4.y], [zm,z2] ]
  4943.        
  4944.                 return [ arc1, arc2 ]
  4945.  
  4946.        
  4947.        
  4948.        
  4949.        
  4950.         for layer in self.layers :
  4951.             if layer in self.selected_paths :
  4952.                 for path in self.selected_paths[layer]:
  4953.                     d = path.get('d')
  4954.                     if d==None:
  4955.                         print_("omitting non-path")
  4956.                         self.error(_("Warning: omitting non-path"),"selection_contains_objects_that_are_not_paths")
  4957.                         continue
  4958.                     csp = cubicsuperpath.parsePath(d)
  4959.                     csp = self.apply_transforms(path, csp)
  4960.                     csp = self.transform_csp(csp, layer)
  4961.                    
  4962.                     # lets pretend that csp is a polyline
  4963.                     poly = [ [point[1] for point in subpath] for subpath in csp ]
  4964.                    
  4965.                     self.draw_csp([ [ [point,point,point] for point in subpoly] for subpoly in poly ],layer)
  4966.                    
  4967.                     # lets create biarcs
  4968.                     for subpoly in poly :
  4969.                         # lets split polyline into different smooth parths.
  4970.                        
  4971.                         if len(subpoly)>2 :
  4972.                             smooth = [ [subpoly[0],subpoly[1]] ]
  4973.                             for p1,p2,p3 in zip(subpoly,subpoly[1:],subpoly[2:]) :
  4974.                                 # normalize p1p2 and p2p3 to get angle
  4975.                                 s1,s2 = normalize( p1[0]-p2[0], p1[1]-p2[1]), normalize( p3[0]-p2[0], p3[1]-p2[1])
  4976.                                 if cross(s1,s2) > corner_tolerance :
  4977.                                     #it's an angle
  4978.                                     smooth += [ [p2,p3] ]
  4979.                                 else:
  4980.                                     smooth[-1].append(p3)                          
  4981.                             for sm in smooth :
  4982.                                 smooth_polyline_to_biarc(sm)   
  4983.  
  4984. ################################################################################
  4985. ###
  4986. ###     Area fill
  4987. ###
  4988. ###     Fills area with lines
  4989. ################################################################################
  4990.  
  4991.  
  4992.     def area_fill(self):
  4993.         # convert degrees into rad
  4994.         self.options.area_fill_angle = self.options.area_fill_angle * math.pi / 180
  4995.         if len(self.selected_paths)<=0:
  4996.             self.error(_("This extension requires at least one selected path."),"warning")
  4997.             return
  4998.         for layer in self.layers :
  4999.             if layer in self.selected_paths :
  5000.                 self.set_tool(layer)
  5001.                 if self.tools[layer][0]['diameter']<=0 :
  5002.                     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")
  5003.                 tool = self.tools[layer][0]
  5004.                 for path in self.selected_paths[layer]:
  5005.                     lines = []
  5006.                     print_(("doing path",   path.get("style"), path.get("d")))
  5007.                     area_group = inkex.etree.SubElement( path.getparent(), inkex.addNS('g','svg') )
  5008.                     d = path.get('d')
  5009.                     if d==None:
  5010.                         print_("omitting non-path")
  5011.                         self.error(_("Warning: omitting non-path"),"selection_contains_objects_that_are_not_paths")
  5012.                         continue
  5013.                     csp = cubicsuperpath.parsePath(d)
  5014.                     csp = self.apply_transforms(path, csp)
  5015.                     csp = csp_close_all_subpaths(csp)
  5016.                     csp = self.transform_csp(csp, layer)
  5017.                     #maxx = max([x,y,i,j,root],maxx)
  5018.                    
  5019.                     # rotate the path to get bounds in defined direction.
  5020.                     a = - self.options.area_fill_angle
  5021.                     rotated_path = [  [ [ [point[0]*math.cos(a) - point[1]*math.sin(a), point[0]*math.sin(a)+point[1]*math.cos(a)] for point in sp] for sp in subpath] for subpath in csp ]
  5022.                     bounds = csp_true_bounds(rotated_path)
  5023.                    
  5024.                     # Draw the lines
  5025.                     # Get path's bounds
  5026.                     b = [0.0, 0.0, 0.0, 0.0] # [minx,miny,maxx,maxy]
  5027.                     for k in range(4):
  5028.                         i, j, t = bounds[k][2], bounds[k][3], bounds[k][4]
  5029.                         b[k] = csp_at_t(rotated_path[i][j-1],rotated_path[i][j],t)[k%2]
  5030.  
  5031.                        
  5032.                     # Zig-zag
  5033.                     r = tool['diameter']*(1-self.options.area_tool_overlap)
  5034.                     if r<=0 :
  5035.                         self.error('Tools diameter must be greater than 0!', 'error')
  5036.                         return
  5037.  
  5038.                     lines += [ [] ]
  5039.  
  5040.                     if self.options.area_fill_method == 'zig-zag' :
  5041.                         i = b[0] - self.options.area_fill_shift*r
  5042.                         top = True
  5043.                         last_one = True
  5044.                         while (i<b[2] or last_one) :
  5045.                             if i>=b[2] : last_one = False
  5046.                             if lines[-1] == [] :
  5047.                                 lines[-1] += [ [i,b[3]] ]
  5048.  
  5049.                             if top :
  5050.                                 lines[-1] += [ [i,b[1]],[i+r,b[1]] ]
  5051.  
  5052.                             else :
  5053.                                     lines[-1] += [ [i,b[3]], [i+r,b[3]] ]
  5054.  
  5055.                             top = not top
  5056.                             i += r
  5057.                     else :
  5058.                    
  5059.                         w, h = b[2]-b[0] + self.options.area_fill_shift*r , b[3]-b[1] + self.options.area_fill_shift*r
  5060.                         x,y = b[0] - self.options.area_fill_shift*r, b[1] - self.options.area_fill_shift*r
  5061.                         lines[-1] += [ [x,y] ]
  5062.                         stage = 0
  5063.                         start = True
  5064.                         while w>0 and h>0 :
  5065.                             stage = (stage+1)%4
  5066.                             if  stage == 0 :
  5067.                                 y -= h
  5068.                                 h -= r
  5069.                             elif stage == 1:
  5070.                                 x += w
  5071.                                 if not start:
  5072.                                     w -= r
  5073.                                 start = False  
  5074.                             elif stage == 2 :
  5075.                                 y += h
  5076.                                 h -= r
  5077.                             elif stage == 3:
  5078.                                 x -= w
  5079.                                 w -=r
  5080.                            
  5081.                             lines[-1] += [ [x,y] ]
  5082.  
  5083.                         stage = (stage+1)%4                        
  5084.                         if w <= 0 and h>0 :
  5085.                             y = y-h if stage == 0 else y+h
  5086.                         if h <= 0 and w>0 :
  5087.                             x = x-w if stage == 3 else x+w
  5088.                         lines[-1] += [ [x,y] ]
  5089.                     # Rotate created paths back
  5090.                     a = self.options.area_fill_angle
  5091.                     lines = [ [ [point[0]*math.cos(a) - point[1]*math.sin(a), point[0]*math.sin(a)+point[1]*math.cos(a)] for point in subpath] for subpath in lines ]
  5092.  
  5093.                     # get the intersection points
  5094.                    
  5095.                     splitted_line = [ [lines[0][0]] ]
  5096.                     intersections = {}
  5097.                     for l1,l2, in zip(lines[0],lines[0][1:]):
  5098.                         ints = []
  5099.                        
  5100.                         if l1[0]==l2[0] and l1[1]==l2[1] : continue
  5101.                         for i in range(len(csp)) :
  5102.                             for j in range(1,len(csp[i])) :
  5103.                                 sp1,sp2 = csp[i][j-1], csp[i][j]
  5104.                                 roots = csp_line_intersection(l1,l2,sp1,sp2)
  5105.                                 for t in roots :
  5106.                                     p = tuple(csp_at_t(sp1,sp2,t))
  5107.                                     if l1[0]==l2[0] :
  5108.                                         t1 = (p[1]-l1[1])/(l2[1]-l1[1])
  5109.                                     else :
  5110.                                         t1 = (p[0]-l1[0])/(l2[0]-l1[0])
  5111.                                     if 0<=t1<=1 :
  5112.                                         ints += [[t1, p[0],p[1], i,j,t]]
  5113.                                         if p in intersections :
  5114.                                             intersections[p] += [ [i,j,t] ]
  5115.                                         else :  
  5116.                                             intersections[p] = [ [i,j,t] ]
  5117.                                         #p = self.transform(p,layer,True)
  5118.                                         #draw_pointer(p)
  5119.                         ints.sort()
  5120.                         for i in ints:
  5121.                             splitted_line[-1] +=[ [ i[1], i[2]] ]
  5122.                             splitted_line += [ [ [ i[1], i[2]] ] ]
  5123.                         splitted_line[-1] += [ l2 ]
  5124.                         i = 0
  5125.                     print_(splitted_line)
  5126.                     while i < len(splitted_line) :
  5127.                         # check if the middle point of the first lines segment is inside the path.
  5128.                         # and remove the subline if not.
  5129.                         l1,l2 = splitted_line[i][0],splitted_line[i][1]
  5130.                         p = [(l1[0]+l2[0])/2, (l1[1]+l2[1])/2]
  5131.                         if not point_inside_csp(p, csp):
  5132.                             #i +=1                     
  5133.                             del splitted_line[i]
  5134.                         else :
  5135.                             i += 1
  5136.                    
  5137.                    
  5138.                    
  5139.                     # if we've used spiral method we'll try to save the order of cutting
  5140.                     do_not_change_order = self.options.area_fill_method == 'spiral'
  5141.                     # now let's try connect splitted lines
  5142.                     #while len(splitted_line)>0 :
  5143.                     #TODO  
  5144.                    
  5145.                     # and apply back transrormations to draw them
  5146.                     csp_line = csp_from_polyline(splitted_line)
  5147.                     csp_line = self.transform_csp(csp_line, layer, True)
  5148.                    
  5149.                     self.draw_csp(csp_line, group = area_group)
  5150. #                   draw_csp(lines)
  5151.                    
  5152.  
  5153.  
  5154.                                        
  5155.  
  5156.  
  5157. ################################################################################
  5158. ###
  5159. ###     Engraving
  5160. ###
  5161. #LT Notes to self: See wiki.inkscape.org/wiki/index.php/PythonEffectTutorial
  5162. # To create anything in the Inkscape document, look at the XML editor for
  5163. # details of how such an element looks in XML, then follow this model.
  5164. #layer number n appears in XML as <svg:g id="layern" inkscape:label="layername">
  5165. #
  5166. #to create it, use
  5167. #Mylayer=inkex.etree.SubElement(self.document.getroot(), 'g') #Create a generic element
  5168. #Mylayer.set(inkex.addNS('label', 'inkscape'), "layername")  #Gives it a name
  5169. #Mylayer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')  #Tells Inkscape it's a layer
  5170. #
  5171. #group appears in XML as <svg:g id="gnnnnn"> where nnnnn is a number
  5172. #
  5173. #to create it, use
  5174. #Mygroup=inkex.etree.SubElement(parent, inkex.addNS('g','svg'), {"gcodetools":"My group label"})
  5175. # where parent may be the layer or a parent group. To get the parent group, you can use
  5176. #parent = self.selected_paths[layer][0].getparent()
  5177. ################################################################################
  5178.     def engraving(self) :
  5179.         #global x1,y1,rx,ry
  5180.         global cspm, wl
  5181.         global nlLT, i, j
  5182.         global gcode_3Dleft ,gcode_3Dright
  5183.         global max_dist #minimum of tool radius and user's requested maximum distance
  5184.         global eye_dist
  5185.         eye_dist = 100 #3D constant. Try varying it for your eyes
  5186.  
  5187.  
  5188.         def bisect((nx1,ny1),(nx2,ny2)) :
  5189.             """LT Find angle bisecting the normals n1 and n2
  5190.  
  5191.             Parameters: Normalised normals
  5192.             Returns: nx - Normal of bisector, normalised to 1/cos(a)
  5193.                   ny -
  5194.                   sinBis2 - sin(angle turned/2): positive if turning in
  5195.             Note that bisect(n1,n2) and bisect(n2,n1) give opposite sinBis2 results
  5196.             If sinturn is less than the user's requested angle tolerance, I return 0
  5197.             """
  5198.             #We can get absolute value of cos(bisector vector)
  5199.             #Note: Need to use max in case of rounding errors
  5200.             cosBis = math.sqrt(max(0,(1.0+nx1*nx2-ny1*ny2)/2.0))
  5201.             #We can get correct sign of the sin, assuming cos is positive
  5202.             if (abs(ny1-ny2)< engraving_tolerance) or (abs(cosBis) < engraving_tolerance) :
  5203.                 if (abs(nx1-nx2)< engraving_tolerance): return(nx1,ny1,0.0)
  5204.                 sinBis = math.copysign(1,ny1)
  5205.             else :
  5206.                 sinBis = cosBis*(nx2-nx1)/(ny1-ny2)
  5207.             #We can correct signs by noting that the dot product
  5208.             # of bisector and either normal must be >0
  5209.             costurn=cosBis*nx1+sinBis*ny1
  5210.             if costurn == 0 : return (ny1*100,-nx1*100,1) #Path doubles back on itself
  5211.             sinturn=sinBis*nx1-cosBis*ny1
  5212.             if costurn<0 : sinturn=-sinturn
  5213.             if 0 < sinturn*114.6 < (180-self.options.engraving_sharp_angle_tollerance) :
  5214.                 sinturn=0 #set to zero if less than the user wants to see.
  5215.             return (cosBis/costurn,sinBis/costurn, sinturn)
  5216.             #end bisect
  5217.  
  5218.         def get_radius_to_line((x1,y1),(nx1,ny1), (nx2,ny2),(x2,y2),(nx23,ny23),(x3,y3),(nx3,ny3)):
  5219.             """LT find biggest circle we can engrave here, if constrained by line 2-3
  5220.  
  5221.             Parameters:
  5222.                 x1,y1,nx1,ny1 coordinates and normal of the line we're currently engraving
  5223.                 nx2,ny2 angle bisector at point 2
  5224.                 x2,y2 coordinates of first point of line 2-3
  5225.                 nx23,ny23 normal to the line 2-3
  5226.                 x3,y3 coordinates of the other end
  5227.                 nx3,ny3 angle bisector at point 3
  5228.             Returns:
  5229.                 radius or self.options.engraving_max_dist if line doesn't limit radius
  5230.             This function can be used in three ways:
  5231.             - With nx1=ny1=0 it finds circle centred at x1,y1
  5232.             - with nx1,ny1 normalised, it finds circle tangential at x1,y1
  5233.             - with nx1,ny1 scaled by 1/cos(a) it finds circle centred on an angle bisector
  5234.                  where a is the angle between the bisector and the previous/next normals
  5235.  
  5236.             If the centre of the circle tangential to the line 2-3 is outside the
  5237.             angle bisectors at its ends, ignore this line.
  5238.  
  5239.             # Note that it handles corners in the conventional manner of letter cutting
  5240.             # by mitering, not rounding.
  5241.             # Algorithm uses dot products of normals to find radius
  5242.             # and hence coordinates of centre
  5243.             """
  5244.  
  5245.             global max_dist
  5246.  
  5247.             #Start by converting coordinates to be relative to x1,y1
  5248.             x2,y2= x2-x1, y2-y1
  5249.             x3,y3= x3-x1, y3-y1
  5250.  
  5251.             #The logic uses vector arithmetic.
  5252.             #The dot product of two vectors gives the product of their lengths
  5253.             #multiplied by the cos of the angle between them.
  5254.             # So, the perpendicular distance from x1y1 to the line 2-3
  5255.             # is equal to the dot product of its normal and x2y2 or x3y3
  5256.             #It is also equal to the projection of x1y1-xcyc on the line's normal
  5257.             # plus the radius. But, as the normal faces inside the path we must negate it.
  5258.  
  5259.             #Make sure the line in question is facing x1,y1 and vice versa
  5260.             dist=-x2*nx23-y2*ny23
  5261.             if dist<0 : return max_dist
  5262.             denom=1.-nx23*nx1-ny23*ny1
  5263.             if denom < engraving_tolerance : return max_dist
  5264.  
  5265.             #radius and centre are:
  5266.             r=dist/denom
  5267.             cx=r*nx1
  5268.             cy=r*ny1
  5269.             #if c is not between the angle bisectors at the ends of the line, ignore
  5270.             #Use vector cross products. Not sure if I need the .0001 safety margins:
  5271.             if (x2-cx)*ny2 > (y2-cy)*nx2 +0.0001 :
  5272.                 return max_dist
  5273.             if (x3-cx)*ny3 < (y3-cy)*nx3 -0.0001 :
  5274.                 return max_dist
  5275.             return min(r, max_dist)
  5276.             #end of get_radius_to_line
  5277.  
  5278.         def get_radius_to_point((x1,y1),(nx,ny), (x2,y2)):
  5279.             """LT find biggest circle we can engrave here, constrained by point x2,y2
  5280.  
  5281.             This function can be used in three ways:
  5282.             - With nx=ny=0 it finds circle centred at x1,y1
  5283.             - with nx,ny normalised, it finds circle tangential at x1,y1
  5284.             - with nx,ny scaled by 1/cos(a) it finds circle centred on an angle bisector
  5285.                  where a is the angle between the bisector and the previous/next normals
  5286.  
  5287.             Note that I wrote this to replace find_cutter_centre. It is far less
  5288.             sophisticated but, I hope, far faster.
  5289.             It turns out that finding a circle touching a point is harder than a circle
  5290.             touching a line.
  5291.             """
  5292.  
  5293.             global max_dist
  5294.  
  5295.             #Start by converting coordinates to be relative to x1,y1
  5296.             x2,y2= x2-x1, y2-y1
  5297.             denom=nx**2+ny**2-1
  5298.             if denom<=engraving_tolerance : #Not a corner bisector
  5299.                 if denom==-1 : #Find circle centre x1,y1
  5300.                     return math.sqrt(x2**2+y2**2)
  5301.                 #if x2,y2 not in front of the normal...
  5302.                 if x2*nx+y2*ny <=0 : return max_dist
  5303.                 #print_("Straight",x1,y1,nx,ny,x2,y2)
  5304.                 return (x2**2+y2**2)/(2*(x2*nx+y2*ny) )
  5305.             #It is a corner bisector, so..
  5306.             discriminator = (x2*nx+y2*ny)**2 - denom*(x2**2+y2**2)
  5307.             if discriminator < 0 :
  5308.                 return max_dist #this part irrelevant
  5309.             r=(x2*nx+y2*ny -math.sqrt(discriminator))/denom
  5310.             #print_("Corner",x1,y1,nx,ny,x1+x2,y1+y2,discriminator,r)
  5311.             return min(r, max_dist)
  5312.             #end of get_radius_to_point
  5313.  
  5314.         def bez_divide(a,b,c,d):
  5315.             """LT recursively divide a Bezier.
  5316.  
  5317.             Divides until difference between each
  5318.             part and a straight line is less than some limit
  5319.             Note that, as simple as this code is, it is mathematically correct.
  5320.             Parameters:
  5321.                 a,b,c and d are each a list of x,y real values
  5322.                 Bezier end points a and d, control points b and c
  5323.             Returns:
  5324.                 a list of Beziers.
  5325.                     Each Bezier is a list with four members,
  5326.                         each a list holding a coordinate pair
  5327.                 Note that the final point of one member is the same as
  5328.                 the first point of the next, and the control points
  5329.                 there are smooth and symmetrical. I use this fact later.
  5330.             """
  5331.             bx=b[0]-a[0]
  5332.             by=b[1]-a[1]
  5333.             cx=c[0]-a[0]
  5334.             cy=c[1]-a[1]
  5335.             dx=d[0]-a[0]
  5336.             dy=d[1]-a[1]
  5337.             limit=8*math.hypot(dx,dy)/self.options.engraving_newton_iterations
  5338.             #LT This is the only limit we get from the user currently
  5339.             if abs(dx*by-bx*dy)<limit and abs(dx*cy-cx*dy)<limit :
  5340.                 return [[a,b,c,d]]
  5341.             abx=(a[0]+b[0])/2.0
  5342.             aby=(a[1]+b[1])/2.0
  5343.             bcx=(b[0]+c[0])/2.0
  5344.             bcy=(b[1]+c[1])/2.0
  5345.             cdx=(c[0]+d[0])/2.0
  5346.             cdy=(c[1]+d[1])/2.0
  5347.             abcx=(abx+bcx)/2.0
  5348.             abcy=(aby+bcy)/2.0
  5349.             bcdx=(bcx+cdx)/2.0
  5350.             bcdy=(bcy+cdy)/2.0
  5351.             m=[(abcx+bcdx)/2.0,(abcy+bcdy)/2.0]
  5352.             return bez_divide(a,[abx,aby],[abcx,abcy],m) + bez_divide(m,[bcdx,bcdy],[cdx,cdy],d)
  5353.             #end of bez_divide
  5354.  
  5355.         def get_biggest((x1,y1),(nx,ny)):
  5356.             """LT Find biggest circle we can draw inside path at point x1,y1 normal nx,ny
  5357.  
  5358.             Parameters:
  5359.                 point - either on a line or at a reflex corner
  5360.                 normal - normalised to 1 if on a line, to 1/cos(a) at a corner
  5361.             Returns:
  5362.                 tuple (j,i,r)
  5363.                 ..where j and i are indices of limiting segment, r is radius
  5364.             """
  5365.             global max_dist, nlLT, i, j
  5366.             n1 = nlLT[j][i-1] #current node
  5367.             jjmin = -1
  5368.             iimin = -1
  5369.             r = max_dist
  5370.             # set limits within which to look for lines
  5371.             xmin, xmax = x1+r*nx-r, x1+r*nx+r
  5372.             ymin, ymax = y1+r*ny-r, y1+r*ny+r
  5373.             for jj in xrange(0,len(nlLT)) : #for every subpath of this object
  5374.                 for ii in xrange(0,len(nlLT[jj])) : #for every point and line
  5375.                     if nlLT[jj][ii-1][2] : #if a point
  5376.                         if jj==j : #except this one
  5377.                             if abs(ii-i)<3 or abs(ii-i)>len(nlLT[j])-3 : continue
  5378.                         t1=get_radius_to_point((x1,y1),(nx,ny),nlLT[jj][ii-1][0] )
  5379.                         #print_("Try pt  i,ii,t1,x1,y1",i,ii,t1,x1,y1)
  5380.                     else: #doing a line
  5381.                         if jj==j : #except this one
  5382.                             if abs(ii-i)<2 or abs(ii-i)==len(nlLT[j])-1 : continue
  5383.                             if abs(ii-i)==2 and nlLT[j][(ii+i)/2-1][3]<=0 : continue
  5384.                             if (abs(ii-i)==len(nlLT[j])-2) and nlLT[j][-1][3]<=0 : continue
  5385.                         nx2,ny2 = nlLT[jj][ii-2][1]
  5386.                         x2,y2 = nlLT[jj][ii-1][0]
  5387.                         nx23,ny23 = nlLT[jj][ii-1][1]
  5388.                         x3,y3 = nlLT[jj][ii][0]
  5389.                         nx3,ny3 = nlLT[jj][ii][1]
  5390.                         if nlLT[jj][ii-2][3]>0 : #acute, so use normal, not bisector
  5391.                             nx2=nx23
  5392.                             ny2=ny23
  5393.                         if nlLT[jj][ii][3]>0 : #acute, so use normal, not bisector
  5394.                             nx3=nx23
  5395.                             ny3=ny23
  5396.                         x23min,x23max=min(x2,x3),max(x2,x3)
  5397.                         y23min,y23max=min(y2,y3),max(y2,y3)
  5398.                         #see if line in range
  5399.                         if n1[2]==False and (x23max<xmin or x23min>xmax or y23max<ymin or y23min>ymax) : continue
  5400.                         t1=get_radius_to_line((x1,y1),(nx,ny), (nx2,ny2),(x2,y2),(nx23,ny23), (x3,y3),(nx3,ny3))
  5401.                         #print_("Try line i,ii,t1,x1,y1",i,ii,t1,x1,y1)
  5402.                     if 0<=t1<r :
  5403.                         r = t1
  5404.                         iimin = ii
  5405.                         jjmin = jj
  5406.                         xmin, xmax = x1+r*nx-r, x1+r*nx+r
  5407.                         ymin, ymax = y1+r*ny-r, y1+r*ny+r
  5408.                 #next ii
  5409.             #next jj
  5410.             return (jjmin,iimin,r)
  5411.             #end of get_biggest
  5412.  
  5413.         def line_divide((x0,y0),j0,i0,(x1,y1),j1,i1,(nx,ny),length):
  5414.             """LT recursively divide a line as much as necessary
  5415.  
  5416.             NOTE: This function is not currently used
  5417.             By noting which other path segment is touched by the circles at each end,
  5418.             we can see if anything is to be gained by a further subdivision, since
  5419.             if they touch the same bit of path we can move linearly between them.
  5420.             Also, we can handle points correctly.
  5421.             Parameters:
  5422.                 end points and indices of limiting path, normal, length
  5423.             Returns:
  5424.                 list of toolpath points
  5425.                     each a list of 3 reals: x, y coordinates, radius
  5426.  
  5427.             """
  5428.             global nlLT, i, j, lmin
  5429.             x2=(x0+x1)/2
  5430.             y2=(y0+y1)/2
  5431.             j2,i2,r2=get_biggest( (x2,y2), (nx,ny))
  5432.             if length<lmin : return [ [x2, y2, r2] ]
  5433.             if j2==j0 and i2==i0 : #Same as left end. Don't subdivide this part any more
  5434.                 return [ [x2, y2, r2], line_divide((x2,y2),j2,i2,(x1,y1),j1,i1,(nx,ny),length/2)]
  5435.             if j2==j1 and i2==i1 : #Same as right end. Don't subdivide this part any more
  5436.                 return [ line_divide((x0,y0),j0,i0,(x2,y2),j2,i2,(nx,ny),length/2), [x2, y2, r2] ]
  5437.             return [ line_divide((x0,y0),j0,i0,(x2,y2),j2,i2,(nx,ny),length/2), line_divide((x2,y2),j2,i2,(x1,y1),j1,i1,(nx,ny),length/2)]
  5438.             #end of line_divide()
  5439.  
  5440.         def save_point((x,y),w,i,j,ii,jj):
  5441.             """LT Save this point and delete previous one if linear
  5442.  
  5443.             The point is, we generate tons of points but many may be in a straight 3D line.
  5444.             There is no benefit in saving the imtermediate points.
  5445.             """
  5446.             global wl, cspm
  5447.             x=round(x,2) #round to 2 decimals
  5448.             y=round(y,2) #round to 2 decimals
  5449.             w=round(w,2) #round to 2 decimals
  5450.             if len(cspm)>1 :
  5451.                 xy1a,xy1,xy1b,i1,j1,ii1,jj1=cspm[-1]
  5452.                 w1=wl[-1]
  5453.                 if i==i1 and j==j1 and ii==ii1 and jj==jj1 : #one match
  5454.                     xy1a,xy2,xy1b,i1,j1,ii1,jj1=cspm[-2]
  5455.                     w2=wl[-2]
  5456.                     if i==i1 and j==j1 and ii==ii1 and jj==jj1 : #two matches. Now test linearity
  5457.                         length1=math.hypot(xy1[0]-x,xy1[1]-y)
  5458.                         length2=math.hypot(xy2[0]-x,xy2[1]-y)
  5459.                         length12=math.hypot(xy2[0]-xy1[0],xy2[1]-xy1[1])
  5460.                         #get the xy distance of point 1 from the line 0-2
  5461.                         if length2>length1 and length2>length12 : #point 1 between them
  5462.                             xydist=abs( (xy2[0]-x)*(xy1[1]-y)-(xy1[0]-x)*(xy2[1]-y) )/length2
  5463.                             if xydist<engraving_tolerance : #so far so good
  5464.                                 wdist=w2+(w-w2)*length1/length2 -w1
  5465.                                 if abs(wdist)<engraving_tolerance :
  5466.                                     #print_("pop",j,i,xy1)
  5467.                                     cspm.pop()
  5468.                                     wl.pop()
  5469.             cspm+=[ [ [x,y],[x,y],[x,y],i,j,ii,jj ] ]
  5470.             wl+=[w]
  5471.             #end of save_point
  5472.  
  5473.         def draw_point((x0,y0),(x,y),w,t):
  5474.             """LT Draw this point as a circle with a 1px dot in the middle (x,y)
  5475.             and a 3D line from (x0,y0) down to x,y. 3D line thickness should be t/2
  5476.  
  5477.             Note that points that are subsequently erased as being unneeded do get
  5478.             displayed, but this helps the user see the total area covered.
  5479.             """
  5480.             global gcode_3Dleft ,gcode_3Dright
  5481.             if self.options.engraving_draw_calculation_paths :
  5482.                 inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
  5483.                         {"gcodetools": "Engraving calculation toolpath", 'style':   "fill:#ff00ff; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;", inkex.addNS('cx','sodipodi'):     str(x), inkex.addNS('cy','sodipodi'):       str(y), inkex.addNS('rx','sodipodi'):   str(1), inkex.addNS('ry','sodipodi'): str(1), inkex.addNS('type','sodipodi'):   'arc'})
  5484.                 #Don't draw zero radius circles
  5485.                 if w:
  5486.                     inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
  5487.                         {"gcodetools": "Engraving calculation paths", 'style'"fill:none; fill-opacity:0.46; stroke:#000000; stroke-width:0.1;", inkex.addNS('cx','sodipodi'):        str(x), inkex.addNS('cy','sodipodi'):       str(y),inkex.addNS('rx','sodipodi'):        str(w), inkex.addNS('ry','sodipodi'):       str(w), inkex.addNS('type','sodipodi'): 'arc'})
  5488.                     # Find slope direction for shading
  5489.                     s=math.atan2(y-y0,x-x0) #-pi to pi
  5490.                     # convert to 2 hex digits as a shade of red
  5491.                     s2="#{0:x}0000".format(int(101*(1.5-math.sin(s+0.5))))
  5492.                     inkex.etree.SubElement( gcode_3Dleft , inkex.addNS('path','svg'),
  5493.                     { "d": "M %f,%f L %f,%f" %(x0-eye_dist,y0,x-eye_dist-0.14*w,y),
  5494.                         'style': "stroke:" + s2 + "; stroke-opacity:1; stroke-width:" + str(t/2) +" ; fill:none",
  5495.                         "gcodetools": "Gcode G1R"
  5496.                     })
  5497.                     inkex.etree.SubElement( gcode_3Dright , inkex.addNS('path','svg'),
  5498.                     { "d": "M %f,%f L %f,%f" %(x0+eye_dist,y0,x+eye_dist+0.14*r,y),
  5499.                         'style': "stroke:" + s2 + "; stroke-opacity:1; stroke-width:" + str(t/2) +" ; fill:none",
  5500.                         "gcodetools": "Gcode G1L"
  5501.                     })
  5502.             #end of draw_point
  5503.  
  5504.         #end of subfunction definitions. engraving() starts here:
  5505.         gcode = ''
  5506.         r,w, wmax = 0,0,0 #theoretical and tool-radius-limited radii in pixels
  5507.         x1,y1,nx,ny =0,0,0,0
  5508.         cspe =[]
  5509.         we = []
  5510.         if len(self.selected_paths)<=0:
  5511.             self.error(_("Please select at least one path to engrave and run again."),"warning")
  5512.             return
  5513.         if not self.check_dir() : return
  5514.         #Find what units the user uses
  5515.         unit=" mm"
  5516.         if self.options.unit == "G20 (All units in inches)" :
  5517.             unit=" inches"
  5518.         elif self.options.unit != "G21 (All units in mm)" :
  5519.             self.error(_("Unknown unit selected. mm assumed"),"warning")
  5520.         print_("engraving_max_dist mm/inch", self.options.engraving_max_dist )
  5521.  
  5522.         #LT See if we can use this parameter for line and Bezier subdivision:
  5523.         bitlen=20/self.options.engraving_newton_iterations
  5524.  
  5525.         for layer in self.layers :
  5526.             if layer in self.selected_paths :
  5527.                 #Calculate scale in pixels per user unit (mm or inch)
  5528.                 p1=self.orientation_points[layer][0][0]
  5529.                 p2=self.orientation_points[layer][0][1]
  5530.                 ol=math.hypot(p1[0][0]-p2[0][0],p1[0][1]-p2[0][1])
  5531.                 oluu=math.hypot(p1[1][0]-p2[1][0],p1[1][1]-p2[1][1])
  5532.                 print_("Orientation2 p1 p2 ol oluu",p1,p2,ol,oluu)
  5533.                 orientation_scale = ol/oluu
  5534.  
  5535.                 self.set_tool(layer)
  5536.                 shape = self.tools[layer][0]['shape']
  5537.                 if re.search('w', shape) :
  5538.                     toolshape = eval('lambda w: ' + shape.strip('"'))
  5539.                 else:
  5540.                     self.error(_("Tool '%s' has no shape. 45 degree cone assumed!") % self.tools[layer][0]['name'],"Continue")
  5541.                     toolshape = lambda w: w
  5542.                 #Get tool radius in pixels
  5543.                 toolr=self.tools[layer][0]['diameter'] * orientation_scale/2
  5544.                 print_("tool radius in pixels=", toolr)
  5545.                 #max dist from path to engrave in user's units
  5546.                 max_distuu = min(self.tools[layer][0]['diameter']/2, self.options.engraving_max_dist)
  5547.                 max_dist=max_distuu*orientation_scale
  5548.                 print_("max_dist pixels", max_dist )
  5549.  
  5550.                 engraving_group = inkex.etree.SubElement( self.selected_paths[layer][0].getparent(), inkex.addNS('g','svg') )
  5551.                 if self.options.engraving_draw_calculation_paths and (self.my3Dlayer == None) :
  5552.                     self.my3Dlayer=inkex.etree.SubElement(self.document.getroot(), 'g') #Create a generic element at root level
  5553.                     self.my3Dlayer.set(inkex.addNS('label', 'inkscape'), "3D") #Gives it a name
  5554.                     self.my3Dlayer.set(inkex.addNS('groupmode', 'inkscape'), 'layer') #Tells Inkscape it's a layer
  5555.                 #Create groups for left and right eyes
  5556.                 if self.options.engraving_draw_calculation_paths :
  5557.                     gcode_3Dleft = inkex.etree.SubElement(self.my3Dlayer, inkex.addNS('g','svg'), {"gcodetools":"Gcode 3D L"})
  5558.                     gcode_3Dright = inkex.etree.SubElement(self.my3Dlayer, inkex.addNS('g','svg'), {"gcodetools":"Gcode 3D R"})
  5559.  
  5560.                 for node in self.selected_paths[layer] :   
  5561.                     if node.tag == inkex.addNS('path','svg'):
  5562.                         cspi = cubicsuperpath.parsePath(node.get('d'))
  5563.                         #LT: Create my own list. n1LT[j] is for subpath j
  5564.                         nlLT = []
  5565.                         for j in xrange(len(cspi)): #LT For each subpath...
  5566.                             # Remove zero length segments, assume closed path
  5567.                             i = 0 #LT was from i=1
  5568.                             while i<len(cspi[j]):
  5569.                                 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:
  5570.                                     cspi[j][i-1][2] = cspi[j][i][2]
  5571.                                     del cspi[j][i]
  5572.                                 else:
  5573.                                     i += 1
  5574.                         for csp in cspi: #LT6a For each subpath...
  5575.                             #Create copies in 3D layer
  5576.                             print_("csp is zz ",csp)
  5577.                             cspl=[]
  5578.                             cspr=[]
  5579.                             #create list containing lines and points, starting with a point
  5580.                             # line members: [x,y],[nx,ny],False,i
  5581.                             # x,y is start of line. Normal on engraved side.
  5582.                             # Normal is normalised (unit length)
  5583.                             #Note that Y axis increases down the page
  5584.                             # corner members: [x,y],[nx,ny],True,sin(halfangle)
  5585.                             # if halfangle>0: radius 0 here. normal is bisector
  5586.                             # if halfangle<0. reflex angle. normal is bisector
  5587.                             # corner normals are divided by cos(halfangle)
  5588.                             #so that they will engrave correctly
  5589.                             print_("csp is",csp)
  5590.                             nlLT.append ([])
  5591.                             for i in range(0,len(csp)): #LT for each point
  5592.                                 #n = []
  5593.                                 sp0, sp1, sp2 = csp[i-2], csp[i-1], csp[i]
  5594.                                 if self.options.engraving_draw_calculation_paths:
  5595.                                     #Copy it to 3D layer objects
  5596.                                     spl=[]
  5597.                                     spr=[]
  5598.                                     for j in range(0,3) :
  5599.                                         pl=[sp2[j][0]-eye_dist,sp2[j][1]]
  5600.                                         pr=[sp2[j][0]+eye_dist,sp2[j][1]]
  5601.                                         spl+=[pl]
  5602.                                         spr+=[pr]
  5603.                                     cspl+=[spl]
  5604.                                     cspr+=[spr]                
  5605.                                 #LT find angle between this and previous segment
  5606.                                 x0,y0 = sp1[1]
  5607.                                 nx1,ny1 = csp_normalized_normal(sp1,sp2,0)
  5608.                                 #I don't trust this function, so test result
  5609.                                 if abs(1-math.hypot(nx1,ny1))> 0.00001 :
  5610.                                     print_("csp_normalised_normal error t=0",nx1,ny1,sp1,sp2)
  5611.                                     self.error(_("csp_normalised_normal error. See log."),"warning")
  5612.  
  5613.                                 nx0, ny0 = csp_normalized_normal(sp0,sp1,1)
  5614.                                 if abs(1-math.hypot(nx0,ny0))> 0.00001 :
  5615.                                     print_("csp_normalised_normal error t=1",nx0,ny0,sp1,sp2)
  5616.                                     self.error(_("csp_normalised_normal error. See log."),"warning")
  5617.                                 bx,by,s=bisect((nx0,ny0),(nx1,ny1))
  5618.                                 #record x,y,normal,ifCorner, sin(angle-turned/2)
  5619.                                 nlLT[-1] += [[ [x0,y0],[bx,by], True, s]]
  5620.  
  5621.                                 #LT now do the line
  5622.                                 if sp1[1]==sp1[2] and sp2[0]==sp2[1] : #straightline
  5623.                                     nlLT[-1]+=[[sp1[1],[nx1,ny1],False,i]]
  5624.                                 else : #Bezier. First, recursively cut it up:
  5625.                                     nn=bez_divide(sp1[1],sp1[2],sp2[0],sp2[1])
  5626.                                     first=True #Flag entry to divided Bezier
  5627.                                     for bLT in nn : #save as two line segments
  5628.                                         for seg in range(3) :
  5629.                                             if seg>0 or first :
  5630.                                                 nx1=bLT[seg][1]-bLT[seg+1][1]
  5631.                                                 ny1=bLT[seg+1][0]-bLT[seg][0]
  5632.                                                 l1=math.hypot(nx1,ny1)
  5633.                                                 if l1<engraving_tolerance :
  5634.                                                     continue
  5635.                                                 nx1=nx1/l1 #normalise them
  5636.                                                 ny1=ny1/l1
  5637.                                                 nlLT[-1]+=[[bLT[seg],[nx1,ny1], False,i]]
  5638.                                                 first=False
  5639.                                             if seg<2 : #get outgoing bisector
  5640.                                                 nx0=nx1
  5641.                                                 ny0=ny1
  5642.                                                 nx1=bLT[seg+1][1]-bLT[seg+2][1]
  5643.                                                 ny1=bLT[seg+2][0]-bLT[seg+1][0]
  5644.                                                 l1=math.hypot(nx1,ny1)
  5645.                                                 if l1<engraving_tolerance :
  5646.                                                     continue
  5647.                                                 nx1=nx1/l1 #normalise them
  5648.                                                 ny1=ny1/l1
  5649.                                                 #bisect
  5650.                                                 bx,by,s=bisect((nx0,ny0),(nx1,ny1))
  5651.                                                 nlLT[-1] += [[bLT[seg+1],[bx,by], True, 0.]]
  5652.                             #LT for each segment - ends here.
  5653.                             print_(("engraving_draw_calculation_paths=",self.options.engraving_draw_calculation_paths))
  5654.                             if self.options.engraving_draw_calculation_paths:
  5655.                                 #Copy complete paths to 3D layer
  5656.                                 #print_("cspl",cspl)
  5657.                                 cspl+=[cspl[0]] #Close paths
  5658.                                 cspr+=[cspr[0]] #Close paths
  5659.                                 inkex.etree.SubElement( gcode_3Dleft , inkex.addNS('path','svg'),
  5660.                                 { "d": cubicsuperpath.formatPath([cspl]),
  5661.                                 'style': "stroke:#808080; stroke-opacity:1; stroke-width:0.6; fill:none",
  5662.                                 "gcodetools": "G1L outline"
  5663.                                 })
  5664.                                 inkex.etree.SubElement( gcode_3Dright , inkex.addNS('path','svg'),
  5665.                                 { "d": cubicsuperpath.formatPath([cspr]),
  5666.                                 'style': "stroke:#808080; stroke-opacity:1; stroke-width:0.6; fill:none",
  5667.                                 "gcodetools": "G1L outline"
  5668.                                 })
  5669.  
  5670.                                 for p in nlLT[-1]: #For last sub-path
  5671.                                     if p[2]: inkex.etree.SubElement(    engraving_group, inkex.addNS('path','svg'),
  5672.                                         { "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),
  5673.                                             'style':    "stroke:#f000af; stroke-opacity:0.46; stroke-width:0.1; fill:none",
  5674.                                             "gcodetools": "Engraving normals"
  5675.                                         })             
  5676.                                     else: inkex.etree.SubElement(   engraving_group, inkex.addNS('path','svg'),
  5677.                                         { "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),
  5678.                                             'style':    "stroke:#0000ff; stroke-opacity:0.46; stroke-width:0.1; fill:none",
  5679.                                             "gcodetools": "Engraving bisectors"
  5680.                                         })             
  5681.  
  5682.  
  5683.                         #LT6a build nlLT[j] for each subpath - ends here
  5684.                         #for nnn in nlLT :
  5685.                             #print_("nlLT",nnn) #LT debug stuff
  5686.                         # Calculate offset points
  5687.                         reflex=False
  5688.                         for j in xrange(len(nlLT)): #LT6b for each subpath
  5689.                             cspm=[] #Will be my output. List of csps.
  5690.                             wl=[] #Will be my w output list
  5691.                             w = r = 0 #LT initial, as first point is an angle
  5692.                             for i in xrange(len(nlLT[j])) : #LT for each node
  5693.                                 #LT Note: Python enables wrapping of array indices
  5694.                                 # backwards to -1, -2, but not forwards. Hence:
  5695.                                 n0 = nlLT[j][i-2] #previous node
  5696.                                 n1 = nlLT[j][i-1] #current node
  5697.                                 n2 = nlLT[j][i] #next node
  5698.                                 #if n1[2] == True and n1[3]==0 : # A straight angle
  5699.                                     #continue
  5700.                                 x1a,y1a = n1[0] #this point/start of this line
  5701.                                 nx,ny = n1[1]
  5702.                                 x1b,y1b = n2[0] #next point/end of this line
  5703.                                 if n1[2] == True : # We're at a corner
  5704.                                     bits=1
  5705.                                     bit0=0
  5706.                                     #lastr=r #Remember r from last line
  5707.                                     lastw=w #Remember w from last line
  5708.                                     w = max_dist
  5709.                                     if n1[3]>0 : #acute. Limit radius
  5710.                                         len1=math.hypot( (n0[0][0]-n1[0][0]),( n0[0][1]-n1[0][1]) )
  5711.                                         if i<(len(nlLT[j])-1) :
  5712.                                             len2=math.hypot( (nlLT[j][i+1][0][0]-n1[0][0]),(nlLT[j][i+1][0][1]-n1[0][1]) )
  5713.                                         else:
  5714.                                             len2=math.hypot( (nlLT[j][0][0][0]-n1[0][0]),(nlLT[j][0][0][1]-n1[0][1]) )
  5715.                                         #set initial r value, not to be exceeded
  5716.                                         w = math.sqrt(min(len1,len2))/n1[3]
  5717.                                 else: #line. Cut it up if long.
  5718.                                     if n0[3]>0 and not self.options.engraving_draw_calculation_paths :
  5719.                                         bit0=r*n0[3] #after acute corner
  5720.                                     else : bit0=0.0
  5721.                                     length=math.hypot((x1b-x1a),(y1a-y1b))
  5722.                                     bit0=(min(length,bit0))
  5723.                                     bits=int((length-bit0)/bitlen)
  5724.                                     #split excess evenly at both ends
  5725.                                     bit0+=(length-bit0-bitlen*bits)/2
  5726.                                     #print_("j,i,r,bit0,bits",j,i,w,bit0,bits)
  5727.                                 for b in xrange(bits) : #divide line into bits
  5728.                                     x1=x1a+ny*(b*bitlen+bit0)
  5729.                                     y1=y1a-nx*(b*bitlen+bit0)
  5730.                                     jjmin,iimin,w=get_biggest( (x1,y1), (nx,ny))
  5731.                                     print_("i,j,jjmin,iimin,w",i,j,jjmin,iimin,w)
  5732.                                     #w = min(r, toolr)
  5733.                                     wmax=max(wmax,w)
  5734.                                     if reflex : #just after a reflex corner
  5735.                                         reflex = False
  5736.                                         if w<lastw : #need to adjust it
  5737.                                             draw_point((x1,y1),(n0[0][0]+n0[1][0]*w,n0[0][1]+n0[1][1]*w),w, (lastw-w)/2)
  5738.                                             save_point((n0[0][0]+n0[1][0]*w,n0[0][1]+n0[1][1]*w),w,i,j,iimin,jjmin)
  5739.                                     if n1[2] == True : # We're at a corner
  5740.                                         if n1[3]>0 : #acute
  5741.                                             save_point((x1+nx*w,y1+ny*w),w,i,j,iimin,jjmin)
  5742.                                             draw_point((x1,y1),(x1,y1),0,0)
  5743.                                             save_point((x1,y1),0,i,j,iimin,jjmin)
  5744.                                         elif n1[3]<0 : #reflex
  5745.                                             if w>lastw :
  5746.                                                 draw_point((x1,y1),(x1+nx*lastw,y1+ny*lastw),w, (w-lastw)/2)
  5747.                                                 wmax=max(wmax,w)
  5748.                                                 save_point((x1+nx*w,y1+ny*w),w,i,j,iimin,jjmin)
  5749.                                     elif b>0 and n2[3]>0 and not self.options.engraving_draw_calculation_paths : #acute corner coming up
  5750.                                         if jjmin==j and iimin==i+2 : break
  5751.                                     draw_point((x1,y1),(x1+nx*w,y1+ny*w),w, bitlen)
  5752.                                     save_point((x1+nx*w,y1+ny*w),w,i,j,iimin,jjmin)
  5753.  
  5754.                                 #LT end of for each bit of this line
  5755.                                 if n1[2] == True and n1[3]<0 : #reflex angle
  5756.                                     reflex=True
  5757.                                 lastw = w #remember this w
  5758.                             #LT next i
  5759.                             cspm+=[cspm[0]]
  5760.                             print_("cspm",cspm)
  5761.                             wl+=[wl[0]]
  5762.                             print_("wl",wl)
  5763.                             #Note: Original csp_points was a list, each element
  5764.                             #being 4 points, with the first being the same as the
  5765.                             #last of the previous set.
  5766.                             #Each point is a list of [cx,cy,r,w]
  5767.                             #I have flattened it to a flat list of points.
  5768.                    
  5769.                             if self.options.engraving_draw_calculation_paths==True:
  5770.                                 node = inkex.etree.SubElement(  engraving_group, inkex.addNS('path','svg'),                                         {
  5771.                                                          "d":    cubicsuperpath.formatPath([cspm]),
  5772.                                                         'style':    styles["biarc_style_i"]['biarc1'],
  5773.                                                         "gcodetools": "Engraving calculation paths",
  5774.                                                     })
  5775.                                 for i in xrange(len(cspm)):
  5776.                                     inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
  5777.                                             {"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(wl[i]), inkex.addNS('ry','sodipodi'):       str(wl[i]), inkex.addNS('type','sodipodi'): 'arc'})
  5778.                             cspe += [cspm]
  5779.                             wluu = [] #width list in user units: mm/inches
  5780.                             for w in wl :
  5781.                                 wluu+=[ w / orientation_scale ]
  5782.                             print_("wl in pixels",wl)
  5783.                             print_("wl in user units",wluu)
  5784.                             #LT previously, we was in pixels so gave wrong depth
  5785.                             we  +=  [wluu]
  5786.                         #LT6b For each subpath - ends here         
  5787.                     #LT5 if it is a path - ends here
  5788.                     #print_("cspe",cspe)
  5789.                     #print_("we",we)
  5790.                 #LT4 for each selected object in this layer - ends here
  5791.  
  5792.                 if cspe!=[]:
  5793.                     curve = self.parse_curve(cspe, layer, we, toolshape) #convert to lines
  5794.                     self.draw_curve(curve, layer, engraving_group)
  5795.                     gcode += self.generate_gcode(curve, layer, self.options.Zsurface)
  5796.  
  5797.             #LT3 for layers loop ends here
  5798.         if gcode!='' :
  5799.             self.header+="(Tool diameter should be at least "+str(2*wmax/orientation_scale)+unit+ ")\n"
  5800.             self.header+="(Depth, as a function of radius w, must be "+ self.tools[layer][0]['shape']+ ")\n"
  5801.             self.header+="(Rapid feeds use safe Z="+ str(self.options.Zsafe) + unit + ")\n"
  5802.             self.header+="(Material surface at Z="+ str(self.options.Zsurface) + unit + ")\n"
  5803.             self.export_gcode(gcode)
  5804.         else :  self.error(_("No need to engrave sharp angles."),"warning")
  5805.  
  5806.  
  5807. ################################################################################
  5808. ###
  5809. ###     Orientation
  5810. ###
  5811. ################################################################################
  5812.     def orientation(self, layer=None) :
  5813.  
  5814.         if layer == None :
  5815.             layer = self.current_layer if self.current_layer is not None else self.document.getroot()
  5816.        
  5817.         transform = self.get_transforms(layer)
  5818.         if transform != [] :
  5819.             transform = self.reverse_transform(transform)
  5820.             transform = simpletransform.formatTransform(transform)
  5821.  
  5822.         if self.options.orientation_points_count == "graffiti" :
  5823.             print_(self.graffiti_reference_points)
  5824.             print_("Inserting graffiti points")
  5825.             if layer in self.graffiti_reference_points: graffiti_reference_points_count = len(self.graffiti_reference_points[layer])
  5826.             else: graffiti_reference_points_count = 0
  5827.             axis = ["X","Y","Z","A"][graffiti_reference_points_count%4]
  5828.             attr = {'gcodetools': "Gcodetools graffiti reference point"}
  5829.             if  transform != [] :
  5830.                 attr["transform"] = transform
  5831.             g = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), attr)
  5832.             inkex.etree.SubElement( g, inkex.addNS('path','svg'),
  5833.                 {
  5834.                     'style':    "stroke:none;fill:#00ff00;",    
  5835.                     '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' % (graffiti_reference_points_count*100, 0),
  5836.                     'gcodetools': "Gcodetools graffiti reference point arrow"
  5837.                 })
  5838.            
  5839.             draw_text(axis,graffiti_reference_points_count*100+10,-10, group = g, gcodetools_tag = "Gcodetools graffiti reference point text")
  5840.  
  5841.         elif self.options.orientation_points_count == "in-out reference point" :
  5842.             draw_pointer(group = self.current_layer, x = self.view_center, figure="arrow", pointer_type = "In-out reference point", text = "In-out point")
  5843.    
  5844.         else :
  5845.             print_("Inserting orientation points")
  5846.  
  5847.             if layer in self.orientation_points:
  5848.                 self.error(_("Active layer already has orientation points! Remove them or select another layer!"),"active_layer_already_has_orientation_points")
  5849.            
  5850.             attr = {"gcodetools":"Gcodetools orientation group"}
  5851.             if  transform != [] :
  5852.                 attr["transform"] = transform
  5853.  
  5854.             orientation_group = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), attr)
  5855.             doc_height = self.unittouu(self.document.getroot().get('height'))
  5856.             if self.document.getroot().get('height') == "100%" :
  5857.                 doc_height = 1052.3622047
  5858.                 print_("Overruding height from 100 percents to %s" % doc_height)
  5859.             if self.options.unit == "G21 (All units in mm)" :
  5860.                 points = [[0.,0.,self.options.Zsurface],[100.,0.,self.options.Zdepth],[0.,100.,0.]]
  5861.                 orientation_scale = 3.5433070660
  5862.                 print_("orientation_scale < 0 ===> switching to mm units=%0.10f"%orientation_scale )
  5863.             elif self.options.unit == "G20 (All units in inches)" :
  5864.                 points = [[0.,0.,self.options.Zsurface],[5.,0.,self.options.Zdepth],[0.,5.,0.]]
  5865.                 orientation_scale = 90
  5866.                 print_("orientation_scale < 0 ===> switching to inches units=%0.10f"%orientation_scale )
  5867.             if self.options.orientation_points_count == "2" :
  5868.                 points = points[:2]
  5869.             print_(("using orientation scale",orientation_scale,"i=",points))
  5870.             for i in points :
  5871.                 si = [i[0]*orientation_scale, i[1]*orientation_scale]
  5872.                 g = inkex.etree.SubElement(orientation_group, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools orientation point (%s points)" % self.options.orientation_points_count})
  5873.                 inkex.etree.SubElement( g, inkex.addNS('path','svg'),
  5874.                     {
  5875.                         'style':    "stroke:none;fill:#000000;",    
  5876.                         '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),
  5877.                         'gcodetools': "Gcodetools orientation point arrow"
  5878.                     })
  5879.  
  5880.                 draw_text("(%s; %s; %s)" % (i[0],i[1],i[2]), (si[0]+10), (-si[1]-10+doc_height), group = g, gcodetools_tag = "Gcodetools orientation point text")
  5881.  
  5882.        
  5883. ################################################################################
  5884. ###
  5885. ###     Tools library
  5886. ###
  5887. ################################################################################
  5888.     def tools_library(self, layer=None) :
  5889.         # Add a tool to the drawing
  5890.         if layer == None :
  5891.             layer = self.current_layer if self.current_layer is not None else self.document.getroot()
  5892.         if layer in self.tools:
  5893.             self.error(_("Active layer already has a tool! Remove it or select another layer!"),"active_layer_already_has_tool")
  5894.  
  5895.         if self.options.tools_library_type == "pen one" :
  5896.             tool = {
  5897.                     "name": "Pen 1",
  5898.                     "id": "Pen 0001",
  5899.                     "diameter":10.,
  5900.                     "shape": "10",
  5901.                     "penetration angle":90.,
  5902.                     "penetration feed":3000.,
  5903.                     "depth step":1.,
  5904.                     "feed":1200.,
  5905.                     "in trajectotry":"",
  5906.                     "out trajectotry":"",
  5907.                     "gcode before path":"M280 P0 S80 ; pen up before path",
  5908.                     "gcode after path":"M280 P0 S50 ; pen down after path",
  5909.                     "sog":"",
  5910.                     "spinlde rpm":"",
  5911.                     "CW or CCW":"",
  5912.                     "tool change gcode":"""
  5913. M400
  5914. M280 P0 S70 ; lower pen into tool bay
  5915. G1 X0 ; pull carriage into safe zone
  5916. G1 Y230 ; move carriage to center of TOOL 1 bay
  5917. G1 X-25 ; move the carriage into the tool 1 bay
  5918. M400
  5919. M280 P0 S40 ; move pen mount up to lift up the tool and start the print
  5920. M400
  5921. G1 X30
  5922.                     """,
  5923.                     "4th axis meaning": " ",
  5924.                     "4th axis scale": 1.,
  5925.                     "4th axis offset": 0.,
  5926.                     "passing feed":"4800",                 
  5927.                     "fine feed":"1200",
  5928.             }
  5929.         elif self.options.tools_library_type == "pen two" :
  5930.             tool = {
  5931.                     "name": "Pen 2",
  5932.                     "id": "Pen 0002",
  5933.                     "diameter":10.,
  5934.                     "shape": "10",
  5935.                     "penetration angle":90.,
  5936.                     "penetration feed":3000.,
  5937.                     "depth step":1.,
  5938.                     "feed":1200.,
  5939.                     "in trajectotry":"",
  5940.                     "out trajectotry":"",
  5941.                     "gcode before path":"M280 P0 S80 ; pen up before path",
  5942.                     "gcode after path":"M280 P0 S50 ; pen down after path",
  5943.                     "sog":"",
  5944.                     "spinlde rpm":"",
  5945.                     "CW or CCW":"",
  5946.                     "tool change gcode":"""
  5947. M400
  5948. M280 P0 S40 ; pen up for tool change
  5949. M400
  5950. G1 X0 ; carriage into safe zone for tool change to happen
  5951. G1 Y230 ; move carriage to center of last TOOL
  5952. G1 X-25 ; move the carriage into the tool bay
  5953. M400
  5954. M280 P0 S70 ; lower pen into tool bay
  5955. G1 X0 ; pull carriage back into safe zone for the tool to disconnect
  5956. G1 Y170 ; move carriage to center of next TOOL
  5957. G1 X-25 ; move the carriage into the tool 1 bay
  5958. M400
  5959. M280 P0 S40 ; move pen mount up to lift up the next tool and continue print
  5960. M400
  5961. G1 X30
  5962.                     """,
  5963.                     "4th axis meaning": " ",
  5964.                     "4th axis scale": 1.,
  5965.                     "4th axis offset": 0.,
  5966.                     "passing feed":"4800",                 
  5967.                     "fine feed":"1200",
  5968.             }
  5969.         elif self.options.tools_library_type == "pen three" :
  5970.             tool = {
  5971.                     "name": "Pen 3",
  5972.                     "id": "Pen 0003",
  5973.                     "diameter":10.,
  5974.                     "shape": "10",
  5975.                     "penetration angle":90.,
  5976.                     "penetration feed":3000.,
  5977.                     "depth step":1.,
  5978.                     "feed":1200.,
  5979.                     "in trajectotry":"",
  5980.                     "out trajectotry":"",
  5981.                     "gcode before path":"M280 P0 S80 ; pen up before path",
  5982.                     "gcode after path":"M280 P0 S50 ; pen down after path",
  5983.                     "sog":"",
  5984.                     "spinlde rpm":"",
  5985.                     "CW or CCW":"",
  5986.                     "tool change gcode":"""
  5987. M400
  5988. M280 P0 S40 ; pen up for tool change
  5989. M400
  5990. G1 X0 ; carriage into safe zone for tool change to happen
  5991. G1 Y170 ; move carriage to center of last TOOL
  5992. G1 X-25 ; move the carriage into the tool bay
  5993. M400
  5994. M280 P0 S70 ; lower pen into tool bay
  5995. G1 X0 ; pull carriage back into safe zone for the tool to disconnect
  5996. G1 Y110 ; move carriage to center of next TOOL
  5997. G1 X-25 ; move the carriage into the tool 1 bay
  5998. M400
  5999. M280 P0 S40 ; move pen mount up to lift up the next tool and continue print
  6000. M400
  6001. G1 X30
  6002.                     """,
  6003.                     "4th axis meaning": " ",
  6004.                     "4th axis scale": 1.,
  6005.                     "4th axis offset": 0.,
  6006.                     "passing feed":"4800",                 
  6007.                     "fine feed":"1200",
  6008.             }
  6009.         elif self.options.tools_library_type == "cylinder cutter" :
  6010.             tool = {
  6011.                     "name": "Cylindrical cutter",
  6012.                     "id": "Cylindrical cutter 0001",
  6013.                     "diameter":10,
  6014.                     "penetration angle":90,
  6015.                     "feed":"400",
  6016.                     "penetration feed":"100",
  6017.                     "depth step":"1",
  6018.                     "tool change gcode":" "
  6019.             }
  6020.         elif self.options.tools_library_type == "lathe cutter" :
  6021.             tool = {
  6022.                     "name": "Lathe cutter",
  6023.                     "id": "Lathe cutter 0001",
  6024.                     "diameter":10,
  6025.                     "penetration angle":90,
  6026.                     "feed":"400",
  6027.                     "passing feed":"800",
  6028.                     "fine feed":"100",
  6029.                     "penetration feed":"100",
  6030.                     "depth step":"1",
  6031.                     "tool change gcode":" "
  6032.             }
  6033.         elif self.options.tools_library_type == "cone cutter"
  6034.             tool = {
  6035.                     "name": "Cone cutter",
  6036.                     "id": "Cone cutter 0001",
  6037.                     "diameter":10,
  6038.                     "shape":"w",
  6039.                     "feed":"400",
  6040.                     "penetration feed":"100",
  6041.                     "depth step":"1",
  6042.                     "tool change gcode":" "
  6043.             }
  6044.         elif self.options.tools_library_type == "tangent knife":   
  6045.             tool = {
  6046.                     "name": "Tangent knife",
  6047.                     "id": "Tangent knife 0001",
  6048.                     "feed":"400",
  6049.                     "penetration feed":"100",
  6050.                     "depth step":"100",
  6051.                     "4th axis meaning": "tangent knife",
  6052.                     "4th axis scale": 1.,
  6053.                     "4th axis offset": 0,
  6054.                     "tool change gcode":" "
  6055.             }
  6056.            
  6057.         elif self.options.tools_library_type == "plasma cutter":   
  6058.             tool = {
  6059.                 "name": "Plasma cutter",
  6060.                 "id": "Plasma cutter 0001",
  6061.                 "diameter":10,
  6062.                 "penetration feed":100,
  6063.                 "feed":400,
  6064.                 "gcode before path":"""G31 Z-100 F500 (find metal)
  6065. G92 Z0 (zero z)
  6066. G00 Z10 F500 (going up)
  6067. M03 (turn on plasma)
  6068. G04 P0.2 (pause)
  6069. G01 Z1 (going to cutting z)\n""",
  6070.                 "gcode after path":"M05 (turn off plasma)\n",
  6071.             }
  6072.         elif self.options.tools_library_type == "graffiti":
  6073.             tool = {
  6074.                 "name": "Graffiti",
  6075.                 "id": "Graffiti 0001",
  6076.                 "diameter":10,
  6077.                 "penetration feed":100,
  6078.                 "feed":400,
  6079.                 "gcode before path":"""M03 S1(Turn spray on)\n """,
  6080.                 "gcode after path":"M05 (Turn spray off)\n ",
  6081.                 "tool change gcode":"(Add G00 here to change sprayer if needed)\n",
  6082.                
  6083.             }
  6084.  
  6085.         else :
  6086.             tool = self.default_tool
  6087.        
  6088.         tool_num = sum([len(self.tools[i]) for i in self.tools])
  6089.         colors = ["00ff00","0000ff","ff0000","fefe00","00fefe", "fe00fe", "fe7e00", "7efe00", "00fe7e", "007efe", "7e00fe", "fe007e"]
  6090.        
  6091.         tools_group = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools tool definition"})
  6092.         bg = inkex.etree.SubElement(    tools_group, inkex.addNS('path','svg'),
  6093.                     {'style':   "fill:#%s;fill-opacity:0.5;stroke:#444444; stroke-width:1px;"%colors[tool_num%len(colors)], "gcodetools":"Gcodetools tool background"})
  6094.  
  6095.         y = 0
  6096.         keys = []
  6097.         for key in self.tools_field_order:
  6098.             if key in tool: keys += [key]
  6099.         for key in tool:
  6100.             if key not in keys: keys += [key]
  6101.         for key in keys :
  6102.             g = inkex.etree.SubElement(tools_group, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools tool parameter"})
  6103.             draw_text(key, 0, y, group = g, gcodetools_tag = "Gcodetools tool definition field name", font_size = 10 if key!='name' else 20)
  6104.             param = tool[key]
  6105.             if type(param)==str and re.match("^\s*$",param) : param = "(None)"
  6106.             draw_text(param, 150, y, group = g, gcodetools_tag = "Gcodetools tool definition field value", font_size = 10 if key!='name' else 20)
  6107.             v = str(param).split("\n")
  6108.             y += 15*len(v) if key!='name' else 20*len(v)
  6109.  
  6110.         bg.set('d',"m -20,-20 l 400,0 0,%f -400,0 z " % (y+50))
  6111.         tool = []
  6112.         tools_group.set("transform", simpletransform.formatTransform([ [1,0,self.view_center[0]-150 ], [0,1,self.view_center[1]] ] ))
  6113.  
  6114.  
  6115. ################################################################################
  6116. ###
  6117. ###     Check tools and OP asignment
  6118. ###
  6119. ################################################################################
  6120.     def check_tools_and_op(self):
  6121.         if len(self.selected)<=0 :
  6122.             self.error(_("Selection is empty! Will compute whole drawing."),"selection_is_empty_will_comupe_drawing")
  6123.             paths = self.paths
  6124.         else :
  6125.             paths = self.selected_paths
  6126.         #   Set group
  6127.         group = inkex.etree.SubElement( self.selected_paths.keys()[0] if len(self.selected_paths.keys())>0 else self.layers[0], inkex.addNS('g','svg') )
  6128.         trans_ = [[1,0.3,0],[0,0.5,0]] 
  6129.  
  6130.         self.set_markers()
  6131.  
  6132.         bounds = [float('inf'),float('inf'),float('-inf'),float('-inf')]
  6133.         tools_bounds = {}
  6134.         for layer in self.layers :
  6135.             if layer in paths :
  6136.                 self.set_tool(layer)
  6137.                 tool = self.tools[layer][0]
  6138.                 tools_bounds[layer] = tools_bounds[layer] if layer in tools_bounds else [float("inf"),float("-inf")]
  6139.                 style = simplestyle.formatStyle(tool["style"])
  6140.                 for path in paths[layer] :
  6141.                     style = "fill:%s; fill-opacity:%s; stroke:#000044; stroke-width:1; marker-mid:url(#CheckToolsAndOPMarker);" % (
  6142.                     tool["style"]["fill"] if "fill" in tool["style"] else "#00ff00",
  6143.                     tool["style"]["fill-opacity"] if "fill-opacity" in tool["style"] else "0.5")
  6144.                     group.insert( 0, inkex.etree.Element(path.tag, path.attrib))
  6145.                     new = group.getchildren()[0]
  6146.                     new.set("style", style)
  6147.                    
  6148.                     trans = self.get_transforms(path)
  6149.                     trans = simpletransform.composeTransform( trans_, trans if trans != [] else [[1.,0.,0.],[0.,1.,0.]])
  6150.                     csp = cubicsuperpath.parsePath(path.get("d"))
  6151.                     simpletransform.applyTransformToPath(trans,csp)
  6152.                     path_bounds = csp_simple_bound(csp)
  6153.                     trans = simpletransform.formatTransform(trans)
  6154.                     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])]
  6155.                     tools_bounds[layer] = [min(tools_bounds[layer][0], path_bounds[1]), max(tools_bounds[layer][1], path_bounds[3])]
  6156.  
  6157.                     new.set("transform", trans)
  6158.                     trans_[1][2] += 20
  6159.                 trans_[1][2] += 100
  6160.  
  6161.         for layer in self.layers :
  6162.             if layer in self.tools :
  6163.                 if layer in tools_bounds :
  6164.                     tool = self.tools[layer][0]
  6165.                     g = copy.deepcopy(tool["self_group"])
  6166.                     g.attrib["gcodetools"] = "Check tools and OP asignment"
  6167.                     trans = [[1,0.3,bounds[2]],[0,0.5,tools_bounds[layer][0]]] 
  6168.                     g.set("transform",simpletransform.formatTransform(trans))
  6169.                     group.insert( 0, g )
  6170.  
  6171.  
  6172. ################################################################################
  6173. ###     TODO Launch browser on help tab
  6174. ################################################################################
  6175.     def help(self):
  6176.         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")
  6177.         return
  6178.  
  6179.  
  6180. ################################################################################
  6181. ###     Lathe
  6182. ################################################################################
  6183.     def generate_lathe_gcode(self, subpath, layer, feed_type) :
  6184.         if len(subpath) <2 : return ""
  6185.         feed = " F %f" % self.tool[feed_type]
  6186.         x,z = self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap
  6187.         flip_angle = -1 if x.lower()+z.lower() in ["xz", "yx", "zy"] else 1
  6188.         alias = {"X":"I", "Y":"J", "Z":"K", "x":"i", "y":"j", "z":"k"}
  6189.         i_, k_ = alias[x], alias[z]
  6190.         c = [ [subpath[0][1], "move", 0, 0, 0] ]
  6191.         #draw_csp(self.transform_csp([subpath],layer,True), color = "Orange", width = .1)
  6192.         for sp1,sp2 in zip(subpath,subpath[1:]) :
  6193.             c += biarc(sp1,sp2,0,0)
  6194.         for i in range(1,len(c)) : # Just in case check end point of each segment
  6195.             c[i-1][4] = c[i][0][:]
  6196.         c += [ [subpath[-1][1], "end", 0, 0, 0] ]          
  6197.         self.draw_curve(c, layer, style = styles["biarc_style_lathe_%s" % feed_type])
  6198.        
  6199.         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...
  6200.         for s in c :
  6201.             if s[1] == 'line':
  6202.                 gcode += ("G01 %s %f %s %f" % (x, s[4][0], z, s[4][1]) ) + feed + "\n"
  6203.             elif s[1] == 'arc':
  6204.                 r = [(s[2][0]-s[0][0]), (s[2][1]-s[0][1])]
  6205.                 if (r[0]**2 + r[1]**2)>self.options.min_arc_radius**2:
  6206.                     r1, r2 = (P(s[0])-P(s[2])), (P(s[4])-P(s[2]))
  6207.                     if abs(r1.mag()-r2.mag()) < 0.001 :
  6208.                         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"
  6209.                     else:
  6210.                         r = (r1.mag()+r2.mag())/2
  6211.                         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"
  6212.         return gcode
  6213.  
  6214.  
  6215.     def lathe(self):
  6216.         if not self.check_dir() : return
  6217.         x,z = self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap
  6218.         x = re.sub("^\s*([XYZxyz])\s*$",r"\1",x)
  6219.         z = re.sub("^\s*([XYZxyz])\s*$",r"\1",z)
  6220.         if x not in ["X", "Y", "Z", "x", "y", "z"] or z not in ["X", "Y", "Z", "x", "y", "z"] :
  6221.             self.error(_("Lathe X and Z axis remap should be 'X', 'Y' or 'Z'. Exiting..."),"warning")
  6222.             return
  6223.         if x.lower() == z.lower() :
  6224.             self.error(_("Lathe X and Z axis remap should be the same. Exiting..."),"warning")
  6225.             return
  6226.         if  x.lower()+z.lower() in ["xy","yx"] : gcode_plane_selection = "G17 (Using XY plane)\n"
  6227.         if  x.lower()+z.lower() in ["xz","zx"] : gcode_plane_selection = "G18 (Using XZ plane)\n"
  6228.         if  x.lower()+z.lower() in ["zy","yz"] : gcode_plane_selection = "G19 (Using YZ plane)\n"
  6229.         self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap = x, z
  6230.    
  6231.         paths = self.selected_paths
  6232.         self.tool = []
  6233.         gcode = ""
  6234.         for layer in self.layers :
  6235.             if layer in paths :
  6236.                 self.set_tool(layer)
  6237.                 if self.tool != self.tools[layer][0] :
  6238.                     self.tool = self.tools[layer][0]
  6239.                     self.tool["passing feed"]   = float(self.tool["passing feed"] if "passing feed" in self.tool else self.tool["feed"])
  6240.                     self.tool["feed"]           = float(self.tool["feed"])
  6241.                     self.tool["fine feed"]      = float(self.tool["fine feed"] if "fine feed" in self.tool else self.tool["feed"])
  6242.                     gcode += ( "(Change tool to %s)\n" % re.sub("\"'\(\)\\\\"," ",self.tool["name"]) ) + self.tool["tool change gcode"] + "\n"
  6243.                    
  6244.                 for path in paths[layer]:
  6245.                     csp = self.transform_csp(cubicsuperpath.parsePath(path.get("d")),layer)
  6246.                    
  6247.                     for subpath in csp :
  6248.                         # Offset the path if fine cut is defined.
  6249.                         fine_cut = subpath[:]
  6250.                         if self.options.lathe_fine_cut_width>0 :
  6251.                             r = self.options.lathe_fine_cut_width
  6252.                             if self.options.lathe_create_fine_cut_using == "Move path" :
  6253.                                 subpath = [ [ [i2[0],i2[1]+r] for i2 in i1] for i1 in subpath]
  6254.                             else :
  6255.                                 # Close the path to make offset correct
  6256.                                 bound = csp_simple_bound([subpath])
  6257.                                 minx,miny,maxx,maxy = csp_true_bounds([subpath])
  6258.                                 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] ] ])
  6259.                                 left,right = subpath[-1][1][0], subpath[0][1][0]
  6260.                                 if left>right : left, right = right,left
  6261.                                 offsetted_subpath = csp_offset([offsetted_subpath], r if not csp_subpath_ccw(offsetted_subpath) else -r )
  6262.                                 offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left,10], [left,0] )
  6263.                                 offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right,0], [right,10] )
  6264.                                 offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1]-r], [10, miny[1]-r] )
  6265.                                 #draw_csp(self.transform_csp(offsetted_subpath,layer,True), color = "Green", width = 1)
  6266.                                 # Join offsetted_subpath together
  6267.                                 # Hope there wont be any cicles
  6268.                                 subpath = csp_join_subpaths(offsetted_subpath)[0]
  6269.                        
  6270.                         # Create solid object from path and lathe_width
  6271.                         bound = csp_simple_bound([subpath])
  6272.                         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]
  6273.  
  6274.                         gcode += ("G01 %s %f F %f \n" % (z, top_start[1], self.tool["passing feed"]) )
  6275.                         gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
  6276.  
  6277.                         subpath = csp_concat_subpaths(csp_subpath_line_to([],[top_start,subpath[0][1]]), subpath)
  6278.                         subpath = csp_subpath_line_to(subpath,[top_end,top_start])
  6279.    
  6280.                        
  6281.                         width = max(0, self.options.lathe_width - max(0, bound[1]) )
  6282.                         step = self.tool['depth step']
  6283.                         steps = int(math.ceil(width/step))
  6284.                         for i in range(steps+1):
  6285.                             current_width = self.options.lathe_width - step*i
  6286.                             intersections = []
  6287.                             for j in range(1,len(subpath)) :
  6288.                                 sp1,sp2 = subpath[j-1], subpath[j]
  6289.                                 intersections += [[j,k] for k in csp_line_intersection([bound[0]-10,current_width], [bound[2]+10,current_width], sp1, sp2)]
  6290.                                 intersections += [[j,k] for k in csp_line_intersection([bound[0]-10,current_width+step], [bound[2]+10,current_width+step], sp1, sp2)]
  6291.                             parts = csp_subpath_split_by_points(subpath,intersections)
  6292.                             for part in parts :
  6293.                                 minx,miny,maxx,maxy = csp_true_bounds([part])
  6294.                                 y = (maxy[1]+miny[1])/2
  6295.                                 if y > current_width+step :
  6296.                                     gcode += self.generate_lathe_gcode(part,layer,"passing feed")
  6297.                                 elif current_width <= y <= current_width+step :
  6298.                                     gcode += self.generate_lathe_gcode(part,layer,"feed")
  6299.                                 else :
  6300.                                     # full step cut
  6301.                                     part = csp_subpath_line_to([], [part[0][1], part[-1][1]] )
  6302.                                     gcode += self.generate_lathe_gcode(part,layer,"feed")
  6303.                                    
  6304.                         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]
  6305.                         gcode += "\n(Fine cutting start)\n(Calculating fine cut using %s)\n"%self.options.lathe_create_fine_cut_using
  6306.                         for i in range(self.options.lathe_fine_cut_count) :
  6307.                             width = self.options.lathe_fine_cut_width*(1-float(i+1)/self.options.lathe_fine_cut_count )
  6308.                             if width == 0 :
  6309.                                 current_pass = fine_cut                        
  6310.                             else
  6311.                                 if self.options.lathe_create_fine_cut_using == "Move path" :
  6312.                                     current_pass = [ [ [i2[0],i2[1]+width] for i2 in i1] for i1 in fine_cut]
  6313.                                 else :
  6314.                                     minx,miny,maxx,maxy = csp_true_bounds([fine_cut])
  6315.                                     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] ] ])
  6316.                                     left,right = fine_cut[-1][1][0], fine_cut[0][1][0]
  6317.                                     if left>right : left, right = right,left
  6318.                                     offsetted_subpath = csp_offset([offsetted_subpath], width if not csp_subpath_ccw(offsetted_subpath) else -width )
  6319.                                     offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left,10], [left,0] )
  6320.                                     offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right,0], [right,10] )
  6321.                                     offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1]-r], [10, miny[1]-r] )
  6322.                                     current_pass = csp_join_subpaths(offsetted_subpath)[0]
  6323.  
  6324.  
  6325.                             gcode += "\n(Fine cut %i-th cicle start)\n"%(i+1)                  
  6326.                             gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
  6327.                             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"]) )
  6328.                             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"]) )
  6329.                        
  6330.                             gcode += self.generate_lathe_gcode(current_pass,layer,"fine feed")
  6331.                             gcode += ("G01 %s %f F %f \n" % (z, top_start[1], self.tool["passing feed"]) )
  6332.                             gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
  6333.    
  6334.         self.export_gcode(gcode)
  6335.        
  6336. ################################################################################
  6337. ###
  6338. ###     Lathe modify path
  6339. ###     Modifies path to fit current cutter. As for now straight rect cutter.
  6340. ###
  6341. ################################################################################
  6342.  
  6343.     def lathe_modify_path(self):
  6344.         if self.selected_paths == {} and self.options.auto_select_paths:
  6345.             paths=self.paths
  6346.             self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
  6347.         else :
  6348.             paths = self.selected_paths
  6349.  
  6350.         for layer in self.layers :
  6351.             if layer in paths :
  6352.                 width = self.options.lathe_rectangular_cutter_width
  6353.                 #self.set_tool(layer)
  6354.                 for path in paths[layer]:
  6355.                     csp = self.transform_csp(cubicsuperpath.parsePath(path.get("d")),layer)
  6356.                     new_csp = []
  6357.                     for subpath in csp:
  6358.                         orientation = subpath[-1][1][0]>subpath[0][1][0]
  6359.                         last_n = None
  6360.                         last_o = 0
  6361.                         new_subpath = []
  6362.  
  6363.                         # Split segment at x' and y' == 0
  6364.                         for sp1, sp2 in zip(subpath[:],subpath[1:]):
  6365.                             ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
  6366.                             roots = cubic_solver_real(0, 3*ax, 2*bx, cx)
  6367.                             roots += cubic_solver_real(0, 3*ay, 2*by, cy)
  6368.                             new_subpath = csp_concat_subpaths(new_subpath, csp_seg_split(sp1,sp2,roots))
  6369.                         subpath = new_subpath
  6370.                         new_subpath = []
  6371.                         first_seg = True
  6372.                         for sp1, sp2 in zip(subpath[:],subpath[1:]):
  6373.                             n = csp_normalized_normal(sp1,sp2,0)
  6374.                             a = math.atan2(n[0],n[1])
  6375.                             if a == 0 or a == math.pi :
  6376.                                 n = csp_normalized_normal(sp1,sp2,1)
  6377.                             a = math.atan2(n[0],n[1])                          
  6378.                             if a!=0 and a!=math.pi:
  6379.                                 o = 0 if 0<a<=math.pi/2 or -math.pi<a<-math.pi/2 else 1
  6380.                                 if not orientation: o = 1-o
  6381.                                
  6382.                                 # Add first horisontal straight line if needed
  6383.                                 if not first_seg and new_subpath==[] : new_subpath = [ [[subpath[0][i][0] - width*o ,subpath[0][i][1]] for i in range(3)] ]
  6384.                                
  6385.                                 new_subpath = csp_concat_subpaths(
  6386.                                         new_subpath,
  6387.                                         [
  6388.                                             [[sp1[i][0] - width*o ,sp1[i][1]] for i in range(3)],
  6389.                                             [[sp2[i][0] - width*o ,sp2[i][1]] for i in range(3)]
  6390.                                         ]
  6391.                                     )
  6392.                             first_seg = False
  6393.                            
  6394.                         # Add last horisontal straigth line if needed
  6395.                         if a==0 or a==math.pi :
  6396.                             new_subpath += [ [[subpath[-1][i][0] - width*o ,subpath[-1][i][1]] for i in range(3)] ]
  6397.  
  6398.                        
  6399.                     new_csp += [new_subpath]   
  6400.                     self.draw_csp(new_csp,layer)
  6401. #                              
  6402. #                               o = (1 if cross(n, [0,1])>0 else -1)*orientation
  6403. #                               new_subpath += [ [sp1[i][0] - width*o,sp1[i][1]] for i in range(3) ]
  6404. #                           n = csp_normalized_normal(sp1,sp2,1)
  6405. #                           o = (1 if cross(n, [0,1])>0 else -1)*orientation
  6406. #                           new_subpath += [ [sp2[i][0] - width*o,sp2[i][1]] for i in range(3) ]
  6407.                            
  6408.                            
  6409. ################################################################################
  6410. ###
  6411. ### Update function
  6412. ###
  6413. ### Gets file containing version information from the web and compaares it with.
  6414. ### current version.
  6415. ################################################################################
  6416.    
  6417.     def update(self) :
  6418.         try :
  6419.             import urllib
  6420.             f = urllib.urlopen("http://www.cnc-club.ru/gcodetools_latest_version", proxies = urllib.getproxies())
  6421.             a = f.read()
  6422.             for s in a.split("\n") :
  6423.                 r = re.search(r"Gcodetools\s+latest\s+version\s*=\s*(.*)",s)
  6424.                 if r :
  6425.                     ver = r.group(1).strip()
  6426.                     if ver != gcodetools_current_version :
  6427.                         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")                
  6428.                     else :
  6429.                         self.error("You are currently using latest stable version of Gcodetools.","Warning")                   
  6430.                     return
  6431.             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")                   
  6432.         except :
  6433.             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")                   
  6434.                
  6435.  
  6436.  
  6437. ################################################################################
  6438. ### Graffiti function generates Gcode for graffiti drawer
  6439. ################################################################################
  6440.     def graffiti(self) :
  6441.         # Get reference points.
  6442.        
  6443.         def get_gcode_coordinates(point,layer):
  6444.             gcode = '' 
  6445.             pos = []
  6446.             for ref_point in self.graffiti_reference_points[layer] :
  6447.                 c = math.sqrt((point[0]-ref_point[0][0])**2 + (point[1]-ref_point[0][1])**2)
  6448.                 gcode += " %s %f"%(ref_point[1], c)
  6449.                 pos += [c]
  6450.             return pos, gcode
  6451.        
  6452.        
  6453.         def graffiti_preview_draw_point(x1,y1,color,radius=.5):
  6454.             self.graffiti_preview = self.graffiti_preview
  6455.             r,g,b,a_ = color
  6456.             for x in range(int(x1-1-math.ceil(radius)), int(x1+1+math.ceil(radius)+1)):
  6457.                 for y in range(int(y1-1-math.ceil(radius)), int(y1+1+math.ceil(radius)+1)):
  6458.                     if x>=0 and y>=0 and y<len(self.graffiti_preview) and x*4<len(self.graffiti_preview[0]) :
  6459.                         d = math.sqrt( (x1-x)**2 +(y1-y)**2 )
  6460.                         a = float(a_)*( max(0,(1-(d-radius))) if d>radius else 1 )/256
  6461.                         self.graffiti_preview[y][x*4] = int(r*a + (1-a)*self.graffiti_preview[y][x*4])
  6462.                         self.graffiti_preview[y][x*4+1] = int(g*a + (1-a)*self.graffiti_preview[y][x*4+1])
  6463.                         self.graffiti_preview[y][x*4+2] = int(g*b + (1-a)*self.graffiti_preview[y][x*4+2])
  6464.                         self.graffiti_preview[y][x*4+3] = min(255,int(self.graffiti_preview[y][x*4+3]+a*256))
  6465.  
  6466.         def graffiti_preview_transform(x,y):
  6467.             tr = self.graffiti_preview_transform
  6468.             d = max(tr[2]-tr[0]+2,tr[3]-tr[1]+2)
  6469.             return [(x-tr[0]+1)*self.options.graffiti_preview_size/d, self.options.graffiti_preview_size - (y-tr[1]+1)*self.options.graffiti_preview_size/d]
  6470.  
  6471.  
  6472.         def draw_graffiti_segment(layer,start,end,feed,color=(0,255,0,40),emmit=1000):
  6473.             # Emit = dots per second
  6474.             l = math.sqrt(sum([(start[i]-end[i])**2 for i in range(len(start))]))
  6475.             time_ = l/feed
  6476.             c1,c2 = self.graffiti_reference_points[layer][0][0],self.graffiti_reference_points[layer][1][0]
  6477.             d = math.sqrt( (c1[0]-c2[0])**2 + (c1[1]-c2[1])**2 )
  6478.             if d == 0 : raise ValueError, "Error! Reference points should not be the same!"
  6479.             for i in range(int(time_*emmit+1)) :
  6480.                 t = i/(time_*emmit)
  6481.                 r1,r2 = start[0]*(1-t) + end[0]*t, start[1]*(1-t) + end[1]*t
  6482.                 a = (r1**2-r2**2+d**2)/(2*d)
  6483.                 h = math.sqrt(r1**2 - a**2)
  6484.                 xa = c1[0] + a*(c2[0]-c1[0])/d
  6485.                 ya = c1[1] + a*(c2[1]-c1[1])/d
  6486.            
  6487.                 x1 = xa + h*(c2[1]-c1[1])/d
  6488.                 x2 = xa - h*(c2[1]-c1[1])/d
  6489.                 y1 = ya - h*(c2[0]-c1[0])/d
  6490.                 y2 = ya + h*(c2[0]-c1[0])/d
  6491.                
  6492.                 x = x1 if y1<y2 else x2
  6493.                 y = min(y1,y2)         
  6494.                 x,y = graffiti_preview_transform(x,y)
  6495.                 graffiti_preview_draw_point(x,y,color)
  6496.            
  6497.         def create_connector(p1,p2,t1,t2):
  6498.             P1,P2 = P(p1), P(p2)
  6499.             N1, N2 = P(rotate_ccw(t1)), P(rotate_ccw(t2))
  6500.             r = self.options.graffiti_min_radius
  6501.             C1,C2 = P1+N1*r, P2+N2*r
  6502.             # Get closest possible centers of arcs, also we define that arcs are both ccw or both not.
  6503.             dc, N1, N2, m = (
  6504.                     (
  6505.                         (((P2-N1*r) - (P1-N2*r)).l2(),-N1,-N2, 1)
  6506.                              if vectors_ccw(t1,t2) else
  6507.                         (((P2+N1*r) - (P1+N2*r)).l2(), N1, N2,-1)
  6508.                     )
  6509.                      if vectors_ccw((P1-C1).to_list(),t1) == vectors_ccw((P2-C2).to_list(),t2) else
  6510.                     (
  6511.                         (((P2+N1*r) - (P1-N2*r)).l2(), N1,-N2, 1)
  6512.                              if vectors_ccw(t1,t2) else
  6513.                         (((P2-N1*r) - (P1+N2*r)).l2(),-N1, N2, 1)
  6514.                     )    
  6515.                 )
  6516.             dc = math.sqrt(dc)
  6517.             C1,C2 = P1+N1*r, P2+N2*r
  6518.             Dc = C2-C1
  6519.            
  6520.             if dc == 0 :
  6521.                 # can be joined by one arc
  6522.                 return csp_from_arc(p1, p2, C1.to_list(), r, t1)
  6523.  
  6524.             cos, sin = Dc.x/dc, Dc.y/dc
  6525.             #draw_csp(self.transform_csp([[ [[C1.x-r*sin,C1.y+r*cos]]*3,[[C2.x-r*sin,C2.y+r*cos]]*3 ]],layer,reverse=True), color = "#00ff00;" )
  6526.             #draw_pointer(self.transform(C1.to_list(),layer,reverse=True))
  6527.             #draw_pointer(self.transform(C2.to_list(),layer,reverse=True))
  6528.            
  6529.             p1_end = [C1.x-r*sin*m,C1.y+r*cos*m]
  6530.             p2_st = [C2.x-r*sin*m,C2.y+r*cos*m]
  6531.             if point_to_point_d2(p1,p1_end)<0.0001 and point_to_point_d2(p2,p2_st)<0.0001 :
  6532.                 return ([[p1,p1,p1],[p2,p2,p2]])
  6533.            
  6534.             arc1 = csp_from_arc(p1, p1_end, C1.to_list(), r, t1)
  6535.             arc2 = csp_from_arc(p2_st, p2, C2.to_list(), r, [cos,sin])
  6536.             return csp_concat_subpaths(arc1,arc2)
  6537.  
  6538.         if not self.check_dir() : return
  6539.         if self.selected_paths == {} and self.options.auto_select_paths:
  6540.             paths=self.paths
  6541.             self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
  6542.         else :
  6543.             paths = self.selected_paths
  6544.         self.tool = []
  6545.         gcode = """(Header)
  6546. (Generated by gcodetools from Inkscape.)
  6547. (Using graffiti extension.)
  6548. (Header end.)"""
  6549.        
  6550.         minx,miny,maxx,maxy = float("inf"),float("inf"),float("-inf"),float("-inf")
  6551.  
  6552.         # Get all reference points and path's bounds to make preview
  6553.  
  6554.         for layer in self.layers :
  6555.             if layer in paths :
  6556.                 # Set reference points
  6557.                 if layer not in self.graffiti_reference_points:
  6558.                     reference_points = None
  6559.                     for i in range(self.layers.index(layer),-1,-1):
  6560.                         if self.layers[i] in self.graffiti_reference_points :
  6561.                             reference_points = self.graffiti_reference_points[self.layers[i]]
  6562.                             self.graffiti_reference_points[layer] = self.graffiti_reference_points[self.layers[i]]
  6563.                             break
  6564.                     if reference_points == None :
  6565.                         self.error('There are no graffiti reference points for layer %s'%layer,"error")
  6566.                        
  6567.                 # Transform reference points
  6568.                 for i in range(len(self.graffiti_reference_points[layer])):
  6569.                     self.graffiti_reference_points[layer][i][0] = self.transform(self.graffiti_reference_points[layer][i][0], layer)
  6570.                     point = self.graffiti_reference_points[layer][i]
  6571.                     gcode += "(Reference point %f;%f for %s axis)\n"%(point[0][0],point[0][1],point[1])
  6572.  
  6573.                 if self.options.graffiti_create_preview :
  6574.                     for point in self.graffiti_reference_points[layer]:
  6575.                         minx,miny,maxx,maxy = min(minx,point[0][0]), min(miny,point[0][1]), max(maxx,point[0][0]), max(maxy,point[0][1])
  6576.                     for path in paths[layer]:
  6577.                         csp = cubicsuperpath.parsePath(path.get("d"))
  6578.                         csp = self.apply_transforms(path, csp)
  6579.                         csp = self.transform_csp(csp, layer)
  6580.                         bounds = csp_simple_bound(csp)
  6581.                         minx,miny,maxx,maxy = min(minx,bounds[0]), min(miny,bounds[1]), max(maxx,bounds[2]), max(maxy,bounds[3])
  6582.                        
  6583.         if self.options.graffiti_create_preview :
  6584.             self.graffiti_preview = list([ [255]*(4*self.options.graffiti_preview_size) for i in range(self.options.graffiti_preview_size)])
  6585.             self.graffiti_preview_transform = [minx,miny,maxx,maxy]
  6586.  
  6587.         for layer in self.layers :
  6588.             if layer in paths :
  6589.  
  6590.                 r = re.match("\s*\(\s*([0-9\-,.]+)\s*;\s*([0-9\-,.]+)\s*\)\s*",self.options.graffiti_start_pos)
  6591.                 if r :
  6592.                     start_point = [float(r.group(1)),float(r.group(2))]
  6593.                 else :
  6594.                     start_point = [0.,0.]
  6595.                 last_sp1 = [[start_point[0],start_point[1]-10] for i in range(3)]  
  6596.                 last_sp2 = [start_point for i in range(3)]
  6597.  
  6598.                 self.set_tool(layer)
  6599.                 self.tool = self.tools[layer][0]
  6600.                 # Change tool every layer. (Probably layer = color so it'll be
  6601.                 # better to change it even if the tool has not been changed)
  6602.                 gcode += ( "(Change tool to %s)\n" % re.sub("\"'\(\)\\\\"," ",self.tool["name"]) ) + self.tool["tool change gcode"] + "\n"
  6603.                
  6604.                 subpaths = []
  6605.                 for path in paths[layer]:
  6606.                     # Rebuild the paths to polyline.
  6607.                     csp = cubicsuperpath.parsePath(path.get("d"))
  6608.                     csp = self.apply_transforms(path, csp)
  6609.                     csp = self.transform_csp(csp, layer)
  6610.                     subpaths += csp
  6611.                 polylines = []
  6612.                 while len(subpaths)>0:
  6613.                     i = min( [( point_to_point_d2(last_sp2[1],subpaths[i][0][1]),i) for i in range(len(subpaths))] )[1]
  6614.                     subpath = subpaths[i][:]
  6615.                     del subpaths[i]
  6616.                     polylines += [
  6617.                                     ['connector', create_connector(
  6618.                                                     last_sp2[1],
  6619.                                                     subpath[0][1],
  6620.                                                     csp_normalized_slope(last_sp1,last_sp2,1.),
  6621.                                                     csp_normalized_slope(subpath[0],subpath[1],0.),
  6622.                                     )]
  6623.                                 ]
  6624.                     polyline = []
  6625.                     spl = None
  6626.                    
  6627.                     # remove zerro length segments
  6628.                     i = 0
  6629.                     while i<len(subpath)-1:
  6630.                         if  (cspseglength(subpath[i],subpath[i+1])<0.00000001 ) :
  6631.                             subpath[i][2] = subpath[i+1][2]
  6632.                             del subpath[i+1]
  6633.                         else :
  6634.                             i += 1
  6635.                    
  6636.                     for sp1, sp2 in zip(subpath,subpath[1:]) :
  6637.                         if spl != None and abs(cross( csp_normalized_slope(spl,sp1,1.),csp_normalized_slope(sp1,sp2,0.) )) > 0.1 : # TODO add coefficient into inx
  6638.                             # We've got sharp angle at sp1.
  6639.                             polyline += [sp1]
  6640.                             polylines += [['draw',polyline[:]]]
  6641.                             polylines += [
  6642.                                             ['connector', create_connector(
  6643.                                                     sp1[1],
  6644.                                                     sp1[1],
  6645.                                                     csp_normalized_slope(spl,sp1,1.),
  6646.                                                     csp_normalized_slope(sp1,sp2,0.),
  6647.                                             )]
  6648.                                         ]
  6649.                             polyline = []
  6650.                         # max_segment_length
  6651.                         polyline += [ sp1 ]
  6652.                         print_(polyline)
  6653.                         print_(sp1)
  6654.                        
  6655.                         spl = sp1
  6656.                     polyline += [ sp2 ]
  6657.                     polylines += [ ['draw',polyline[:]] ]
  6658.  
  6659.                     last_sp1, last_sp2 = sp1,sp2
  6660.                    
  6661.                    
  6662.                 # Add return to start_point
  6663.                 if polylines == [] : continue
  6664.                 polylines += [ ["connect1", [ [polylines[-1][1][-1][1] for i in range(3)],[start_point for i in range(3)] ] ] ]
  6665.                
  6666.                 # Make polilynes from polylines. They are still csp.
  6667.                 for i in range(len(polylines)) :
  6668.                     polyline = []
  6669.                     l = 0
  6670.                     print_("polylines",polylines)
  6671.                     print_(polylines[i])
  6672.                     for sp1,sp2 in zip(polylines[i][1],polylines[i][1][1:]) :
  6673.                         print_(sp1,sp2)
  6674.                         l = cspseglength(sp1,sp2)
  6675.                         if l>0.00000001 :
  6676.                             polyline += [sp1[1]]
  6677.                             parts = int(math.ceil(l/self.options.graffiti_max_seg_length))
  6678.                             for j in range(1,parts):
  6679.                                 polyline += [csp_at_length(sp1,sp2,float(j)/parts) ]
  6680.                     if l>0.00000001 :
  6681.                         polyline += [sp2[1]]
  6682.                     print_(i)
  6683.                     polylines[i][1] = polyline
  6684.                                    
  6685.                 t = 0
  6686.                 last_state = None
  6687.                 for polyline_ in polylines:
  6688.                     polyline = polyline_[1]
  6689.                     # Draw linearization
  6690.                     if self.options.graffiti_create_linearization_preview :
  6691.                         t += 1
  6692.                         csp = [ [polyline[i],polyline[i],polyline[i]] for i in range(len(polyline))]
  6693.                         draw_csp(self.transform_csp([csp],layer,reverse=True), color = "#00cc00;" if polyline_[0]=='draw' else "#ff5555;")
  6694.  
  6695.    
  6696.                 # Export polyline to gcode
  6697.                 # we are making trnsform from XYZA coordinates to R1...Rn
  6698.                 # where R1...Rn are radius vectors from grafiti reference points
  6699.                 # to current (x,y) point. Also we need to assign custom feed rate
  6700.                 # for each segment. And we'll use only G01 gcode.
  6701.                     last_real_pos, g = get_gcode_coordinates(polyline[0],layer)
  6702.                     last_pos = polyline[0]
  6703.                     if polyline_[0] == "draw" and last_state!="draw":
  6704.                         gcode += self.tool['gcode before path']+"\n"
  6705.                     for point in polyline :
  6706.                         real_pos, g = get_gcode_coordinates(point,layer)
  6707.                         real_l = sum([(real_pos[i]-last_real_pos[i])**2 for i in range(len(last_real_pos))])
  6708.                         l = (last_pos[0]-point[0])**2 + (last_pos[1]-point[1])**2
  6709.                         if l!=0:
  6710.                             feed = self.tool['feed']*math.sqrt(real_l/l)
  6711.                             gcode += "G01 " + g + " F %f\n"%feed
  6712.                             if self.options.graffiti_create_preview :          
  6713.                                 draw_graffiti_segment(layer,real_pos,last_real_pos,feed,color=(0,0,255,200) if polyline_[0] == "draw" else (255,0,0,200),emmit=self.options.graffiti_preview_emmit)
  6714.                             last_real_pos = real_pos
  6715.                             last_pos = point[:]
  6716.                     if polyline_[0] == "draw" and last_state!="draw" :
  6717.                         gcode += self.tool['gcode after path']+"\n"
  6718.                     last_state = polyline_[0]
  6719.         self.export_gcode(gcode, no_headers=True)              
  6720.         if self.options.graffiti_create_preview :
  6721.             try :
  6722.                 # Draw reference points        
  6723.                 for layer in self.graffiti_reference_points:
  6724.                     for point in self.graffiti_reference_points[layer] :
  6725.                         x, y = graffiti_preview_transform(point[0][0],point[0][1])
  6726.                         graffiti_preview_draw_point(x,y,(0,255,0,255),radius=5)
  6727.            
  6728.                 import png
  6729.                 writer = png.Writer(width=self.options.graffiti_preview_size, height=self.options.graffiti_preview_size, size=None, greyscale=False, alpha=True, bitdepth=8, palette=None, transparent=None, background=None, gamma=None, compression=None, interlace=False, bytes_per_sample=None, planes=None, colormap=None, maxval=None, chunk_limit=1048576)
  6730.                 f = open(self.options.directory+self.options.file+".png", 'wb')
  6731.                 writer.write(f,self.graffiti_preview)
  6732.                 f.close()
  6733.                
  6734.             except :   
  6735.                 self.error("Png module have not been found!","warning")
  6736.                
  6737.  
  6738.                                        
  6739. ################################################################################
  6740. ###
  6741. ###     Effect
  6742. ###
  6743. ###     Main function of Gcodetools class
  6744. ###
  6745. ################################################################################
  6746.     def effect(self) :
  6747.         start_time = time.time()
  6748.         global options
  6749.         options = self.options
  6750.         options.self = self
  6751.         options.doc_root = self.document.getroot()
  6752.  
  6753.         # define print_ function
  6754.         global print_
  6755.         if self.options.log_create_log :
  6756.             try :
  6757.                 if os.path.isfile(self.options.log_filename) : os.remove(self.options.log_filename)
  6758.                 f = open(self.options.log_filename,"a")
  6759.                 f.write("Gcodetools log file.\nStarted at %s.\n%s\n" % (time.strftime("%d.%m.%Y %H:%M:%S"),options.log_filename))
  6760.                 f.write("%s tab is active.\n" % self.options.active_tab)
  6761.                 f.close()
  6762.             except :
  6763.                 print_ = lambda *x : None
  6764.         else : print_ = lambda *x : None
  6765.         if self.options.active_tab == '"help"' :
  6766.             self.help()
  6767.             return
  6768.         elif self.options.active_tab == '"about"' :
  6769.             self.help()
  6770.             return
  6771.        
  6772.         elif self.options.active_tab == '"test"' :
  6773.             self.test()
  6774.            
  6775.         elif self.options.active_tab not in ['"dxfpoints"','"path-to-gcode"', '"area_fill"', '"area"', '"area_artefacts"', '"engraving"', '"orientation"', '"tools_library"', '"lathe"', '"offset"', '"arrangement"', '"update"', '"graffiti"', '"lathe_modify_path"', '"plasma-prepare-path"']:
  6776.             self.error(_("Select one of the action tabs - Path to Gcode, Area, Engraving, DXF points, Orientation, Offset, Lathe or Tools library.\n Current active tab id is %s" % self.options.active_tab),"error")
  6777.         else:
  6778.             # Get all Gcodetools data from the scene.
  6779.             self.get_info()
  6780.             if self.options.active_tab in ['"dxfpoints"','"path-to-gcode"', '"area_fill"', '"area"', '"area_artefacts"', '"engraving"', '"lathe"', '"graffiti"', '"plasma-prepare-path"']:
  6781.                 if self.orientation_points == {} :
  6782.                     self.error(_("Orientation points have not been defined! A default set of orientation points has been automatically added."),"warning")
  6783.                     self.orientation( self.layers[min(1,len(self.layers)-1)] )     
  6784.                     self.get_info()
  6785.                 if self.tools == {} :
  6786.                     self.error(_("Cutting tool has not been defined! A default tool has been automatically added."),"warning")
  6787.                     self.options.tools_library_type = "default"
  6788.                     self.tools_library( self.layers[min(1,len(self.layers)-1)] )
  6789.                     self.get_info()
  6790.             if self.options.active_tab == '"path-to-gcode"':
  6791.                 self.path_to_gcode()       
  6792.             elif self.options.active_tab == '"area_fill"':
  6793.                 self.area_fill()       
  6794.             elif self.options.active_tab == '"area"':
  6795.                 self.area()    
  6796.             elif self.options.active_tab == '"area_artefacts"':
  6797.                 self.area_artefacts()      
  6798.             elif self.options.active_tab == '"dxfpoints"':
  6799.                 self.dxfpoints()       
  6800.             elif self.options.active_tab == '"engraving"':
  6801.                 self.engraving()       
  6802.             elif self.options.active_tab == '"orientation"':
  6803.                 self.orientation()     
  6804.             elif self.options.active_tab == '"graffiti"':
  6805.                 self.graffiti()    
  6806.             elif self.options.active_tab == '"tools_library"':
  6807.                 if self.options.tools_library_type != "check":
  6808.                     self.tools_library()
  6809.                 else
  6810.                     self.check_tools_and_op()
  6811.             elif self.options.active_tab == '"lathe"':
  6812.                 self.lathe()
  6813.             elif self.options.active_tab == '"lathe_modify_path"':
  6814.                 self.lathe_modify_path()
  6815.             elif self.options.active_tab == '"update"':
  6816.                 self.update()
  6817.             elif self.options.active_tab == '"offset"':
  6818.                 if self.options.offset_just_get_distance :
  6819.                     for layer in self.selected_paths :
  6820.                         if len(self.selected_paths[layer]) == 2 :
  6821.                             csp1, csp2 = cubicsuperpath.parsePath(self.selected_paths[layer][0].get("d")), cubicsuperpath.parsePath(self.selected_paths[layer][1].get("d"))
  6822.                             dist = csp_to_csp_distance(csp1,csp2)
  6823.                             print_(dist)
  6824.                             draw_pointer( list(csp_at_t(csp1[dist[1]][dist[2]-1],csp1[dist[1]][dist[2]],dist[3]))
  6825.                                         +list(csp_at_t(csp2[dist[4]][dist[5]-1],csp2[dist[4]][dist[5]],dist[6])),"red","line", comment = math.sqrt(dist[0]))
  6826.                     return 
  6827.                 if self.options.offset_step == 0 : self.options.offset_step = self.options.offset_radius
  6828.                 if self.options.offset_step*self.options.offset_radius <0 : self.options.offset_step *= -1
  6829.                 time_ = time.time()
  6830.                 offsets_count = 0
  6831.                 for layer in self.selected_paths :
  6832.                     for path in self.selected_paths[layer] :
  6833.                                        
  6834.                         offset = self.options.offset_step/2
  6835.                         while abs(offset) <= abs(self.options.offset_radius) :
  6836.                             offset_ = csp_offset(cubicsuperpath.parsePath(path.get("d")), offset)              
  6837.                             offsets_count += 1
  6838.                             if offset_ != [] :
  6839.                                 for iii in offset_ :
  6840.                                     draw_csp([iii], color="Green", width=1)    
  6841.                                     #print_(offset_)
  6842.                             else :
  6843.                                 print_("------------Reached empty offset at radius %s"% offset )
  6844.                                 break
  6845.                             offset += self.options.offset_step
  6846.                 print_()
  6847.                 print_("-----------------------------------------------------------------------------------")              
  6848.                 print_("-----------------------------------------------------------------------------------")              
  6849.                 print_("-----------------------------------------------------------------------------------")              
  6850.                 print_()
  6851.                 print_("Done in %s"%(time.time()-time_))               
  6852.                 print_("Total offsets count %s"%offsets_count)             
  6853.             elif self.options.active_tab == '"arrangement"':
  6854.                 self.arrangement()
  6855.  
  6856.             elif self.options.active_tab == '"plasma-prepare-path"':
  6857.                 self.plasma_prepare_path()     
  6858.  
  6859.  
  6860.         print_("------------------------------------------")
  6861.         print_("Done in %f seconds"%(time.time()-start_time))
  6862.         print_("End at %s."%time.strftime("%d.%m.%Y %H:%M:%S"))
  6863.        
  6864.        
  6865. #                      
  6866. gcodetools = Gcodetools()
  6867. gcodetools.affect()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement