#! /usr/bin/python3 # # thunderlaser.py -- driver for exporting an SVG drawing as an RDCAM file for a Thunderlaser RUIDA machine. # # This is an Inkscape extension to output paths in rdcam format. # recursivelyTraverseSvg() is originally from eggbot. Thank! # inkscape-paths2openscad and inkscape-silhouette contain copies of recursivelyTraverseSvg() # with almost identical features, but different inmplementation details. The version used here is derived from # inkscape-paths2openscad. # # 1.5a - cut/mark color filtering implemented via colorname2rgb() and svg.matchStrokeColor(). # Works with dummy device. TODO: ruida output using Layers. # 1.5b - using class Ruida through the new layer interface. # TODO: ruida output using multiple layers, currently only layer 0 is used. # 1.5c - _removed _before _tags and _attributes in *.inx, to disable false automatic translations. # That does not seem to work. Strings are still subject to automatic translations. # Replaced all empty gui-text="" with repetitive noise, to avoid 0.91 # translating "" into a 16 lines boiler plate text. # 1.6 - juergen@fabmail.org # multi layer support added. Can now mark and cut in one job. # 1.6b - bugfix release: [ ] bbox, [ ] move only, did always cut. # Updated InkSvg() class preserves native order of SVG elements. # 1.7 - Updated InkSvg() class to use inline style defs by class name, tag or id. # 1.7a - Survive SVG with comments. # 1.7b - allow empty path_lists if one of the colors is 'any'. # 1.8 - Support bodor laser. # # python2 compatibility: from __future__ import print_function import sys sys_platform = sys.platform.lower() if sys_platform.startswith('win'): sys.path.append('C:\Program Files\Inkscape\share\extensions') elif sys_platform.startswith('darwin'): sys.path.append('~/.config/inkscape/extensions') else: # Linux sys.path.append('/usr/share/inkscape/extensions/') #! /usr/bin/python # # inksvg.py - parse an svg file into a plain list of paths. # # (C) 2017 juergen@fabmail.org, authors of eggbot and others. # # 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 # ################# # 2017-12-04 jw, v1.0 Refactored class InkSvg from cookiecutter extension # 2017-12-07 jw, v1.1 Added roundedRectBezier() # 2017-12-10 jw, v1.3 Added styleDasharray() with stroke-dashoffset # 2017-12-14 jw, v1.4 Added matchStrokeColor() # 2017-12-21 jw, v1.5 Changed getPathVertices() to construct a to self.paths list, instead of # a dictionary. (Preserving native ordering) # 2017-12-22 jw, v1.6 fixed "use" to avoid errors with unknown global symbal 'composeTransform' # 2017-12-25 jw, v1.7 Added getNodeStyle(), cssDictAdd(), expanded matchStrokeColor() to use # inline style defs. Added a warning message for not-implemented CSS styles. # v1.7a Added getNodeStyleOne() made getNodeStyle() recurse through parents. import inkex import simplepath import simplestyle import simpletransform import cubicsuperpath import cspsubdiv import bezmisc import gettext import re class InkSvg(): """ """ __version__ = "1.7a" DEFAULT_WIDTH = 100 DEFAULT_HEIGHT = 100 def getNodeStyleOne(self, node): """ Finds style declarations by .class, #id or by tag.class syntax, and of course by a direct style='...' attribute. """ sheet = '' selectors = [] classes = node.get('class', '') # classes == None can happen here. if classes is not None and classes != '': selectors = ["."+cls for cls in re.split('[\s,]+', classes)] selectors += [node.tag+sel for sel in selectors] node_id = node.get('id', '') if node_id is not None and node_id != '': selectors += [ "#"+node_id ] for sel in selectors: if sel in self.css_dict: sheet += '; '+self.css_dict[sel] style = node.get('style', '') if style is not None and style != '': sheet += '; '+style return simplestyle.parseStyle(sheet) def getNodeStyle(self, node): """ Recurse into parent group nodes, like simpletransform.ComposeParents Calling getNodeStyleOne() for each. """ combined_style = {} parent = node.getparent() if parent.tag == inkex.addNS('g','svg') or parent.tag == 'g': combined_style = self.getNodeStyle(parent) style = self.getNodeStyleOne(node) for s in style: combined_style[s] = style[s] # overwrite or add return combined_style def styleDasharray(self, path_d, node): """ Check the style of node for a stroke-dasharray, and apply it to the path d returning the result. d is returned unchanged, if no stroke-dasharray was found. ## Extracted from inkscape extension convert2dashes; original ## comments below. ## Added stroke-dashoffset handling, made it a universal operator ## on nodes and 'd' paths. This extension converts a path into a dashed line using 'stroke-dasharray' It is a modification of the file addnodes.py Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org Copyright (C) 2009 Alvin Penner, penner@vaxxine.com """ def tpoint((x1,y1), (x2,y2), t = 0.5): return [x1+t*(x2-x1),y1+t*(y2-y1)] def cspbezsplit(sp1, sp2, t = 0.5): m1=tpoint(sp1[1],sp1[2],t) m2=tpoint(sp1[2],sp2[0],t) m3=tpoint(sp2[0],sp2[1],t) m4=tpoint(m1,m2,t) m5=tpoint(m2,m3,t) m=tpoint(m4,m5,t) return [[sp1[0][:],sp1[1][:],m1], [m4,m,m5], [m3,sp2[1][:],sp2[2][:]]] def cspbezsplitatlength(sp1, sp2, l = 0.5, tolerance = 0.001): bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]) t = bezmisc.beziertatlength(bez, l, tolerance) return cspbezsplit(sp1, sp2, t) def cspseglength(sp1,sp2, tolerance = 0.001): bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:]) return bezmisc.bezierlength(bez, tolerance) style = self.getNodeStyle(node) if not style.has_key('stroke-dasharray'): return path_d dashes = [] if style['stroke-dasharray'].find(',') > 0: dashes = [float (dash) for dash in style['stroke-dasharray'].split(',') if dash] if not dashes: return path_d dashoffset = 0.0 if style.has_key('stroke-dashoffset'): dashoffset = float(style['stroke-dashoffset']) if dashoffset < 0.0: dashoffset = 0.0 if dashoffset > dashes[0]: dashoffset = dashes[0] # avoids a busy-loop below! p = cubicsuperpath.parsePath(path_d) new = [] for sub in p: idash = 0 dash = dashes[0] # print("initial dash length: ", dash, dashoffset) dash = dash - dashoffset length = 0 new.append([sub[0][:]]) i = 1 while i < len(sub): dash = dash - length length = cspseglength(new[-1][-1], sub[i]) while dash < length: new[-1][-1], next, sub[i] = cspbezsplitatlength(new[-1][-1], sub[i], dash/length) if idash % 2: # create a gap new.append([next[:]]) else: # splice the curve new[-1].append(next[:]) length = length - dash idash = (idash + 1) % len(dashes) dash = dashes[idash] if idash % 2: new.append([sub[i]]) else: new[-1].append(sub[i]) i+=1 return cubicsuperpath.formatPath(new) def matchStrokeColor(self, node, rgb, eps=None, avg=True): """ Return True if the line color found in the style attribute of elem does not differ from rgb in any of the components more than eps. The default eps with avg=True is 64. With avg=False the default is eps=85 (33% on a 0..255 scale). In avg mode, the average of all three color channel differences is compared against eps. Otherwise each color channel difference is compared individually. The special cases None, False, True for rgb are interpreted logically. Otherwise rgb is expected as a list of three integers in 0..255 range. Missing style attribute or no stroke element is interpreted as False. Unparseable stroke elements are interpreted as 'black' (0,0,0). Hexadecimal stroke formats of '#RRGGBB' or '#RGB' are understood as well as 'rgb(100%, 0%, 0%) or 'red' relying on simplestyle. """ if eps is None: eps = 64 if avg == True else 85 if rgb is None or rgb is False: return False if rgb is True: return True style = self.getNodeStyle(node) s = style.get('stroke', '') if s == '': return False c = simplestyle.parseColor(s) if sum: s = abs(rgb[0]-c[0]) + abs(rgb[1]-c[1]) + abs(rgb[2]-c[2]) if s < 3*eps: return True return False if abs(rgb[0]-c[0]) > eps: return False if abs(rgb[1]-c[1]) > eps: return False if abs(rgb[2]-c[2]) > eps: return False return True def cssDictAdd(self, text): """ Represent css cdata as a hash in css_dict. Implements what is seen on: http://www.blooberry.com/indexdot/css/examples/cssembedded.htm """ text=re.sub('^\s*(