Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- """
- Comments starting "#LT" or "#CLT" are by Chris Lusby Taylor who rewrote the engraving function in 2011.
- History of CLT changes to engraving and other functions it uses:
- 9 May 2011 Changed test of tool diameter to square it
- 10 May Note that there are many unused functions, including:
- bound_to_bound_distance, csp_curvature_radius_at_t,
- csp_special_points, csplength, rebuild_csp, csp_slope,
- csp_simple_bound_to_point_distance, csp_bound_to_point_distance,
- bez_at_t, bez_to_point_distance, bez_normalized_slope, matrix_mul, transpose
- Fixed csp_point_inside_bound() to work if x outside bounds
- 20 May Now encoding the bisectors of angles.
- 23 May Using r/cos(a) instead of normalised normals for bisectors of angles.
- 23 May Note that Z values generated for engraving are in pixels, not mm.
- Removed the biarc curves - straight lines are better.
- 24 May Changed Bezier slope calculation to be less sensitive to tiny differences in points.
- Added use of self.options.engraving_newton_iterations to control accuracy
- 25 May Big restructure and new recursive function.
- Changed the way I treat corners - I now find if the centre of a proposed circle is
- within the area bounded by the line being tested and the two angle bisectors at
- its ends. See get_radius_to_line().
- 29 May Eliminating redundant points. If A,B,C colinear, drop B
- 30 May Eliminating redundant lines in divided Beziers. Changed subdivision of lines
- 7Jun Try to show engraving in 3D
- 8 Jun Displaying in stereo 3D.
- Fixed a bug in bisect - it could go wrong due to rounding errors if
- 1+x1.x2+y1.y2<0 which should never happen. BTW, I spotted a non-normalised normal
- returned by csp_normalized_normal. Need to check for that.
- 9 Jun Corrected spelling of 'definition' but still match previous 'defention' and 'defenition' if found in file
- Changed get_tool to find 1.6.04 tools or new tools with corrected spelling
- 10 Jun Put 3D into a separate layer called 3D, created unless it already exists
- Changed csp_normalized_slope to reject lines shorter than 1e-9.
- 10 Jun Changed all dimensions seen by user to be mm/inch, not pixels. This includes
- tool diameter, maximum engraving distance, tool shape and all Z values.
- 12 Jun ver 208 Now scales correctly if orientation points moved or stretched.
- 12 Jun ver 209. Now detect if engraving toolshape not a function of radius
- Graphics now indicate Gcode toolpath, limited by min(tool diameter/2,max-dist)
- TODO Change line division to be recursive, depending on what line is touched. See line_divide
- engraving() functions (c) 2011 Chris Lusby Taylor, [email protected]
- Copyright (C) 2009 Nick Drobchenko, [email protected]
- based on gcode.py (C) 2007 hugomatic...
- based on addnodes.py (C) 2005,2007 Aaron Spike, [email protected]
- based on dots.py (C) 2005 Aaron Spike, [email protected]
- based on interp.py (C) 2005 Aaron Spike, [email protected]
- based on bezmisc.py (C) 2005 Aaron Spike, [email protected]
- based on cubicsuperpath.py (C) 2005 Aaron Spike, [email protected]
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- """
- ###
- ### Gcodetools v 1.7
- ###
- gcodetools_current_version = "1.7"
- # standard library
- import os
- import math
- import bezmisc
- import re
- import copy
- import sys
- import time
- import cmath
- import numpy
- import codecs
- import random
- # local library
- import inkex
- import simplestyle
- import simplepath
- import cubicsuperpath
- import simpletransform
- import bezmisc
- inkex.localize()
- ### Check if inkex has errormsg (0.46 version does not have one.) Could be removed later.
- if "errormsg" not in dir(inkex):
- inkex.errormsg = lambda msg: sys.stderr.write((unicode(msg) + "\n").encode("UTF-8"))
- def bezierslopeatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t):
- ax,ay,bx,by,cx,cy,x0,y0=bezmisc.bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
- dx=3*ax*(t**2)+2*bx*t+cx
- dy=3*ay*(t**2)+2*by*t+cy
- if dx==dy==0 :
- dx = 6*ax*t+2*bx
- dy = 6*ay*t+2*by
- if dx==dy==0 :
- dx = 6*ax
- dy = 6*ay
- if dx==dy==0 :
- 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))
- print_(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)))
- dx, dy = 1, 1
- return dx,dy
- bezmisc.bezierslopeatt = bezierslopeatt
- def ireplace(self,old,new,count=0):
- pattern = re.compile(re.escape(old),re.I)
- return re.sub(pattern,new,self,count)
- def isset(variable):
- # VARIABLE NAME SHOULD BE A STRING! Like isset("foobar")
- return variable in locals() or variable in globals()
- ################################################################################
- ###
- ### Styles and additional parameters
- ###
- ################################################################################
- math.pi2 = math.pi*2
- straight_tolerance = 0.0001
- straight_distance_tolerance = 0.0001
- engraving_tolerance = 0.0001
- loft_lengths_tolerance = 0.0000001
- EMC_TOLERANCE_EQUAL = 0.00001
- options = {}
- defaults = {
- 'header': """%
- (Header)
- (Generated by gcodetools from Inkscape.)
- (Using default header. To add your own header create file "header" in the output dir.)
- M3
- (Header end.)
- """,
- 'footer': """
- (Footer)
- M5
- G00 X0.0000 Y0.0000
- M2
- (Using default footer. To add your own footer create file "footer" in the output dir.)
- (end)
- %"""
- }
- intersection_recursion_depth = 10
- intersection_tolerance = 0.00001
- styles = {
- "in_out_path_style" : simplestyle.formatStyle({ 'stroke': '#0072a7', 'fill': 'none', 'stroke-width':'1', 'marker-mid':'url(#InOutPathMarker)' }),
- "loft_style" : {
- 'main curve': simplestyle.formatStyle({ 'stroke': '#88f', 'fill': 'none', 'stroke-width':'1', 'marker-end':'url(#Arrow2Mend)' }),
- },
- "biarc_style" : {
- 'biarc0': simplestyle.formatStyle({ 'stroke': '#88f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'biarc1': simplestyle.formatStyle({ 'stroke': '#8f8', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'line': simplestyle.formatStyle({ 'stroke': '#f88', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'area': simplestyle.formatStyle({ 'stroke': '#777', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
- },
- "biarc_style_dark" : {
- 'biarc0': simplestyle.formatStyle({ 'stroke': '#33a', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'biarc1': simplestyle.formatStyle({ 'stroke': '#3a3', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'line': simplestyle.formatStyle({ 'stroke': '#a33', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'area': simplestyle.formatStyle({ 'stroke': '#222', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
- },
- "biarc_style_dark_area" : {
- 'biarc0': simplestyle.formatStyle({ 'stroke': '#33a', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
- 'biarc1': simplestyle.formatStyle({ 'stroke': '#3a3', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
- 'line': simplestyle.formatStyle({ 'stroke': '#a33', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' }),
- 'area': simplestyle.formatStyle({ 'stroke': '#222', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
- },
- "biarc_style_i" : {
- 'biarc0': simplestyle.formatStyle({ 'stroke': '#880', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'biarc1': simplestyle.formatStyle({ 'stroke': '#808', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'line': simplestyle.formatStyle({ 'stroke': '#088', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'area': simplestyle.formatStyle({ 'stroke': '#999', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
- },
- "biarc_style_dark_i" : {
- 'biarc0': simplestyle.formatStyle({ 'stroke': '#dd5', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'biarc1': simplestyle.formatStyle({ 'stroke': '#d5d', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'line': simplestyle.formatStyle({ 'stroke': '#5dd', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' }),
- 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
- },
- "biarc_style_lathe_feed" : {
- 'biarc0': simplestyle.formatStyle({ 'stroke': '#07f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
- 'biarc1': simplestyle.formatStyle({ 'stroke': '#0f7', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
- 'line': simplestyle.formatStyle({ 'stroke': '#f44', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
- 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
- },
- "biarc_style_lathe_passing feed" : {
- 'biarc0': simplestyle.formatStyle({ 'stroke': '#07f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
- 'biarc1': simplestyle.formatStyle({ 'stroke': '#0f7', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
- 'line': simplestyle.formatStyle({ 'stroke': '#f44', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
- 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
- },
- "biarc_style_lathe_fine feed" : {
- 'biarc0': simplestyle.formatStyle({ 'stroke': '#7f0', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
- 'biarc1': simplestyle.formatStyle({ 'stroke': '#f70', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
- 'line': simplestyle.formatStyle({ 'stroke': '#744', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' }),
- 'area': simplestyle.formatStyle({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' }),
- },
- "area artefact": simplestyle.formatStyle({ 'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width':'1' }),
- "area artefact arrow": simplestyle.formatStyle({ 'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width':'1' }),
- "dxf_points": simplestyle.formatStyle({ "stroke": "#ff0000", "fill": "#ff0000"}),
- }
- ################################################################################
- ### Gcode additional functions
- ################################################################################
- def gcode_comment_str(s, replace_new_line = False):
- if replace_new_line :
- s = re.sub(r"[\n\r]+", ".", s)
- res = ""
- if s[-1] == "\n" : s = s[:-1]
- for a in s.split("\n") :
- if a != "" :
- res += ";(" + re.sub(r"[\(\)\\\n\r]", ".", a) + ")\n"
- else :
- res += "\n"
- return res
- ################################################################################
- ### Cubic Super Path additional functions
- ################################################################################
- def csp_from_polyline(line) :
- return [ [ [point[:] for k in range(3) ] for point in subline ] for subline in line ]
- def csp_remove_zerro_segments(csp, tolerance = 1e-7):
- res = []
- for subpath in csp:
- if len(subpath) > 0 :
- res.append([subpath[0]])
- for sp1,sp2 in zip(subpath,subpath[1:]) :
- 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 :
- res[-1][-1][2] = sp2[2]
- else :
- res[-1].append(sp2)
- return res
- def point_inside_csp(p,csp, on_the_path = True) :
- # we'll do the raytracing and see how many intersections are there on the ray's way.
- # if number of intersections is even then point is outside.
- # ray will be x=p.x and y=>p.y
- # you can assing any value to on_the_path, by dfault if point is on the path
- # function will return thai it's inside the path.
- x,y = p
- ray_intersections_count = 0
- for subpath in csp :
- for i in range(1, len(subpath)) :
- sp1, sp2 = subpath[i-1], subpath[i]
- ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
- if ax==0 and bx==0 and cx==0 and dx==x :
- #we've got a special case here
- b = csp_true_bounds( [[sp1,sp2]])
- if b[1][1]<=y<=b[3][1] :
- # points is on the path
- return on_the_path
- else :
- # we can skip this segment because it wont influence the answer.
- pass
- else:
- for t in csp_line_intersection([x,y],[x,y+5],sp1,sp2) :
- if t == 0 or t == 1 :
- #we've got another special case here
- x1,y1 = csp_at_t(sp1,sp2,t)
- if y1==y :
- # the point is on the path
- return on_the_path
- # if t == 0 we sould have considered this case previously.
- if t == 1 :
- # we have to check the next segmant if it is on the same side of the ray
- st_d = csp_normalized_slope(sp1,sp2,1)[0]
- if st_d == 0 : st_d = csp_normalized_slope(sp1,sp2,0.99)[0]
- for j in range(1, len(subpath)+1):
- if (i+j) % len(subpath) == 0 : continue # skip the closing segment
- sp11,sp22 = subpath[(i-1+j) % len(subpath)], subpath[(i+j) % len(subpath)]
- ax1,ay1,bx1,by1,cx1,cy1,dx1,dy1 = csp_parameterize(sp1,sp2)
- if ax1==0 and bx1==0 and cx1==0 and dx1==x : continue # this segment parallel to the ray, so skip it
- en_d = csp_normalized_slope(sp11,sp22,0)[0]
- if en_d == 0 : en_d = csp_normalized_slope(sp11,sp22,0.01)[0]
- if st_d*en_d <=0 :
- ray_intersections_count += 1
- break
- else :
- x1,y1 = csp_at_t(sp1,sp2,t)
- if y1==y :
- # the point is on the path
- return on_the_path
- else :
- if y1>y and 3*ax*t**2 + 2*bx*t + cx !=0 : # if it's 0 the path only touches the ray
- ray_intersections_count += 1
- return ray_intersections_count%2 == 1
- def csp_close_all_subpaths(csp, tolerance = 0.000001):
- for i in range(len(csp)):
- if point_to_point_d2(csp[i][0][1] , csp[i][-1][1])> tolerance**2 :
- csp[i][-1][2] = csp[i][-1][1][:]
- csp[i] += [ [csp[i][0][1][:] for j in range(3)] ]
- else:
- if csp[i][0][1] != csp[i][-1][1] :
- csp[i][-1][1] = csp[i][0][1][:]
- return csp
- def csp_simple_bound(csp):
- minx,miny,maxx,maxy = None,None,None,None
- for subpath in csp:
- for sp in subpath :
- for p in sp:
- minx = min(minx,p[0]) if minx!=None else p[0]
- miny = min(miny,p[1]) if miny!=None else p[1]
- maxx = max(maxx,p[0]) if maxx!=None else p[0]
- maxy = max(maxy,p[1]) if maxy!=None else p[1]
- return minx,miny,maxx,maxy
- def csp_segment_to_bez(sp1,sp2) :
- return sp1[1:]+sp2[:2]
- def bound_to_bound_distance(sp1,sp2,sp3,sp4) :
- min_dist = 1e100
- max_dist = 0
- points1 = csp_segment_to_bez(sp1,sp2)
- points2 = csp_segment_to_bez(sp3,sp4)
- for i in range(4) :
- for j in range(4) :
- min_, max_ = line_to_line_min_max_distance_2(points1[i-1], points1[i], points2[j-1], points2[j])
- min_dist = min(min_dist,min_)
- max_dist = max(max_dist,max_)
- print_("bound_to_bound", min_dist, max_dist)
- return min_dist, max_dist
- def csp_to_point_distance(csp, p, dist_bounds = [0,1e100], tolerance=.01) :
- min_dist = [1e100,0,0,0]
- for j in range(len(csp)) :
- for i in range(1,len(csp[j])) :
- d = csp_seg_to_point_distance(csp[j][i-1],csp[j][i],p,sample_points = 5, tolerance = .01)
- if d[0] < dist_bounds[0] :
- # draw_pointer( list(csp_at_t(subpath[dist[2]-1],subpath[dist[2]],dist[3]))
- # +list(csp_at_t(csp[dist[4]][dist[5]-1],csp[dist[4]][dist[5]],dist[6])),"red","line", comment = math.sqrt(dist[0]))
- return [d[0],j,i,d[1]]
- else :
- if d[0] < min_dist[0] : min_dist = [d[0],j,i,d[1]]
- return min_dist
- def csp_seg_to_point_distance(sp1,sp2,p,sample_points = 5, tolerance = .01) :
- ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
- dx, dy = dx-p[0], dy-p[1]
- if sample_points < 2 : sample_points = 2
- 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.] )
- for k in range(sample_points) :
- t = float(k)/(sample_points-1)
- i = 0
- while i==0 or abs(f)>0.000001 and i<20 :
- t2,t3 = t**2,t**3
- 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)
- 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
- if df!=0 :
- t = t - f/df
- else :
- break
- i += 1
- if 0<=t<=1 :
- p1 = csp_at_t(sp1,sp2,t)
- d1 = (p1[0]-p[0])**2 + (p1[1]-p[1])**2
- if d1 < d[0] :
- d = [d1,t]
- return d
- def csp_seg_to_csp_seg_distance(sp1,sp2,sp3,sp4, dist_bounds = [0,1e100], sample_points = 5, tolerance=.01) :
- # check the ending points first
- dist = csp_seg_to_point_distance(sp1,sp2,sp3[1],sample_points, tolerance)
- dist += [0.]
- if dist[0] <= dist_bounds[0] : return dist
- d = csp_seg_to_point_distance(sp1,sp2,sp4[1],sample_points, tolerance)
- if d[0]<dist[0] :
- dist = d+[1.]
- if dist[0] <= dist_bounds[0] : return dist
- d = csp_seg_to_point_distance(sp3,sp4,sp1[1],sample_points, tolerance)
- if d[0]<dist[0] :
- dist = [d[0],0.,d[1]]
- if dist[0] <= dist_bounds[0] : return dist
- d = csp_seg_to_point_distance(sp3,sp4,sp2[1],sample_points, tolerance)
- if d[0]<dist[0] :
- dist = [d[0],1.,d[1]]
- if dist[0] <= dist_bounds[0] : return dist
- sample_points -= 2
- if sample_points < 1 : sample_points = 1
- ax1,ay1,bx1,by1,cx1,cy1,dx1,dy1 = csp_parameterize(sp1,sp2)
- ax2,ay2,bx2,by2,cx2,cy2,dx2,dy2 = csp_parameterize(sp3,sp4)
- # try to find closes points using Newtons method
- for k in range(sample_points) :
- for j in range(sample_points) :
- t1,t2 = float(k+1)/(sample_points+1), float(j)/(sample_points+1)
- t12, t13, t22, t23 = t1*t1, t1*t1*t1, t2*t2, t2*t2*t2
- i = 0
- F1, F2, F = [0,0], [[0,0],[0,0]], 1e100
- 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)
- while i<2 or abs(F-Flast)>tolerance and i<30 :
- #draw_pointer(csp_at_t(sp1,sp2,t1))
- f1x = 3*ax1*t12+2*bx1*t1+cx1
- f1y = 3*ay1*t12+2*by1*t1+cy1
- f2x = 3*ax2*t22+2*bx2*t2+cx2
- f2y = 3*ay2*t22+2*by2*t2+cy2
- F1[0] = 2*f1x*x + 2*f1y*y
- F1[1] = -2*f2x*x - 2*f2y*y
- F2[0][0] = 2*(6*ax1*t1+2*bx1)*x + 2*f1x*f1x + 2*(6*ay1*t1+2*by1)*y +2*f1y*f1y
- F2[0][1] = -2*f1x*f2x - 2*f1y*f2y
- F2[1][0] = -2*f2x*f1x - 2*f2y*f1y
- F2[1][1] = -2*(6*ax2*t2+2*bx2)*x + 2*f2x*f2x - 2*(6*ay2*t2+2*by2)*y + 2*f2y*f2y
- F2 = inv_2x2(F2)
- if F2!=None :
- t1 -= ( F2[0][0]*F1[0] + F2[0][1]*F1[1] )
- t2 -= ( F2[1][0]*F1[0] + F2[1][1]*F1[1] )
- t12, t13, t22, t23 = t1*t1, t1*t1*t1, t2*t2, t2*t2*t2
- 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)
- Flast = F
- F = x*x+y*y
- else :
- break
- i += 1
- if F < dist[0] and 0<=t1<=1 and 0<=t2<=1:
- dist = [F,t1,t2]
- if dist[0] <= dist_bounds[0] :
- return dist
- return dist
- def csp_to_csp_distance(csp1,csp2, dist_bounds = [0,1e100], tolerance=.01) :
- dist = [1e100,0,0,0,0,0,0]
- for i1 in range(len(csp1)) :
- for j1 in range(1,len(csp1[i1])) :
- for i2 in range(len(csp2)) :
- for j2 in range(1,len(csp2[i2])) :
- 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])
- if d[0] >= dist_bounds[1] : continue
- if d[1] < dist_bounds[0] : return [d[1],i1,j1,1,i2,j2,1]
- 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)
- if d[0] < dist[0] :
- dist = [d[0], i1,j1,d[1], i2,j2,d[2]]
- if dist[0] <= dist_bounds[0] :
- return dist
- if dist[0] >= dist_bounds[1] :
- return dist
- return dist
- # draw_pointer( list(csp_at_t(csp1[dist[1]][dist[2]-1],csp1[dist[1]][dist[2]],dist[3]))
- # + list(csp_at_t(csp2[dist[4]][dist[5]-1],csp2[dist[4]][dist[5]],dist[6])), "#507","line")
- def csp_split(sp1,sp2,t=.5) :
- [x1,y1],[x2,y2],[x3,y3],[x4,y4] = sp1[1], sp1[2], sp2[0], sp2[1]
- x12 = x1+(x2-x1)*t
- y12 = y1+(y2-y1)*t
- x23 = x2+(x3-x2)*t
- y23 = y2+(y3-y2)*t
- x34 = x3+(x4-x3)*t
- y34 = y3+(y4-y3)*t
- x1223 = x12+(x23-x12)*t
- y1223 = y12+(y23-y12)*t
- x2334 = x23+(x34-x23)*t
- y2334 = y23+(y34-y23)*t
- x = x1223+(x2334-x1223)*t
- y = y1223+(y2334-y1223)*t
- return [sp1[0],sp1[1],[x12,y12]], [[x1223,y1223],[x,y],[x2334,y2334]], [[x34,y34],sp2[1],sp2[2]]
- def csp_true_bounds(csp) :
- # Finds minx,miny,maxx,maxy of the csp and return their (x,y,i,j,t)
- minx = [float("inf"), 0, 0, 0]
- maxx = [float("-inf"), 0, 0, 0]
- miny = [float("inf"), 0, 0, 0]
- maxy = [float("-inf"), 0, 0, 0]
- for i in range(len(csp)):
- for j in range(1,len(csp[i])):
- ax,ay,bx,by,cx,cy,x0,y0 = bezmisc.bezierparameterize((csp[i][j-1][1],csp[i][j-1][2],csp[i][j][0],csp[i][j][1]))
- roots = cubic_solver(0, 3*ax, 2*bx, cx) + [0,1]
- for root in roots :
- if type(root) is complex and abs(root.imag)<1e-10:
- root = root.real
- if type(root) is not complex and 0<=root<=1:
- y = ay*(root**3)+by*(root**2)+cy*root+y0
- x = ax*(root**3)+bx*(root**2)+cx*root+x0
- maxx = max([x,y,i,j,root],maxx)
- minx = min([x,y,i,j,root],minx)
- roots = cubic_solver(0, 3*ay, 2*by, cy) + [0,1]
- for root in roots :
- if type(root) is complex and root.imag==0:
- root = root.real
- if type(root) is not complex and 0<=root<=1:
- y = ay*(root**3)+by*(root**2)+cy*root+y0
- x = ax*(root**3)+bx*(root**2)+cx*root+x0
- maxy = max([y,x,i,j,root],maxy)
- miny = min([y,x,i,j,root],miny)
- maxy[0],maxy[1] = maxy[1],maxy[0]
- miny[0],miny[1] = miny[1],miny[0]
- return minx,miny,maxx,maxy
- ############################################################################
- ### csp_segments_intersection(sp1,sp2,sp3,sp4)
- ###
- ### Returns array containig all intersections between two segmets of cubic
- ### super path. Results are [ta,tb], or [ta0, ta1, tb0, tb1, "Overlap"]
- ### where ta, tb are values of t for the intersection point.
- ############################################################################
- def csp_segments_intersection(sp1,sp2,sp3,sp4) :
- a, b = csp_segment_to_bez(sp1,sp2), csp_segment_to_bez(sp3,sp4)
- def polish_intersection(a,b,ta,tb, tolerance = intersection_tolerance) :
- ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize(a)
- ax1,ay1,bx1,by1,cx1,cy1,dx1,dy1 = bezmisc.bezierparameterize(b)
- i = 0
- F, F1 = [.0,.0], [[.0,.0],[.0,.0]]
- while i==0 or (abs(F[0])**2+abs(F[1])**2 > tolerance and i<10):
- ta3, ta2, tb3, tb2 = ta**3, ta**2, tb**3, tb**2
- F[0] = ax*ta3+bx*ta2+cx*ta+dx-ax1*tb3-bx1*tb2-cx1*tb-dx1
- F[1] = ay*ta3+by*ta2+cy*ta+dy-ay1*tb3-by1*tb2-cy1*tb-dy1
- F1[0][0] = 3*ax *ta2 + 2*bx *ta + cx
- F1[0][1] = -3*ax1*tb2 - 2*bx1*tb - cx1
- F1[1][0] = 3*ay *ta2 + 2*by *ta + cy
- F1[1][1] = -3*ay1*tb2 - 2*by1*tb - cy1
- det = F1[0][0]*F1[1][1] - F1[0][1]*F1[1][0]
- if det!=0 :
- F1 = [ [ F1[1][1]/det, -F1[0][1]/det], [-F1[1][0]/det, F1[0][0]/det] ]
- ta = ta - ( F1[0][0]*F[0] + F1[0][1]*F[1] )
- tb = tb - ( F1[1][0]*F[0] + F1[1][1]*F[1] )
- else: break
- i += 1
- return ta, tb
- def recursion(a,b, ta0,ta1,tb0,tb1, depth_a,depth_b) :
- global bezier_intersection_recursive_result
- if a==b :
- bezier_intersection_recursive_result += [[ta0,tb0,ta1,tb1,"Overlap"]]
- return
- tam, tbm = (ta0+ta1)/2, (tb0+tb1)/2
- if depth_a>0 and depth_b>0 :
- a1,a2 = bez_split(a,0.5)
- b1,b2 = bez_split(b,0.5)
- if bez_bounds_intersect(a1,b1) : recursion(a1,b1, ta0,tam,tb0,tbm, depth_a-1,depth_b-1)
- if bez_bounds_intersect(a2,b1) : recursion(a2,b1, tam,ta1,tb0,tbm, depth_a-1,depth_b-1)
- if bez_bounds_intersect(a1,b2) : recursion(a1,b2, ta0,tam,tbm,tb1, depth_a-1,depth_b-1)
- if bez_bounds_intersect(a2,b2) : recursion(a2,b2, tam,ta1,tbm,tb1, depth_a-1,depth_b-1)
- elif depth_a>0 :
- a1,a2 = bez_split(a,0.5)
- if bez_bounds_intersect(a1,b) : recursion(a1,b, ta0,tam,tb0,tb1, depth_a-1,depth_b)
- if bez_bounds_intersect(a2,b) : recursion(a2,b, tam,ta1,tb0,tb1, depth_a-1,depth_b)
- elif depth_b>0 :
- b1,b2 = bez_split(b,0.5)
- if bez_bounds_intersect(a,b1) : recursion(a,b1, ta0,ta1,tb0,tbm, depth_a,depth_b-1)
- if bez_bounds_intersect(a,b2) : recursion(a,b2, ta0,ta1,tbm,tb1, depth_a,depth_b-1)
- else : # Both segments have been subdevided enougth. Let's get some intersections :).
- intersection, t1, t2 = straight_segments_intersection([a[0]]+[a[3]],[b[0]]+[b[3]])
- if intersection :
- if intersection == "Overlap" :
- t1 = ( max(0,min(1,t1[0]))+max(0,min(1,t1[1])) )/2
- t2 = ( max(0,min(1,t2[0]))+max(0,min(1,t2[1])) )/2
- bezier_intersection_recursive_result += [[ta0+t1*(ta1-ta0),tb0+t2*(tb1-tb0)]]
- global bezier_intersection_recursive_result
- bezier_intersection_recursive_result = []
- recursion(a,b,0.,1.,0.,1.,intersection_recursion_depth,intersection_recursion_depth)
- intersections = bezier_intersection_recursive_result
- for i in range(len(intersections)) :
- if len(intersections[i])<5 or intersections[i][4] != "Overlap" :
- intersections[i] = polish_intersection(a,b,intersections[i][0],intersections[i][1])
- return intersections
- def csp_segments_true_intersection(sp1,sp2,sp3,sp4) :
- intersections = csp_segments_intersection(sp1,sp2,sp3,sp4)
- res = []
- for intersection in intersections :
- if (
- (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) )
- or ( 0<=intersection[0]<=1 and 0<=intersection[1]<=1 )
- ) :
- res += [intersection]
- return res
- def csp_get_t_at_curvature(sp1,sp2,c, sample_points = 16):
- # returns a list containning [t1,t2,t3,...,tn], 0<=ti<=1...
- if sample_points < 2 : sample_points = 2
- tolerance = .0000000001
- res = []
- ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
- for k in range(sample_points) :
- t = float(k)/(sample_points-1)
- i, F = 0, 1e100
- while i<2 or abs(F)>tolerance and i<17 :
- try : # some numerical calculation could exceed the limits
- t2 = t*t
- #slopes...
- f1x = 3*ax*t2+2*bx*t+cx
- f1y = 3*ay*t2+2*by*t+cy
- f2x = 6*ax*t+2*bx
- f2y = 6*ay*t+2*by
- f3x = 6*ax
- f3y = 6*ay
- d = (f1x**2+f1y**2)**1.5
- F1 = (
- ( (f1x*f3y-f3x*f1y)*d - (f1x*f2y-f2x*f1y)*3.*(f2x*f1x+f2y*f1y)*((f1x**2+f1y**2)**.5) ) /
- ((f1x**2+f1y**2)**3)
- )
- F = (f1x*f2y-f1y*f2x)/d - c
- t -= F/F1
- except:
- break
- i += 1
- if 0<=t<=1 and F<=tolerance:
- if len(res) == 0 :
- res.append(t)
- for i in res :
- if abs(t-i)<=0.001 :
- break
- if not abs(t-i)<=0.001 :
- res.append(t)
- return res
- def csp_max_curvature(sp1,sp2):
- ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
- tolerance = .0001
- F = 0.
- i = 0
- while i<2 or F-Flast<tolerance and i<10 :
- t = .5
- f1x = 3*ax*t**2 + 2*bx*t + cx
- f1y = 3*ay*t**2 + 2*by*t + cy
- f2x = 6*ax*t + 2*bx
- f2y = 6*ay*t + 2*by
- f3x = 6*ax
- f3y = 6*ay
- d = pow(f1x**2+f1y**2,1.5)
- if d != 0 :
- Flast = F
- F = (f1x*f2y-f1y*f2x)/d
- F1 = (
- ( d*(f1x*f3y-f3x*f1y) - (f1x*f2y-f2x*f1y)*3.*(f2x*f1x+f2y*f1y)*pow(f1x**2+f1y**2,.5) ) /
- (f1x**2+f1y**2)**3
- )
- i+=1
- if F1!=0:
- t -= F/F1
- else:
- break
- else: break
- return t
- def csp_curvature_at_t(sp1,sp2,t, depth = 3) :
- ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize(csp_segment_to_bez(sp1,sp2))
- #curvature = (x'y''-y'x'') / (x'^2+y'^2)^1.5
- f1x = 3*ax*t**2 + 2*bx*t + cx
- f1y = 3*ay*t**2 + 2*by*t + cy
- f2x = 6*ax*t + 2*bx
- f2y = 6*ay*t + 2*by
- d = (f1x**2+f1y**2)**1.5
- if d != 0 :
- return (f1x*f2y-f1y*f2x)/d
- else :
- t1 = f1x*f2y-f1y*f2x
- if t1 > 0 : return 1e100
- if t1 < 0 : return -1e100
- # Use the Lapitals rule to solve 0/0 problem for 2 times...
- t1 = 2*(bx*ay-ax*by)*t+(ay*cx-ax*cy)
- if t1 > 0 : return 1e100
- if t1 < 0 : return -1e100
- t1 = bx*ay-ax*by
- if t1 > 0 : return 1e100
- if t1 < 0 : return -1e100
- if depth>0 :
- # little hack ;^) hope it wont influence anything...
- return csp_curvature_at_t(sp1,sp2,t*1.004, depth-1)
- return 1e100
- def csp_curvature_radius_at_t(sp1,sp2,t) :
- c = csp_curvature_at_t(sp1,sp2,t)
- if c == 0 : return 1e100
- else: return 1/c
- def csp_special_points(sp1,sp2) :
- # special points = curvature == 0
- ax,ay,bx,by,cx,cy,dx,dy = bezmisc.bezierparameterize((sp1[1],sp1[2],sp2[0],sp2[1]))
- a = 3*ax*by-3*ay*bx
- b = 3*ax*cy-3*cx*ay
- c = bx*cy-cx*by
- roots = cubic_solver(0, a, b, c)
- res = []
- for i in roots :
- if type(i) is complex and i.imag==0:
- i = i.real
- if type(i) is not complex and 0<=i<=1:
- res.append(i)
- return res
- def csp_subpath_ccw(subpath):
- # Remove all zerro length segments
- s = 0
- #subpath = subpath[:]
- if (P(subpath[-1][1])-P(subpath[0][1])).l2() > 1e-10 :
- subpath[-1][2] = subpath[-1][1]
- subpath[0][0] = subpath[0][1]
- subpath += [ [subpath[0][1],subpath[0][1],subpath[0][1]] ]
- pl = subpath[-1][2]
- for sp1 in subpath:
- for p in sp1 :
- s += (p[0]-pl[0])*(p[1]+pl[1])
- pl = p
- return s<0
- def csp_at_t(sp1,sp2,t):
- ax,bx,cx,dx = sp1[1][0], sp1[2][0], sp2[0][0], sp2[1][0]
- ay,by,cy,dy = sp1[1][1], sp1[2][1], sp2[0][1], sp2[1][1]
- x1, y1 = ax+(bx-ax)*t, ay+(by-ay)*t
- x2, y2 = bx+(cx-bx)*t, by+(cy-by)*t
- x3, y3 = cx+(dx-cx)*t, cy+(dy-cy)*t
- x4,y4 = x1+(x2-x1)*t, y1+(y2-y1)*t
- x5,y5 = x2+(x3-x2)*t, y2+(y3-y2)*t
- x,y = x4+(x5-x4)*t, y4+(y5-y4)*t
- return [x,y]
- def csp_at_length(sp1,sp2,l=0.5, tolerance = 0.01):
- bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
- t = bezmisc.beziertatlength(bez, l, tolerance)
- return csp_at_t(sp1,sp2,t)
- def csp_splitatlength(sp1, sp2, l = 0.5, tolerance = 0.01):
- bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
- t = bezmisc.beziertatlength(bez, l, tolerance)
- return csp_split(sp1, sp2, t)
- def cspseglength(sp1,sp2, tolerance = 0.01):
- bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
- return bezmisc.bezierlength(bez, tolerance)
- def csplength(csp):
- total = 0
- lengths = []
- for sp in csp:
- for i in xrange(1,len(sp)):
- l = cspseglength(sp[i-1],sp[i])
- lengths.append(l)
- total += l
- return lengths, total
- def csp_segments(csp):
- l, seg = 0, [0]
- for sp in csp:
- for i in xrange(1,len(sp)):
- l += cspseglength(sp[i-1],sp[i])
- seg += [ l ]
- if l>0 :
- seg = [seg[i]/l for i in xrange(len(seg))]
- return seg,l
- def rebuild_csp (csp, segs, s=None):
- # rebuild_csp() adds to csp control points making it's segments looks like segs
- if s==None : s, l = csp_segments(csp)
- if len(s)>len(segs) : return None
- segs = segs[:]
- segs.sort()
- for i in xrange(len(s)):
- d = None
- for j in xrange(len(segs)):
- d = min( [abs(s[i]-segs[j]),j], d) if d!=None else [abs(s[i]-segs[j]),j]
- del segs[d[1]]
- for i in xrange(len(segs)):
- for j in xrange(0,len(s)):
- if segs[i]<s[j] : break
- if s[j]-s[j-1] != 0 :
- t = (segs[i] - s[j-1])/(s[j]-s[j-1])
- sp1,sp2,sp3 = csp_split(csp[j-1],csp[j], t)
- csp = csp[:j-1] + [sp1,sp2,sp3] + csp[j+1:]
- s = s[:j] + [ s[j-1]*(1-t)+s[j]*t ] + s[j:]
- return csp, s
- def csp_slope(sp1,sp2,t):
- bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
- return bezmisc.bezierslopeatt(bez,t)
- def csp_line_intersection(l1,l2,sp1,sp2):
- dd=l1[0]
- cc=l2[0]-l1[0]
- bb=l1[1]
- aa=l2[1]-l1[1]
- if aa==cc==0 : return []
- if aa:
- coef1=cc/aa
- coef2=1
- else:
- coef1=1
- coef2=aa/cc
- bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
- ax,ay,bx,by,cx,cy,x0,y0=bezmisc.bezierparameterize(bez)
- a=coef1*ay-coef2*ax
- b=coef1*by-coef2*bx
- c=coef1*cy-coef2*cx
- d=coef1*(y0-bb)-coef2*(x0-dd)
- roots = cubic_solver(a,b,c,d)
- retval = []
- for i in roots :
- if type(i) is complex and abs(i.imag)<1e-7:
- i = i.real
- if type(i) is not complex and -1e-10<=i<=1.+1e-10:
- retval.append(i)
- return retval
- def csp_split_by_two_points(sp1,sp2,t1,t2) :
- if t1>t2 : t1, t2 = t2, t1
- if t1 == t2 :
- sp1,sp2,sp3 = csp_split(sp1,sp2,t)
- return [sp1,sp2,sp2,sp3]
- elif t1 <= 1e-10 and t2 >= 1.-1e-10 :
- return [sp1,sp1,sp2,sp2]
- elif t1 <= 1e-10:
- sp1,sp2,sp3 = csp_split(sp1,sp2,t2)
- return [sp1,sp1,sp2,sp3]
- elif t2 >= 1.-1e-10 :
- sp1,sp2,sp3 = csp_split(sp1,sp2,t1)
- return [sp1,sp2,sp3,sp3]
- else:
- sp1,sp2,sp3 = csp_split(sp1,sp2,t1)
- sp2,sp3,sp4 = csp_split(sp2,sp3,(t2-t1)/(1-t1) )
- return [sp1,sp2,sp3,sp4]
- def csp_seg_split(sp1,sp2, points):
- # points is float=t or list [t1, t2, ..., tn]
- if type(points) is float :
- points = [points]
- points.sort()
- res = [sp1,sp2]
- last_t = 0
- for t in points:
- if 1e-10<t<1.-1e-10 :
- sp3,sp4,sp5 = csp_split(res[-2],res[-1], (t-last_t)/(1-last_t))
- last_t = t
- res[-2:] = [sp3,sp4,sp5]
- return res
- def csp_subpath_split_by_points(subpath, points) :
- # points are [[i,t]...] where i-segment's number
- points.sort()
- points = [[1,0.]] + points + [[len(subpath)-1,1.]]
- parts = []
- for int1,int2 in zip(points,points[1:]) :
- if int1==int2 :
- continue
- if int1[1] == 1. :
- int1[0] += 1
- int1[1] = 0.
- if int1==int2 :
- continue
- if int2[1] == 0. :
- int2[0] -= 1
- int2[1] = 1.
- if int1[0] == 0 and int2[0]==len(subpath)-1:# and small(int1[1]) and small(int2[1]-1) :
- continue
- if int1[0]==int2[0] : # same segment
- sp = csp_split_by_two_points(subpath[int1[0]-1],subpath[int1[0]],int1[1], int2[1])
- if sp[1]!=sp[2] :
- parts += [ [sp[1],sp[2]] ]
- else :
- sp5,sp1,sp2 = csp_split(subpath[int1[0]-1],subpath[int1[0]],int1[1])
- sp3,sp4,sp5 = csp_split(subpath[int2[0]-1],subpath[int2[0]],int2[1])
- if int1[0]==int2[0]-1 :
- parts += [ [sp1, [sp2[0],sp2[1],sp3[2]], sp4] ]
- else :
- parts += [ [sp1,sp2]+subpath[int1[0]+1:int2[0]-1]+[sp3,sp4] ]
- return parts
- def arc_from_s_r_n_l(s,r,n,l) :
- if abs(n[0]**2+n[1]**2 - 1) > 1e-10 : n = normalize(n)
- return arc_from_c_s_l([s[0]+n[0]*r, s[1]+n[1]*r],s,l)
- def arc_from_c_s_l(c,s,l) :
- r = point_to_point_d(c,s)
- if r == 0 : return []
- alpha = l/r
- cos_, sin_ = math.cos(alpha), math.sin(alpha)
- 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_]
- n = [c[0]-s[0],c[1]-s[1]]
- slope = rotate_cw(n) if l>0 else rotate_ccw(n)
- return csp_from_arc(s, e, c, r, slope)
- def csp_from_arc(start, end, center, r, slope_st) :
- # Creates csp that approximise specified arc
- r = abs(r)
- alpha = (atan2(end[0]-center[0],end[1]-center[1]) - atan2(start[0]-center[0],start[1]-center[1])) % math.pi2
- sectors = int(abs(alpha)*2/math.pi)+1
- alpha_start = atan2(start[0]-center[0],start[1]-center[1])
- cos_,sin_ = math.cos(alpha_start), math.sin(alpha_start)
- k = (4.*math.tan(alpha/sectors/4.)/3.)
- if dot(slope_st , [- sin_*k*r, cos_*k*r]) < 0 :
- if alpha>0 : alpha -= math.pi2
- else: alpha += math.pi2
- if abs(alpha*r)<0.001 :
- return []
- sectors = int(abs(alpha)*2/math.pi)+1
- k = (4.*math.tan(alpha/sectors/4.)/3.)
- result = []
- for i in range(sectors+1) :
- cos_,sin_ = math.cos(alpha_start + alpha*i/sectors), math.sin(alpha_start + alpha*i/sectors)
- sp = [ [], [center[0] + cos_*r, center[1] + sin_*r], [] ]
- sp[0] = [sp[1][0] + sin_*k*r, sp[1][1] - cos_*k*r ]
- sp[2] = [sp[1][0] - sin_*k*r, sp[1][1] + cos_*k*r ]
- result += [sp]
- result[0][0] = result[0][1][:]
- result[-1][2] = result[-1][1]
- return result
- def point_to_arc_distance(p, arc):
- ### Distance calculattion from point to arc
- P0,P2,c,a = arc
- dist = None
- p = P(p)
- r = (P0-c).mag()
- if r>0 :
- i = c + (p-c).unit()*r
- alpha = ((i-c).angle() - (P0-c).angle())
- if a*alpha<0:
- if alpha>0: alpha = alpha-math.pi2
- else: alpha = math.pi2+alpha
- if between(alpha,0,a) or min(abs(alpha),abs(alpha-a))<straight_tolerance :
- return (p-i).mag(), [i.x, i.y]
- else :
- d1, d2 = (p-P0).mag(), (p-P2).mag()
- if d1<d2 :
- return (d1, [P0.x,P0.y])
- else :
- return (d2, [P2.x,P2.y])
- def csp_to_arc_distance(sp1,sp2, arc1, arc2, tolerance = 0.01 ): # arc = [start,end,center,alpha]
- n, i = 10, 0
- d, d1, dl = (0,(0,0)), (0,(0,0)), 0
- while i<1 or (abs(d1[0]-dl[0])>tolerance and i<4):
- i += 1
- dl = d1*1
- for j in range(n+1):
- t = float(j)/n
- p = csp_at_t(sp1,sp2,t)
- d = min(point_to_arc_distance(p,arc1), point_to_arc_distance(p,arc2))
- d1 = max(d1,d)
- n=n*2
- return d1[0]
- def csp_simple_bound_to_point_distance(p, csp):
- minx,miny,maxx,maxy = None,None,None,None
- for subpath in csp:
- for sp in subpath:
- for p_ in sp:
- minx = min(minx,p_[0]) if minx!=None else p_[0]
- miny = min(miny,p_[1]) if miny!=None else p_[1]
- maxx = max(maxx,p_[0]) if maxx!=None else p_[0]
- maxy = max(maxy,p_[1]) if maxy!=None else p_[1]
- return math.sqrt(max(minx-p[0],p[0]-maxx,0)**2+max(miny-p[1],p[1]-maxy,0)**2)
- def csp_point_inside_bound(sp1, sp2, p):
- bez = [sp1[1],sp1[2],sp2[0],sp2[1]]
- x,y = p
- c = 0
- #CLT added test of x in range
- xmin=1e100
- xmax=-1e100
- for i in range(4):
- [x0,y0], [x1,y1] = bez[i-1], bez[i]
- xmin=min(xmin,x0)
- xmax=max(xmax,x0)
- if x0-x1!=0 and (y-y0)*(x1-x0)>=(x-x0)*(y1-y0) and x>min(x0,x1) and x<=max(x0,x1) :
- c +=1
- return xmin<=x<=xmax and c%2==0
- def csp_bound_to_point_distance(sp1, sp2, p):
- if csp_point_inside_bound(sp1, sp2, p) :
- return 0.
- bez = csp_segment_to_bez(sp1,sp2)
- min_dist = 1e100
- for i in range(0,4):
- d = point_to_line_segment_distance_2(p, bez[i-1],bez[i])
- if d <= min_dist : min_dist = d
- return min_dist
- def line_line_intersect(p1,p2,p3,p4) : # Return only true intersection.
- if (p1[0]==p2[0] and p1[1]==p2[1]) or (p3[0]==p4[0] and p3[1]==p4[1]) : return False
- x = (p2[0]-p1[0])*(p4[1]-p3[1]) - (p2[1]-p1[1])*(p4[0]-p3[0])
- if x==0 : # Lines are parallel
- if (p3[0]-p1[0])*(p2[1]-p1[1]) == (p3[1]-p1[1])*(p2[0]-p1[0]) :
- if p3[0]!=p4[0] :
- t11 = (p1[0]-p3[0])/(p4[0]-p3[0])
- t12 = (p2[0]-p3[0])/(p4[0]-p3[0])
- t21 = (p3[0]-p1[0])/(p2[0]-p1[0])
- t22 = (p4[0]-p1[0])/(p2[0]-p1[0])
- else:
- t11 = (p1[1]-p3[1])/(p4[1]-p3[1])
- t12 = (p2[1]-p3[1])/(p4[1]-p3[1])
- t21 = (p3[1]-p1[1])/(p2[1]-p1[1])
- t22 = (p4[1]-p1[1])/(p2[1]-p1[1])
- return ("Overlap" if (0<=t11<=1 or 0<=t12<=1) and (0<=t21<=1 or 0<=t22<=1) else False)
- else: return False
- else :
- return (
- 0<=((p4[0]-p3[0])*(p1[1]-p3[1]) - (p4[1]-p3[1])*(p1[0]-p3[0]))/x<=1 and
- 0<=((p2[0]-p1[0])*(p1[1]-p3[1]) - (p2[1]-p1[1])*(p1[0]-p3[0]))/x<=1 )
- def line_line_intersection_points(p1,p2,p3,p4) : # Return only points [ (x,y) ]
- if (p1[0]==p2[0] and p1[1]==p2[1]) or (p3[0]==p4[0] and p3[1]==p4[1]) : return []
- x = (p2[0]-p1[0])*(p4[1]-p3[1]) - (p2[1]-p1[1])*(p4[0]-p3[0])
- if x==0 : # Lines are parallel
- if (p3[0]-p1[0])*(p2[1]-p1[1]) == (p3[1]-p1[1])*(p2[0]-p1[0]) :
- if p3[0]!=p4[0] :
- t11 = (p1[0]-p3[0])/(p4[0]-p3[0])
- t12 = (p2[0]-p3[0])/(p4[0]-p3[0])
- t21 = (p3[0]-p1[0])/(p2[0]-p1[0])
- t22 = (p4[0]-p1[0])/(p2[0]-p1[0])
- else:
- t11 = (p1[1]-p3[1])/(p4[1]-p3[1])
- t12 = (p2[1]-p3[1])/(p4[1]-p3[1])
- t21 = (p3[1]-p1[1])/(p2[1]-p1[1])
- t22 = (p4[1]-p1[1])/(p2[1]-p1[1])
- res = []
- if (0<=t11<=1 or 0<=t12<=1) and (0<=t21<=1 or 0<=t22<=1) :
- if 0<=t11<=1 : res += [p1]
- if 0<=t12<=1 : res += [p2]
- if 0<=t21<=1 : res += [p3]
- if 0<=t22<=1 : res += [p4]
- return res
- else: return []
- else :
- t1 = ((p4[0]-p3[0])*(p1[1]-p3[1]) - (p4[1]-p3[1])*(p1[0]-p3[0]))/x
- t2 = ((p2[0]-p1[0])*(p1[1]-p3[1]) - (p2[1]-p1[1])*(p1[0]-p3[0]))/x
- if 0<=t1<=1 and 0<=t2<=1 : return [ [p1[0]*(1-t1)+p2[0]*t1, p1[1]*(1-t1)+p2[1]*t1] ]
- else : return []
- def point_to_point_d2(a,b):
- return (a[0]-b[0])**2 + (a[1]-b[1])**2
- def point_to_point_d(a,b):
- return math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2)
- def point_to_line_segment_distance_2(p1, p2,p3) :
- # p1 - point, p2,p3 - line segment
- #draw_pointer(p1)
- w0 = [p1[0]-p2[0], p1[1]-p2[1]]
- v = [p3[0]-p2[0], p3[1]-p2[1]]
- c1 = w0[0]*v[0] + w0[1]*v[1]
- if c1 <= 0 :
- return w0[0]*w0[0]+w0[1]*w0[1]
- c2 = v[0]*v[0] + v[1]*v[1]
- if c2 <= c1 :
- return (p1[0]-p3[0])**2 + (p1[1]-p3[1])**2
- return (p1[0]- p2[0]-v[0]*c1/c2)**2 + (p1[1]- p2[1]-v[1]*c1/c2)
- def line_to_line_distance_2(p1,p2,p3,p4):
- if line_line_intersect(p1,p2,p3,p4) : return 0
- return min(
- point_to_line_segment_distance_2(p1,p3,p4),
- point_to_line_segment_distance_2(p2,p3,p4),
- point_to_line_segment_distance_2(p3,p1,p2),
- point_to_line_segment_distance_2(p4,p1,p2))
- def csp_seg_bound_to_csp_seg_bound_max_min_distance(sp1,sp2,sp3,sp4) :
- bez1 = csp_segment_to_bez(sp1,sp2)
- bez2 = csp_segment_to_bez(sp3,sp4)
- min_dist = 1e100
- max_dist = 0.
- for i in range(4) :
- if csp_point_inside_bound(sp1, sp2, bez2[i]) or csp_point_inside_bound(sp3, sp4, bez1[i]) :
- min_dist = 0.
- break
- for i in range(4) :
- for j in range(4) :
- d = line_to_line_distance_2(bez1[i-1],bez1[i],bez2[j-1],bez2[j])
- if d < min_dist : min_dist = d
- d = (bez2[j][0]-bez1[i][0])**2 + (bez2[j][1]-bez1[i][1])**2
- if max_dist < d : max_dist = d
- return min_dist, max_dist
- def csp_reverse(csp) :
- for i in range(len(csp)) :
- n = []
- for j in csp[i] :
- n = [ [j[2][:],j[1][:],j[0][:]] ] + n
- csp[i] = n[:]
- return csp
- def csp_normalized_slope(sp1,sp2,t) :
- ax,ay,bx,by,cx,cy,dx,dy=bezmisc.bezierparameterize((sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]))
- if sp1[1]==sp2[1]==sp1[2]==sp2[0] : return [1.,0.]
- f1x = 3*ax*t*t+2*bx*t+cx
- f1y = 3*ay*t*t+2*by*t+cy
- if abs(f1x*f1x+f1y*f1y) > 1e-9 : #LT changed this from 1e-20, which caused problems
- l = math.sqrt(f1x*f1x+f1y*f1y)
- return [f1x/l, f1y/l]
- if t == 0 :
- f1x = sp2[0][0]-sp1[1][0]
- f1y = sp2[0][1]-sp1[1][1]
- if abs(f1x*f1x+f1y*f1y) > 1e-9 : #LT changed this from 1e-20, which caused problems
- l = math.sqrt(f1x*f1x+f1y*f1y)
- return [f1x/l, f1y/l]
- else :
- f1x = sp2[1][0]-sp1[1][0]
- f1y = sp2[1][1]-sp1[1][1]
- if f1x*f1x+f1y*f1y != 0 :
- l = math.sqrt(f1x*f1x+f1y*f1y)
- return [f1x/l, f1y/l]
- elif t == 1 :
- f1x = sp2[1][0]-sp1[2][0]
- f1y = sp2[1][1]-sp1[2][1]
- if abs(f1x*f1x+f1y*f1y) > 1e-9 :
- l = math.sqrt(f1x*f1x+f1y*f1y)
- return [f1x/l, f1y/l]
- else :
- f1x = sp2[1][0]-sp1[1][0]
- f1y = sp2[1][1]-sp1[1][1]
- if f1x*f1x+f1y*f1y != 0 :
- l = math.sqrt(f1x*f1x+f1y*f1y)
- return [f1x/l, f1y/l]
- else :
- return [1.,0.]
- def csp_normalized_normal(sp1,sp2,t) :
- nx,ny = csp_normalized_slope(sp1,sp2,t)
- return [-ny, nx]
- def csp_parameterize(sp1,sp2):
- return bezmisc.bezierparameterize(csp_segment_to_bez(sp1,sp2))
- def csp_concat_subpaths(*s):
- def concat(s1,s2) :
- if s1 == [] : return s2
- if s2 == [] : return s1
- if (s1[-1][1][0]-s2[0][1][0])**2 + (s1[-1][1][1]-s2[0][1][1])**2 > 0.00001 :
- return s1[:-1]+[ [s1[-1][0],s1[-1][1],s1[-1][1]], [s2[0][1],s2[0][1],s2[0][2]] ] + s2[1:]
- else :
- return s1[:-1]+[ [s1[-1][0],s2[0][1],s2[0][2]] ] + s2[1:]
- if len(s) == 0 : return []
- if len(s) ==1 : return s[0]
- result = s[0]
- for s1 in s[1:]:
- result = concat(result,s1)
- return result
- def csp_subpaths_end_to_start_distance2(s1,s2):
- return (s1[-1][1][0]-s2[0][1][0])**2 + (s1[-1][1][1]-s2[0][1][1])**2
- def csp_clip_by_line(csp,l1,l2) :
- result = []
- for i in range(len(csp)):
- s = csp[i]
- intersections = []
- for j in range(1,len(s)) :
- intersections += [ [j,int_] for int_ in csp_line_intersection(l1,l2,s[j-1],s[j])]
- splitted_s = csp_subpath_split_by_points(s, intersections)
- for s in splitted_s[:] :
- clip = False
- for p in csp_true_bounds([s]) :
- if (l1[1]-l2[1])*p[0] + (l2[0]-l1[0])*p[1] + (l1[0]*l2[1]-l2[0]*l1[1])<-0.01 :
- clip = True
- break
- if clip :
- splitted_s.remove(s)
- result += splitted_s
- return result
- def csp_subpath_line_to(subpath, points, prepend = False) :
- # Appends subpath with line or polyline.
- if len(points)>0 :
- if not prepend :
- if len(subpath)>0:
- subpath[-1][2] = subpath[-1][1][:]
- if type(points[0]) == type([1,1]) :
- for p in points :
- subpath += [ [p[:],p[:],p[:]] ]
- else:
- subpath += [ [points,points,points] ]
- else :
- if len(subpath)>0:
- subpath[0][0] = subpath[0][1][:]
- if type(points[0]) == type([1,1]) :
- for p in points :
- subpath = [ [p[:],p[:],p[:]] ] + subpath
- else:
- subpath = [ [points,points,points] ] + subpath
- return subpath
- def csp_join_subpaths(csp) :
- result = csp[:]
- done_smf = True
- joined_result = []
- while done_smf :
- done_smf = False
- while len(result)>0:
- s1 = result[-1][:]
- del(result[-1])
- j = 0
- joined_smf = False
- while j<len(joined_result) :
- if csp_subpaths_end_to_start_distance2(joined_result[j],s1) <0.000001 :
- joined_result[j] = csp_concat_subpaths(joined_result[j],s1)
- done_smf = True
- joined_smf = True
- break
- if csp_subpaths_end_to_start_distance2(s1,joined_result[j]) <0.000001 :
- joined_result[j] = csp_concat_subpaths(s1,joined_result[j])
- done_smf = True
- joined_smf = True
- break
- j += 1
- if not joined_smf : joined_result += [s1[:]]
- if done_smf :
- result = joined_result[:]
- joined_result = []
- return joined_result
- def triangle_cross(a,b,c):
- return (a[0]-b[0])*(c[1]-b[1]) - (c[0]-b[0])*(a[1]-b[1])
- def csp_segment_convex_hull(sp1,sp2):
- a,b,c,d = sp1[1][:], sp1[2][:], sp2[0][:], sp2[1][:]
- abc = triangle_cross(a,b,c)
- abd = triangle_cross(a,b,d)
- bcd = triangle_cross(b,c,d)
- cad = triangle_cross(c,a,d)
- if abc == 0 and abd == 0 : return [min(a,b,c,d), max(a,b,c,d)]
- if abc == 0 : return [d, min(a,b,c), max(a,b,c)]
- if abd == 0 : return [c, min(a,b,d), max(a,b,d)]
- if bcd == 0 : return [a, min(b,c,d), max(b,c,d)]
- if cad == 0 : return [b, min(c,a,d), max(c,a,d)]
- m1, m2, m3 = abc*abd>0, abc*bcd>0, abc*cad>0
- if m1 and m2 and m3 : return [a,b,c]
- if m1 and m2 and not m3 : return [a,b,c,d]
- if m1 and not m2 and m3 : return [a,b,d,c]
- if not m1 and m2 and m3 : return [a,d,b,c]
- if m1 and not (m2 and m3) : return [a,b,d]
- if not (m1 and m2) and m3 : return [c,a,d]
- if not (m1 and m3) and m2 : return [b,c,d]
- raise ValueError, "csp_segment_convex_hull happend something that shouldnot happen!"
- ################################################################################
- ### Bezier additional functions
- ################################################################################
- def bez_bounds_intersect(bez1, bez2) :
- return bounds_intersect(bez_bound(bez2), bez_bound(bez1))
- def bez_bound(bez) :
- return [
- min(bez[0][0], bez[1][0], bez[2][0], bez[3][0]),
- min(bez[0][1], bez[1][1], bez[2][1], bez[3][1]),
- max(bez[0][0], bez[1][0], bez[2][0], bez[3][0]),
- max(bez[0][1], bez[1][1], bez[2][1], bez[3][1]),
- ]
- def bounds_intersect(a, b) :
- return not ( (a[0]>b[2]) or (b[0]>a[2]) or (a[1]>b[3]) or (b[1]>a[3]) )
- def tpoint((x1,y1),(x2,y2),t):
- return [x1+t*(x2-x1),y1+t*(y2-y1)]
- def bez_to_csp_segment(bez) :
- return [bez[0],bez[0],bez[1]], [bez[2],bez[3],bez[3]]
- def bez_split(a,t=0.5) :
- a1 = tpoint(a[0],a[1],t)
- at = tpoint(a[1],a[2],t)
- b2 = tpoint(a[2],a[3],t)
- a2 = tpoint(a1,at,t)
- b1 = tpoint(b2,at,t)
- a3 = tpoint(a2,b1,t)
- return [a[0],a1,a2,a3], [a3,b1,b2,a[3]]
- def bez_at_t(bez,t) :
- return csp_at_t([bez[0],bez[0],bez[1]],[bez[2],bez[3],bez[3]],t)
- def bez_to_point_distance(bez,p,needed_dist=[0.,1e100]):
- # returns [d^2,t]
- return csp_seg_to_point_distance(bez_to_csp_segment(bez),p,needed_dist)
- def bez_normalized_slope(bez,t):
- return csp_normalized_slope([bez[0],bez[0],bez[1]], [bez[2],bez[3],bez[3]],t)
- ################################################################################
- ### Some vector functions
- ################################################################################
- def normalize((x,y)) :
- l = math.sqrt(x**2+y**2)
- if l == 0 : return [0.,0.]
- else : return [x/l, y/l]
- def cross(a,b) :
- return a[1] * b[0] - a[0] * b[1]
- def dot(a,b) :
- return a[0] * b[0] + a[1] * b[1]
- def rotate_ccw(d) :
- return [-d[1],d[0]]
- def rotate_cw(d) :
- return [d[1],-d[0]]
- def vectors_ccw(a,b):
- return a[0]*b[1]-b[0]*a[1] < 0
- def vector_add(a,b) :
- return [a[0]+b[0],a[1]+b[1]]
- def vector_mul(a,b) :
- return [a[0]*b,a[1]*b]
- def vector_from_to_length(a,b):
- return math.sqrt((a[0]-b[0])*(a[0]-b[0]) + (a[1]-b[1])*(a[1]-b[1]))
- ################################################################################
- ### Common functions
- ################################################################################
- def matrix_mul(a,b) :
- 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))]
- try :
- 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))]
- except :
- return None
- def transpose(a) :
- try :
- return [ [ a[i][j] for i in range(len(a)) ] for j in range(len(a[0])) ]
- except :
- return None
- def det_3x3(a):
- return float(
- a[0][0]*a[1][1]*a[2][2] + a[0][1]*a[1][2]*a[2][0] + a[1][0]*a[2][1]*a[0][2]
- - a[0][2]*a[1][1]*a[2][0] - a[0][0]*a[2][1]*a[1][2] - a[0][1]*a[2][2]*a[1][0]
- )
- def inv_3x3(a): # invert matrix 3x3
- det = det_3x3(a)
- if det==0: return None
- return [
- [ (a[1][1]*a[2][2] - a[2][1]*a[1][2])/det, -(a[0][1]*a[2][2] - a[2][1]*a[0][2])/det, (a[0][1]*a[1][2] - a[1][1]*a[0][2])/det ],
- [ -(a[1][0]*a[2][2] - a[2][0]*a[1][2])/det, (a[0][0]*a[2][2] - a[2][0]*a[0][2])/det, -(a[0][0]*a[1][2] - a[1][0]*a[0][2])/det ],
- [ (a[1][0]*a[2][1] - a[2][0]*a[1][1])/det, -(a[0][0]*a[2][1] - a[2][0]*a[0][1])/det, (a[0][0]*a[1][1] - a[1][0]*a[0][1])/det ]
- ]
- def inv_2x2(a): # invert matrix 2x2
- det = a[0][0]*a[1][1] - a[1][0]*a[0][1]
- if det==0: return None
- return [
- [a[1][1]/det, -a[0][1]/det],
- [-a[1][0]/det, a[0][0]/det]
- ]
- def small(a) :
- global small_tolerance
- return abs(a)<small_tolerance
- def atan2(*arg):
- if len(arg)==1 and ( type(arg[0]) == type([0.,0.]) or type(arg[0])==type((0.,0.)) ) :
- return (math.pi/2 - math.atan2(arg[0][0], arg[0][1]) ) % math.pi2
- elif len(arg)==2 :
- return (math.pi/2 - math.atan2(arg[0],arg[1]) ) % math.pi2
- else :
- raise ValueError, "Bad argumets for atan! (%s)" % arg
- def get_text(node) :
- value = None
- if node.text!=None : value = value +"\n" + node.text if value != None else node.text
- for k in node :
- if k.tag == inkex.addNS('tspan','svg'):
- if k.text!=None : value = value +"\n" + k.text if value != None else k.text
- return value
- def draw_text(text,x,y, group = None, style = None, font_size = 10, gcodetools_tag = None) :
- if style == None :
- 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;"
- style += "font-size:%fpx;"%font_size
- attributes = { 'x': str(x),
- inkex.addNS("space","xml"):"preserve",
- 'y': str(y),
- 'style' : style
- }
- if gcodetools_tag!=None :
- attributes["gcodetools"] = str(gcodetools_tag)
- if group == None:
- group = options.doc_root
- t = inkex.etree.SubElement( group, inkex.addNS('text','svg'), attributes)
- text = str(text).split("\n")
- for s in text :
- span = inkex.etree.SubElement( t, inkex.addNS('tspan','svg'),
- {
- 'x': str(x),
- 'y': str(y),
- inkex.addNS("role","sodipodi"):"line",
- })
- y += font_size
- span.text = s.encode('utf-8')
- def draw_csp(csp, stroke = "#f00", fill = "none", comment = "", width = 0.354, group = None, style = None, gcodetools_tag = None) :
- if style == None :
- style = "fill:%s;fill-opacity:1;stroke:%s;stroke-width:%s"%(fill,stroke,width)
- attributes = { 'd': cubicsuperpath.formatPath(csp),
- 'style' : style
- }
- if comment != '':
- attributes['comment'] = comment
- if group == None :
- group = options.doc_root
- return inkex.etree.SubElement( group, inkex.addNS('path','svg'), attributes)
- 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) :
- size = size/2
- if attrib == None : attrib = {}
- if pointer_type == None:
- pointer_type = "Pointer"
- attrib["gcodetools"] = pointer_type
- if group == None:
- group = options.self.current_layer
- if text != None :
- if font_size == None : font_size = 7
- group = inkex.etree.SubElement( group, inkex.addNS('g','svg'), {"gcodetools": pointer_type+" group"} )
- draw_text(text,x[0]+size*2.2,x[1]-size, group = group, font_size = font_size)
- if figure == "line" :
- s = ""
- for i in range(1,len(x)/2) :
- s+= " %s, %s " %(x[i*2],x[i*2+1])
- 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)})
- inkex.etree.SubElement( group, inkex.addNS('path','svg'), attrib)
- elif figure == "arrow" :
- if fill == None : fill = "#12b3ff"
- fill_opacity = "0.8"
- 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")
- attrib.update({"d": d, "style":"fill:%s;stroke:none;fill-opacity:%s;"%(fill,fill_opacity),"comment":str(comment)})
- inkex.etree.SubElement( group, inkex.addNS('path','svg'), attrib)
- else :
- 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)})
- inkex.etree.SubElement( group, inkex.addNS('path','svg'), attrib)
- def straight_segments_intersection(a,b, true_intersection = True) : # (True intersection means check ta and tb are in [0,1])
- 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]
- if (ax==bx and ay==by) or (cx==dx and cy==dy) : return False, 0, 0
- if (bx-ax)*(dy-cy)-(by-ay)*(dx-cx)==0 : # Lines are parallel
- ta = (ax-cx)/(dx-cx) if cx!=dx else (ay-cy)/(dy-cy)
- tb = (bx-cx)/(dx-cx) if cx!=dx else (by-cy)/(dy-cy)
- tc = (cx-ax)/(bx-ax) if ax!=bx else (cy-ay)/(by-ay)
- td = (dx-ax)/(bx-ax) if ax!=bx else (dy-ay)/(by-ay)
- 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)
- else :
- ta = ( (ay-cy)*(dx-cx)-(ax-cx)*(dy-cy) ) / ( (bx-ax)*(dy-cy)-(by-ay)*(dx-cx) )
- tb = ( ax-cx+ta*(bx-ax) ) / (dx-cx) if dx!=cx else ( ay-cy+ta*(by-ay) ) / (dy-cy)
- return (0<=ta<=1 and 0<=tb<=1 or not true_intersection), ta, tb
- def isnan(x): return type(x) is float and x != x
- def isinf(x): inf = 1e5000; return x == inf or x == -inf
- def between(c,x,y):
- return x-straight_tolerance<=c<=y+straight_tolerance or y-straight_tolerance<=c<=x+straight_tolerance
- def cubic_solver_real(a,b,c,d):
- # returns only real roots of a cubic equation.
- roots = cubic_solver(a,b,c,d)
- res = []
- for root in roots :
- if type(root) is complex :
- if -1e-10<root.imag<1e-10 :
- res.append(root.real)
- else :
- res.append(root)
- return res
- def cubic_solver(a,b,c,d):
- if a!=0:
- # Monics formula see http://en.wikipedia.org/wiki/Cubic_function#Monic_formula_of_roots
- a,b,c = (b/a, c/a, d/a)
- m = 2*a**3 - 9*a*b + 27*c
- k = a**2 - 3*b
- n = m**2 - 4*k**3
- w1 = -.5 + .5*cmath.sqrt(3)*1j
- w2 = -.5 - .5*cmath.sqrt(3)*1j
- if n>=0 :
- t = m+math.sqrt(n)
- m1 = pow(t/2,1./3) if t>=0 else -pow(-t/2,1./3)
- t = m-math.sqrt(n)
- n1 = pow(t/2,1./3) if t>=0 else -pow(-t/2,1./3)
- else :
- m1 = pow(complex((m+cmath.sqrt(n))/2),1./3)
- n1 = pow(complex((m-cmath.sqrt(n))/2),1./3)
- x1 = -1./3 * (a + m1 + n1)
- x2 = -1./3 * (a + w1*m1 + w2*n1)
- x3 = -1./3 * (a + w2*m1 + w1*n1)
- return [x1,x2,x3]
- elif b!=0:
- det = c**2-4*b*d
- if det>0 :
- return [(-c+math.sqrt(det))/(2*b),(-c-math.sqrt(det))/(2*b)]
- elif d == 0 :
- return [-c/(b*b)]
- else :
- return [(-c+cmath.sqrt(det))/(2*b),(-c-cmath.sqrt(det))/(2*b)]
- elif c!=0 :
- return [-d/c]
- else : return []
- ################################################################################
- ### print_ prints any arguments into specified log file
- ################################################################################
- def print_(*arg):
- f = open(options.log_filename,"a")
- for s in arg :
- s = str(unicode(s).encode('unicode_escape'))+" "
- f.write( s )
- f.write("\n")
- f.close()
- ################################################################################
- ### Point (x,y) operations
- ################################################################################
- class P:
- def __init__(self, x, y=None):
- if not y==None:
- self.x, self.y = float(x), float(y)
- else:
- self.x, self.y = float(x[0]), float(x[1])
- def __add__(self, other): return P(self.x + other.x, self.y + other.y)
- def __sub__(self, other): return P(self.x - other.x, self.y - other.y)
- def __neg__(self): return P(-self.x, -self.y)
- def __mul__(self, other):
- if isinstance(other, P):
- return self.x * other.x + self.y * other.y
- return P(self.x * other, self.y * other)
- __rmul__ = __mul__
- def __div__(self, other): return P(self.x / other, self.y / other)
- def mag(self): return math.hypot(self.x, self.y)
- def unit(self):
- h = self.mag()
- if h: return self / h
- else: return P(0,0)
- def dot(self, other): return self.x * other.x + self.y * other.y
- def rot(self, theta):
- c = math.cos(theta)
- s = math.sin(theta)
- return P(self.x * c - self.y * s, self.x * s + self.y * c)
- def angle(self): return math.atan2(self.y, self.x)
- def __repr__(self): return '%f,%f' % (self.x, self.y)
- def pr(self): return "%.2f,%.2f" % (self.x, self.y)
- def to_list(self): return [self.x, self.y]
- def ccw(self): return P(-self.y,self.x)
- def l2(self): return self.x*self.x + self.y*self.y
- class Arc():
- def __init__(self,st,end,c,a):
- self.st = P(st)
- self.end = P(end)
- self.c = P(c)
- self.r = (P(st)-P(c)).mag()
- self.a = ( (self.st-self.c).angle() - (self.end-self.c).angle() ) % math.pi2
- if a<0 : self.a -= math.pi2
- def offset(self, r):
- if self.a>0 :
- r += self.r
- else :
- r = self.r - r
- if self.r != 0 :
- self.st = self.c + (self.st-self.c)*r/self.r
- self.end = self.c + (self.end-self.c)*r/self.r
- self.r = r
- def length(self):
- return abs(self.a*self.r)
- def draw(self, group, style, layer, transform, num = 0, reverse_angle = 1):
- st = P(gcodetools.transform(self.st.to_list(), layer, True))
- c = P(gcodetools.transform(self.c.to_list(), layer, True))
- a = self.a * reverse_angle
- r = (st-c)
- a_st = (math.atan2(r.x,-r.y) - math.pi/2) % (math.pi*2)
- r = r.mag()
- if a<0:
- a_end = a_st+a
- style = style['biarc%s'%(num%2)]
- else:
- a_end = a_st
- a_st = a_st+a
- style = style['biarc%s_r'%(num%2)]
- attr = {
- 'style': style,
- inkex.addNS('cx','sodipodi'): str(c.x),
- inkex.addNS('cy','sodipodi'): str(c.y),
- inkex.addNS('rx','sodipodi'): str(r),
- inkex.addNS('ry','sodipodi'): str(r),
- inkex.addNS('start','sodipodi'): str(a_st),
- inkex.addNS('end','sodipodi'): str(a_end),
- inkex.addNS('open','sodipodi'): 'true',
- inkex.addNS('type','sodipodi'): 'arc',
- "gcodetools": "Preview",
- }
- if transform != [] :
- attr["transform"] = transform
- inkex.etree.SubElement( group, inkex.addNS('path','svg'), attr)
- def intersect(self,b) :
- return []
- class Line():
- def __init__(self,st,end):
- if st.__class__ == P :
- st = st.to_list()
- if end.__class__ == P :
- end = end.to_list()
- self.st = P(st)
- self.end = P(end)
- self.l = self.length()
- if self.l != 0 :
- self.n = ((self.end-self.st)/self.l).ccw()
- else:
- self.n = [0,1]
- def offset(self, r):
- self.st -= self.n*r
- self.end -= self.n*r
- def l2(self): return (self.st-self.end).l2()
- def length(self): return (self.st-self.end).mag()
- def draw(self, group, style, layer, transform, num = 0, reverse_angle = 1):
- st = gcodetools.transform(self.st.to_list(), layer, True)
- end = gcodetools.transform(self.end.to_list(), layer, True)
- attr = { 'style': style['line'],
- 'd':'M %s,%s L %s,%s' % (st[0],st[1],end[0],end[1]),
- "gcodetools": "Preview",
- }
- if transform != [] :
- attr["transform"] = transform
- inkex.etree.SubElement( group, inkex.addNS('path','svg'), attr )
- def intersect(self,b) :
- if b.__class__ == Line :
- if self.l < 10e-8 or b.l < 10e-8 : return []
- v1 = self.end - self.st
- v2 = b.end - b.st
- x = v1.x*v2.y - v2.x*v1.y
- if x == 0 :
- # lines are parallel
- res = []
- if (self.st.x-b.st.x)*v1.y - (self.st.y-b.st.y)*v1.x == 0:
- # lines are the same
- if v1.x != 0 :
- if 0<=(self.st.x-b.st.x)/v2.x<=1 : res.append(self.st)
- if 0<=(self.end.x-b.st.x)/v2.x<=1 : res.append(self.end)
- if 0<=(b.st.x-self.st.x)/v1.x<=1 : res.append(b.st)
- if 0<=(b.end.x-b.st.x)/v1.x<=1 : res.append(b.end)
- else :
- if 0<=(self.st.y-b.st.y)/v2.y<=1 : res.append(self.st)
- if 0<=(self.end.y-b.st.y)/v2.y<=1 : res.append(self.end)
- if 0<=(b.st.y-self.st.y)/v1.y<=1 : res.append(b.st)
- if 0<=(b.end.y-b.st.y)/v1.y<=1 : res.append(b.end)
- return res
- else :
- t1 = ( -v1.x*(b.end.y-self.end.y) + v1.y*(b.end.x-self.end.x) ) / x
- t2 = ( -v1.y*(self.st.x-b.st.x) + v1.x*(self.st.y-b.st.y) ) / x
- gcodetools.error((x,t1,t2), "warning")
- if 0<=t1<=1 and 0<=t2<=1 : return [ self.st+v1*t1 ]
- else : return []
- else: return []
- class Biarc:
- def __init__(self, items=None):
- if items == None :
- self.items = []
- else:
- self.items = items
- def l(self) :
- return sum([i.length() for i in items])
- def close(self) :
- for subitems in self.items:
- if (subitems[0].st-subitems[-1].end).l2()>10e-16 :
- subitems.append(Line(subitems[-1].end,subitems[0].st))
- def offset(self,r) :
- # offset each element
- self.close()
- for subitems in self.items :
- for item in subitems :
- item.offset(r)
- self.connect(r)
- def connect(self, r) :
- for subitems in self.items :
- for a,b in zip(subitems, subitems[1:]) :
- i = a.intersect(b)
- for p in i :
- draw_pointer(p.to_list())
- def clip_offset(self):
- pass
- def draw(self, layer, group=None, style=styles["biarc_style"]):
- global gcodetools
- gcodetools.set_markers()
- for i in [0,1]:
- style['biarc%s_r'%i] = simplestyle.parseStyle(style['biarc%s'%i])
- style['biarc%s_r'%i]["marker-start"] = "url(#DrawCurveMarker_r)"
- del(style['biarc%s_r'%i]["marker-end"])
- style['biarc%s_r'%i] = simplestyle.formatStyle(style['biarc%s_r'%i])
- if group==None:
- if "preview_groups" not in dir(options.self) :
- gcodetools.preview_groups = { layer: inkex.etree.SubElement( gcodetools.layers[min(1,len(gcodetools.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} ) }
- elif layer not in gcodetools.preview_groups :
- gcodetools.preview_groups[layer] = inkex.etree.SubElement( gcodetools.layers[min(1,len(gcodetools.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} )
- group = gcodetools.preview_groups[layer]
- transform = gcodetools.get_transforms(group)
- if transform != [] :
- transform = gcodetools.reverse_transform(transform)
- transform = simpletransform.formatTransform(transform)
- a,b,c = [0.,0.], [1.,0.], [0.,1.]
- k = (b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1])
- a,b,c = gcodetools.transform(a, layer, True), gcodetools.transform(b, layer, True), gcodetools.transform(c, layer, True)
- if ((b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1]))*k > 0 : reverse_angle = -1
- else : reverse_angle = 1
- num = 0
- for subitems in self.items :
- for item in subitems :
- num += 1
- #if num>1 : break
- item.draw(group, style, layer, transform, num, reverse_angle)
- def from_old_style(self, curve) :
- #Crve defenitnion [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]]
- self.items = []
- for sp in curve:
- print_(sp)
- if sp[1] == 'move':
- self.items.append([])
- if sp[1] == 'arc':
- self.items[-1].append(Arc(sp[0],sp[4],sp[2],sp[3]))
- if sp[1] == 'line':
- self.items[-1].append(Line(sp[0],sp[4]))
- ################################################################################
- ###
- ### Offset function
- ###
- ### This function offsets given cubic super path.
- ### It's based on src/livarot/PathOutline.cpp from Inkscape's source code.
- ###
- ###
- ################################################################################
- def csp_offset(csp, r) :
- offset_tolerance = 0.05
- offset_subdivision_depth = 10
- time_ = time.time()
- time_start = time_
- print_("Offset start at %s"% time_)
- print_("Offset radius %s"% r)
- def csp_offset_segment(sp1,sp2,r) :
- result = []
- t = csp_get_t_at_curvature(sp1,sp2,1/r)
- if len(t) == 0 : t =[0.,1.]
- t.sort()
- if t[0]>.00000001 : t = [0.]+t
- if t[-1]<.99999999 : t.append(1.)
- for st,end in zip(t,t[1:]) :
- c = csp_curvature_at_t(sp1,sp2,(st+end)/2)
- sp = csp_split_by_two_points(sp1,sp2,st,end)
- if sp[1]!=sp[2]:
- if (c>1/r and r<0 or c<1/r and r>0) :
- offset = offset_segment_recursion(sp[1],sp[2],r, offset_subdivision_depth, offset_tolerance)
- else : # This part will be clipped for sure... TODO Optimize it...
- offset = offset_segment_recursion(sp[1],sp[2],r, offset_subdivision_depth, offset_tolerance)
- if result==[] :
- result = offset[:]
- else:
- if csp_subpaths_end_to_start_distance2(result,offset)<0.0001 :
- result = csp_concat_subpaths(result,offset)
- else:
- intersection = csp_get_subapths_last_first_intersection(result,offset)
- if intersection != [] :
- i,t1,j,t2 = intersection
- sp1_,sp2_,sp3_ = csp_split(result[i-1],result[i],t1)
- result = result[:i-1] + [ sp1_, sp2_ ]
- sp1_,sp2_,sp3_ = csp_split(offset[j-1],offset[j],t2)
- result = csp_concat_subpaths( result, [sp2_,sp3_] + offset[j+1:] )
- else :
- pass # ???
- #raise ValueError, "Offset curvature clipping error"
- #draw_csp([result])
- return result
- def create_offset_segment(sp1,sp2,r) :
- # See Gernot Hoffmann "Bezier Curves" p.34 -> 7.1 Bezier Offset Curves
- p0,p1,p2,p3 = P(sp1[1]),P(sp1[2]),P(sp2[0]),P(sp2[1])
- s0,s1,s3 = p1-p0,p2-p1,p3-p2
- n0 = s0.ccw().unit() if s0.l2()!=0 else P(csp_normalized_normal(sp1,sp2,0))
- n3 = s3.ccw().unit() if s3.l2()!=0 else P(csp_normalized_normal(sp1,sp2,1))
- n1 = s1.ccw().unit() if s1.l2()!=0 else (n0.unit()+n3.unit()).unit()
- q0,q3 = p0+r*n0, p3+r*n3
- c = csp_curvature_at_t(sp1,sp2,0)
- q1 = q0 + (p1-p0)*(1- (r*c if abs(c)<100 else 0) )
- c = csp_curvature_at_t(sp1,sp2,1)
- q2 = q3 + (p2-p3)*(1- (r*c if abs(c)<100 else 0) )
- return [[q0.to_list(), q0.to_list(), q1.to_list()],[q2.to_list(), q3.to_list(), q3.to_list()]]
- def csp_get_subapths_last_first_intersection(s1,s2):
- _break = False
- for i in range(1,len(s1)) :
- sp11, sp12 = s1[-i-1], s1[-i]
- for j in range(1,len(s2)) :
- sp21,sp22 = s2[j-1], s2[j]
- intersection = csp_segments_true_intersection(sp11,sp12,sp21,sp22)
- if intersection != [] :
- _break = True
- break
- if _break:break
- if _break :
- intersection = max(intersection)
- return [len(s1)-i,intersection[0], j,intersection[1]]
- else :
- return []
- def csp_join_offsets(prev,next,sp1,sp2,sp1_l,sp2_l,r):
- if len(next)>1 :
- if (P(prev[-1][1])-P(next[0][1])).l2()<0.001 :
- return prev,[],next
- intersection = csp_get_subapths_last_first_intersection(prev,next)
- if intersection != [] :
- i,t1,j,t2 = intersection
- sp1_,sp2_,sp3_ = csp_split(prev[i-1],prev[i],t1)
- sp3_,sp4_,sp5_ = csp_split(next[j-1], next[j],t2)
- return prev[:i-1] + [ sp1_, sp2_ ], [], [sp4_,sp5_] + next[j+1:]
- # Offsets do not intersect... will add an arc...
- start = (P(csp_at_t(sp1_l,sp2_l,1.)) + r*P(csp_normalized_normal(sp1_l,sp2_l,1.))).to_list()
- end = (P(csp_at_t(sp1,sp2,0.)) + r*P(csp_normalized_normal(sp1,sp2,0.))).to_list()
- arc = csp_from_arc(start, end, sp1[1], r, csp_normalized_slope(sp1_l,sp2_l,1.) )
- if arc == [] :
- return prev,[],next
- else:
- # Clip prev by arc
- if csp_subpaths_end_to_start_distance2(prev,arc)>0.00001 :
- intersection = csp_get_subapths_last_first_intersection(prev,arc)
- if intersection != [] :
- i,t1,j,t2 = intersection
- sp1_,sp2_,sp3_ = csp_split(prev[i-1],prev[i],t1)
- sp3_,sp4_,sp5_ = csp_split(arc[j-1],arc[j],t2)
- prev = prev[:i-1] + [ sp1_, sp2_ ]
- arc = [sp4_,sp5_] + arc[j+1:]
- #else : raise ValueError, "Offset curvature clipping error"
- # Clip next by arc
- if next == [] :
- return prev,[],arc
- if csp_subpaths_end_to_start_distance2(arc,next)>0.00001 :
- intersection = csp_get_subapths_last_first_intersection(arc,next)
- if intersection != [] :
- i,t1,j,t2 = intersection
- sp1_,sp2_,sp3_ = csp_split(arc[i-1],arc[i],t1)
- sp3_,sp4_,sp5_ = csp_split(next[j-1],next[j],t2)
- arc = arc[:i-1] + [ sp1_, sp2_ ]
- next = [sp4_,sp5_] + next[j+1:]
- #else : raise ValueError, "Offset curvature clipping error"
- return prev,arc,next
- def offset_segment_recursion(sp1,sp2,r, depth, tolerance) :
- sp1_r,sp2_r = create_offset_segment(sp1,sp2,r)
- err = max(
- 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],
- 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],
- 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],
- )
- if err>tolerance**2 and depth>0:
- #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)
- if depth > offset_subdivision_depth-2 :
- t = csp_max_curvature(sp1,sp2)
- t = max(.1,min(.9 ,t))
- else :
- t = .5
- sp3,sp4,sp5 = csp_split(sp1,sp2,t)
- r1 = offset_segment_recursion(sp3,sp4,r, depth-1, tolerance)
- r2 = offset_segment_recursion(sp4,sp5,r, depth-1, tolerance)
- return r1[:-1]+ [[r1[-1][0],r1[-1][1],r2[0][2]]] + r2[1:]
- else :
- #draw_csp([[sp1_r,sp2_r]])
- #draw_pointer(sp1[1]+sp1_r[1], "#057", "line")
- #draw_pointer(sp2[1]+sp2_r[1], "#705", "line")
- return [sp1_r,sp2_r]
- ############################################################################
- # Some small definitions
- ############################################################################
- csp_len = len(csp)
- ############################################################################
- # Prepare the path
- ############################################################################
- # Remove all small segments (segment length < 0.001)
- for i in xrange(len(csp)) :
- for j in xrange(len(csp[i])) :
- sp = csp[i][j]
- if (P(sp[1])-P(sp[0])).mag() < 0.001 :
- csp[i][j][0] = sp[1]
- if (P(sp[2])-P(sp[0])).mag() < 0.001 :
- csp[i][j][2] = sp[1]
- for i in xrange(len(csp)) :
- for j in xrange(1,len(csp[i])) :
- if cspseglength(csp[i][j-1], csp[i][j])<0.001 :
- csp[i] = csp[i][:j] + csp[i][j+1:]
- if cspseglength(csp[i][-1],csp[i][0])>0.001 :
- csp[i][-1][2] = csp[i][-1][1]
- csp[i]+= [ [csp[i][0][1],csp[i][0][1],csp[i][0][1]] ]
- # TODO Get rid of self intersections.
- original_csp = csp[:]
- # Clip segments which has curvature>1/r. Because their offset will be selfintersecting and very nasty.
- print_("Offset prepared the path in %s"%(time.time()-time_))
- print_("Path length = %s"% sum([len(i)for i in csp] ) )
- time_ = time.time()
- ############################################################################
- # Offset
- ############################################################################
- # Create offsets for all segments in the path. And join them together inside each subpath.
- unclipped_offset = [[] for i in xrange(csp_len)]
- offsets_original = [[] for i in xrange(csp_len)]
- join_points = [[] for i in xrange(csp_len)]
- intersection = [[] for i in xrange(csp_len)]
- for i in xrange(csp_len) :
- subpath = csp[i]
- subpath_offset = []
- last_offset_len = 0
- for sp1,sp2 in zip(subpath, subpath[1:]) :
- segment_offset = csp_offset_segment(sp1,sp2,r)
- if subpath_offset == [] :
- subpath_offset = segment_offset
- prev_l = len(subpath_offset)
- else :
- prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:],segment_offset,sp1,sp2,sp1_l,sp2_l,r)
- #draw_csp([prev],"Blue")
- #draw_csp([arc],"Magenta")
- subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l+1],prev,arc,next)
- prev_l = len(next)
- sp1_l, sp2_l = sp1[:], sp2[:]
- # Join last and first offsets togother to close the curve
- prev, arc, next = csp_join_offsets(subpath_offset[-prev_l:], subpath_offset[:2], subpath[0], subpath[1], sp1_l,sp2_l, r)
- subpath_offset[:2] = next[:]
- subpath_offset = csp_concat_subpaths(subpath_offset[:-prev_l+1],prev,arc)
- #draw_csp([prev],"Blue")
- #draw_csp([arc],"Red")
- #draw_csp([next],"Red")
- # Collect subpath's offset and save it to unclipped offset list.
- unclipped_offset[i] = subpath_offset[:]
- #for k,t in intersection[i]:
- # draw_pointer(csp_at_t(subpath_offset[k-1], subpath_offset[k], t))
- #inkex.etree.SubElement( options.doc_root, inkex.addNS('path','svg'), {"d": cubicsuperpath.formatPath(unclipped_offset), "style":"fill:none;stroke:#0f0;"} )
- print_("Offsetted path in %s"%(time.time()-time_))
- time_ = time.time()
- #for i in range(len(unclipped_offset)):
- # draw_csp([unclipped_offset[i]], color = ["Green","Red","Blue"][i%3], width = .1)
- #return []
- ############################################################################
- # Now to the clipping.
- ############################################################################
- # First of all find all intersection's between all segments of all offseted subpaths, including self intersections.
- #TODO define offset tolerance here
- global small_tolerance
- small_tolerance = 0.01
- summ = 0
- summ1 = 0
- for subpath_i in xrange(csp_len) :
- for subpath_j in xrange(subpath_i,csp_len) :
- subpath = unclipped_offset[subpath_i]
- subpath1 = unclipped_offset[subpath_j]
- for i in xrange(1,len(subpath)) :
- # If subpath_i==subpath_j we are looking for self intersections, so
- # we'll need search intersections only for xrange(i,len(subpath1))
- for j in ( xrange(i,len(subpath1)) if subpath_i==subpath_j else xrange(len(subpath1))) :
- if subpath_i==subpath_j and j==i :
- # Find self intersections of a segment
- sp1,sp2,sp3 = csp_split(subpath[i-1],subpath[i],.5)
- intersections = csp_segments_intersection(sp1,sp2,sp2,sp3)
- summ +=1
- for t in intersections :
- summ1 += 1
- if not ( small(t[0]-1) and small(t[1]) ) and 0<=t[0]<=1 and 0<=t[1]<=1 :
- intersection[subpath_i] += [ [i,t[0]/2],[j,t[1]/2+.5] ]
- else :
- intersections = csp_segments_intersection(subpath[i-1],subpath[i],subpath1[j-1],subpath1[j])
- summ +=1
- for t in intersections :
- summ1 += 1
- #TODO tolerance dependence to cpsp_length(t)
- if len(t) == 2 and 0<=t[0]<=1 and 0<=t[1]<=1 and not (
- subpath_i==subpath_j and (
- (j-i-1) % (len(subpath)-1) == 0 and small(t[0]-1) and small(t[1]) or
- (i-j-1) % (len(subpath)-1) == 0 and small(t[1]-1) and small(t[0]) ) ) :
- intersection[subpath_i] += [ [i,t[0]] ]
- intersection[subpath_j] += [ [j,t[1]] ]
- #draw_pointer(csp_at_t(subpath[i-1],subpath[i],t[0]),"#f00")
- #print_(t)
- #print_(i,j)
- elif len(t)==5 and t[4]=="Overlap":
- intersection[subpath_i] += [ [i,t[0]], [i,t[1]] ]
- intersection[subpath_j] += [ [j,t[1]], [j,t[3]] ]
- print_("Intersections found in %s"%(time.time()-time_))
- print_("Examined %s segments"%(summ))
- print_("found %s intersections"%(summ1))
- time_ = time.time()
- ########################################################################
- # Split unclipped offset by intersection points into splitted_offset
- ########################################################################
- splitted_offset = []
- for i in xrange(csp_len) :
- subpath = unclipped_offset[i]
- if len(intersection[i]) > 0 :
- parts = csp_subpath_split_by_points(subpath, intersection[i])
- # Close parts list to close path (The first and the last parts are joined together)
- if [1,0.] not in intersection[i] :
- parts[0][0][0] = parts[-1][-1][0]
- parts[0] = csp_concat_subpaths(parts[-1], parts[0])
- splitted_offset += parts[:-1]
- else:
- splitted_offset += parts[:]
- else :
- splitted_offset += [subpath[:]]
- #for i in range(len(splitted_offset)):
- # draw_csp([splitted_offset[i]], color = ["Green","Red","Blue"][i%3])
- print_("Splitted in %s"%(time.time()-time_))
- time_ = time.time()
- ########################################################################
- # Clipping
- ########################################################################
- result = []
- for subpath_i in range(len(splitted_offset)):
- clip = False
- s1 = splitted_offset[subpath_i]
- for subpath_j in range(len(splitted_offset)):
- s2 = splitted_offset[subpath_j]
- if (P(s1[0][1])-P(s2[-1][1])).l2()<0.0001 and ( (subpath_i+1) % len(splitted_offset) != subpath_j ):
- if dot(csp_normalized_normal(s2[-2],s2[-1],1.),csp_normalized_slope(s1[0],s1[1],0.))*r<-0.0001 :
- clip = True
- break
- if (P(s2[0][1])-P(s1[-1][1])).l2()<0.0001 and ( (subpath_j+1) % len(splitted_offset) != subpath_i ):
- if dot(csp_normalized_normal(s2[0],s2[1],0.),csp_normalized_slope(s1[-2],s1[-1],1.))*r>0.0001 :
- clip = True
- break
- if not clip :
- result += [s1[:]]
- elif options.offset_draw_clippend_path :
- draw_csp([s1],color="Red",width=.1)
- draw_pointer( csp_at_t(s2[-2],s2[-1],1.)+
- (P(csp_at_t(s2[-2],s2[-1],1.))+ P(csp_normalized_normal(s2[-2],s2[-1],1.))*10).to_list(),"Green", "line" )
- draw_pointer( csp_at_t(s1[0],s1[1],0.)+
- (P(csp_at_t(s1[0],s1[1],0.))+ P(csp_normalized_slope(s1[0],s1[1],0.))*10).to_list(),"Red", "line" )
- # Now join all together and check closure and orientation of result
- joined_result = csp_join_subpaths(result)
- # Check if each subpath from joined_result is closed
- #draw_csp(joined_result,color="Green",width=1)
- for s in joined_result[:] :
- if csp_subpaths_end_to_start_distance2(s,s) > 0.001 :
- # Remove open parts
- if options.offset_draw_clippend_path:
- draw_csp([s],color="Orange",width=1)
- draw_pointer(s[0][1], comment= csp_subpaths_end_to_start_distance2(s,s))
- draw_pointer(s[-1][1], comment = csp_subpaths_end_to_start_distance2(s,s))
- joined_result.remove(s)
- else :
- # Remove small parts
- minx,miny,maxx,maxy = csp_true_bounds([s])
- if (minx[0]-maxx[0])**2 + (miny[1]-maxy[1])**2 < 0.1 :
- joined_result.remove(s)
- print_("Clipped and joined path in %s"%(time.time()-time_))
- time_ = time.time()
- ########################################################################
- # Now to the Dummy cliping: remove parts from splitted offset if their
- # centers are closer to the original path than offset radius.
- ########################################################################
- r1,r2 = ( (0.99*r)**2, (1.01*r)**2 ) if abs(r*.01)<1 else ((abs(r)-1)**2, (abs(r)+1)**2)
- for s in joined_result[:]:
- dist = csp_to_point_distance(original_csp, s[int(len(s)/2)][1], dist_bounds = [r1,r2], tolerance = .000001)
- if not r1 < dist[0] < r2 :
- joined_result.remove(s)
- if options.offset_draw_clippend_path:
- draw_csp([s], comment = math.sqrt(dist[0]))
- 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] )
- print_("-----------------------------")
- print_("Total offset time %s"%(time.time()-time_start))
- print_()
- return joined_result
- ################################################################################
- ###
- ### Biarc function
- ###
- ### Calculates biarc approximation of cubic super path segment
- ### splits segment if needed or approximates it with straight line
- ###
- ################################################################################
- def biarc(sp1, sp2, z1, z2, depth=0):
- def biarc_split(sp1,sp2, z1, z2, depth):
- if depth<options.biarc_max_split_depth:
- sp1,sp2,sp3 = csp_split(sp1,sp2)
- l1, l2 = cspseglength(sp1,sp2), cspseglength(sp2,sp3)
- if l1+l2 == 0 : zm = z1
- else : zm = z1+(z2-z1)*l1/(l1+l2)
- return biarc(sp1,sp2,z1,zm,depth+1)+biarc(sp2,sp3,zm,z2,depth+1)
- else: return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
- P0, P4 = P(sp1[1]), P(sp2[1])
- TS, TE, v = (P(sp1[2])-P0), -(P(sp2[0])-P4), P0 - P4
- tsa, tea, va = TS.angle(), TE.angle(), v.angle()
- if TE.mag()<straight_distance_tolerance and TS.mag()<straight_distance_tolerance:
- # Both tangents are zerro - line straight
- return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
- if TE.mag() < straight_distance_tolerance:
- TE = -(TS+v).unit()
- r = TS.mag()/v.mag()*2
- elif TS.mag() < straight_distance_tolerance:
- TS = -(TE+v).unit()
- r = 1/( TE.mag()/v.mag()*2 )
- else:
- r=TS.mag()/TE.mag()
- TS, TE = TS.unit(), TE.unit()
- tang_are_parallel = ((tsa-tea)%math.pi<straight_tolerance or math.pi-(tsa-tea)%math.pi<straight_tolerance )
- if ( tang_are_parallel and
- ((v.mag()<straight_distance_tolerance or TE.mag()<straight_distance_tolerance or TS.mag()<straight_distance_tolerance) or
- 1-abs(TS*v/(TS.mag()*v.mag()))<straight_tolerance) ):
- # Both tangents are parallel and start and end are the same - line straight
- # or one of tangents still smaller then tollerance
- # Both tangents and v are parallel - line straight
- return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
- c,b,a = v*v, 2*v*(r*TS+TE), 2*r*(TS*TE-1)
- if v.mag()==0:
- return biarc_split(sp1, sp2, z1, z2, depth)
- asmall, bsmall, csmall = abs(a)<10**-10,abs(b)<10**-10,abs(c)<10**-10
- if asmall and b!=0: beta = -c/b
- elif csmall and a!=0: beta = -b/a
- elif not asmall:
- discr = b*b-4*a*c
- if discr < 0: raise ValueError, (a,b,c,discr)
- disq = discr**.5
- beta1 = (-b - disq) / 2 / a
- beta2 = (-b + disq) / 2 / a
- if beta1*beta2 > 0 : raise ValueError, (a,b,c,disq,beta1,beta2)
- beta = max(beta1, beta2)
- elif asmall and bsmall:
- return biarc_split(sp1, sp2, z1, z2, depth)
- alpha = beta * r
- ab = alpha + beta
- P1 = P0 + alpha * TS
- P3 = P4 - beta * TE
- P2 = (beta / ab) * P1 + (alpha / ab) * P3
- def calculate_arc_params(P0,P1,P2):
- D = (P0+P2)/2
- if (D-P1).mag()==0: return None, None
- R = D - ( (D-P0).mag()**2/(D-P1).mag() )*(P1-D).unit()
- p0a, p1a, p2a = (P0-R).angle()%(2*math.pi), (P1-R).angle()%(2*math.pi), (P2-R).angle()%(2*math.pi)
- alpha = (p2a - p0a) % (2*math.pi)
- if (p0a<p2a and (p1a<p0a or p2a<p1a)) or (p2a<p1a<p0a) :
- alpha = -2*math.pi+alpha
- if abs(R.x)>1000000 or abs(R.y)>1000000 or (R-P0).mag<options.min_arc_radius**2 :
- return None, None
- else :
- return R, alpha
- R1,a1 = calculate_arc_params(P0,P1,P2)
- R2,a2 = calculate_arc_params(P2,P3,P4)
- 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]] ]
- d = csp_to_arc_distance(sp1,sp2, [P0,P2,R1,a1],[P2,P4,R2,a2])
- if d > options.biarc_tolerance and depth<options.biarc_max_split_depth : return biarc_split(sp1, sp2, z1, z2, depth)
- else:
- if R2.mag()*a2 == 0 : zm = z2
- else : zm = z1 + (z2-z1)*(abs(R1.mag()*a1))/(abs(R2.mag()*a2)+abs(R1.mag()*a1))
- l = (P0-P2).l2()
- if l < EMC_TOLERANCE_EQUAL**2 or l<EMC_TOLERANCE_EQUAL**2 * R1.l2() /100 :
- # arc should be straight otherwise it could be threated as full circle
- arc1 = [ sp1[1], 'line', 0, 0, [P2.x,P2.y], [z1,zm] ]
- else :
- arc1 = [ sp1[1], 'arc', [R1.x,R1.y], a1, [P2.x,P2.y], [z1,zm] ]
- l = (P4-P2).l2()
- if l < EMC_TOLERANCE_EQUAL**2 or l<EMC_TOLERANCE_EQUAL**2 * R2.l2() /100 :
- # arc should be straight otherwise it could be threated as full circle
- arc2 = [ [P2.x,P2.y], 'line', 0, 0, [P4.x,P4.y], [zm,z2] ]
- else :
- arc2 = [ [P2.x,P2.y], 'arc', [R2.x,R2.y], a2, [P4.x,P4.y], [zm,z2] ]
- return [ arc1, arc2 ]
- def biarc_curve_segment_length(seg):
- if seg[1] == "arc" :
- return math.sqrt((seg[0][0]-seg[2][0])**2+(seg[0][1]-seg[2][1])**2)*seg[3]
- elif seg[1] == "line" :
- return math.sqrt((seg[0][0]-seg[4][0])**2+(seg[0][1]-seg[4][1])**2)
- else:
- return 0
- def biarc_curve_clip_at_l(curve, l, clip_type = "strict") :
- # get first subcurve and ceck it's length
- subcurve, subcurve_l, moved = [], 0, False
- for seg in curve:
- if seg[1] == "move" and moved or seg[1] == "end" :
- break
- if seg[1] == "move" : moved = True
- subcurve_l += biarc_curve_segment_length(seg)
- if seg[1] == "arc" or seg[1] == "line" :
- subcurve += [seg]
- if subcurve_l < l and clip_type == "strict" : return []
- lc = 0
- if (subcurve[-1][4][0]-subcurve[0][0][0])**2 + (subcurve[-1][4][1]-subcurve[0][0][1])**2 < 10**-7 : subcurve_closed = True
- i = 0
- reverse = False
- while lc<l :
- seg = subcurve[i]
- if reverse :
- if seg[1] == "line" :
- 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?
- elif seg[1] == "arc" :
- 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?
- ls = biarc_curve_segment_length(seg)
- if ls != 0 :
- if l-lc>ls :
- res += [seg]
- else :
- if seg[1] == "arc" :
- r = math.sqrt((seg[0][0]-seg[2][0])**2+(seg[0][1]-seg[2][1])**2)
- x,y = seg[0][0]-seg[2][0], seg[0][1]-seg[2][1]
- a = seg[3]/ls*(l-lc)
- x,y = x*math.cos(a) - y*math.sin(a), x*math.sin(a) + y*math.cos(a)
- x,y = x+seg[2][0], y+seg[2][1]
- res += [[ seg[0], "arc", seg[2], a, [x,y], [seg[5][0],seg[5][1]/ls*(l-lc)] ]]
- if seg[1] == "line" :
- 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)] ]]
- i += 1
- if i >= len(subcurve) and not subcurve_closed:
- reverse = not reverse
- i = i%len(subcurve)
- return res
- class Postprocessor():
- def __init__(self, error_function_handler):
- self.error = error_function_handler
- self.functions = {
- "remap" : self.remap,
- "remapi" : self.remapi ,
- "scale" : self.scale,
- "move" : self.move,
- "flip" : self.flip_axis,
- "flip_axis" : self.flip_axis,
- "round" : self.round_coordinates,
- "parameterize" : self.parameterize,
- "regex" : self.re_sub_on_gcode_lines
- }
- def process(self,command):
- command = re.sub(r"\\\\",":#:#:slash:#:#:",command)
- command = re.sub(r"\\;",":#:#:semicolon:#:#:",command)
- command = command.split(";")
- for s in command:
- s = re.sub(":#:#:slash:#:#:","\\\\",s)
- s = re.sub(":#:#:semicolon:#:#:","\\;",s)
- s = s.strip()
- if s!="" :
- self.parse_command(s)
- def parse_command(self,command):
- r = re.match(r"([A-Za-z0-9_]+)\s*\(\s*(.*)\)",command)
- if not r:
- self.error("Parse error while postprocessing.\n(Command: '%s')"%(command), "error")
- function, parameters = r.group(1).lower(),r.group(2)
- if function in self.functions :
- print_("Postprocessor: executing function %s(%s)"%(function,parameters))
- self.functions[function](parameters)
- else :
- self.error("Unrecognized function '%s' while postprocessing.\n(Command: '%s')"%(function,command), "error")
- def re_sub_on_gcode_lines(self, parameters):
- gcode = self.gcode.split("\n")
- self.gcode = ""
- try :
- for line in gcode :
- self.gcode += eval( "re.sub(%s,line)"%parameters) +"\n"
- except Exception as ex :
- 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")
- def remapi(self,parameters):
- self.remap(parameters, case_sensitive = True)
- def remap(self,parameters, case_sensitive = False):
- # remap parameters should be like "x->y,y->x"
- parameters = parameters.replace("\,",":#:#:coma:#:#:")
- parameters = parameters.split(",")
- pattern, remap = [], []
- for s in parameters:
- s = s.replace(":#:#:coma:#:#:","\,")
- r = re.match("""\s*(\'|\")(.*)\\1\s*->\s*(\'|\")(.*)\\3\s*""",s)
- if not r :
- self.error("Bad parameters for remap.\n(Parameters: '%s')"%(parameters), "error")
- pattern +=[r.group(2)]
- remap +=[r.group(4)]
- for i in range(len(pattern)) :
- if case_sensitive :
- self.gcode = ireplace(self.gcode, pattern[i], ":#:#:remap_pattern%s:#:#:"%i )
- else :
- self.gcode = self.gcode.replace(pattern[i], ":#:#:remap_pattern%s:#:#:"%i)
- for i in range(len(remap)) :
- self.gcode = self.gcode.replace(":#:#:remap_pattern%s:#:#:"%i, remap[i])
- def transform(self, move, scale):
- axis = ["xi","yj","zk","a"]
- flip = scale[0]*scale[1]*scale[2] < 0
- gcode = ""
- warned = []
- r_scale = scale[0]
- plane = "g17"
- for s in self.gcode.split("\n"):
- # get plane selection:
- s_wo_comments = re.sub(r"\([^\)]*\)","",s)
- r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments)
- if r :
- plane = r.group(1).lower()
- if plane == "g17" : r_scale = scale[0] # plane XY -> scale x
- if plane == "g18" : r_scale = scale[0] # plane XZ -> scale x
- if plane == "g19" : r_scale = scale[1] # plane YZ -> scale y
- # Raise warning if scale factors are not the game for G02 and G03
- if plane not in warned:
- r = re.search(r"(?i)(G02|G03)", s_wo_comments)
- if r :
- 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")
- 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")
- 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")
- warned += [plane]
- # Transform
- for i in range(len(axis)) :
- if move[i] != 0 or scale[i] != 1:
- for a in axis[i] :
- r = re.search(r"(?i)("+a+r")\s*(-?)\s*(\d*\.?\d*)", s)
- if r and r.group(3)!="":
- 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)
- #scale radius R
- if r_scale != 1 :
- r = re.search(r"(?i)(r)\s*(-?\s*(\d*\.?\d*))", s)
- if r and r.group(3)!="":
- try:
- s = re.sub(r"(?i)(r)\s*(-?)\s*(\d*\.?\d*)", r"\1 %f"%( float(r.group(2)+r.group(3))*r_scale ), s)
- except:
- pass
- gcode += s + "\n"
- self.gcode = gcode
- if flip :
- self.remapi("'G02'->'G03', 'G03'->'G02'")
- def parameterize(self,parameters) :
- planes = []
- feeds = {}
- coords = []
- gcode = ""
- coords_def = {"x":"x","y":"y","z":"z","i":"x","j":"y","k":"z","a":"a"}
- for s in self.gcode.split("\n"):
- s_wo_comments = re.sub(r"\([^\)]*\)","",s)
- # get Planes
- r = re.search(r"(?i)(G17|G18|G19)", s_wo_comments)
- if r :
- plane = r.group(1).lower()
- if plane not in planes :
- planes += [plane]
- # get Feeds
- r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s_wo_comments)
- if r :
- feed = float (r.group(2)+r.group(3))
- if feed not in feeds :
- feeds[feed] = "#"+str(len(feeds)+20)
- #Coordinates
- for c in "xyzijka" :
- r = re.search(r"(?i)("+c+r")\s*(-?)\s*(\d*\.?\d*)", s_wo_comments)
- if r :
- c = coords_def[r.group(1).lower()]
- if c not in coords :
- coords += [c]
- # Add offset parametrization
- offset = {"x":"#6","y":"#7","z":"#8","a":"#9"}
- for c in coords:
- gcode += "%s = 0 (%s axis offset)\n" % (offset[c],c.upper())
- # Add scale parametrization
- if planes == [] : planes = ["g17"]
- if len(planes)>1 : # have G02 and G03 in several planes scale_x = scale_y = scale_z required
- gcode += "#10 = 1 (Scale factor)\n"
- scale = {"x":"#10","i":"#10","y":"#10","j":"#10","z":"#10","k":"#10","r":"#10"}
- else :
- gcode += "#10 = 1 (%s Scale factor)\n" % ({"g17":"XY","g18":"XZ","g19":"YZ"}[planes[0]])
- gcode += "#11 = 1 (%s Scale factor)\n" % ({"g17":"Z","g18":"Y","g19":"X"}[planes[0]])
- scale = {"x":"#10","i":"#10","y":"#10","j":"#10","z":"#10","k":"#10","r":"#10"}
- if "g17" in planes :
- scale["z"] = "#11"
- scale["k"] = "#11"
- if "g18" in planes :
- scale["y"] = "#11"
- scale["j"] = "#11"
- if "g19" in planes :
- scale["x"] = "#11"
- scale["i"] = "#11"
- # Add a scale
- if "a" in coords:
- gcode += "#12 = 1 (A axis scale)\n"
- scale["a"] = "#12"
- # Add feed parametrization
- for f in feeds :
- gcode += "%s = %f (Feed definition)\n" % (feeds[f],f)
- # Parameterize Gcode
- for s in self.gcode.split("\n"):
- #feed replace :
- r = re.search(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", s)
- if r and len(r.group(3))>0:
- s = re.sub(r"(?i)(F)\s*(-?)\s*(\d*\.?\d*)", "F [%s]"%feeds[float(r.group(2)+r.group(3))], s)
- #Coords XYZA replace
- for c in "xyza" :
- r = re.search(r"(?i)(("+c+r")\s*(-?)\s*(\d*\.?\d*))", s)
- if r and len(r.group(4))>0:
- s = re.sub(r"(?i)("+c+r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*%s+%s]"%(scale[c],offset[c]), s)
- #Coords IJKR replace
- for c in "ijkr" :
- r = re.search(r"(?i)(("+c+r")\s*(-?)\s*(\d*\.?\d*))", s)
- if r and len(r.group(4))>0:
- s = re.sub(r"(?i)("+c+r")\s*((-?)\s*(\d*\.?\d*))", r"\1[\2*%s]"%scale[c], s)
- gcode += s + "\n"
- self.gcode = gcode
- #post processing processor code where a space appears somehow
- def round_coordinates(self,parameters) :
- try:
- round_ = int(parameters)
- except :
- self.error("Bad parameters for round. Round should be an integer! \n(Parameters: '%s')"%(parameters), "error")
- gcode = ""
- for s in self.gcode.split("\n"):
- for a in "xyzijkaf" :
- r = re.search(r"(?i)("+a+r")\s*(-?\s*(\d*\.?\d*))", s)
- if r :
- if r.group(2)!="":
- s = re.sub(
- r"(?i)("+a+r")\s*(-?)\s*(\d*\.?\d*)",
- (r"\1 %0."+str(round_)+"f" if round_>0 else r"\1 %d")%round(float(r.group(2)),round_),
- s)
- gcode += s + "\n"
- self.gcode = gcode
- def scale(self, parameters):
- parameters = parameters.split(",")
- scale = [1.,1.,1.,1.]
- try :
- for i in range(len(parameters)) :
- if float(parameters[i])==0 :
- self.error("Bad parameters for scale. Scale should not be 0 at any axis! \n(Parameters: '%s')"%(parameters), "error")
- scale[i] = float(parameters[i])
- except :
- self.error("Bad parameters for scale.\n(Parameters: '%s')"%(parameters), "error")
- self.transform([0,0,0,0],scale)
- def move(self, parameters):
- parameters = parameters.split(",")
- move = [0.,0.,0.,0.]
- try :
- for i in range(len(parameters)) :
- move[i] = float(parameters[i])
- except :
- self.error("Bad parameters for move.\n(Parameters: '%s')"%(parameters), "error")
- self.transform(move,[1.,1.,1.,1.])
- def flip_axis(self, parameters):
- parameters = parameters.lower()
- axis = {"x":1.,"y":1.,"z":1.,"a":1.}
- for p in parameters:
- if p in [","," "," ","\r","'",'"'] : continue
- if p not in ["x","y","z","a"] :
- self.error("Bad parameters for flip_axis. Parameter should be string consists of 'xyza' \n(Parameters: '%s')"%(parameters), "error")
- axis[p] = -axis[p]
- self.scale("%f,%f,%f,%f"%(axis["x"],axis["y"],axis["z"],axis["a"]))
- ################################################################################
- ### Polygon class
- ################################################################################
- class Polygon:
- def __init__(self, polygon=None):
- self.polygon = [] if polygon==None else polygon[:]
- def move(self, x, y) :
- for i in range(len(self.polygon)) :
- for j in range(len(self.polygon[i])) :
- self.polygon[i][j][0] += x
- self.polygon[i][j][1] += y
- def bounds(self) :
- minx,miny,maxx,maxy = 1e400, 1e400, -1e400, -1e400
- for poly in self.polygon :
- for p in poly :
- if minx > p[0] : minx = p[0]
- if miny > p[1] : miny = p[1]
- if maxx < p[0] : maxx = p[0]
- if maxy < p[1] : maxy = p[1]
- return minx*1,miny*1,maxx*1,maxy*1
- def width(self):
- b = self.bounds()
- return b[2]-b[0]
- def rotate_(self,sin,cos) :
- self.polygon = [
- [
- [point[0]*cos - point[1]*sin,point[0]*sin + point[1]*cos] for point in subpoly
- ]
- for subpoly in self.polygon
- ]
- def rotate(self, a):
- cos, sin = math.cos(a), math.sin(a)
- self.rotate_(sin,cos)
- def drop_into_direction(self, direction, surface) :
- # Polygon is a list of simple polygons
- # Surface is a polygon + line y = 0
- # Direction is [dx,dy]
- if len(self.polygon) == 0 or len(self.polygon[0])==0 : return
- if direction[0]**2 + direction[1]**2 <1e-10 : return
- direction = normalize(direction)
- sin,cos = direction[0], -direction[1]
- self.rotate_(-sin,cos)
- surface.rotate_(-sin,cos)
- self.drop_down(surface, zerro_plane = False)
- self.rotate_(sin,cos)
- surface.rotate_(sin,cos)
- def centroid(self):
- centroids = []
- sa = 0
- for poly in self.polygon:
- cx,cy,a = 0,0,0
- for i in range(len(poly)):
- [x1,y1],[x2,y2] = poly[i-1],poly[i]
- cx += (x1+x2)*(x1*y2-x2*y1)
- cy += (y1+y2)*(x1*y2-x2*y1)
- a += (x1*y2-x2*y1)
- a *= 3.
- if abs(a)>0 :
- cx /= a
- cy /= a
- sa += abs(a)
- centroids += [ [cx,cy,a] ]
- if sa == 0 : return [0.,0.]
- cx,cy = 0.,0.
- for c in centroids :
- cx += c[0]*c[2]
- cy += c[1]*c[2]
- cx /= sa
- cy /= sa
- return [cx,cy]
- def drop_down(self, surface, zerro_plane = True) :
- # Polygon is a list of simple polygons
- # Surface is a polygon + line y = 0
- # Down means min y (0,-1)
- if len(self.polygon) == 0 or len(self.polygon[0])==0 : return
- # Get surface top point
- top = surface.bounds()[3]
- if zerro_plane : top = max(0, top)
- # Get polygon bottom point
- bottom = self.bounds()[1]
- self.move(0, top - bottom + 10)
- # Now get shortest distance from surface to polygon in positive x=0 direction
- # Such distance = min(distance(vertex, edge)...) where edge from surface and
- # vertex from polygon and vice versa...
- dist = 1e300
- for poly in surface.polygon :
- for i in range(len(poly)) :
- for poly1 in self.polygon :
- for i1 in range(len(poly1)) :
- st,end = poly[i-1], poly[i]
- vertex = poly1[i1]
- if st[0]<=vertex[0]<= end[0] or end[0]<=vertex[0]<=st[0] :
- if st[0]==end[0] : d = min(vertex[1]-st[1],vertex[1]-end[1])
- else : d = vertex[1] - st[1] - (end[1]-st[1])*(vertex[0]-st[0])/(end[0]-st[0])
- if dist > d : dist = d
- # and vice versa just change the sign because vertex now under the edge
- st,end = poly1[i1-1], poly1[i1]
- vertex = poly[i]
- if st[0]<=vertex[0]<=end[0] or end[0]<=vertex[0]<=st[0] :
- if st[0]==end[0] : d = min(- vertex[1]+st[1],-vertex[1]+end[1])
- else : d = - vertex[1] + st[1] + (end[1]-st[1])*(vertex[0]-st[0])/(end[0]-st[0])
- if dist > d : dist = d
- if zerro_plane and dist > 10 + top : dist = 10 + top
- #print_(dist, top, bottom)
- #self.draw()
- self.move(0, -dist)
- def draw(self,color="#075",width=.1, group = None) :
- csp = [csp_subpath_line_to([],poly+[poly[0]]) for poly in self.polygon]
- draw_csp( csp, color=color,width=width, group = group)
- def add(self, add) :
- if type(add) == type([]) :
- self.polygon += add[:]
- else :
- self.polygon += add.polygon[:]
- def point_inside(self,p) :
- inside = False
- for poly in self.polygon :
- for i in range(len(poly)):
- st,end = poly[i-1], poly[i]
- if p==st or p==end : return True # point is a vertex = point is on the edge
- if st[0]>end[0] : st, end = end, st # This will be needed to check that edge if open only at rigth end
- c = (p[1]-st[1])*(end[0]-st[0])-(end[1]-st[1])*(p[0]-st[0])
- #print_(c)
- if st[0]<=p[0]<end[0] :
- if c<0 :
- inside = not inside
- elif c == 0 : return True # point is on the edge
- 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
- return True
- return inside
- def hull(self) :
- # Add vertices at all self intersection points.
- hull = []
- for i1 in range(len(self.polygon)):
- poly1 = self.polygon[i1]
- poly_ = []
- for j1 in range(len(poly1)):
- s, e = poly1[j1-1],poly1[j1]
- poly_ += [s]
- # Check self intersections
- for j2 in range(j1+1,len(poly1)):
- s1, e1 = poly1[j2-1],poly1[j2]
- int_ = line_line_intersection_points(s,e,s1,e1)
- for p in int_ :
- if point_to_point_d2(p,s)>0.000001 and point_to_point_d2(p,e)>0.000001 :
- poly_ += [p]
- # Check self intersections with other polys
- for i2 in range(len(self.polygon)):
- if i1==i2 : continue
- poly2 = self.polygon[i2]
- for j2 in range(len(poly2)):
- s1, e1 = poly2[j2-1],poly2[j2]
- int_ = line_line_intersection_points(s,e,s1,e1)
- for p in int_ :
- if point_to_point_d2(p,s)>0.000001 and point_to_point_d2(p,e)>0.000001 :
- poly_ += [p]
- hull += [poly_]
- # Create the dictionary containing all edges in both directions
- edges = {}
- for poly in self.polygon :
- for i in range(len(poly)):
- s,e = tuple(poly[i-1]), tuple(poly[i])
- if (point_to_point_d2(e,s)<0.000001) : continue
- break_s, break_e = False, False
- for p in edges :
- if point_to_point_d2(p,s)<0.000001 :
- break_s = True
- s = p
- if point_to_point_d2(p,e)<0.000001 :
- break_e = True
- e = p
- if break_s and break_e : break
- l = point_to_point_d(s,e)
- if not break_s and not break_e :
- edges[s] = [ [s,e,l] ]
- edges[e] = [ [e,s,l] ]
- #draw_pointer(s+e,"red","line")
- #draw_pointer(s+e,"red","line")
- else :
- if e in edges :
- for edge in edges[e] :
- if point_to_point_d2(edge[1],s)<0.000001 :
- break
- if point_to_point_d2(edge[1],s)>0.000001 :
- edges[e] += [ [e,s,l] ]
- #draw_pointer(s+e,"red","line")
- else :
- edges[e] = [ [e,s,l] ]
- #draw_pointer(s+e,"green","line")
- if s in edges :
- for edge in edges[s] :
- if point_to_point_d2(edge[1],e)<0.000001 :
- break
- if point_to_point_d2(edge[1],e)>0.000001 :
- edges[s] += [ [s,e, l] ]
- #draw_pointer(s+e,"red","line")
- else :
- edges[s] = [ [s,e,l] ]
- #draw_pointer(s+e,"green","line")
- def angle_quadrant(sin,cos):
- # 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
- if sin>0 and cos>=0 : return 1
- if sin>=0 and cos<0 : return 2
- if sin<0 and cos<=0 : return 3
- if sin<=0 and cos>0 : return 4
- def angle_is_less(sin,cos,sin1,cos1):
- # 0 = 2*pi is the largest angle
- if [sin1, cos1] == [0,1] : return True
- if [sin, cos] == [0,1] : return False
- if angle_quadrant(sin,cos)>angle_quadrant(sin1,cos1) :
- return False
- if angle_quadrant(sin,cos)<angle_quadrant(sin1,cos1) :
- return True
- if sin>=0 and cos>0 : return sin<sin1
- if sin>0 and cos<=0 : return sin>sin1
- if sin<=0 and cos<0 : return sin>sin1
- if sin<0 and cos>=0 : return sin<sin1
- def get_closes_edge_by_angle(edges, last):
- # Last edge is normalized vector of the last edge.
- min_angle = [0,1]
- next = last
- last_edge = [(last[0][0]-last[1][0])/last[2], (last[0][1]-last[1][1])/last[2]]
- for p in edges:
- #draw_pointer(list(p[0])+[p[0][0]+last_edge[0]*40,p[0][1]+last_edge[1]*40], "Red", "line", width=1)
- #print_("len(edges)=",len(edges))
- cur = [(p[1][0]-p[0][0])/p[2],(p[1][1]-p[0][1])/p[2]]
- cos, sin = dot(cur,last_edge), cross(cur,last_edge)
- #draw_pointer(list(p[0])+[p[0][0]+cur[0]*40,p[0][1]+cur[1]*40], "Orange", "line", width=1, comment = [sin,cos])
- #print_("cos, sin=",cos,sin)
- #print_("min_angle_before=",min_angle)
- if angle_is_less(sin,cos,min_angle[0],min_angle[1]) :
- min_angle = [sin,cos]
- next = p
- #print_("min_angle=",min_angle)
- return next
- # Join edges together into new polygon cutting the vertexes inside new polygon
- self.polygon = []
- len_edges = sum([len(edges[p]) for p in edges])
- loops = 0
- while len(edges)>0 :
- poly = []
- if loops > len_edges : raise ValueError, "Hull error"
- loops+=1
- # Find left most vertex.
- start = (1e100,1)
- for edge in edges :
- start = min(start, min(edges[edge]))
- last = [(start[0][0]-1,start[0][1]),start[0],1]
- first_run = True
- loops1 = 0
- while (last[1]!=start[0] or first_run) :
- first_run = False
- if loops1 > len_edges : raise ValueError, "Hull error"
- loops1 += 1
- next = get_closes_edge_by_angle(edges[last[1]],last)
- #draw_pointer(next[0]+next[1],"Green","line", comment=i, width= 1)
- #print_(next[0],"-",next[1])
- last = next
- poly += [ list(last[0]) ]
- self.polygon += [ poly ]
- # Remove all edges that are intersects new poly (any vertex inside new poly)
- poly_ = Polygon([poly])
- for p in edges.keys()[:] :
- if poly_.point_inside(list(p)) : del edges[p]
- self.draw(color="Green", width=1)
- class Arangement_Genetic:
- # gene = [fittness, order, rotation, xposition]
- # spieces = [gene]*shapes count
- # population = [spieces]
- def __init__(self, polygons, material_width):
- self.population = []
- self.genes_count = len(polygons)
- self.polygons = polygons
- self.width = material_width
- self.mutation_factor = 0.1
- self.order_mutate_factor = 1.
- self.move_mutate_factor = 1.
- def add_random_species(self,count):
- for i in range(count):
- specimen = []
- order = range(self.genes_count)
- random.shuffle(order)
- for j in order:
- specimen += [ [j, random.random(), random.random()] ]
- self.population += [ [None,specimen] ]
- def species_distance2(self,sp1,sp2) :
- # retun distance, each component is normalized
- s = 0
- for j in range(self.genes_count) :
- 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
- return s
- def similarity(self,sp1,top) :
- # Define similarity as a simple distance between two points in len(gene)*len(spiece) -th dimentions
- # for sp2 in top_spieces sum(|sp1-sp2|)/top_count
- sim = 0
- for sp2 in top :
- sim += math.sqrt(species_distance2(sp1,sp2[1]))
- return sim/len(top)
- def leave_top_species(self,count):
- self.population.sort()
- res = [ copy.deepcopy(self.population[0]) ]
- del self.population[0]
- for i in range(count-1) :
- t = []
- for j in range(20) :
- i1 = random.randint(0,len(self.population)-1)
- t += [ [self.population[i1][0],i1] ]
- t.sort()
- res += [ copy.deepcopy(self.population[t[0][1]]) ]
- del self.population[t[0][1]]
- self.population = res
- #del self.population[0]
- #for c in range(count-1) :
- # rank = []
- # for i in range(len(self.population)) :
- # sim = self.similarity(self.population[i][1],res)
- # rank += [ [self.population[i][0] / sim if sim>0 else 1e100,i] ]
- # rank.sort()
- # res += [ copy.deepcopy(self.population[rank[0][1]]) ]
- # print_(rank[0],self.population[rank[0][1]][0])
- # print_(res[-1])
- # del self.population[rank[0][1]]
- self.population = res
- def populate_species(self,count, parent_count):
- self.population.sort()
- self.inc = 0
- for c in range(count):
- parent1 = random.randint(0,parent_count-1)
- parent2 = random.randint(0,parent_count-1)
- if parent1==parent2 : parent2 = (parent2+1) % parent_count
- parent1, parent2 = self.population[parent1][1], self.population[parent2][1]
- i1,i2 = 0, 0
- genes_order = []
- specimen = [ [0,0.,0.] for i in range(self.genes_count) ]
- self.incest_mutation_multiplyer = 1.
- self.incest_mutation_count_multiplyer = 1.
- if self.species_distance2(parent1, parent2) <= .01/self.genes_count :
- # OMG it's a incest :O!!!
- # Damn you bastards!
- self.inc +=1
- self.incest_mutation_multiplyer = 2.
- self.incest_mutation_count_multiplyer = 2.
- else :
- pass
- # if random.random()<.01 : print_(self.species_distance2(parent1, parent2))
- start_gene = random.randint(0,self.genes_count)
- end_gene = (max(1,random.randint(0,self.genes_count),int(self.genes_count/4))+start_gene) % self.genes_count
- if end_gene<start_gene :
- end_gene, start_gene = start_gene, end_gene
- parent1, parent2 = parent2, parent1
- for i in range(start_gene,end_gene) :
- #rotation_mutate_param = random.random()/100
- #xposition_mutate_param = random.random()/100
- tr = 1. #- rotation_mutate_param
- tp = 1. #- xposition_mutate_param
- specimen[i] = [parent1[i][0], parent1[i][1]*tr+parent2[i][1]*(1-tr),parent1[i][2]*tp+parent2[i][2]*(1-tp)]
- genes_order += [ parent1[i][0] ]
- for i in range(0,start_gene)+range(end_gene,self.genes_count) :
- tr = 0. #rotation_mutate_param
- tp = 0. #xposition_mutate_param
- j = i
- while parent2[j][0] in genes_order :
- j = (j+1)%self.genes_count
- specimen[i] = [parent2[j][0], parent1[i][1]*tr+parent2[i][1]*(1-tr),parent1[i][2]*tp+parent2[i][2]*(1-tp)]
- genes_order += [ parent2[j][0] ]
- for i in range(random.randint(self.mutation_genes_count[0],self.mutation_genes_count[0]*self.incest_mutation_count_multiplyer )) :
- if random.random() < self.order_mutate_factor * self.incest_mutation_multiplyer :
- i1,i2 = random.randint(0,self.genes_count-1),random.randint(0,self.genes_count-1)
- specimen[i1][0], specimen[i2][0] = specimen[i2][0], specimen[i1][0]
- if random.random() < self.move_mutation_factor * self.incest_mutation_multiplyer:
- i1 = random.randint(0,self.genes_count-1)
- specimen[i1][1] = (specimen[i1][1]+random.random()*math.pi2*self.move_mutation_multiplier)%1.
- specimen[i1][2] = (specimen[i1][2]+random.random()*self.move_mutation_multiplier)%1.
- self.population += [ [None,specimen] ]
- def test_spiece_drop_down(self,spiece) :
- surface = Polygon()
- for p in spiece :
- time_ = time.time()
- poly = Polygon(copy.deepcopy(self.polygons[p[0]].polygon))
- poly.rotate(p[1]*math.pi2)
- w = poly.width()
- left = poly.bounds()[0]
- poly.move( -left + (self.width-w)*p[2],0)
- poly.drop_down(surface)
- surface.add(poly)
- return surface
- def test(self,test_function):
- time_ = time.time()
- for i in range(len(self.population)) :
- if self.population[i][0] == None :
- surface = test_function(self.population[i][1])
- b = surface.bounds()
- self.population[i][0] = (b[3]-b[1])*(b[2]-b[0])
- self.population.sort()
- def test_spiece_centroid(self,spiece) :
- poly = Polygon( self.polygons[spiece[0][0]].polygon[:])
- poly.rotate(spiece[0][1]*math.pi2)
- surface = Polygon(poly.polygon)
- for p in spiece[1:] :
- poly = Polygon(self.polygons[p[0]].polygon[:])
- c = surface.centroid()
- surface.move(-c[0],-c[1])
- c1 = poly.centroid()
- poly.move(-c1[0],-c1[1])
- poly.rotate(p[1]*math.pi2+p[2]*math.pi2)
- surface.rotate(p[2]*math.pi2)
- poly.drop_down(surface)
- surface.add(poly)
- surface.rotate(-p[2]*math.pi2)
- return surface
- def test_inline(self) :
- ###
- ### Fast test function using weave's from scipy inline function
- ###
- try :
- converters is None
- except :
- try:
- from scipy import weave
- from scipy.weave import converters
- except:
- options.self.error("For this function Scipy is needed. See http://www.cnc-club.ru/gcodetools for details.","error")
- # Prepare vars
- poly_, subpoly_, points_ = [], [], []
- for poly in self.polygons :
- p = poly.polygon
- poly_ += [len(subpoly_), len(subpoly_)+len(p)*2]
- for subpoly in p :
- subpoly_ += [len(points_), len(points_)+len(subpoly)*2+2]
- for point in subpoly :
- points_ += point
- points_ += subpoly[0] # Close subpolygon
- test_ = []
- population_ = []
- for spiece in self.population:
- test_.append( spiece[0] if spiece[0] != None else -1)
- for sp in spiece[1]:
- population_ += sp
- lp_, ls_, l_, lt_ = len(poly_), len(subpoly_), len(points_), len(test_)
- f = open('inline_test.c', 'r')
- code = f.read()
- f.close()
- f = open('inline_test_functions.c', 'r')
- functions = f.read()
- f.close()
- stdout_ = sys.stdout
- s = ''
- sys.stdout = s
- test = weave.inline(
- code,
- ['points_','subpoly_','poly_', 'lp_', 'ls_', 'l_', 'lt_','test_', 'population_'],
- compiler='gcc',
- support_code = functions,
- )
- if s!='' : options.self.error(s,"warning")
- sys.stdout = stdout_
- for i in range(len(test_)):
- self.population[i][0] = test_[i]
- #surface.draw()
- ################################################################################
- ###
- ### Gcodetools class
- ###
- ################################################################################
- class Gcodetools(inkex.Effect):
- def export_gcode(self,gcode, no_headers = False) :
- if self.options.postprocessor != "" or self.options.postprocessor_custom != "" :
- postprocessor = Postprocessor(self.error)
- postprocessor.gcode = gcode
- if self.options.postprocessor != "" :
- postprocessor.process(self.options.postprocessor)
- if self.options.postprocessor_custom != "" :
- postprocessor.process(self.options.postprocessor_custom)
- if not no_headers :
- postprocessor.gcode = self.header + postprocessor.gcode + self.footer
- f = open(self.options.directory+self.options.file, "w")
- f.write(postprocessor.gcode)
- f.close()
- ################################################################################
- ### In/out paths:
- ### TODO move it to the bottom
- ################################################################################
- def plasma_prepare_path(self) :
- def add_arc(sp1,sp2,end = False,l=10.,r=10.) :
- if not end :
- n = csp_normalized_normal(sp1,sp2,0.)
- return csp_reverse([arc_from_s_r_n_l(sp1[1],r,n,-l)])[0]
- else:
- n = csp_normalized_normal(sp1,sp2,1.)
- return arc_from_s_r_n_l(sp2[1],r,n,l)
- def add_normal(sp1,sp2,end = False,l=10.,r=10.) :
- # r is needed only for be compatible with add_arc
- if not end :
- n = csp_normalized_normal(sp1,sp2,0.)
- p = [n[0]*l+sp1[1][0],n[1]*l+sp1[1][1]]
- return csp_subpath_line_to([], [p,sp1[1]])
- else:
- n = csp_normalized_normal(sp1,sp2,1.)
- p = [n[0]*l+sp2[1][0],n[1]*l+sp2[1][1]]
- return csp_subpath_line_to([], [sp2[1],p])
- def add_tangent(sp1,sp2,end = False,l=10.,r=10.) :
- # r is needed only for be compatible with add_arc
- if not end :
- n = csp_normalized_slope(sp1,sp2,0.)
- p = [-n[0]*l+sp1[1][0],-n[1]*l+sp1[1][1]]
- return csp_subpath_line_to([], [p,sp1[1]])
- else:
- n = csp_normalized_slope(sp1,sp2,1.)
- p = [n[0]*l+sp2[1][0],n[1]*l+sp2[1][1]]
- return csp_subpath_line_to([], [sp2[1],p])
- 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:
- 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!")
- return
- # Add in-out-reference point if there is no one yet.
- if ( (len(self.in_out_reference_points)==0 and self.options.in_out_path
- or not self.options.in_out_path and not self.options.plasma_prepare_corners )
- and not self.options.in_out_path_do_not_add_reference_point) :
- self.options.orientation_points_count = "in-out reference point"
- self.orientation()
- if self.options.in_out_path or self.options.plasma_prepare_corners:
- self.set_markers()
- add_func = {"Round":add_arc, "Perpendicular": add_normal, "Tangent": add_tangent}[self.options.in_out_path_type]
- 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 :
- self.error("In-out len is to big for in-out radius will cropp it to be r*3/2*pi!", "warning")
- if self.selected_paths == {} and self.options.auto_select_paths:
- self.selected_paths = self.paths
- self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
- if self.selected_paths == {}:
- self.error(_("Nothing is selected. Please select something."),"warning")
- a = self.options.plasma_prepare_corners_tolerance
- corner_tolerance = cross([1.,0.], [math.cos(a),math.sin(a)])
- for layer in self.layers :
- if layer in self.selected_paths :
- max_dist = self.transform_scalar(self.options.in_out_path_point_max_dist, layer, reverse=True)
- l = self.transform_scalar(self.options.in_out_path_len, layer, reverse=True)
- plasma_l = self.transform_scalar(self.options.plasma_prepare_corners_distance, layer, reverse=True)
- r = self.transform_scalar(self.options.in_out_path_radius, layer, reverse=True)
- l = min(l,r*3/2*math.pi)
- for path in self.selected_paths[layer]:
- csp = self.apply_transforms( path, cubicsuperpath.parsePath(path.get("d")) )
- csp = csp_remove_zerro_segments(csp)
- res = []
- for subpath in csp :
- # Find closes point to in-out reference point
- # If subpath is open skip this step
- if self.options.in_out_path :
- # split and reverse path for further add in-out points
- if point_to_point_d2(subpath[0][1], subpath[-1][1]) < 1.e-10 :
- d = [1e100,1,1,1.]
- for p in self.in_out_reference_points :
- d1 = csp_to_point_distance([subpath], p, dist_bounds = [0,max_dist], tolerance=.01)
- if d1[0] < d[0] :
- d = d1[:]
- p_ = p
- if d[0] < max_dist**2 :
- # Lets find is there any angles near this point to put in-out path in
- # the angle if it's possible
- # remove last node to make iterations easier
- subpath[0][0] = subpath[-1][0]
- del subpath[-1]
- max_cross = [-1e100, None]
- for j in range(len(subpath)) :
- sp1,sp2,sp3 = subpath[j-2],subpath[j-1],subpath[j]
- if point_to_point_d2(sp2[1],p_)<max_dist**2:
- s1,s2 = csp_normalized_slope(sp1,sp2,1.), csp_normalized_slope(sp2,sp3,0.)
- max_cross = max(max_cross,[cross(s1,s2),j-1])
- # return back last point
- subpath.append(subpath[0])
- if max_cross[1] !=None and max_cross[0]>corner_tolerance :
- # there's an angle near the point
- j = max_cross[1]
- if j<0 : j -= 1
- if j!=0 :
- subpath = csp_concat_subpaths(subpath[j:],subpath[:j+1])
- else :
- # have to cut path's segment
- d,i,j,t = d
- sp1,sp2,sp3 = csp_split(subpath[j-1],subpath[j],t)
- subpath = csp_concat_subpaths([sp2,sp3], subpath[j:], subpath[:j], [sp1,sp2])
- if self.options.plasma_prepare_corners :
- # prepare corners
- # find corners and add some nodes
- # corner at path's start/end is ignored
- res_ = [subpath[0]]
- for sp2, sp3 in zip(subpath[1:],subpath[2:]) :
- sp1 = res_[-1]
- s1,s2 = csp_normalized_slope(sp1,sp2,1.), csp_normalized_slope(sp2,sp3,0.)
- if cross(s1,s2) > corner_tolerance :
- # got a corner to process
- S1,S2 = P(s1),P(s2)
- N = (S1-S2).unit()*plasma_l
- SP2= P(sp2[1])
- P1 = (SP2 + N)
- res_ += [
- [sp2[0],sp2[1], (SP2+S1*plasma_l).to_list() ],
- [ (P1-N.ccw()/2 ).to_list(), P1.to_list(), (P1+N.ccw()/2).to_list()],
- [(SP2-S2*plasma_l).to_list(), sp2[1],sp2[2]]
- ]
- else:
- res_ += [sp2]
- res_ += [sp3]
- subpath = res_
- if self.options.in_out_path :
- # finally add let's add in-out paths...
- subpath = csp_concat_subpaths(
- add_func(subpath[0],subpath[1],False,l,r),
- subpath,
- add_func(subpath[-2],subpath[-1],True,l,r)
- )
- res += [ subpath ]
- if self.options.in_out_path_replace_original_path :
- path.set("d", cubicsuperpath.formatPath( self.apply_transforms(path,res,True) ))
- else:
- draw_csp(res, width=1, style=styles["in_out_path_style"] )
- ################################################################################
- ### Arrangement: arranges paths by givven params
- ### TODO move it to the bottom
- ################################################################################
- def arrangement(self) :
- paths = self.selected_paths
- surface = Polygon()
- polygons = []
- time_ = time.time()
- print_("Arrangement start at %s"%(time_))
- original_paths = []
- for layer in self.layers :
- if layer in paths :
- for path in paths[layer] :
- csp = cubicsuperpath.parsePath(path.get("d"))
- polygon = Polygon()
- for subpath in csp :
- for sp1, sp2 in zip(subpath,subpath[1:]) :
- polygon.add([csp_segment_convex_hull(sp1,sp2)])
- #print_("Redused edges count from", sum([len(poly) for poly in polygon.polygon ]) )
- polygon.hull()
- original_paths += [path]
- polygons += [polygon]
- print_("Paths hull computed in %s sec."%(time.time()-time_))
- 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) ) )
- time_ = time.time()
- # material_width = self.options.arrangement_material_width
- # population = Arangement_Genetic(polygons, material_width)
- # population.add_random_species(1)
- # population.test_population_centroid()
- ## return
- material_width = self.options.arrangement_material_width
- population = Arangement_Genetic(polygons, material_width)
- print_("Genetic algorithm start at %s"%(time_))
- start_time = time.time()
- time_ = time.time()
- population.add_random_species(50)
- #population.test(population.test_spiece_centroid)
- print_("Initial population done in %s"%(time.time()-time_))
- time_ = time.time()
- pop = copy.deepcopy(population)
- population_count = self.options.arrangement_population_count
- last_champ = -1
- champions_count = 0
- for i in range(population_count):
- population.leave_top_species(20)
- population.move_mutation_multiplier = random.random()/2
- population.order_mutation_factor = .2
- population.move_mutation_factor = 1.
- population.mutation_genes_count = [1,2]
- population.populate_species(250, 20)
- print_("Populate done at %s"%(time.time()-time_))
- """
- randomize = i%100 < 40
- if i%100 < 40 :
- population.add_random_species(250)
- if 40<= i%100 < 100 :
- 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))]
- population.move_mutation_multiplier = 1. if 40<=i%100<80 else .1
- population.move_mutation_factor = (-(i%100)/30+10/3) if 50<=i%100<100 else .5
- population.order_mutation_factor = 1./(i%100-79) if 80<=i%100<100 else 1.
- population.populate_species(250, 10)
- """
- if self.options.arrangement_inline_test :
- population.test_inline()
- else:
- population.test(population.test_spiece_centroid)
- print_("Test done at %s"%(time.time()-time_))
- draw_new_champ = False
- print_()
- if population.population[0][0]!= last_champ :
- draw_new_champ = True
- improve = last_champ-population.population[0][0]
- last_champ = population.population[0][0]*1
- print_("Cicle %s done in %s"%(i,time.time()-time_))
- time_ = time.time()
- print_("%s incests been found"%population.inc)
- print_()
- if i == 0 or i == population_count-1 or draw_new_champ :
- colors = ["blue"]
- surface = population.test_spiece_centroid(population.population[0][1])
- b = surface.bounds()
- x,y = 400* (champions_count%10), 700*int(champions_count/10)
- surface.move(x-b[0],y-b[1])
- surface.draw(width=2, color=colors[0])
- 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)
- champions_count += 1
- """
- spiece = population.population[0][1]
- poly = Polygon(copy.deepcopy(population.polygons[spiece[0][0]].polygon))
- poly.rotate(spiece[0][2]*math.pi2)
- surface = Polygon(poly.polygon)
- poly.draw(width = 2, color= "Violet")
- for p in spiece[1:] :
- poly = Polygon(copy.deepcopy(population.polygons[p[0]].polygon))
- poly.rotate(p[2]*math.pi2)
- direction = [math.cos(p[1]*math.pi2), -math.sin(p[1]*math.pi2)]
- normalize(direction)
- c = surface.centroid()
- c1 = poly.centroid()
- poly.move(c[0]-c1[0]-direction[0]*400,c[1]-c1[1]-direction[1]*400)
- c = surface.centroid()
- c1 = poly.centroid()
- poly.draw(width = 5, color= "Violet")
- draw_pointer(c+c1,"Green","line")
- direction = normalize(direction)
- sin,cos = direction[0], direction[1]
- poly.rotate_(-sin,cos)
- surface.rotate_(-sin,cos)
- # poly.draw(color = "Violet",width=4)
- surface.draw(color = "Orange",width=4)
- poly.rotate_(sin,cos)
- surface.rotate_(sin,cos)
- poly.drop_into_direction(direction,surface)
- surface.add(poly)
- """
- # Now we'll need apply transforms to original paths
- def __init__(self):
- inkex.Effect.__init__(self)
- self.OptionParser.add_option("-d", "--directory", action="store", type="string", dest="directory", default="/home/", help="Directory for gcode file")
- self.OptionParser.add_option("-f", "--filename", action="store", type="string", dest="file", default="-1.0", help="File name")
- 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")
- self.OptionParser.add_option("", "--Zscale", action="store", type="float", dest="Zscale", default="1.0", help="Scale factor Z")
- self.OptionParser.add_option("", "--Zoffset", action="store", type="float", dest="Zoffset", default="0.0", help="Offset along Z")
- self.OptionParser.add_option("-s", "--Zsafe", action="store", type="float", dest="Zsafe", default="0.5", help="Z above all obstacles")
- self.OptionParser.add_option("-z", "--Zsurface", action="store", type="float", dest="Zsurface", default="0.0", help="Z of the surface")
- self.OptionParser.add_option("-c", "--Zdepth", action="store", type="float", dest="Zdepth", default="-0.125", help="Z depth of cut")
- self.OptionParser.add_option("", "--Zstep", action="store", type="float", dest="Zstep", default="-0.125", help="Z step of cutting")
- self.OptionParser.add_option("-p", "--feed", action="store", type="float", dest="feed", default="4.0", help="Feed rate in unit/min")
- self.OptionParser.add_option("", "--biarc-tolerance", action="store", type="float", dest="biarc_tolerance", default="1", help="Tolerance used when calculating biarc interpolation.")
- 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.")
- 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.")
- 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.")
- 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.")
- self.OptionParser.add_option("", "--comment-gcode", action="store", type="string", dest="comment_gcode", default="", help="Comment Gcode")
- 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")
- self.OptionParser.add_option("", "--tool-diameter", action="store", type="float", dest="tool_diameter", default="3", help="Tool diameter used for area cutting")
- self.OptionParser.add_option("", "--max-area-curves", action="store", type="int", dest="max_area_curves", default="100", help="Maximum area curves for each area")
- 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])")
- self.OptionParser.add_option("", "--area-tool-overlap", action="store", type="float", dest="area_tool_overlap", default="-10", help="Radius for preparing curves using inkscape")
- self.OptionParser.add_option("", "--unit", action="store", type="string", dest="unit", default="G21 (All units in mm)", help="Units")
- self.OptionParser.add_option("", "--active-tab", action="store", type="string", dest="active_tab", default="", help="Defines which tab is active")
- 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")
- 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")
- 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")
- self.OptionParser.add_option("", "--area-find-artefacts-diameter",action="store", type="float", dest="area_find_artefacts_diameter", default="1", help="Artefacts seeking radius")
- 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")
- 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.")
- self.OptionParser.add_option("", "--loft-distances", action="store", type="string", dest="loft_distances", default="10", help="Distances between paths.")
- self.OptionParser.add_option("", "--loft-direction", action="store", type="string", dest="loft_direction", default="crosswise", help="Direction of loft's interpolation.")
- 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.")
- 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")
- 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")
- 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)")
- 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")
- 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")
- 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. ")
- self.OptionParser.add_option("", "--lathe-width", action="store", type="float", dest="lathe_width", default=10., help="Lathe width")
- self.OptionParser.add_option("", "--lathe-fine-cut-width", action="store", type="float", dest="lathe_fine_cut_width", default=1., help="Fine cut width")
- self.OptionParser.add_option("", "--lathe-fine-cut-count", action="store", type="int", dest="lathe_fine_cut_count", default=1., help="Fine cut count")
- 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")
- self.OptionParser.add_option("", "--lathe-x-axis-remap", action="store", type="string", dest="lathe_x_axis_remap", default="X", help="Lathe X axis remap")
- self.OptionParser.add_option("", "--lathe-z-axis-remap", action="store", type="string", dest="lathe_z_axis_remap", default="Z", help="Lathe Z axis remap")
- self.OptionParser.add_option("", "--lathe-rectangular-cutter-width",action="store", type="float", dest="lathe_rectangular_cutter_width", default="4", help="Rectangular cutter width")
- self.OptionParser.add_option("", "--create-log", action="store", type="inkbool", dest="log_create_log", default=False, help="Create log files")
- self.OptionParser.add_option("", "--log-filename", action="store", type="string", dest="log_filename", default='', help="Create log files")
- self.OptionParser.add_option("", "--orientation-points-count", action="store", type="string", dest="orientation_points_count", default="2", help="Orientation points count")
- self.OptionParser.add_option("", "--tools-library-type", action="store", type="string", dest="tools_library_type", default='default tool', help="Create tools definition")
- self.OptionParser.add_option("", "--dxfpoints-action", action="store", type="string", dest="dxfpoints_action", default='replace', help="dxfpoint sign toggle")
- 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.")
- self.OptionParser.add_option("", "--offset-radius", action="store", type="float", dest="offset_radius", default=10., help="Offset radius")
- self.OptionParser.add_option("", "--offset-step", action="store", type="float", dest="offset_step", default=10., help="Offset step")
- self.OptionParser.add_option("", "--offset-draw-clippend-path", action="store", type="inkbool", dest="offset_draw_clippend_path", default=False, help="Draw clipped path")
- 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")
- self.OptionParser.add_option("", "--arrangement-material-width", action="store", type="float", dest="arrangement_material_width", default=500, help="Materials width for arrangement")
- self.OptionParser.add_option("", "--arrangement-population-count",action="store", type="int", dest="arrangement_population_count", default=100, help="Genetic algorithm populations count")
- 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)")
- self.OptionParser.add_option("", "--postprocessor", action="store", type="string", dest="postprocessor", default='', help="Postprocessor command.")
- self.OptionParser.add_option("", "--postprocessor-custom", action="store", type="string", dest="postprocessor_custom", default='', help="Postprocessor custom command.")
- self.OptionParser.add_option("", "--graffiti-max-seg-length", action="store", type="float", dest="graffiti_max_seg_length", default=1., help="Graffiti maximum segment length.")
- self.OptionParser.add_option("", "--graffiti-min-radius", action="store", type="float", dest="graffiti_min_radius", default=10., help="Graffiti minimal connector's radius.")
- self.OptionParser.add_option("", "--graffiti-start-pos", action="store", type="string", dest="graffiti_start_pos", default="(0;0)", help="Graffiti Start position (x;y).")
- self.OptionParser.add_option("", "--graffiti-create-linearization-preview", action="store", type="inkbool", dest="graffiti_create_linearization_preview", default=True, help="Graffiti create linearization preview.")
- self.OptionParser.add_option("", "--graffiti-create-preview", action="store", type="inkbool", dest="graffiti_create_preview", default=True, help="Graffiti create preview.")
- self.OptionParser.add_option("", "--graffiti-preview-size", action="store", type="int", dest="graffiti_preview_size", default=800, help="Graffiti preview's size.")
- self.OptionParser.add_option("", "--graffiti-preview-emmit", action="store", type="int", dest="graffiti_preview_emmit", default=800, help="Preview's paint emmit (pts/s).")
- self.OptionParser.add_option("", "--in-out-path", action="store", type="inkbool", dest="in_out_path", default=True, help="Create in-out paths")
- 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")
- 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")
- self.OptionParser.add_option("", "--in-out-path-type", action="store", type="string", dest="in_out_path_type", default="Round", help="In-out path type")
- self.OptionParser.add_option("", "--in-out-path-len", action="store", type="float", dest="in_out_path_len", default=10., help="In-out path length")
- 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")
- 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")
- self.OptionParser.add_option("", "--plasma-prepare-corners", action="store", type="inkbool", dest="plasma_prepare_corners", default=True, help="Prepare corners")
- self.OptionParser.add_option("", "--plasma-prepare-corners-distance", action="store", type="float", dest="plasma_prepare_corners_distance", default=10.,help="Stepout distance for corners")
- 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)")
- self.default_tool = {
- "name": "Default tool",
- "id": "default tool",
- "diameter":10.,
- "shape": "10",
- "penetration angle":90.,
- "penetration feed":3000.,
- "depth step":1.,
- "feed":1200.,
- "in trajectotry":"",
- "out trajectotry":"",
- "gcode before path":"M280 P0 S80",
- "gcode after path":"M280 P0 S50",
- "sog":"",
- "spinlde rpm":"",
- "CW or CCW":"",
- "tool change gcode":"",
- "4th axis meaning": " ",
- "4th axis scale": 1.,
- "4th axis offset": 0.,
- "passing feed":"4800",
- "fine feed":"1200",
- }
- self.tools_field_order = [
- 'name',
- 'id',
- 'diameter',
- 'feed',
- 'shape',
- 'penetration angle',
- 'penetration feed',
- "passing feed",
- 'depth step',
- "in trajectotry",
- "out trajectotry",
- "gcode before path",
- "gcode after path",
- "sog",
- "spinlde rpm",
- "CW or CCW",
- "tool change gcode",
- ]
- def parse_curve(self, p, layer, w = None, f = None):
- c = []
- if len(p)==0 :
- return []
- p = self.transform_csp(p, layer)
- ### Sort to reduce Rapid distance
- k = range(1,len(p))
- keys = [0]
- while len(k)>0:
- end = p[keys[-1]][-1][1]
- dist = None
- for i in range(len(k)):
- start = p[k[i]][0][1]
- dist = max( ( -( ( end[0]-start[0])**2+(end[1]-start[1])**2 ) ,i) , dist )
- keys += [k[dist[1]]]
- del k[dist[1]]
- for k in keys:
- subpath = p[k]
- c += [ [ [subpath[0][1][0],subpath[0][1][1]] , 'move', 0, 0] ]
- for i in range(1,len(subpath)):
- sp1 = [ [subpath[i-1][j][0], subpath[i-1][j][1]] for j in range(3)]
- sp2 = [ [subpath[i ][j][0], subpath[i ][j][1]] for j in range(3)]
- c += biarc(sp1,sp2,0,0) if w==None else biarc(sp1,sp2,-f(w[k][i-1]),-f(w[k][i]))
- # l1 = biarc(sp1,sp2,0,0) if w==None else biarc(sp1,sp2,-f(w[k][i-1]),-f(w[k][i]))
- # print_((-f(w[k][i-1]),-f(w[k][i]), [i1[5] for i1 in l1]) )
- c += [ [ [subpath[-1][1][0],subpath[-1][1][1]] ,'end',0,0] ]
- return c
- ################################################################################
- ### Draw csp
- ################################################################################
- def draw_csp(self, csp, layer=None, group=None, fill='none', stroke='#178ade', width=0.354, style=None):
- if layer!=None :
- csp = self.transform_csp(csp,layer,reverse=True)
- if group==None and layer==None:
- group = self.document.getroot()
- elif group==None and layer!=None :
- group = layer
- csp = self.apply_transforms(group,csp, reverse=True)
- if style!=None :
- return draw_csp(csp, group=group, style=style)
- else :
- return draw_csp(csp, group=group, fill=fill, stroke=stroke, width=width)
- def draw_curve(self, curve, layer, group=None, style=styles["biarc_style"]):
- self.set_markers()
- for i in [0,1]:
- style['biarc%s_r'%i] = simplestyle.parseStyle(style['biarc%s'%i])
- style['biarc%s_r'%i]["marker-start"] = "url(#DrawCurveMarker_r)"
- del(style['biarc%s_r'%i]["marker-end"])
- style['biarc%s_r'%i] = simplestyle.formatStyle(style['biarc%s_r'%i])
- if group==None:
- if "preview_groups" not in dir(self) :
- self.preview_groups = { layer: inkex.etree.SubElement( self.layers[min(1,len(self.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} ) }
- elif layer not in self.preview_groups :
- self.preview_groups[layer] = inkex.etree.SubElement( self.layers[min(1,len(self.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} )
- group = self.preview_groups[layer]
- s, arcn = '', 0
- transform = self.get_transforms(group)
- if transform != [] :
- transform = self.reverse_transform(transform)
- transform = simpletransform.formatTransform(transform)
- a,b,c = [0.,0.], [1.,0.], [0.,1.]
- k = (b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1])
- a,b,c = self.transform(a, layer, True), self.transform(b, layer, True), self.transform(c, layer, True)
- if ((b[0]-a[0])*(c[1]-a[1])-(c[0]-a[0])*(b[1]-a[1]))*k > 0 : reverse_angle = 1
- else : reverse_angle = -1
- for sk in curve:
- si = sk[:]
- 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])
- if s!='':
- if s[1] == 'line':
- attr = { 'style': style['line'],
- 'd':'M %s,%s L %s,%s' % (s[0][0], s[0][1], si[0][0], si[0][1]),
- "gcodetools": "Preview",
- }
- if transform != [] :
- attr["transform"] = transform
- inkex.etree.SubElement( group, inkex.addNS('path','svg'), attr )
- elif s[1] == 'arc':
- arcn += 1
- sp = s[0]
- c = s[2]
- s[3] = s[3]*reverse_angle
- a = ( (P(si[0])-P(c)).angle() - (P(s[0])-P(c)).angle() )%math.pi2 #s[3]
- if s[3]*a<0:
- if a>0: a = a-math.pi2
- else: a = math.pi2+a
- r = math.sqrt( (sp[0]-c[0])**2 + (sp[1]-c[1])**2 )
- a_st = ( math.atan2(sp[0]-c[0],- (sp[1]-c[1])) - math.pi/2 ) % (math.pi*2)
- st = style['biarc%s' % (arcn%2)][:]
- if a>0:
- a_end = a_st+a
- st = style['biarc%s'%(arcn%2)]
- else:
- a_end = a_st*1
- a_st = a_st+a
- st = style['biarc%s_r'%(arcn%2)]
- attr = {
- 'style': st,
- inkex.addNS('cx','sodipodi'): str(c[0]),
- inkex.addNS('cy','sodipodi'): str(c[1]),
- inkex.addNS('rx','sodipodi'): str(r),
- inkex.addNS('ry','sodipodi'): str(r),
- inkex.addNS('start','sodipodi'): str(a_st),
- inkex.addNS('end','sodipodi'): str(a_end),
- inkex.addNS('open','sodipodi'): 'true',
- inkex.addNS('type','sodipodi'): 'arc',
- "gcodetools": "Preview",
- }
- if transform != [] :
- attr["transform"] = transform
- inkex.etree.SubElement( group, inkex.addNS('path','svg'), attr)
- s = si
- def check_dir(self):
- if self.options.directory[-1] not in ["/","\\"]:
- if "\\" in self.options.directory :
- self.options.directory += "\\"
- else :
- self.options.directory += "/"
- print_("Checking directory: '%s'"%self.options.directory)
- if (os.path.isdir(self.options.directory)):
- if (os.path.isfile(self.options.directory+'header')):
- f = open(self.options.directory+'header', 'r')
- self.header = f.read()
- f.close()
- else:
- self.header = defaults['header']
- if (os.path.isfile(self.options.directory+'footer')):
- f = open(self.options.directory+'footer','r')
- self.footer = f.read()
- f.close()
- else:
- self.footer = defaults['footer']
- self.header += self.options.unit + "\n"
- else:
- self.error(_("Directory does not exist! Please specify existing directory at Preferences tab!"),"error")
- return False
- if self.options.add_numeric_suffix_to_filename :
- dir_list = os.listdir(self.options.directory)
- if "." in self.options.file :
- r = re.match(r"^(.*)(\..*)$",self.options.file)
- ext = r.group(2)
- name = r.group(1)
- else:
- ext = ""
- name = self.options.file
- max_n = 0
- for s in dir_list :
- r = re.match(r"^%s_0*(\d+)%s$"%(re.escape(name),re.escape(ext) ), s)
- if r :
- max_n = max(max_n,int(r.group(1)))
- filename = name + "_" + ( "0"*(4-len(str(max_n+1))) + str(max_n+1) ) + ext
- self.options.file = filename
- if self.options.directory[-1] not in ["/","\\"]:
- if "\\" in self.options.directory :
- self.options.directory += "\\"
- else :
- self.options.directory += "/"
- try:
- f = open(self.options.directory+self.options.file, "w")
- f.close()
- except:
- self.error(_("Can not write to specified file!\n%s"%(self.options.directory+self.options.file)),"error")
- return False
- return True
- ################################################################################
- ###
- ### Generate Gcode
- ### Generates Gcode on given curve.
- ###
- ### Curve definition [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]]
- ###
- ################################################################################
- def generate_gcode(self, curve, layer, depth):
- Zauto_scale = self.Zauto_scale[layer]
- tool = self.tools[layer][0]
- g = ""
- def c(c):
- c = [c[i] if i<len(c) else None for i in range(6)]
- if c[5] == 0 : c[5]=None
- s,s1 = [" X", " Y", " Z", " I", " J", " K"], ["","","","","",""]
- m,a = [1,1,self.options.Zscale*Zauto_scale,1,1,self.options.Zscale*Zauto_scale], [0,0,self.options.Zoffset,0,0,0]
- r = ''
- for i in range(6):
- if c[i]!=None:
- r += s[i] + ("%f" % (c[i]*m[i]+a[i])) + s1[i]
- return r
- def calculate_angle(a, current_a):
- return min(
- [abs(a-current_a%math.pi2+math.pi2), a+current_a-current_a%math.pi2+math.pi2],
- [abs(a-current_a%math.pi2-math.pi2), a+current_a-current_a%math.pi2-math.pi2],
- [abs(a-current_a%math.pi2), a+current_a-current_a%math.pi2])[1]
- if len(curve)==0 : return ""
- try :
- self.last_used_tool == None
- except :
- self.last_used_tool = None
- print_("working on curve")
- print_(curve)
- if tool != self.last_used_tool :
- g += ( ";(Change tool to %s)\n" % re.sub("\"'\(\)\\\\"," ",tool["name"]) ) + tool["tool change gcode"] + "\n"
- lg, zs, f = 'G00', self.options.Zsafe, " F%f"%tool['feed']
- current_a = 0
- go_to_safe_distance = "G00" + c([None,None,zs]) + "\n"
- penetration_feed = " F%s"%tool['penetration feed']
- for i in range(1,len(curve)):
- # Creating Gcode for curve between s=curve[i-1] and si=curve[i] start at s[0] end at s[4]=si[0]
- s, si = curve[i-1], curve[i]
- feed = f if lg not in ['G01','G02','G03'] else ''
- if s[1] == 'move':
- g += go_to_safe_distance + "G00" + c(si[0]) + "\nM400\n" + tool['gcode before path'] + "\nM400\n"
- lg = 'G00'
- elif s[1] == 'end':
- g += go_to_safe_distance + "\nM400\n" + tool['gcode after path'] + "\nM400\n"
- lg = 'G00'
- elif s[1] == 'line':
- if tool['4th axis meaning'] == "tangent knife" :
- a = atan2(si[0][0]-s[0][0],si[0][1]-s[0][1])
- a = calculate_angle(a, current_a)
- g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
- current_a = a
- if lg=="G00": g += "G01" + c([None,None,s[5][0]+depth]) + penetration_feed +";(Penetrate)\n"
- g += "G01" +c(si[0]+[s[5][1]+depth]) + feed + "\n"
- lg = 'G01'
- elif s[1] == 'arc':
- r = [(s[2][0]-s[0][0]), (s[2][1]-s[0][1])]
- if tool['4th axis meaning'] == "tangent knife" :
- if s[3]<0 : # CW
- a1 = atan2(s[2][1]-s[0][1],-s[2][0]+s[0][0]) + math.pi
- else: #CCW
- a1 = atan2(-s[2][1]+s[0][1],s[2][0]-s[0][0]) + math.pi
- a = calculate_angle(a1, current_a)
- g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
- current_a = a
- axis4 = " A%s"%((current_a+s[3])*tool['4th axis scale']+tool['4th axis offset'])
- current_a = current_a+s[3]
- else : axis4 = ""
- if lg=="G00": g += "G01" + c([None,None,s[5][0]+depth]) + penetration_feed + ";(Penetrate)\n"
- if (r[0]**2 + r[1]**2)>self.options.min_arc_radius**2:
- r1, r2 = (P(s[0])-P(s[2])), (P(si[0])-P(s[2]))
- if abs(r1.mag()-r2.mag()) < 0.001 :
- 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"
- else:
- r = (r1.mag()+r2.mag())/2
- g += ("G02" if s[3]<0 else "G03") + c(si[0]+[s[5][1]+depth]) + " R%f" % (r) + feed + axis4 + "\n"
- lg = 'G02'
- else:
- if tool['4th axis meaning'] == "tangent knife" :
- a = atan2(si[0][0]-s[0][0],si[0][1]-s[0][1]) + math.pi
- a = calculate_angle(a, current_a)
- g+="G01 A%s\n" % (a*tool['4th axis scale']+tool['4th axis offset'])
- current_a = a
- g += "G01" +c(si[0]+[s[5][1]+depth]) + feed + "\n"
- lg = 'G01'
- if si[1] == 'end':
- g += go_to_safe_distance + "\nM400\n" + tool['gcode after path'] + "\nM400\n"
- return g
- def get_transforms(self,g):
- root = self.document.getroot()
- trans = []
- while (g!=root):
- if 'transform' in g.keys():
- t = g.get('transform')
- t = simpletransform.parseTransform(t)
- trans = simpletransform.composeTransform(t,trans) if trans != [] else t
- print_(trans)
- g=g.getparent()
- return trans
- def reverse_transform(self,transform):
- trans = numpy.array(transform + [[0,0,1]])
- if numpy.linalg.det(trans)!=0 :
- trans = numpy.linalg.inv(trans).tolist()[:2]
- return trans
- else :
- return transform
- def apply_transforms(self,g,csp, reverse=False):
- trans = self.get_transforms(g)
- if trans != []:
- if not reverse :
- simpletransform.applyTransformToPath(trans, csp)
- else :
- simpletransform.applyTransformToPath(self.reverse_transform(trans), csp)
- return csp
- def transform_scalar(self,x,layer,reverse=False):
- return self.transform([x,0],layer,reverse)[0] - self.transform([0,0],layer,reverse)[0]
- def transform(self,source_point, layer, reverse=False):
- if layer not in self.transform_matrix:
- for i in range(self.layers.index(layer),-1,-1):
- if self.layers[i] in self.orientation_points :
- break
- if self.layers[i] not in self.orientation_points :
- 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")
- elif self.layers[i] in self.transform_matrix :
- self.transform_matrix[layer] = self.transform_matrix[self.layers[i]]
- self.Zcoordinates[layer] = self.Zcoordinates[self.layers[i]]
- else :
- orientation_layer = self.layers[i]
- if len(self.orientation_points[orientation_layer])>1 :
- 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")
- points = self.orientation_points[orientation_layer][0]
- if len(points)==2:
- 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]] ] ]
- if len(points)==3:
- print_("Layer '%s' Orientation points: " % orientation_layer.get(inkex.addNS('label','inkscape')))
- for point in points:
- print_(point)
- # Zcoordinates definition taken from Orientatnion point 1 and 2
- self.Zcoordinates[layer] = [max(points[0][1][2],points[1][1][2]), min(points[0][1][2],points[1][1][2])]
- matrix = numpy.array([
- [points[0][0][0], points[0][0][1], 1, 0, 0, 0, 0, 0, 0],
- [0, 0, 0, points[0][0][0], points[0][0][1], 1, 0, 0, 0],
- [0, 0, 0, 0, 0, 0, points[0][0][0], points[0][0][1], 1],
- [points[1][0][0], points[1][0][1], 1, 0, 0, 0, 0, 0, 0],
- [0, 0, 0, points[1][0][0], points[1][0][1], 1, 0, 0, 0],
- [0, 0, 0, 0, 0, 0, points[1][0][0], points[1][0][1], 1],
- [points[2][0][0], points[2][0][1], 1, 0, 0, 0, 0, 0, 0],
- [0, 0, 0, points[2][0][0], points[2][0][1], 1, 0, 0, 0],
- [0, 0, 0, 0, 0, 0, points[2][0][0], points[2][0][1], 1]
- ])
- if numpy.linalg.det(matrix)!=0 :
- m = numpy.linalg.solve(matrix,
- numpy.array(
- [[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]]
- )
- ).tolist()
- self.transform_matrix[layer] = [[m[j*3+i][0] for i in range(3)] for j in range(3)]
- else :
- 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")
- else :
- 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")
- self.transform_matrix_reverse[layer] = numpy.linalg.inv(self.transform_matrix[layer]).tolist()
- print_("\n Layer '%s' transformation matrixes:" % layer.get(inkex.addNS('label','inkscape')) )
- print_(self.transform_matrix)
- print_(self.transform_matrix_reverse)
- ###self.Zauto_scale[layer] = math.sqrt( (self.transform_matrix[layer][0][0]**2 + self.transform_matrix[layer][1][1]**2)/2 )
- ### Zautoscale is absolete
- self.Zauto_scale[layer] = 1
- print_("Z automatic scale = %s (computed according orientation points)" % self.Zauto_scale[layer])
- x,y = source_point[0], source_point[1]
- if not reverse :
- t = self.transform_matrix[layer]
- else :
- t = self.transform_matrix_reverse[layer]
- return [t[0][0]*x+t[0][1]*y+t[0][2], t[1][0]*x+t[1][1]*y+t[1][2]]
- def transform_csp(self, csp_, layer, reverse = False):
- 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_)) ]
- for i in xrange(len(csp)):
- for j in xrange(len(csp[i])):
- for k in xrange(len(csp[i][j])):
- csp[i][j][k] = self.transform(csp[i][j][k],layer, reverse)
- return csp
- ################################################################################
- ### Errors handling function, notes are just printed into Logfile,
- ### warnings are printed into log file and warning message is displayed but
- ### extension continues working, errors causes log and execution is halted
- ### Notes, warnings adn errors could be assigned to space or comma or dot
- ### sepparated strings (case is ignoreg).
- ################################################################################
- def error(self, s, type_= "Warning"):
- notes = "Note "
- warnings = """
- Warning tools_warning
- orientation_warning
- bad_orientation_points_in_some_layers
- more_than_one_orientation_point_groups
- more_than_one_tool
- orientation_have_not_been_defined
- tool_have_not_been_defined
- selection_does_not_contain_paths
- selection_does_not_contain_paths_will_take_all
- selection_is_empty_will_comupe_drawing
- selection_contains_objects_that_are_not_paths
- Continue
- """
- errors = """
- Error
- wrong_orientation_points
- area_tools_diameter_error
- no_tool_error
- active_layer_already_has_tool
- active_layer_already_has_orientation_points
- """
- s = s.encode('utf-8')
- if type_.lower() in re.split("[\s\n,\.]+", errors.lower()) :
- print_(s)
- inkex.errormsg(s+"\n")
- sys.exit()
- elif type_.lower() in re.split("[\s\n,\.]+", warnings.lower()) :
- print_(s)
- inkex.errormsg(s+"\n")
- elif type_.lower() in re.split("[\s\n,\.]+", notes.lower()) :
- print_(s)
- else :
- print_(s)
- inkex.errormsg(s)
- sys.exit()
- ################################################################################
- ### Set markers
- ################################################################################
- def set_markers(self) :
- self.get_defs()
- # Add marker to defs if it doesnot exists
- if "CheckToolsAndOPMarker" not in self.defs :
- defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
- marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"CheckToolsAndOPMarker","orient":"auto","refX":"-4","refY":"-1.687441","style":"overflow:visible"})
- inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
- { "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",
- "style": "fill:#000044; fill-rule:evenodd;9oke:none;" }
- )
- if "DrawCurveMarker" not in self.defs :
- defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
- marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"DrawCurveMarker","orient":"auto","refX":"-4","refY":"-1.687441","style":"overflow:visible"})
- inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
- { "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",
- "style": "fill:#000044; fill-rule:evenodd;stroke:none;" }
- )
- if "DrawCurveMarker_r" not in self.defs :
- defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
- marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"DrawCurveMarker_r","orient":"auto","refX":"4","refY":"-1.687441","style":"overflow:visible"})
- inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
- { "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",
- "style": "fill:#000044; fill-rule:evenodd;stroke:none;" }
- )
- if "InOutPathMarker" not in self.defs :
- defs = inkex.etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg"))
- marker = inkex.etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"InOutPathMarker","orient":"auto","refX":"-4","refY":"-1.687441","style":"overflow:visible"})
- inkex.etree.SubElement( marker, inkex.addNS("path","svg"),
- { "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",
- "style": "fill:#0072a7; fill-rule:evenodd;stroke:none;" }
- )
- ################################################################################
- ### Get defs from svg
- ################################################################################
- def get_defs(self) :
- self.defs = {}
- def recursive(g) :
- for i in g:
- if i.tag == inkex.addNS("defs","svg") :
- for j in i:
- self.defs[j.get("id")] = i
- if i.tag ==inkex.addNS("g",'svg') :
- recursive(i)
- recursive(self.document.getroot())
- ################################################################################
- ###
- ### Get Gcodetools info from the svg
- ###
- ################################################################################
- def get_info(self):
- self.selected_paths = {}
- self.paths = {}
- self.tools = {}
- self.orientation_points = {}
- self.graffiti_reference_points = {}
- self.layers = [self.document.getroot()]
- self.Zcoordinates = {}
- self.transform_matrix = {}
- self.transform_matrix_reverse = {}
- self.Zauto_scale = {}
- self.in_out_reference_points = []
- self.my3Dlayer = None
- def recursive_search(g, layer, selected=False):
- items = g.getchildren()
- items.reverse()
- for i in items:
- if selected:
- self.selected[i.get("id")] = i
- if i.tag == inkex.addNS("g",'svg') and i.get(inkex.addNS('groupmode','inkscape')) == 'layer':
- if i.get(inkex.addNS('label','inkscape')) == '3D' :
- self.my3Dlayer=i
- else :
- self.layers += [i]
- recursive_search(i,i)
- elif i.get('gcodetools') == "Gcodetools orientation group" :
- points = self.get_orientation_points(i)
- if points != None :
- self.orientation_points[layer] = self.orientation_points[layer]+[points[:]] if layer in self.orientation_points else [points[:]]
- print_("Found orientation points in '%s' layer: %s" % (layer.get(inkex.addNS('label','inkscape')), points))
- else :
- 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")
- #Need to recognise old files ver 1.6.04 and earlier
- elif i.get("gcodetools") == "Gcodetools tool definition" or i.get("gcodetools") == "Gcodetools tool defenition" :
- tool = self.get_tool(i)
- self.tools[layer] = self.tools[layer] + [tool.copy()] if layer in self.tools else [tool.copy()]
- print_("Found tool in '%s' layer: %s" % (layer.get(inkex.addNS('label','inkscape')), tool))
- elif i.get("gcodetools") == "Gcodetools graffiti reference point" :
- point = self.get_graffiti_reference_points(i)
- if point != [] :
- self.graffiti_reference_points[layer] = self.graffiti_reference_points[layer]+[point[:]] if layer in self.graffiti_reference_points else [point]
- else :
- 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")
- elif i.tag == inkex.addNS('path','svg'):
- if "gcodetools" not in i.keys() :
- self.paths[layer] = self.paths[layer] + [i] if layer in self.paths else [i]
- if i.get("id") in self.selected :
- self.selected_paths[layer] = self.selected_paths[layer] + [i] if layer in self.selected_paths else [i]
- elif i.get("gcodetools") == "In-out reference point group" :
- items_ = i.getchildren()
- items_.reverse()
- for j in items_ :
- if j.get("gcodetools") == "In-out reference point" :
- self.in_out_reference_points.append( self.apply_transforms(j,cubicsuperpath.parsePath(j.get("d")))[0][0][1] )
- elif i.tag == inkex.addNS("g",'svg'):
- recursive_search(i,layer, (i.get("id") in self.selected) )
- elif i.get("id") in self.selected :
- # xgettext:no-pango-format
- 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")
- recursive_search(self.document.getroot(),self.document.getroot())
- if len(self.layers) == 1 :
- self.error(_("Document has no layers! Add at least one layer using layers panel (Ctrl+Shift+L)"),"Error")
- root = self.document.getroot()
- if root in self.selected_paths or root in self.paths :
- 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" )
- if root in self.selected_paths :
- if self.layers[-1] in self.selected_paths :
- self.selected_paths[self.layers[-1]] += self.selected_paths[root][:]
- else :
- self.selected_paths[self.layers[-1]] = self.selected_paths[root][:]
- del self.selected_paths[root]
- if root in self.paths :
- if self.layers[-1] in self.paths :
- self.paths[self.layers[-1]] += self.paths[root][:]
- else :
- self.paths[self.layers[-1]] = self.paths[root][:]
- del self.paths[root]
- def get_orientation_points(self,g):
- items = g.getchildren()
- items.reverse()
- p2, p3 = [], []
- p = None
- for i in items:
- if i.tag == inkex.addNS("g",'svg') and i.get("gcodetools") == "Gcodetools orientation point (2 points)":
- p2 += [i]
- if i.tag == inkex.addNS("g",'svg') and i.get("gcodetools") == "Gcodetools orientation point (3 points)":
- p3 += [i]
- if len(p2)==2 : p=p2
- elif len(p3)==3 : p=p3
- if p==None : return None
- points = []
- for i in p :
- point = [[],[]]
- for node in i :
- if node.get('gcodetools') == "Gcodetools orientation point arrow":
- point[0] = self.apply_transforms(node,cubicsuperpath.parsePath(node.get("d")))[0][0][1]
- if node.get('gcodetools') == "Gcodetools orientation point text":
- 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))
- point[1] = [float(r.group(1)),float(r.group(2)),float(r.group(3))]
- if point[0]!=[] and point[1]!=[]: points += [point]
- if len(points)==len(p2)==2 or len(points)==len(p3)==3 : return points
- else : return None
- def get_graffiti_reference_points(self,g):
- point = [[], '']
- for node in g :
- if node.get('gcodetools') == "Gcodetools graffiti reference point arrow":
- point[0] = self.apply_transforms(node,cubicsuperpath.parsePath(node.get("d")))[0][0][1]
- if node.get('gcodetools') == "Gcodetools graffiti reference point text":
- point[1] = get_text(node)
- if point[0]!=[] and point[1]!='' : return point
- else : return []
- def get_tool(self, g):
- tool = self.default_tool.copy()
- tool["self_group"] = g
- for i in g:
- # Get parameters
- if i.get("gcodetools") == "Gcodetools tool background" :
- tool["style"] = simplestyle.parseStyle(i.get("style"))
- elif i.get("gcodetools") == "Gcodetools tool parameter" :
- key = None
- value = None
- for j in i:
- #need to recognise old tools from ver 1.6.04
- if j.get("gcodetools") == "Gcodetools tool definition field name" or j.get("gcodetools") == "Gcodetools tool defention field name":
- key = get_text(j)
- if j.get("gcodetools") == "Gcodetools tool definition field value" or j.get("gcodetools") == "Gcodetools tool defention field value":
- value = get_text(j)
- if value == "(None)": value = ""
- if value == None or key == None: continue
- #print_("Found tool parameter '%s':'%s'" % (key,value))
- if key in self.default_tool.keys() :
- try :
- tool[key] = type(self.default_tool[key])(value)
- except :
- tool[key] = self.default_tool[key]
- 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")
- else :
- tool[key] = value
- self.error(_("Warning! Tool has parameter that default tool has not ( '%s': '%s' ).") % (key, value), "tools_warning" )
- return tool
- def set_tool(self,layer):
- # print_(("index(layer)=",self.layers.index(layer),"set_tool():layer=",layer,"self.tools=",self.tools))
- # for l in self.layers:
- # print_(("l=",l))
- for i in range(self.layers.index(layer),-1,-1):
- # print_(("processing layer",i))
- if self.layers[i] in self.tools :
- break
- if self.layers[i] in self.tools :
- if self.layers[i] != layer : self.tools[layer] = self.tools[self.layers[i]]
- 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")
- return self.tools[layer]
- else :
- 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")
- ################################################################################
- ###
- ### Path to Gcode
- ###
- ################################################################################
- def path_to_gcode(self) :
- from functools import partial
- def get_boundaries(points):
- minx,miny,maxx,maxy=None,None,None,None
- out=[[],[],[],[]]
- for p in points:
- if minx==p[0]:
- out[0]+=[p]
- if minx==None or p[0]<minx:
- minx=p[0]
- out[0]=[p]
- if miny==p[1]:
- out[1]+=[p]
- if miny==None or p[1]<miny:
- miny=p[1]
- out[1]=[p]
- if maxx==p[0]:
- out[2]+=[p]
- if maxx==None or p[0]>maxx:
- maxx=p[0]
- out[2]=[p]
- if maxy==p[1]:
- out[3]+=[p]
- if maxy==None or p[1]>maxy:
- maxy=p[1]
- out[3]=[p]
- return out
- def remove_duplicates(points):
- i=0
- out=[]
- for p in points:
- for j in xrange(i,len(points)):
- if p==points[j]: points[j]=[None,None]
- if p!=[None,None]: out+=[p]
- i+=1
- return(out)
- def get_way_len(points):
- l=0
- for i in xrange(1,len(points)):
- l+=math.sqrt((points[i][0]-points[i-1][0])**2 + (points[i][1]-points[i-1][1])**2)
- return l
- def sort_dxfpoints(points):
- points=remove_duplicates(points)
- # print_(get_boundaries(get_boundaries(points)[2])[1])
- ways=[
- # l=0, d=1, r=2, u=3
- [3,0], # ul
- [3,2], # ur
- [1,0], # dl
- [1,2], # dr
- [0,3], # lu
- [0,1], # ld
- [2,3], # ru
- [2,1], # rd
- ]
- # print_(("points=",points))
- minimal_way=[]
- minimal_len=None
- minimal_way_type=None
- for w in ways:
- tpoints=points[:]
- cw=[]
- # print_(("tpoints=",tpoints))
- for j in xrange(0,len(points)):
- p=get_boundaries(get_boundaries(tpoints)[w[0]])[w[1]]
- # print_(p)
- tpoints.remove(p[0])
- cw+=p
- curlen = get_way_len(cw)
- if minimal_len==None or curlen < minimal_len:
- minimal_len=curlen
- minimal_way=cw
- minimal_way_type=w
- return minimal_way
- def sort_lines(lines):
- if len(lines) == 0 : return []
- lines = [ [key]+lines[key] for key in range(len(lines))]
- keys = [0]
- end_point = lines[0][3:]
- print_("!!!",lines,"\n",end_point)
- del lines[0]
- while len(lines)>0:
- dist = [ [point_to_point_d2(end_point,lines[i][1:3]),i] for i in range(len(lines))]
- i = min(dist)[1]
- keys.append(lines[i][0])
- end_point = lines[i][3:]
- del lines[i]
- return keys
- def sort_curves(curves):
- lines = []
- for curve in curves:
- lines += [curve[0][0][0] + curve[-1][-1][0]]
- return sort_lines(lines)
- def print_dxfpoints(points):
- gcode=""
- for point in points:
- 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)
- # print_(("got dxfpoints array=",points))
- return gcode
- def get_path_properties(node, recursive=True, tags={inkex.addNS('desc','svg'):"Description",inkex.addNS('title','svg'):"Title"} ) :
- res = {}
- done = False
- root = self.document.getroot()
- while not done and node != root :
- for i in node.getchildren():
- if i.tag in tags:
- res[tags[i.tag]] = i.text
- done = True
- node = node.getparent()
- return res
- if self.selected_paths == {} and self.options.auto_select_paths:
- paths=self.paths
- self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
- else :
- paths = self.selected_paths
- self.check_dir()
- gcode = ""
- 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') )
- print_(("self.layers=",self.layers))
- print_(("paths=",paths))
- colors = {}
- for layer in self.layers :
- if layer in paths :
- print_(("layer",layer))
- # transform simple path to get all var about orientation
- self.transform_csp([ [ [[0,0],[0,0],[0,0]], [[0,0],[0,0],[0,0]] ] ], layer)
- self.set_tool(layer)
- curves = []
- dxfpoints = []
- try :
- depth_func = eval('lambda c,d,s: ' + self.options.path_to_gcode_depth_function.strip('"'))
- except:
- self.error("Bad depth function! Enter correct function at Path to Gcode tab!")
- for path in paths[layer] :
- if "d" not in path.keys() :
- 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")
- continue
- csp = cubicsuperpath.parsePath(path.get("d"))
- csp = self.apply_transforms(path, csp)
- id_ = path.get("id")
- def set_comment(match, path):
- if match.group(1) in path.keys() :
- return path.get(match.group(1))
- else:
- return "None"
- if self.options.comment_gcode != "" :
- comment = re.sub("\[([A-Za-z_\-\:]+)\]", partial(set_comment, path=path), self.options.comment_gcode)
- comment = comment.replace(":newline:","\n")
- comment = gcode_comment_str(comment)
- else:
- comment = ""
- if self.options.comment_gcode_from_properties :
- tags = get_path_properties(path)
- for tag in tags :
- comment += gcode_comment_str("%s: %s"%(tag,tags[tag]))
- style = simplestyle.parseStyle(path.get("style"))
- colors[id_] = simplestyle.parseColor(style['stroke'] if "stroke" in style and style['stroke']!='none' else "#000")
- if path.get("dxfpoint") == "1":
- tmp_curve=self.transform_csp(csp, layer)
- x=tmp_curve[0][0][0][0]
- y=tmp_curve[0][0][0][1]
- print_("got dxfpoint (scaled) at (%f,%f)" % (x,y))
- dxfpoints += [[x,y]]
- else:
- zd,zs = self.Zcoordinates[layer][1], self.Zcoordinates[layer][0]
- c = 1. - float(sum(colors[id_]))/255/3
- curves += [
- [
- [id_, depth_func(c,zd,zs), comment],
- [ self.parse_curve([subpath], layer) for subpath in csp ]
- ]
- ]
- # for c in curves :
- # print_(c)
- dxfpoints=sort_dxfpoints(dxfpoints)
- gcode+=print_dxfpoints(dxfpoints)
- for curve in curves :
- for subcurve in curve[1] :
- self.draw_curve(subcurve, layer)
- if self.options.path_to_gcode_order == 'subpath by subpath':
- curves_ = []
- for curve in curves :
- curves_ += [ [curve[0],[subcurve]] for subcurve in curve[1] ]
- curves = curves_
- self.options.path_to_gcode_order = 'path by path'
- if self.options.path_to_gcode_order == 'path by path':
- if self.options.path_to_gcode_sort_paths :
- keys = sort_curves( [curve[1] for curve in curves] )
- else :
- keys = range(len(curves))
- for key in keys:
- d = curves[key][0][1]
- for step in range( 0, int(math.ceil( abs((zs-d)/self.tools[layer][0]["depth step"] )) ) ):
- z = max(d, zs - abs(self.tools[layer][0]["depth step"]*(step+1)))
- gcode += gcode_comment_str("\nStart cutting path id: %s"%curves[key][0][0])
- if curves[key][0][2] != "()" :
- gcode += curves[key][0][2] # add comment
- for curve in curves[key][1]:
- gcode += self.generate_gcode(curve, layer, z)
- gcode += gcode_comment_str("End cutting path id: %s\n\n"%curves[key][0][0])
- else: # pass by pass
- mind = min( [curve[0][1] for curve in curves] )
- for step in range( 0, int(math.ceil( abs((zs-mind)/self.tools[layer][0]["depth step"] )) ) ):
- z = zs - abs(self.tools[layer][0]["depth step"]*(step))
- curves_ = []
- for curve in curves:
- if curve[0][1]<z :
- curves_.append(curve)
- z = zs - abs(self.tools[layer][0]["depth step"]*(step+1))
- gcode += "\n(Pass at depth %s)\n"%z
- if self.options.path_to_gcode_sort_paths :
- keys = sort_curves( [curve[1] for curve in curves_] )
- else :
- keys = range(len(curves_))
- for key in keys:
- gcode += gcode_comment_str("Start cutting path id: %s"%curves[key][0][0])
- if curves[key][0][2] != "()" :
- gcode += curves[key][0][2] # add comment
- for subcurve in curves_[key][1]:
- gcode += self.generate_gcode(subcurve, layer, max(z,curves_[key][0][1]))
- gcode += gcode_comment_str("End cutting path id: %s\n\n"%curves[key][0][0])
- self.export_gcode(gcode)
- ################################################################################
- ###
- ### dxfpoints
- ###
- ################################################################################
- def dxfpoints(self):
- if self.selected_paths == {}:
- self.error(_("Nothing is selected. Please select something to convert to drill point (dxfpoint) or clear point sign."),"warning")
- for layer in self.layers :
- if layer in self.selected_paths :
- for path in self.selected_paths[layer]:
- # print_(("processing path",path.get('d')))
- if self.options.dxfpoints_action == 'replace':
- # print_("trying to set as dxfpoint")
- path.set("dxfpoint","1")
- r = re.match("^\s*.\s*(\S+)",path.get("d"))
- if r!=None:
- print_(("got path=",r.group(1)))
- 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))
- path.set("style",styles["dxf_points"])
- if self.options.dxfpoints_action == 'save':
- path.set("dxfpoint","1")
- if self.options.dxfpoints_action == 'clear' and path.get("dxfpoint") == "1":
- path.set("dxfpoint","0")
- # for id, node in self.selected.iteritems():
- # print_((id,node,node.attrib))
- ################################################################################
- ###
- ### Artefacts
- ###
- ################################################################################
- def area_artefacts(self) :
- if self.selected_paths == {} and self.options.auto_select_paths:
- paths=self.paths
- self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
- else :
- paths = self.selected_paths
- for layer in paths :
- # paths[layer].reverse() # Reverse list of paths to leave their order
- for path in paths[layer] :
- parent = path.getparent()
- style = path.get("style") if "style" in path.keys() else ""
- if "d" not in path.keys() :
- 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")
- continue
- csp = cubicsuperpath.parsePath(path.get("d"))
- remove = []
- for i in range(len(csp)) :
- subpath = [ [point[:] for point in points] for points in csp[i]]
- subpath = self.apply_transforms(path,[subpath])[0]
- bounds = csp_simple_bound([subpath])
- if (bounds[2]-bounds[0])**2+(bounds[3]-bounds[1])**2 < self.options.area_find_artefacts_diameter**2:
- if self.options.area_find_artefacts_action == "mark with an arrow" :
- 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]) )
- arrow = self.apply_transforms(path,arrow,True)
- inkex.etree.SubElement(parent, inkex.addNS('path','svg'),
- {
- 'd': cubicsuperpath.formatPath(arrow),
- 'style': styles["area artefact arrow"],
- 'gcodetools': 'area artefact arrow',
- })
- elif self.options.area_find_artefacts_action == "mark with style" :
- inkex.etree.SubElement(parent, inkex.addNS('path','svg'), {'d': cubicsuperpath.formatPath(csp[i]), 'style': styles["area artefact"]})
- remove.append(i)
- elif self.options.area_find_artefacts_action == "delete" :
- remove.append(i)
- print_("Deleted artefact %s" % subpath )
- remove.reverse()
- for i in remove :
- del csp[i]
- if len(csp) == 0 :
- parent.remove(path)
- else :
- path.set("d", cubicsuperpath.formatPath(csp))
- return
- ################################################################################
- ###
- ### Calculate area curves
- ###
- ################################################################################
- def area(self) :
- if len(self.selected_paths)<=0:
- self.error(_("This extension requires at least one selected path."),"warning")
- return
- for layer in self.layers :
- if layer in self.selected_paths :
- self.set_tool(layer)
- if self.tools[layer][0]['diameter']<=0 :
- 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")
- for path in self.selected_paths[layer]:
- print_(("doing path", path.get("style"), path.get("d")))
- area_group = inkex.etree.SubElement( path.getparent(), inkex.addNS('g','svg') )
- d = path.get('d')
- print_(d)
- if d==None:
- print_("omitting non-path")
- self.error(_("Warning: omitting non-path"),"selection_contains_objects_that_are_not_paths")
- continue
- csp = cubicsuperpath.parsePath(d)
- if path.get(inkex.addNS('type','sodipodi'))!="inkscape:offset":
- print_("Path %s is not an offset. Preparation started." % path.get("id"))
- # Path is not offset. Preparation will be needed.
- # Finding top most point in path (min y value)
- min_x,min_y,min_i,min_j,min_t = csp_true_bounds(csp)[1]
- # Reverse path if needed.
- if min_y!=float("-inf") :
- # Move outline subpath to the begining of csp
- subp = csp[min_i]
- del csp[min_i]
- j = min_j
- # Split by the topmost point and join again
- if min_t in [0,1]:
- if min_t == 0: j=j-1
- subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1]
- subp = [ [subp[j][1], subp[j][1], subp[j][2]] ] + subp[j+1:] + subp[:j] + [ [subp[j][0], subp[j][1], subp[j][1]] ]
- else:
- sp1,sp2,sp3 = csp_split(subp[j-1],subp[j],min_t)
- subp[-1][2], subp[0][0] = subp[-1][1], subp[0][1]
- subp = [ [ sp2[1], sp2[1],sp2[2] ] ] + [sp3] + subp[j+1:] + subp[:j-1] + [sp1] + [[ sp2[0], sp2[1],sp2[1] ]]
- csp = [subp] + csp
- # reverse path if needed
- if csp_subpath_ccw(csp[0]) :
- for i in range(len(csp)):
- n = []
- for j in csp[i]:
- n = [ [j[2][:],j[1][:],j[0][:]] ] + n
- csp[i] = n[:]
- d = cubicsuperpath.formatPath(csp)
- print_(("original d=",d))
- d = re.sub(r'(?i)(m[^mz]+)',r'\1 Z ',d)
- d = re.sub(r'(?i)\s*z\s*z\s*',r' Z ',d)
- d = re.sub(r'(?i)\s*([A-Za-z])\s*',r' \1 ',d)
- print_(("formatted d=",d))
- # scale = sqrt(Xscale**2 + Yscale**2) / sqrt(1**2 + 1**2)
- p0 = self.transform([0,0],layer)
- p1 = self.transform([0,1],layer)
- scale = (P(p0)-P(p1)).mag()
- if scale == 0 : scale = 1.
- else : scale = 1./scale
- print_(scale)
- tool_d = self.tools[layer][0]['diameter']*scale
- r = self.options.area_inkscape_radius * scale
- sign=1 if r>0 else -1
- print_("Tool diameter = %s, r = %s" % (tool_d, r))
- # avoiding infinite loops
- if self.options.area_tool_overlap>0.9 : self.options.area_tool_overlap = .9
- for i in range(self.options.max_area_curves):
- radius = - tool_d * (i*(1-self.options.area_tool_overlap)+0.5) * sign
- if abs(radius)>abs(r):
- radius = -r
- inkex.etree.SubElement( area_group, inkex.addNS('path','svg'),
- {
- inkex.addNS('type','sodipodi'): 'inkscape:offset',
- inkex.addNS('radius','inkscape'): str(radius),
- inkex.addNS('original','inkscape'): d,
- 'style': styles["biarc_style_i"]['area']
- })
- print_(("adding curve",area_group,d,styles["biarc_style_i"]['area']))
- if radius == -r : break
- ################################################################################
- ###
- ### Polyline to biarc
- ###
- ### Converts Polyline to Biarc
- ################################################################################
- def polyline_to_biarc(self):
- def biarc(sm, depth=0):
- def biarc_split(sp1,sp2, z1, z2, depth):
- if depth<options.biarc_max_split_depth:
- sp1,sp2,sp3 = csp_split(sp1,sp2)
- l1, l2 = cspseglength(sp1,sp2), cspseglength(sp2,sp3)
- if l1+l2 == 0 : zm = z1
- else : zm = z1+(z2-z1)*l1/(l1+l2)
- return biarc(sp1,sp2,z1,zm,depth+1)+biarc(sp2,sp3,zm,z2,depth+1)
- else: return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
- P0, P4 = P(sp1[1]), P(sp2[1])
- TS, TE, v = (P(sp1[2])-P0), -(P(sp2[0])-P4), P0 - P4
- tsa, tea, va = TS.angle(), TE.angle(), v.angle()
- if TE.mag()<straight_distance_tolerance and TS.mag()<straight_distance_tolerance:
- # Both tangents are zerro - line straight
- return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
- if TE.mag() < straight_distance_tolerance:
- TE = -(TS+v).unit()
- r = TS.mag()/v.mag()*2
- elif TS.mag() < straight_distance_tolerance:
- TS = -(TE+v).unit()
- r = 1/( TE.mag()/v.mag()*2 )
- else:
- r=TS.mag()/TE.mag()
- TS, TE = TS.unit(), TE.unit()
- tang_are_parallel = ((tsa-tea)%math.pi<straight_tolerance or math.pi-(tsa-tea)%math.pi<straight_tolerance )
- if ( tang_are_parallel and
- ((v.mag()<straight_distance_tolerance or TE.mag()<straight_distance_tolerance or TS.mag()<straight_distance_tolerance) or
- 1-abs(TS*v/(TS.mag()*v.mag()))<straight_tolerance) ):
- # Both tangents are parallel and start and end are the same - line straight
- # or one of tangents still smaller then tollerance
- # Both tangents and v are parallel - line straight
- return [ [sp1[1],'line', 0, 0, sp2[1], [z1,z2]] ]
- c,b,a = v*v, 2*v*(r*TS+TE), 2*r*(TS*TE-1)
- if v.mag()==0:
- return biarc_split(sp1, sp2, z1, z2, depth)
- asmall, bsmall, csmall = abs(a)<10**-10,abs(b)<10**-10,abs(c)<10**-10
- if asmall and b!=0: beta = -c/b
- elif csmall and a!=0: beta = -b/a
- elif not asmall:
- discr = b*b-4*a*c
- if discr < 0: raise ValueError, (a,b,c,discr)
- disq = discr**.5
- beta1 = (-b - disq) / 2 / a
- beta2 = (-b + disq) / 2 / a
- if beta1*beta2 > 0 : raise ValueError, (a,b,c,disq,beta1,beta2)
- beta = max(beta1, beta2)
- elif asmall and bsmall:
- return biarc_split(sp1, sp2, z1, z2, depth)
- alpha = beta * r
- ab = alpha + beta
- P1 = P0 + alpha * TS
- P3 = P4 - beta * TE
- P2 = (beta / ab) * P1 + (alpha / ab) * P3
- def calculate_arc_params(P0,P1,P2):
- D = (P0+P2)/2
- if (D-P1).mag()==0: return None, None
- R = D - ( (D-P0).mag()**2/(D-P1).mag() )*(P1-D).unit()
- p0a, p1a, p2a = (P0-R).angle()%(2*math.pi), (P1-R).angle()%(2*math.pi), (P2-R).angle()%(2*math.pi)
- alpha = (p2a - p0a) % (2*math.pi)
- if (p0a<p2a and (p1a<p0a or p2a<p1a)) or (p2a<p1a<p0a) :
- alpha = -2*math.pi+alpha
- if abs(R.x)>1000000 or abs(R.y)>1000000 or (R-P0).mag<options.min_arc_radius**2 :
- return None, None
- else :
- return R, alpha
- R1,a1 = calculate_arc_params(P0,P1,P2)
- R2,a2 = calculate_arc_params(P2,P3,P4)
- 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]] ]
- d = csp_to_arc_distance(sp1,sp2, [P0,P2,R1,a1],[P2,P4,R2,a2])
- if d > options.biarc_tolerance and depth<options.biarc_max_split_depth : return biarc_split(sp1, sp2, z1, z2, depth)
- else:
- if R2.mag()*a2 == 0 : zm = z2
- else : zm = z1 + (z2-z1)*(abs(R1.mag()*a1))/(abs(R2.mag()*a2)+abs(R1.mag()*a1))
- l = (P0-P2).l2()
- if l < EMC_TOLERANCE_EQUAL**2 or l<EMC_TOLERANCE_EQUAL**2 * R1.l2() /100 :
- # arc should be straight otherwise it could be threated as full circle
- arc1 = [ sp1[1], 'line', 0, 0, [P2.x,P2.y], [z1,zm] ]
- else :
- arc1 = [ sp1[1], 'arc', [R1.x,R1.y], a1, [P2.x,P2.y], [z1,zm] ]
- l = (P4-P2).l2()
- if l < EMC_TOLERANCE_EQUAL**2 or l<EMC_TOLERANCE_EQUAL**2 * R2.l2() /100 :
- # arc should be straight otherwise it could be threated as full circle
- arc2 = [ [P2.x,P2.y], 'line', 0, 0, [P4.x,P4.y], [zm,z2] ]
- else :
- arc2 = [ [P2.x,P2.y], 'arc', [R2.x,R2.y], a2, [P4.x,P4.y], [zm,z2] ]
- return [ arc1, arc2 ]
- for layer in self.layers :
- if layer in self.selected_paths :
- for path in self.selected_paths[layer]:
- d = path.get('d')
- if d==None:
- print_("omitting non-path")
- self.error(_("Warning: omitting non-path"),"selection_contains_objects_that_are_not_paths")
- continue
- csp = cubicsuperpath.parsePath(d)
- csp = self.apply_transforms(path, csp)
- csp = self.transform_csp(csp, layer)
- # lets pretend that csp is a polyline
- poly = [ [point[1] for point in subpath] for subpath in csp ]
- self.draw_csp([ [ [point,point,point] for point in subpoly] for subpoly in poly ],layer)
- # lets create biarcs
- for subpoly in poly :
- # lets split polyline into different smooth parths.
- if len(subpoly)>2 :
- smooth = [ [subpoly[0],subpoly[1]] ]
- for p1,p2,p3 in zip(subpoly,subpoly[1:],subpoly[2:]) :
- # normalize p1p2 and p2p3 to get angle
- s1,s2 = normalize( p1[0]-p2[0], p1[1]-p2[1]), normalize( p3[0]-p2[0], p3[1]-p2[1])
- if cross(s1,s2) > corner_tolerance :
- #it's an angle
- smooth += [ [p2,p3] ]
- else:
- smooth[-1].append(p3)
- for sm in smooth :
- smooth_polyline_to_biarc(sm)
- ################################################################################
- ###
- ### Area fill
- ###
- ### Fills area with lines
- ################################################################################
- def area_fill(self):
- # convert degrees into rad
- self.options.area_fill_angle = self.options.area_fill_angle * math.pi / 180
- if len(self.selected_paths)<=0:
- self.error(_("This extension requires at least one selected path."),"warning")
- return
- for layer in self.layers :
- if layer in self.selected_paths :
- self.set_tool(layer)
- if self.tools[layer][0]['diameter']<=0 :
- 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")
- tool = self.tools[layer][0]
- for path in self.selected_paths[layer]:
- lines = []
- print_(("doing path", path.get("style"), path.get("d")))
- area_group = inkex.etree.SubElement( path.getparent(), inkex.addNS('g','svg') )
- d = path.get('d')
- if d==None:
- print_("omitting non-path")
- self.error(_("Warning: omitting non-path"),"selection_contains_objects_that_are_not_paths")
- continue
- csp = cubicsuperpath.parsePath(d)
- csp = self.apply_transforms(path, csp)
- csp = csp_close_all_subpaths(csp)
- csp = self.transform_csp(csp, layer)
- #maxx = max([x,y,i,j,root],maxx)
- # rotate the path to get bounds in defined direction.
- a = - self.options.area_fill_angle
- 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 ]
- bounds = csp_true_bounds(rotated_path)
- # Draw the lines
- # Get path's bounds
- b = [0.0, 0.0, 0.0, 0.0] # [minx,miny,maxx,maxy]
- for k in range(4):
- i, j, t = bounds[k][2], bounds[k][3], bounds[k][4]
- b[k] = csp_at_t(rotated_path[i][j-1],rotated_path[i][j],t)[k%2]
- # Zig-zag
- r = tool['diameter']*(1-self.options.area_tool_overlap)
- if r<=0 :
- self.error('Tools diameter must be greater than 0!', 'error')
- return
- lines += [ [] ]
- if self.options.area_fill_method == 'zig-zag' :
- i = b[0] - self.options.area_fill_shift*r
- top = True
- last_one = True
- while (i<b[2] or last_one) :
- if i>=b[2] : last_one = False
- if lines[-1] == [] :
- lines[-1] += [ [i,b[3]] ]
- if top :
- lines[-1] += [ [i,b[1]],[i+r,b[1]] ]
- else :
- lines[-1] += [ [i,b[3]], [i+r,b[3]] ]
- top = not top
- i += r
- else :
- w, h = b[2]-b[0] + self.options.area_fill_shift*r , b[3]-b[1] + self.options.area_fill_shift*r
- x,y = b[0] - self.options.area_fill_shift*r, b[1] - self.options.area_fill_shift*r
- lines[-1] += [ [x,y] ]
- stage = 0
- start = True
- while w>0 and h>0 :
- stage = (stage+1)%4
- if stage == 0 :
- y -= h
- h -= r
- elif stage == 1:
- x += w
- if not start:
- w -= r
- start = False
- elif stage == 2 :
- y += h
- h -= r
- elif stage == 3:
- x -= w
- w -=r
- lines[-1] += [ [x,y] ]
- stage = (stage+1)%4
- if w <= 0 and h>0 :
- y = y-h if stage == 0 else y+h
- if h <= 0 and w>0 :
- x = x-w if stage == 3 else x+w
- lines[-1] += [ [x,y] ]
- # Rotate created paths back
- a = self.options.area_fill_angle
- 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 ]
- # get the intersection points
- splitted_line = [ [lines[0][0]] ]
- intersections = {}
- for l1,l2, in zip(lines[0],lines[0][1:]):
- ints = []
- if l1[0]==l2[0] and l1[1]==l2[1] : continue
- for i in range(len(csp)) :
- for j in range(1,len(csp[i])) :
- sp1,sp2 = csp[i][j-1], csp[i][j]
- roots = csp_line_intersection(l1,l2,sp1,sp2)
- for t in roots :
- p = tuple(csp_at_t(sp1,sp2,t))
- if l1[0]==l2[0] :
- t1 = (p[1]-l1[1])/(l2[1]-l1[1])
- else :
- t1 = (p[0]-l1[0])/(l2[0]-l1[0])
- if 0<=t1<=1 :
- ints += [[t1, p[0],p[1], i,j,t]]
- if p in intersections :
- intersections[p] += [ [i,j,t] ]
- else :
- intersections[p] = [ [i,j,t] ]
- #p = self.transform(p,layer,True)
- #draw_pointer(p)
- ints.sort()
- for i in ints:
- splitted_line[-1] +=[ [ i[1], i[2]] ]
- splitted_line += [ [ [ i[1], i[2]] ] ]
- splitted_line[-1] += [ l2 ]
- i = 0
- print_(splitted_line)
- while i < len(splitted_line) :
- # check if the middle point of the first lines segment is inside the path.
- # and remove the subline if not.
- l1,l2 = splitted_line[i][0],splitted_line[i][1]
- p = [(l1[0]+l2[0])/2, (l1[1]+l2[1])/2]
- if not point_inside_csp(p, csp):
- #i +=1
- del splitted_line[i]
- else :
- i += 1
- # if we've used spiral method we'll try to save the order of cutting
- do_not_change_order = self.options.area_fill_method == 'spiral'
- # now let's try connect splitted lines
- #while len(splitted_line)>0 :
- #TODO
- # and apply back transrormations to draw them
- csp_line = csp_from_polyline(splitted_line)
- csp_line = self.transform_csp(csp_line, layer, True)
- self.draw_csp(csp_line, group = area_group)
- # draw_csp(lines)
- ################################################################################
- ###
- ### Engraving
- ###
- #LT Notes to self: See wiki.inkscape.org/wiki/index.php/PythonEffectTutorial
- # To create anything in the Inkscape document, look at the XML editor for
- # details of how such an element looks in XML, then follow this model.
- #layer number n appears in XML as <svg:g id="layern" inkscape:label="layername">
- #
- #to create it, use
- #Mylayer=inkex.etree.SubElement(self.document.getroot(), 'g') #Create a generic element
- #Mylayer.set(inkex.addNS('label', 'inkscape'), "layername") #Gives it a name
- #Mylayer.set(inkex.addNS('groupmode', 'inkscape'), 'layer') #Tells Inkscape it's a layer
- #
- #group appears in XML as <svg:g id="gnnnnn"> where nnnnn is a number
- #
- #to create it, use
- #Mygroup=inkex.etree.SubElement(parent, inkex.addNS('g','svg'), {"gcodetools":"My group label"})
- # where parent may be the layer or a parent group. To get the parent group, you can use
- #parent = self.selected_paths[layer][0].getparent()
- ################################################################################
- def engraving(self) :
- #global x1,y1,rx,ry
- global cspm, wl
- global nlLT, i, j
- global gcode_3Dleft ,gcode_3Dright
- global max_dist #minimum of tool radius and user's requested maximum distance
- global eye_dist
- eye_dist = 100 #3D constant. Try varying it for your eyes
- def bisect((nx1,ny1),(nx2,ny2)) :
- """LT Find angle bisecting the normals n1 and n2
- Parameters: Normalised normals
- Returns: nx - Normal of bisector, normalised to 1/cos(a)
- ny -
- sinBis2 - sin(angle turned/2): positive if turning in
- Note that bisect(n1,n2) and bisect(n2,n1) give opposite sinBis2 results
- If sinturn is less than the user's requested angle tolerance, I return 0
- """
- #We can get absolute value of cos(bisector vector)
- #Note: Need to use max in case of rounding errors
- cosBis = math.sqrt(max(0,(1.0+nx1*nx2-ny1*ny2)/2.0))
- #We can get correct sign of the sin, assuming cos is positive
- if (abs(ny1-ny2)< engraving_tolerance) or (abs(cosBis) < engraving_tolerance) :
- if (abs(nx1-nx2)< engraving_tolerance): return(nx1,ny1,0.0)
- sinBis = math.copysign(1,ny1)
- else :
- sinBis = cosBis*(nx2-nx1)/(ny1-ny2)
- #We can correct signs by noting that the dot product
- # of bisector and either normal must be >0
- costurn=cosBis*nx1+sinBis*ny1
- if costurn == 0 : return (ny1*100,-nx1*100,1) #Path doubles back on itself
- sinturn=sinBis*nx1-cosBis*ny1
- if costurn<0 : sinturn=-sinturn
- if 0 < sinturn*114.6 < (180-self.options.engraving_sharp_angle_tollerance) :
- sinturn=0 #set to zero if less than the user wants to see.
- return (cosBis/costurn,sinBis/costurn, sinturn)
- #end bisect
- def get_radius_to_line((x1,y1),(nx1,ny1), (nx2,ny2),(x2,y2),(nx23,ny23),(x3,y3),(nx3,ny3)):
- """LT find biggest circle we can engrave here, if constrained by line 2-3
- Parameters:
- x1,y1,nx1,ny1 coordinates and normal of the line we're currently engraving
- nx2,ny2 angle bisector at point 2
- x2,y2 coordinates of first point of line 2-3
- nx23,ny23 normal to the line 2-3
- x3,y3 coordinates of the other end
- nx3,ny3 angle bisector at point 3
- Returns:
- radius or self.options.engraving_max_dist if line doesn't limit radius
- This function can be used in three ways:
- - With nx1=ny1=0 it finds circle centred at x1,y1
- - with nx1,ny1 normalised, it finds circle tangential at x1,y1
- - with nx1,ny1 scaled by 1/cos(a) it finds circle centred on an angle bisector
- where a is the angle between the bisector and the previous/next normals
- If the centre of the circle tangential to the line 2-3 is outside the
- angle bisectors at its ends, ignore this line.
- # Note that it handles corners in the conventional manner of letter cutting
- # by mitering, not rounding.
- # Algorithm uses dot products of normals to find radius
- # and hence coordinates of centre
- """
- global max_dist
- #Start by converting coordinates to be relative to x1,y1
- x2,y2= x2-x1, y2-y1
- x3,y3= x3-x1, y3-y1
- #The logic uses vector arithmetic.
- #The dot product of two vectors gives the product of their lengths
- #multiplied by the cos of the angle between them.
- # So, the perpendicular distance from x1y1 to the line 2-3
- # is equal to the dot product of its normal and x2y2 or x3y3
- #It is also equal to the projection of x1y1-xcyc on the line's normal
- # plus the radius. But, as the normal faces inside the path we must negate it.
- #Make sure the line in question is facing x1,y1 and vice versa
- dist=-x2*nx23-y2*ny23
- if dist<0 : return max_dist
- denom=1.-nx23*nx1-ny23*ny1
- if denom < engraving_tolerance : return max_dist
- #radius and centre are:
- r=dist/denom
- cx=r*nx1
- cy=r*ny1
- #if c is not between the angle bisectors at the ends of the line, ignore
- #Use vector cross products. Not sure if I need the .0001 safety margins:
- if (x2-cx)*ny2 > (y2-cy)*nx2 +0.0001 :
- return max_dist
- if (x3-cx)*ny3 < (y3-cy)*nx3 -0.0001 :
- return max_dist
- return min(r, max_dist)
- #end of get_radius_to_line
- def get_radius_to_point((x1,y1),(nx,ny), (x2,y2)):
- """LT find biggest circle we can engrave here, constrained by point x2,y2
- This function can be used in three ways:
- - With nx=ny=0 it finds circle centred at x1,y1
- - with nx,ny normalised, it finds circle tangential at x1,y1
- - with nx,ny scaled by 1/cos(a) it finds circle centred on an angle bisector
- where a is the angle between the bisector and the previous/next normals
- Note that I wrote this to replace find_cutter_centre. It is far less
- sophisticated but, I hope, far faster.
- It turns out that finding a circle touching a point is harder than a circle
- touching a line.
- """
- global max_dist
- #Start by converting coordinates to be relative to x1,y1
- x2,y2= x2-x1, y2-y1
- denom=nx**2+ny**2-1
- if denom<=engraving_tolerance : #Not a corner bisector
- if denom==-1 : #Find circle centre x1,y1
- return math.sqrt(x2**2+y2**2)
- #if x2,y2 not in front of the normal...
- if x2*nx+y2*ny <=0 : return max_dist
- #print_("Straight",x1,y1,nx,ny,x2,y2)
- return (x2**2+y2**2)/(2*(x2*nx+y2*ny) )
- #It is a corner bisector, so..
- discriminator = (x2*nx+y2*ny)**2 - denom*(x2**2+y2**2)
- if discriminator < 0 :
- return max_dist #this part irrelevant
- r=(x2*nx+y2*ny -math.sqrt(discriminator))/denom
- #print_("Corner",x1,y1,nx,ny,x1+x2,y1+y2,discriminator,r)
- return min(r, max_dist)
- #end of get_radius_to_point
- def bez_divide(a,b,c,d):
- """LT recursively divide a Bezier.
- Divides until difference between each
- part and a straight line is less than some limit
- Note that, as simple as this code is, it is mathematically correct.
- Parameters:
- a,b,c and d are each a list of x,y real values
- Bezier end points a and d, control points b and c
- Returns:
- a list of Beziers.
- Each Bezier is a list with four members,
- each a list holding a coordinate pair
- Note that the final point of one member is the same as
- the first point of the next, and the control points
- there are smooth and symmetrical. I use this fact later.
- """
- bx=b[0]-a[0]
- by=b[1]-a[1]
- cx=c[0]-a[0]
- cy=c[1]-a[1]
- dx=d[0]-a[0]
- dy=d[1]-a[1]
- limit=8*math.hypot(dx,dy)/self.options.engraving_newton_iterations
- #LT This is the only limit we get from the user currently
- if abs(dx*by-bx*dy)<limit and abs(dx*cy-cx*dy)<limit :
- return [[a,b,c,d]]
- abx=(a[0]+b[0])/2.0
- aby=(a[1]+b[1])/2.0
- bcx=(b[0]+c[0])/2.0
- bcy=(b[1]+c[1])/2.0
- cdx=(c[0]+d[0])/2.0
- cdy=(c[1]+d[1])/2.0
- abcx=(abx+bcx)/2.0
- abcy=(aby+bcy)/2.0
- bcdx=(bcx+cdx)/2.0
- bcdy=(bcy+cdy)/2.0
- m=[(abcx+bcdx)/2.0,(abcy+bcdy)/2.0]
- return bez_divide(a,[abx,aby],[abcx,abcy],m) + bez_divide(m,[bcdx,bcdy],[cdx,cdy],d)
- #end of bez_divide
- def get_biggest((x1,y1),(nx,ny)):
- """LT Find biggest circle we can draw inside path at point x1,y1 normal nx,ny
- Parameters:
- point - either on a line or at a reflex corner
- normal - normalised to 1 if on a line, to 1/cos(a) at a corner
- Returns:
- tuple (j,i,r)
- ..where j and i are indices of limiting segment, r is radius
- """
- global max_dist, nlLT, i, j
- n1 = nlLT[j][i-1] #current node
- jjmin = -1
- iimin = -1
- r = max_dist
- # set limits within which to look for lines
- xmin, xmax = x1+r*nx-r, x1+r*nx+r
- ymin, ymax = y1+r*ny-r, y1+r*ny+r
- for jj in xrange(0,len(nlLT)) : #for every subpath of this object
- for ii in xrange(0,len(nlLT[jj])) : #for every point and line
- if nlLT[jj][ii-1][2] : #if a point
- if jj==j : #except this one
- if abs(ii-i)<3 or abs(ii-i)>len(nlLT[j])-3 : continue
- t1=get_radius_to_point((x1,y1),(nx,ny),nlLT[jj][ii-1][0] )
- #print_("Try pt i,ii,t1,x1,y1",i,ii,t1,x1,y1)
- else: #doing a line
- if jj==j : #except this one
- if abs(ii-i)<2 or abs(ii-i)==len(nlLT[j])-1 : continue
- if abs(ii-i)==2 and nlLT[j][(ii+i)/2-1][3]<=0 : continue
- if (abs(ii-i)==len(nlLT[j])-2) and nlLT[j][-1][3]<=0 : continue
- nx2,ny2 = nlLT[jj][ii-2][1]
- x2,y2 = nlLT[jj][ii-1][0]
- nx23,ny23 = nlLT[jj][ii-1][1]
- x3,y3 = nlLT[jj][ii][0]
- nx3,ny3 = nlLT[jj][ii][1]
- if nlLT[jj][ii-2][3]>0 : #acute, so use normal, not bisector
- nx2=nx23
- ny2=ny23
- if nlLT[jj][ii][3]>0 : #acute, so use normal, not bisector
- nx3=nx23
- ny3=ny23
- x23min,x23max=min(x2,x3),max(x2,x3)
- y23min,y23max=min(y2,y3),max(y2,y3)
- #see if line in range
- if n1[2]==False and (x23max<xmin or x23min>xmax or y23max<ymin or y23min>ymax) : continue
- t1=get_radius_to_line((x1,y1),(nx,ny), (nx2,ny2),(x2,y2),(nx23,ny23), (x3,y3),(nx3,ny3))
- #print_("Try line i,ii,t1,x1,y1",i,ii,t1,x1,y1)
- if 0<=t1<r :
- r = t1
- iimin = ii
- jjmin = jj
- xmin, xmax = x1+r*nx-r, x1+r*nx+r
- ymin, ymax = y1+r*ny-r, y1+r*ny+r
- #next ii
- #next jj
- return (jjmin,iimin,r)
- #end of get_biggest
- def line_divide((x0,y0),j0,i0,(x1,y1),j1,i1,(nx,ny),length):
- """LT recursively divide a line as much as necessary
- NOTE: This function is not currently used
- By noting which other path segment is touched by the circles at each end,
- we can see if anything is to be gained by a further subdivision, since
- if they touch the same bit of path we can move linearly between them.
- Also, we can handle points correctly.
- Parameters:
- end points and indices of limiting path, normal, length
- Returns:
- list of toolpath points
- each a list of 3 reals: x, y coordinates, radius
- """
- global nlLT, i, j, lmin
- x2=(x0+x1)/2
- y2=(y0+y1)/2
- j2,i2,r2=get_biggest( (x2,y2), (nx,ny))
- if length<lmin : return [ [x2, y2, r2] ]
- if j2==j0 and i2==i0 : #Same as left end. Don't subdivide this part any more
- return [ [x2, y2, r2], line_divide((x2,y2),j2,i2,(x1,y1),j1,i1,(nx,ny),length/2)]
- if j2==j1 and i2==i1 : #Same as right end. Don't subdivide this part any more
- return [ line_divide((x0,y0),j0,i0,(x2,y2),j2,i2,(nx,ny),length/2), [x2, y2, r2] ]
- 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)]
- #end of line_divide()
- def save_point((x,y),w,i,j,ii,jj):
- """LT Save this point and delete previous one if linear
- The point is, we generate tons of points but many may be in a straight 3D line.
- There is no benefit in saving the imtermediate points.
- """
- global wl, cspm
- x=round(x,2) #round to 2 decimals
- y=round(y,2) #round to 2 decimals
- w=round(w,2) #round to 2 decimals
- if len(cspm)>1 :
- xy1a,xy1,xy1b,i1,j1,ii1,jj1=cspm[-1]
- w1=wl[-1]
- if i==i1 and j==j1 and ii==ii1 and jj==jj1 : #one match
- xy1a,xy2,xy1b,i1,j1,ii1,jj1=cspm[-2]
- w2=wl[-2]
- if i==i1 and j==j1 and ii==ii1 and jj==jj1 : #two matches. Now test linearity
- length1=math.hypot(xy1[0]-x,xy1[1]-y)
- length2=math.hypot(xy2[0]-x,xy2[1]-y)
- length12=math.hypot(xy2[0]-xy1[0],xy2[1]-xy1[1])
- #get the xy distance of point 1 from the line 0-2
- if length2>length1 and length2>length12 : #point 1 between them
- xydist=abs( (xy2[0]-x)*(xy1[1]-y)-(xy1[0]-x)*(xy2[1]-y) )/length2
- if xydist<engraving_tolerance : #so far so good
- wdist=w2+(w-w2)*length1/length2 -w1
- if abs(wdist)<engraving_tolerance :
- #print_("pop",j,i,xy1)
- cspm.pop()
- wl.pop()
- cspm+=[ [ [x,y],[x,y],[x,y],i,j,ii,jj ] ]
- wl+=[w]
- #end of save_point
- def draw_point((x0,y0),(x,y),w,t):
- """LT Draw this point as a circle with a 1px dot in the middle (x,y)
- and a 3D line from (x0,y0) down to x,y. 3D line thickness should be t/2
- Note that points that are subsequently erased as being unneeded do get
- displayed, but this helps the user see the total area covered.
- """
- global gcode_3Dleft ,gcode_3Dright
- if self.options.engraving_draw_calculation_paths :
- inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
- {"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'})
- #Don't draw zero radius circles
- if w:
- inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
- {"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'})
- # Find slope direction for shading
- s=math.atan2(y-y0,x-x0) #-pi to pi
- # convert to 2 hex digits as a shade of red
- s2="#{0:x}0000".format(int(101*(1.5-math.sin(s+0.5))))
- inkex.etree.SubElement( gcode_3Dleft , inkex.addNS('path','svg'),
- { "d": "M %f,%f L %f,%f" %(x0-eye_dist,y0,x-eye_dist-0.14*w,y),
- 'style': "stroke:" + s2 + "; stroke-opacity:1; stroke-width:" + str(t/2) +" ; fill:none",
- "gcodetools": "Gcode G1R"
- })
- inkex.etree.SubElement( gcode_3Dright , inkex.addNS('path','svg'),
- { "d": "M %f,%f L %f,%f" %(x0+eye_dist,y0,x+eye_dist+0.14*r,y),
- 'style': "stroke:" + s2 + "; stroke-opacity:1; stroke-width:" + str(t/2) +" ; fill:none",
- "gcodetools": "Gcode G1L"
- })
- #end of draw_point
- #end of subfunction definitions. engraving() starts here:
- gcode = ''
- r,w, wmax = 0,0,0 #theoretical and tool-radius-limited radii in pixels
- x1,y1,nx,ny =0,0,0,0
- cspe =[]
- we = []
- if len(self.selected_paths)<=0:
- self.error(_("Please select at least one path to engrave and run again."),"warning")
- return
- if not self.check_dir() : return
- #Find what units the user uses
- unit=" mm"
- if self.options.unit == "G20 (All units in inches)" :
- unit=" inches"
- elif self.options.unit != "G21 (All units in mm)" :
- self.error(_("Unknown unit selected. mm assumed"),"warning")
- print_("engraving_max_dist mm/inch", self.options.engraving_max_dist )
- #LT See if we can use this parameter for line and Bezier subdivision:
- bitlen=20/self.options.engraving_newton_iterations
- for layer in self.layers :
- if layer in self.selected_paths :
- #Calculate scale in pixels per user unit (mm or inch)
- p1=self.orientation_points[layer][0][0]
- p2=self.orientation_points[layer][0][1]
- ol=math.hypot(p1[0][0]-p2[0][0],p1[0][1]-p2[0][1])
- oluu=math.hypot(p1[1][0]-p2[1][0],p1[1][1]-p2[1][1])
- print_("Orientation2 p1 p2 ol oluu",p1,p2,ol,oluu)
- orientation_scale = ol/oluu
- self.set_tool(layer)
- shape = self.tools[layer][0]['shape']
- if re.search('w', shape) :
- toolshape = eval('lambda w: ' + shape.strip('"'))
- else:
- self.error(_("Tool '%s' has no shape. 45 degree cone assumed!") % self.tools[layer][0]['name'],"Continue")
- toolshape = lambda w: w
- #Get tool radius in pixels
- toolr=self.tools[layer][0]['diameter'] * orientation_scale/2
- print_("tool radius in pixels=", toolr)
- #max dist from path to engrave in user's units
- max_distuu = min(self.tools[layer][0]['diameter']/2, self.options.engraving_max_dist)
- max_dist=max_distuu*orientation_scale
- print_("max_dist pixels", max_dist )
- engraving_group = inkex.etree.SubElement( self.selected_paths[layer][0].getparent(), inkex.addNS('g','svg') )
- if self.options.engraving_draw_calculation_paths and (self.my3Dlayer == None) :
- self.my3Dlayer=inkex.etree.SubElement(self.document.getroot(), 'g') #Create a generic element at root level
- self.my3Dlayer.set(inkex.addNS('label', 'inkscape'), "3D") #Gives it a name
- self.my3Dlayer.set(inkex.addNS('groupmode', 'inkscape'), 'layer') #Tells Inkscape it's a layer
- #Create groups for left and right eyes
- if self.options.engraving_draw_calculation_paths :
- gcode_3Dleft = inkex.etree.SubElement(self.my3Dlayer, inkex.addNS('g','svg'), {"gcodetools":"Gcode 3D L"})
- gcode_3Dright = inkex.etree.SubElement(self.my3Dlayer, inkex.addNS('g','svg'), {"gcodetools":"Gcode 3D R"})
- for node in self.selected_paths[layer] :
- if node.tag == inkex.addNS('path','svg'):
- cspi = cubicsuperpath.parsePath(node.get('d'))
- #LT: Create my own list. n1LT[j] is for subpath j
- nlLT = []
- for j in xrange(len(cspi)): #LT For each subpath...
- # Remove zero length segments, assume closed path
- i = 0 #LT was from i=1
- while i<len(cspi[j]):
- 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:
- cspi[j][i-1][2] = cspi[j][i][2]
- del cspi[j][i]
- else:
- i += 1
- for csp in cspi: #LT6a For each subpath...
- #Create copies in 3D layer
- print_("csp is zz ",csp)
- cspl=[]
- cspr=[]
- #create list containing lines and points, starting with a point
- # line members: [x,y],[nx,ny],False,i
- # x,y is start of line. Normal on engraved side.
- # Normal is normalised (unit length)
- #Note that Y axis increases down the page
- # corner members: [x,y],[nx,ny],True,sin(halfangle)
- # if halfangle>0: radius 0 here. normal is bisector
- # if halfangle<0. reflex angle. normal is bisector
- # corner normals are divided by cos(halfangle)
- #so that they will engrave correctly
- print_("csp is",csp)
- nlLT.append ([])
- for i in range(0,len(csp)): #LT for each point
- #n = []
- sp0, sp1, sp2 = csp[i-2], csp[i-1], csp[i]
- if self.options.engraving_draw_calculation_paths:
- #Copy it to 3D layer objects
- spl=[]
- spr=[]
- for j in range(0,3) :
- pl=[sp2[j][0]-eye_dist,sp2[j][1]]
- pr=[sp2[j][0]+eye_dist,sp2[j][1]]
- spl+=[pl]
- spr+=[pr]
- cspl+=[spl]
- cspr+=[spr]
- #LT find angle between this and previous segment
- x0,y0 = sp1[1]
- nx1,ny1 = csp_normalized_normal(sp1,sp2,0)
- #I don't trust this function, so test result
- if abs(1-math.hypot(nx1,ny1))> 0.00001 :
- print_("csp_normalised_normal error t=0",nx1,ny1,sp1,sp2)
- self.error(_("csp_normalised_normal error. See log."),"warning")
- nx0, ny0 = csp_normalized_normal(sp0,sp1,1)
- if abs(1-math.hypot(nx0,ny0))> 0.00001 :
- print_("csp_normalised_normal error t=1",nx0,ny0,sp1,sp2)
- self.error(_("csp_normalised_normal error. See log."),"warning")
- bx,by,s=bisect((nx0,ny0),(nx1,ny1))
- #record x,y,normal,ifCorner, sin(angle-turned/2)
- nlLT[-1] += [[ [x0,y0],[bx,by], True, s]]
- #LT now do the line
- if sp1[1]==sp1[2] and sp2[0]==sp2[1] : #straightline
- nlLT[-1]+=[[sp1[1],[nx1,ny1],False,i]]
- else : #Bezier. First, recursively cut it up:
- nn=bez_divide(sp1[1],sp1[2],sp2[0],sp2[1])
- first=True #Flag entry to divided Bezier
- for bLT in nn : #save as two line segments
- for seg in range(3) :
- if seg>0 or first :
- nx1=bLT[seg][1]-bLT[seg+1][1]
- ny1=bLT[seg+1][0]-bLT[seg][0]
- l1=math.hypot(nx1,ny1)
- if l1<engraving_tolerance :
- continue
- nx1=nx1/l1 #normalise them
- ny1=ny1/l1
- nlLT[-1]+=[[bLT[seg],[nx1,ny1], False,i]]
- first=False
- if seg<2 : #get outgoing bisector
- nx0=nx1
- ny0=ny1
- nx1=bLT[seg+1][1]-bLT[seg+2][1]
- ny1=bLT[seg+2][0]-bLT[seg+1][0]
- l1=math.hypot(nx1,ny1)
- if l1<engraving_tolerance :
- continue
- nx1=nx1/l1 #normalise them
- ny1=ny1/l1
- #bisect
- bx,by,s=bisect((nx0,ny0),(nx1,ny1))
- nlLT[-1] += [[bLT[seg+1],[bx,by], True, 0.]]
- #LT for each segment - ends here.
- print_(("engraving_draw_calculation_paths=",self.options.engraving_draw_calculation_paths))
- if self.options.engraving_draw_calculation_paths:
- #Copy complete paths to 3D layer
- #print_("cspl",cspl)
- cspl+=[cspl[0]] #Close paths
- cspr+=[cspr[0]] #Close paths
- inkex.etree.SubElement( gcode_3Dleft , inkex.addNS('path','svg'),
- { "d": cubicsuperpath.formatPath([cspl]),
- 'style': "stroke:#808080; stroke-opacity:1; stroke-width:0.6; fill:none",
- "gcodetools": "G1L outline"
- })
- inkex.etree.SubElement( gcode_3Dright , inkex.addNS('path','svg'),
- { "d": cubicsuperpath.formatPath([cspr]),
- 'style': "stroke:#808080; stroke-opacity:1; stroke-width:0.6; fill:none",
- "gcodetools": "G1L outline"
- })
- for p in nlLT[-1]: #For last sub-path
- if p[2]: inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
- { "d": "M %f,%f L %f,%f" %(p[0][0],p[0][1],p[0][0]+p[1][0]*10,p[0][1]+p[1][1]*10),
- 'style': "stroke:#f000af; stroke-opacity:0.46; stroke-width:0.1; fill:none",
- "gcodetools": "Engraving normals"
- })
- else: inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
- { "d": "M %f,%f L %f,%f" %(p[0][0],p[0][1],p[0][0]+p[1][0]*10,p[0][1]+p[1][1]*10),
- 'style': "stroke:#0000ff; stroke-opacity:0.46; stroke-width:0.1; fill:none",
- "gcodetools": "Engraving bisectors"
- })
- #LT6a build nlLT[j] for each subpath - ends here
- #for nnn in nlLT :
- #print_("nlLT",nnn) #LT debug stuff
- # Calculate offset points
- reflex=False
- for j in xrange(len(nlLT)): #LT6b for each subpath
- cspm=[] #Will be my output. List of csps.
- wl=[] #Will be my w output list
- w = r = 0 #LT initial, as first point is an angle
- for i in xrange(len(nlLT[j])) : #LT for each node
- #LT Note: Python enables wrapping of array indices
- # backwards to -1, -2, but not forwards. Hence:
- n0 = nlLT[j][i-2] #previous node
- n1 = nlLT[j][i-1] #current node
- n2 = nlLT[j][i] #next node
- #if n1[2] == True and n1[3]==0 : # A straight angle
- #continue
- x1a,y1a = n1[0] #this point/start of this line
- nx,ny = n1[1]
- x1b,y1b = n2[0] #next point/end of this line
- if n1[2] == True : # We're at a corner
- bits=1
- bit0=0
- #lastr=r #Remember r from last line
- lastw=w #Remember w from last line
- w = max_dist
- if n1[3]>0 : #acute. Limit radius
- len1=math.hypot( (n0[0][0]-n1[0][0]),( n0[0][1]-n1[0][1]) )
- if i<(len(nlLT[j])-1) :
- len2=math.hypot( (nlLT[j][i+1][0][0]-n1[0][0]),(nlLT[j][i+1][0][1]-n1[0][1]) )
- else:
- len2=math.hypot( (nlLT[j][0][0][0]-n1[0][0]),(nlLT[j][0][0][1]-n1[0][1]) )
- #set initial r value, not to be exceeded
- w = math.sqrt(min(len1,len2))/n1[3]
- else: #line. Cut it up if long.
- if n0[3]>0 and not self.options.engraving_draw_calculation_paths :
- bit0=r*n0[3] #after acute corner
- else : bit0=0.0
- length=math.hypot((x1b-x1a),(y1a-y1b))
- bit0=(min(length,bit0))
- bits=int((length-bit0)/bitlen)
- #split excess evenly at both ends
- bit0+=(length-bit0-bitlen*bits)/2
- #print_("j,i,r,bit0,bits",j,i,w,bit0,bits)
- for b in xrange(bits) : #divide line into bits
- x1=x1a+ny*(b*bitlen+bit0)
- y1=y1a-nx*(b*bitlen+bit0)
- jjmin,iimin,w=get_biggest( (x1,y1), (nx,ny))
- print_("i,j,jjmin,iimin,w",i,j,jjmin,iimin,w)
- #w = min(r, toolr)
- wmax=max(wmax,w)
- if reflex : #just after a reflex corner
- reflex = False
- if w<lastw : #need to adjust it
- draw_point((x1,y1),(n0[0][0]+n0[1][0]*w,n0[0][1]+n0[1][1]*w),w, (lastw-w)/2)
- save_point((n0[0][0]+n0[1][0]*w,n0[0][1]+n0[1][1]*w),w,i,j,iimin,jjmin)
- if n1[2] == True : # We're at a corner
- if n1[3]>0 : #acute
- save_point((x1+nx*w,y1+ny*w),w,i,j,iimin,jjmin)
- draw_point((x1,y1),(x1,y1),0,0)
- save_point((x1,y1),0,i,j,iimin,jjmin)
- elif n1[3]<0 : #reflex
- if w>lastw :
- draw_point((x1,y1),(x1+nx*lastw,y1+ny*lastw),w, (w-lastw)/2)
- wmax=max(wmax,w)
- save_point((x1+nx*w,y1+ny*w),w,i,j,iimin,jjmin)
- elif b>0 and n2[3]>0 and not self.options.engraving_draw_calculation_paths : #acute corner coming up
- if jjmin==j and iimin==i+2 : break
- draw_point((x1,y1),(x1+nx*w,y1+ny*w),w, bitlen)
- save_point((x1+nx*w,y1+ny*w),w,i,j,iimin,jjmin)
- #LT end of for each bit of this line
- if n1[2] == True and n1[3]<0 : #reflex angle
- reflex=True
- lastw = w #remember this w
- #LT next i
- cspm+=[cspm[0]]
- print_("cspm",cspm)
- wl+=[wl[0]]
- print_("wl",wl)
- #Note: Original csp_points was a list, each element
- #being 4 points, with the first being the same as the
- #last of the previous set.
- #Each point is a list of [cx,cy,r,w]
- #I have flattened it to a flat list of points.
- if self.options.engraving_draw_calculation_paths==True:
- node = inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'), {
- "d": cubicsuperpath.formatPath([cspm]),
- 'style': styles["biarc_style_i"]['biarc1'],
- "gcodetools": "Engraving calculation paths",
- })
- for i in xrange(len(cspm)):
- inkex.etree.SubElement( engraving_group, inkex.addNS('path','svg'),
- {"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'})
- cspe += [cspm]
- wluu = [] #width list in user units: mm/inches
- for w in wl :
- wluu+=[ w / orientation_scale ]
- print_("wl in pixels",wl)
- print_("wl in user units",wluu)
- #LT previously, we was in pixels so gave wrong depth
- we += [wluu]
- #LT6b For each subpath - ends here
- #LT5 if it is a path - ends here
- #print_("cspe",cspe)
- #print_("we",we)
- #LT4 for each selected object in this layer - ends here
- if cspe!=[]:
- curve = self.parse_curve(cspe, layer, we, toolshape) #convert to lines
- self.draw_curve(curve, layer, engraving_group)
- gcode += self.generate_gcode(curve, layer, self.options.Zsurface)
- #LT3 for layers loop ends here
- if gcode!='' :
- self.header+="(Tool diameter should be at least "+str(2*wmax/orientation_scale)+unit+ ")\n"
- self.header+="(Depth, as a function of radius w, must be "+ self.tools[layer][0]['shape']+ ")\n"
- self.header+="(Rapid feeds use safe Z="+ str(self.options.Zsafe) + unit + ")\n"
- self.header+="(Material surface at Z="+ str(self.options.Zsurface) + unit + ")\n"
- self.export_gcode(gcode)
- else : self.error(_("No need to engrave sharp angles."),"warning")
- ################################################################################
- ###
- ### Orientation
- ###
- ################################################################################
- def orientation(self, layer=None) :
- if layer == None :
- layer = self.current_layer if self.current_layer is not None else self.document.getroot()
- transform = self.get_transforms(layer)
- if transform != [] :
- transform = self.reverse_transform(transform)
- transform = simpletransform.formatTransform(transform)
- if self.options.orientation_points_count == "graffiti" :
- print_(self.graffiti_reference_points)
- print_("Inserting graffiti points")
- if layer in self.graffiti_reference_points: graffiti_reference_points_count = len(self.graffiti_reference_points[layer])
- else: graffiti_reference_points_count = 0
- axis = ["X","Y","Z","A"][graffiti_reference_points_count%4]
- attr = {'gcodetools': "Gcodetools graffiti reference point"}
- if transform != [] :
- attr["transform"] = transform
- g = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), attr)
- inkex.etree.SubElement( g, inkex.addNS('path','svg'),
- {
- 'style': "stroke:none;fill:#00ff00;",
- '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),
- 'gcodetools': "Gcodetools graffiti reference point arrow"
- })
- draw_text(axis,graffiti_reference_points_count*100+10,-10, group = g, gcodetools_tag = "Gcodetools graffiti reference point text")
- elif self.options.orientation_points_count == "in-out reference point" :
- draw_pointer(group = self.current_layer, x = self.view_center, figure="arrow", pointer_type = "In-out reference point", text = "In-out point")
- else :
- print_("Inserting orientation points")
- if layer in self.orientation_points:
- self.error(_("Active layer already has orientation points! Remove them or select another layer!"),"active_layer_already_has_orientation_points")
- attr = {"gcodetools":"Gcodetools orientation group"}
- if transform != [] :
- attr["transform"] = transform
- orientation_group = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), attr)
- doc_height = self.unittouu(self.document.getroot().get('height'))
- if self.document.getroot().get('height') == "100%" :
- doc_height = 1052.3622047
- print_("Overruding height from 100 percents to %s" % doc_height)
- if self.options.unit == "G21 (All units in mm)" :
- points = [[0.,0.,self.options.Zsurface],[100.,0.,self.options.Zdepth],[0.,100.,0.]]
- orientation_scale = 3.5433070660
- print_("orientation_scale < 0 ===> switching to mm units=%0.10f"%orientation_scale )
- elif self.options.unit == "G20 (All units in inches)" :
- points = [[0.,0.,self.options.Zsurface],[5.,0.,self.options.Zdepth],[0.,5.,0.]]
- orientation_scale = 90
- print_("orientation_scale < 0 ===> switching to inches units=%0.10f"%orientation_scale )
- if self.options.orientation_points_count == "2" :
- points = points[:2]
- print_(("using orientation scale",orientation_scale,"i=",points))
- for i in points :
- si = [i[0]*orientation_scale, i[1]*orientation_scale]
- g = inkex.etree.SubElement(orientation_group, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools orientation point (%s points)" % self.options.orientation_points_count})
- inkex.etree.SubElement( g, inkex.addNS('path','svg'),
- {
- 'style': "stroke:none;fill:#000000;",
- '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),
- 'gcodetools': "Gcodetools orientation point arrow"
- })
- 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")
- ################################################################################
- ###
- ### Tools library
- ###
- ################################################################################
- def tools_library(self, layer=None) :
- # Add a tool to the drawing
- if layer == None :
- layer = self.current_layer if self.current_layer is not None else self.document.getroot()
- if layer in self.tools:
- self.error(_("Active layer already has a tool! Remove it or select another layer!"),"active_layer_already_has_tool")
- if self.options.tools_library_type == "pen one" :
- tool = {
- "name": "Pen 1",
- "id": "Pen 0001",
- "diameter":10.,
- "shape": "10",
- "penetration angle":90.,
- "penetration feed":3000.,
- "depth step":1.,
- "feed":1200.,
- "in trajectotry":"",
- "out trajectotry":"",
- "gcode before path":"M280 P0 S80 ; pen up before path",
- "gcode after path":"M280 P0 S50 ; pen down after path",
- "sog":"",
- "spinlde rpm":"",
- "CW or CCW":"",
- "tool change gcode":"""
- M400
- M280 P0 S70 ; lower pen into tool bay
- G1 X0 ; pull carriage into safe zone
- G1 Y230 ; move carriage to center of TOOL 1 bay
- G1 X-25 ; move the carriage into the tool 1 bay
- M400
- M280 P0 S40 ; move pen mount up to lift up the tool and start the print
- M400
- G1 X30
- """,
- "4th axis meaning": " ",
- "4th axis scale": 1.,
- "4th axis offset": 0.,
- "passing feed":"4800",
- "fine feed":"1200",
- }
- elif self.options.tools_library_type == "pen two" :
- tool = {
- "name": "Pen 2",
- "id": "Pen 0002",
- "diameter":10.,
- "shape": "10",
- "penetration angle":90.,
- "penetration feed":3000.,
- "depth step":1.,
- "feed":1200.,
- "in trajectotry":"",
- "out trajectotry":"",
- "gcode before path":"M280 P0 S80 ; pen up before path",
- "gcode after path":"M280 P0 S50 ; pen down after path",
- "sog":"",
- "spinlde rpm":"",
- "CW or CCW":"",
- "tool change gcode":"""
- M400
- M280 P0 S40 ; pen up for tool change
- M400
- G1 X0 ; carriage into safe zone for tool change to happen
- G1 Y230 ; move carriage to center of last TOOL
- G1 X-25 ; move the carriage into the tool bay
- M400
- M280 P0 S70 ; lower pen into tool bay
- G1 X0 ; pull carriage back into safe zone for the tool to disconnect
- G1 Y170 ; move carriage to center of next TOOL
- G1 X-25 ; move the carriage into the tool 1 bay
- M400
- M280 P0 S40 ; move pen mount up to lift up the next tool and continue print
- M400
- G1 X30
- """,
- "4th axis meaning": " ",
- "4th axis scale": 1.,
- "4th axis offset": 0.,
- "passing feed":"4800",
- "fine feed":"1200",
- }
- elif self.options.tools_library_type == "pen three" :
- tool = {
- "name": "Pen 3",
- "id": "Pen 0003",
- "diameter":10.,
- "shape": "10",
- "penetration angle":90.,
- "penetration feed":3000.,
- "depth step":1.,
- "feed":1200.,
- "in trajectotry":"",
- "out trajectotry":"",
- "gcode before path":"M280 P0 S80 ; pen up before path",
- "gcode after path":"M280 P0 S50 ; pen down after path",
- "sog":"",
- "spinlde rpm":"",
- "CW or CCW":"",
- "tool change gcode":"""
- M400
- M280 P0 S40 ; pen up for tool change
- M400
- G1 X0 ; carriage into safe zone for tool change to happen
- G1 Y170 ; move carriage to center of last TOOL
- G1 X-25 ; move the carriage into the tool bay
- M400
- M280 P0 S70 ; lower pen into tool bay
- G1 X0 ; pull carriage back into safe zone for the tool to disconnect
- G1 Y110 ; move carriage to center of next TOOL
- G1 X-25 ; move the carriage into the tool 1 bay
- M400
- M280 P0 S40 ; move pen mount up to lift up the next tool and continue print
- M400
- G1 X30
- """,
- "4th axis meaning": " ",
- "4th axis scale": 1.,
- "4th axis offset": 0.,
- "passing feed":"4800",
- "fine feed":"1200",
- }
- elif self.options.tools_library_type == "cylinder cutter" :
- tool = {
- "name": "Cylindrical cutter",
- "id": "Cylindrical cutter 0001",
- "diameter":10,
- "penetration angle":90,
- "feed":"400",
- "penetration feed":"100",
- "depth step":"1",
- "tool change gcode":" "
- }
- elif self.options.tools_library_type == "lathe cutter" :
- tool = {
- "name": "Lathe cutter",
- "id": "Lathe cutter 0001",
- "diameter":10,
- "penetration angle":90,
- "feed":"400",
- "passing feed":"800",
- "fine feed":"100",
- "penetration feed":"100",
- "depth step":"1",
- "tool change gcode":" "
- }
- elif self.options.tools_library_type == "cone cutter":
- tool = {
- "name": "Cone cutter",
- "id": "Cone cutter 0001",
- "diameter":10,
- "shape":"w",
- "feed":"400",
- "penetration feed":"100",
- "depth step":"1",
- "tool change gcode":" "
- }
- elif self.options.tools_library_type == "tangent knife":
- tool = {
- "name": "Tangent knife",
- "id": "Tangent knife 0001",
- "feed":"400",
- "penetration feed":"100",
- "depth step":"100",
- "4th axis meaning": "tangent knife",
- "4th axis scale": 1.,
- "4th axis offset": 0,
- "tool change gcode":" "
- }
- elif self.options.tools_library_type == "plasma cutter":
- tool = {
- "name": "Plasma cutter",
- "id": "Plasma cutter 0001",
- "diameter":10,
- "penetration feed":100,
- "feed":400,
- "gcode before path":"""G31 Z-100 F500 (find metal)
- G92 Z0 (zero z)
- G00 Z10 F500 (going up)
- M03 (turn on plasma)
- G04 P0.2 (pause)
- G01 Z1 (going to cutting z)\n""",
- "gcode after path":"M05 (turn off plasma)\n",
- }
- elif self.options.tools_library_type == "graffiti":
- tool = {
- "name": "Graffiti",
- "id": "Graffiti 0001",
- "diameter":10,
- "penetration feed":100,
- "feed":400,
- "gcode before path":"""M03 S1(Turn spray on)\n """,
- "gcode after path":"M05 (Turn spray off)\n ",
- "tool change gcode":"(Add G00 here to change sprayer if needed)\n",
- }
- else :
- tool = self.default_tool
- tool_num = sum([len(self.tools[i]) for i in self.tools])
- colors = ["00ff00","0000ff","ff0000","fefe00","00fefe", "fe00fe", "fe7e00", "7efe00", "00fe7e", "007efe", "7e00fe", "fe007e"]
- tools_group = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools tool definition"})
- bg = inkex.etree.SubElement( tools_group, inkex.addNS('path','svg'),
- {'style': "fill:#%s;fill-opacity:0.5;stroke:#444444; stroke-width:1px;"%colors[tool_num%len(colors)], "gcodetools":"Gcodetools tool background"})
- y = 0
- keys = []
- for key in self.tools_field_order:
- if key in tool: keys += [key]
- for key in tool:
- if key not in keys: keys += [key]
- for key in keys :
- g = inkex.etree.SubElement(tools_group, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools tool parameter"})
- draw_text(key, 0, y, group = g, gcodetools_tag = "Gcodetools tool definition field name", font_size = 10 if key!='name' else 20)
- param = tool[key]
- if type(param)==str and re.match("^\s*$",param) : param = "(None)"
- draw_text(param, 150, y, group = g, gcodetools_tag = "Gcodetools tool definition field value", font_size = 10 if key!='name' else 20)
- v = str(param).split("\n")
- y += 15*len(v) if key!='name' else 20*len(v)
- bg.set('d',"m -20,-20 l 400,0 0,%f -400,0 z " % (y+50))
- tool = []
- tools_group.set("transform", simpletransform.formatTransform([ [1,0,self.view_center[0]-150 ], [0,1,self.view_center[1]] ] ))
- ################################################################################
- ###
- ### Check tools and OP asignment
- ###
- ################################################################################
- def check_tools_and_op(self):
- if len(self.selected)<=0 :
- self.error(_("Selection is empty! Will compute whole drawing."),"selection_is_empty_will_comupe_drawing")
- paths = self.paths
- else :
- paths = self.selected_paths
- # Set group
- group = inkex.etree.SubElement( self.selected_paths.keys()[0] if len(self.selected_paths.keys())>0 else self.layers[0], inkex.addNS('g','svg') )
- trans_ = [[1,0.3,0],[0,0.5,0]]
- self.set_markers()
- bounds = [float('inf'),float('inf'),float('-inf'),float('-inf')]
- tools_bounds = {}
- for layer in self.layers :
- if layer in paths :
- self.set_tool(layer)
- tool = self.tools[layer][0]
- tools_bounds[layer] = tools_bounds[layer] if layer in tools_bounds else [float("inf"),float("-inf")]
- style = simplestyle.formatStyle(tool["style"])
- for path in paths[layer] :
- style = "fill:%s; fill-opacity:%s; stroke:#000044; stroke-width:1; marker-mid:url(#CheckToolsAndOPMarker);" % (
- tool["style"]["fill"] if "fill" in tool["style"] else "#00ff00",
- tool["style"]["fill-opacity"] if "fill-opacity" in tool["style"] else "0.5")
- group.insert( 0, inkex.etree.Element(path.tag, path.attrib))
- new = group.getchildren()[0]
- new.set("style", style)
- trans = self.get_transforms(path)
- trans = simpletransform.composeTransform( trans_, trans if trans != [] else [[1.,0.,0.],[0.,1.,0.]])
- csp = cubicsuperpath.parsePath(path.get("d"))
- simpletransform.applyTransformToPath(trans,csp)
- path_bounds = csp_simple_bound(csp)
- trans = simpletransform.formatTransform(trans)
- 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])]
- tools_bounds[layer] = [min(tools_bounds[layer][0], path_bounds[1]), max(tools_bounds[layer][1], path_bounds[3])]
- new.set("transform", trans)
- trans_[1][2] += 20
- trans_[1][2] += 100
- for layer in self.layers :
- if layer in self.tools :
- if layer in tools_bounds :
- tool = self.tools[layer][0]
- g = copy.deepcopy(tool["self_group"])
- g.attrib["gcodetools"] = "Check tools and OP asignment"
- trans = [[1,0.3,bounds[2]],[0,0.5,tools_bounds[layer][0]]]
- g.set("transform",simpletransform.formatTransform(trans))
- group.insert( 0, g )
- ################################################################################
- ### TODO Launch browser on help tab
- ################################################################################
- def help(self):
- 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")
- return
- ################################################################################
- ### Lathe
- ################################################################################
- def generate_lathe_gcode(self, subpath, layer, feed_type) :
- if len(subpath) <2 : return ""
- feed = " F %f" % self.tool[feed_type]
- x,z = self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap
- flip_angle = -1 if x.lower()+z.lower() in ["xz", "yx", "zy"] else 1
- alias = {"X":"I", "Y":"J", "Z":"K", "x":"i", "y":"j", "z":"k"}
- i_, k_ = alias[x], alias[z]
- c = [ [subpath[0][1], "move", 0, 0, 0] ]
- #draw_csp(self.transform_csp([subpath],layer,True), color = "Orange", width = .1)
- for sp1,sp2 in zip(subpath,subpath[1:]) :
- c += biarc(sp1,sp2,0,0)
- for i in range(1,len(c)) : # Just in case check end point of each segment
- c[i-1][4] = c[i][0][:]
- c += [ [subpath[-1][1], "end", 0, 0, 0] ]
- self.draw_curve(c, layer, style = styles["biarc_style_lathe_%s" % feed_type])
- 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...
- for s in c :
- if s[1] == 'line':
- gcode += ("G01 %s %f %s %f" % (x, s[4][0], z, s[4][1]) ) + feed + "\n"
- elif s[1] == 'arc':
- r = [(s[2][0]-s[0][0]), (s[2][1]-s[0][1])]
- if (r[0]**2 + r[1]**2)>self.options.min_arc_radius**2:
- r1, r2 = (P(s[0])-P(s[2])), (P(s[4])-P(s[2]))
- if abs(r1.mag()-r2.mag()) < 0.001 :
- 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"
- else:
- r = (r1.mag()+r2.mag())/2
- 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"
- return gcode
- def lathe(self):
- if not self.check_dir() : return
- x,z = self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap
- x = re.sub("^\s*([XYZxyz])\s*$",r"\1",x)
- z = re.sub("^\s*([XYZxyz])\s*$",r"\1",z)
- if x not in ["X", "Y", "Z", "x", "y", "z"] or z not in ["X", "Y", "Z", "x", "y", "z"] :
- self.error(_("Lathe X and Z axis remap should be 'X', 'Y' or 'Z'. Exiting..."),"warning")
- return
- if x.lower() == z.lower() :
- self.error(_("Lathe X and Z axis remap should be the same. Exiting..."),"warning")
- return
- if x.lower()+z.lower() in ["xy","yx"] : gcode_plane_selection = "G17 (Using XY plane)\n"
- if x.lower()+z.lower() in ["xz","zx"] : gcode_plane_selection = "G18 (Using XZ plane)\n"
- if x.lower()+z.lower() in ["zy","yz"] : gcode_plane_selection = "G19 (Using YZ plane)\n"
- self.options.lathe_x_axis_remap, self.options.lathe_z_axis_remap = x, z
- paths = self.selected_paths
- self.tool = []
- gcode = ""
- for layer in self.layers :
- if layer in paths :
- self.set_tool(layer)
- if self.tool != self.tools[layer][0] :
- self.tool = self.tools[layer][0]
- self.tool["passing feed"] = float(self.tool["passing feed"] if "passing feed" in self.tool else self.tool["feed"])
- self.tool["feed"] = float(self.tool["feed"])
- self.tool["fine feed"] = float(self.tool["fine feed"] if "fine feed" in self.tool else self.tool["feed"])
- gcode += ( "(Change tool to %s)\n" % re.sub("\"'\(\)\\\\"," ",self.tool["name"]) ) + self.tool["tool change gcode"] + "\n"
- for path in paths[layer]:
- csp = self.transform_csp(cubicsuperpath.parsePath(path.get("d")),layer)
- for subpath in csp :
- # Offset the path if fine cut is defined.
- fine_cut = subpath[:]
- if self.options.lathe_fine_cut_width>0 :
- r = self.options.lathe_fine_cut_width
- if self.options.lathe_create_fine_cut_using == "Move path" :
- subpath = [ [ [i2[0],i2[1]+r] for i2 in i1] for i1 in subpath]
- else :
- # Close the path to make offset correct
- bound = csp_simple_bound([subpath])
- minx,miny,maxx,maxy = csp_true_bounds([subpath])
- 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] ] ])
- left,right = subpath[-1][1][0], subpath[0][1][0]
- if left>right : left, right = right,left
- offsetted_subpath = csp_offset([offsetted_subpath], r if not csp_subpath_ccw(offsetted_subpath) else -r )
- offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left,10], [left,0] )
- offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right,0], [right,10] )
- offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1]-r], [10, miny[1]-r] )
- #draw_csp(self.transform_csp(offsetted_subpath,layer,True), color = "Green", width = 1)
- # Join offsetted_subpath together
- # Hope there wont be any cicles
- subpath = csp_join_subpaths(offsetted_subpath)[0]
- # Create solid object from path and lathe_width
- bound = csp_simple_bound([subpath])
- 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]
- gcode += ("G01 %s %f F %f \n" % (z, top_start[1], self.tool["passing feed"]) )
- gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
- subpath = csp_concat_subpaths(csp_subpath_line_to([],[top_start,subpath[0][1]]), subpath)
- subpath = csp_subpath_line_to(subpath,[top_end,top_start])
- width = max(0, self.options.lathe_width - max(0, bound[1]) )
- step = self.tool['depth step']
- steps = int(math.ceil(width/step))
- for i in range(steps+1):
- current_width = self.options.lathe_width - step*i
- intersections = []
- for j in range(1,len(subpath)) :
- sp1,sp2 = subpath[j-1], subpath[j]
- intersections += [[j,k] for k in csp_line_intersection([bound[0]-10,current_width], [bound[2]+10,current_width], sp1, sp2)]
- intersections += [[j,k] for k in csp_line_intersection([bound[0]-10,current_width+step], [bound[2]+10,current_width+step], sp1, sp2)]
- parts = csp_subpath_split_by_points(subpath,intersections)
- for part in parts :
- minx,miny,maxx,maxy = csp_true_bounds([part])
- y = (maxy[1]+miny[1])/2
- if y > current_width+step :
- gcode += self.generate_lathe_gcode(part,layer,"passing feed")
- elif current_width <= y <= current_width+step :
- gcode += self.generate_lathe_gcode(part,layer,"feed")
- else :
- # full step cut
- part = csp_subpath_line_to([], [part[0][1], part[-1][1]] )
- gcode += self.generate_lathe_gcode(part,layer,"feed")
- 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]
- gcode += "\n(Fine cutting start)\n(Calculating fine cut using %s)\n"%self.options.lathe_create_fine_cut_using
- for i in range(self.options.lathe_fine_cut_count) :
- width = self.options.lathe_fine_cut_width*(1-float(i+1)/self.options.lathe_fine_cut_count )
- if width == 0 :
- current_pass = fine_cut
- else :
- if self.options.lathe_create_fine_cut_using == "Move path" :
- current_pass = [ [ [i2[0],i2[1]+width] for i2 in i1] for i1 in fine_cut]
- else :
- minx,miny,maxx,maxy = csp_true_bounds([fine_cut])
- 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] ] ])
- left,right = fine_cut[-1][1][0], fine_cut[0][1][0]
- if left>right : left, right = right,left
- offsetted_subpath = csp_offset([offsetted_subpath], width if not csp_subpath_ccw(offsetted_subpath) else -width )
- offsetted_subpath = csp_clip_by_line(offsetted_subpath, [left,10], [left,0] )
- offsetted_subpath = csp_clip_by_line(offsetted_subpath, [right,0], [right,10] )
- offsetted_subpath = csp_clip_by_line(offsetted_subpath, [0, miny[1]-r], [10, miny[1]-r] )
- current_pass = csp_join_subpaths(offsetted_subpath)[0]
- gcode += "\n(Fine cut %i-th cicle start)\n"%(i+1)
- gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
- 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"]) )
- 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"]) )
- gcode += self.generate_lathe_gcode(current_pass,layer,"fine feed")
- gcode += ("G01 %s %f F %f \n" % (z, top_start[1], self.tool["passing feed"]) )
- gcode += ("G01 %s %f %s %f F %f \n" % (x, top_start[0], z, top_start[1], self.tool["passing feed"]) )
- self.export_gcode(gcode)
- ################################################################################
- ###
- ### Lathe modify path
- ### Modifies path to fit current cutter. As for now straight rect cutter.
- ###
- ################################################################################
- def lathe_modify_path(self):
- if self.selected_paths == {} and self.options.auto_select_paths:
- paths=self.paths
- self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
- else :
- paths = self.selected_paths
- for layer in self.layers :
- if layer in paths :
- width = self.options.lathe_rectangular_cutter_width
- #self.set_tool(layer)
- for path in paths[layer]:
- csp = self.transform_csp(cubicsuperpath.parsePath(path.get("d")),layer)
- new_csp = []
- for subpath in csp:
- orientation = subpath[-1][1][0]>subpath[0][1][0]
- last_n = None
- last_o = 0
- new_subpath = []
- # Split segment at x' and y' == 0
- for sp1, sp2 in zip(subpath[:],subpath[1:]):
- ax,ay,bx,by,cx,cy,dx,dy = csp_parameterize(sp1,sp2)
- roots = cubic_solver_real(0, 3*ax, 2*bx, cx)
- roots += cubic_solver_real(0, 3*ay, 2*by, cy)
- new_subpath = csp_concat_subpaths(new_subpath, csp_seg_split(sp1,sp2,roots))
- subpath = new_subpath
- new_subpath = []
- first_seg = True
- for sp1, sp2 in zip(subpath[:],subpath[1:]):
- n = csp_normalized_normal(sp1,sp2,0)
- a = math.atan2(n[0],n[1])
- if a == 0 or a == math.pi :
- n = csp_normalized_normal(sp1,sp2,1)
- a = math.atan2(n[0],n[1])
- if a!=0 and a!=math.pi:
- o = 0 if 0<a<=math.pi/2 or -math.pi<a<-math.pi/2 else 1
- if not orientation: o = 1-o
- # Add first horisontal straight line if needed
- if not first_seg and new_subpath==[] : new_subpath = [ [[subpath[0][i][0] - width*o ,subpath[0][i][1]] for i in range(3)] ]
- new_subpath = csp_concat_subpaths(
- new_subpath,
- [
- [[sp1[i][0] - width*o ,sp1[i][1]] for i in range(3)],
- [[sp2[i][0] - width*o ,sp2[i][1]] for i in range(3)]
- ]
- )
- first_seg = False
- # Add last horisontal straigth line if needed
- if a==0 or a==math.pi :
- new_subpath += [ [[subpath[-1][i][0] - width*o ,subpath[-1][i][1]] for i in range(3)] ]
- new_csp += [new_subpath]
- self.draw_csp(new_csp,layer)
- #
- # o = (1 if cross(n, [0,1])>0 else -1)*orientation
- # new_subpath += [ [sp1[i][0] - width*o,sp1[i][1]] for i in range(3) ]
- # n = csp_normalized_normal(sp1,sp2,1)
- # o = (1 if cross(n, [0,1])>0 else -1)*orientation
- # new_subpath += [ [sp2[i][0] - width*o,sp2[i][1]] for i in range(3) ]
- ################################################################################
- ###
- ### Update function
- ###
- ### Gets file containing version information from the web and compaares it with.
- ### current version.
- ################################################################################
- def update(self) :
- try :
- import urllib
- f = urllib.urlopen("http://www.cnc-club.ru/gcodetools_latest_version", proxies = urllib.getproxies())
- a = f.read()
- for s in a.split("\n") :
- r = re.search(r"Gcodetools\s+latest\s+version\s*=\s*(.*)",s)
- if r :
- ver = r.group(1).strip()
- if ver != gcodetools_current_version :
- 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")
- else :
- self.error("You are currently using latest stable version of Gcodetools.","Warning")
- return
- 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")
- except :
- 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")
- ################################################################################
- ### Graffiti function generates Gcode for graffiti drawer
- ################################################################################
- def graffiti(self) :
- # Get reference points.
- def get_gcode_coordinates(point,layer):
- gcode = ''
- pos = []
- for ref_point in self.graffiti_reference_points[layer] :
- c = math.sqrt((point[0]-ref_point[0][0])**2 + (point[1]-ref_point[0][1])**2)
- gcode += " %s %f"%(ref_point[1], c)
- pos += [c]
- return pos, gcode
- def graffiti_preview_draw_point(x1,y1,color,radius=.5):
- self.graffiti_preview = self.graffiti_preview
- r,g,b,a_ = color
- for x in range(int(x1-1-math.ceil(radius)), int(x1+1+math.ceil(radius)+1)):
- for y in range(int(y1-1-math.ceil(radius)), int(y1+1+math.ceil(radius)+1)):
- if x>=0 and y>=0 and y<len(self.graffiti_preview) and x*4<len(self.graffiti_preview[0]) :
- d = math.sqrt( (x1-x)**2 +(y1-y)**2 )
- a = float(a_)*( max(0,(1-(d-radius))) if d>radius else 1 )/256
- self.graffiti_preview[y][x*4] = int(r*a + (1-a)*self.graffiti_preview[y][x*4])
- self.graffiti_preview[y][x*4+1] = int(g*a + (1-a)*self.graffiti_preview[y][x*4+1])
- self.graffiti_preview[y][x*4+2] = int(g*b + (1-a)*self.graffiti_preview[y][x*4+2])
- self.graffiti_preview[y][x*4+3] = min(255,int(self.graffiti_preview[y][x*4+3]+a*256))
- def graffiti_preview_transform(x,y):
- tr = self.graffiti_preview_transform
- d = max(tr[2]-tr[0]+2,tr[3]-tr[1]+2)
- 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]
- def draw_graffiti_segment(layer,start,end,feed,color=(0,255,0,40),emmit=1000):
- # Emit = dots per second
- l = math.sqrt(sum([(start[i]-end[i])**2 for i in range(len(start))]))
- time_ = l/feed
- c1,c2 = self.graffiti_reference_points[layer][0][0],self.graffiti_reference_points[layer][1][0]
- d = math.sqrt( (c1[0]-c2[0])**2 + (c1[1]-c2[1])**2 )
- if d == 0 : raise ValueError, "Error! Reference points should not be the same!"
- for i in range(int(time_*emmit+1)) :
- t = i/(time_*emmit)
- r1,r2 = start[0]*(1-t) + end[0]*t, start[1]*(1-t) + end[1]*t
- a = (r1**2-r2**2+d**2)/(2*d)
- h = math.sqrt(r1**2 - a**2)
- xa = c1[0] + a*(c2[0]-c1[0])/d
- ya = c1[1] + a*(c2[1]-c1[1])/d
- x1 = xa + h*(c2[1]-c1[1])/d
- x2 = xa - h*(c2[1]-c1[1])/d
- y1 = ya - h*(c2[0]-c1[0])/d
- y2 = ya + h*(c2[0]-c1[0])/d
- x = x1 if y1<y2 else x2
- y = min(y1,y2)
- x,y = graffiti_preview_transform(x,y)
- graffiti_preview_draw_point(x,y,color)
- def create_connector(p1,p2,t1,t2):
- P1,P2 = P(p1), P(p2)
- N1, N2 = P(rotate_ccw(t1)), P(rotate_ccw(t2))
- r = self.options.graffiti_min_radius
- C1,C2 = P1+N1*r, P2+N2*r
- # Get closest possible centers of arcs, also we define that arcs are both ccw or both not.
- dc, N1, N2, m = (
- (
- (((P2-N1*r) - (P1-N2*r)).l2(),-N1,-N2, 1)
- if vectors_ccw(t1,t2) else
- (((P2+N1*r) - (P1+N2*r)).l2(), N1, N2,-1)
- )
- if vectors_ccw((P1-C1).to_list(),t1) == vectors_ccw((P2-C2).to_list(),t2) else
- (
- (((P2+N1*r) - (P1-N2*r)).l2(), N1,-N2, 1)
- if vectors_ccw(t1,t2) else
- (((P2-N1*r) - (P1+N2*r)).l2(),-N1, N2, 1)
- )
- )
- dc = math.sqrt(dc)
- C1,C2 = P1+N1*r, P2+N2*r
- Dc = C2-C1
- if dc == 0 :
- # can be joined by one arc
- return csp_from_arc(p1, p2, C1.to_list(), r, t1)
- cos, sin = Dc.x/dc, Dc.y/dc
- #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;" )
- #draw_pointer(self.transform(C1.to_list(),layer,reverse=True))
- #draw_pointer(self.transform(C2.to_list(),layer,reverse=True))
- p1_end = [C1.x-r*sin*m,C1.y+r*cos*m]
- p2_st = [C2.x-r*sin*m,C2.y+r*cos*m]
- if point_to_point_d2(p1,p1_end)<0.0001 and point_to_point_d2(p2,p2_st)<0.0001 :
- return ([[p1,p1,p1],[p2,p2,p2]])
- arc1 = csp_from_arc(p1, p1_end, C1.to_list(), r, t1)
- arc2 = csp_from_arc(p2_st, p2, C2.to_list(), r, [cos,sin])
- return csp_concat_subpaths(arc1,arc2)
- if not self.check_dir() : return
- if self.selected_paths == {} and self.options.auto_select_paths:
- paths=self.paths
- self.error(_("No paths are selected! Trying to work on all available paths."),"warning")
- else :
- paths = self.selected_paths
- self.tool = []
- gcode = """(Header)
- (Generated by gcodetools from Inkscape.)
- (Using graffiti extension.)
- (Header end.)"""
- minx,miny,maxx,maxy = float("inf"),float("inf"),float("-inf"),float("-inf")
- # Get all reference points and path's bounds to make preview
- for layer in self.layers :
- if layer in paths :
- # Set reference points
- if layer not in self.graffiti_reference_points:
- reference_points = None
- for i in range(self.layers.index(layer),-1,-1):
- if self.layers[i] in self.graffiti_reference_points :
- reference_points = self.graffiti_reference_points[self.layers[i]]
- self.graffiti_reference_points[layer] = self.graffiti_reference_points[self.layers[i]]
- break
- if reference_points == None :
- self.error('There are no graffiti reference points for layer %s'%layer,"error")
- # Transform reference points
- for i in range(len(self.graffiti_reference_points[layer])):
- self.graffiti_reference_points[layer][i][0] = self.transform(self.graffiti_reference_points[layer][i][0], layer)
- point = self.graffiti_reference_points[layer][i]
- gcode += "(Reference point %f;%f for %s axis)\n"%(point[0][0],point[0][1],point[1])
- if self.options.graffiti_create_preview :
- for point in self.graffiti_reference_points[layer]:
- minx,miny,maxx,maxy = min(minx,point[0][0]), min(miny,point[0][1]), max(maxx,point[0][0]), max(maxy,point[0][1])
- for path in paths[layer]:
- csp = cubicsuperpath.parsePath(path.get("d"))
- csp = self.apply_transforms(path, csp)
- csp = self.transform_csp(csp, layer)
- bounds = csp_simple_bound(csp)
- minx,miny,maxx,maxy = min(minx,bounds[0]), min(miny,bounds[1]), max(maxx,bounds[2]), max(maxy,bounds[3])
- if self.options.graffiti_create_preview :
- self.graffiti_preview = list([ [255]*(4*self.options.graffiti_preview_size) for i in range(self.options.graffiti_preview_size)])
- self.graffiti_preview_transform = [minx,miny,maxx,maxy]
- for layer in self.layers :
- if layer in paths :
- r = re.match("\s*\(\s*([0-9\-,.]+)\s*;\s*([0-9\-,.]+)\s*\)\s*",self.options.graffiti_start_pos)
- if r :
- start_point = [float(r.group(1)),float(r.group(2))]
- else :
- start_point = [0.,0.]
- last_sp1 = [[start_point[0],start_point[1]-10] for i in range(3)]
- last_sp2 = [start_point for i in range(3)]
- self.set_tool(layer)
- self.tool = self.tools[layer][0]
- # Change tool every layer. (Probably layer = color so it'll be
- # better to change it even if the tool has not been changed)
- gcode += ( "(Change tool to %s)\n" % re.sub("\"'\(\)\\\\"," ",self.tool["name"]) ) + self.tool["tool change gcode"] + "\n"
- subpaths = []
- for path in paths[layer]:
- # Rebuild the paths to polyline.
- csp = cubicsuperpath.parsePath(path.get("d"))
- csp = self.apply_transforms(path, csp)
- csp = self.transform_csp(csp, layer)
- subpaths += csp
- polylines = []
- while len(subpaths)>0:
- i = min( [( point_to_point_d2(last_sp2[1],subpaths[i][0][1]),i) for i in range(len(subpaths))] )[1]
- subpath = subpaths[i][:]
- del subpaths[i]
- polylines += [
- ['connector', create_connector(
- last_sp2[1],
- subpath[0][1],
- csp_normalized_slope(last_sp1,last_sp2,1.),
- csp_normalized_slope(subpath[0],subpath[1],0.),
- )]
- ]
- polyline = []
- spl = None
- # remove zerro length segments
- i = 0
- while i<len(subpath)-1:
- if (cspseglength(subpath[i],subpath[i+1])<0.00000001 ) :
- subpath[i][2] = subpath[i+1][2]
- del subpath[i+1]
- else :
- i += 1
- for sp1, sp2 in zip(subpath,subpath[1:]) :
- 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
- # We've got sharp angle at sp1.
- polyline += [sp1]
- polylines += [['draw',polyline[:]]]
- polylines += [
- ['connector', create_connector(
- sp1[1],
- sp1[1],
- csp_normalized_slope(spl,sp1,1.),
- csp_normalized_slope(sp1,sp2,0.),
- )]
- ]
- polyline = []
- # max_segment_length
- polyline += [ sp1 ]
- print_(polyline)
- print_(sp1)
- spl = sp1
- polyline += [ sp2 ]
- polylines += [ ['draw',polyline[:]] ]
- last_sp1, last_sp2 = sp1,sp2
- # Add return to start_point
- if polylines == [] : continue
- polylines += [ ["connect1", [ [polylines[-1][1][-1][1] for i in range(3)],[start_point for i in range(3)] ] ] ]
- # Make polilynes from polylines. They are still csp.
- for i in range(len(polylines)) :
- polyline = []
- l = 0
- print_("polylines",polylines)
- print_(polylines[i])
- for sp1,sp2 in zip(polylines[i][1],polylines[i][1][1:]) :
- print_(sp1,sp2)
- l = cspseglength(sp1,sp2)
- if l>0.00000001 :
- polyline += [sp1[1]]
- parts = int(math.ceil(l/self.options.graffiti_max_seg_length))
- for j in range(1,parts):
- polyline += [csp_at_length(sp1,sp2,float(j)/parts) ]
- if l>0.00000001 :
- polyline += [sp2[1]]
- print_(i)
- polylines[i][1] = polyline
- t = 0
- last_state = None
- for polyline_ in polylines:
- polyline = polyline_[1]
- # Draw linearization
- if self.options.graffiti_create_linearization_preview :
- t += 1
- csp = [ [polyline[i],polyline[i],polyline[i]] for i in range(len(polyline))]
- draw_csp(self.transform_csp([csp],layer,reverse=True), color = "#00cc00;" if polyline_[0]=='draw' else "#ff5555;")
- # Export polyline to gcode
- # we are making trnsform from XYZA coordinates to R1...Rn
- # where R1...Rn are radius vectors from grafiti reference points
- # to current (x,y) point. Also we need to assign custom feed rate
- # for each segment. And we'll use only G01 gcode.
- last_real_pos, g = get_gcode_coordinates(polyline[0],layer)
- last_pos = polyline[0]
- if polyline_[0] == "draw" and last_state!="draw":
- gcode += self.tool['gcode before path']+"\n"
- for point in polyline :
- real_pos, g = get_gcode_coordinates(point,layer)
- real_l = sum([(real_pos[i]-last_real_pos[i])**2 for i in range(len(last_real_pos))])
- l = (last_pos[0]-point[0])**2 + (last_pos[1]-point[1])**2
- if l!=0:
- feed = self.tool['feed']*math.sqrt(real_l/l)
- gcode += "G01 " + g + " F %f\n"%feed
- if self.options.graffiti_create_preview :
- 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)
- last_real_pos = real_pos
- last_pos = point[:]
- if polyline_[0] == "draw" and last_state!="draw" :
- gcode += self.tool['gcode after path']+"\n"
- last_state = polyline_[0]
- self.export_gcode(gcode, no_headers=True)
- if self.options.graffiti_create_preview :
- try :
- # Draw reference points
- for layer in self.graffiti_reference_points:
- for point in self.graffiti_reference_points[layer] :
- x, y = graffiti_preview_transform(point[0][0],point[0][1])
- graffiti_preview_draw_point(x,y,(0,255,0,255),radius=5)
- import png
- 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)
- f = open(self.options.directory+self.options.file+".png", 'wb')
- writer.write(f,self.graffiti_preview)
- f.close()
- except :
- self.error("Png module have not been found!","warning")
- ################################################################################
- ###
- ### Effect
- ###
- ### Main function of Gcodetools class
- ###
- ################################################################################
- def effect(self) :
- start_time = time.time()
- global options
- options = self.options
- options.self = self
- options.doc_root = self.document.getroot()
- # define print_ function
- global print_
- if self.options.log_create_log :
- try :
- if os.path.isfile(self.options.log_filename) : os.remove(self.options.log_filename)
- f = open(self.options.log_filename,"a")
- f.write("Gcodetools log file.\nStarted at %s.\n%s\n" % (time.strftime("%d.%m.%Y %H:%M:%S"),options.log_filename))
- f.write("%s tab is active.\n" % self.options.active_tab)
- f.close()
- except :
- print_ = lambda *x : None
- else : print_ = lambda *x : None
- if self.options.active_tab == '"help"' :
- self.help()
- return
- elif self.options.active_tab == '"about"' :
- self.help()
- return
- elif self.options.active_tab == '"test"' :
- self.test()
- 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"']:
- 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")
- else:
- # Get all Gcodetools data from the scene.
- self.get_info()
- if self.options.active_tab in ['"dxfpoints"','"path-to-gcode"', '"area_fill"', '"area"', '"area_artefacts"', '"engraving"', '"lathe"', '"graffiti"', '"plasma-prepare-path"']:
- if self.orientation_points == {} :
- self.error(_("Orientation points have not been defined! A default set of orientation points has been automatically added."),"warning")
- self.orientation( self.layers[min(1,len(self.layers)-1)] )
- self.get_info()
- if self.tools == {} :
- self.error(_("Cutting tool has not been defined! A default tool has been automatically added."),"warning")
- self.options.tools_library_type = "default"
- self.tools_library( self.layers[min(1,len(self.layers)-1)] )
- self.get_info()
- if self.options.active_tab == '"path-to-gcode"':
- self.path_to_gcode()
- elif self.options.active_tab == '"area_fill"':
- self.area_fill()
- elif self.options.active_tab == '"area"':
- self.area()
- elif self.options.active_tab == '"area_artefacts"':
- self.area_artefacts()
- elif self.options.active_tab == '"dxfpoints"':
- self.dxfpoints()
- elif self.options.active_tab == '"engraving"':
- self.engraving()
- elif self.options.active_tab == '"orientation"':
- self.orientation()
- elif self.options.active_tab == '"graffiti"':
- self.graffiti()
- elif self.options.active_tab == '"tools_library"':
- if self.options.tools_library_type != "check":
- self.tools_library()
- else :
- self.check_tools_and_op()
- elif self.options.active_tab == '"lathe"':
- self.lathe()
- elif self.options.active_tab == '"lathe_modify_path"':
- self.lathe_modify_path()
- elif self.options.active_tab == '"update"':
- self.update()
- elif self.options.active_tab == '"offset"':
- if self.options.offset_just_get_distance :
- for layer in self.selected_paths :
- if len(self.selected_paths[layer]) == 2 :
- csp1, csp2 = cubicsuperpath.parsePath(self.selected_paths[layer][0].get("d")), cubicsuperpath.parsePath(self.selected_paths[layer][1].get("d"))
- dist = csp_to_csp_distance(csp1,csp2)
- print_(dist)
- draw_pointer( list(csp_at_t(csp1[dist[1]][dist[2]-1],csp1[dist[1]][dist[2]],dist[3]))
- +list(csp_at_t(csp2[dist[4]][dist[5]-1],csp2[dist[4]][dist[5]],dist[6])),"red","line", comment = math.sqrt(dist[0]))
- return
- if self.options.offset_step == 0 : self.options.offset_step = self.options.offset_radius
- if self.options.offset_step*self.options.offset_radius <0 : self.options.offset_step *= -1
- time_ = time.time()
- offsets_count = 0
- for layer in self.selected_paths :
- for path in self.selected_paths[layer] :
- offset = self.options.offset_step/2
- while abs(offset) <= abs(self.options.offset_radius) :
- offset_ = csp_offset(cubicsuperpath.parsePath(path.get("d")), offset)
- offsets_count += 1
- if offset_ != [] :
- for iii in offset_ :
- draw_csp([iii], color="Green", width=1)
- #print_(offset_)
- else :
- print_("------------Reached empty offset at radius %s"% offset )
- break
- offset += self.options.offset_step
- print_()
- print_("-----------------------------------------------------------------------------------")
- print_("-----------------------------------------------------------------------------------")
- print_("-----------------------------------------------------------------------------------")
- print_()
- print_("Done in %s"%(time.time()-time_))
- print_("Total offsets count %s"%offsets_count)
- elif self.options.active_tab == '"arrangement"':
- self.arrangement()
- elif self.options.active_tab == '"plasma-prepare-path"':
- self.plasma_prepare_path()
- print_("------------------------------------------")
- print_("Done in %f seconds"%(time.time()-start_time))
- print_("End at %s."%time.strftime("%d.%m.%Y %H:%M:%S"))
- #
- gcodetools = Gcodetools()
- gcodetools.affect()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement