added splitall and linesimplify to vpype; added basic layer handling

(more to come)
This commit is contained in:
Mario Voigt 2021-04-06 00:29:02 +02:00
parent 4bc3b24572
commit 4296faf68b
4 changed files with 202 additions and 72 deletions

View File

@ -1,5 +1,9 @@
#!/usr/bin/env python3
import logging
logger = logging.getLogger()
logger.setLevel(level=logging.ERROR) #we set this to error before importing vpype to ignore the nasty output "WARNING:root:!!! `vpype.Length` is deprecated, use `vpype.LengthType` instead."
import sys
import os
from lxml import etree
@ -14,6 +18,11 @@ import vpype_viewer
from vpype_viewer import ViewMode
from vpype_cli import execute
logger = logging.getLogger()
logger.setLevel(level=logging.WARNING) #after importing vpype we enabled logging again
import warnings # we import this to suppress moderngl warnings from vpype_viewer
from shapely.geometry import LineString, Point
"""
@ -32,24 +41,19 @@ vpypetools is based on
- Aaron Spike's "Flatten Bezier" extension, licensed by GPL v2
- Mark Riedesel's "Apply Transform" extension (https://github.com/Klowner/inkscape-applytransforms), licensed by GPL v2
- a lot of other extensions to rip off the required code pieces ;-)
Used (tested) version of vpype: commit id https://github.com/abey79/vpype/commit/0b0dc8dd7e32998dbef639f9db578c3bff02690b (29.03.2021)
Used (tested) version of vpype occult: commit id https://github.com/LoicGoulefert/occult/commit/2d04ca57d69078755c340066c226fd6cd927d41e (04.02.2021)
- used (tested) version of vpype: commit id https://github.com/abey79/vpype/commit/0b0dc8dd7e32998dbef639f9db578c3bff02690b (29.03.2021)
- used (tested) version of vpype occult: commit id https://github.com/LoicGoulefert/occult/commit/2d04ca57d69078755c340066c226fd6cd927d41e (04.02.2021)
CLI / API docs:
- https://vpype.readthedocs.io/en/stable/api/vpype_cli.html#module-vpype_cli
- https://vpype.readthedocs.io/en/stable/api/vpype.html#module-vpype
vpype commands could be performed differently:
- 1. Work with current selection (line-wise): we could get the selected nodes/groups and check if those nodes are paths. If yes we could convert them to polylines and put it into vpype using doc.add(LineCollection, Layer)
- 2. We could execute vpype on the complete document only (svg file handling, possible as one layer or multiple layers)
working line of code (example:) doc = vpype.read_multilayer_svg(self.options.input_file, quantization=0.1, crop=False, simplify=False, parallel=False)
Todo's
- allow to change pen width / opacity in vpype viewer: https://github.com/abey79/vpype/issues/243
- command chain is really slow on Windows (takes ~5 times longer than Linux)
- add some debugging options to remove deprecation warnings of vpype/vpype_viewer
- allow to select other units than mm for tolerance, trimming, ... At the moment vpype uses millimeters regardless of the document units/display units in namedview
- handle styles of layers
- allow to select single layers instead of whole canvas (4th mode)
"""
class vpypetools (inkex.EffectExtension):
@ -66,6 +70,10 @@ class vpypetools (inkex.EffectExtension):
self.arg_parser.add_argument("--linemerge_tolerance", type=float, default=0.500, help="Maximum distance between two line endings that should be merged (default 0.500 mm)")
self.arg_parser.add_argument("--linemerge_no_flip", type=inkex.Boolean, default=False, help="Disable reversing stroke direction for merging")
# Line Simplification
self.arg_parser.add_argument("--linesimplify", type=inkex.Boolean, default=False)
self.arg_parser.add_argument("--linesimplify_tolerance", type=float, default=0.050, help="The resulting geometries' points will be at a maximum distance from the original controlled by the (default 0.05 mm)")
# Trimming
self.arg_parser.add_argument("--trim", type=inkex.Boolean, default=False)
self.arg_parser.add_argument("--trim_x_margin", type=float, default=0.000, help="trim margin - x direction (mm)") # keep default at 0.000 to keep clean bbox
@ -89,6 +97,9 @@ class vpypetools (inkex.EffectExtension):
self.arg_parser.add_argument("--filter_max_length_enabled", type=inkex.Boolean, default=False, help="filter by max length")
self.arg_parser.add_argument("--filter_max_length", type=float, default=0.000, help="Keep lines whose length isn't greater than value")
# Split All
self.arg_parser.add_argument("--splitall", type=inkex.Boolean, default=False)
# Plugin Occult
self.arg_parser.add_argument("--plugin_occult", type=inkex.Boolean, default=False)
self.arg_parser.add_argument("--plugin_occult_tolerance", type=float, default=0.01, help="Max distance between start and end point to consider a path closed (default 0.01 mm)")
@ -106,8 +117,10 @@ class vpypetools (inkex.EffectExtension):
self.arg_parser.add_argument("--freemode_cmd4_enabled", type=inkex.Boolean, default=False)
self.arg_parser.add_argument("--freemode_cmd5")
self.arg_parser.add_argument("--freemode_cmd5_enabled", type=inkex.Boolean, default=False)
self.arg_parser.add_argument("--freemode_show_cmd", type=inkex.Boolean, default=False)
# General Settings
self.arg_parser.add_argument("--input_handling", default="paths", help="Input handling")
self.arg_parser.add_argument("--flattenbezier", type=inkex.Boolean, default=False, help="Flatten bezier curves to polylines")
self.arg_parser.add_argument("--flatness", type=float, default=0.1, help="Minimum flatness = 0.1. The smaller the value the more fine segments you will get.")
self.arg_parser.add_argument("--apply_transformations", type=inkex.Boolean, default=False, help="Run 'Apply Transformations' extension before running vpype. Helps avoiding geometry shifting")
@ -122,7 +135,7 @@ class vpypetools (inkex.EffectExtension):
def effect(self):
lc = vpype.LineCollection() # create a new array of LineStrings consisting of Points. We convert selected paths to polylines and grab their points
nodesToConvert = [] # we make an array of all collected nodes to get the boundingbox of that array. We need it to place the vpype converted stuff to the correct XY coordinates
nodesToWork = [] # we make an array of all collected nodes to get the boundingbox of that array. We need it to place the vpype converted stuff to the correct XY coordinates
applyTransformAvailable = False
@ -151,7 +164,7 @@ class vpypetools (inkex.EffectExtension):
def convertPath(node):
if node.tag == inkex.addNS('path','svg'):
nodesToConvert.append(node)
nodesToWork.append(node)
if self.options.flattenbezier is True:
flatten(node)
d = node.get('d')
@ -166,35 +179,41 @@ class vpypetools (inkex.EffectExtension):
for child in children:
convertPath(child)
# check if ApplyTransform Extension is available. If yes we use it
if self.options.apply_transformations is True and applyTransformAvailable is True:
applytransform.ApplyTransform().recursiveFuseTransform(self.document.getroot())
doc = None #create a vpype document
# getting the bounding box of the current selection. We use to calculate the offset XY from top-left corner of the canvas. This helps us placing back the elements
input_bbox = None
if len(self.svg.selected) == 0:
convertPath(self.document.getroot())
for element in nodesToConvert:
input_bbox += element.bounding_box()
else:
for id, item in self.svg.selected.items():
convertPath(item)
input_bbox = inkex.elements._selected.ElementList.bounding_box(self.svg.selected) # get BoundingBox for selection
# if 'paths' we process paths only. Objects like rectangles or strokes like polygon have to be converted before accessing them
# if 'layers' we can process all elements.
if self.options.input_handling == "paths":
# getting the bounding box of the current selection. We use to calculate the offset XY from top-left corner of the canvas. This helps us placing back the elements
input_bbox = None
if len(self.svg.selected) == 0:
if self.options.apply_transformations is True and applyTransformAvailable is True:
applytransform.ApplyTransform().recursiveFuseTransform(self.document.getroot())
convertPath(self.document.getroot())
for element in nodesToWork:
input_bbox += element.bounding_box()
else:
for id, item in self.svg.selected.items():
if self.options.apply_transformations is True and applyTransformAvailable is True:
applytransform.ApplyTransform().recursiveFuseTransform(item)
convertPath(item)
input_bbox = inkex.elements._selected.ElementList.bounding_box(self.svg.selected) # get BoundingBox for selection
if len(lc) == 0:
inkex.errormsg('Selection appears to be empty or does not contain any valid svg:path nodes. Try to cast your objects to paths using CTRL + SHIFT + C or strokes to paths using CTRL + ALT+ C')
return
# find the first object in selection which has a style attribute (skips groups and other things which have no style)
firstElementStyle = None
for node in nodesToWork:
if node.get('style') != None:
firstElementStyle = node.get('style')
doc = vpype.Document(page_size=(input_bbox.width + input_bbox.left, input_bbox.height + input_bbox.top)) #create new vpype document
doc.add(lc, layer_id=None) # we add the lineCollection (converted selection) to the vpype document
if len(lc) == 0:
inkex.errormsg('Selection appears to be empty or does not contain any valid svg:path nodes. Try to cast your objects to paths using CTRL + SHIFT + C or strokes to paths using CTRL + ALT+ C')
return
# find the first object in selection which has a style attribute (skips groups and other things which have no style)
firstElementStyle = None
for node in nodesToConvert:
if node.get('style') != None:
firstElementStyle = node.get('style')
doc = vpype.Document(page_size=(input_bbox.width + input_bbox.left, input_bbox.height + input_bbox.top)) #create new vpype document
# we add the lineCollection (converted selection) to the vpype document
doc.add(lc, layer_id=None)
elif self.options.input_handling == "layers":
doc = vpype.read_multilayer_svg(self.options.input_file, quantization = 0.1, crop = False, simplify = False, parallel = False, \
default_width = self.document.getroot().get('width'), default_height = self.document.getroot().get('height'))
for node in self.document.getroot().getchildren():
nodesToWork.append(node)
tooling_length_before = doc.length()
traveling_length_before = doc.pen_up_length()
@ -203,7 +222,7 @@ class vpypetools (inkex.EffectExtension):
# the following code block is not intended to sum up the commands to build a series (pipe) of commands!
##########################################
# Line Sort
# Line Sorting
if self.options.linesort is True:
command = "linesort "
if self.options.linesort_no_flip is True:
@ -215,6 +234,10 @@ class vpypetools (inkex.EffectExtension):
if self.options.linemerge_no_flip is True:
command += " --no-flip"
# Line Simplification
if self.options.linesimplify is True:
command = "linesimplify --tolerance " + str(self.options.linesimplify_tolerance)
# Trimming
if self.options.trim is True:
command = "trim " + str(self.options.trim_x_margin) + " " + str(self.options.trim_y_margin)
@ -249,6 +272,10 @@ class vpypetools (inkex.EffectExtension):
if self.options.plugin_occult is True:
command = "occult --tolerance " + str(self.options.plugin_occult_tolerance)
# Split All
if self.options.splitall is True:
command = " splitall"
# Free Mode
if self.options.freemode is True:
command = ""
@ -270,7 +297,9 @@ class vpypetools (inkex.EffectExtension):
inkex.utils.debug("Please enabled at least one set of commands")
return
else:
inkex.utils.debug("Your command pipe will be the following: " + command)
if self.options.freemode_show_cmd is True:
inkex.utils.debug("Your command pipe will be the following:")
inkex.utils.debug(command)
# inkex.utils.debug(command)
try:
@ -305,12 +334,19 @@ class vpypetools (inkex.EffectExtension):
# show the vpype document visually
if self.options.output_show:
warnings.filterwarnings("ignore") # workaround to suppress annoying DeprecationWarning
# vpype_viewer.show(doc, view_mode=ViewMode.PREVIEW, show_pen_up=self.options.output_trajectories, show_points=False, pen_width=0.1, pen_opacity=1.0, argv=None)
vpype_viewer.show(doc, view_mode=ViewMode.PREVIEW, show_pen_up=self.options.output_trajectories, show_points=False, argv=None) # https://vpype.readthedocs.io/en/stable/api/vpype_viewer.ViewMode.html
warnings.filterwarnings("default") # reset warning filter
# save the vpype document to new svg file and close it afterwards
output_file = self.options.input_file + ".vpype.svg"
output_fileIO = open(output_file, "w", encoding="utf-8")
# vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer', no_basic_shapes = True)
vpype.write_svg(output_fileIO, doc, page_size=None, center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer')
#vpype.write_svg(output_fileIO, doc, page_size=(self.svg.unittouu(self.document.getroot().get('width')), self.svg.unittouu(self.document.getroot().get('height'))), center=False, source_string='', layer_label_format='%d', show_pen_up=self.options.output_trajectories, color_mode='layer')
output_fileIO.close()
# convert vpype polylines/lines/polygons to regular paths again. We need to use "--with-gui" to respond to "WARNING: ignoring verb FileSave - GUI required for this verb."
@ -322,28 +358,45 @@ class vpypetools (inkex.EffectExtension):
# new parse the SVG file and insert it as new group into the current document tree
# the label id is the number of layer_id=None (will start with 1)
lines = etree.parse(output_file).getroot().xpath("//svg:g[@inkscape:label='1']",namespaces=inkex.NSS)
vpypeLinesGroup = self.document.getroot().add(inkex.Group())
if self.options.use_style_of_first_element is True and firstElementStyle is not None:
vpypeLinesGroup.style = firstElementStyle
else:
vpypeLinesGroup.set('style', 'stroke:#000000;stroke-width:'+ str(self.options.trajectories_stroke_width) + 'px;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill:none')
for item in lines:
for child in item.getchildren():
vpypeLinesGroup.append(child)
vpypeLinesGroupId = self.svg.get_unique_id('vpypetools-lines-')
vpypeLinesGroup.set('id', vpypeLinesGroupId)
#import_doc = etree.parse(output_file)
try:
stream = open(output_file, 'r')
except FileNotFoundError as e:
inkex.utils.debug("There was no SVG output generated by vpype. Cannot continue")
exit(1)
p = etree.XMLParser(huge_tree=True)
import_doc = etree.parse(stream, parser=etree.XMLParser(huge_tree=True))
stream.close()
# handle pen_up trajectories (travel lines)
trajectoriesLayer = import_doc.getroot().xpath("//svg:g[@id='pen_up_trajectories']", namespaces=inkex.NSS)
if self.options.output_trajectories is True:
trajectories = etree.parse(output_file).getroot().xpath("//svg:g[@id='pen_up_trajectories']",namespaces=inkex.NSS)
vpypeTrajectoriesGroup = self.document.getroot().add(inkex.Group())
vpypeTrajectoriesGroup.set('style', 'stroke:#0000ff;stroke-width:'+ str(self.options.trajectories_stroke_width) + 'px;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill:none')
for item in trajectories:
for child in item.getchildren():
vpypeTrajectoriesGroup.append(child)
vpypeTrajectoriesId = self.svg.get_unique_id('vpypetools-trajectories-')
vpypeTrajectoriesGroup.set('id', vpypeTrajectoriesId)
if len(trajectoriesLayer) > 0:
trajectoriesLayer[0].set('style', 'stroke:#0000ff;stroke-width:'+ str(self.options.trajectories_stroke_width) + 'px;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill:none')
else:
if len(trajectoriesLayer) > 0:
trajectoriesLayer[0].getparent().remove(trajectoriesLayer[0])
# lineLayers = import_doc.getroot().xpath("//svg:g[@inkscape:label='1']", namespaces=inkex.NSS)
lineLayers = import_doc.getroot().xpath("//svg:g[not(@id='pen_up_trajectories')]", namespaces=inkex.NSS) #all layer except the pen_up trajectories layer
if self.options.input_handling == "paths" and self.options.use_style_of_first_element is True and firstElementStyle is not None:
for lineLayer in lineLayers:
lineLayer.set('style', firstElementStyle)
else:
for lineLayer in lineLayers:
lineLayer.set('style', 'stroke:#000000;stroke-width:'+ str(self.options.lines_stroke_width) + 'px;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill:none')
import_viewBox = import_doc.getroot().get('viewBox').split(" ")
self_viewBox = self.document.getroot().get('viewBox').split(" ")
scaleX = self.svg.unittouu(self_viewBox[2]) / self.svg.unittouu(import_viewBox[2])
scaleY = self.svg.unittouu(self_viewBox[3]) / self.svg.unittouu(import_viewBox[3])
# for element in import_doc.getroot().iter("*"): # through all
for element in import_doc.getroot().iter("{http://www.w3.org/2000/svg}g"):
element.set('transform', 'scale(' + str(scaleX) + ',' + str(scaleY) + ')') #imported groups need to be transformed. Or they have wrong size. Reason: different viewBox sizes/units in namedview definitions
self.document.getroot().append(element)
if self.options.apply_transformations is True and applyTransformAvailable is True: #we apply the transforms directly after adding them
applytransform.ApplyTransform().recursiveFuseTransform(element)
# Delete the temporary file again because we do not need it anymore
if os.path.exists(output_file):
@ -351,7 +404,7 @@ class vpypetools (inkex.EffectExtension):
# Remove selection objects to do a real replace with new objects from vpype document
if self.options.keep_selection is False:
for node in nodesToConvert:
for node in nodesToWork:
node.getparent().remove(node)
if __name__ == '__main__':

View File

@ -16,8 +16,14 @@
<param name="freemode_cmd4_enabled" type="bool" gui-text="Enable Command Set 4">false</param>
<param name="freemode_cmd5" type="string" appearance="multiline" gui-text="Command Set 5">splitall</param>
<param name="freemode_cmd5_enabled" type="bool" gui-text="Enable Command Set 5">false</param>
<separator/>
<param name="freemode_show_cmd" type="bool" gui-text="Show command" gui-description="Print the full command chain. Helpful for debugging.">false</param>
</page>
<page name="general_settings" gui-text="General Settings">
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input handling">
<option value="layers">Layers</option>
<option value="paths">Elements with type 'path'</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Flatten bezier curves to polylines" gui-description="Converts bezier curves to polylines.">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="flatness" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get.">0.100</param>
<param name="apply_transformations" type="bool" gui-text="Use 'Apply Transformations' extension" gui-description="Run 'Apply Transformations' extension before running vpype. Helps avoiding geometry shifting">false</param>
@ -26,9 +32,9 @@
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_selection" type="bool" gui-text="Keep selected paths" gui-description="If false, selected paths (original objects) will be removed">false</param>
<param name="strokes_to_paths" type="bool" gui-text="Auto-convert low-level strokes to paths" gui-description="Recommended option. Performs 'Path' > 'Stroke to Path' (CTRL + ALT + C) to convert vpype converted lines back to regular path objects">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element" gui-description="If enabled the first element in selection is scanned and we apply it's style to all imported vpype lines">true</param>
<param name="lines_stroke_width" type="float" min="0.001" max="99999.000" precision="3" gui-text="stroke width of tooling lines (px)" gui-description="Gets overwritten if 'Use style of first selected element' is enabled">1.000</param>
<param name="trajectories_stroke_width" type="float" min="0.001" max="99999.000" precision="3" gui-text="stroke width of trajectory lines (px)">1.000</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element" gui-description="If enabled the first element in selection is scanned and we apply it's style to all imported vpype lines (but not for trajectories))">true</param>
<param name="lines_stroke_width" type="float" min="0.001" max="99999.000" precision="3" gui-text="Stroke width of tooling lines (px)" gui-description="Gets overwritten if 'Use style of first selected element' is enabled">1.000</param>
<param name="trajectories_stroke_width" type="float" min="0.001" max="99999.000" precision="3" gui-text="Stroke width of trajectory lines (px)">1.000</param>
</page>
<page name="about" gui-text="About">
<label appearance="url">https://fablabchemnitz.de</label>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Line Simplification</name>
<id>fablabchemnitz.de.vpype_linesimplification</id>
<label appearance="header">Line Simplification</label>
<label>Reduce the number of segments in the geometries.</label>
<param name="linesimplify" type="bool" gui-hidden="true">true</param>
<param name="linesimplify_tolerance" type="float" min="0.000" max="99999.000" precision="3" gui-text="tolerance (mm)" gui-description="The resulting geometries' points will be at a maximum distance from the original controlled by the (default 0.05 mm)">0.050</param>
<spacer/>
<label appearance="header">General Settings</label>
<param name="flattenbezier" type="bool" gui-text="Flatten bezier curves to polylines" gui-description="Converts bezier curves to polylines.">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="flatness" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get.">0.100</param>
<param name="apply_transformations" type="bool" gui-text="Use 'Apply Transformations' extension" gui-description="Run 'Apply Transformations' extension before running vpype. Helps avoiding geometry shifting">false</param>
<param name="output_show" type="bool" gui-text="Show debug output" gui-description="This will open a new matplotlib window showing modified SVG data">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion">false</param>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_selection" type="bool" gui-text="Keep selected paths" gui-description="If false, selected paths (original objects) will be removed">false</param>
<param name="strokes_to_paths" type="bool" gui-text="Auto-convert low-level strokes to paths" gui-description="Recommended option. Performs 'Path' > 'Stroke to Path' (CTRL + ALT + C) to convert vpype converted lines back to regular path objects">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element" gui-description="If enabled the first element in selection is scanned and we apply it's style to all imported vpype lines">true</param>
<param name="lines_stroke_width" type="float" min="0.001" max="99999.000" precision="3" gui-text="stroke width of tooling lines (px)" gui-description="Gets overwritten if 'Use style of first selected element' is enabled">1.000</param>
<param name="trajectories_stroke_width" type="float" min="0.001" max="99999.000" precision="3" gui-text="stroke width of trajectory lines (px)">1.000</param>
<label appearance="header">About</label>
<separator/>
<label appearance="url">https://fablabchemnitz.de</label>
<label>License: GNU GPL v3</label>
<effect needs-live-preview="true">
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="vpype Tools"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">vpypetools.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Split All (Break Paths)</name>
<id>fablabchemnitz.de.vpype_splitall</id>
<label appearance="header">Split All (Break Paths)</label>
<label>Split all paths into their constituent segments. This command may be used together with linemerge for cases such as densely-connected meshes where the latter cannot optimize well enough by itself. This command will filter out segments with identical end-points. Note that since some paths (especially curved ones) can be made of a large number of segments, this command may significantly increase the processing time of the pipeline.</label>
<param name="splitall" type="bool" gui-hidden="true">true</param>
<spacer/>
<label appearance="header">General Settings</label>
<param name="flattenbezier" type="bool" gui-text="Flatten bezier curves to polylines" gui-description="Converts bezier curves to polylines.">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="flatness" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get.">0.100</param>
<param name="apply_transformations" type="bool" gui-text="Use 'Apply Transformations' extension" gui-description="Run 'Apply Transformations' extension before running vpype. Helps avoiding geometry shifting">false</param>
<param name="output_show" type="bool" gui-text="Show debug output" gui-description="This will open a new matplotlib window showing modified SVG data">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion">false</param>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_selection" type="bool" gui-text="Keep selected paths" gui-description="If false, selected paths (original objects) will be removed">false</param>
<param name="strokes_to_paths" type="bool" gui-text="Auto-convert low-level strokes to paths" gui-description="Recommended option. Performs 'Path' > 'Stroke to Path' (CTRL + ALT + C) to convert vpype converted lines back to regular path objects">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element" gui-description="If enabled the first element in selection is scanned and we apply it's style to all imported vpype lines">true</param>
<param name="lines_stroke_width" type="float" min="0.001" max="99999.000" precision="3" gui-text="stroke width of tooling lines (px)" gui-description="Gets overwritten if 'Use style of first selected element' is enabled">1.000</param>
<param name="trajectories_stroke_width" type="float" min="0.001" max="99999.000" precision="3" gui-text="stroke width of trajectory lines (px)">1.000</param>
<label appearance="header">About</label>
<separator/>
<label appearance="url">https://fablabchemnitz.de</label>
<label>License: GNU GPL v3</label>
<effect needs-live-preview="true">
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="vpype Tools"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">vpypetools.py</command>
</script>
</inkscape-extension>