diff --git a/MI Inkscape Extension/README.md b/MI Inkscape Extension/README.md new file mode 100644 index 0000000..38e8572 --- /dev/null +++ b/MI Inkscape Extension/README.md @@ -0,0 +1,32 @@ +# MI-GRBL-Z-AXIS-Servo-Controller + +Upstream project: https://github.com/karlg100/MI-GRBL-Z-AXIS-Servo-Controller/tree/Inkscape-1.3 + +### Overview +This plug-in allows for servo up or down. Here is a description of the features of the plug-in. + +***This version/branch of the plugin was written for Inkscape 1.0.2 and updated for Python3 changes. This version allows for use with newer versions of Inkscape which are compatible with newer system architechtures. It is recommended to use this version of the plugin or newer.*** + +* Servo down: The command for move servo down. For example, M03 or M106. +* Servo up: The command for move servo up. For example, M05 or M107. +* X axis speed (mm/min): The speed of the X axis mm/min. +* Y axis speed (mm/min): The speed of the Y axis in mm/min. +* Angle for servo S# (0-180): If you have PWM control, then you can adjust this. For J Tech firmware and most 3D printers use a number between 0 and 255 (255 being full power). For GRBL 0.9 and 1 standard, use a number between 0 and 12000 (12000 being full power). If you don’t have PWM, keep at max power (either 255 or 12000). +* Delay (s): This will turn on the laser and wait to move until the delay is complete. It is used to heat up the material and initiate the burning process. Delay in ms for 3D printers and seconds for GRBL. +* Directory: The directory to store the file. +Filename: Name of the file. +* Add numeric suffix to filename: Adds a number to the name in case there already is a file with the same name in the directory. +Live preview: Shows the path being generated. +* Apply: Click to run the gcode generator. + + +``` +M3 S255 (turn servo full on) +M5 (turn servo off) +M3 S125 (turn servo half way) +M3 S0 (turn servo on full off - similar to M5) +``` + +### Installation + +To manually install a new extension, download (from the releases section of this project) and unpack the `servo.inx` and `servo.py` files. Copy the files into the directory listed at `Edit` > `Preferences` > `System: User extensions`. After a restart of Inkscape, the new extension will be available. diff --git a/MI Inkscape Extension/dxf_input.inx b/MI Inkscape Extension/dxf_input.inx deleted file mode 100644 index 1102d03..0000000 --- a/MI Inkscape Extension/dxf_input.inx +++ /dev/null @@ -1,37 +0,0 @@ - - - <_name>DXF Input - org.inkscape.input.dxf - dxf_input.py - inkex.py - - - true - 1.0 - false - ------------------------------------------------------------------------- - - Latin 1 - CP 1250 - CP 1252 - UTF 8 - - - - <_param name="inputhelp" type="description" xml:space="preserve">- AutoCAD Release 13 and newer. -- assume dxf drawing is in mm. -- assume svg drawing is in pixels, at 90 dpi. -- layers are preserved only on File->Open, not Import. -- limited support for BLOCKS, use AutoCAD Explode Blocks instead, if needed. - - - - .dxf - image/x-svgz - <_filetypename>AutoCAD DXF R13 (*.dxf) - <_filetypetooltip>Import AutoCAD's Document Exchange Format - - - diff --git a/MI Inkscape Extension/dxf_input.py b/MI Inkscape Extension/dxf_input.py deleted file mode 100644 index 742ed4a..0000000 --- a/MI Inkscape Extension/dxf_input.py +++ /dev/null @@ -1,457 +0,0 @@ -#!/usr/bin/env python -''' -dxf_input.py - input a DXF file >= (AutoCAD Release 13 == AC1012) - -Copyright (C) 2008, 2009 Alvin Penner, penner@vaxxine.com -Copyright (C) 2009 Christian Mayer, inkscape@christianmayer.de -- thanks to Aaron Spike for inkex.py and simplestyle.py -- without which this would not have been possible - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -''' - -import inkex, simplestyle, math -from StringIO import StringIO -from urllib import quote - -def export_MTEXT(): - # mandatory group codes : (1 or 3, 10, 20) (text, x, y) - if (vals[groups['1']] or vals[groups['3']]) and vals[groups['10']] and vals[groups['20']]: - x = vals[groups['10']][0] - y = vals[groups['20']][0] - # optional group codes : (21, 40, 50) (direction, text height mm, text angle) - size = 12 # default fontsize in px - if vals[groups['40']]: - size = scale*vals[groups['40']][0] - attribs = {'x': '%f' % x, 'y': '%f' % y, 'style': 'font-size: %.1fpx; fill: %s' % (size, color)} - angle = 0 # default angle in degrees - if vals[groups['50']]: - angle = vals[groups['50']][0] - attribs.update({'transform': 'rotate (%f %f %f)' % (-angle, x, y)}) - elif vals[groups['21']]: - if vals[groups['21']][0] == 1.0: - attribs.update({'transform': 'rotate (%f %f %f)' % (-90, x, y)}) - elif vals[groups['21']][0] == -1.0: - attribs.update({'transform': 'rotate (%f %f %f)' % (90, x, y)}) - attribs.update({inkex.addNS('linespacing','sodipodi'): '125%'}) - node = inkex.etree.SubElement(layer, 'text', attribs) - text = '' - if vals[groups['3']]: - for i in range (0, len(vals[groups['3']])): - text += vals[groups['3']][i] - if vals[groups['1']]: - text += vals[groups['1']][0] - found = text.find('\P') # new line - while found > -1: - tspan = inkex.etree.SubElement(node , 'tspan', {inkex.addNS('role','sodipodi'): 'line'}) - tspan.text = text[:found] - text = text[(found+2):] - found = text.find('\P') - tspan = inkex.etree.SubElement(node , 'tspan', {inkex.addNS('role','sodipodi'): 'line'}) - tspan.text = text - -def export_POINT(): - # mandatory group codes : (10, 20) (x, y) - if vals[groups['10']] and vals[groups['20']]: - if options.gcodetoolspoints: - generate_gcodetools_point(vals[groups['10']][0], vals[groups['20']][0]) - else: - generate_ellipse(vals[groups['10']][0], vals[groups['20']][0], w/2, 0.0, 1.0, 0.0, 0.0) - -def export_LINE(): - # mandatory group codes : (10, 11, 20, 21) (x1, x2, y1, y2) - if vals[groups['10']] and vals[groups['11']] and vals[groups['20']] and vals[groups['21']]: - path = 'M %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], scale*(vals[groups['11']][0] - xmin), - scale*(vals[groups['21']][0] - ymax)) - attribs = {'d': path, 'style': style} - inkex.etree.SubElement(layer, 'path', attribs) - -def export_SPLINE(): - # mandatory group codes : (10, 20, 70) (x, y, flags) - if vals[groups['10']] and vals[groups['20']] and vals[groups['70']]: - if not (vals[groups['70']][0] & 3) and len(vals[groups['10']]) == 4 and len(vals[groups['20']]) == 4: - path = 'M %f,%f C %f,%f %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], vals[groups['10']][1], vals[groups['20']][1], vals[groups['10']][2], vals[groups['20']][2], vals[groups['10']][3], vals[groups['20']][3]) - attribs = {'d': path, 'style': style} - inkex.etree.SubElement(layer, 'path', attribs) - if not (vals[groups['70']][0] & 3) and len(vals[groups['10']]) == 3 and len(vals[groups['20']]) == 3: - path = 'M %f,%f Q %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], vals[groups['10']][1], vals[groups['20']][1], vals[groups['10']][2], vals[groups['20']][2]) - attribs = {'d': path, 'style': style} - inkex.etree.SubElement(layer, 'path', attribs) - -def export_CIRCLE(): - # mandatory group codes : (10, 20, 40) (x, y, radius) - if vals[groups['10']] and vals[groups['20']] and vals[groups['40']]: - generate_ellipse(vals[groups['10']][0], vals[groups['20']][0], scale*vals[groups['40']][0], 0.0, 1.0, 0.0, 0.0) - -def export_ARC(): - # mandatory group codes : (10, 20, 40, 50, 51) (x, y, radius, angle1, angle2) - if vals[groups['10']] and vals[groups['20']] and vals[groups['40']] and vals[groups['50']] and vals[groups['51']]: - generate_ellipse(vals[groups['10']][0], vals[groups['20']][0], scale*vals[groups['40']][0], 0.0, 1.0, vals[groups['50']][0]*math.pi/180.0, vals[groups['51']][0]*math.pi/180.0) - -def export_ELLIPSE(): - # mandatory group codes : (10, 11, 20, 21, 40, 41, 42) (xc, xm, yc, ym, width ratio, angle1, angle2) - if vals[groups['10']] and vals[groups['11']] and vals[groups['20']] and vals[groups['21']] and vals[groups['40']] and vals[groups['41']] and vals[groups['42']]: - generate_ellipse(vals[groups['10']][0], vals[groups['20']][0], scale*vals[groups['11']][0], scale*vals[groups['21']][0], vals[groups['40']][0], vals[groups['41']][0], vals[groups['42']][0]) - -def export_LEADER(): - # mandatory group codes : (10, 20) (x, y) - if vals[groups['10']] and vals[groups['20']]: - if len(vals[groups['10']]) > 1 and len(vals[groups['20']]) == len(vals[groups['10']]): - path = 'M %f,%f' % (vals[groups['10']][0], vals[groups['20']][0]) - for i in range (1, len(vals[groups['10']])): - path += ' %f,%f' % (vals[groups['10']][i], vals[groups['20']][i]) - attribs = {'d': path, 'style': style} - inkex.etree.SubElement(layer, 'path', attribs) - -def export_LWPOLYLINE(): - # mandatory group codes : (10, 20, 70) (x, y, flags) - if vals[groups['10']] and vals[groups['20']] and vals[groups['70']]: - if len(vals[groups['10']]) > 1 and len(vals[groups['20']]) == len(vals[groups['10']]): - a=seqs - if (seqs[-2]=='42' or seqs[-1]=='42') and vals[groups['70']][0]==1: - if seqs[-1]=='42': - a=seqs - a.append("10") - a.append("20") - else: - a=seqs[0:-1] - a.append("10") - a.append("20") - a.append(seqs[-1]) - vals[groups['10']].append(vals[groups['10']][0]) - vals[groups['20']].append(vals[groups['20']][0]) - # optional group codes : (42) (bulge) - iseqs = 0 - ibulge = 0 - while a[iseqs] != '20': - iseqs += 1 - path = 'M %f,%f' % (vals[groups['10']][0], vals[groups['20']][0]) - xold = vals[groups['10']][0] - yold = vals[groups['20']][0] - for i in range (1, len(vals[groups['10']])): - bulge = 0 - iseqs += 1 - while a[iseqs] != '20': - if a[iseqs] == '42': - bulge = vals[groups['42']][ibulge] - ibulge += 1 - iseqs += 1 - if bulge: - sweep = 0 # sweep CCW - if bulge < 0: - sweep = 1 # sweep CW - bulge = -bulge - large = 0 # large-arc-flag - if bulge > 1: - large = 1 - r = math.sqrt((vals[groups['10']][i] - xold)**2 + (vals[groups['20']][i] - yold)**2) - r = 0.25*r*(bulge + 1.0/bulge) - path += ' A %f,%f 0.0 %d %d %f,%f' % (r, r, large, sweep, vals[groups['10']][i], vals[groups['20']][i]) - else: - path += ' L %f,%f' % (vals[groups['10']][i], vals[groups['20']][i]) - xold = vals[groups['10']][i] - yold = vals[groups['20']][i] - if vals[groups['70']][0] == 1: # closed path - path += ' z' - attribs = {'d': path, 'style': style} - inkex.etree.SubElement(layer, 'path', attribs) - -def export_HATCH(): - # mandatory group codes : (10, 20, 70, 72, 92, 93) (x, y, fill, Edge Type, Path Type, Number of edges) - if vals[groups['10']] and vals[groups['20']] and vals[groups['70']] and vals[groups['72']] and vals[groups['92']] and vals[groups['93']]: - if vals[groups['70']][0] and len(vals[groups['10']]) > 1 and len(vals[groups['20']]) == len(vals[groups['10']]): - # optional group codes : (11, 21, 40, 50, 51, 73) (x, y, r, angle1, angle2, CCW) - i10 = 1 # count start points - i11 = 0 # count line end points - i40 = 0 # count circles - i72 = 0 # count edge type flags - path = '' - for i in range (0, len(vals[groups['93']])): - xc = vals[groups['10']][i10] - yc = vals[groups['20']][i10] - if vals[groups['72']][i72] == 2: # arc - rm = scale*vals[groups['40']][i40] - a1 = vals[groups['50']][i40] - path += 'M %f,%f ' % (xc + rm*math.cos(a1*math.pi/180.0), yc + rm*math.sin(a1*math.pi/180.0)) - else: - a1 = 0 - path += 'M %f,%f ' % (xc, yc) - for j in range(0, vals[groups['93']][i]): - if vals[groups['92']][i] & 2: # polyline - if j > 0: - path += 'L %f,%f ' % (vals[groups['10']][i10], vals[groups['20']][i10]) - if j == vals[groups['93']][i] - 1: - i72 += 1 - elif vals[groups['72']][i72] == 2: # arc - xc = vals[groups['10']][i10] - yc = vals[groups['20']][i10] - rm = scale*vals[groups['40']][i40] - a2 = vals[groups['51']][i40] - diff = (a2 - a1 + 360) % (360) - sweep = 1 - vals[groups['73']][i40] # sweep CCW - large = 0 # large-arc-flag - if diff: - path += 'A %f,%f 0.0 %d %d %f,%f ' % (rm, rm, large, sweep, xc + rm*math.cos(a2*math.pi/180.0), yc + rm*math.sin(a2*math.pi/180.0)) - else: - path += 'A %f,%f 0.0 %d %d %f,%f ' % (rm, rm, large, sweep, xc + rm*math.cos((a1+180.0)*math.pi/180.0), yc + rm*math.sin((a1+180.0)*math.pi/180.0)) - path += 'A %f,%f 0.0 %d %d %f,%f ' % (rm, rm, large, sweep, xc + rm*math.cos(a1*math.pi/180.0), yc + rm*math.sin(a1*math.pi/180.0)) - i40 += 1 - i72 += 1 - elif vals[groups['72']][i72] == 1: # line - path += 'L %f,%f ' % (scale*(vals[groups['11']][i11] - xmin), -scale*(vals[groups['21']][i11] - ymax)) - i11 += 1 - i72 += 1 - i10 += 1 - path += "z " - style = simplestyle.formatStyle({'fill': '%s' % color}) - attribs = {'d': path, 'style': style} - inkex.etree.SubElement(layer, 'path', attribs) - -def export_DIMENSION(): - # mandatory group codes : (10, 11, 13, 14, 20, 21, 23, 24) (x1..4, y1..4) - if vals[groups['10']] and vals[groups['11']] and vals[groups['13']] and vals[groups['14']] and vals[groups['20']] and vals[groups['21']] and vals[groups['23']] and vals[groups['24']]: - dx = abs(vals[groups['10']][0] - vals[groups['13']][0]) - dy = abs(vals[groups['20']][0] - vals[groups['23']][0]) - if (vals[groups['10']][0] == vals[groups['14']][0]) and dx > 0.00001: - d = dx/scale - dy = 0 - path = 'M %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], vals[groups['13']][0], vals[groups['20']][0]) - elif (vals[groups['20']][0] == vals[groups['24']][0]) and dy > 0.00001: - d = dy/scale - dx = 0 - path = 'M %f,%f %f,%f' % (vals[groups['10']][0], vals[groups['20']][0], vals[groups['10']][0], vals[groups['23']][0]) - else: - return - attribs = {'d': path, 'style': style + '; marker-start: url(#DistanceX); marker-end: url(#DistanceX)'} - inkex.etree.SubElement(layer, 'path', attribs) - x = scale*(vals[groups['11']][0] - xmin) - y = - scale*(vals[groups['21']][0] - ymax) - size = 12 # default fontsize in px - if vals[groups['3']]: - if DIMTXT.has_key(vals[groups['3']][0]): - size = scale*DIMTXT[vals[groups['3']][0]] - if size < 2: - size = 2 - attribs = {'x': '%f' % x, 'y': '%f' % y, 'style': 'font-size: %.1fpx; fill: %s' % (size, color)} - if dx == 0: - attribs.update({'transform': 'rotate (%f %f %f)' % (-90, x, y)}) - node = inkex.etree.SubElement(layer, 'text', attribs) - tspan = inkex.etree.SubElement(node , 'tspan', {inkex.addNS('role','sodipodi'): 'line'}) - tspan.text = str(float('%.2f' % d)) - -def export_INSERT(): - # mandatory group codes : (2, 10, 20) (block name, x, y) - if vals[groups['2']] and vals[groups['10']] and vals[groups['20']]: - x = vals[groups['10']][0] - y = vals[groups['20']][0] - scale*ymax - attribs = {'x': '%f' % x, 'y': '%f' % y, inkex.addNS('href','xlink'): '#' + quote(vals[groups['2']][0].encode("utf-8"))} - inkex.etree.SubElement(layer, 'use', attribs) - -def export_BLOCK(): - # mandatory group codes : (2) (block name) - if vals[groups['2']]: - global block - block = inkex.etree.SubElement(defs, 'symbol', {'id': vals[groups['2']][0]}) - -def export_ENDBLK(): - global block - block = defs # initiallize with dummy - -def export_ATTDEF(): - # mandatory group codes : (1, 2) (default, tag) - if vals[groups['1']] and vals[groups['2']]: - vals[groups['1']][0] = vals[groups['2']][0] - export_MTEXT() - -def generate_ellipse(xc, yc, xm, ym, w, a1, a2): - rm = math.sqrt(xm*xm + ym*ym) - a = math.atan2(ym, xm) - diff = (a2 - a1 + 2*math.pi) % (2*math.pi) - if abs(diff) > 0.0000001 and abs(diff - 2*math.pi) > 0.0000001: # open arc - large = 0 # large-arc-flag - if diff > math.pi: - large = 1 - xt = rm*math.cos(a1) - yt = w*rm*math.sin(a1) - x1 = xt*math.cos(a) - yt*math.sin(a) - y1 = xt*math.sin(a) + yt*math.cos(a) - xt = rm*math.cos(a2) - yt = w*rm*math.sin(a2) - x2 = xt*math.cos(a) - yt*math.sin(a) - y2 = xt*math.sin(a) + yt*math.cos(a) - path = 'M %f,%f A %f,%f %f %d 0 %f,%f' % (xc+x1, yc-y1, rm, w*rm, -180.0*a/math.pi, large, xc+x2, yc-y2) - else: # closed arc - path = 'M %f,%f A %f,%f %f 1 0 %f,%f %f,%f %f 1 0 %f,%f z' % (xc+xm, yc-ym, rm, w*rm, -180.0*a/math.pi, xc-xm, yc+ym, rm, w*rm, -180.0*a/math.pi, xc+xm, yc-ym) - attribs = {'d': path, 'style': style} - inkex.etree.SubElement(layer, 'path', attribs) - -def generate_gcodetools_point(xc, yc): - path= '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' % (xc,yc) - attribs = {'d': path, 'dxfpoint':'1', 'style': 'stroke:#ff0000;fill:#ff0000'} - inkex.etree.SubElement(layer, 'path', attribs) - -def get_line(): - return (stream.readline().strip(), stream.readline().strip()) - -def get_group(group): - line = get_line() - if line[0] == group: - return float(line[1]) - else: - return 0.0 - -# define DXF Entities and specify which Group Codes to monitor - -entities = {'MTEXT': export_MTEXT, 'TEXT': export_MTEXT, 'POINT': export_POINT, 'LINE': export_LINE, 'SPLINE': export_SPLINE, 'CIRCLE': export_CIRCLE, 'ARC': export_ARC, 'ELLIPSE': export_ELLIPSE, 'LEADER': export_LEADER, 'LWPOLYLINE': export_LWPOLYLINE, 'HATCH': export_HATCH, 'DIMENSION': export_DIMENSION, 'INSERT': export_INSERT, 'BLOCK': export_BLOCK, 'ENDBLK': export_ENDBLK, 'ATTDEF': export_ATTDEF, 'DICTIONARY': False} -groups = {'1': 0, '2': 1, '3': 2, '6': 3, '8': 4, '10': 5, '11': 6, '13': 7, '14': 8, '20': 9, '21': 10, '23': 11, '24': 12, '40': 13, '41': 14, '42': 15, '50': 16, '51': 17, '62': 18, '70': 19, '72': 20, '73': 21, '92': 22, '93': 23, '370': 24} -colors = { 1: '#FF0000', 2: '#FFFF00', 3: '#00FF00', 4: '#00FFFF', 5: '#0000FF', - 6: '#FF00FF', 8: '#414141', 9: '#808080', 12: '#BD0000', 30: '#FF7F00', - 250: '#333333', 251: '#505050', 252: '#696969', 253: '#828282', 254: '#BEBEBE', 255: '#FFFFFF'} - -parser = inkex.optparse.OptionParser(usage="usage: %prog [options] SVGfile", option_class=inkex.InkOption) -parser.add_option("--auto", action="store", type="inkbool", dest="auto", default=True) -parser.add_option("--scale", action="store", type="string", dest="scale", default="1.0") -parser.add_option("--gcodetoolspoints", action="store", type="inkbool", dest="gcodetoolspoints", default=True) -parser.add_option("--encoding", action="store", type="string", dest="input_encode", default="latin_1") -parser.add_option("--tab", action="store", type="string", dest="tab", default="Options") -parser.add_option("--inputhelp", action="store", type="string", dest="inputhelp", default="") -(options, args) = parser.parse_args(inkex.sys.argv[1:]) -doc = inkex.etree.parse(StringIO('')) -desc = inkex.etree.SubElement(doc.getroot(), 'desc', {}) -defs = inkex.etree.SubElement(doc.getroot(), 'defs', {}) -marker = inkex.etree.SubElement(defs, 'marker', {'id': 'DistanceX', 'orient': 'auto', 'refX': '0.0', 'refY': '0.0', 'style': 'overflow:visible'}) -inkex.etree.SubElement(marker, 'path', {'d': 'M 3,-3 L -3,3 M 0,-5 L 0,5', 'style': 'stroke:#000000; stroke-width:0.5'}) -stream = open(args[0], 'r') -xmax = xmin = 0.0 -ymax = 297.0 # default A4 height in mm -line = get_line() -flag = 0 # (0, 1, 2, 3) = (none, LAYER, LTYPE, DIMTXT) -layer_colors = {} # store colors by layer -layer_nodes = {} # store nodes by layer -linetypes = {} # store linetypes by name -DIMTXT = {} # store DIMENSION text sizes - -while line[0] and line[1] != 'BLOCKS': - line = get_line() - if options.auto: - if line[1] == '$EXTMIN': - xmin = get_group('10') - if line[1] == '$EXTMAX': - xmax = get_group('10') - ymax = get_group('20') - if flag == 1 and line[0] == '2': - layername = unicode(line[1], options.input_encode) - attribs = {inkex.addNS('groupmode','inkscape'): 'layer', inkex.addNS('label','inkscape'): '%s' % layername} - layer_nodes[layername] = inkex.etree.SubElement(doc.getroot(), 'g', attribs) - if flag == 2 and line[0] == '2': - linename = unicode(line[1], options.input_encode) - linetypes[linename] = [] - if flag == 3 and line[0] == '2': - stylename = unicode(line[1], options.input_encode) - if line[0] == '2' and line[1] == 'LAYER': - flag = 1 - if line[0] == '2' and line[1] == 'LTYPE': - flag = 2 - if line[0] == '2' and line[1] == 'DIMSTYLE': - flag = 3 - if flag == 1 and line[0] == '62': - layer_colors[layername] = int(line[1]) - if flag == 2 and line[0] == '49': - linetypes[linename].append(float(line[1])) - if flag == 3 and line[0] == '140': - DIMTXT[stylename] = float(line[1]) - if line[0] == '0' and line[1] == 'ENDTAB': - flag = 0 - -if options.auto: - scale = 1.0 - if xmax > xmin: - scale = 210.0/(xmax - xmin) # scale to A4 width -else: - scale = float(options.scale) # manual scale factor -desc.text = '%s - scale = %f' % (unicode(args[0], options.input_encode), scale) -scale *= 90.0/25.4 # convert from mm to pixels - -if not layer_nodes: - attribs = {inkex.addNS('groupmode','inkscape'): 'layer', inkex.addNS('label','inkscape'): '0'} - layer_nodes['0'] = inkex.etree.SubElement(doc.getroot(), 'g', attribs) - layer_colors['0'] = 7 - -for linename in linetypes.keys(): # scale the dashed lines - linetype = '' - for length in linetypes[linename]: - linetype += '%.4f,' % math.fabs(length*scale) - linetypes[linename] = 'stroke-dasharray:' + linetype - -entity = '' -block = defs # initiallize with dummy -while line[0] and line[1] != 'DICTIONARY': - line = get_line() - if entity and groups.has_key(line[0]): - seqs.append(line[0]) # list of group codes - if line[0] == '1' or line[0] == '2' or line[0] == '3' or line[0] == '6' or line[0] == '8': # text value - val = line[1].replace('\~', ' ') - val = inkex.re.sub( '\\\\A.*;', '', val) - val = inkex.re.sub( '\\\\H.*;', '', val) - val = inkex.re.sub( '\\^I', '', val) - val = inkex.re.sub( '{\\\\L', '', val) - val = inkex.re.sub( '}', '', val) - val = inkex.re.sub( '\\\\S.*;', '', val) - val = inkex.re.sub( '\\\\W.*;', '', val) - val = unicode(val, options.input_encode) - val = val.encode('unicode_escape') - val = inkex.re.sub( '\\\\\\\\U\+([0-9A-Fa-f]{4})', '\\u\\1', val) - val = val.decode('unicode_escape') - elif line[0] == '62' or line[0] == '70' or line[0] == '92' or line[0] == '93': - val = int(line[1]) - elif line[0] == '10' or line[0] == '13' or line[0] == '14': # scaled float x value - val = scale*(float(line[1]) - xmin) - elif line[0] == '20' or line[0] == '23' or line[0] == '24': # scaled float y value - val = - scale*(float(line[1]) - ymax) - else: # unscaled float value - val = float(line[1]) - vals[groups[line[0]]].append(val) - elif entities.has_key(line[1]): - if entities.has_key(entity): - if block != defs: # in a BLOCK - layer = block - elif vals[groups['8']]: # use Common Layer Name - layer = layer_nodes[vals[groups['8']][0]] - color = '#000000' # default color - if vals[groups['8']]: - if layer_colors.has_key(vals[groups['8']][0]): - if colors.has_key(layer_colors[vals[groups['8']][0]]): - color = colors[layer_colors[vals[groups['8']][0]]] - if vals[groups['62']]: # Common Color Number - if colors.has_key(vals[groups['62']][0]): - color = colors[vals[groups['62']][0]] - style = simplestyle.formatStyle({'stroke': '%s' % color, 'fill': 'none'}) - w = 0.5 # default lineweight for POINT - if vals[groups['370']]: # Common Lineweight - if vals[groups['370']][0] > 0: - w = 90.0/25.4*vals[groups['370']][0]/00.0 - if w < 0.5: - w = 0.5 - style = simplestyle.formatStyle({'stroke': '%s' % color, 'fill': 'none', 'stroke-width': '%.1f' % w}) - if vals[groups['6']]: # Common Linetype - if linetypes.has_key(vals[groups['6']][0]): - style += ';' + linetypes[vals[groups['6']][0]] - entities[entity]() - entity = line[1] - vals = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]] - seqs = [] - -doc.write(inkex.sys.stdout) - -# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99 diff --git a/MI Inkscape Extension/servo.inx b/MI Inkscape Extension/servo.inx index 45dc370..a078b7f 100644 --- a/MI Inkscape Extension/servo.inx +++ b/MI Inkscape Extension/servo.inx @@ -1,17 +1,16 @@  - <_name>MI GRBL Z-AXIS Servo Controller - youtube.com/c/mrinnovative + MI GRBL Z-AXIS Servo Controller + https://github.com/ikae servo.py - inkex.py - M3 - M5 - 1000 - 500 - 90 - 1 - C:\Users\Public + M3 + M5 + 1000 + 500 + 90 + 1 + servo.gcode true diff --git a/MI Inkscape Extension/servo.py b/MI Inkscape Extension/servo.py index 21023d2..aa574d3 100644 --- a/MI Inkscape Extension/servo.py +++ b/MI Inkscape Extension/servo.py @@ -1,3172 +1,3190 @@ -#!/usr/bin/env python -""" -Modified by Eletronica Hoje 2016, youtube.com/eletronicahoje -Modified by Jay Johnson 2015, J Tech Photonics, Inc., jtechphotonics.com -modified by Adam Polak 2014, polakiumengineering.org - -based on 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -""" -import inkex, simplestyle, simplepath -import cubicsuperpath, simpletransform, bezmisc - -import os -import math -import bezmisc -import re -import copy -import sys -import time -import cmath -import numpy -import codecs -import random -import gettext -_ = gettext.gettext - - -### Check if inkex has errormsg (0.46 version doesnot have one.) Could be removed later. -if "errormsg" not in dir(inkex): - inkex.errormsg = lambda msg: sys.stderr.write((unicode(msg) + "\n").encode("UTF-8")) - - -def bezierslopeatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t): - ax,ay,bx,by,cx,cy,x0,y0=bezmisc.bezierparameterize(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3))) - dx=3*ax*(t**2)+2*bx*t+cx - dy=3*ay*(t**2)+2*by*t+cy - if dx==dy==0 : - dx = 6*ax*t+2*bx - dy = 6*ay*t+2*by - if dx==dy==0 : - dx = 6*ax - dy = 6*ay - if dx==dy==0 : - print_("Slope error x = %s*t^3+%s*t^2+%s*t+%s, y = %s*t^3+%s*t^2+%s*t+%s, t = %s, dx==dy==0" % (ax,bx,cx,dx,ay,by,cy,dy,t)) - print_(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3))) - dx, dy = 1, 1 - - return dx,dy -bezmisc.bezierslopeatt = bezierslopeatt - - -def ireplace(self,old,new,count=0): - pattern = re.compile(re.escape(old),re.I) - return re.sub(pattern,new,self,count) - -################################################################################ -### -### 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 -options = {} -defaults = { -'header': """G90 -""", -'footer': """G1 X0 Y0 -""" -} - -intersection_recursion_depth = 10 -intersection_tolerance = 0.00001 - -styles = { - "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"}), - - } - - -################################################################################ -### Cubic Super Path additional functions -################################################################################ - -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]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 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_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.001): - 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]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_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 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))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 - for i in range(4): - [x0,y0], [x1,y1] = bez[i-1], bez[i] - 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 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-20 : - 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-20 : - 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-20 : - 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_draw(csp, color="#05f", group = None, style="fill:none;", width = .1, comment = "") : - if csp!=[] and csp!=[[]] : - if group == None : group = options.doc_root - style += "stroke:"+color+";"+ "stroke-width:%0.4fpx;"%width - args = {"d": cubicsuperpath.formatPath(csp), "style":style} - if comment!="" : args["comment"] = str(comment) - inkex.etree.SubElement( group, inkex.addNS('path','svg'), args ) - - -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) : - # Appends subpath with line or polyline. - if len(points)>0 : - 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] ] - 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 j0, abc*bcd>0, abc*cad>0 - if m1 and m2 and m3 : return [a,b,c] - if m1 and m2 and not m3 : return [a,b,c,d] - if m1 and not m2 and m3 : return [a,b,d,c] - if not m1 and m2 and m3 : return [a,d,b,c] - if m1 and not (m2 and m3) : return [a,b,d] - if not (m1 and m2) and m3 : return [c,a,d] - if not (m1 and m3) and m2 : return [b,c,d] - - raise ValueError, "csp_segment_convex_hull happend something that shouldnot happen!" - - -################################################################################ -### Bezier additional functions -################################################################################ - -def bez_bounds_intersect(bez1, bez2) : - return bounds_intersect(bez_bound(bez2), bez_bound(bez1)) - - -def bez_bound(bez) : - return [ - min(bez[0][0], bez[1][0], bez[2][0], bez[3][0]), - min(bez[0][1], bez[1][1], bez[2][1], bez[3][1]), - max(bez[0][0], bez[1][0], bez[2][0], bez[3][0]), - max(bez[0][1], bez[1][1], bez[2][1], bez[3][1]), - ] - - -def bounds_intersect(a, b) : - return not ( (a[0]>b[2]) or (b[0]>a[2]) or (a[1]>b[3]) or (b[1]>a[3]) ) - - -def tpoint((x1,y1),(x2,y2),t): - return [x1+t*(x2-x1),y1+t*(y2-y1)] - - -def bez_to_csp_segment(bez) : - return [bez[0],bez[0],bez[1]], [bez[2],bez[3],bez[3]] - - -def bez_split(a,t=0.5) : - a1 = tpoint(a[0],a[1],t) - at = tpoint(a[1],a[2],t) - b2 = tpoint(a[2],a[3],t) - a2 = tpoint(a1,at,t) - b1 = tpoint(b2,at,t) - a3 = tpoint(a2,b1,t) - return [a[0],a1,a2,a3], [a3,b1,b2,a[3]] - - -def bez_at_t(bez,t) : - return csp_at_t([bez[0],bez[0],bez[1]],[bez[2],bez[3],bez[3]],t) - - -def bez_to_point_distance(bez,p,needed_dist=[0.,1e100]): - # returns [d^2,t] - return csp_seg_to_point_distance(bez_to_csp_segment(bez),p,needed_dist) - - -def bez_normalized_slope(bez,t): - return csp_normalized_slope([bez[0],bez[0],bez[1]], [bez[2],bez[3],bez[3]],t) - -################################################################################ -### Some vector functions -################################################################################ - -def normalize((x,y)) : - l = math.sqrt(x**2+y**2) - if l == 0 : return [0.,0.] - else : return [x/l, y/l] - - -def cross(a,b) : - return a[1] * b[0] - a[0] * b[1] - - -def dot(a,b) : - return a[0] * b[0] + a[1] * b[1] - - -def rotate_ccw(d) : - return [-d[1],d[0]] - - -def vectors_ccw(a,b): - return a[0]*b[1]-b[0]*a[1] < 0 - - -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)=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 - -################################################################################ -### -### 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" - #csp_draw([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 : - #csp_draw([[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) - #csp_draw([prev],"Blue") - #csp_draw([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) - #csp_draw([prev],"Blue") - #csp_draw([arc],"Red") - #csp_draw([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)): - # csp_draw([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)): - # csp_draw([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 : - csp_draw([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 - #csp_draw(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: - csp_draw([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: - csp_draw([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 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 (p0a1000000 or abs(R.y)>1000000 or (R-P0).mag<.1 : - 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() 1 and depthls : - 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 - -################################################################################ -### 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) : - for i in range(len(self.polygon)) : - for j in range(len(self.polygon[i])) : - x,y = self.polygon[i][j][0], self.polygon[i][j][1] - self.polygon[i][j][0] = x*cos - y*sin - self.polygon[i][j][1] = x*sin + y*cos - - - 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) : - for poly in self.polygon : - csp_draw( [csp_subpath_line_to([],poly+[poly[0]])], color=color,width=width ) - - - 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]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)=0 and cos>0 : return sin0 and cos<=0 : return sin>sin1 - if sin<=0 and cos<0 : return sin>sin1 - if sin<0 and cos>=0 : return sin len_edges : raise ValueError, "Hull error" - loops1 += 1 - next = get_closes_edge_by_angle(edges[last[1]],last) - #draw_pointer(next[0]+next[1],"Green","line", comment=i, width= 1) - #print_(next[0],"-",next[1]) - - last = next - poly += [ list(last[0]) ] - self.polygon += [ poly ] - # Remove all edges that are intersects new poly (any vertex inside new poly) - poly_ = Polygon([poly]) - for p in edges.keys()[:] : - if poly_.point_inside(list(p)) : del edges[p] - self.draw(color="Green", width=1) - - -class Arangement_Genetic: - # gene = [fittness, order, rotation, xposition] - # spieces = [gene]*shapes count - # population = [spieces] - def __init__(self, polygons, material_width): - self.population = [] - self.genes_count = len(polygons) - self.polygons = polygons - self.width = material_width - self.mutation_factor = 0.1 - self.order_mutate_factor = 1. - self.move_mutate_factor = 1. - - - def add_random_species(self,count): - for i in range(count): - specimen = [] - order = range(self.genes_count) - random.shuffle(order) - for j in order: - specimen += [ [j, random.random(), random.random()] ] - self.population += [ [None,specimen] ] - - - def species_distance2(self,sp1,sp2) : - # retun distance, each component is normalized - s = 0 - for j in range(self.genes_count) : - s += ((sp1[j][0]-sp2[j][0])/self.genes_count)**2 + (( sp1[j][1]-sp2[j][1]))**2 + ((sp1[j][2]-sp2[j][2]))**2 - return s - - - def similarity(self,sp1,top) : - # Define similarity as a simple distance between two points in len(gene)*len(spiece) -th dimentions - # for sp2 in top_spieces sum(|sp1-sp2|)/top_count - sim = 0 - for sp2 in top : - sim += math.sqrt(species_distance2(sp1,sp2[1])) - return sim/len(top) - - - def leave_top_species(self,count): - self.population.sort() - res = [ copy.deepcopy(self.population[0]) ] - del self.population[0] - for i in range(count-1) : - t = [] - for j in range(20) : - i1 = random.randint(0,len(self.population)-1) - t += [ [self.population[i1][0],i1] ] - t.sort() - res += [ copy.deepcopy(self.population[t[0][1]]) ] - del self.population[t[0][1]] - self.population = res - #del self.population[0] - #for c in range(count-1) : - # rank = [] - # for i in range(len(self.population)) : - # sim = self.similarity(self.population[i][1],res) - # rank += [ [self.population[i][0] / sim if sim>0 else 1e100,i] ] - # rank.sort() - # res += [ copy.deepcopy(self.population[rank[0][1]]) ] - # print_(rank[0],self.population[rank[0][1]][0]) - # print_(res[-1]) - # del self.population[rank[0][1]] - - self.population = res - - - def populate_species(self,count, parent_count): - self.population.sort() - self.inc = 0 - for c in range(count): - parent1 = random.randint(0,parent_count-1) - parent2 = random.randint(0,parent_count-1) - if parent1==parent2 : parent2 = (parent2+1) % parent_count - parent1, parent2 = self.population[parent1][1], self.population[parent2][1] - i1,i2 = 0, 0 - genes_order = [] - specimen = [ [0,0.,0.] for i in range(self.genes_count) ] - - self.incest_mutation_multiplyer = 1. - self.incest_mutation_count_multiplyer = 1. - - if self.species_distance2(parent1, parent2) <= .01/self.genes_count : - # OMG it's a incest :O!!! - # Damn you bastards! - self.inc +=1 - self.incest_mutation_multiplyer = 2. - self.incest_mutation_count_multiplyer = 2. - else : - 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_gene0: - 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] ] - print_("Curve: " + str(c)) - return c - - - def draw_curve(self, curve, layer, group=None, style=styles["biarc_style"]): - - self.get_defs() - # Add marker to defs if it doesnot exists - 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":"-8","refY":"-2.41063","style":"overflow:visible"}) - inkex.etree.SubElement( marker, inkex.addNS("path","svg"), - { "d":"m -6.55552,-2.41063 0,0 L -13.11104,0 c 1.0473,-1.42323 1.04126,-3.37047 0,-4.82126", - "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" } - ) - 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":"8","refY":"-2.41063","style":"overflow:visible"}) - inkex.etree.SubElement( marker, inkex.addNS("path","svg"), - { "d":"m 6.55552,-2.41063 0,0 L 13.11104,0 c -1.0473,-1.42323 -1.04126,-3.37047 0,-4.82126", - "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" } - ) - 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: - group = inkex.etree.SubElement( self.layers[min(1,len(self.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} ) - s, arcn = '', 0 - - - 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': - inkex.etree.SubElement( group, inkex.addNS('path','svg'), - { - 'style': style['line'], - 'd':'M %s,%s L %s,%s' % (s[0][0], s[0][1], si[0][0], si[0][1]), - "gcodetools": "Preview", - } - ) - 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)] - inkex.etree.SubElement( group, inkex.addNS('path','svg'), - { - '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", - }) - 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 direcrory: '%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'] - - if self.options.unit == "G21 (All units in mm)" : - self.header += "G21\n" - elif self.options.unit == "G20 (All units in inches)" : - self.header += "G20\n" - else: - self.error(_("Directory does not exist! Please specify existing directory at options 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 - - print_("Testing writing rights on '%s'"%(self.options.directory+self.options.file)) - 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. -### -### Crve defenitnion [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]] -### -################################################################################ - def generate_gcode(self, curve, layer, depth): - tool = self.tools - print_("Tool in g-code generator: " + str(tool)) - def c(c): - c = [c[i] if i.1: - r1, r2 = (P(s[0])-P(s[2])), (P(si[0])-P(s[2])) - if abs(r1.mag()-r2.mag()) < 0.001 : - g += ("G2" if s[3]<0 else "G3") + c(si[0]+[ None, (s[2][0]-s[0][0]),(s[2][1]-s[0][1]) ]) + "\n" - else: - r = (r1.mag()+r2.mag())/2 - g += ("G2" if s[3]<0 else "G3") + c(si[0]) + " R%f" % (r) + "\n" - lg = 'G02' - else: - g += "G1 " + c(si[0]) + " " + feed + "\n" - lg = 'G01' - if si[1] == 'end': - g += 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 apply_transforms(self,g,csp): - trans = self.get_transforms(g) - if trans != []: - simpletransform.applyTransformToPath(trans, csp) - return csp - - - def transform(self, source_point, layer, reverse=False): - if layer == None : - layer = self.current_layer if self.current_layer is not None else self.document.getroot() - 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 - - print_(str(self.layers)) - print_(str("I: " + str(i))) - print_("Transform: " + str(self.layers[i])) - 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]] - 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 sould 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 sould not be the same. If there are three orientation points they should not be in a straight line.)"),"wrong_orientation_points") - - self.transform_matrix_reverse[layer] = numpy.linalg.inv(self.transform_matrix[layer]).tolist() - print_("\n Layer '%s' transformation matrixes:" % layer.get(inkex.addNS('label','inkscape')) ) - print_(self.transform_matrix) - print_(self.transform_matrix_reverse) - - ###self.Zauto_scale[layer] = math.sqrt( (self.transform_matrix[layer][0][0]**2 + self.transform_matrix[layer][1][1]**2)/2 ) - ### Zautoscale is absolete - self.Zauto_scale[layer] = 1 - print_("Z automatic scale = %s (computed according orientation points)" % self.Zauto_scale[layer]) - - x,y = source_point[0], source_point[1] - if not reverse : - t = self.transform_matrix[layer] - else : - t = self.transform_matrix_reverse[layer] - return [t[0][0]*x+t[0][1]*y+t[0][2], t[1][0]*x+t[1][1]*y+t[1][2]] - - - def transform_csp(self, csp_, layer, reverse = False): - csp = [ [ [csp_[i][j][0][:],csp_[i][j][1][:],csp_[i][j][2][:]] for j in range(len(csp_[i])) ] for i in range(len(csp_)) ] - for i in xrange(len(csp)): - for j in xrange(len(csp[i])): - for k in xrange(len(csp[i][j])): - csp[i][j][k] = self.transform(csp[i][j][k],layer, reverse) - return csp - - -################################################################################ -### Errors handling function, notes are just printed into Logfile, -### warnings are printed into log file and warning message is displayed but -### extension continues working, errors causes log and execution is halted -### Notes, warnings adn errors could be assigned to space or comma or dot -### sepparated strings (case is ignoreg). -################################################################################ - def error(self, s, type_= "Warning"): - notes = "Note " - warnings = """ - Warning tools_warning - 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 - """ - errors = """ - Error - wrong_orientation_points - area_tools_diameter_error - no_tool_error - active_layer_already_has_tool - active_layer_already_has_orientation_points - """ - 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) - if not self.options.suppress_all_messages : - inkex.errormsg(s+"\n") - elif type_.lower() in re.split("[\s\n,\.]+", notes.lower()) : - print_(s) - else : - print_(s) - inkex.errormsg(s) - sys.exit() - - -################################################################################ -### 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.orientation_points = {} - self.layers = [self.document.getroot()] - self.Zcoordinates = {} - self.transform_matrix = {} - self.transform_matrix_reverse = {} - self.Zauto_scale = {} - - 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': - 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") - 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.tag == inkex.addNS("g",'svg'): - recursive_search(i,layer, (i.get("id") in self.selected) ) - elif i.get("id") in self.selected : - 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()) - - - 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*',node.text) - 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 - -################################################################################ -### -### dxfpoints -### -################################################################################ - def dxfpoints(self): - if self.selected_paths == {}: - self.error(_("Noting 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]: - if self.options.dxfpoints_action == 'replace': - 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") - -################################################################################ -### -### Laser -### -################################################################################ - def laser(self) : - - 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]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) - - 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 - ] - - minimal_way=[] - minimal_len=None - minimal_way_type=None - for w in ways: - tpoints=points[:] - cw=[] - for j in xrange(0,len(points)): - p=get_boundaries(get_boundaries(tpoints)[w[0]])[w[1]] - 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 - - if self.selected_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)) - for layer in self.layers : - if layer in paths : - print_(("layer",layer)) - p = [] - dxfpoints = [] - for path in paths[layer] : - print_(str(layer)) - if "d" not in path.keys() : - self.error(_("Warning: One or more paths dont have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!"),"selection_contains_objects_that_are_not_paths") - continue - csp = cubicsuperpath.parsePath(path.get("d")) - csp = self.apply_transforms(path, csp) - 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: - p += csp - dxfpoints=sort_dxfpoints(dxfpoints) - curve = self.parse_curve(p, layer) - self.draw_curve(curve, layer, biarc_group) - gcode += self.generate_gcode(curve, layer, 0) - - self.export_gcode(gcode) - -################################################################################ -### -### Orientation -### -################################################################################ - def orientation(self, layer=None) : - print_("entering orientations") - if layer == None : - layer = self.current_layer if self.current_layer is not None else self.document.getroot() - 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") - - orientation_group = inkex.etree.SubElement(layer, inkex.addNS('g','svg'), {"gcodetools":"Gcodetools orientation group"}) - - # translate == ['0', '-917.7043'] - if layer.get("transform") != None : - translate = layer.get("transform").replace("translate(", "").replace(")", "").split(",") - else : - translate = [0,0] - - # doc height in pixels (38 mm == 134.64566px) - doc_height = inkex.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) - - print_("Document height: " + str(doc_height)); - - if self.options.unit == "G21 (All units in mm)" : - points = [[0.,0.,0.],[100.,0.,0.],[0.,100.,0.]] - orientation_scale = 3.5433070660 - print_("orientation_scale < 0 ===> switching to mm units=%0.10f"%orientation_scale ) - elif self.options.unit == "G20 (All units in inches)" : - points = [[0.,0.,0.],[5.,0.,0.],[0.,5.,0.]] - orientation_scale = 90 - print_("orientation_scale < 0 ===> switching to inches units=%0.10f"%orientation_scale ) - - points = points[:2] - - print_(("using orientation scale",orientation_scale,"i=",points)) - for i in points : - # X == Correct! - # si == x,y coordinate in px - # si have correct coordinates - # if layer have any tranform it will be in translate so lets add that - si = [i[0]*orientation_scale, (i[1]*orientation_scale)+float(translate[1])] - g = inkex.etree.SubElement(orientation_group, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools orientation point (2 points)"}) - inkex.etree.SubElement( g, inkex.addNS('path','svg'), - { - 'style': "stroke:none;fill:#000000;", - 'd':'m %s,%s 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z z' % (si[0], -si[1]+doc_height), - 'gcodetools': "Gcodetools orientation point arrow" - }) - t = inkex.etree.SubElement( g, inkex.addNS('text','svg'), - { - 'style': "font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;", - inkex.addNS("space","xml"):"preserve", - 'x': str(si[0]+10), - 'y': str(-si[1]-10+doc_height), - 'gcodetools': "Gcodetools orientation point text" - }) - t.text = "(%s; %s; %s)" % (i[0],i[1],i[2]) - - -################################################################################ -### -### Effect -### -### Main function of Gcodetools class -### -################################################################################ - def effect(self) : - 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 - self.get_info() - 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(0,len(self.layers)-1)] ) - self.get_info() - - self.tools = { - "name": "Laser Engraver", - "id": "Laser Engraver", - "penetration feed": self.options.laser_speed, - "feed": self.options.laser_speed, - "gcode before path": (self.options.laser_command + " S" + str(int(self.options.laser_power)) + "\nG4 P" + self.options.power_delay), - "gcode after path": (self.options.laser_off_command + "\nG4 P" + self.options.power_delay + "\n" + "G1 F" + self.options.travel_speed), - } - - self.get_info() - self.laser() - -e = laser_gcode() -e.affect() +#!/usr/bin/env python +""" +Modified by ikae 2020, https://github.com/ikae +Modified by Eletronica Hoje 2016, youtube.com/eletronicahoje +Modified by Jay Johnson 2015, J Tech Photonics, Inc., jtechphotonics.com +modified by Adam Polak 2014, polakiumengineering.org + +based on 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" +import inkex, simplestyle, simplepath +import cubicsuperpath, simpletransform, bezmisc +from lxml import etree + +import os +import math +import bezmisc +import re +import copy +import sys +import time +import cmath +import numpy +import codecs +import random +import gettext +_ = gettext.gettext + + +### Check if inkex has errormsg (0.46 version doesnot have one.) Could be removed later. +if "errormsg" not in dir(inkex): + inkex.errormsg = lambda msg: sys.stderr.write((unicode(msg) + "\n").encode("UTF-8")) + + +#def bezierslopeatt(((bx0,by0),(bx1,by1),(bx2,by2),(bx3,by3)),t): # need to fix +def bezierslopeatt(a, t): + (bx0, by0) = a[0] + (bx1, by1) = a[1] + (bx2, by2) = a[2] + (bx3, by3) = a[3] + 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) + +################################################################################ +### +### 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 +options = {} +defaults = { +'header': """G90 +""", +'footer': """G1 X0 Y0 +""" +} + +intersection_recursion_depth = 10 +intersection_tolerance = 0.00001 + +styles = { + "loft_style" : { + 'main curve': str(inkex.Style({ 'stroke': '#88f', 'fill': 'none', 'stroke-width':'1', 'marker-end':'url(#Arrow2Mend)' })), + }, + "biarc_style" : { + 'biarc0': str(inkex.Style({ 'stroke': '#88f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'biarc1': str(inkex.Style({ 'stroke': '#8f8', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'line': str(inkex.Style({ 'stroke': '#f88', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'area': str(inkex.Style({ 'stroke': '#777', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' })), + }, + "biarc_style_dark" : { + 'biarc0': str(inkex.Style({ 'stroke': '#33a', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'biarc1': str(inkex.Style({ 'stroke': '#3a3', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'line': str(inkex.Style({ 'stroke': '#a33', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'area': str(inkex.Style({ 'stroke': '#222', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' })), + }, + "biarc_style_dark_area" : { + 'biarc0': str(inkex.Style({ 'stroke': '#33a', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' })), + 'biarc1': str(inkex.Style({ 'stroke': '#3a3', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' })), + 'line': str(inkex.Style({ 'stroke': '#a33', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.1' })), + 'area': str(inkex.Style({ 'stroke': '#222', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' })), + }, + "biarc_style_i" : { + 'biarc0': str(inkex.Style({ 'stroke': '#880', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'biarc1': str(inkex.Style({ 'stroke': '#808', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'line': str(inkex.Style({ 'stroke': '#088', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'area': str(inkex.Style({ 'stroke': '#999', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' })), + }, + "biarc_style_dark_i" : { + 'biarc0': str(inkex.Style({ 'stroke': '#dd5', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'biarc1': str(inkex.Style({ 'stroke': '#d5d', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'line': str(inkex.Style({ 'stroke': '#5dd', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'1' })), + 'area': str(inkex.Style({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' })), + }, + "biarc_style_lathe_feed" : { + 'biarc0': str(inkex.Style({ 'stroke': '#07f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' })), + 'biarc1': str(inkex.Style({ 'stroke': '#0f7', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' })), + 'line': str(inkex.Style({ 'stroke': '#f44', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' })), + 'area': str(inkex.Style({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' })), + }, + "biarc_style_lathe_passing feed" : { + 'biarc0': str(inkex.Style({ 'stroke': '#07f', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' })), + 'biarc1': str(inkex.Style({ 'stroke': '#0f7', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' })), + 'line': str(inkex.Style({ 'stroke': '#f44', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' })), + 'area': str(inkex.Style({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' })), + }, + "biarc_style_lathe_fine feed" : { + 'biarc0': str(inkex.Style({ 'stroke': '#7f0', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' })), + 'biarc1': str(inkex.Style({ 'stroke': '#f70', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' })), + 'line': str(inkex.Style({ 'stroke': '#744', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'.4' })), + 'area': str(inkex.Style({ 'stroke': '#aaa', 'fill': 'none', "marker-end":"url(#DrawCurveMarker)", 'stroke-width':'0.3' })), + }, + "area artefact": str(inkex.Style({ 'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width':'1' })), + "area artefact arrow": str(inkex.Style({ 'stroke': '#ff0000', 'fill': '#ffff00', 'stroke-width':'1' })), + "dxf_points": str(inkex.Style({ "stroke": "#ff0000", "fill": "#ff0000"})), + + } + +################################################################################ +### Cubic Super Path additional functions +################################################################################ + +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]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 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_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.001): + 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 range(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 range(1,len(sp)): + l += cspseglength(sp[i-1],sp[i]) + seg += [ l ] + + if l>0 : + seg = [seg[i]/l for i in range(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 range(len(s)): + d = None + for j in range(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 range(len(segs)): + for j in range(0,len(s)): + if segs[i]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_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 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 calculation 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))tolerance and i<4): + #P3 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) # orig python2 max. new line above strips out all but floats + #P3 d1 = max(d1+d) # orig python2 max. new line above strips out all but floats + n=n*2 + return d1 + #P3 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 + for i in range(4): + [x0,y0], [x1,y1] = bez[i-1], bez[i] + 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 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-20 : + 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-20 : + 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-20 : + 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_draw(csp, color="#05f", group = None, style="fill:none;", width = .1, comment = "") : + if csp!=[] and csp!=[[]] : + if group == None : group = options.doc_root + style += "stroke:"+color+";"+ "stroke-width:%0.4fpx;"%width + args = {"d": cubicsuperpath.formatPath(csp), "style":style} + if comment!="" : args["comment"] = str(comment) + etree.SubElement( group, inkex.addNS('path','svg'), args ) + + +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) : + # Appends subpath with line or polyline. + if len(points)>0 : + 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] ] + 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 j0, abc*bcd>0, abc*cad>0 + if m1 and m2 and m3 : return [a,b,c] + if m1 and m2 and not m3 : return [a,b,c,d] + if m1 and not m2 and m3 : return [a,b,d,c] + if not m1 and m2 and m3 : return [a,d,b,c] + if m1 and not (m2 and m3) : return [a,b,d] + if not (m1 and m2) and m3 : return [c,a,d] + if not (m1 and m3) and m2 : return [b,c,d] + + raise ValueError("csp_segment_convex_hull happend something that shouldnot happen!") + + +################################################################################ +### Bezier additional functions +################################################################################ + +def bez_bounds_intersect(bez1, bez2) : + return bounds_intersect(bez_bound(bez2), bez_bound(bez1)) + + +def bez_bound(bez) : + return [ + min(bez[0][0], bez[1][0], bez[2][0], bez[3][0]), + min(bez[0][1], bez[1][1], bez[2][1], bez[3][1]), + max(bez[0][0], bez[1][0], bez[2][0], bez[3][0]), + max(bez[0][1], bez[1][1], bez[2][1], bez[3][1]), + ] + + +def bounds_intersect(a, b) : + return not ( (a[0]>b[2]) or (b[0]>a[2]) or (a[1]>b[3]) or (b[1]>a[3]) ) + + +def tpoint(a, b, t): + x1 = a[0] + y1 = a[1] + x2 = b[0] + y2 = b[1] + 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(a) : + x = a[0] + y = a[1] + 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 vectors_ccw(a,b): + return a[0]*b[1]-b[0]*a[1] < 0 + + +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)=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 __truediv__(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 + +################################################################################ +### +### 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" + #csp_draw([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 : + #csp_draw([[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 range(len(csp)) : + for j in range(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 range(len(csp)) : + for j in range(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 range(csp_len)] + offsets_original = [[] for i in range(csp_len)] + join_points = [[] for i in range(csp_len)] + intersection = [[] for i in range(csp_len)] + for i in range(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) + #csp_draw([prev],"Blue") + #csp_draw([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) + #csp_draw([prev],"Blue") + #csp_draw([arc],"Red") + #csp_draw([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)) + + #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)): + # csp_draw([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 range(csp_len) : + for subpath_j in range(subpath_i,csp_len) : + subpath = unclipped_offset[subpath_i] + subpath1 = unclipped_offset[subpath_j] + for i in range(1,len(subpath)) : + # If subpath_i==subpath_j we are looking for self intersections, so + # we'll need search intersections only for range(i,len(subpath1)) + for j in ( range(i,len(subpath1)) if subpath_i==subpath_j else range(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 range(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)): + # csp_draw([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 : + csp_draw([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 + #csp_draw(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: + csp_draw([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: + csp_draw([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 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 (p0a1000000 or abs(R.y)>1000000 or (R-P0).mag()<.1 : + 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() 1 and depthls : + 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 + +################################################################################ +### 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) : + for i in range(len(self.polygon)) : + for j in range(len(self.polygon[i])) : + x,y = self.polygon[i][j][0], self.polygon[i][j][1] + self.polygon[i][j][0] = x*cos - y*sin + self.polygon[i][j][1] = x*sin + y*cos + + + 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) : + for poly in self.polygon : + csp_draw( [csp_subpath_line_to([],poly+[poly[0]])], color=color,width=width ) + + + 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]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)=0 and cos>0 : return sin0 and cos<=0 : return sin>sin1 + if sin<=0 and cos<0 : return sin>sin1 + if sin<0 and cos>=0 : return sin len_edges : raise ValueError("Hull error") + loops1 += 1 + next = get_closes_edge_by_angle(edges[last[1]],last) + #draw_pointer(next[0]+next[1],"Green","line", comment=i, width= 1) + #print_(next[0],"-",next[1]) + + last = next + poly += [ list(last[0]) ] + self.polygon += [ poly ] + # Remove all edges that are intersects new poly (any vertex inside new poly) + poly_ = Polygon([poly]) + for p in edges.keys()[:] : + if poly_.point_inside(list(p)) : del edges[p] + self.draw(color="Green", width=1) + + +class Arangement_Genetic: + # gene = [fittness, order, rotation, xposition] + # spieces = [gene]*shapes count + # population = [spieces] + def __init__(self, polygons, material_width): + self.population = [] + self.genes_count = len(polygons) + self.polygons = polygons + self.width = material_width + self.mutation_factor = 0.1 + self.order_mutate_factor = 1. + self.move_mutate_factor = 1. + + + def add_random_species(self,count): + for i in range(count): + specimen = [] + order = range(self.genes_count) + random.shuffle(order) + for j in order: + specimen += [ [j, random.random(), random.random()] ] + self.population += [ [None,specimen] ] + + + def species_distance2(self,sp1,sp2) : + # retun distance, each component is normalized + s = 0 + for j in range(self.genes_count) : + s += ((sp1[j][0]-sp2[j][0])/self.genes_count)**2 + (( sp1[j][1]-sp2[j][1]))**2 + ((sp1[j][2]-sp2[j][2]))**2 + return s + + + def similarity(self,sp1,top) : + # Define similarity as a simple distance between two points in len(gene)*len(spiece) -th dimentions + # for sp2 in top_spieces sum(|sp1-sp2|)/top_count + sim = 0 + for sp2 in top : + sim += math.sqrt(species_distance2(sp1,sp2[1])) + return sim/len(top) + + + def leave_top_species(self,count): + self.population.sort() + res = [ copy.deepcopy(self.population[0]) ] + del self.population[0] + for i in range(count-1) : + t = [] + for j in range(20) : + i1 = random.randint(0,len(self.population)-1) + t += [ [self.population[i1][0],i1] ] + t.sort() + res += [ copy.deepcopy(self.population[t[0][1]]) ] + del self.population[t[0][1]] + self.population = res + #del self.population[0] + #for c in range(count-1) : + # rank = [] + # for i in range(len(self.population)) : + # sim = self.similarity(self.population[i][1],res) + # rank += [ [self.population[i][0] / sim if sim>0 else 1e100,i] ] + # rank.sort() + # res += [ copy.deepcopy(self.population[rank[0][1]]) ] + # print_(rank[0],self.population[rank[0][1]][0]) + # print_(res[-1]) + # del self.population[rank[0][1]] + + self.population = res + + + def populate_species(self,count, parent_count): + self.population.sort() + self.inc = 0 + for c in range(count): + parent1 = random.randint(0,parent_count-1) + parent2 = random.randint(0,parent_count-1) + if parent1==parent2 : parent2 = (parent2+1) % parent_count + parent1, parent2 = self.population[parent1][1], self.population[parent2][1] + i1,i2 = 0, 0 + genes_order = [] + specimen = [ [0,0.,0.] for i in range(self.genes_count) ] + + self.incest_mutation_multiplyer = 1. + self.incest_mutation_count_multiplyer = 1. + + if self.species_distance2(parent1, parent2) <= .01/self.genes_count : + # OMG it's a incest :O!!! + # Damn you bastards! + self.inc +=1 + self.incest_mutation_multiplyer = 2. + self.incest_mutation_count_multiplyer = 2. + else : + 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_gene0: + end = p[keys[-1]][-1][1] + dist = 0 + 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.extend([k[dist]]) + del k[dist] + 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] ] + print_("Curve: " + str(c)) + return c + + + def draw_curve(self, curve, layer, group=None, style=styles["biarc_style"]): + + self.get_defs() + # Add marker to defs if it doesnot exists + if "DrawCurveMarker" not in self.defs : + defs = etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg")) + marker = etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"DrawCurveMarker","orient":"auto","refX":"-8","refY":"-2.41063","style":"overflow:visible"}) + etree.SubElement( marker, inkex.addNS("path","svg"), + { "d":"m -6.55552,-2.41063 0,0 L -13.11104,0 c 1.0473,-1.42323 1.04126,-3.37047 0,-4.82126", + "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" } + ) + if "DrawCurveMarker_r" not in self.defs : + defs = etree.SubElement( self.document.getroot(), inkex.addNS("defs","svg")) + marker = etree.SubElement( defs, inkex.addNS("marker","svg"), {"id":"DrawCurveMarker_r","orient":"auto","refX":"8","refY":"-2.41063","style":"overflow:visible"}) + etree.SubElement( marker, inkex.addNS("path","svg"), + { "d":"m 6.55552,-2.41063 0,0 L 13.11104,0 c -1.0473,-1.42323 -1.04126,-3.37047 0,-4.82126", + "style": "fill:#000044; fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round;" } + ) + for i in [0,1]: + style['biarc%s_r'%i] = dict(inkex.Style.parse_str(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] = str(inkex.Style(style['biarc%s_r'%i])) + + if group==None: + group = etree.SubElement( self.layers[min(1,len(self.layers)-1)], inkex.addNS('g','svg'), {"gcodetools": "Preview group"} ) + s, arcn = '', 0 + + + 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': + etree.SubElement( group, inkex.addNS('path','svg'), + { + 'style': style['line'], + 'd':'M %s,%s L %s,%s' % (s[0][0], s[0][1], si[0][0], si[0][1]), + "gcodetools": "Preview", + } + ) + 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)] + etree.SubElement( group, inkex.addNS('path','svg'), + { + '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", + }) + 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 direcrory: '%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'] + + if self.options.unit == "G21 (All units in mm)" : + self.header += "G21\n" + elif self.options.unit == "G20 (All units in inches)" : + self.header += "G20\n" + else: + self.error(_("Directory does not exist! Please specify existing directory at options 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 + + print_("Testing writing rights on '%s'"%(self.options.directory+self.options.file)) + 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. +### +### Crve defenitnion [start point, type = {'arc','line','move','end'}, arc center, arc angle, end point, [zstart, zend]] +### +################################################################################ + def generate_gcode(self, curve, layer, depth): + tool = self.tools + print_("Tool in g-code generator: " + str(tool)) + def c(c): + c = [c[i] if i.1: + r1, r2 = (P(s[0])-P(s[2])), (P(si[0])-P(s[2])) + if abs(r1.mag()-r2.mag()) < 0.001 : + g += ("G2" if s[3]<0 else "G3") + c(si[0]+[ None, (s[2][0]-s[0][0]),(s[2][1]-s[0][1]) ]) + "\n" + else: + r = (r1.mag()+r2.mag())/2 + g += ("G2" if s[3]<0 else "G3") + c(si[0]) + " R%f" % (r) + "\n" + lg = 'G02' + else: + g += "G1 " + c(si[0]) + " " + feed + "\n" + lg = 'G01' + if si[1] == 'end': + g += 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 apply_transforms(self,g,csp): + trans = self.get_transforms(g) + if trans != []: + simpletransform.applyTransformToPath(trans, csp) + return csp + + + def transform(self, source_point, layer, reverse=False): + if layer == None : + layer = self.current_layer if self.current_layer is not None else self.document.getroot() + 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 + + print_(str(self.layers)) + print_(str("I: " + str(i))) + print_("Transform: " + str(self.layers[i])) + 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]] + 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 sould 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 sould 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 range(len(csp)): + for j in range(len(csp[i])): + for k in range(len(csp[i][j])): + csp[i][j][k] = self.transform(csp[i][j][k],layer, reverse) + return csp + + +################################################################################ +### Errors handling function, notes are just printed into Logfile, +### warnings are printed into log file and warning message is displayed but +### extension continues working, errors causes log and execution is halted +### Notes, warnings adn errors could be assigned to space or comma or dot +### sepparated strings (case is ignoreg). +################################################################################ + def error(self, s, type_= "Warning"): + notes = "Note " + warnings = """ + Warning tools_warning + 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 + """ + errors = """ + Error + wrong_orientation_points + area_tools_diameter_error + no_tool_error + active_layer_already_has_tool + active_layer_already_has_orientation_points + """ + 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) + if not self.options.suppress_all_messages : + inkex.errormsg(s+"\n") + elif type_.lower() in re.split("[\s\n,\.]+", notes.lower()) : + print_(s) + else : + print_(s) + inkex.errormsg(s) + sys.exit() + + +################################################################################ +### 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.orientation_points = {} + self.layers = [self.document.getroot()] + self.Zcoordinates = {} + self.transform_matrix = {} + self.transform_matrix_reverse = {} + self.Zauto_scale = {} + + def recursive_search(g, layer, selected=False): + items = g.getchildren() + items.reverse() + for i in items: + if selected: + self.svg.selected[i.get("id")] = i + if i.tag == inkex.addNS("g",'svg') and i.get(inkex.addNS('groupmode','inkscape')) == 'layer': + 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") + 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.svg.selected : + self.selected_paths[layer] = self.selected_paths[layer] + [i] if layer in self.selected_paths else [i] + elif i.tag == inkex.addNS("g",'svg'): + recursive_search(i,layer, (i.get("id") in self.svg.selected) ) + elif i.get("id") in self.svg.selected : + 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()) + + + 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,inkex.paths.CubicSuperPath(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*',node.text) + 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 + +################################################################################ +### +### dxfpoints +### +################################################################################ + def dxfpoints(self): + if self.selected_paths == {}: + self.error(_("Noting 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]: + if self.options.dxfpoints_action == 'replace': + 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") + +################################################################################ +### +### Laser +### +################################################################################ + def laser(self) : + + 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]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 range(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 range(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) + + 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 + ] + + minimal_way=[] + minimal_len=None + minimal_way_type=None + for w in ways: + tpoints=points[:] + cw=[] + for j in range(0,len(points)): + p=get_boundaries(get_boundaries(tpoints)[w[0]])[w[1]] + 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 + + if self.selected_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 = etree.SubElement( list(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)) + for layer in self.layers : + if layer in paths : + print_(("layer",layer)) + p = [] + dxfpoints = [] + for path in paths[layer] : + print_(str(layer)) + if "d" not in path.keys() : + self.error(_("Warning: One or more paths dont have 'd' parameter, try to Ungroup (Ctrl+Shift+G) and Object to Path (Ctrl+Shift+C)!"),"selection_contains_objects_that_are_not_paths") + continue + csp = inkex.paths.CubicSuperPath(path.get("d")) + csp = self.apply_transforms(path, csp) + 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: + p += csp + dxfpoints=sort_dxfpoints(dxfpoints) + curve = self.parse_curve(p, layer) + self.draw_curve(curve, layer, biarc_group) + gcode += self.generate_gcode(curve, layer, 0) + + self.export_gcode(gcode) + +################################################################################ +### +### Orientation +### +################################################################################ + def orientation(self, layer=None) : + print_("entering orientations") + if layer == None : + layer = self.current_layer if self.current_layer is not None else self.document.getroot() + 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") + + orientation_group = etree.SubElement(layer, inkex.addNS('g','svg'), {"gcodetools":"Gcodetools orientation group"}) + + # translate == ['0', '-917.7043'] + if layer.get("transform") != None : + translate = layer.get("transform").replace("translate(", "").replace(")", "").split(",") + else : + translate = [0,0] + + # doc height in pixels (38 mm == 134.64566px) + doc_height = self.svg.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) + + print_("Document height: " + str(doc_height)); + + if self.options.unit == "G21 (All units in mm)" : + points = [[0.,0.,0.],[100.,0.,0.],[0.,100.,0.]] + orientation_scale = 3.5433070660 + print_("orientation_scale < 0 ===> switching to mm units=%0.10f"%orientation_scale ) + elif self.options.unit == "G20 (All units in inches)" : + points = [[0.,0.,0.],[5.,0.,0.],[0.,5.,0.]] + orientation_scale = 90 + print_("orientation_scale < 0 ===> switching to inches units=%0.10f"%orientation_scale ) + + points = points[:2] + + print_(("using orientation scale",orientation_scale,"i=",points)) + for i in points : + # X == Correct! + # si == x,y coordinate in px + # si have correct coordinates + # if layer have any tranform it will be in translate so lets add that + si = [i[0]*orientation_scale, (i[1]*orientation_scale)+float(translate[1])] + g = etree.SubElement(orientation_group, inkex.addNS('g','svg'), {'gcodetools': "Gcodetools orientation point (2 points)"}) + etree.SubElement( g, inkex.addNS('path','svg'), + { + 'style': "stroke:none;fill:#000000;", + 'd':'m %s,%s 2.9375,-6.343750000001 0.8125,1.90625 6.843748640396,-6.84374864039 0,0 0.6875,0.6875 -6.84375,6.84375 1.90625,0.812500000001 z z' % (si[0], -si[1]+doc_height), + 'gcodetools': "Gcodetools orientation point arrow" + }) + t = etree.SubElement( g, inkex.addNS('text','svg'), + { + 'style': "font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;", + inkex.addNS("space","xml"):"preserve", + 'x': str(si[0]+10), + 'y': str(-si[1]-10+doc_height), + 'gcodetools': "Gcodetools orientation point text" + }) + t.text = "(%s; %s; %s)" % (i[0],i[1],i[2]) + + +################################################################################ +### +### Effect +### +### Main function of Gcodetools class +### +################################################################################ + def effect(self) : + 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 + self.get_info() + 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(0,len(self.layers)-1)] ) + self.get_info() + + self.tools = { + "name": "GRBL Z-Servo Controller", + "id": "GRBL Z-Servo Controller", + "penetration feed": self.options.y_speed, + "feed": self.options.y_speed, + "gcode before path": (self.options.servo_up_command + " S" + str(int(self.options.servo_pwn)) + "\nG4 P" + self.options.delay), + "gcode after path": (self.options.servo_down_command + "\nG4 P" + self.options.delay + "\n" + "G1 F" + self.options.x_speed), + } + + self.get_info() + self.laser() + +e = laser_gcode() +e.run()