diff --git a/extensions/fablabchemnitz/input_stl.inx b/extensions/fablabchemnitz/input_stl.inx index 5db19882..25b1ee3d 100644 --- a/extensions/fablabchemnitz/input_stl.inx +++ b/extensions/fablabchemnitz/input_stl.inx @@ -4,40 +4,52 @@ fablabchemnitz.de.input_stl + /your/object/file.stl 1.0 0 0 false false + 2 + 879076607 + 1943148287 + false + + + + + - slic3r + slic3r +$HOME/Downloads/Slic3r-1.3.0-x86_64.AppImage (Linux) +%USERPROFILE%/Slic3r/Slic3r-console.exe (Windows) -https://github.com/jnweiger/inkscape-input-stl -(C) 2018 by Jürgen Weigert <jnweiger@gmail.com> -Version 0.6 + + + - - .stl - application/sla - Stereolitography File (*.stl) - Import STL Files - + + all + + + + + + diff --git a/extensions/fablabchemnitz/input_stl.py b/extensions/fablabchemnitz/input_stl.py index ea1445a4..7b7a68ef 100644 --- a/extensions/fablabchemnitz/input_stl.py +++ b/extensions/fablabchemnitz/input_stl.py @@ -1,31 +1,45 @@ #!/usr/bin/env python3 -# input-stl.py -# (C) 2018 Juergen Weigert , distribute under GPLv2 or ask -# -# This is an input extension for inkscape to read STL files. -# -# Requires: (python-lxml | python3-lxml), slic3r -# For optional(!) rotation support: -# Requires: (python-numpy-stl | python3-numpy-stl) -# If you get ImportError: cannot import name 'mesh' -# although an stl module is installed, then you have the wrong stl module. -# Try 'pip3 uninstall stl; pip3 install numpy-stl' -# -# 2018-12-22 jw, v0.1 Initial draught -# v0.1 First working standalone tool. -# 2018-12-26 jw, v0.3 Mesh rotation support via numpy-stl. Fully optional. -# v0.4 Works fine as an inkscape input extension under Linux. -# 2019-03-01 jw, v0.5 numbers and center option added. -# 2019-07-17 jw, v0.6 fixed ry rotation. -# -# FIXME: should use svg_pathstats(path_d): to compute bounding boxes. +''' +(C) 2018 Juergen Weigert , distribute under GPLv2 or ask -from __future__ import print_function -import sys, os, re, argparse -import subprocess, tempfile +This is an input extension for inkscape to read STL files. + +Requires: (python-lxml | python3-lxml), slic3r +For optional(!) rotation support: + Requires: (python-numpy-stl | python3-numpy-stl) + If you get ImportError: cannot import name 'mesh' + although an stl module is installed, then you have the wrong stl module. + Try 'pip3 uninstall stl; pip3 install numpy-stl' + +2018-12-22 jw, v0.1 Initial draught + v0.1 First working standalone tool. +2018-12-26 jw, v0.3 Mesh rotation support via numpy-stl. Fully optional. + v0.4 Works fine as an inkscape input extension under Linux. +2019-03-01 jw, v0.5 numbers and center option added. +2019-07-17 jw, v0.6 fixed ry rotation. + +2021-05-14 - Mario Voigt: + - changed extension to support ply,off,obj,stl by using OpenMesh + - moved extension to sub menu structure (allows preview) + - added different options + +#ToDos + * Make it available through PrusaSlic3r + * FIXME: should use svg_pathstats(path_d): to compute bounding boxes. +''' +import sys +import os +import re +import subprocess from lxml import etree from subprocess import Popen, PIPE -_version = '0.6' +import inkex +from inkex import Color +import tempfile +import openmesh as om +import stl #numpy-stl lib +import numpy +import math sys_platform = sys.platform.lower() if sys_platform.startswith('win'): @@ -37,260 +51,264 @@ else: # Linux if not os.path.exists(slic3r): slic3r = 'slic3r' -parser = argparse.ArgumentParser(description='convert an STL file to a nice SVG for inkscape. The STL object is projected onto the X-Y plane.') -parser.add_argument('-l', '--layer_height', default=None, help='slic3r layer height, probably in mm. Default: per slic3r config') -parser.add_argument('--rx', default=None, type=float, help='Rotate STL object around X-Axis before importing.') -parser.add_argument('--ry', default=None, type=float, help='Rotate STL object around Y-Axis before importing.') -parser.add_argument('--numbers', default='false', help='Add layer numbers.') -parser.add_argument('--center', default='false', help='Add center marks.') -parser.add_argument('--stdout', '--tab', default=None, help=argparse.SUPPRESS) -parser.add_argument('-s', '--slic3r-cmd', '--slic3r_cmd', default=slic3r, help='Command to invoke slic3r. Default is "'+slic3r+'"') -parser.add_argument('-o', '--output', default=None, help='SVG output file name or "-" for stdout. Default: Name derived from STL input.') -parser.add_argument('stlfile', help='STL input file to convert to SVG with the same name, but ".svg" suffix.') +class InputSTL(inkex.EffectExtension): + + def add_arguments(self, pars): + pars.add_argument('--tab') -args = parser.parse_args() + pars.add_argument('--inputfile', help='STL input file to convert to SVG with the same name, but ".svg" suffix.') + pars.add_argument('-l', '--layer_height', default=None, help='slic3r layer height, probably in mm. Default: per slic3r config') + pars.add_argument('--rx', default=None, type=float, help='Rotate STL object around X-Axis before importing.') + pars.add_argument('--ry', default=None, type=float, help='Rotate STL object around Y-Axis before importing.') + pars.add_argument('--numbers', type=inkex.Boolean, default=False, help='Add layer numbers.') + pars.add_argument('--center', type=inkex.Boolean, default=False, help='Add center marks.') + pars.add_argument("--stroke_width", type=float, default=2.0, help="Stroke width (px)") + pars.add_argument('--path_color', type=Color, default='879076607', help="Path color") + pars.add_argument("--fill_color", type=Color, default='1943148287', help="Fill color") + pars.add_argument("--use_fill_color", type=inkex.Boolean, default=False, help="Use fill color") + pars.add_argument("--tone_down", default="regular", help="Town down opacity for each layer") + + pars.add_argument('-s', '--slic3r-cmd', '--slic3r_cmd', default="slic3r", help="Command to invoke slic3r.") -# input-stl.inx advertises use of '$HOME' -- windows has HOMEPATH instead of HOME -home = os.environ.get('HOME', os.environ.get('HOMEPATH', 'NO-HOME')) -#args.slic3r_cmd = re.sub('^\$HOME(PATH)?', home, args.slic3r_cmd) + def effect(self): + args = self.options + inputfile = args.inputfile + outputfilebase = os.path.splitext(os.path.basename(inputfile))[0] + converted_inputfile = os.path.join(tempfile.gettempdir(), outputfilebase + ".stl") + if not os.path.exists(inputfile): + inkex.utils.debug("The input file does not exist.") + exit(1) + om.write_mesh(converted_inputfile, om.read_trimesh(inputfile)) #read + convert # might throw "[STLWriter] : Warning non-triangle data!" + args.inputfile = converted_inputfile #overwrite -if sys_platform.startswith('win'): - # assert we run the commandline version of slic3r - args.slic3r_cmd = re.sub('slic3r(\.exe)?$', 'slic3r-console.exe', args.slic3r_cmd, flags=re.I) + # input-stl.inx advertises use of '$HOME' -- windows has HOMEPATH instead of HOME + home = os.environ.get('HOME', os.environ.get('HOMEPATH', 'NO-HOME')) + #args.slic3r_cmd = re.sub('^\$HOME(PATH)?', home, args.slic3r_cmd) + + if sys_platform.startswith('win'): + # assert we run the commandline version of slic3r + args.slic3r_cmd = re.sub('slic3r(\.exe)?$', 'slic3r-console.exe', args.slic3r_cmd, flags=re.I) + -stlfile = args.stlfile -tmpstlfile = None + tmp_inputfile = None + + if args.rx is not None and abs(args.rx) < 0.01: args.rx = None + if args.ry is not None and abs(args.ry) < 0.01: args.ry = None + + if args.rx or args.ry: + try: + mesh = stl.Mesh.from_file(inputfile) + if args.rx: mesh.rotate([1.0, 0.0, 0.0], math.radians(float(args.rx))) + if args.ry: mesh.rotate([0.0, 1.0, 0.0], math.radians(float(args.ry))) + inputfile = tmp_inputfile = tempfile.gettempdir() + os.path.sep + 'ink-stl-' + str(os.getpid()) + '.stl' + mesh.save(inputfile) + except Exception as e: + inkex.utils.debug("Rotate failed: " + str(e)) + + svgfile = re.sub('\.stl', '.svg', args.inputfile, flags=re.IGNORECASE) + + cmd = [args.slic3r_cmd, '--version'] + try: + proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError as e: + hint="Maybe use --slic3r-cmd option?" + inkex.utils.debug("{0}\nCommand failed: errno={1} {2}\n\n{3}".format(' '.join(cmd), e.errno, e.strerror, hint), file=sys.stderr) + sys.exit(1) + stdout, stderr = proc.communicate() + + # option --layer-height does not work. We use --scale instead... + scale = 1/float(args.layer_height) + cmd = [args.slic3r_cmd, '--no-gui'] + if args.layer_height is not None: + cmd += ['--scale', str(scale), '--first-layer-height', '0.1mm'] # args.layer_height+'mm'] + cmd += ['--export-svg', '-o', svgfile, inputfile] + + magic = 10 # layer width seems to be 0.1mm ??? + + def scale_points(pts, scale): + """ str='276.422496,309.4 260.209984,309.4 260.209984,209.03 276.422496,209.03' + """ + return re.sub('\d*\.\d*', lambda x: str(float(x.group(0))*scale*magic), pts) + + + ## CAUTION: keep svg_pathstats() in sync with inkscape-centerlinetrace + def svg_pathstats(path_d): + """ calculate statistics from an svg path: + length (measuring bezier splines as straight lines through the handles). + points (all, including duplicates) + segments (number of not-connected!) path segments. + simple bounding box (ignoring curves of splines, but inclding handles.) + """ + xmin = 1e99 + ymin = 1e99 + xmax = -1e99 + ymax = -1e99 + p_points = 0 + p_length = 0 + p_segments = 0 + + path_d = path_d.lower() + for p in path_d.split('m'): + + pp = re.sub('[cl,]', ' ', p) + pp,closed = re.subn('z\s*$','',pp) + xy = pp.split() + if len(xy) < 2: + # inkex.utils.debug(len(pp)) + # inkex.utils.debug("short path error") + continue + x0 = float(xy[0]) + y0 = float(xy[1]) + if x0 > xmax: xmax = x0 + if x0 < xmin: xmin = x0 + if y0 > ymax: ymax = y0 + if y0 < ymin: ymin = y0 + + p_points += 1 + x = xy[2::2] + y = xy[3::2] + if len(x): + p_segments += 1 + if closed: + x.extend(x0) + y.extend(y0) + + for i in range(len(x)): + p_points += 1 + dx = float(x[i]) - x0 + dy = float(y[i]) - y0 + p_length += math.sqrt( dx * dx + dy * dy ) + x0,y0 = float(x[i]),float(y[i]) + if x0 > xmax: xmax = x0 + if x0 < xmin: xmin = x0 + if y0 > ymax: ymax = y0 + if y0 < ymin: ymin = y0 + + return { 'points':p_points, 'segments':p_segments, 'length':p_length, 'bbox': (xmin,ymin, xmax, ymax) } + + + def bbox_info(slic3r, file): + cmd = [ slic3r, '--no-gui', '--info', file ] + p = Popen(cmd, stdout=PIPE, stderr=PIPE) + out, err = p.communicate() + if len(err): + raise ValueError(err) + + bb = {} + for l in out.decode().split("\n"): + m = re.match('((min|max)_[xyz])\s*=\s*(.*)', l) + if m: bb[m.group(1)] = float(m.group(3)) + if (len(bb) != 6): + raise ValueError("slic3r --info did not return 6 elements for bbox") + return bb + + if args.center is not False: + bb = bbox_info(args.slic3r_cmd, inputfile) + # Ouch: bbox info gives us stl coordinates. slic3r translates them into svg px using 75dpi. + cx = (-bb['min_x'] + bb['max_x']) * 0.5 * 1/scale * magic * 25.4 / 75 + cy = (-bb['min_y'] + bb['max_y']) * 0.5 * 1/scale * magic * 25.4 / 75 -if args.rx is not None and abs(args.rx) < 0.01: args.rx = None -if args.ry is not None and abs(args.ry) < 0.01: args.ry = None + try: + proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError as e: + raise OSError("{0}\nCommand failed: errno={1} {2}".format(' '.join(cmd), e.errno, e.strerror)) + stdout, stderr = proc.communicate() + + if tmp_inputfile and os.path.exists(tmp_inputfile): + os.unlink(tmp_inputfile) + + if not b'Done.' in stdout: + inkex.utils.debug("Command failed: {0}".format(' '.join(cmd))) + inkex.utils.debug("OUT: " + str(stdout)) + inkex.utils.debug("ERR: " + str(stderr)) + sys.exit(1) + + # slic3r produces correct svg files, but with polygons instead of paths, and with undefined strokes. + # When opened with inkscape, most lines are invisible and polygons cannot be edited. + # To fix these issues, we postprocess the svg file: + # * replace polygon nodes with corresponding path nodes. + # * replace style attribute in polygon nodes with one that has a black stroke + + stream = open(svgfile, 'r') + p = etree.XMLParser(huge_tree=True) + doc = etree.parse(stream, parser=p) + stream.close() + + ## To change the document units to mm, insert directly after the root node: + # e.tag = '{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}namedview' + # e.attrib['id'] = "base" + # e.attrib['{http://www.inkscape.org/namespaces/inkscape}document-units'] = "mm" + + layercount = 0 + for e in doc.iterfind('//{*}g'): + if e.attrib['{http://slic3r.org/namespaces/slic3r}z'] and e.attrib['id']: + e.attrib['{http://www.inkscape.org/namespaces/inkscape}label'] = e.attrib['id'] + ' slic3r:z=' + e.attrib['{http://slic3r.org/namespaces/slic3r}z'] + del e.attrib['{http://slic3r.org/namespaces/slic3r}z'] + # for some fun with our inkscape-paths2openscad extension, add sibling to e: + # Depth: 1mm\nOffset: 31mm + desc = etree.Element('{http://www.w3.org/2000/svg}desc') + desc.attrib['id'] = 'descl'+str(layercount) + desc.text = "Depth: %.2fmm\nRaise: %.2fmm\n" % (1/scale, layercount/scale) + e.append(desc) + layercount+=1 + if args.numbers is True: + num = etree.Element('{http://www.w3.org/2000/svg}text') + num.attrib['id'] = 'textnum'+str(layercount) + num.attrib['x'] = str(layercount*2) + num.attrib['y'] = str(layercount*4+10) + num.attrib['style'] = 'fill:#00FF00;fill-opacity:1;stroke:#00FF00;font-family:FreeSans;font-size:10pt;stroke-opacity:1;stroke-width:0.1' + num.text = "%d" % layercount + e.append(num) + if args.center is True: + cc = etree.Element('{http://www.w3.org/2000/svg}path') + cc.attrib['id'] = 'ccross'+str(layercount) + cc.attrib['style'] = 'fill:none;fill-opacity:1;stroke:#0000FF;font-family:FreeSans;font-size:10pt;stroke-opacity:1;stroke-width:0.1' + cc.attrib['d'] = 'M %s,%s v 10 M %s,%s h 10 M %s,%s h 4' % (cx, cy-5, cx-5, cy, cx-2, cy+5) + e.append(cc) + + totalPolygoncount = 0 + for e in doc.iterfind('//{*}polygon'): + totalPolygoncount += 1 + + polygoncount = 0 + + if args.use_fill_color is False: + fill = "none" + else: + fill = args.fill_color + + for e in doc.iterfind('//{*}polygon'): + polygoncount += 1 + if args.tone_down == "front_to_back": + stroke_and_fill_opacity = polygoncount / totalPolygoncount + elif args.tone_down == "back_to_front": + stroke_and_fill_opacity = 1 - (polygoncount / totalPolygoncount) + elif args.tone_down == "regular": + stroke_and_fill_opacity = 1.0 + else: + inkex.utils.debug("Error: unkown town down option") + exit(1) + # e.tag = '{http://www.w3.org/2000/svg}polygon' + # e.attrib = {'{http://slic3r.org/namespaces/slic3r}type': 'contour', 'points': '276.422496,309.4 260.209984,309.4 260.209984,209.03 276.422496,209.03', 'style': 'fill: white'} + e.tag = re.sub('polygon$', 'path', e.tag) + e.attrib['id'] = 'polygon%d' % polygoncount + e.attrib['{http://www.inkscape.org/namespaces/inkscape}connector-curvature'] = '0' + e.attrib['style'] = 'fill:{};fill-opacity:{};stroke:{};stroke-opacity:{};stroke-width:{}'.format(fill, stroke_and_fill_opacity, args.path_color, stroke_and_fill_opacity, args.stroke_width) + e.attrib['d'] = 'M ' + re.sub(' ', ' L ', scale_points(e.attrib['points'], 1/scale)) + ' Z' + del e.attrib['points'] + if e.attrib.get('{http://slic3r.org/namespaces/slic3r}type') == 'contour': + # remove contour, but keep all slic3r:type='hole', whatever it is worth later. + del e.attrib['{http://slic3r.org/namespaces/slic3r}type'] + + etree.cleanup_namespaces(doc.getroot(), top_nsmap={ + 'inkscape': 'http://www.inkscape.org/namespaces/inkscape', + 'sodipodi': 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'}) + + #inkex.utils.debug("{0}: {1} polygons in {2} layers converted to paths.".format(svgfile, polygoncount, layercount)) -if args.rx or args.ry: - try: - import numpy, stl, math + stl_group = self.document.getroot().add(inkex.Group(id=self.svg.get_unique_id("slic3r-stl-input-"))) #make a new group at root level + for element in doc.getroot().iter("{http://www.w3.org/2000/svg}g"): + stl_group.append(element) - mesh = stl.Mesh.from_file(stlfile) - if args.rx: mesh.rotate([1.0, 0.0, 0.0], math.radians(float(args.rx))) - if args.ry: mesh.rotate([0.0, 1.0, 0.0], math.radians(float(args.ry))) - stlfile = tmpstlfile = tempfile.gettempdir() + os.path.sep + 'ink-stl-' + str(os.getpid()) + '.stl' - mesh.save(stlfile) - except Exception as e: - print("Rotate failed: " + str(e), file=sys.stderr) - -if args.output == '-': args.stdout = True - -if args.stdout: - svgfile = tempfile.gettempdir() + os.path.sep + 'ink-stl-' + str(os.getpid()) + '.svg' -else: - svgfile = re.sub('\.stl', '.svg', args.stlfile, flags=re.IGNORECASE) - if args.output is not None: svgfile = args.output - -cmd = [args.slic3r_cmd, '--version'] -try: - proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) -except OSError as e: - if args.stdout: - hint="Check your slic3r command setting in the second tab of the STL Input dialog." - else: - hint="Maybe use --slic3r-cmd option?" - print("{0}\nCommand failed: errno={1} {2}\n\n{3}".format(' '.join(cmd), e.errno, e.strerror, hint), file=sys.stderr) - sys.exit(1) -stdout, stderr = proc.communicate() - -# option --layer-height does not work. We use --scale instead... -scale = 1/float(args.layer_height) -cmd = [args.slic3r_cmd, '--no-gui'] -if args.layer_height is not None: - cmd += ['--scale', str(scale), '--first-layer-height', '0.1mm'] # args.layer_height+'mm'] -cmd += ['--export-svg', '-o', svgfile, stlfile] - -magic = 10 # layer width seems to be 0.1mm ??? - -def scale_points(pts, scale): - """ str='276.422496,309.4 260.209984,309.4 260.209984,209.03 276.422496,209.03' - """ - return re.sub('\d*\.\d*', lambda x: str(float(x.group(0))*scale*magic), pts) - - -## CAUTION: keep svg_pathstats() in sync with inkscape-centerlinetrace -def svg_pathstats(path_d): - """ calculate statistics from an svg path: - length (measuring bezier splines as straight lines through the handles). - points (all, including duplicates) - segments (number of not-connected!) path segments. - simple bounding box (ignoring curves of splines, but inclding handles.) - """ - xmin = 1e99 - ymin = 1e99 - xmax = -1e99 - ymax = -1e99 - p_points = 0 - p_length = 0 - p_segments = 0 - - path_d = path_d.lower() - for p in path_d.split('m'): - - pp = re.sub('[cl,]', ' ', p) - pp,closed = re.subn('z\s*$','',pp) - xy = pp.split() - if len(xy) < 2: - # print len(pp) - # print "short path error" - continue - x0 = float(xy[0]) - y0 = float(xy[1]) - if x0 > xmax: xmax = x0 - if x0 < xmin: xmin = x0 - if y0 > ymax: ymax = y0 - if y0 < ymin: ymin = y0 - - p_points += 1 - x = xy[2::2] - y = xy[3::2] - if len(x): - p_segments += 1 - if closed: - x.extend(x0) - y.extend(y0) - - for i in range(len(x)): - p_points += 1 - dx = float(x[i]) - x0 - dy = float(y[i]) - y0 - p_length += math.sqrt( dx * dx + dy * dy ) - x0,y0 = float(x[i]),float(y[i]) - if x0 > xmax: xmax = x0 - if x0 < xmin: xmin = x0 - if y0 > ymax: ymax = y0 - if y0 < ymin: ymin = y0 - - return { 'points':p_points, 'segments':p_segments, 'length':p_length, 'bbox': (xmin,ymin, xmax, ymax) } - - -def bbox_info(slic3r, file): - cmd = [ slic3r, '--no-gui', '--info', file ] - p = Popen(cmd, stdout=PIPE, stderr=PIPE) - out, err = p.communicate() - if len(err): - raise ValueError(err) - - bb = {} - for l in out.decode().split("\n"): - m = re.match('((min|max)_[xyz])\s*=\s*(.*)', l) - if m: bb[m.group(1)] = float(m.group(3)) - if (len(bb) != 6): - raise ValueError("slic3r --info did not return 6 elements for bbox") - return bb - -if args.center != 'false': - bb = bbox_info(args.slic3r_cmd, stlfile) - # Ouch: bbox info gives us stl coordinates. slic3r translates them into svg px using 75dpi. - cx = (-bb['min_x'] + bb['max_x']) * 0.5 * 1/scale * magic * 25.4 / 75 - cy = (-bb['min_y'] + bb['max_y']) * 0.5 * 1/scale * magic * 25.4 / 75 -# print(cx, cy, file=sys.stderr) - - -try: - if args.stdout: - tty = open("/dev/tty", "w") - else: - tty = sys.stderr -except: - tty = sys.stderr - -try: - proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) -except OSError as e: - raise OSError("{0}\nCommand failed: errno={1} {2}".format(' '.join(cmd), e.errno, e.strerror)) -stdout, stderr = proc.communicate() - -if tmpstlfile and os.path.exists(tmpstlfile): - os.unlink(tmpstlfile) - -if not b'Done.' in stdout: - print("Command failed: {0}".format(' '.join(cmd))) - print("OUT: " + str(stdout), file=sys.stderr) - print("ERR: " + str(stderr), file=sys.stderr) - sys.exit(1) - -# slic3r produces correct svg files, but with polygons instead of paths, and with undefined strokes. -# When opened with inkscape, most lines are invisible and polygons cannot be edited. -# To fix these issues, we postprocess the svg file: -# * replace polygon nodes with corresponding path nodes. -# * replace style attribute in polygon nodes with one that has a black stroke - -stream = open(svgfile, 'r') -p = etree.XMLParser(huge_tree=True) -doc = etree.parse(stream, parser=p) -stream.close() - -doc.getroot().addprevious(etree.Comment(' Imported with '+sys.argv[0]+' V'+_version+" by Juergen Weigert ")) -doc.getroot().attrib['{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}docname'] = 'input-stl.svg' - -## To change the document units to mm, insert directly after the root node: -# e.tag = '{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}namedview' -# e.attrib['id'] = "base" -# e.attrib['{http://www.inkscape.org/namespaces/inkscape}document-units'] = "mm" - -layercount = 0 -for e in doc.iterfind('//{*}g'): - if e.attrib['{http://slic3r.org/namespaces/slic3r}z'] and e.attrib['id']: - e.attrib['{http://www.inkscape.org/namespaces/inkscape}label'] = e.attrib['id'] + ' slic3r:z=' + e.attrib['{http://slic3r.org/namespaces/slic3r}z'] - del e.attrib['{http://slic3r.org/namespaces/slic3r}z'] - # for some fun with our inkscape-paths2openscad extension, add sibling to e: - # Depth: 1mm\nOffset: 31mm - desc = etree.Element('{http://www.w3.org/2000/svg}desc') - desc.attrib['id'] = 'descl'+str(layercount) - desc.text = "Depth: %.2fmm\nRaise: %.2fmm\n" % (1/scale, layercount/scale) - e.append(desc) - layercount+=1 - if args.numbers != 'false': - num = etree.Element('{http://www.w3.org/2000/svg}text') - num.attrib['id'] = 'textnum'+str(layercount) - num.attrib['x'] = str(layercount*2) - num.attrib['y'] = str(layercount*4+10) - num.attrib['style'] = 'fill:#00FF00;fill-opacity:1;stroke:#00FF00;font-family:FreeSans;font-size:10pt;stroke-opacity:1;stroke-width:0.1' - num.text = "%d" % layercount - e.append(num) - if args.center != 'false': - cc = etree.Element('{http://www.w3.org/2000/svg}path') - cc.attrib['id'] = 'ccross'+str(layercount) - cc.attrib['style'] = 'fill:none;fill-opacity:1;stroke:#0000FF;font-family:FreeSans;font-size:10pt;stroke-opacity:1;stroke-width:0.1' - cc.attrib['d'] = 'M %s,%s v 10 M %s,%s h 10 M %s,%s h 4' % (cx, cy-5, cx-5, cy, cx-2, cy+5) - e.append(cc) - - - -polygoncount = 0 -for e in doc.iterfind('//{*}polygon'): - # e.tag = '{http://www.w3.org/2000/svg}polygon' - # e.attrib = {'{http://slic3r.org/namespaces/slic3r}type': 'contour', 'points': '276.422496,309.4 260.209984,309.4 260.209984,209.03 276.422496,209.03', 'style': 'fill: white'} - e.tag = re.sub('polygon$', 'path', e.tag) - polygoncount += 1 - e.attrib['id'] = 'polygon%d' % polygoncount - e.attrib['{http://www.inkscape.org/namespaces/inkscape}connector-curvature'] = '0' - e.attrib['style'] = 'fill:none;fill-opacity:1;stroke:#FF0000;stroke-opacity:1;stroke-width:0.1' - e.attrib['d'] = 'M ' + re.sub(' ', ' L ', scale_points(e.attrib['points'], 1/scale)) + ' Z' - del e.attrib['points'] - if e.attrib.get('{http://slic3r.org/namespaces/slic3r}type') == 'contour': - # remove contour, but keep all slic3r:type='hole', whatever it is worth later. - del e.attrib['{http://slic3r.org/namespaces/slic3r}type'] - -try: - # Available in lxml since 3.5.0 - # Make an xmlns declaration in the svg header, and use the "inkscape:" prefix throughout the document. - etree.cleanup_namespaces(doc.getroot(), top_nsmap={ - 'inkscape': 'http://www.inkscape.org/namespaces/inkscape', - 'sodipodi': 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'}) -except: - pass - -try: - print("{0}: {1} polygons in {2} layers converted to paths.".format(svgfile, polygoncount, layercount), file=tty) -except: - pass - -if args.stdout: - doc.write(sys.stdout.buffer) -else: - doc.write(svgfile, pretty_print=True) \ No newline at end of file +if __name__ == '__main__': + InputSTL().run() \ No newline at end of file