massive update for STL Input extension

This commit is contained in:
leyghisbb 2021-05-15 02:37:35 +02:00
parent 02635a5982
commit da2023d139
2 changed files with 144 additions and 90 deletions

View File

@ -3,43 +3,80 @@
<name>Slic3r STL Input</name>
<id>fablabchemnitz.de.input_stl</id>
<param name="tab" type="notebook">
<page name="options" gui-text="Options">
<page name="tab_options" gui-text="Options">
<label appearance="header">Processor</label>
<param name="slic3r_cmd" type="path" mode="file" gui-text="Slic3r command">/path/to/slic3r/v1.3.1-dev</param>
<label appearance="header">Input</label>
<param name="inputfile" type="path" gui-text="Input file" filetypes="obj,off,ply,stl" mode="file">/your/object/file.stl</param>
<param name="layer_height" type="string" gui-text="Layer height [mm]:">1.0</param>
<param name="rx" type="float" precision="1" min="-360.0" max="360.0" gui-text="3D-Rotate X-Axis [deg]:">0</param>
<param name="ry" type="float" precision="1" min="-360.0" max="360.0" gui-text="3D-Rotate Y-Axis [deg]:">0</param>
<param name="numbers" type="bool" gui-text="Add layer numbers">false</param>
<param name="center" type="bool" gui-text="Add center marks">false</param>
<param name="stroke_width" type="float" min="0" max="100" gui-text="Stroke width (px):">2</param>
<param name="path_color" type="color" appearance="colorbutton" gui-text="Path color">879076607</param>
<param name="fill_color" type="color" appearance="colorbutton" gui-text="Fill color">1943148287</param>
<param name="use_fill_color" type="bool" gui-text="Use fill color">false</param>
<param name="tone_down" type="optiongroup" appearance="combo" gui-text="Town down opacity for each layer">
<option value="regular">regular</option>
<option value="front_to_back">front to back</option>
<option value="back_to_front">back to front</option>
</param>
<param name="max_num_faces" type="int" min="1" max="99999" gui-text="Maximum allowed faces" gui-description="If the STL file has too much detail it contains a large number of faces. This will make processing extremely slow. So we can limit it.">200</param>
<param name="layer_height" type="string" min="0.001" max="99999.000" precision="3" gui-text="Layer height [mm]:">1.000</param>
<hbox>
<vbox>
<label appearance="header">Transforms</label>
<param name="scalefactor" type="float" precision="3" min="0.0001" max="10000.0" gui-text="Manual scale factor" gui-description="default is 1.0">1.0</param>
<param name="rx" type="float" precision="1" min="-360.0" max="360.0" gui-text="3D-Rotate X-Axis [deg]:">0</param>
<param name="ry" type="float" precision="1" min="-360.0" max="360.0" gui-text="3D-Rotate Y-Axis [deg]:">0</param>
<param name="rz" type="float" precision="1" min="-360.0" max="360.0" gui-text="3D-Rotate Z-Axis [deg]:">0</param>
</vbox>
<separator/>
<vbox>
<label appearance="header">Output</label>
<param name="resizetoimport" type="bool" gui-text="Resize the canvas to the imported drawing's bounding box">true</param>
<param name="extraborder" type="float" precision="3" gui-text="Add extra border around fitted canvas">0.0</param>
<param name="extraborder_units" type="optiongroup" appearance="combo" gui-text="Border offset units">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
<option value="pt">pt</option>
<option value="px">px</option>
</param>
<param name="numbers" type="bool" gui-text="Add layer numbers">false</param>
<param name="center" type="bool" gui-text="Add center marks">false</param>
</vbox>
<separator/>
<vbox>
<label appearance="header">Style</label>
<param name="stroke_width" type="float" min="0" max="100" gui-text="Stroke width (px):">2</param>
<param name="path_color" type="color" appearance="colorbutton" gui-text="Path color">879076607</param>
<param name="use_path_color" type="bool" gui-text="Use path color">true</param>
<param name="fill_color" type="color" appearance="colorbutton" gui-text="Fill color">1943148287</param>
<param name="use_fill_color" type="bool" gui-text="Use fill color">false</param>
<param name="tone_down" type="optiongroup" appearance="combo" gui-text="Town down opacity for each layer">
<option value="regular">regular</option>
<option value="front_to_back">front to back</option>
<option value="back_to_front">back to front</option>
</param>
</vbox>
</hbox>
</page>
<page name="slic3r" gui-text="Slic3r Settings">
<param name="slic3r_cmd" type="path" mode="file" gui-text="Slic3r Command">slic3r</param>
<label xml:space="preserve">The slic3r command name depends on your operating system, and how slic3r was installed.
Typical values are:
$HOME/Downloads/Slic3r-1.3.0-x86_64.AppImage (Linux)
%USERPROFILE%/Slic3r/Slic3r-console.exe (Windows)</label>
<page name="tab_about" gui-text="About">
<label appearance="header">Slic3r STL Input</label>
<label>This extension is highly based on the work of Jürgen Weigert. It projects an STL file on the X-Y plane by cutting the objects into "slices". Each "slice" is a group of polygons, with a label indicating it's z position. The polygons are converted to paths for better editing in inkscape. Use Object -&gt; Rows &amp; Columns to distribute the slices in a grid. Possible input files are STL, Wavefront OBJ, PLY and OFF.</label>
<label>2021 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
<spacer/>
<label appearance="header">Online Documentation</label>
<label appearance="url">https://y.stadtfabrikanten.org/slic3rstlinput</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/MarioVoigt/mightyscape-1.X</label>
<label appearance="url">mailto:mario.voigt@stadtfabrikanten.org</label>
<spacer/>
<label appearance="header">Third Party Modules</label>
<label appearance="url">https://slic3r.org/download</label>
<spacer/>
<label appearance="header">MightyScape Extension Collection</label>
<label>This piece of software is part of the MightyScape for Inkscape Extension Collection and is licensed under GNU GPL v3</label>
<label appearance="url">https://y.stadtfabrikanten.org/mightyscape-overview</label>
</page>
<page name="help" gui-text="Help">
<label xml:space="preserve">- Projects an STL file on the X-Y plane by cutting the objects into "slices".
- Each "slice" is a group of polygons, with a label indicating its z position.
- The polygons are converted to paths for better editing in inkscape.
- Use Object -&gt; Rows &amp; Columns ... distribute the slices in a grid.</label>
<label appearance="url">https://github.com/jnweiger/inkscape-input-stl</label>
<label xml:space="preserve">(C) 2018 by Jürgen Weigert &lt;jnweiger@gmail.com&gt; and others.
Distribute under GPLv2 or ask.</label>
<label appearance="url">https://slic3r.org/download</label>
<page name="tab_donate" gui-text="Donate">
<label appearance="header">Coffee + Pizza</label>
<label>We are the Stadtfabrikanten, running the FabLab Chemnitz since 2016. A FabLab is an open workshop that gives people access to machines and digital tools like 3D printers, laser cutters and CNC milling machines.</label>
<spacer/>
<label>You like our work and want to support us? You can donate to our non-profit organization by different ways:</label>
<label appearance="url">https://y.stadtfabrikanten.org/donate</label>
<spacer/>
<label>Thanks for using our extension and helping us!</label>
<image>000_about_fablabchemnitz.svg</image>
</page>
</param>
<effect>

