diff options
| author | Martin Owens <doctormo@gmail.com> | 2018-09-09 08:28:15 +0000 |
|---|---|---|
| committer | Martin Owens <doctormo@gmail.com> | 2018-09-09 08:28:15 +0000 |
| commit | 36c73a704b281e616d779b1eac5430d7527a9338 (patch) | |
| tree | 189c5defb29728d428f44e17903bde11b89540a1 /share/extensions/gcodetools.py | |
| parent | Update selection-chemistry.cpp (diff) | |
| download | inkscape-36c73a704b281e616d779b1eac5430d7527a9338.tar.gz inkscape-36c73a704b281e616d779b1eac5430d7527a9338.zip | |
Move extensions to git inkscape-extensions repository
Diffstat (limited to 'share/extensions/gcodetools.py')
| -rwxr-xr-x | share/extensions/gcodetools.py | 6752 |
1 files changed, 0 insertions, 6752 deletions
diff --git a/share/extensions/gcodetools.py b/share/extensions/gcodetools.py deleted file mode 100755 index be471136e..000000000 --- a/share/extensions/gcodetools.py +++ /dev/null @@ -1,6752 +0,0 @@ -#!/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) -24 Jan 2017 Removed hard-coded scale values from orientation point calculation -TODO Change line division to be recursive, depending on what line is touched. See line_divide - - -engraving() functions (c) 2011 Chris Lusby Taylor, clusbytaylor@enterprise.net -Copyright (C) 2009 Nick Drobchenko, nick@cnc-club.ru -based on gcode.py (C) 2007 hugomatic... -based on addnodes.py (C) 2005,2007 Aaron Spike, aaron@ekips.org -based on dots.py (C) 2005 Aaron Spike, aaron@ekips.org -based on interp.py (C) 2005 Aaron Spike, aaron@ekips.org -based on bezmisc.py (C) 2005 Aaron Spike, aaron@ekips.org -based on cubicsuperpath.py (C) 2005 Aaron Spike, aaron@ekips.org - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 codecs -import random -# local library -import inkex -import simplestyle -import simplepath -import cubicsuperpath -import simpletransform -import bezmisc - -### 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")) - -try: - import numpy -except: - inkex.errormsg(_("Failed to import the numpy modules. These modules are required by this extension. Please install them and try again. On a Debian-like system this can be done with the command, sudo apt-get install python-numpy.")) - exit() - - -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 should 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 happened which is something that shouldn't 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 = str(s) - -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 - - - 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 dimensions - # 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_("Reduced 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="Distance from original path where engraving is not needed (usually 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='cylinder cutter', 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":100., - "depth step":1., - "feed":400., - "in trajectotry":"", - "out trajectotry":"", - "gcode before path":"", - "gcode after path":"", - "sog":"", - "spinlde rpm":"", - "CW or CCW":"", - "tool change gcode":" ", - "4th axis meaning": " ", - "4th axis scale": 1., - "4th axis offset": 0., - "passing feed":"800", - "fine feed":"800", - } - 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]) + "\n" + tool['gcode before path'] + "\n" - lg = 'G00' - elif s[1] == 'end': - g += go_to_safe_distance + tool['gcode after path'] + "\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 + tool['gcode after path'] + "\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 and 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 = str(s) - 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;stroke: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 beginning 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,4) #round to 4 decimals - y=round(y,4) #round to 4 decimals - w=round(w,4) #round to 4 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.]] - elif self.options.unit == "G20 (All units in inches)" : - points = [[0.,0.,self.options.Zsurface],[5.,0.,self.options.Zdepth],[0.,5.,0.]] - if self.options.orientation_points_count == "2" : - points = points[:2] - for i in points : - 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' % (i[0], -i[1]+doc_height), - 'gcodetools': "Gcodetools orientation point arrow" - }) - - draw_text("(%s; %s; %s)" % (i[0],i[1],i[2]), (i[0]+10), (-i[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 == "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() - |
