diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1377554 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/equipment/laser/README.md b/equipment/laser/README.md new file mode 100644 index 0000000..9f48e11 --- /dev/null +++ b/equipment/laser/README.md @@ -0,0 +1,24 @@ +# LASER + +Uses for a LASER to make PCBAs: + +* Cut solder paste stencil. + +* Etch PCB. + +* Serialization/Marking. + + +There are two Inkscape plugins that can be used to convert an SVG into gcode. + +* Under the `jtech` directory are Inkscape plugins from J-Tech Photonics +released under GPLv2+. +https://jtechphotonics.com/?page_id=2012 + + + +Source: + +marlin-lulzbot-laser -- Marlin firmware for TAZ 6 with J-Tech Photonics LASER. +https://code.forksand.com/forksand/marlin-lulzbot-laser + diff --git a/equipment/laser/jtech/JTP_Laser_Tool_V2_2 - inkscape 9_2 version.zip b/equipment/laser/jtech/JTP_Laser_Tool_V2_2 - inkscape 9_2 version.zip new file mode 100644 index 0000000..dfaa21b Binary files /dev/null and b/equipment/laser/jtech/JTP_Laser_Tool_V2_2 - inkscape 9_2 version.zip differ diff --git a/equipment/laser/jtech/README.md b/equipment/laser/jtech/README.md new file mode 100644 index 0000000..7ed58ce --- /dev/null +++ b/equipment/laser/jtech/README.md @@ -0,0 +1,6 @@ +# J-Tech Photonics Inkscape LASER Plugin + +* Inkscape plugins from J-Tech Photonics released under GPLv2+. +https://jtechphotonics.com/?page_id=2012 +https://jtechphotonics.com/?page_id=1980 +https://jtechphotonics.com/Downloads/Inkscape/JTP_Laser_Tool_V2_2%20-%20inkscape%209_2%20version.zip diff --git a/equipment/laser/jtech/dxf_input.inx b/equipment/laser/jtech/dxf_input.inx new file mode 100644 index 0000000..1102d03 --- /dev/null +++ b/equipment/laser/jtech/dxf_input.inx @@ -0,0 +1,37 @@ + + + <_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/equipment/laser/jtech/dxf_input.py b/equipment/laser/jtech/dxf_input.py new file mode 100644 index 0000000..742ed4a --- /dev/null +++ b/equipment/laser/jtech/dxf_input.py @@ -0,0 +1,457 @@ +#!/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/equipment/laser/jtech/laser.inx b/equipment/laser/jtech/laser.inx new file mode 100644 index 0000000..41327ee --- /dev/null +++ b/equipment/laser/jtech/laser.inx @@ -0,0 +1,36 @@ + + + <_name>J Tech Photonics Laser Tool + jtechphotonics.com + laser.py + inkex.py + + M03 + M05 + 3000 + 750 + 255 + 0 + 1 + 1 + + output.gcode + true + + + mm + in + + + + + + + path + + + + + diff --git a/equipment/laser/jtech/laser.py b/equipment/laser/jtech/laser.py new file mode 100644 index 0000000..3837e1d --- /dev/null +++ b/equipment/laser/jtech/laser.py @@ -0,0 +1,3173 @@ +#!/usr/bin/env python +""" +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 == 143.62204724px) + doc_height = self.unittouu(self.document.getroot().xpath('@height', namespaces=inkex.NSS)[0]) + + 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 = 1 + 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": ("G4 P0 \n" + self.options.laser_command + " S" + str(int(self.options.laser_power)) + "\nG4 P" + self.options.power_delay), + "gcode after path": ("G4 P0 \n" + self.options.laser_off_command + " S0" + "\n" + "G1 F" + self.options.travel_speed), + } + + self.get_info() + self.laser() + +e = laser_gcode() +e.affect()