View File

@ -23,9 +23,15 @@ For optional(!) rotation support:
- moved extension to sub menu structure (allows preview)
- added different options
#Notes
* requires exactly Slic3r-1.3.1-dev
-> https://dl.slic3r.org/dev/linux/
-> https://dl.slic3r.org/dev/win/
* Make it available through PrusaSlic3r? > this is not possible because no support to slice into SVG layers
#ToDos
* Make it available through PrusaSlic3r
* FIXME: should use svg_pathstats(path_d): to compute bounding boxes.
'''
import sys
import os
@ -34,12 +40,10 @@ import subprocess
from lxml import etree
from subprocess import Popen, PIPE
import inkex
from inkex import Color
from inkex import Color, Transform
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'):
@ -47,7 +51,7 @@ if sys_platform.startswith('win'):
elif sys_platform.startswith('darwin'):
slic3r = 'slic3r'
else: # Linux
slic3r = os.environ['HOME']+ '/Downloads/Slic3r-1.3.0-x86_64.AppImage'
slic3r = os.environ['HOME']+ '/Downloads/Slic3r-1.3.1-dev-x86_64.AppImage'
if not os.path.exists(slic3r):
slic3r = 'slic3r'
@ -57,13 +61,20 @@ class InputSTL(inkex.EffectExtension):
pars.add_argument('--tab')
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('--scalefactor', type=float, default=1.0, help='Scale the model to custom size')
pars.add_argument("--max_num_faces", type=int, default=200, help="If the STL file has too much detail it contains a large number of faces. This will make processing extremely slow. So we can limit it.")
pars.add_argument('-l', '--layer_height', type=float, default=1.000, help='slic3r layer height, probably in mm. Default: per slic3r config')
pars.add_argument('--rx', type=float, default=None, help='Rotate STL object around X-Axis before importing.')
pars.add_argument('--ry', type=float, default=None, help='Rotate STL object around Y-Axis before importing.')
pars.add_argument('--rz', type=float, default=None, help='Rotate STL object around Z-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("--resizetoimport", type=inkex.Boolean, default=True, help="Resize the canvas to the imported drawing's bounding box")
pars.add_argument("--extraborder", type=float, default=0.0)
pars.add_argument("--extraborder_units")
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("--use_path_color", type=inkex.Boolean, default=True, help="Use 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")
@ -72,41 +83,13 @@ class InputSTL(inkex.EffectExtension):
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
# 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)
#############################################
# test slic3r command
#############################################
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)
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)
args.slic3r_cmd = re.sub('slic3r(\.exe)?$', 'slic3r-console.exe', args.slic3r_cmd, flags=re.I)
cmd = [args.slic3r_cmd, '--version']
try:
proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@ -116,18 +99,35 @@ class InputSTL(inkex.EffectExtension):
sys.exit(1)
stdout, stderr = proc.communicate()
#############################################
# prepare STL input (also accept and convert obj, stl, ply, off)
#############################################
outputfilebase = os.path.splitext(os.path.basename(args.inputfile))[0]
converted_inputfile = os.path.join(tempfile.gettempdir(), outputfilebase + ".stl")
if not os.path.exists(args.inputfile):
inkex.utils.debug("The input file does not exist.")
exit(1)
input_mesh = om.read_trimesh(args.inputfile)
if input_mesh.n_faces() > args.max_num_faces:
inkex.utils.debug("Aborted. Target STL file has " + str(input_mesh.n_faces()) + " faces, but only " + str(args.max_num_faces) + " are allowed.")
exit(1)
om.write_mesh(converted_inputfile, input_mesh) #read + convert, might throw errors; warning. output is ASCII but cannot controlled to be set to binary because om.Options() is not available in python binding yet
if not os.path.exists(converted_inputfile):
inkex.utils.debug("The converted input file does not exist.")
exit(1)
args.inputfile = converted_inputfile #overwrite
#############################################
# create the layer slices
#############################################
svgfile = re.sub('\.stl', '.svg', args.inputfile, flags=re.IGNORECASE)
# 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]
scale = 1.0 / args.layer_height
cmd = [args.slic3r_cmd, '--no-gui', '--scale', str(scale), '--rotate-x', str(args.rx), '--rotate-y', str(args.ry), '--rotate', str(args.rz), '--first-layer-height', '0.1mm', '--export-svg', '-o', svgfile, args.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'
"""
""" 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)
@ -203,7 +203,7 @@ class InputSTL(inkex.EffectExtension):
return bb
if args.center is not False:
bb = bbox_info(args.slic3r_cmd, inputfile)
bb = bbox_info(args.slic3r_cmd, args.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
@ -214,9 +214,6 @@ class InputSTL(inkex.EffectExtension):
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))
@ -280,20 +277,25 @@ class InputSTL(inkex.EffectExtension):
for e in doc.iterfind('//{*}polygon'):
polygoncount += 1
if args.tone_down == "front_to_back":
stroke_and_fill_opacity = polygoncount / totalPolygoncount
stroke_and_fill_opacity = 1 - (polygoncount / totalPolygoncount)
elif args.tone_down == "back_to_front":
stroke_and_fill_opacity = 1 - (polygoncount / totalPolygoncount)
stroke_and_fill_opacity = polygoncount / totalPolygoncount
elif args.tone_down == "regular":
stroke_and_fill_opacity = 1.0
else:
inkex.utils.debug("Error: unkown town down option")
exit(1)
if args.use_path_color is False:
stroke = ""
else:
stroke = "stroke:{}".format(args.path_color)
# 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['style'] = 'fill:{};fill-opacity:{};{};stroke-opacity:{};stroke-width:{}'.format(fill, stroke_and_fill_opacity, stroke, 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':
@ -310,5 +312,20 @@ class InputSTL(inkex.EffectExtension):
for element in doc.getroot().iter("{http://www.w3.org/2000/svg}g"):
stl_group.append(element)
#apply scale factor
translation_matrix = [[args.scalefactor, 0.0, 0.0], [0.0, args.scalefactor, 0.0]]
stl_group.transform = Transform(translation_matrix) * stl_group.transform
#adjust canvas to the inserted unfolding
if args.resizetoimport:
bbox = stl_group.bounding_box()
namedView = self.document.getroot().find(inkex.addNS('namedview', 'sodipodi'))
root = self.svg.getElement('//svg:svg');
offset = self.svg.unittouu(str(args.extraborder) + args.extraborder_units)
root.set('viewBox', '%f %f %f %f' % (bbox.left - offset, bbox.top - offset, bbox.width + 2 * offset, bbox.height + 2 * offset))
root.set('width', "{:0.6f}{}".format(bbox.width + 2 * offset, self.svg.unit))
root.set('height', "{:0.6f}{}".format(bbox.height + 2 * offset, self.svg.unit))
if __name__ == '__main__':
InputSTL().run()