More adds and fixes

This commit is contained in:
Mario Voigt 2022-11-05 10:41:20 +01:00
parent 3df556e4fa
commit fed542ba37
42 changed files with 5790 additions and 21 deletions

View File

@ -7,14 +7,14 @@
"original_name": "Animate Order",
"original_id": "fablabchemnitz.de.animate_order",
"license": "GNU GPL v3",
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/LICENSE",
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE",
"comment": "Written by Mario Voigt",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/animate_order",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/animate_order",
"fork_url": null,
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Animate+Order",
"inkscape_gallery_url": "https://inkscape.org/~MarioVoigt/%E2%98%85animate-order",
"main_authors": [
"github.com/vmario89"
"github.com/eridur-de"
]
}
]

View File

@ -55,7 +55,6 @@ class CuttingOptimizer(inkex.EffectExtension):
elements = self.svg.selected
if len(elements) > 0: #if selection is existing, then we export only selected items to a new svg, which is then going to be processed. Otherwise we process the whole SVG document
extra_param = None
template = self.svg.copy()
for child in template.getchildren():
if child.tag == '{http://www.w3.org/2000/svg}defs':
@ -81,7 +80,7 @@ class CuttingOptimizer(inkex.EffectExtension):
actions_list.append("export-filename:{}".format(svg_out))
actions_list.append("export-do")
actions = ";".join(actions_list)
cli_output = inkscape(svg_out, extra_param, actions=actions) #process recent file
cli_output = inkscape(svg_out, actions=actions) #process recent file
if len(cli_output) > 0:
self.msg("Inkscape returned the following output when trying to run the file export; the file export may still have worked:")
self.msg(cli_output)

View File

@ -9,13 +9,13 @@
"license": "MIT License",
"license_url": "https://github.com/thierry7100/CutOptim/blob/master/LICENSE",
"comment": "",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/cutting_optimizer",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/cutting_optimizer",
"fork_url": "https://github.com/thierry7100/CutOptim",
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=55018148",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/thierry7100",
"github.com/vmario89"
"github.com/eridur-de"
]
}
]

View File

@ -66,8 +66,6 @@ class ExportObject(inkex.EffectExtension):
scale_factor = self.svg.unittouu("1px")
svg_export = self.options.export_svg
#extra_param = "--batch-process"
extra_param = None
if self.options.export_svg is False and \
self.options.export_dxf is False and \
@ -179,7 +177,7 @@ class ExportObject(inkex.EffectExtension):
actions_list.append("export-filename:{}".format(svg_out))
actions_list.append("export-do")
actions = ";".join(actions_list)
cli_output = inkscape(svg_out, extra_param, actions=actions) #process recent file
cli_output = inkscape(svg_out, actions=actions) #process recent file
if len(cli_output) > 0:
self.msg("Inkscape returned the following output when trying to run the file export; the file export may still have worked:")
self.msg(cli_output)
@ -217,7 +215,7 @@ class ExportObject(inkex.EffectExtension):
inkex.utils.debug("%d %s %s" % (proc.returncode, stdout, stderr))
if self.options.export_pdf is True:
cli_output = inkscape(os.path.join(tempfile.gettempdir(), svg_filename), extra_param, actions='export-pdf-version:1.5;export-text-to-path;export-filename:{file_name};export-do'.format(file_name=os.path.join(export_dir, filename_base + '.pdf')))
cli_output = inkscape(os.path.join(tempfile.gettempdir(), svg_filename), actions='export-pdf-version:1.5;export-text-to-path;export-filename:{file_name};export-do'.format(file_name=os.path.join(export_dir, filename_base + '.pdf')))
if len(cli_output) > 0:
self.msg("Inkscape returned the following output when trying to run the file export; the file export may still have worked:")
self.msg(cli_output)
@ -236,7 +234,7 @@ class ExportObject(inkex.EffectExtension):
actions_list.append("export-filename:{}".format(png_export))
actions_list.append("export-do")
actions = ";".join(actions_list)
cli_output = inkscape(os.path.join(tempfile.gettempdir(), svg_filename), extra_param, actions=actions)
cli_output = inkscape(os.path.join(tempfile.gettempdir(), svg_filename), actions=actions)
if len(cli_output) > 0:
self.msg("Inkscape returned the following output when trying to run the file export; the file export may still have worked:")
self.msg(cli_output)
@ -256,7 +254,7 @@ class ExportObject(inkex.EffectExtension):
actions_list.append("export-filename:{}".format(png_export))
actions_list.append("export-do")
actions = ";".join(actions_list)
cli_output = inkscape(os.path.join(tempfile.gettempdir(), svg_filename), extra_param, actions=actions)
cli_output = inkscape(os.path.join(tempfile.gettempdir(), svg_filename), actions=actions)
if len(cli_output) > 0:
self.msg("Inkscape returned the following output when trying to run the file export; the file export may still have worked:")
self.msg(cli_output)

View File

@ -9,13 +9,13 @@
"license": "MIT License",
"license_url": "https://github.com/mireq/inkscape-export-selection-as-svg/blob/master/LICENSE",
"comment": "",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/export_selection_as",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/export_selection_as",
"fork_url": "https://github.com/mireq/inkscape-export-selection-as-svg",
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=104923223",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/mireq",
"github.com/vmario89"
"github.com/eridur-de"
]
}
]

View File

@ -0,0 +1,673 @@
#!/usr/bin/env python3
"""
ImportGCode, and Inkscape extension by Nathaniel Klumb
This extension adds support for some GCode files to the File/Import...
dialog in Inkscape. It loads the GCode file passed to it by Inkscape as a
command-line parameter and writes the resulting SVG to stdout (which is how
Inkscape input plugins work).
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.
"""
import re
import inkex
import sys
import argparse
from io import StringIO
from math import sqrt ,pi, sin, cos, tan, acos, atan2, fabs
from lxml import etree
class ImportGCode:
""" Import a GCode file and process it into an SVG. """
current_id = 0
geometry_error = False
def __init__(self,gcode_filename,v_carve=False,laser_mode=False,
ignore_z=True,label_z=True,
tool_diameter=1.0,v_angle=90.0,v_top=0.0,v_step=1.0):
""" Load a GCode file and process it into an SVG. """
self.unit = 1.0
self.ignore_z = ignore_z or v_carve
self.label_z = label_z and not v_carve
self.tool_diameter = tool_diameter
self.v_carve = v_carve
self.v_angle = v_angle * pi / 180.0
self.v_top = v_top
self.v_step = v_step
self.laser_mode = laser_mode
self.spindle = False
self.speed = 0
with open(gcode_filename) as file:
self.loadGCode(file)
self.createSVG()
def getIJ(self,x1,y1,x2,y2,r):
""" Calculate I and J from two arc endpoints and a radius. """
theta = atan2(y2 - y1, x2 - x1)
alpha = acos(sqrt((x2 - x1)**2 + (y2 - y1)**2)/(2 * abs(r)))
return (r * cos(theta + alpha), r * sin(theta + alpha))
def getTangentPoints(self,x1,y1,r1,x2,y2,r2):
""" Compute the four outer tangent endpoints of two circles. """
theta = atan2(y2 - y1, x2 - x1)
try:
alpha = acos((r1 - r2)/sqrt((x2 - x1)**2 + (y2 - y1)**2))
except ValueError:
# It's broken, but we'll just cap it off.
# The SVG will be messed up, but that's better feedback
# than just blankly saying, "Sorry, please try again."
if not self.geometry_error: #Only show the error once.
inkex.errormsg('Math error importing V-carve: '
'V-bit angle too large?')
inkex.errormsg(' Check your included angle '
'setting and try again.')
self.geometry_error = True
if ((r1 - r2)/sqrt((x2 - x1)**2 + (y2 - y1)**2) < -1):
alpha = pi
else:
alpha = 0
return ((x1 + r1 * cos(theta - alpha), y1 + r1 * sin(theta - alpha)),
(x1 + r1 * cos(theta + alpha), y1 + r1 * sin(theta + alpha)),
(x2 + r2 * cos(theta + alpha), y2 + r2 * sin(theta + alpha)),
(x2 + r2 * cos(theta - alpha), y2 + r2 * sin(theta - alpha)))
def intersectLines(self, p1, p2, p3, p4):
""" Calculate the intersection of Line(p1,p2) and Line(p3,p4)
returns a tuple: (x, y, valid, included)
(x, y): the intersection
valid: a unique solution exists
included: the solution is within both the line segments
Segment(p1,p2) and Segment(p3,p4)
"""
DET_TOLERANCE = 0.00000001
T = 0.00000001
# the first line is pt1 + r*(p2-p1)
x1,y1 = p1
x2,y2 = p2
dx1 = x2 - x1
dy1 = y2 - y1
# the second line is p4 + s*(p4-p3)
x3,y3 = p3
x4,y4 = p4;
dx2 = x4 - x3
dy2 = y4 - y3
# In matrix form:
# [ dx1 -dx2 ][ r ] = [ x3-x1 ]
# [ dy1 -dy2 ][ s ] = [ y3-y1 ]
#
# Which can be solved:
# [ r ] = _1_ [ -dy2 dx2 ] [ x3-x1 ]
# [ s ] = DET [ -dy1 dx1 ] [ y3-y1 ]
#
# With the deteminant: DET = (-dx1 * dy2 + dy1 * dx2)
DET = (-dx1 * dy2 + dy1 * dx2)
# If DET is zero, they're parallel
if fabs(DET) < DET_TOLERANCE:
# If they overlap, either p3 or p4 must be
# an included point, so check one, then check the
# other. If either falls inside the segment from
# p1 to p2, return it as *a* valid intersection.
# Otherwise, return the bad news -- no intersection.
#
# Also, when checking the limits, allow a tolerance, T,
# since we're working in floating point.
if ((((x3 >= x1 - T) and (x3 <= x2 + T)) or
((x3 <= x1 + T) and (x3 >= x2 - T))) and
(((y3 >= y1 - T) and (y3 <= y2 + T)) or
((y3 <= y1 + T) and (y3 >= y2 - T)))):
return (x3,y3,False,True)
elif ((((x4 >= x1 - T) and (x4 <= x2 + T)) or
((x4 <= x1 + T) and (x4 >= x2 - T))) and
(((y4 >= y1 - T) and (y4 <= y2 + T)) or
((y4 <= y1 + T) and (y4 >= y2 - T)))):
return (x4,y4,False,True)
# NO CONNECTION... *dialtone*
else:
return (None,None,False,False)
# Since the determinant is non-zero, now take the reciprocal.
invDET = 1.0/DET
# We want to calculate the intersection for each line so we can
# average the results together. They should be identical, but
# floating-point and rounding error, etc...
# Calculate the scalar distances along Line(p1,p2) and Line(p3,p4)
r = invDET * (-dy2 * (x3-x1) + dx2 * (y3-y1))
s = invDET * (-dy1 * (x3-x1) + dx1 * (y3-y1))
# Average the intersection's coordinates from the two lines.
x = (x1 + r*dx1 + x3 + s*dx2)/2.0
y = (y1 + r*dy1 + y3 + s*dy2)/2.0
# Now one last check to see if the intersection's coordinates are
# included within both line segments.
included = ((((x >= x1 - T) and (x <= x2 + T)) or
((x <= x1 + T) and (x >= x2 - T))) and
(((y >= y1 - T) and (y <= y2 + T)) or
((y <= y1 + T) and (y >= y2 - T))) and
(((x >= x3 - T) and (x <= x4 + T)) or
((x <= x3 + T) and (x >= x4 - T))) and
(((y >= y3 - T) and (y <= y4 + T)) or
((y <= y3 + T) and (y >= y4 - T))))
return (x,y,True,included)
def getRadius(self,Z):
""" Compute the radius of a V-bit given a Z coordinate.
If the V-bit is above stock top, we just mirror it.
Technically, the file's broken, but hey, may as well do something.
"""
if (self.v_top <= Z):
return (Z - self.v_top) * tan(self.v_angle / 2)
else:
return (self.v_top - Z) * tan(self.v_angle / 2)
def getAngle(self,center,point):
""" Calculate the angle from a center to a point. """
a = atan2(point[1] - center[1], point[0] - center[0])
return a + ((2*pi) if (a<0.0) else 0)
def isLargeAngle(self,center,p1,p2):
""" Determine whether the SVG large angle flag should be set. """
a1 = self.getAngle(center,p1)
a2 = self.getAngle(center,p2)
angle = a1 - a2
if angle < 0:
angle += 2 * pi
return 1 if (abs(angle) > pi) else 0
def interpolatePoints(self,center,p1,p2):
""" Interpolate a set of points along an arc. """
a1 = self.getAngle(center,p1)
a2 = self.getAngle(center,p2)
angle = a2 - a1
dz = 1.0 * (p2[2]-p1[2])
r = sqrt((center[0] - p1[0])**2 + (center[1] - p1[1])**2)
length = r * abs(angle) / pi
steps = int(round(length/self.v_step))
points = []
for i in range(1,steps):
point = (center[0] + r * cos(a1 + angle*i/steps),
center[1] + r * sin(a1 + angle*i/steps),
p1[2] + dz*i/steps)
points += [point]
points += [p2]
return points
def makeVcarve(self,v_segments):
""" Connect multiple V-carve segments into one path.
Start on one V-carve segment and chain all the way to the
opposite end, then add a switchback and chain all the way
back to the beginning.
"""
vs = v_segments
# Move to the starting point.
path = 'M {} {} '.format(vs[0][1][0][0],vs[0][1][0][1])
# Initial arc, if it's not a point.
if vs[0][0][0][2] > 0:
path += ('A {} {} 0 {} {} {} {} '
).format(vs[0][0][0][2],vs[0][0][0][2],
1 if (vs[0][0][0][2] > vs[0][0][1][2]) else 0,
0,vs[0][1][1][0],vs[0][1][1][1])
# Step through all the segments on the way to the other end.
for v in range(len(vs)-1):
# Check whether an intersection exists between the two
# line segments. If so, use it, otherwise, connect with an arc.
x,y,valid,included = self.intersectLines(vs[v][1][1],
vs[v][1][2],
vs[v+1][1][1],
vs[v+1][1][2])
if included: #line segments
path += 'L {} {} '.format(x,y)
else:
path += ('L {} {} A {} {} 0 {} {} {} {} '
).format(vs[v][1][2][0],vs[v][1][2][1],
vs[v][0][1][2],vs[v][0][1][2],
self.isLargeAngle(vs[v][0][1],
vs[v][1][2],
vs[v+1][1][1]),
0,vs[v+1][1][1][0],vs[v+1][1][1][1])
# Connecting line.
path += 'L {} {} '.format(vs[len(vs)-1][1][2][0],
vs[len(vs)-1][1][2][1])
# Switchback arc, if it's not a point.
if vs[len(vs)-1][0][1][2] > 0:
path += ('A {} {} 0 {} {} {} {} '
).format(vs[len(vs)-1][0][1][2],vs[len(vs)-1][0][1][2],
1 if (vs[len(vs)-1][0][1][2] >
vs[len(vs)-2][0][0][2]) else 0,
0,vs[len(vs)-1][1][3][0],vs[len(vs)-1][1][3][1])
# Step through all the segments on the way back home.
for v in range(len(vs)-1,0,-1):
# Check whether an intersection exists between the two
# line segments. If so, use it, otherwise, connect with an arc.
x,y,valid,included = self.intersectLines(vs[v][1][3],
vs[v][1][0],
vs[v-1][1][3],
vs[v-1][1][0])
if included: #line segments
path += 'L {} {} '.format(x,y)
else:
path += ('L {} {} A {} {} 0 {} {} {} {} '
).format(vs[v][1][0][0],vs[v][1][0][1],
vs[v-1][0][1][2],vs[v-1][0][1][2],
self.isLargeAngle(vs[v-1][0][1],
vs[v][1][0],
vs[v-1][1][3]),
0,vs[v-1][1][3][0],vs[v-1][1][3][1])
# And finally, close the curve.
path += 'Z'
return path
def getVsegment(self,x1,y1,z1,x2,y2,z2):
""" Compute the required data to define a V-carve segment. """
r1 = self.getRadius(z1)
r2 = self.getRadius(z2)
p = self.getTangentPoints(x1,y1,r1,x2,y2,r2)
return (((x1,y1,r1),(x2,y2,r2)),p)
def parseLine(self,command,X,Y,Z,line,no_path=False):
""" Parse a line of G-code.
This takes the current coordinates and modal command, then processes
the new line of G-code to yield a new ending set of coordinates
plus values necessary for curve computations. It also returns the
resulting path data, unless otherwise indicated, e.g. for V-carves.
"""
comments = re.compile('\([^\)]*\)')
commands = re.compile('([MSGXYZIJKR])([-.0-9]+)')
lastX = X
lastY = Y
lastZ = Z
I = 0.0
J = 0.0
K = 0.0
R = None
results = commands.findall(comments.sub('',line))
if not line.startswith(";"):
inkex.utils.debug(results)
for (code,val) in results:
v = float(val)
i = int(v)
if code == 'M':
if i == 3:
self.spindle = True
elif i == 5:
self.spindle = False
elif code == 'S':
self.speed = v
elif code == 'G':
if i == 0:
command = 'G0'
elif i == 1:
command = 'G1'
elif i == 2:
command = 'G2'
elif i == 3:
command = 'G3'
elif i == 20:
self.unit = 25.4
elif i == 21:
self.unit = 1.0
elif val == "90":
self.absolute = True
elif val == "91":
self.absolute = False
elif val == "90.1":
self.absoluteIJK = True
elif val == "91.1":
self.absoluteIJK = False
elif code == 'X':
if self.absolute:
X = v * self.unit
else:
X += v * self.unit
elif code == 'Y':
if self.absolute:
Y = v * self.unit
else:
Y += v * self.unit
elif code == 'Z':
if self.absolute:
Z = v * self.unit
else:
Z += v * self.unit
elif code == 'I':
I = v * self.unit
if self.absoluteIJK:
I -= X
elif code == 'J':
J = v * self.unit
if self.absoluteIJK:
J -= Y
elif code == 'K':
# Sure, process it, but we don't *do* anything with K.
K = v * self.unit
if self.absoluteIJK:
K -= Z
elif code == 'R':
R = v * self.unit
if no_path: # V-carving doesn't need any path data.
return ((command, X, Y, Z, I, J, K, R, ''))
# The line's been parsed. Now let's generate path data from it.
path = ''
if command == 'G1':
# If there's any XY motion, make a line segment.
if ((X != lastX) or (Y != lastY)):
path = 'L {} {} '.format(round(X,5),round(Y,5))
elif (command == 'G2') or (command == 'G3'):
# Arcs! Oh, what glorious fun we'll have! First, sweep direction.
sweep = 0 if (command == 'G2') else 1
# R overrules I and J if both are present, so we compute
# new I and J values based on R. We need those to determine
# whether the Large Angle Flag needs to be set.
if R is not None:
I,J = self.getIJ(lastX,lastY,X,Y,R)
if (I != 0.0) or (J != 0.0):
if sweep == 0:
large_arc = self.isLargeAngle((lastX+I,lastY+J),
(lastX,lastY),(X,Y))
else:
large_arc = self.isLargeAngle((lastX+I,lastY+J),
(X,Y),(lastX,lastY))
radius = sqrt(I**2 + J**2)
path = 'A {} {} 0 {} {} {} {} '.format(round(radius,5),
round(radius,5),
large_arc,sweep,
round(X,5),round(Y,5))
# No R, and no I or J either? Let's just call it a line segment.
# (It may have had a K, but we don't believe in K for SVG imports.)
else:
path = 'L {} {} '.format(round(X,5),round(Y,5))
# In laser mode, if the spindle isn't active or the speed is zero,
# there's no lasing to be had. Drop the path data. (The Inkscape
# extension from J Tech Photonics uses G1/G2/G3 moves throughout,
# with nary a G0, so if we don't do this, we'll show unlasered paths.)
if (self.laser_mode and ((not self.spindle) or (self.speed == 0))):
path = ''
return ((command, X, Y, Z, I, J, K, R, path))
def savePath(self,path,Z):
""" Save a set of path data, filing it by Z if appropriate. """
if (path.find('A') == -1) and (path.find('L') == -1):
return #empty path
if self.ignore_z:
if path not in self.paths:
self.paths.add(path)
else:
try:
if path not in self.paths_by_z[Z]:
self.paths_by_z[Z].add(path)
except KeyError:
self.paths_by_z[Z] = set([path])
def loadGCode(self,gcode_file):
""" Load a G-code file, handling the contents. """
if self.ignore_z:
self.paths = set([])
else:
self.paths_by_z = {}
self.absolute = True
self.absoluteIJK = False
self.unit=1.0
command = ''
X = 0.0
Y = 0.0
Z = 0.0
lastX = X
lastY = Y
lastZ = Z
self.minX = 0.0
self.minY = 0.0
self.minZ = 0.0
self.maxX = 0.0
self.maxY = 0.0
self.maxZ = 0.0
path = ''
line = gcode_file.readline()
v_segments = []
while line:
command,X,Y,Z,I,J,K,R,path_data = self.parseLine(command,
X, Y, Z, line,
self.v_carve)
self.minX = X if X < self.minX else self.minX
self.maxX = X if X > self.maxX else self.maxX
self.minY = Y if Y < self.minY else self.minY
self.maxY = Y if Y > self.maxY else self.maxY
self.minZ = Z if Z < self.minZ else self.minZ
self.maxZ = Z if Z > self.maxZ else self.maxZ
# V-carve mode.
if self.v_carve:
if (lastX != X) or (lastY != Y):
if command == 'G1':
v_segments += [self.getVsegment(lastX, lastY, lastZ,
X, Y, Z)]
elif (command == 'G2') or (command == 'G3'):
# We don't attempt to handle the plethora of curves
# that can result from V-carving arcs. Instead, we
# just interpolate them and process the subparts.
points = self.interpolatePoints((lastX+I,lastY+J),
(lastX,lastY,lastZ),
(X,Y,Z))
iX = lastX
iY = lastY
iZ = lastZ
for p in points:
v_segments += [self.getVsegment(iX, iY, iZ,
p[0], p[1], p[2])]
iX = p[0]
iY = p[1]
iZ = p[2]
else:
if len(v_segments):
self.savePath(self.makeVcarve(v_segments),'VCarve')
v_segments = []
# Standard mode (non-V-carve).
else:
if ((command == 'G0') or
(not self.ignore_z and (Z != lastZ)) or
(self.laser_mode and ((not self.spindle) or
(self.speed == 0)))):
if (path != ''):
self.savePath(path,lastZ)
path = ''
if (((command == 'G1') or
(command == 'G2') or
(command == 'G3')) and (path == '')):
path = 'M {} {} {}'.format(lastX,lastY,path_data)
else:
path += path_data
lastX = X
lastY = Y
lastZ = Z
line = gcode_file.readline()
# Always remember to save the tail end of your work.
if self.v_carve:
if len(v_segments):
self.savePath(self.makeVcarve(v_segments),'VCarve')
else:
if (path != ''):
self.savePath(path,lastZ)
def filterPaths(self):
""" Filter out duplicate paths, leaving only the deepest instance. """
if self.ignore_z:
return
z_depths = sorted(self.paths_by_z,None,None,True)
for i in range(0,len(z_depths)):
for j in range(i+1, len(z_depths)):
self.paths_by_z[z_depths[i]] -= self.paths_by_z[z_depths[j]]
def next_id(self):
""" Return an incrementing value. """
self.current_id += 1
return self.current_id
def getStyle(self,color='#000000',width=None):
""" Create a CSS-type style string. """
if width is None:
width = self.tool_diameter
return ('opacity:1;vector-effect:none;fill:none;fill-opacity:1;'
'stroke:{};stroke-width:{};stroke-opacity:1;'
'stroke-linecap:round;stroke-linejoin:round;'
'stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0'
).format(color,width)
def createSVG(self):
""" Create the output SVG. """
base = ('<svg xmlns="http://www.w3.org/2000/svg"'
' width="{}mm" height="{}mm" viewBox="{} {} {} {}"/>'
).format(self.maxX-self.minX, self.maxY-self.minY,
self.minX, self.minY, self.maxX-self.minX, self.maxY-self.minY)
self.doc = etree.parse(StringIO((base)))
svg = self.doc.getroot()
# Since G-code and SVG interpret Y in opposite directions,
# we just group everything under a transform that mirrors Y.
svg = etree.SubElement(svg,'g',{'id':'gcode',
'transform':'scale(1,-1)'})
# Add illustrative axes to the SVG to facilitate positioning.
etree.SubElement(svg,'path',
{'d':'M 0 {} V {}'.format(self.minY, self.maxY),
'style':self.getStyle('#00ff00',0.5),
'id':'vertical'})
etree.SubElement(svg,'path',
{'d':'M {} 0 H {}'.format(self.minX, self.maxX),
'style':self.getStyle('#ff0000',0.5),
'id':'horizontal'})
# For V-carves, include the paths and use a narrow stroke width.
if self.v_carve:
for path in self.paths:
etree.SubElement(svg,'path',
{'d':path,
'style':self.getStyle(width=0.1),
'id':'path{}'.format(self.next_id())})
# For standard mode with Z ignored, include the paths.
elif self.ignore_z:
for path in self.paths:
etree.SubElement(svg,'path',
{'d':path,
'style':self.getStyle(),
'id':'path{}'.format(self.next_id())})
# For standard mode with Z grouping, filter the paths,
# then add each group of paths (and optionally, labels).
else:
self.filterPaths()
z_depths = sorted(self.paths_by_z)
depth_num = 0
for i in range(0,len(z_depths)):
if len(self.paths_by_z[z_depths[i]]):
params = {'id':('group{}-{}'
).format(self.next_id(),z_depths[i]),
'style':self.getStyle()}
group = etree.SubElement(svg,'g',params)
# If labels are enabled, add the label to the group.
if self.label_z:
params = {'x':'{}'.format(self.maxX),
'y':'{}'.format(depth_num*-5),
'transform':'scale(1,-1)',
'id':'text{}'.format(i),
'style':('opacity:1;fill:#0000ff;'
'fill-opacity:1;stroke:none;'
'font-size:4.5')}
if self.unit == 1.0:
label = '{} mm'.format(z_depths[i])
else:
label = '{} in'.format(z_depths[i]/self.unit)
etree.SubElement(group,'text',params
).text = label
depth_num += 1
# Now add the paths to the group.
for path in self.paths_by_z[z_depths[i]]:
id = 'path{}'.format(self.next_id())
etree.SubElement(group,'path',
{'d':path,
'style':self.getStyle(),
'id':id})
# If labels are enabled, label the labels.
if self.label_z:
etree.SubElement(svg,'text',
{'x':'{}'.format(self.maxX),
'y':'{}'.format(depth_num*-5),
'transform':'scale(1,-1)',
'id':'text{}'.format(i),
'style':('opacity:1;fill:#0000ff;'
'fill-opacity:1;stroke:none;'
'font-size:4.5')}
).text = 'Z Groups:'
# And now for the code to allow Inkscape to run our lovely extension.
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=('usage: %prog [options] GCodeFile'))
parser.add_argument('-m', '--mode', help='Mode: vcarve, standard, laser', default='standard')
parser.add_argument('-a', '--v_angle', help='Included (full) angle for V-bit, in degrees.', default=None)
parser.add_argument('-t', '--v_top', help='Stock top (usually zero)', default=None)
parser.add_argument('-s', '--v_step', help='Step size for curve interpolation.', default=None)
parser.add_argument('-d', '--tool_diameter', help='Tool diameter / path width.', default=None)
parser.add_argument('-u', '--units', help='Dialog units.', default='mm')
parser.add_argument('-z', '--z_axis', help='Z-axis: ignore,group,label', default=False)
parser.add_argument('--tab')
parser.add_argument('--inputhelp')
parser.add_argument('inputfile')
# Now, process, my lovelies!
args = parser.parse_args()
# First steps first, what mode?
v_carve = False
ignore_z = False
laser_mode = False
if (args.mode == 'vcarve'):
v_carve = True
elif (args.mode == 'laser'):
laser_mode = True
# V-carve parameters.
try:
v_angle = round(float(args.v_angle),3)
except ValueError:
v_angle = 1.0
try:
v_top = round(float(args.v_top) *
(25.4 if (args.units == 'in') else 1.0),5)
except ValueError:
v_top = 0.0
try:
v_step = round(float(args.v_step) *
(25.4 if (args.units == 'in') else 1.0),5)
except ValueError:
v_step = 1.0
# Standard parameters.
try:
diameter = round(float(args.tool_diameter) *
(25.4 if (args.units == 'in') else 1.0),3)
except ValueError:
diameter = 1.0
# General args.
ignore_z = (args.z_axis == 'ignore')
label_z = (args.z_axis == 'label')
gc = ImportGCode(args.inputfile, v_carve, laser_mode, ignore_z, label_z, diameter, v_angle, v_top, v_step)
gc.doc.write(sys.stdout.buffer)

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>GCode Import (*.gcode)</name>
<id>fablabchemnitz.de.gcode_import.gcode</id>
<param name="tab" type="notebook">
<page name="options" gui-text="Options">
<param name="mode" type="optiongroup" appearance="combo" gui-text="GCode Import Mode">
<option value="vcarve">V-Carve</option>
<option value="standard">Standard</option>
<option value="laser">Laser</option>
</param>
<label appearance="header">V-Carve Settings</label>
<param name="v_angle" indent="1" type="float" min="0.001" max="179" precision="0" gui-text="Included (full) angle, degrees">90</param>
<param name="v_top" indent="1" type="float" min="-9999" max="9999" precision="3" gui-text="Z value at top of stock">0</param>
<param name="v_step" indent="1" type="float" min="0.001" max="9999" precision="3" gui-text="Curve interpolation step size">0</param>
<label appearance="header">Standard Mode Settings</label>
<param name="tool_diameter" indent="1" type="float" min="0.001" max="999.999" precision="3" gui-text="Path width / tool diameter">6.35</param>
<label appearance="header">General Options</label>
<param name="units" indent="1" type="optiongroup" appearance="combo" gui-text="Units (in this import dialog)">
<option value="mm">mm</option>
<option value="in">in</option>
</param>
<param name="z_axis" indent="1" type="optiongroup" appearance="combo" gui-text="Z-Axis (except V-Carve)">
<option value="ignore">Ignore Z axis data.</option>
<option value="group">Group by Z if able.</option>
<option value="label">Group by Z, with labels.</option>
</param>
</page>
<page name="help" gui-text="Help">
<label xml:space="preserve">
- This is intended to enable hobby CNC users to recover
geometry from G-code files in order to reconstitute
SVG design files.
- Importing G-code for 3D printing is not an intended target.
- Importing G-code will not result in an immediately
usable SVG, but with some manipulation, cromulent
results may be achieved.
- All individual moves are processed at constant Z
except in V-carve mode, which requires Z data.
- Any K parameters for G2/G3 arcs are summarily ignored.
- Importing a fully 3D carve is unlikely to give a useful result.</label>
</page>
</param>
<input>
<extension>.gcode</extension>
<mimetype>application/x-gcode</mimetype>
<filetypename>GCode File (*.gcode)</filetypename>
<filetypetooltip>Import GCode File</filetypetooltip>
</input>
<script>
<command location="inx" interpreter="python">gcode_import.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>GCode Import (*.nc)</name>
<id>fablabchemnitz.de.gcode_import.nc</id>
<param name="tab" type="notebook">
<page name="options" gui-text="Options">
<param name="mode" type="optiongroup" appearance="combo" gui-text="GCode Import Mode">
<option value="vcarve">V-Carve</option>
<option value="standard">Standard</option>
<option value="laser">Laser</option>
</param>
<label appearance="header">V-Carve Settings</label>
<param name="v_angle" indent="1" type="float" min="0.001" max="179" precision="0" gui-text="Included (full) angle, degrees">90</param>
<param name="v_top" indent="1" type="float" min="-9999" max="9999" precision="3" gui-text="Z value at top of stock">0</param>
<param name="v_step" indent="1" type="float" min="0.001" max="9999" precision="3" gui-text="Curve interpolation step size">0</param>
<label appearance="header">Standard Mode Settings</label>
<param name="tool_diameter" indent="1" type="float" min="0.001" max="999.999" precision="3" gui-text="Path width / tool diameter">6.35</param>
<label appearance="header">General Options</label>
<param name="units" indent="1" type="optiongroup" appearance="combo" gui-text="Units (in this import dialog)">
<option value="mm">mm</option>
<option value="in">in</option>
</param>
<param name="z_axis" indent="1" type="optiongroup" appearance="combo" gui-text="Z-Axis (except V-Carve)">
<option value="ignore">Ignore Z axis data.</option>
<option value="group">Group by Z if able.</option>
<option value="label">Group by Z, with labels.</option>
</param>
</page>
<page name="help" gui-text="Help">
<label xml:space="preserve">
- This is intended to enable hobby CNC users to recover
geometry from G-code files in order to reconstitute
SVG design files.
- Importing G-code for 3D printing is not an intended target.
- Importing G-code will not result in an immediately
usable SVG, but with some manipulation, cromulent
results may be achieved.
- All individual moves are processed at constant Z
except in V-carve mode, which requires Z data.
- Any K parameters for G2/G3 arcs are summarily ignored.
- Importing a fully 3D carve is unlikely to give a useful result.</label>
</page>
</param>
<input>
<extension>.nc</extension>
<mimetype>application/x-gcode</mimetype>
<filetypename>GCode File (*.nc)</filetypename>
<filetypetooltip>Import GCode File</filetypetooltip>
</input>
<script>
<command location="inx" interpreter="python">gcode_import.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,21 @@
[
{
"name": "GCode Import <various>",
"id": "fablabchemnitz.de.gcode_import",
"path": "gcode_import.<various>",
"dependent_extensions": null,
"original_name": "<various>",
"original_id": "com.ClayJar.GCodeImport.<various>",
"license": "GNU GPL v2",
"license_url": "https://github.com/ClayJarCom/ImportGCode/blob/master/LICENSE",
"comment": "",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/gcode_import",
"fork_url": "https://github.com/ClayJarCom/ImportGCode",
"documentation_url": "https://stadtfabrikanten.org/display/IFM/GCode+Import",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/ClayJarCom",
"github.com/vmario89"
]
}
]

View File

@ -9,14 +9,14 @@
"license": "GNU GPL v3",
"license_url": "https://inkscape.org/de/~DrWiggly/%E2%98%85guillocheextensions-for-v1x",
"comment": "fork of https://inkscape.org/de/~fluent_user/%E2%98%85guilloche-pattern-extension",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/guilloche_creations",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/guilloche_creations",
"fork_url": "https://inkscape.org/de/~DrWiggly/%E2%98%85guillocheextensions-for-v1x",
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Guilloche+Pattern",
"inkscape_gallery_url": null,
"main_authors": [
"inkscape.org/fluent_user",
"inkscape.org/DrWiggly",
"github.com/vmario89"
"github.com/eridur-de"
]
}
]

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Inventory Sticker</name>
<id>fablabchemnitz.de.inventory_sticker</id>
<param name="tab" type="notebook">
<page name="tab_settings" gui-text="Settings">
<label appearance="header">Inventory Download</label>
<param name="server_address" type="string" gui-text="inventory.csv URL">https://the.domain.de/items.csv</param>
<param name="htuser" type="string" gui-text="Basic Auth User">User</param>
<param name="htpassword" type="string" gui-text="Basic Auth Password">Password</param>
<label appearance="header">Sticker Customization</label>
<param name="target_url" type="string" gui-text="Target URL" gui-description="The URL which will be embedded into DataMatrix">qwa.es</param>
<param name="target_owner" type="string" gui-text="Owner">Stadtfabrikanten e.V.</param>
<label appearance="header">Sticker Export options</label>
<param name="sticker_ids" type="string" gui-text="Sticker Ids" gui-description="comma-separated list of numeric Ids. Type * (wildcard) to generate just ALL possible Ids">1</param>
<param name="export_dir" type="path" gui-text="Export directory" gui-description="The directory to export the stickers" mode="folder">/home/</param>
<param name="flat_export" type="bool" gui-text="Flat export" gui-description="If enabled no sub directories are created.">false</param>
<param name="export_svg" type="bool" gui-text="Export SVG">true</param>
<param name="export_png" type="bool" gui-text="Export PNG">false</param>
<param name="print_png" type="int" gui-text="Print PNG to Brother QL-720NW (count)" gui-description="Enter desired amount of stickers to print for each Id">0</param>
<param name="print_device" type="string" gui-text="Printer interface (USB)" gui-description="[VendorID:ProductID], Example: 04f9:2044">04f9:2044</param>
<param name="preview" type="bool" gui-text="Generate preview only" gui-description="If enabled stickers will not be exported. Just generate sticker for the first given Id">false</param>
</page>
<page name="tab_about" gui-text="About">
<label appearance="header">Inventory Sticker</label>
<label>This extension generates inventory stickers for thermo printers (we use a Brother QL-720NW) from our Teedy instance. Teedy is an open source software document management system (DMS).</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/inventorysticker</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X</label>
<label appearance="url">mailto:mario.voigt@stadtfabrikanten.org</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="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>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Cutting/Plotting/Printing"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">inventory_sticker.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,652 @@
#!/usr/bin/env python3
#
# An extension to generate SVG/PNG labels (stickers) for use with our item inventory system.
# It pulls a .csv file from a server URL (protected by basic auth) and exports and prints the labels to a Brother QL-720NW label printer
# Documentation: https://wiki.fablabchemnitz.de/display/TEED/Werkstattorientierung+im+FabLab+-+Digtales+Inventar
#
# Made by FabLab Chemnitz / Stadtfabrikanten e.V. - Developer: Mario Voigt (year 2021)
#
# This extension is based on the "original" barcode extension included in default InkScape Extension Set, which is licensed by the following:
#
# Copyright (C) 2009 John Beard john.j.beard@gmail.com
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
import csv
import os
import shutil
import urllib.request
from lxml import etree
import inkex
from inkex import Rectangle
from inkex.command import inkscape
import re
import subprocess
from subprocess import Popen, PIPE
INVALID_BIT = 2
# CODEWORD STREAM GENERATION =========================================
# take the text input and return the codewords,
# including the Reed-Solomon error-correcting codes.
# =====================================================================
def get_codewords(text, nd, nc, inter, size144):
# convert the data to the codewords
data = list(encode_to_ascii(text))
if not size144: # render a "normal" datamatrix
data_blocks = partition_data(data, nd * inter) # partition into data blocks of length nd*inter -> inter Reed-Solomon block
data_blocks = interleave(data_blocks, inter) # interleave consecutive inter blocks if required
data_blocks = reed_solomon(data_blocks, nd, nc) # generate and append the Reed-Solomon codewords
data_blocks = combine_interleaved(data_blocks, inter, nd, nc, False) # concatenate Reed-Solomon blocks bound for the same datamatrix
return data_blocks
# Takes a codeword stream and splits up into "inter" blocks.
# eg interleave( [1,2,3,4,5,6], 2 ) -> [1,3,5], [2,4,6]
def interleave(blocks, inter):
if inter == 1: # if we don"t have to interleave, just return the blocks
return blocks
else:
result = []
for block in blocks: # for each codeword block in the stream
block_length = int(len(block) / inter) # length of each interleaved block
inter_blocks = [[0] * block_length for i in range(inter)] # the interleaved blocks
for i in range(block_length): # for each element in the interleaved blocks
for j in range(inter): # for each interleaved block
inter_blocks[j][i] = block[i * inter + j]
result.extend(inter_blocks) # add the interleaved blocks to the output
return result
# Combine interleaved blocks into the groups for the same datamatrix
#
# e.g combine_interleaved( [[d1, d3, d5, e1, e3, e5], [d2, d4, d6, e2, e4, e6]], 2, 3, 3 )
# --> [[d1, d2, d3, d4, d5, d6, e1, e2, e3, e4, e5, e6]]
def combine_interleaved(blocks, inter, nd, nc, size144):
if inter == 1: # the blocks aren"t interleaved
return blocks
else:
result = []
for i in range(len(blocks) // inter): # for each group of "inter" blocks -> one full datamatrix
data_codewords = [] # interleaved data blocks
if size144:
nd_range = 1558 # 1558 = 156*8 + 155*2
nc_range = 620 # 620 = 62*8 + 62*2
else:
nd_range = nd * inter
nc_range = nc * inter
for j in range(nd_range): # for each codeword in the final list
data_codewords.append(blocks[i * inter + j % inter][j // inter])
for j in range(nc_range): # for each block, add the ecc codewords
data_codewords.append(blocks[i * inter + j % inter][nd + j // inter])
result.append(data_codewords)
return result
def encode_to_ascii(text):
"""Encode this text into chunks, ascii or digits"""
i = 0
while i < len(text):
# check for double digits, if the next char is also a digit
if text[i].isdigit() and (i < len(text) - 1) and text[i + 1].isdigit():
yield int(text[i] + text[i + 1]) + 130
i += 2 # move on 2 characters
else: # encode as a normal ascii,
yield ord(text[i]) + 1 # codeword is ASCII value + 1 (ISO 16022:2006 5.2.3)
i += 1 # next character
# partition data into blocks of the appropriate size to suit the
# Reed-Solomon block being used.
# e.g. partition_data([1,2,3,4,5], 3) -> [[1,2,3],[4,5,PAD]]
def partition_data(data, rs_data):
PAD_VAL = 129 # PAD codeword (ISO 16022:2006 5.2.3)
data_blocks = []
i = 0
while i < len(data):
if len(data) >= i + rs_data: # we have a whole block in our data
data_blocks.append(data[i:i + rs_data])
i = i + rs_data
else: # pad out with the pad codeword
data_block = data[i:len(data)] # add any remaining data
pad_pos = len(data)
padded = False
while len(data_block) < rs_data: # and then pad with randomised pad codewords
if not padded:
data_block.append(PAD_VAL) # add a normal pad codeword
padded = True
else:
data_block.append(randomise_pad_253(PAD_VAL, pad_pos))
pad_pos += 1
data_blocks.append(data_block)
break
return data_blocks
# Pad character randomisation, to prevent regular patterns appearing
# in the data matrix
def randomise_pad_253(pad_value, pad_position):
pseudo_random_number = ((149 * pad_position) % 253) + 1
randomised = pad_value + pseudo_random_number
if randomised <= 254:
return randomised
else:
return randomised - 254
# REED-SOLOMON ENCODING ROUTINES =====================================
# "prod(x,y,log,alog,gf)" returns the product "x" times "y"
def prod(x, y, log, alog, gf):
if x == 0 or y == 0:
return 0
else:
result = alog[(log[x] + log[y]) % (gf - 1)]
return result
# generate the log & antilog lists:
def gen_log_alog(gf, pp):
log = [0] * gf
alog = [0] * gf
log[0] = 1 - gf
alog[0] = 1
for i in range(1, gf):
alog[i] = alog[i - 1] * 2
if alog[i] >= gf:
alog[i] = alog[i] ^ pp
log[alog[i]] = i
return log, alog
# generate the generator polynomial coefficients:
def gen_poly_coeffs(nc, log, alog, gf):
c = [0] * (nc + 1)
c[0] = 1
for i in range(1, nc + 1):
c[i] = c[i - 1]
j = i - 1
while j >= 1:
c[j] = c[j - 1] ^ prod(c[j], alog[i], log, alog, gf)
j -= 1
c[0] = prod(c[0], alog[i], log, alog, gf)
return c
# "ReedSolomon(wd,nd,nc)" takes "nd" data codeword values in wd[]
# and adds on "nc" check codewords, all within GF(gf) where "gf" is a
# power of 2 and "pp" is the value of its prime modulus polynomial */
def reed_solomon(data, nd, nc):
# parameters of the polynomial arithmetic
gf = 256 # operating on 8-bit codewords -> Galois field = 2^8 = 256
pp = 301 # prime modulus polynomial for ECC-200 is 0b100101101 = 301 (ISO 16022:2006 5.7.1)
log, alog = gen_log_alog(gf, pp)
c = gen_poly_coeffs(nc, log, alog, gf)
for block in data: # for each block of data codewords
block.extend([0] * (nc + 1)) # extend to make space for the error codewords
# generate "nc" checkwords in the list block
for i in range(0, nd):
k = block[nd] ^ block[i]
for j in range(0, nc):
block[nd + j] = block[nd + j + 1] ^ prod(k, c[nc - j - 1], log, alog, gf)
block.pop()
return data
# MODULE PLACEMENT ROUTINES===========================================
# These routines take a steam of codewords, and place them into the
# DataMatrix in accordance with Annex F of BS ISO/IEC 16022:2006
def bit(byte, bit_ch):
"""bit() returns the bit"th bit of the byte"""
# the MSB is bit 1, LSB is bit 8
return (byte >> (8 - bit_ch)) % 2
def module(array, nrow, ncol, row, col, bit_ch):
"""place a given bit with appropriate wrapping within array"""
if row < 0:
row = row + nrow
col = col + 4 - ((nrow + 4) % 8)
if col < 0:
col = col + ncol
row = row + 4 - ((ncol + 4) % 8)
array[row][col] = bit_ch
def place_square(case, array, nrow, ncol, row, col, char):
"""Populate corner cases (0-3) and utah case (-1)"""
for i in range(8):
x, y = [
[(row - 1, 0), (row - 1, 1), (row - 1, 2), (0, col - 2),
(0, col - 1), (1, col - 1), (2, col - 1), (3, col - 1)],
[(row - 3, 0), (row - 2, 0), (row - 1, 0), (0, col - 4),
(0, col - 3), (0, col - 2), (0, col - 1), (1, col - 1)],
[(row - 3, 0), (row - 2, 0), (row - 1, 0), (0, col - 2),
(0, col - 1), (1, col - 1), (2, col - 1), (3, col - 1)],
[(row - 1, 0), (row - 1, col - 1), (0, col - 3), (0, col - 2),
(0, col - 1), (1, col - 3), (1, col - 2), (1, col - 1)],
# "utah" places the 8 bits of a utah-shaped symbol character in ECC200
[(row - 2, col -2), (row - 2, col -1), (row - 1, col - 2), (row - 1, col - 1),
(row - 1, col), (row, col - 2), (row, col - 1), (row, col)],
][case][i]
module(array, nrow, ncol, x, y, bit(char, i + 1))
return 1
def place_bits(data, nrow, ncol):
"""fill an nrow x ncol array with the bits from the codewords in data."""
# initialise and fill with -1"s (invalid value)
array = [[INVALID_BIT] * ncol for i in range(nrow)]
# Starting in the correct location for character #1, bit 8,...
char = 0
row = 4
col = 0
while True:
# first check for one of the special corner cases, then...
if (row == nrow) and (col == 0):
char += place_square(0, array, nrow, ncol, nrow, ncol, data[char])
elif (row == nrow - 2) and (col == 0) and (ncol % 4):
char += place_square(1, array, nrow, ncol, nrow, ncol, data[char])
elif (row == nrow - 2) and (col == 0) and (ncol % 8 == 4):
char += place_square(2, array, nrow, ncol, nrow, ncol, data[char])
elif (row == nrow + 4) and (col == 2) and ((ncol % 8) == 0):
char += place_square(3, array, nrow, ncol, nrow, ncol, data[char])
# sweep upward diagonally, inserting successive characters,...
while (row >= 0) and (col < ncol):
if (row < nrow) and (col >= 0) and (array[row][col] == INVALID_BIT):
char += place_square(-1, array, nrow, ncol, row, col, data[char])
row -= 2
col += 2
row += 1
col += 3
# & then sweep downward diagonally, inserting successive characters,...
while (row < nrow) and (col >= 0):
if (row >= 0) and (col < ncol) and (array[row][col] == INVALID_BIT):
char += place_square(-1, array, nrow, ncol, row, col, data[char])
row += 2
col -= 2
row += 3
col += 1
# ... until the entire array is scanned
if not ((row < nrow) or (col < ncol)):
break
# Lastly, if the lower righthand corner is untouched, fill in fixed pattern */
if array[nrow - 1][ncol - 1] == INVALID_BIT:
array[nrow - 1][ncol - 2] = 0
array[nrow - 1][ncol - 1] = 1
array[nrow - 2][ncol - 1] = 0
array[nrow - 2][ncol - 2] = 1
return array # return the array of 1"s and 0"s
def add_finder_pattern(array, data_nrow, data_ncol, reg_row, reg_col):
# get the total size of the datamatrix
nrow = (data_nrow + 2) * reg_row
ncol = (data_ncol + 2) * reg_col
datamatrix = [[0] * ncol for i in range(nrow)] # initialise and fill with 0"s
for i in range(reg_col): # for each column of data regions
for j in range(nrow):
datamatrix[j][i * (data_ncol + 2)] = 1 # vertical black bar on left
datamatrix[j][i * (data_ncol + 2) + data_ncol + 1] = j % 2 # alternating blocks
for i in range(reg_row): # for each row of data regions
for j in range(ncol):
datamatrix[i * (data_nrow + 2) + data_nrow + 1][j] = 1 # horizontal black bar at bottom
datamatrix[i * (data_nrow + 2)][j] = (j + 1) % 2 # alternating blocks
for i in range(data_nrow * reg_row):
for j in range(data_ncol * reg_col):
# offset by 1, plus two for every addition block
dest_col = j + 1 + 2 * (j // data_ncol)
dest_row = i + 1 + 2 * (i // data_nrow)
datamatrix[dest_row][dest_col] = array[i][j] # transfer from the plain bit array
return datamatrix
def get_valid_filename(s):
s = str(s).strip().replace(" ", "_")
return re.sub(r"(?u)[^-\w.]", "", s)
def splitAt(string, length):
return ' '.join(string[i:i+length] for i in range(0,len(string),length))
class InventorySticker(inkex.Effect):
def add_arguments(self, pars):
pars.add_argument("--tab")
pars.add_argument("--server_address", default="https://the.domain.de/items.csv")
pars.add_argument("--htuser", default="user")
pars.add_argument("--htpassword", default="password")
pars.add_argument("--sticker_ids", default="*")
pars.add_argument("--target_url", default="qwa.es")
pars.add_argument("--target_owner", default="Stadtfabrikanten e.V.")
pars.add_argument("--export_dir", default="/home/")
pars.add_argument("--flat_export", type=inkex.Boolean, default=False)
pars.add_argument("--preview", type=inkex.Boolean, default=False)
pars.add_argument("--export_svg", type=inkex.Boolean, default=True)
pars.add_argument("--export_png", type=inkex.Boolean, default=False)
pars.add_argument("--print_png", type=int, default=0)
pars.add_argument("--print_device", default="04f9:2044")
def effect(self):
# Adjust the document view for the desired sticker size
root = self.svg.getElement("//svg:svg")
subline_fontsize = 40 #px; one line of bottom text (id and owner) creates a box of that height
#our DataMatrix has size 16x16, each cube is sized by 16x16px -> total size is 256x256px. We use 4px padding for all directions
DataMatrix_xy = 16
DataMatrix_height = 16 * DataMatrix_xy
DataMatrix_width = DataMatrix_height
sticker_padding = 4
sticker_height = DataMatrix_height + subline_fontsize + 3 * sticker_padding
sticker_width = 696
#configure font sizes and box heights to define how large the font size may be at maximum (to omit overflow)
objectNameMaxHeight = sticker_height - 2 * subline_fontsize - 4 * sticker_padding
objectNameMaxLines = 5
objectNameFontSize = objectNameMaxHeight / objectNameMaxLines #px; generate main font size from lines and box size
root.set("width", str(sticker_width) + "px")
root.set("height", str(sticker_height) + "px")
root.set("viewBox", "%f %f %f %f" % (0, 0, sticker_width, sticker_height))
#clean the document (make it blank) to avoid printing duplicated things
for node in self.document.xpath('//*', namespaces=inkex.NSS):
if node.TAG not in ('svg', 'defs', 'namedview'):
node.delete()
#set the document units
self.document.getroot().find(inkex.addNS("namedview", "sodipodi")).set("inkscape:document-units", "px")
# Download the recent inventory CSV file and parse line by line to create an inventory sticker
password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
password_mgr.add_password(None, self.options.server_address, self.options.htuser, self.options.htpassword)
handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
opener = urllib.request.build_opener(handler)
try:
inventoryData = opener.open(self.options.server_address).read().decode("utf-8")
urllib.request.install_opener(opener)
inventoryCSVParent = os.path.join(self.options.export_dir, "InventorySticker")
inventoryCSV = os.path.join(inventoryCSVParent, "inventory.csv")
# To avoid messing with old stickers we remove the directory on Client before doing something new
shutil.rmtree(inventoryCSVParent, ignore_errors=True) #remove the output directory before doing new job
# we are going to write the imported Server CSV file temporarily. Otherwise CSV reader seems to mess with the file if passed directly
if not os.path.exists(inventoryCSVParent):
os.mkdir(inventoryCSVParent)
with open(inventoryCSV, 'w', encoding="utf-8") as f:
f.write(inventoryData)
f.close()
#parse sticker Ids from user input
if self.options.sticker_ids != "*":
sticker_ids = self.options.sticker_ids.split(",")
else:
sticker_ids = None
with open(inventoryCSV, 'r', encoding="utf-8") as csv_file:
csv_reader = csv.reader(csv_file, delimiter=",")
totalOutputs = 0
for row in csv_reader:
internal_id = row[0]
doc_title = row[1]
sticker_id = row[2]
level = row[3]
zone = row[4]
if sticker_ids is None or sticker_id in sticker_ids:
totalOutputs += 1
#create new sub directories for each non-existent FabLab zone (if flat export is disabled)
if self.options.flat_export == False:
if not zone:
zoneDir = os.path.join(inventoryCSVParent, get_valid_filename("Keinem Bereich zugeordnet"))
else:
zoneDir = os.path.join(inventoryCSVParent, get_valid_filename(zone)) #remove invalid charaters from zone
if not os.path.exists(zoneDir):
os.mkdir(zoneDir)
else:
zoneDir = inventoryCSVParent #use top directory
#Generate the recent sticker content
stickerGroup = self.document.getroot().add(inkex.Group(id="InventorySticker_Id" + sticker_id)) #make a new group at root level
DataMatrixStyle = inkex.Style({"stroke": "none", "stroke-width": "1", "fill": "#000000"})
DataMatrixAttribs = {"style": str(DataMatrixStyle), "height": str(DataMatrix_xy) + "px", "width": str(DataMatrix_xy) + "px"}
# 1 - create DataMatrix (create a 2d list corresponding to the 1"s and 0s of the DataMatrix)
encoded = self.encode(self.options.target_url + "/" + sticker_id)
DataMatrixGroup = stickerGroup.add(inkex.Group(id="DataMatrix_Id" + sticker_id)) #make a new group at root level
for x, y in self.render_data_matrix(encoded, DataMatrix_xy):
DataMatrixAttribs.update({"x": str(x + sticker_padding), "y": str(y + sticker_padding)})
etree.SubElement(DataMatrixGroup, inkex.addNS("rect","svg"), DataMatrixAttribs)
inline_size = sticker_width - DataMatrix_width - 3 * sticker_padding #remaining width for objects next to the DataMatrix
x_pos = DataMatrix_width + 2 * sticker_padding
# 2 - Add Object Name Text
objectName = etree.SubElement(stickerGroup,
inkex.addNS("text", "svg"),
{
"font-size": str(objectNameFontSize) + "px",
"x": str(x_pos) + "px",
#"xml:space": "preserve", #we cannot add this here because InkScape throws an error
"y": str(objectNameFontSize) + "px",
"text-align" : "left",
"text-anchor": "left",
"vertical-align" : "bottom",
#style: inline-size required for text wrapping inside box; letter spacing is required to remove the additional whitespaces. The letter spacing depends to the selected font family (Miso)
"style": str(inkex.Style({"fill": "#000000", "writing-mode": "horizontal-tb", "inline-size": str(inline_size) + "px", "stroke": "none", "font-family": "Miso", "font-weight": "bold", "letter-spacing": "-3.66px"}))
}
)
objectName.set("id", "objectName_Id" + sticker_id)
objectName.set("xml:space", "preserve") #so we add it here instead .. if multiple whitespaces in text are coming after each other just render them (preserve!)
objectNameTextSpan = etree.SubElement(objectName, inkex.addNS("tspan", "svg"), {})
objectNameTextSpan.text = splitAt(doc_title, 1) #add 1 whitespace after each chacter. So we can simulate a in-word line break (break by char instead by word)
# 3 - Add Object Id Text - use the same position but revert text anchors/align
objectId = etree.SubElement(stickerGroup,
inkex.addNS("text", "svg"),
{
"font-size": str(subline_fontsize) + "px",
"x": str(sticker_padding) + "px",
"y": "30px",
"transform": "translate(0," + str(sticker_height - subline_fontsize) + ")",
"text-align" : "left",
"text-anchor": "left",
"vertical-align" : "bottom",
"style": str(inkex.Style({"fill": "#000000", "inline-size":str(inline_size) + "px", "stroke": "none", "font-family": "Miso", "font-weight": "bold"})) #inline-size required for text wrapping
}
)
objectId.set("id", "objectId_Id" + sticker_id)
objectIdTextSpan = etree.SubElement(objectId, inkex.addNS("tspan", "svg"), {})
objectIdTextSpan.text = "Thing #" + sticker_id
# 4 - Add Owner Text
owner = etree.SubElement(stickerGroup,
inkex.addNS("text", "svg"),
{
"font-size": str(subline_fontsize) + "px",
"x": str(x_pos) + "px",
"y": "30px",
"transform": "translate(0," + str(sticker_height - subline_fontsize) + ")",
"text-align" : "right",
"text-anchor": "right",
"vertical-align" : "bottom",
"style": str(inkex.Style({"fill": "#000000", "inline-size":str(inline_size) + "px", "stroke": "none", "font-family": "Miso", "font-weight": "300"})) #inline-size required for text wrapping
}
)
owner.set("id", "owner_Id" + sticker_id)
ownerTextSpan = etree.SubElement(owner, inkex.addNS("tspan", "svg"), {})
ownerTextSpan.text = self.options.target_owner
# 5 - Add Level Text
levelText = etree.SubElement(stickerGroup,
inkex.addNS("text", "svg"),
{
"font-size": str(subline_fontsize) + "px",
"x": str(x_pos) + "px",
"y": "30px",
"transform": "translate(0," + str(sticker_height - subline_fontsize - subline_fontsize) + ")",
"text-align" : "right",
"text-anchor": "right",
"vertical-align" : "bottom",
"style": str(inkex.Style({"fill": "#000000", "inline-size":str(inline_size) + "px", "stroke": "none", "font-family": "Miso", "font-weight": "bold"})) #inline-size required for text wrapping
}
)
levelText.set("id", "level_Id" + sticker_id)
levelTextTextSpan = etree.SubElement(levelText, inkex.addNS("tspan", "svg"), {})
levelTextTextSpan.text = level
# 6 - Add horizontal divider line
line_thickness = 2 #px
line_x_pos = 350 #px; start of the line (left coord)
line_length = sticker_width - line_x_pos
divider = etree.SubElement(stickerGroup,
inkex.addNS("path", "svg"),
{
"d": "m " + str(line_x_pos) + "," + str(sticker_height - subline_fontsize - subline_fontsize) + " h " + str(line_length) ,
"style": str(inkex.Style({"fill": "none", "stroke": "#000000", "stroke-width": str(line_thickness) + "px", "stroke-linecap": "butt", "stroke-linejoin":"miter", "stroke-opacity": "1"})) #inline-size required for text wrapping
}
)
divider.set("id", "divider_Id" + sticker_id)
if self.options.preview == False:
export_file_name = sticker_id + "_" + get_valid_filename(doc_title)
export_file_path = os.path.join(zoneDir, export_file_name)
#"Export" as SVG by just copying the recent SVG document to the target directory. We need to remove special characters to have valid file names on Windows/Linux
export_file_svg = open(export_file_path + ".svg", "w", encoding="utf-8")
export_file_svg.write(str(etree.tostring(self.document), "utf-8"))
export_file_svg.close()
if self.options.export_png == False and self.options.export_svg == False:
inkex.errormsg("Nothing to export. Generating preview only ...")
break
if self.options.export_png == True: #we need to generate SVG before to get PNG. But if user selected PNG only we need to remove SVG afterwards
#Make PNG from SVG (slow because each file is picked up separately. Takes about 10 minutes for 600 files
inkscape(export_file_path + ".svg", actions="export-dpi:96;export-background:white;export-filename:{file_name};export-do;FileClose".format(file_name=export_file_path + ".png"))
#fix for "usb.core.USBError: [Errno 13] Access denied (insufficient permissions)"
#echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2044", MODE="666"' > /etc/udev/rules.d/99-garmin.rules && sudo udevadm trigger
if self.options.print_png > 0:
if self.options.export_png == False:
inkex.errormsg("No file output for printing. Please set 'Export PNG' to true first.")
else:
for x in range(self.options.print_png):
command = "brother_ql -m QL-720NW --backend pyusb --printer usb://" + self.options.print_device + " print -l 62 --600dpi -r auto " + export_file_path + ".png"
p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE) #forr Windows: shell=False
stdout, stderr = p.communicate()
p.wait()
if p.returncode != 0:
std_out = stdout.decode('utf-8')
std_err = stderr.decode('utf-8')
if std_err.endswith("ValueError: Device not found\n") is True:
self.msg("Printer device not found or offline. Check for power, cables and entered printer interface ID")
else:
inkex.errormsg("brother_ql returned errors:\nError code {:d}\n{}\n{}".format(p.returncode, std_out, std_err))
if self.options.export_svg != True: #If user selected PNG only we need to remove SVG again
os.remove(export_file_path + ".svg")
self.document.getroot().remove(stickerGroup) #remove the stickerGroup again
else: #create preview by just breaking the for loop without executing remove(stickerGroup)
break
csv_file.close()
if totalOutputs == 0:
self.msg("No output was generated. Check if your entered IDs are valid!")
except Exception as e:
inkex.errormsg(e)
#inkex.errormsg("Wrong inventory.csv URL or invalid credentials for Basic Auth")
# parameters for the selected datamatrix size
# drow number of rows in each data region
# dcol number of cols in each data region
# reg_row number of rows of data regions
# reg_col number of cols of data regions
# nd number of data codewords per reed-solomon block
# nc number of ECC codewords per reed-solomon block
# inter number of interleaved Reed-Solomon blocks
def encode(self, text, nrow = 16, ncol = 16, data_nrow = 14, data_ncol = 14, reg_row = 1, reg_col = 1, nd = 12, nc = 12, inter = 1):
"""
Take an input string and convert it to a sequence (or sequences)
of codewords as specified in ISO/IEC 16022:2006 (section 5.2.3)
"""
# generate the codewords including padding and ECC
codewords = get_codewords(text, nd, nc, inter, nrow == 144)
# break up into separate arrays if more than one DataMatrix is needed
module_arrays = []
for codeword_stream in codewords: # for each datamatrix
# place the codewords" bits across the array as modules
bit_array = place_bits(codeword_stream, data_nrow * reg_row, data_ncol * reg_col)
# add finder patterns around the modules
module_arrays.append(add_finder_pattern(bit_array, data_nrow, data_ncol, reg_row, reg_col))
return module_arrays
def render_data_matrix(self, module_arrays, size):
"""turn a 2D array of 1"s and 0"s into a set of black squares"""
spacing = 16 * size * 1.5
for i, line in enumerate(module_arrays):
height = len(line)
width = len(line[0])
for y in range(height): # loop over all the modules in the datamatrix
for x in range(width):
if line[y][x] == 1: # A binary 1 is a filled square
yield (x * size + i * spacing, y * size)
elif line[y][x] == INVALID_BIT: # we have an invalid bit value
inkex.errormsg("Invalid bit value, {}!".format(line[y][x]))
if __name__ == "__main__":
InventorySticker().run()

View File

@ -0,0 +1,20 @@
[
{
"name": "Inventory Sticker",
"id": "fablabchemnitz.de.inventory_sticker",
"path": "inventory_sticker",
"dependent_extensions": null,
"original_name": "Inventory Sticker",
"original_id": "fablabchemnitz.de.inventory_sticker",
"license": "GNU GPL v3",
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE",
"comment": "",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/inventory_sticker",
"fork_url": null,
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Inventory+Sticker",
"inkscape_gallery_url": "https://inkscape.org/de/~MarioVoigt/%E2%98%85inventory-sticker",
"main_authors": [
"github.com/eridur-de"
]
}
]

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Lasercut Jigsaw</name>
<id>fablabchemnitz.de.lasercut_jigsaw</id>
<param name="tab" type="notebook">
<page name="Style" gui-text="Style">
<label>The Jigsaw lines color does not apply if 'Create separated pieces' option is enabled.</label>
<param name="color_border" type="color" appearance="colorbutton" gui-text="Border color">4278190335</param>
<param name="color_jigsaw" type="color" appearance="colorbutton" gui-text="Jigsaw lines color">65535</param>
</page>
<page name="Dimensions" gui-text="Dimensions">
<label>Define the Jigsaw size and grid size.</label>
<param name="sizetype" gui-text="Width/Height for ..." type="optiongroup" appearance="radio">
<option value="boxsize">box size</option>
<option value="partsize">part size</option>
</param>
<param name="width" type="float" min="0.1" max="1000.0" precision="2" gui-text="Width">100.0</param>
<param name="height" type="float" min="0.1" max="1000.0" precision="2" gui-text="Height">80.0</param>
<param name="innerradius" type="float" min="0.0" max="500.0" precision="2" gui-text="Corner radius" gui-description="0 implies square corners">5.0</param>
<param name="units" gui-text="Units" type="optiongroup" appearance="combo" gui-description="The unit of the box dimensions">
<option value="px">px</option>
<option value="pt">pt</option>
<option value="in">in</option>
<option value="cm">cm</option>
<option value="mm">mm</option>
</param>
<param name="border" type="bool" gui-text="Outer Border" gui-description="Add Outer Surround">false</param>
<param name="borderwidth" type="float" min="0.0" max="500.0" precision="2" gui-text="Border width" gui-description="Size of external surrounding border.">20.0</param>
<param name="outerradius" type="float" min="0.0" max="500.0" precision="2" gui-text="Border radius" gui-description="0 implies square corners">5.0</param>
<param name="pack" type="optiongroup" appearance="combo" gui-text="Pack Location" gui-description="Where to place backing piece on page">
<option value="Right">Right</option>
<option value="Below">Below</option>
<option value="Separate">Separate</option>
</param>
<param name="pieces_W" type="int" min="2" max="199" gui-text="How many pieces across (cols)">5</param>
<param name="pieces_H" type="int" min="2" max="199" gui-text="How many pieces down (rows)">4</param>
</page>
<page name="Notches" gui-text="Notches">
<label>The interlocking pieces can be shaped here. Also the random nature of the layout.</label>
<param name="notch_percent" type="float" min="0.0" max="1.0" precision="2" appearance="full" gui-text="Notch relative size" gui-description="Notch relative size. 0.15 to 0.50 is good">0.5</param>
<param name="rand" type="float" min="0.0" max="1.0" precision="2" appearance="full" gui-text="Grid Randomisation" gui-description="Amount to perturb the basic piece grid.">0.4</param>
<param name="smooth_edges" type="bool" gui-text="Some edges can be smooth" gui-description="Allow pieces with smooth edges.">false</param>
<param name="noknob_frequency" type="float" min="0.0" max="100.0" appearance="full" precision="2" gui-text="percentage of smooth edges">10</param>
<param name="use_seed" type="bool" gui-text="Random jigsaw" gui-description="Use the kerf value as the drawn line width">true</param>
<param name="seed" type="int" min="0" max="99999999" gui-text="or Jigsaw pattern (seed)" gui-description="Random seed for repeatability">12345</param>
<param name="pieces" type="bool" gui-text="Create separated pieces">false</param>
<param name="shift" type="float" min="0.0" max="100.0" precision="2" gui-text="Shifting for each piece (%)">0</param>
</page>
<page name="Usage" gui-text="Usage">
<label xml:space="preserve">Jigsaw lines are single for minimal laser cutting.
(The pieces are not discrete shapes.)
The outer edge can be a rectangle or have rounded corners.
A Surrounding border can be added to frame the jigsaw.
Notch size is related to the averaged Jigsaw piece size.
Randomization creates irregularity for unique pieces.
Adjust Notch size and Randomization to avoid overlapping lines:
- High values of randomization will cause overlapping lines
on small notches.
- Highly unbalanced grids (E.g. 9x2 with 0.5 notches) will
create overlapping lines.
</label>
</page>
</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Shape Generators">
<submenu name="Puzzles/Mazes/Nests" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">lasercut_jigsaw.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,507 @@
#!/usr/bin/env python3
'''
Copyright (C) 2011 Mark Schafer <neon.mark (a) gmail dot com>
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
'''
# Build a Jigsaw puzzle for Lasercutting.
# User defines:
# - dimensions,
# - number of pieces in X and Y,
# - notch size,
# - random amount of perturbation for uniqueness,
# - border and rounding for border and inner corners
# - random or random seed for repeats
### 0.1 make basic jigsaw for lasercut - March 2011
### 0.2 add random seed so repeatable, add pieces for manual booleans - May 2011
### 0.3 add some no-knob edges - June 2019
### Todo
# add option to cut pieces:
# - taking two rows(cols) at a time - reverse the second one and concat on end - add z to close
# - taking a row and a col - do intersect = piece.
__version__ = "0.3"
import sys, math, random, copy
from lxml import etree
import inkex
from inkex import Path, CubicSuperPath, Color
from inkex.command import inkscape
def dirtyFormat(path):
return str(path).replace('[','').replace(']','').replace(',','').replace('\'','')
def randomize(x_y, radius, norm=True, absolute=False):
""" return x,y moved by a random amount inside a radius.
use uniform distribution unless
- norm = True - then use a normal distribution
If absolute is true - ensure random is only added to x,y """
# if norm:
# r = abs(random.normalvariate(0.0,0.5*radius))
# else:
# r = random.uniform(0.0,radius)
x, y = x_y
a = random.uniform(0.0,2*math.pi)
x += math.cos(a)*radius
y += math.sin(a)*radius
if absolute:
x = abs(x)
y = abs(y)
return [x, y]
def add_rounded_rectangle(startx, starty, radius, width, height, style, name, parent, mask=False):
line_path = [['M', [startx, starty+radius]]]
if radius > 0.0: # rounded corners
line_path.append(['c', [0, -radius/2, radius/2, -radius, radius, -radius]])
if mask == "Below":
line_path.append(['m', [width-2*radius, 0,]])
else:
line_path.append(['c', [radius/2, 0, width-2*radius-radius/2, 0, width-2*radius,0 ]]) # top line
line_path.append(['c', [radius/2, 0, radius, radius/2, radius, radius]])
line_path.append(['c', [0, radius/2, 0, height-2*radius-radius/2, 0, height-2*radius]]) # RHS line
line_path.append(['c', [0, radius/2, -radius/2, radius, -radius, radius]])
line_path.append(['c', [-radius/2,0, -width+2*radius+radius/2,0, -width+2*radius,0]]) # bottom line
line_path.append(['c', [-radius/2, 0, -radius, -radius/2, -radius, -radius]])
if mask == "Right":
line_path.append(['m', [0, height]])
else:
line_path.append(['c', [0, -radius/2, 0, -height+2*radius+radius/2, 0, -height+2*radius]]) # LHS line
else: # square corners
if mask == "Below":
line_path.append(['m', [width, 0]])
line_path.append(['l', [0, height, -width, 0, 0, -height]])
elif mask == "Right":
line_path.append(['l', [width, 0, 0, height, -width, 0,]])
else: # separate
line_path.append(['l', [width, 0, 0, height, -width, 0, 0, -height]])
#
#sys.stderr.write("%s\n"% line_path)
attribs = {'style':str(inkex.Style(style)), inkex.addNS('label','inkscape'):name, 'd':dirtyFormat(line_path)}
#sys.stderr.write("%s\n"% attribs)
etree.SubElement(parent, inkex.addNS('path','svg'), attribs )
###----------------------
### all for intersection from http://www.kevlindev.com/gui/index.htm
def get_derivative(polynomial):
deriv = []
for i in range(len(polynomial)):
deriv.append(i* polynomial[i])
return deriv
class LasercutJigsaw(inkex.EffectExtension):
def add_arguments(self, pars):
# General settings
pars.add_argument("--tab")
#Style
pars.add_argument("--color_border", type=Color, default='4278190335', help="Border color")
pars.add_argument("--color_jigsaw", type=Color, default='65535', help="Jigsaw lines color")
#Dimensions
pars.add_argument("--sizetype", default="boxsize")
pars.add_argument("--width", type=float, default=50.0)
pars.add_argument("--height", type=float, default=30.0)
pars.add_argument("--innerradius", type=float, default=5.0, help="0 implies square corners")
pars.add_argument("--units", default="cm", help="The unit of the box dimensions")
pars.add_argument("--border", type=inkex.Boolean, default=False, help="Add Outer Surround")
pars.add_argument("--borderwidth", type=float, default=10.0, help="Size of external surrounding border.")
pars.add_argument("--outerradius", type=float, default=5.0, help="0 implies square corners")
pars.add_argument("--pack", default="Below", help="Where to place backing piece on page")
pars.add_argument("--pieces_W", type=int, default=11, help="How many pieces across")
pars.add_argument("--pieces_H", type=int, default=11, help="How many pieces down")
#Notches
pars.add_argument("--notch_percent", type=float, default=0.0, help="Notch relative size. 0 to 1. 0.15 is good")
pars.add_argument("--rand", type=float, default=0.1, help="Amount to perturb the basic piece grid.")
pars.add_argument("--noknob_frequency", type=float, default=10, help="Percentage of smooth-sided edges.")
pars.add_argument("--smooth_edges", type=inkex.Boolean, default=False, help="Allow pieces with smooth edges.")
pars.add_argument("--use_seed", type=inkex.Boolean, default=False, help="Use the kerf value as the drawn line width")
pars.add_argument("--seed", type=int, default=12345, help="Random seed for repeatability")
pars.add_argument("--pieces", type=inkex.Boolean, default=False, help="Create separated pieces")
pars.add_argument("--shift", type=float, default=0.0, help="Shifting for each piece (%)")
def add_jigsaw_horiz_line(self, startx, starty, stepx, steps, width, style, name, parent):
""" complex version All C smooth
- get ctrl pt offset and use on both sides of each node (negate for smooth)"""
line_path = []
# starts with an M - then C with first point same as M = smooth (also last point still in C but doubled)
line_path.append(['M', [startx, starty]])
clist = [startx, starty] # duplicate 1st point so its smooth
for i in range(1,steps+1):
flip = 1
if random.uniform(0.0,1.0) < 0.5:
flip = -1
do_smooth = False
if self.smooth_edges:
if random.uniform(0.0,100.0) < self.noknob_frequency:
do_smooth = True
if do_smooth:
pt1 = randomize((startx+i*stepx/2+stepx/2*(i-1), starty), self.random_radius/3, True)
rand1 = randomize((0, 0), self.random_radius/4, True, True)
# up to pt1
ctrl1 = (-self.notch_step*1.5, self.notch_step*1.5)
clist.extend([pt1[0]+ctrl1[0]-rand1[0], pt1[1]-ctrl1[1]*flip+rand1[1]*flip])
clist.extend(pt1)
# last ctrl point for next step
clist.extend([pt1[0]-ctrl1[0]+rand1[0], pt1[1]+ctrl1[1]*flip-rand1[1]*flip])
else:
pt1 = randomize((startx-self.notch_step+i*stepx/2+stepx/2*(i-1), starty+self.notch_step/4*flip), self.random_radius/3, True)
pt2 = randomize((startx-self.notch_step+i*stepx/2+stepx/2*(i-1), starty-self.notch_step*flip), self.random_radius/3, True)
# pt3 is foor tip of the notch - required ?
pt4 = randomize((startx+self.notch_step+i*stepx/2+stepx/2*(i-1), starty-self.notch_step*flip), self.random_radius/3, True) #mirror of 2
pt5 = randomize((startx+self.notch_step+i*stepx/2+stepx/2*(i-1), starty+self.notch_step/4*flip), self.random_radius/3, True) # mirror of pt1
# Create random local value for x,y of handle - then reflect to enforce smoothness
rand1 = randomize((0, 0), self.random_radius/4, True, True)
rand2 = randomize((0, 0), self.random_radius/4, True, True)
rand4 = randomize((0, 0), self.random_radius/4, True, True)
rand5 = randomize((0, 0), self.random_radius/4, True, True)
# up to pt1
#ctrl1_2 = (startx+i*stepx/2+(i-1)*stepx/2, starty-self.notch_step/3)
ctrl1 = (self.notch_step/1.2, -self.notch_step/3)
clist.extend([pt1[0]-ctrl1[0]-rand1[0], pt1[1]-ctrl1[1]*flip+rand1[1]*flip])
clist.extend(pt1)
# up to pt2
clist.extend([pt1[0]+ctrl1[0]+rand1[0], pt1[1]+ctrl1[1]*flip-rand1[1]*flip])
ctrl2 = (0, -self.notch_step/1.2)
clist.extend([pt2[0]+ctrl2[0]-rand2[0], pt2[1]-ctrl2[1]*flip+rand2[1]*flip])
clist.extend(pt2)
# up to pt4
clist.extend([pt2[0]-ctrl2[0]+rand2[0], pt2[1]+ctrl2[1]*flip-rand2[1]*flip])
ctrl4 = (0, self.notch_step/1.2)
clist.extend([pt4[0]+ctrl4[0]-rand4[0], pt4[1]-ctrl4[1]*flip+rand4[1]*flip])
clist.extend(pt4)
# up to pt5
clist.extend([pt4[0]-ctrl4[0]+rand4[0], pt4[1]+ctrl4[1]*flip-rand4[1]*flip])
ctrl5 = (self.notch_step/1.2, self.notch_step/3)
clist.extend([pt5[0]-ctrl5[0]+rand5[0], pt5[1]-ctrl5[1]*flip-rand5[1]*flip])
clist.extend(pt5)
# last ctrl point for next step
clist.extend([pt5[0]+ctrl5[0]-rand5[0], pt5[1]+ctrl5[1]*flip+rand5[1]*flip])
#
clist.extend([width, starty, width, starty]) # doubled up at end for smooth curve
line_path.append(['C',clist])
borderLineStyle = str(inkex.Style(style))
attribs = { 'style':borderLineStyle, 'id':name, 'd':dirtyFormat(line_path)}
etree.SubElement(parent, inkex.addNS('path','svg'), attribs )
def create_horiz_blocks(self, group, gridy, style):
path = lastpath = 0
blocks = []
count = 0
for node in gridy.iterchildren():
if node.tag == inkex.addNS('path','svg'): # which they ALL should because we just made them
path = CubicSuperPath(node.get('d')) # turn it into a global C style SVG path
#sys.stderr.write("count: %d\n"% count)
if count == 0: # first one so use the top border
spath = node.get('d') # work on string instead of cubicpath
lastpoint = spath.split()[-2:]
lastx = float(lastpoint[0])
lasty = float(lastpoint[1])
#sys.stderr.write("lastpoint: %s\n"% lastpoint)
spath += ' %f %f %f %f %f %f' % (lastx,lasty-self.inner_radius, lastx,1.5*self.inner_radius, lastx,self.inner_radius)
spath += ' %f %f %f %f %f %f' % (self.width,self.inner_radius/2, self.width-self.inner_radius/2,0, self.width-self.inner_radius,0)
spath += ' %f %f %f %f %f %f' % (self.width-self.inner_radius/2,0, 1.5*self.inner_radius,0, self.inner_radius, 0)
spath += ' %f %f %f %f %f %f' % (self.inner_radius/2, 0, 0,self.inner_radius/2, 0,self.inner_radius)
spath += 'z'
#sys.stderr.write("spath: %s\n"% spath)
#
name = "RowPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':spath }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
else: # internal line - concat a reversed version with the last one
thispath = copy.deepcopy(path)
for i in range(len(thispath[0])): # reverse the internal C pairs
thispath[0][i].reverse()
thispath[0].reverse() # reverse the entire line
lastpath[0].extend(thispath[0]) # append
name = "RowPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':dirtyFormat(lastpath) }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
n.set('d', n.get('d')+'z') # close it
#
count += 1
lastpath = path
# do the last row
spath = node.get('d') # work on string instead of cubicpath
lastpoint = spath.split()[-2:]
lastx = float(lastpoint[0])
lasty = float(lastpoint[1])
#sys.stderr.write("lastpoint: %s\n"% lastpoint)
spath += ' %f %f %f %f %f %f' % (lastx,lasty+self.inner_radius, lastx,self.height-1.5*self.inner_radius, lastx,self.height-self.inner_radius)
spath += ' %f %f %f %f %f %f' % (self.width,self.height-self.inner_radius/2, self.width-self.inner_radius/2,self.height, self.width-self.inner_radius,self.height)
spath += ' %f %f %f %f %f %f' % (self.width-self.inner_radius/2,self.height, 1.5*self.inner_radius,self.height, self.inner_radius, self.height)
spath += ' %f %f %f %f %f %f' % (self.inner_radius/2, self.height, 0,self.height-self.inner_radius/2, 0,self.height-self.inner_radius)
spath += 'z'
#
name = "RowPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':spath }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
#
return(blocks)
def create_vert_blocks(self, group, gridx, style):
path = lastpath = 0
blocks = []
count = 0
for node in gridx.iterchildren():
if node.tag == inkex.addNS('path','svg'): # which they ALL should because we just made them
path = CubicSuperPath(node.get('d')) # turn it into a global C style SVG path
#sys.stderr.write("count: %d\n"% count)
if count == 0: # first one so use the right border
spath = node.get('d') # work on string instead of cubicpath
lastpoint = spath.split()[-2:]
lastx = float(lastpoint[0])
lasty = float(lastpoint[1])
#sys.stderr.write("lastpoint: %s\n"% lastpoint)
spath += ' %f %f %f %f %f %f' % (lastx+self.inner_radius/2,lasty, self.width-1.5*self.inner_radius,lasty, self.width-self.inner_radius, lasty)
spath += ' %f %f %f %f %f %f' % (self.width-self.inner_radius/2,lasty, self.width,self.height-self.inner_radius/2, self.width,self.height-self.inner_radius)
spath += ' %f %f %f %f %f %f' % (self.width,self.height-1.5*self.inner_radius, self.width, 1.5*self.inner_radius, self.width,self.inner_radius)
spath += ' %f %f %f %f %f %f' % (self.width,self.inner_radius/2, self.width-self.inner_radius/2,0, self.width-self.inner_radius,0)
spath += 'z'
#sys.stderr.write("spath: %s\n"% spath)
#
name = "ColPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':spath }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
else: # internal line - concat a reversed version with the last one
thispath = copy.deepcopy(path)
for i in range(len(thispath[0])): # reverse the internal C pairs
thispath[0][i].reverse()
thispath[0].reverse() # reverse the entire line
lastpath[0].extend(thispath[0]) # append
name = "ColPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':dirtyFormat(lastpath) }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
n.set('d', n.get('d')+'z') # close it
#
count += 1
lastpath = path
# do the last one (LHS)
spath = node.get('d') # work on string instead of cubicpath
lastpoint = spath.split()[-2:]
lastx = float(lastpoint[0])
lasty = float(lastpoint[1])
#sys.stderr.write("lastpoint: %s\n"% lastpoint)
spath += ' %f %f %f %f %f %f' % (lastx-self.inner_radius,lasty, 1.5*self.inner_radius, lasty, self.inner_radius,lasty)
spath += ' %f %f %f %f %f %f' % (self.inner_radius/2,lasty, 0,lasty-self.inner_radius/2, 0,lasty-self.inner_radius)
spath += ' %f %f %f %f %f %f' % (0,lasty-1.5*self.inner_radius, 0,1.5*self.inner_radius, 0,self.inner_radius)
spath += ' %f %f %f %f %f %f' % (self.inner_radius/2,0, self.inner_radius,0, 1.5*self.inner_radius, 0)
spath += 'z'
#
name = "ColPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':spath }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
#
return(blocks)
def create_pieces(self, jigsaw, gridx, gridy):
""" Loop through each row """
# Treat outer edge carefully as border runs around. So special code the edges
# Internal lines should be in pairs - with second line reversed and appended to first. Close with a 'z'
# Create new group
g_attribs = {inkex.addNS('label','inkscape'):'JigsawPieces:X' + \
str( self.pieces_W )+':Y'+str( self.pieces_H ) }
jigsaw_pieces = etree.SubElement(jigsaw, 'g', g_attribs)
jigsaw_pieces_id = self.svg.get_unique_id("pieces-")
jigsaw_pieces.attrib['id'] = jigsaw_pieces_id
borderLineStyle = str(inkex.Style(self.borderLineStyle))
xblocks = self.create_horiz_blocks(jigsaw_pieces, gridy, borderLineStyle)
yblocks = self.create_vert_blocks(jigsaw_pieces, gridx, borderLineStyle)
# for each xblock intersect it with each Y block
puzzlePartNo = 1
allPathPairsToIntersect = []
allPathsToDelete = []
for x in range(len(xblocks)):
for y in range(len(yblocks)):
allPathPairsToIntersect.append([copy.copy(xblocks[x]), copy.copy(yblocks[y])])
allPathsToDelete.append(xblocks[x])
allPathsToDelete.append(yblocks[y])
for pair in allPathPairsToIntersect:
pair[0].attrib['id'] = str(puzzlePartNo) + "_X"
pair[1].attrib['id'] = str(puzzlePartNo) + "_Y"
xId = pair[0].get('id')
yId = pair[1].get('id')
#self.msg("intersecting {} with {}".format(xId, yId))
puzzlePartNo += 1
jigsaw_pieces.append(pair[0])
jigsaw_pieces.append(pair[1])
for pathToDelete in allPathsToDelete:
pathToDelete.delete()
actions_list = []
for pair in allPathPairsToIntersect:
actions_list.append("select-by-id:{}".format(pair[0].attrib['id']))
actions_list.append("select-by-id:{}".format(pair[1].attrib['id']))
actions_list.append("path-intersection")
actions_list.append("select-clear")
#self.msg(actions_list)
#workaround to fix it (we use export to tempfile instead processing and saving again)
tempfile = self.options.input_file + "-intersected.svg"
with open(tempfile, 'wb') as fp:
fp.write(self.svg.tostring())
actions_list.append("export-type:svg")
actions_list.append("export-filename:{}".format(tempfile))
actions_list.append("export-do")
actions = ";".join(actions_list)
#self.msg(actions)
cli_output = inkscape(tempfile, actions=actions) #process recent file
if len(cli_output) > 0:
self.msg("Inkscape returned the following output when trying to run the file export; the file export may still have worked:")
self.msg(cli_output)
# replace current document with content of temp copy file
self.document = inkex.load_svg(tempfile)
# update self.svg
self.svg = self.document.getroot()
row = 1
col = 1
offsetW = self.options.shift / 100 * (self.options.width / self.options.pieces_W)
offsetH = self.options.shift / 100 * (self.options.height / self.options.pieces_H)
for jigsaw_piece in self.svg.getElementById(jigsaw_pieces_id).getchildren():
jigsaw_piece.apply_transform()
jigsaw_piece.set('transform', 'translate(%f,%f)' % (-col * offsetH, 0))
jigsaw_piece.apply_transform()
jigsaw_piece.set('transform', 'translate(%f,%f)' % (0, row * offsetW))
jigsaw_piece.apply_transform()
currentPiece = int(jigsaw_piece.get('id').split('_')[0])
#self.msg("piece {} zeile {} Spalte {}".format(currentPiece, row, col))
if currentPiece % self.options.pieces_W == 0:
row += 1
col -= self.options.pieces_W
col += 1
return jigsaw_pieces_id
def effect(self):
# internal useful variables
self.stroke_width =str(self.svg.unittouu("1px")) # default for visiblity
self.borderLineStyle = {'stroke': self.options.color_border, 'fill': 'none', 'stroke-width': self.stroke_width,
'stroke-linecap': 'butt', 'stroke-linejoin': 'miter'}
self.jigsawLineStyle = {'stroke': self.options.color_jigsaw, 'fill': 'none', 'stroke-width': self.stroke_width,
'stroke-linecap': 'butt', 'stroke-linejoin': 'miter'}
# document dimensions (for centering)
docW = self.svg.unittouu(self.document.getroot().get('width'))
docH = self.svg.unittouu(self.document.getroot().get('height'))
# extract fields from UI
self.width = self.svg.unittouu( str(self.options.width) + self.options.units )
self.height = self.svg.unittouu( str(self.options.height) + self.options.units )
self.pieces_W = self.options.pieces_W
self.pieces_H = self.options.pieces_H
if self.options.sizetype == "partsize":
self.width = self.width * self.pieces_W
self.height = self.height * self.pieces_H
average_block = (self.width/self.pieces_W + self.height/self.pieces_H) / 2
self.notch_step = average_block * self.options.notch_percent / 3 # 3 = a useful notch size factor
self.smooth_edges = self.options.smooth_edges
self.noknob_frequency = self.options.noknob_frequency
self.random_radius = self.options.rand * average_block / 5 # 5 = a useful range factor
self.inner_radius = self.options.innerradius
if self.inner_radius < 0.01: self.inner_radius = 0.0 # snap to 0 for UI error when setting spinner to 0.0
self.border = self.options.border
self.borderwidth = self.options.borderwidth
self.outer_radius = self.options.outerradius
if self.outer_radius < 0.01: self.outer_radius = 0.0 # snap to 0 for UI error when setting spinner to 0.0
self.pack = self.options.pack
# pieces
self.pieces = self.options.pieces
# random function
if not self.options.use_seed:
random.seed(self.options.seed)
#
# set up the main object in the current layer - group gridlines
g_attribs = {inkex.addNS('label','inkscape'):'Jigsaw:X' + str(self.pieces_W )+':Y'+str(self.pieces_H) + "={}Pcs)".format(self.pieces_W * self.pieces_H)}
jigsaw_group = etree.SubElement(self.svg.get_current_layer(), 'g', g_attribs)
#Group for X grid
g_attribs = {inkex.addNS('label','inkscape'):'X_Gridlines'}
gridx = etree.SubElement(jigsaw_group, 'g', g_attribs)
#Group for Y grid
g_attribs = {inkex.addNS('label','inkscape'):'Y_Gridlines'}
gridy = etree.SubElement(jigsaw_group, 'g', g_attribs)
# Draw the Border
add_rounded_rectangle(0,0, self.inner_radius, self.width, self.height, self.borderLineStyle, 'innerborder', jigsaw_group)
# Do the Border
if self.border:
add_rounded_rectangle(-self.borderwidth,-self.borderwidth, self.outer_radius, self.borderwidth*2+self.width,
self.borderwidth*2+self.height, self.borderLineStyle, 'outerborder', jigsaw_group)
# make a second copy below the jigsaw for the cutout BG
if self.pack == "Below":
add_rounded_rectangle(-self.borderwidth,self.borderwidth+ self.height, self.outer_radius, self.borderwidth*2+self.width,
self.borderwidth*2+self.height, self.borderLineStyle, 'BG', jigsaw_group, self.pack)
elif self.pack == "Right":
add_rounded_rectangle(self.width+self.borderwidth,-self.borderwidth, self.outer_radius, self.borderwidth*2+self.width,
self.borderwidth*2+self.height, self.borderLineStyle, 'BG', jigsaw_group, self.pack)
else: # Separate
add_rounded_rectangle(self.width+self.borderwidth*2,-self.borderwidth, self.outer_radius, self.borderwidth*2+self.width,
self.borderwidth*2+self.height, self.borderLineStyle, 'BG', jigsaw_group)
# Step through the Grid
Xstep = self.width / (self.pieces_W)
Ystep = self.height / (self.pieces_H)
# Draw Horizontal lines on Y step with Xstep notches
for i in range(1, self.pieces_H):
self.add_jigsaw_horiz_line(0, Ystep*i, Xstep, self.pieces_W, self.width, self.jigsawLineStyle, 'YDiv'+str(i), gridy)
# Draw Vertical lines on X step with Ystep notches
for i in range(1, self.pieces_W):
self.add_jigsaw_horiz_line(0, Xstep*i, Ystep, self.pieces_H, self.height, self.jigsawLineStyle, 'XDiv'+str(i), gridx)
# Rotate lines into pos
# actualy transform can have multiple transforms in it e.g. 'translate(10,10) rotate(10)'
for node in gridx.iterchildren():
if node.tag == inkex.addNS('path','svg'):
node.set('transform', 'translate(%f,%f) rotate(90)' % (self.width, 0))
node.apply_transform()
# center the jigsaw
jigsaw_group.set('transform', 'translate(%f,%f)' % ( (docW-self.width)/2, (docH-self.height)/2 ) )
#inkex.utils.debug("Your puzzle consists out of {} pieces.".format(self.pieces_W * self.pieces_H))
# pieces
if self.pieces:
gridx.delete() #delete the previous x generated stuff because we have single pieces instead!
gridy.delete() #delete the previous y generated stuff because we have single pieces instead!
jigsaw_group.getchildren()[0].delete() #delete inner border
jigsaw_pieces_id = self.create_pieces(jigsaw_group, gridx,gridy)
for jigsaw_piece in self.svg.getElementById(jigsaw_pieces_id).getchildren():
jigsaw_piece.attrib['id'] = jigsaw_pieces_id + "_" + jigsaw_piece.attrib['id']
if __name__ == '__main__':
LasercutJigsaw().run()

View File

@ -0,0 +1,25 @@
[
{
"name": "Lasercut Jigsaw",
"id": "fablabchemnitz.de.lasercut_jigsaw",
"path": "lasercut_jigsaw",
"dependent_extensions": null,
"original_name": "Lasercut Jigsaw",
"original_id": "org.inkscape.LasercutJigsa",
"license": "GNU GPL v2",
"license_url": "https://github.com/Neon22/inkscape-jigsaw/blob/master/LICENSE",
"comment": "",
"source_url": "https://stadtfabrikanten.org/display/IFM/Lasercut+Jigsaw",
"fork_url": "https://github.com/Neon22/inkscape-jigsaw",
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Lasercut+Jigsaw",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/Neon22",
"github.com/jonadem",
"github.com/speleo3",
"github.com/LynNor1",
"github.com/roeschter",
"github.com/vmario89"
]
}
]

View File

@ -9,13 +9,13 @@
"license": "GNU GPL v2",
"license_url": "https://gitlab.com/Moini/ink_line_animator/-/blob/master/LICENSE",
"comment": "",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/line_animator",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/line_animator",
"fork_url": "https://gitlab.com/Moini/ink_line_animator",
"documentation_url": "",
"inkscape_gallery_url": null,
"main_authors": [
"gitlab.com/Moini",
"github.com/vmario89"
"github.com/eridur-de"
]
}
]

View File

@ -0,0 +1,21 @@
[
{
"name": "Paperfold",
"id": "fablabchemnitz.de.paperfold",
"path": "paperfold",
"dependent_extensions": null,
"original_name": "Paperfold",
"original_id": "fablabchemnitz.de.paperfold",
"license": "GNU GPL v3",
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE",
"comment": "Written by Mario Voigt, based on https://github.com/felixfeliz/paperfoldmodels",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/paperfold",
"fork_url": null,
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Paperfold",
"inkscape_gallery_url": "https://inkscape.org/~MarioVoigt/%E2%98%85paperfold",
"main_authors": [
"github.com/felixfeliz",
"github.com/eridur-de"
]
}
]

View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Paperfold</name>
<id>fablabchemnitz.de.paperfold</id>
<param name="tab" type="notebook">
<page name="tab_settings" gui-text="Paperfold for Inkscape">
<label appearance="header">Input</label>
<param name="inputfile" type="path" gui-text="Input File" filetypes="obj,off,ply,stl" mode="file" gui-description="The model to unfold. You can use obj files provided in extensions dir of Inkscape \Poly3DObjects\*.obj to play around">/your/beautiful/3dmodel/file</param>
<param name="maxNumFaces" 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 unfolding extremely slow. So we can limit it.">200</param>
<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="roundingDigits" type="int" min="0" max="16" gui-text="Digits for rounding" gui-description="Controls how (nearly) coplanar lines are handled.">3</param>
<separator/>
<hbox>
<vbox>
<label appearance="header">Output</label>
<param name="printGluePairNumbers" type="bool" gui-text="Print glue pair numbers on cut edges">false</param>
<param name="printAngles" type="bool" gui-text="Print folding angles on edges">false</param>
<param name="printLengths" type="bool" gui-text="Print lengths on edges">false</param>
<param name="printTriangleNumbers" type="bool" gui-text="Print triangle numbers on faces">false</param>
<param name="importCoplanarEdges" type="bool" gui-text="Import coplanar edges">false</param>
<param name="experimentalWeights" type="bool" gui-text="Mess around with algorithm">false</param>
<param name="printStats" type="bool" gui-text="Show some unfold statistics">false</param>
<param name="resizetoimport" type="bool" gui-text="Resize canvas" gui-description="Resize the canvas to the imported drawing's bounding box">true</param>
<param name="extraborder" type="float" precision="3" gui-text="Extra border" gui-description="Add extra border around fitted canvas">0.0</param>
<param name="extraborderUnits" 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="writeTwoDSTL" type="bool" gui-text="Write 2D STL unfoldings">false</param>
<param name="TwoDSTLdir" type="path" mode="folder" gui-text="Location to save exported 2D STL">./inkscape_export/</param>
</vbox>
<separator/>
<vbox>
<label appearance="header">Style</label>
<param name="fontSize" type="int" min="1" max="100" gui-text="Label font size (%)">15</param>
<param name="flipLabels" type="bool" gui-text="Flip labels">false</param>
<param name="dashes" type="bool" gui-text="Dashes for cut/coplanar lines">true</param>
<param name="merge_cut_lines" type="bool" gui-text="Merge cut lines" gui-description="This will merge all cut lines to a single path">true</param>
<param name="edgeStyle" type="optiongroup" appearance="combo" gui-text="Edge style">
<option value="regular">regular</option>
<option value="saturationsForAngles">saturations for angles</option>
<option value="opacitiesForAngles">opacities for angles</option>
</param>
<param name="separateGluePairsByColor" type="bool" gui-text="Separate glue tab pairs by color" gui-description="Generates random color sets for glue tab pairs">false</param>
<param name="colorCutEdges" type="color" appearance="colorbutton" gui-text="Cut edges">255</param>
<param name="colorCoplanarEdges" type="color" appearance="colorbutton" gui-text="Coplanar edges">1943148287</param>
<param name="colorValleyFolds" type="color" appearance="colorbutton" gui-text="Valley fold edges">3422552319</param>
<param name="colorMountainFolds" type="color" appearance="colorbutton" gui-text="Mountain fold edges">879076607</param>
</vbox>
</hbox>
</page>
<page name="tab_postprocessing" gui-text="Post Processing">
<label appearance="header">Joinery</label>
<label>Joinery is a tool to add interlockings, tabs and other connectors to your edges. It only works on ungrouped paths (flat structure).</label>
<param name="joineryMode" type="bool" gui-text="Enable joinery mode" gui-description="Makes flat file instead creating groups. Guarantees compability for joinery.">false</param>
<label appearance="url">https://clementzheng.github.io/joinery</label>
<label appearance="url">https://www.instructables.com/Joinery-Joints-for-Laser-Cut-Assemblies</label>
<label appearance="header">Origami Simulator</label>
<label>Simulate your foldings in 3D! See Origami Simulator: "File" -> "Design Tips" -> "Importing SVG"</label>
<label appearance="url">https://origamisimulator.org</label>
<label appearance="url">https://github.com/amandaghassaei/OrigamiSimulator</label>
<param name="origamiSimulatorMode" type="bool" gui-text="Enable origami simulator mode" gui-description="Overwrites styles to be compatible to origami simulator.">false</param>
</page>
<page name="tab_about" gui-text="About">
<label appearance="header">Paperfold for Inkscape</label>
<label>Paperfold is another flattener for triangle mesh files, heavily based on paperfoldmodels by Felix Scholz aka felixfeliz. Possible input files are STL, Wavefront OBJ, PLY and OFF.</label>
<label>2020 - 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/paperfold</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/felixfeliz/paperfoldmodels</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="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 needs-live-preview="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Boxes/Papercraft">
<submenu name="Papercraft Flatteners"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">paperfold.py</command>
</script>
</inkscape-extension>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
[
{
"name": "Styles To Layers",
"id": "fablabchemnitz.de.styles_to_layers",
"path": "styles_to_layers",
"dependent_extensions": [
"apply_transformations",
"remove_empty_groups"
],
"original_name": "Styles To Layers",
"original_id": "fablabchemnitz.de.styles_to_layers",
"license": "GNU GPL v3",
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE",
"comment": "written by Mario Voigt",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/styles_to_layers",
"fork_url": null,
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Styles+To+Layers",
"inkscape_gallery_url": "https://inkscape.org/de/~MarioVoigt/%E2%98%85styles-to-layers",
"main_authors": [
"github.com/eridur-de"
]
}
]

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Styles To Layers</name>
<id>fablabchemnitz.de.styles_to_layers</id>
<param name="tab" type="notebook">
<page name="tab_settings" gui-text="Settings">
<param name="separateby" gui-text="Separate by" type="optiongroup" appearance="combo">
<option value="element_tag">Element tag</option>
<option value="stroke">Stroke color</option>
<option value="stroke_width">Stroke width</option>
<option value="stroke_hairline">Stroke hairline</option>
<option value="stroke_opacity">Stroke opacity</option>
<option value="fill">Fill color</option>
<option value="fill_opacity">Fill opacity</option>
</param>
<param name="sortcolorby" gui-text="Sort colors by" type="optiongroup" appearance="combo" gui-description="This option only applies to stroke color and fill color">
<option value="hexval">Hex value</option>
<option value="hue">Hue</option>
<option value="saturation">Saturation</option>
<option value="luminance">Luminance</option>
</param>
<param name="subdividethreshold" type="int" min="1" max="9999" gui-text="Number of sub layers" gui-description="A min/max range of the selected style type value will be calculated and you retrieve a set of layer (coarse grouping) with sub-layers (fine grouping). If you have less calculated sub layers than this threshold it will be limited automatically.">1</param>
<param name="decimals" type="int" min="0" max="10" gui-text="Decimal tolerance" gui-description="The more decimals the more distinct layers you will get. This only applies for the sub layers (threshold > 1)">1</param>
<param name="apply_transformations" type="bool" gui-text="Apply transformations (requires separate extension)" gui-description="This will call the extension 'Apply Transformations'. Helps avoiding geometry shifting">false</param>
<param name="cleanup" type="bool" gui-text="Cleanup all unused groups/layers (requires separate extension)" gui-description="This will call the extension 'Remove Empty Groups' if available">true</param>
<param name="put_unfiltered" type="bool" gui-text="Put unfiltered elements to a separate layer">false</param>
<param name="show_info" type="bool" gui-text="Show elements which have no style attributes to filter">false</param>
</page>
<page name="tab_about" gui-text="About">
<label appearance="header">Styles To Layers</label>
<label>This extension will re-layer your selected items or the whole document according to their style values (stroke or fill). The filtering applies only to style attribute of the elements. It does not filter for stroke or fill if they are set separately (dedicated attributes). It will also NOT apply to svg:style classes. You can use the separate 'Cleanup Styles' extension to migrate these separated attributes into style attribute.</label>
<label>2020 - 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/stylestolayers</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X</label>
<label appearance="url">mailto:mario.voigt@stadtfabrikanten.org</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="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>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Groups and Layers"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">styles_to_layers.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,308 @@
#!/usr/bin/env python3
"""
Extension for InkScape 1.0
Features
- filters the current selection or the whole document for fill or stroke style. Each style will be put onto it's own layer. This way you can devide elements by their colors.
Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org
Date: 19.08.2020
Last patch: 17.10.2021
License: GNU GPL v3
"""
import inkex
import re
import sys
from lxml import etree
import math
from operator import itemgetter
from inkex.colors import Color
sys.path.append("../remove_empty_groups")
sys.path.append("../apply_transformations")
class StylesToLayers(inkex.EffectExtension):
def findLayer(self, layerName):
svg_layers = self.document.xpath('//svg:g[@inkscape:groupmode="layer"]', namespaces=inkex.NSS)
for layer in svg_layers:
#self.msg(str(layer.get('inkscape:label')) + " == " + layerName)
if layer.get('inkscape:label') == layerName:
return layer
return None
def createLayer(self, layerNodeList, layerName):
svg = self.document.xpath('//svg:svg',namespaces=inkex.NSS)[0]
for layer in layerNodeList:
#self.msg(str(layer[0].get('inkscape:label')) + " == " + layerName)
if layer[0].get('inkscape:label') == layerName:
return layer[0] #already exists. Do not create duplicate
layer = etree.SubElement(svg, 'g')
layer.set(inkex.addNS('label', 'inkscape'), '%s' % layerName)
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
return layer
def add_arguments(self, pars):
pars.add_argument("--tab")
pars.add_argument("--apply_transformations", type=inkex.Boolean, default=False, help="Run 'Apply Transformations' extension before running vpype. Helps avoiding geometry shifting")
pars.add_argument("--separateby", default = "stroke", help = "Separate by")
pars.add_argument("--sortcolorby", default = "hexval", help = "Sort colors by")
pars.add_argument("--subdividethreshold", type=int, default = 1, help = "Threshold for splitting into sub layers")
pars.add_argument("--decimals", type=int, default = 1, help = "Decimal tolerance")
pars.add_argument("--cleanup", type=inkex.Boolean, default = True, help = "Cleanup all unused groups/layers (requires separate extension)")
pars.add_argument("--put_unfiltered", type=inkex.Boolean, default = False, help = "Put unfiltered elements to a separate layer")
pars.add_argument("--show_info", type=inkex.Boolean, default = False, help = "Show elements which have no style attributes to filter")
def effect(self):
def colorsort(stroke_value): #this function applies to stroke or fill (hex colors)
if self.options.sortcolorby == "hexval":
return float(int(stroke_value[1:], 16))
elif self.options.sortcolorby == "hue":
return float(Color(stroke_value).to_hsl()[0])
elif self.options.sortcolorby == "saturation":
return float(Color(stroke_value).to_hsl()[1])
elif self.options.sortcolorby == "luminance":
return float(Color(stroke_value).to_hsl()[2])
return None
applyTransformationsAvailable = False # at first we apply external extension
try:
import apply_transformations
applyTransformationsAvailable = True
except Exception as e:
# self.msg(e)
self.msg("Calling 'Apply Transformations' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...")
layer_name = None
layerNodeList = [] #list with layer, neutral_value, element and self.options.separateby type
selected = [] #list of items to parse
if len(self.svg.selected) == 0:
for element in self.document.getroot().iter("*"):
selected.append(element)
else:
selected = self.svg.selected.values()
for element in selected:
# additional option to apply transformations. As we clear up some groups to form new layers, we might lose translations, rotations, etc.
if self.options.apply_transformations is True and applyTransformationsAvailable is True:
apply_transformations.ApplyTransformations().recursiveFuseTransform(element)
if isinstance(element, inkex.ShapeElement): # Elements which have a visible representation on the canvas (even without a style attribute but by their type); if we do not use that ifInstance Filter we provokate unkown InkScape fatal crashes
style = element.style
if style is not None:
#if no style attributes or stroke/fill are set as extra attribute
stroke = element.get('stroke')
stroke_width = element.get('stroke-width')
stroke_opacity = element.get('stroke-opacity')
fill = element.get('fill')
fill_opacity = element.get('fill-opacity')
# possible values for fill are #HEXCOLOR (like #000000), color name (like purple, black, red) or gradients (URLs)
neutral_value = None #we will use this value to slice the filter result into sub layers (threshold)
if fill is not None:
style['fill'] = fill
if stroke is not None:
style['stroke'] = stroke
#we don't want to destroy elements with gradients (they contain svg:stop elements which have a style too) and we don't want to mess with tspans (text)
#the Styles to Layers extension still might brick the gradients (some tests failed)
if style and element.tag != inkex.addNS('stop','svg') and element.tag != inkex.addNS('tspan','svg'):
if self.options.separateby == "element_tag":
neutral_value = 1
layer_name = "element_tag-" + element.tag.replace("{http://www.w3.org/2000/svg}", "")
elif self.options.separateby == "stroke":
stroke = style.get('stroke')
if stroke is not None and stroke != "none":
stroke_converted = str(Color(stroke).to_rgb()) #the color can be hex code or clear name. we handle both the same
neutral_value = colorsort(stroke_converted)
layer_name = "stroke-{}-{}".format(self.options.sortcolorby, stroke_converted)
else:
layer_name = "stroke-{}-none".format(self.options.sortcolorby)
elif self.options.separateby == "stroke_width":
stroke_width = style.get('stroke-width')
if stroke_width is not None:
neutral_value = self.svg.unittouu(stroke_width)
layer_name = "stroke-width-{}".format(neutral_value)
else:
layer_name = "stroke-width-none"
elif self.options.separateby == "stroke_hairline":
inkscape_stroke = style.get('-inkscape-stroke')
if inkscape_stroke is not None and inkscape_stroke == "hairline":
neutral_value = 1
layer_name = "stroke-hairline-yes"
else:
neutral_value = 0
layer_name = "stroke-hairline-no"
elif self.options.separateby == "stroke_opacity":
stroke_opacity = style.get('stroke-opacity')
if stroke_opacity is not None:
neutral_value = float(stroke_opacity)
layer_name = "stroke-opacity-{}".format(neutral_value)
else:
layer_name = "stroke-opacity-none"
elif self.options.separateby == "fill":
fill = style.get('fill')
if fill is not None:
#check if the fill color is a real color or a gradient. if it's a gradient we skip the element
if fill != "none" and "url" not in fill:
fill_converted = str(Color(fill).to_rgb()) #the color can be hex code or clear name. we handle both the same
neutral_value = colorsort(fill_converted)
layer_name = "fill-{}-{}".format(self.options.sortcolorby,fill_converted)
elif "url" in fill: #okay we found a gradient. we put it to some group
layer_name = "fill-{}-gradient".format(self.options.sortcolorby)
else: #none
layer_name = "fill-" + self.options.sortcolorby + "-none"
else:
layer_name = "fill-" + self.options.sortcolorby + "-none"
elif self.options.separateby == "fill_opacity":
fill_opacity = style.get('fill-opacity')
if fill_opacity is not None:
neutral_value = float(fill_opacity)
layer_name = "fill-opacity-{}".format(neutral_value)
else:
layer_name = "fill-opacity-none"
else:
self.msg("No proper option selected.")
exit(1)
if neutral_value is not None: #apply decimals filter
neutral_value = float(round(neutral_value, self.options.decimals))
if layer_name is not None:
layer_name = layer_name.split(";")[0] #cut off existing semicolons to avoid duplicated layers with/without semicolon
currentLayer = self.findLayer(layer_name)
if currentLayer is None: #layer does not exist, so create a new one
layerNodeList.append([self.createLayer(layerNodeList, layer_name), neutral_value, element, self.options.separateby])
else:
layerNodeList.append([currentLayer, neutral_value, element, self.options.separateby]) #layer is existent. append items to this later
elif layer_name is None and self.options.put_unfiltered:
layer_name = 'without-' + self.options.separateby + '-in-style-attribute'
else: #if no style attribute in element and not a group
if isinstance(element, inkex.Group) is False:
if self.options.show_info:
self.msg(element.get('id') + ' has no style attribute')
if self.options.put_unfiltered:
layer_name = 'without-style-attribute'
currentLayer = self.findLayer(layer_name)
if currentLayer is None: #layer does not exist, so create a new one
layerNodeList.append([self.createLayer(layerNodeList, layer_name), None, element, None])
else:
layerNodeList.append([currentLayer, None, element, None]) #layer is existent. append items to this later
contentlength = 0 #some counter to track if there are layers inside or if it is just a list with empty children
for layerNode in layerNodeList:
try: #some nasty workaround. Make better code
layerNode[0].append(layerNode[2]) #append element to created layer
if layerNode[1] is not None: contentlength += 1 #for each found layer we increment +1
except:
continue
# we do some cosmetics with layers. Sometimes it can happen that one layer includes another. We don't want that. We move all layers to the top level
for newLayerNode in layerNodeList:
self.document.getroot().append(newLayerNode[0])
# Additionally if threshold was defined re-arrange the previously created layers by putting them into sub layers
if self.options.subdividethreshold > 1 and contentlength > 0: #check if we need to subdivide and if there are items we could rearrange into sub layers
#disabled sorting because it can return NoneType values which will kill the algorithm
#layerNodeList.sort(key=itemgetter(1)) #sort by neutral values from min to max to put them with ease into parent layers
topLevelLayerNodeList = [] #list with new layers and sub layers (mapping)
minmax_range = []
for layerNode in layerNodeList:
if layerNode[1] is not None:
if layerNode[1] not in minmax_range:
minmax_range.append(layerNode[1]) #get neutral_value
if len(minmax_range) >= 3: #if there are less than 3 distinct values a sub-layering will make no sense
#adjust the subdividethreshold if there are less layers than division threshold value dictates
if len(minmax_range) - 1 < self.options.subdividethreshold:
self.options.subdividethreshold = len(minmax_range)-1
#calculate the sub layer slices (sub ranges)
minval = min(minmax_range)
maxval = max(minmax_range)
sliceinterval = (maxval - minval) / self.options.subdividethreshold
#self.msg("neutral values (sorted) = " + str(minmax_range))
#self.msg("min neutral_value = " + str(minval))
#self.msg("max neutral_value = " + str(maxval))
#self.msg("slice value (divide step size) = " + str(sliceinterval))
#self.msg("subdivides (parent layers) = " + str(self.options.subdividethreshold))
for layerNode in layerNodeList:
for x in range(0, self.options.subdividethreshold): #loop through the sorted neutral_values and determine to which layer they should belong
if layerNode[1] is None:
layer_name = str(layerNode[3]) + "#parent:unfilterable"
currentLayer = self.findLayer(layer_name)
if currentLayer is None: #layer does not exist, so create a new one
topLevelLayerNodeList.append([self.createLayer(topLevelLayerNodeList, layer_name), layerNode[0]])
else:
topLevelLayerNodeList.append([currentLayer, layerNode[0]]) #layer is existent. append items to this later
break
else:
layer_name = str(layerNode[3]) + "#parent" + str(x+1)
currentLayer = self.findLayer(layer_name)
#value example for arranging:
#min neutral_value = 0.07
#max neutral_value = 2.50
#slice value = 0.81
#subdivides = 3
#
#that finally should generate:
# layer #1: (from 0.07) to (0.07 + 0.81 = 0.88)
# layer #2: (from 0.88) to (0.88 + 0.81 = 1.69)
# layer #3: (from 1.69) to (1.69 + 0.81 = 2.50)
#
#now check layerNode[1] (neutral_value) and sort it into the correct layer
if (layerNode[1] >= minval + sliceinterval * x) and (layerNode[1] <= minval + sliceinterval + sliceinterval * x):
if currentLayer is None: #layer does not exist, so create a new one
topLevelLayerNodeList.append([self.createLayer(topLevelLayerNodeList, layer_name), layerNode[0]])
else:
topLevelLayerNodeList.append([currentLayer, layerNode[0]]) #layer is existent. append items to this later
break
#finally append the sublayers to the slices
#for layer in topLevelLayerNodeList:
#self.msg(layer[0].get('inkscape:label'))
#self.msg(layer[1])
for newLayerNode in topLevelLayerNodeList:
newLayerNode[0].append(newLayerNode[1]) #append newlayer to layer
#clean all empty layers from node list. Please note that the following remove_empty_groups
#call does not apply for this so we need to do it as PREVIOUS step before!
for i in range(0, len(layerNodeList)):
deletes = []
for j in range(0, len(layerNodeList[i][0])):
if len(layerNodeList[i][0][j]) == 0 and isinstance(layerNodeList[i][0][j], inkex.Group):
deletes.append(layerNodeList[i][0][j])
for delete in deletes:
delete.getparent().remove(delete)
if len(layerNodeList[i][0]) == 0:
if layerNodeList[i][0].getparent() is not None:
layerNodeList[i][0].getparent().remove(layerNodeList[i][0])
if self.options.cleanup == True:
try:
import remove_empty_groups
remove_empty_groups.RemoveEmptyGroups.effect(self)
except:
self.msg("Calling 'Remove Empty Groups' extension failed. Maybe the extension is not installed. You can download it from official InkScape Gallery. Skipping ...")
if __name__ == '__main__':
StylesToLayers().run()

View File

@ -0,0 +1,21 @@
[
{
"name": "Sundial Declining",
"id": "fablabchemnitz.de.sundial_declining",
"path": "sundial_declining",
"dependent_extensions": null,
"original_name": "Sundial",
"original_id": "fr.electropol.tableausimple.inkscape",
"license": "Public Domain",
"license_url": "https://inkscape.org/de/~TomasUrban/%E2%98%85sundial-declining",
"comment": "ported to Inkscape v1 by Mario Voigt",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/sundial_declining",
"fork_url": "https://inkscape.org/de/~TomasUrban/%E2%98%85sundial-declining",
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Sundial+Declining",
"inkscape_gallery_url": null,
"main_authors": [
"inkscape.org/TomasUrban",
"github.com/eridur-de"
]
}
]

View File

@ -0,0 +1,210 @@
#! /bin/sh
# qqwing - temporary wrapper script for .libs/qqwing
# Generated by libtool (GNU libtool) 2.4.2 Debian-2.4.2-1.10ubuntu1
#
# The qqwing program cannot be directly executed until all the libtool
# libraries that it depends on are installed.
#
# This wrapper script should never be moved out of the build directory.
# If it is, it will not operate correctly.
# Sed substitution that helps us do robust quoting. It backslashifies
# metacharacters that are still active within double-quoted strings.
sed_quote_subst='s/\([`"$\\]\)/\\\1/g'
# Be Bourne compatible
if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
emulate sh
NULLCMD=:
# Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
# is contrary to our usage. Disable this feature.
alias -g '${1+"$@"}'='"$@"'
setopt NO_GLOB_SUBST
else
case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
fi
BIN_SH=xpg4; export BIN_SH # for Tru64
DUALCASE=1; export DUALCASE # for MKS sh
# The HP-UX ksh and POSIX shell print the target directory to stdout
# if CDPATH is set.
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
relink_command=""
# This environment variable determines our operation mode.
if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
# install mode needs the following variables:
generated_by_libtool_version='2.4.2'
notinst_deplibs=' ./libqqwing.la'
else
# When we are sourced in execute mode, $file and $ECHO are already set.
if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
file="$0"
# A function that is used when there is no print builtin or printf.
func_fallback_echo ()
{
eval 'cat <<_LTECHO_EOF
$1
_LTECHO_EOF'
}
ECHO="printf %s\\n"
fi
# Very basic option parsing. These options are (a) specific to
# the libtool wrapper, (b) are identical between the wrapper
# /script/ and the wrapper /executable/ which is used only on
# windows platforms, and (c) all begin with the string --lt-
# (application programs are unlikely to have options which match
# this pattern).
#
# There are only two supported options: --lt-debug and
# --lt-dump-script. There is, deliberately, no --lt-help.
#
# The first argument to this parsing function should be the
# script's ./libtool value, followed by no.
lt_option_debug=
func_parse_lt_options ()
{
lt_script_arg0=$0
shift
for lt_opt
do
case "$lt_opt" in
--lt-debug) lt_option_debug=1 ;;
--lt-dump-script)
lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
cat "$lt_dump_D/$lt_dump_F"
exit 0
;;
--lt-*)
$ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
exit 1
;;
esac
done
# Print the debug banner immediately:
if test -n "$lt_option_debug"; then
echo "qqwing:qqwing:${LINENO}: libtool wrapper (GNU libtool) 2.4.2 Debian-2.4.2-1.10ubuntu1" 1>&2
fi
}
# Used when --lt-debug. Prints its arguments to stdout
# (redirection is the responsibility of the caller)
func_lt_dump_args ()
{
lt_dump_args_N=1;
for lt_arg
do
$ECHO "qqwing:qqwing:${LINENO}: newargv[$lt_dump_args_N]: $lt_arg"
lt_dump_args_N=`expr $lt_dump_args_N + 1`
done
}
# Core function for launching the target application
func_exec_program_core ()
{
if test -n "$lt_option_debug"; then
$ECHO "qqwing:qqwing:${LINENO}: newargv[0]: $progdir/$program" 1>&2
func_lt_dump_args ${1+"$@"} 1>&2
fi
exec "$progdir/$program" ${1+"$@"}
$ECHO "$0: cannot exec $program $*" 1>&2
exit 1
}
# A function to encapsulate launching the target application
# Strips options in the --lt-* namespace from $@ and
# launches target application with the remaining arguments.
func_exec_program ()
{
case " $* " in
*\ --lt-*)
for lt_wr_arg
do
case $lt_wr_arg in
--lt-*) ;;
*) set x "$@" "$lt_wr_arg"; shift;;
esac
shift
done ;;
esac
func_exec_program_core ${1+"$@"}
}
# Parse options
func_parse_lt_options "$0" ${1+"$@"}
# Find the directory that this script lives in.
thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
test "x$thisdir" = "x$file" && thisdir=.
# Follow symbolic links until we get to the real thisdir.
file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
while test -n "$file"; do
destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
# If there was a directory component, then change thisdir.
if test "x$destdir" != "x$file"; then
case "$destdir" in
[\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
*) thisdir="$thisdir/$destdir" ;;
esac
fi
file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
done
# Usually 'no', except on cygwin/mingw when embedded into
# the cwrapper.
WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
# special case for '.'
if test "$thisdir" = "."; then
thisdir=`pwd`
fi
# remove .libs from thisdir
case "$thisdir" in
*[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
.libs ) thisdir=. ;;
esac
fi
# Try to get the absolute directory name.
absdir=`cd "$thisdir" && pwd`
test -n "$absdir" && thisdir="$absdir"
program='qqwing'
progdir="$thisdir/.libs"
if test -f "$progdir/$program"; then
# Add our own library path to LD_LIBRARY_PATH
LD_LIBRARY_PATH="/home/himbeere/Downloads/qqwing-1.3.4/.libs:$LD_LIBRARY_PATH"
# Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
# The second colon is a workaround for a bug in BeOS R4 sed
LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
export LD_LIBRARY_PATH
if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
# Run the actual program with our arguments.
func_exec_program ${1+"$@"}
fi
else
# The program doesn't exist.
$ECHO "$0: error: \`$progdir/$program' does not exist" 1>&2
$ECHO "This script is just a wrapper for $program." 1>&2
$ECHO "See the libtool documentation for more information." 1>&2
exit 1
fi
fi

Binary file not shown.

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Sudoku</name>
<id>fablabchemnitz.de.sudoku</id>
<param name="tab" type="notebook">
<page name="tab_puzzle" gui-text="Puzzle">
<param name="difficulty" type="optiongroup" appearance="combo" gui-text="Difficulty">
<option value="mixed">Mixed</option>
<option value="simple">Simple</option>
<option value="easy">Easy</option>
<option value="intermediate">Intermediate</option>
<option value="expert">Expert</option>
</param>
<label>Layout parameters:</label>
<param name="rows" type="int" min="1" max="6" gui-text="Rows">1</param>
<param name="cols" type="int" min="1" max="6" gui-text="Cols">1</param>
<label>Puzzle Dimensions:</label>
<param name="units" gui-text="Units" type="optiongroup" appearance="combo">
<option value="cm">cm</option>
<option value="mm">mm</option>
<option value="in">in</option>
<option value="px">px</option>
<option value="pt">pt</option>
</param>
<param name="puzzle_size" type="int" min="1" max="1000" gui-text="Puzzle Width(Height)">6</param>
<param name="puzzle_gap" type="int" min="1" max="100" gui-text="Puzzle Gap">1</param>
</page>
<page name="colors" gui-text="Colors">
<param name="color_text" type="color" appearance="colorbutton" gui-text="Text color ">255</param>
<param name="color_bkgnd" type="color" appearance="colorbutton" gui-text="Background color">4243148799</param>
<param name="color_puzzle" type="color" appearance="colorbutton" gui-text="Puzzle Border color">2290779647</param>
<param name="color_boxes" type="color" appearance="colorbutton" gui-text="Box Border color ">3298820351</param>
<param name="color_cells" type="color" appearance="colorbutton" gui-text="Cell Border color">1923076095</param>
</page>
</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Shape Generators">
<submenu name="Puzzles/Mazes/Nests"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">sudoku.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,119 @@
#!/usr/bin/env python3
'''
render_sudoku.py
A sudoku generator plugin for Inkscape, but also can be used as a standalone
command line application.
Copyright (C) 2011 Chris Savery <chrissavery(a)gmail.com>
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
'''
__version__ = "0.1"
import inkex
import os
import subprocess
from lxml import etree
from inkex import Color
class Sudoku(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument("--tab")
pars.add_argument("--difficulty",default="mixed", help='How difficult to make puzzles.')
pars.add_argument("--rows", type=int, default=1, help='Number of puzzle rows.')
pars.add_argument("--cols", type=int, default=1, help='Number of puzzle columns.')
pars.add_argument("--puzzle_size", type=int, default=6, help='The width & height of each puzzle.')
pars.add_argument("--puzzle_gap", type=int, default=1, help='The space between puzzles.')
pars.add_argument("--color_text", type=Color, default=255, help='Color for given numbers.')
pars.add_argument("--color_bkgnd", type=Color, default=4243148799, help='Color for the puzzle background.')
pars.add_argument("--color_puzzle",type=Color, default=2290779647, help='Border color for the puzzles.')
pars.add_argument("--color_boxes", type=Color, default=3298820351, help='Border color for puzzle boxes.')
pars.add_argument("--color_cells", type=Color, default=1923076095, help='Border color for the puzzle cells.')
pars.add_argument("--units", help="The unit of the dimensions")
def draw_grid(self, g_puz, x, y):
bkgnd_style = {'stroke':'none', 'stroke-width':'2', 'fill':self.options.color_bkgnd }
puzzle_style = {'stroke':self.options.color_puzzle, 'stroke-width':'2', 'fill':'none' }
boxes_style = {'stroke':self.options.color_boxes, 'stroke-width':'2', 'fill':'none' }
cells_style = {'stroke':self.options.color_cells, 'stroke-width':'1', 'fill':'none' }
g = etree.SubElement(g_puz, 'g')
self.draw_rect(g, bkgnd_style, self.left+x, self.top+y, self.size, self.size)
self.draw_rect(g, cells_style, self.left+x+self.size/9, self.top+y, self.size/9, self.size)
self.draw_rect(g, cells_style, self.left+x+self.size/3+self.size/9, self.top+y, self.size/9, self.size)
self.draw_rect(g, cells_style, self.left+x+2*self.size/3+self.size/9, self.top+y, self.size/9, self.size)
self.draw_rect(g, cells_style, self.left+x, self.top+y+self.size/9, self.size, self.size/9)
self.draw_rect(g, cells_style, self.left+x, self.top+y+self.size/3+self.size/9, self.size, self.size/9)
self.draw_rect(g, cells_style, self.left+x, self.top+y+2*self.size/3+self.size/9, self.size, self.size/9)
self.draw_rect(g, boxes_style, self.left+x+self.size/3, self.top+y, self.size/3, self.size)
self.draw_rect(g, boxes_style, self.left+x, self.top+y+self.size/3, self.size, self.size/3)
self.draw_rect(g, puzzle_style, self.left+x, self.top+y, self.size, self.size)
def draw_rect(self, g, style, x, y, w, h):
attribs = {'style':str(inkex.Style(style)), 'x':str(x), 'y':str(y), 'height':str(h), 'width':str(w) }
etree.SubElement(g, inkex.addNS('rect','svg'), attribs)
def fill_puzzle(self, g_puz, x, y, data):
cellsize = self.size / 9
txtsize = self.size / 12
offset = (cellsize + txtsize)/2.25
g = etree.SubElement(g_puz, 'g')
text_style = {'font-size':str(txtsize),
'fill':self.options.color_text,
'font-family':'arial',
'text-anchor':'middle', 'text-align':'center' }
#inkex.utils.debug(len(data))
for n in range(len(data)):
#inkex.utils.debug(str(n))
#inkex.utils.debug("data["+str(n)+"]="+str(data[n]))
if str(data[n]) in "123456789":
attribs = {'style': str(inkex.Style(text_style)),
'x': str(self.left + x + n%9 * cellsize + cellsize/2 ), 'y': str(self.top + y + n//9 * cellsize + offset ) }
etree.SubElement(g, 'text', attribs).text = str(data[n])
def effect(self):
extension_dir = os.path.dirname(os.path.realpath(__file__))
if os.name == "nt":
qqwing = os.path.join(extension_dir, "qqwing.exe")
else:
qqwing = os.path.join(extension_dir, "qqwing")
args = [qqwing, "--one-line", "--generate", str(self.options.rows * self.options.cols)]
if self.options.difficulty != 'mixed':
args.extend(["--difficulty", self.options.difficulty])
with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as qqwing_process:
data = qqwing_process.communicate()
#inkex.utils.debug(data)
data_processed = data[0].decode('UTF-8').splitlines()
parent = self.document.getroot()
self.doc_w = self.svg.unittouu(parent.get('width'))
self.doc_h = self.svg.unittouu(parent.get('height'))
self.size = self.svg.unittouu(str(self.options.puzzle_size) + self.options.units)
self.gap = self.svg.unittouu(str(self.options.puzzle_gap) + self.options.units)
self.shift = self.size + self.gap
self.left = (self.doc_w - (self.options.cols * self.shift - self.gap))/2
self.top = (self.doc_h - (self.options.rows * self.shift - self.gap))/2
self.sudoku_g = etree.SubElement(parent, 'g', {'id':'sudoku'})
for row in range(0, self.options.rows):
for col in range(0, self.options.cols):
g = etree.SubElement(self.sudoku_g, 'g', {'id':'puzzle_'+str(col)+str(row)})
self.draw_grid(g, col*self.shift, row*self.shift)
self.fill_puzzle(g, col*self.shift, row*self.shift, data_processed[col+row*self.options.cols])
if __name__ == '__main__':
Sudoku().run()

View File

@ -9,13 +9,13 @@
"license": "GNU LGPL v3",
"license_url": "https://github.com/t-oster/VisiCut/blob/master/LICENSE",
"comment": "",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X/src/branch/master/extensions/fablabchemnitz/visicut",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/visicut",
"fork_url": "https://github.com/t-oster/VisiCut/tree/master/tools/inkscape_extension",
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Open+in+VisiCut",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/t-oster",
"github.com/vmario89"
"github.com/eridur-de"
]
}
]

View File

@ -0,0 +1,20 @@
[
{
"name": "<various>",
"id": "fablabchemnitz.de.vpype_<various>",
"path": "vpypetools",
"dependent_extensions": null,
"original_name": "vpypetools",
"original_id": "fablabchemnitz.de.vpype_<various>",
"license": "GNU GPL v3",
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE",
"comment": "Created by Mario Voigt",
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/vpypetools",
"fork_url": null,
"documentation_url": "https://stadtfabrikanten.org/display/IFM/vpypetools",
"inkscape_gallery_url": "https://inkscape.org/de/~MarioVoigt/%E2%98%85vpypetools-vpype-for-inkscape",
"main_authors": [
"github.com/eridur-de"
]
}
]

View File

@ -0,0 +1,392 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="160.00143mm"
height="40.75mm"
viewBox="0 0 160.00144 40.75"
version="1.1"
id="svg5"
sodipodi:docname="vpype_logo.svg"
inkscape:version="1.2-dev (8ea702d, 2021-04-07)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
objecttolerance="10.0"
gridtolerance="10.0"
guidetolerance="10.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.95875069"
inkscape:cx="500.13002"
inkscape:cy="154.36756"
inkscape:window-width="1920"
inkscape:window-height="950"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="g7907"
fit-margin-left="5"
fit-margin-top="5"
fit-margin-right="5"
fit-margin-bottom="5" />
<defs
id="defs2" />
<g
inkscape:label="fill-hexval-#000000"
inkscape:groupmode="layer"
id="g7855"
transform="translate(5.3677085,5.3677085)" />
<g
inkscape:label="fill-hexval-#008000"
inkscape:groupmode="layer"
id="g7864"
transform="translate(5.3677085,5.3677085)" />
<g
inkscape:label="fill-hexval-#00e000"
inkscape:groupmode="layer"
id="g7880"
transform="translate(5.3677085,5.3677085)" />
<g
inkscape:label="fill-hexval-#9b009b"
inkscape:groupmode="layer"
id="g7887"
transform="translate(5.3677085,5.3677085)" />
<g
inkscape:label="fill-hexval-#f1f5fa"
inkscape:groupmode="layer"
id="g7907"
transform="translate(5.3677085,5.3677085)">
<g
id="g999"
transform="matrix(0.70424172,0,0,0.70424172,-0.10875285,-0.10875285)">
<path
opacity="0.996078"
d="M 1.62057,0.297656 H 20.0091 L 20.2737,1.62057 H 193.443 l 0.133,-1.322914 h 17.065 V 1.62057 l 1.323,0.13229 V 41.308 h -1.323 l -0.132,1.3229 H 193.443 L 193.311,41.308 H 20.1414 l -0.1323,1.3229 H 1.62057 V 41.308 L 0.297656,41.1757 V 1.62057 c 1.322914,0.44097 1.763884,0 1.322914,-1.322914 z m 0,1.322914 V 41.308 H 17.4956 V 39.9851 H 2.94349 V 37.6039 L 3.20807,37.3393 H 17.4956 v -3.9688 h 1.3229 V 5.8539 L 17.4956,5.58932 V 1.62057 Z m 191.82243,0 V 41.308 h 15.875 v -1.3229 h -14.552 v -2.3812 l 0.265,-0.2646 h 14.287 v -3.9688 h 1.323 V 5.8539 L 209.318,5.58932 V 1.62057 Z M 21.4643,2.94349 V 34.6934 H 21.7289 190.797 V 3.20807 2.94349 Z m 0,34.39581 v 2.6458 h 0.2646 169.0681 v -2.3812 -0.2646 z"
id="path6810"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 61.1517,12.2039 1.323,0.1323 v 6.4823 l -1.323,-0.1323 z"
id="path6812"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 74.3809,12.2039 1.3229,0.1323 v 10.451 H 73.058 v 2.6458 h -2.6458 v 2.6459 h -2.6459 v 2.6458 h -2.6458 c -0.441,-1.3229 0,-1.7639 1.3229,-1.3229 V 26.756 h 2.6458 v -2.6459 h 2.6459 v -2.6458 h 2.6458 z"
id="path6814"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 92.9017,12.2039 c 1.3229,-0.441 1.7639,0 1.3229,1.3229 -1.3229,0.441 -1.7639,0 -1.3229,-1.3229 z"
id="path6816"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 103.485,12.2039 1.323,0.1323 v 6.4823 l -1.323,-0.1323 z"
id="path6818"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 114.068,12.2039 1.323,0.1323 v 7.8052 h -2.646 v 2.6458 H 110.1 v 7.9375 h -5.292 v -1.3229 h 3.969 v -7.8052 l 0.132,-0.1323 h 2.514 v -2.6458 h 2.645 z"
id="path6820"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 132.589,12.2039 c 1.323,-0.441 1.764,0 1.323,1.3229 -1.323,0.441 -1.764,0 -1.323,-1.3229 z"
id="path6822"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 153.756,12.2039 h 1.323 v 2.6458 h -10.451 l -0.133,0.1323 v 3.8365 h -1.323 v -5.2917 h 10.452 z"
id="path6824"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 82.3184,13.5268 h 7.8052 l 0.1323,1.3229 h -6.6146 v 6.4823 l -1.3229,0.1323 z"
id="path6826"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 122.006,13.5268 h 7.805 l 0.132,1.3229 h -6.614 v 6.4823 l -1.323,0.1323 z"
id="path6828"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 95.5475,14.8497 1.323,0.1323 v 7.8052 h -2.6459 v 2.5135 l -0.1323,0.1323 h -10.451 v 5.2917 h -5.2917 v -1.3229 h 3.9688 v -5.2917 h 10.5833 v -2.6458 h 2.5135 l 0.1323,-0.1323 z"
id="path6830"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 135.235,14.8497 1.323,0.1323 v 7.8052 h -2.646 v 2.5135 l -0.132,0.1323 h -10.451 v 5.2917 h -5.292 v -1.3229 h 3.969 v -5.2917 h 10.583 v -2.6458 h 2.514 l 0.132,-0.1323 z"
id="path6832"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 99.5163,18.8185 c 1.3227,-0.441 1.7637,0 1.3227,1.3229 -1.3227,0.441 -1.7637,0 -1.3227,-1.3229 z"
id="path6834"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 63.7976,20.1414 c 1.3229,-0.441 1.7639,0 1.3229,1.3229 -1.3229,0.441 -1.7639,0 -1.3229,-1.3229 z"
id="path6836"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 153.756,20.1414 h 1.323 v 2.6458 h -10.451 l -0.133,0.1323 v 3.8365 h -1.323 v -5.2917 h 10.452 z"
id="path6838"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 57.183,21.4643 c 1.3229,-0.441 1.7639,0 1.3229,1.3229 -1.3229,0.441 -1.7639,0 -1.3229,-1.3229 z"
id="path6840"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 102.162,21.4643 c 1.323,-0.441 1.764,0 1.323,1.3229 -1.323,0.441 -1.764,0 -1.323,-1.3229 z"
id="path6842"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 59.8288,24.1101 c 1.3229,-0.4409 1.7639,0 1.3229,1.3229 -1.3229,0.441 -1.7638,0 -1.3229,-1.3229 z"
id="path6844"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 62.4747,26.756 c 1.3229,-0.441 1.7638,0 1.3229,1.3229 -1.3229,0.4409 -1.7639,0 -1.3229,-1.3229 z"
id="path6846"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="m 153.756,28.0789 h 1.323 v 2.6458 h -15.743 l -0.132,-1.3229 h 14.42 z"
id="path6848"
style="fill:#000000;stroke:#000000;stroke-width:1" />
<path
opacity="0.996078"
d="M 2.94349,1.62057 H 17.4956 v 3.83645 l 1.3229,0.26459 V 9.55806 H 17.4956 V 5.72161 L 17.3633,5.58932 H 2.94349 Z"
id="path6850"
style="fill:#008000;stroke:#008000;stroke-width:1" />
<path
opacity="0.996078"
d="m 194.766,1.62057 h 14.552 v 3.83645 l 1.323,0.26459 v 3.83645 h -1.323 V 5.72161 l -0.132,-0.13229 h -14.42 z"
id="path6852"
style="fill:#008000;stroke:#008000;stroke-width:1" />
<path
opacity="0.996078"
d="M 21.4643,2.94349 H 190.665 l 0.132,0.13229 V 8.23515 H 21.5966 l -0.1323,-0.1323 z"
id="path6854"
style="fill:#008000;stroke:#008000;stroke-width:1" />
<path
opacity="0.996078"
d="m 17.4956,29.4018 h 1.3229 v 3.9687 h -1.3229 v 3.9688 H 3.07578 l -0.13229,0.1323 v 2.5135 H 17.3633 l 0.1323,1.3229 H 3.07578 L 2.94349,39.9851 H 1.62057 V 37.3393 H 2.94349 V 33.5028 L 3.07578,33.3705 H 17.4956 Z"
id="path6856"
style="fill:#008000;stroke:#008000;stroke-width:1" />
<path
opacity="0.996078"
d="m 209.318,29.4018 h 1.323 v 3.9687 h -1.323 v 3.9688 h -14.42 l -0.132,0.1323 v 2.5135 h 14.42 l 0.132,1.3229 h -14.42 l -0.132,-1.3229 h -1.323 v -2.6458 h 1.323 v -3.8365 l 0.132,-0.1323 h 14.42 z"
id="path6858"
style="fill:#008000;stroke:#008000;stroke-width:1" />
<path
opacity="0.996078"
d="M 21.4643,30.7247 H 190.665 l 0.132,0.1323 v 3.8364 H 21.5966 l -0.1323,-0.1322 z"
id="path6860"
style="fill:#008000;stroke:#008000;stroke-width:1" />
<path
opacity="0.996078"
d="M 21.4643,37.3393 H 190.665 l 0.132,0.1323 v 2.5135 H 21.5966 l -0.1323,-0.1323 z"
id="path6862"
style="fill:#008000;stroke:#008000;stroke-width:1" />
<path
opacity="0.996078"
d="m 1.62057,1.62057 h 1.32292 v 3.83645 l 0.13229,0.1323 H 17.4956 v 3.83645 l 1.3229,0.26458 V 29.4018 h -1.3229 v 3.9687 H 3.07578 l -0.13229,0.1323 v 3.8365 H 1.62057 V 29.5341 L 2.94349,29.2695 V 18.8185 H 17.4956 V 13.5268 H 2.94349 V 9.69035 L 1.62057,9.42577 Z"
id="path6864"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 193.443,1.62057 h 1.323 v 3.83645 l 0.132,0.1323 h 14.42 v 3.83645 l 1.323,0.26458 V 29.4018 h -1.323 v 3.9687 h -14.42 l -0.132,0.1323 v 3.8365 h -1.323 v -7.8052 l 1.323,-0.2646 v -10.451 h 14.552 V 13.5268 H 194.766 V 9.69035 l -1.323,-0.26458 z"
id="path6866"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="M 21.4643,8.23515 H 190.665 l 0.132,0.13229 v 6.48226 h -35.586 l -0.132,-0.1323 v -2.5135 h -1.323 l -0.132,-1.3229 h -15.743 v 3.9687 h -2.646 v -1.3229 h -1.323 v -1.3229 h -1.323 l -0.132,-1.3229 h -15.743 v 3.9687 h -1.323 v -2.6458 c -1.323,0.441 -1.764,0 -1.323,-1.3229 h -5.291 v 3.9687 h -3.969 v -2.6458 c -1.323,0.441 -1.764,0 -1.323,-1.3229 h -5.2916 v 3.9687 H 95.5475 V 13.5268 H 94.2246 V 12.2039 H 92.9017 L 92.7694,10.881 H 77.0267 v 3.9687 h -1.3229 v -2.6458 c -1.3229,0.441 -1.7639,0 -1.3229,-1.3229 h -5.2917 v 3.9687 h -6.6145 v -2.6458 c -1.323,0.441 -1.7639,0 -1.323,-1.3229 h -5.2916 v 3.8364 l -0.1323,0.1323 H 21.4643 Z"
id="path6868"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 21.4643,18.8185 h 34.2635 l 0.1323,0.1323 v 2.5135 h 1.3229 v 1.3229 h 1.3229 v 1.3229 h 1.3229 v 1.3229 h 1.3229 v 1.323 h 1.323 v 1.3229 h 1.3229 c -0.441,1.3229 0,1.7639 1.3229,1.3229 v 1.3229 H 21.5966 l -0.1323,-0.1323 z"
id="path6870"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 63.7976,18.8185 h 2.6458 v 2.6458 h -1.3229 v -1.3229 c -1.3229,0.441 -1.7639,0 -1.3229,-1.3229 z"
id="path6872"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 75.7038,18.8185 1.3229,0.1323 v 10.3187 l 1.3229,0.1323 v 1.3229 h -10.451 l -0.1323,-0.1323 v -2.5135 h 2.6459 V 25.433 h 2.6458 v -2.6458 h 2.6458 z"
id="path6874"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 83.6413,18.8185 h 6.6146 v 2.6458 h -6.6146 z"
id="path6876"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 96.8705,18.8185 h 2.6458 v 1.3229 h 1.3227 v 1.3229 h 1.323 c -0.441,1.3229 0,1.7639 1.323,1.3229 v 6.4823 l 1.323,0.1323 v 1.3229 H 83.7736 L 83.6413,30.5924 V 25.433 h 10.5833 v -2.6458 h 2.6459 z"
id="path6878"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 115.391,18.8185 1.323,0.1323 v 10.3187 l 1.323,0.1323 v 1.3229 H 110.1 v -7.9375 h 2.645 v -2.6458 h 2.646 z"
id="path6880"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 123.329,18.8185 h 6.614 v 2.6458 h -6.614 z"
id="path6882"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 136.558,18.8185 1.323,0.1323 v 10.3187 l 1.323,0.1323 v 1.3229 H 123.329 V 25.433 h 10.583 v -2.6458 h 2.646 z"
id="path6884"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 153.756,18.8185 h 36.909 l 0.132,0.1323 v 11.7739 h -35.586 l -0.132,-0.1323 v -2.5135 c -1.323,0.4409 -1.764,0 -1.323,-1.3229 h -9.261 v -3.9688 h 10.451 l 0.133,-0.1323 v -2.5135 c -1.323,0.441 -1.764,0 -1.323,-1.3229 z"
id="path6886"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 1.62057,39.9851 c 1.32292,-0.441 1.76389,0 1.32292,1.3229 -1.32292,0.441 -1.76389,0 -1.32292,-1.3229 z"
id="path6888"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 193.443,39.9851 c 1.323,-0.441 1.764,0 1.323,1.3229 -1.323,0.441 -1.764,0 -1.323,-1.3229 z"
id="path6890"
style="fill:#00e000;stroke:#00e000;stroke-width:1" />
<path
opacity="0.996078"
d="m 55.8601,10.881 h 5.2916 v 7.9375 h 2.6459 v 2.6458 h 2.6458 v -2.6458 h 2.5135 l 0.1323,-0.1323 V 10.881 h 5.2917 v 10.5833 h -2.6458 v 2.6458 h -2.6459 v 2.6459 h -2.6458 v 2.6458 H 63.7976 V 26.756 h -2.6459 v -2.6459 h -2.6458 v -2.6458 h -2.6458 z"
id="path6892"
style="fill:#9b009b;stroke:#9b009b;stroke-width:1" />
<path
opacity="0.996078"
d="m 77.0267,10.881 h 15.875 v 2.6458 h 2.5135 l 0.1323,0.1323 v 7.8052 h -2.6458 v 2.5135 l -0.1323,0.1323 h -10.451 v 5.2917 h -5.2917 z m 5.2917,2.6458 v 7.9375 h 7.9375 v -7.9375 z"
id="path6894"
style="fill:#9b009b;stroke:#9b009b;stroke-width:1" />
<path
opacity="0.996078"
d="m 98.1934,10.881 h 5.2916 v 7.9375 h 5.292 V 10.881 h 5.291 v 7.8052 l -0.132,0.1323 h -2.513 v 2.6458 h -2.646 v 7.9375 h -5.292 v -7.8052 l -0.132,-0.1323 h -2.514 v -2.6458 h -2.6456 z"
id="path6896"
style="fill:#9b009b;stroke:#9b009b;stroke-width:1" />
<path
opacity="0.996078"
d="m 116.714,10.881 h 15.875 v 2.6458 h 2.514 l 0.132,0.1323 v 7.8052 h -2.646 v 2.5135 l -0.132,0.1323 h -10.451 v 5.2917 h -5.292 z m 5.292,2.6458 v 7.9375 h 7.937 v -7.9375 z"
id="path6898"
style="fill:#9b009b;stroke:#9b009b;stroke-width:1" />
<path
opacity="0.996078"
d="m 137.881,10.881 h 15.743 l 0.132,0.1323 v 2.5135 h -10.584 v 5.2917 h 10.452 l 0.132,0.1323 v 2.5135 h -10.584 v 5.2917 h 10.452 l 0.132,0.1322 v 2.5136 h -15.875 z"
id="path6900"
style="fill:#9b009b;stroke:#9b009b;stroke-width:1" />
<path
opacity="0.996078"
d="m 0.297656,0.297656 c 1.322914,-0.440972 1.763884,0 1.322914,1.322914 -1.322914,0.44097 -1.763886,0 -1.322914,-1.322914 z"
id="path6902"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="M 20.1414,0.297656 H 193.311 l 0.132,1.322914 H 20.2737 Z"
id="path6904"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 210.641,0.297656 c 1.323,-0.440972 1.764,0 1.323,1.322914 -1.323,0.44097 -1.764,0 -1.323,-1.322914 z"
id="path6906"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 1.62057,9.55806 h 1.32292 v 3.83644 l 0.13229,0.1323 H 17.4956 v 5.2917 H 2.94349 v 10.451 l -1.32292,0.1323 z"
id="path6908"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 193.443,9.55806 h 1.323 v 3.83644 l 0.132,0.1323 h 14.42 v 5.2917 h -14.552 v 10.451 l -1.323,0.1323 z"
id="path6910"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 21.4643,14.8497 h 34.2635 l 0.1323,0.1323 v 3.8365 H 21.5966 l -0.1323,-0.1323 z"
id="path6912"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 62.4747,14.8497 h 6.6145 v 3.9688 h -6.6145 z"
id="path6914"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 75.7038,14.8497 h 1.3229 v 3.9688 h -1.3229 z"
id="path6916"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 83.6413,14.8497 h 6.6146 v 3.9688 h -6.6146 z"
id="path6918"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 96.8705,14.8497 h 1.3229 v 3.9688 h -1.3229 z"
id="path6920"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 104.808,14.8497 h 3.969 v 3.9688 h -3.969 z"
id="path6922"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 115.391,14.8497 h 1.323 v 3.9688 h -1.323 z"
id="path6924"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 123.329,14.8497 h 6.614 v 3.9688 h -6.614 z"
id="path6926"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 136.558,14.8497 h 1.323 v 3.9688 h -1.323 z"
id="path6928"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 144.495,14.8497 h 46.17 l 0.132,0.1323 v 3.8365 h -46.169 l -0.133,-0.1323 z"
id="path6930"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 0.297656,41.308 c 1.322914,-0.441 1.763884,0 1.322914,1.3229 -1.322914,0.441 -1.763886,0 -1.322914,-1.3229 z"
id="path6932"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="M 20.1414,41.308 H 193.311 l 0.132,1.3229 H 20.2737 Z"
id="path6934"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
<path
opacity="0.996078"
d="m 210.641,41.308 c 1.323,-0.441 1.764,0 1.323,1.3229 -1.323,0.441 -1.764,0 -1.323,-1.3229 z"
id="path6936"
style="fill:#f1f5fa;stroke:#f1f5fa;stroke-width:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,453 @@
#!/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
import inkex
from inkex import transforms, bezier, PathElement
from inkex.paths import CubicSuperPath, Path
from inkex.command import inkscape
import vpype
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
"""
Extension for InkScape 1.X
Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org
Date: 02.04.2021
Last patch: 06.06.2021
License: GNU GPL v3
This piece of spaghetti-code, called "vpypetools", is a wrapper to pass (pipe) line elements from InkScape selection (or complete canvas) to vpype.
It allows to run basic commands on the geometry. The converted lines are getting pushed back into InkScape.
vpypetools allows to enable some important adjusters and debugging settings to get the best out of it.
vpypetools is based on
- Aaron Spike's "Flatten Bezier" extension, 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)
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
Todo's
- find some python code to auto-convert strokes and objects to paths (for input and for output again)
- remove fill property of converted lines (because there is no fill anymore) without crashing Inkscape ...
- as we use flatten() we modify the original path. rewrite to avoid modifications to original path
"""
class vpypetools (inkex.EffectExtension):
def add_arguments(self, pars):
# Line Sorting
pars.add_argument("--linesort", type=inkex.Boolean, default=False)
pars.add_argument("--linesort_no_flip", type=inkex.Boolean, default=False, help="Disable reversing stroke direction for optimization")
# Line Merging
pars.add_argument("--linemerge", type=inkex.Boolean, default=False)
pars.add_argument("--linemerge_tolerance", type=float, default=0.500, help="Maximum distance between two line endings that should be merged (default 0.500 mm)")
pars.add_argument("--linemerge_no_flip", type=inkex.Boolean, default=False, help="Disable reversing stroke direction for merging")
# Trimming
pars.add_argument("--trim", type=inkex.Boolean, default=False)
pars.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
pars.add_argument("--trim_y_margin", type=float, default=0.000, help="trim margin - y direction (mm)") # keep default at 0.000 to keep clean bbox
# Relooping
pars.add_argument("--reloop", type=inkex.Boolean, default=False)
pars.add_argument("--reloop_tolerance", type=float, default=0.500, help="Controls how close the path beginning and end must be to consider it closed (default 0.500 mm)")
# Multipass
pars.add_argument("--multipass", type=inkex.Boolean, default=False)
pars.add_argument("--multipass_count", type=int, default=2, help="How many passes for each line (default 2)")
# Filter
pars.add_argument("--filter", type=inkex.Boolean, default=False)
pars.add_argument("--filter_tolerance", type=float, default=0.050, help="Tolerance used to determined if a line is closed or not (default 0.050 mm)")
pars.add_argument("--filter_closed", type=inkex.Boolean, default=False, help="Keep closed lines")
pars.add_argument("--filter_not_closed", type=inkex.Boolean, default=False, help="Keep open lines")
pars.add_argument("--filter_min_length_enabled", type=inkex.Boolean, default=False, help="filter by min length")
pars.add_argument("--filter_min_length", type=float, default=0.000, help="Keep lines whose length isn't shorter than value")
pars.add_argument("--filter_max_length_enabled", type=inkex.Boolean, default=False, help="filter by max length")
pars.add_argument("--filter_max_length", type=float, default=0.000, help="Keep lines whose length isn't greater than value")
# Split All
pars.add_argument("--splitall", type=inkex.Boolean, default=False)
# Plugin Occult
pars.add_argument("--plugin_occult", type=inkex.Boolean, default=False)
pars.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)")
pars.add_argument("--plugin_occult_keepseparatelayer", type=inkex.Boolean, default=False, help="Put occulted lines to separate layer")
# Plugin Deduplicate
pars.add_argument("--plugin_deduplicate", type=inkex.Boolean, default=False)
pars.add_argument("--plugin_deduplicate_tolerance", type=float, default=0.01, help="Max distance between points to consider them equal (default 0.01 mm)")
pars.add_argument("--plugin_deduplicate_keepseparatelayer", type=inkex.Boolean, default=False, help="Put duplicate lines to separate layer")
# Free Mode
pars.add_argument("--tab")
pars.add_argument("--freemode", type=inkex.Boolean, default=False)
pars.add_argument("--freemode_cmd1", default="")
pars.add_argument("--freemode_cmd1_enabled", type=inkex.Boolean, default=True)
pars.add_argument("--freemode_cmd2", default="")
pars.add_argument("--freemode_cmd2_enabled", type=inkex.Boolean, default=False)
pars.add_argument("--freemode_cmd3", default="")
pars.add_argument("--freemode_cmd3_enabled", type=inkex.Boolean, default=False)
pars.add_argument("--freemode_cmd4", default="")
pars.add_argument("--freemode_cmd4_enabled", type=inkex.Boolean, default=False)
pars.add_argument("--freemode_cmd5", default="")
pars.add_argument("--freemode_cmd5_enabled", type=inkex.Boolean, default=False)
pars.add_argument("--freemode_show_cmd", type=inkex.Boolean, default=False)
# General Settings
pars.add_argument("--input_handling", default="paths", help="Input handling")
pars.add_argument("--flattenbezier", type=inkex.Boolean, default=False, help="Flatten bezier curves to polylines")
pars.add_argument("--flatness", type=float, default=0.1, help="Minimum flatness = 0.1. The smaller the value the more fine segments you will get (quantization).")
pars.add_argument("--decimals", type=int, default=3, help="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'")
pars.add_argument("--simplify", type=inkex.Boolean, default=False, help="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'")
pars.add_argument("--parallel", type=inkex.Boolean, default=False, help="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'")
pars.add_argument("--output_show", type=inkex.Boolean, default=False, help="This will open a separate window showing the finished SVG data. If enabled, output is not applied to InkScape canvas (only for preview)!")
pars.add_argument("--output_show_points", type=inkex.Boolean, default=False, help="Enable point display in viewer")
pars.add_argument("--output_stats", type=inkex.Boolean, default=False, help="Show output statistics before/after conversion")
pars.add_argument("--output_trajectories", type=inkex.Boolean, default=False, help="Add paths for the travel trajectories")
pars.add_argument("--keep_objects", type=inkex.Boolean, default=False, help="If false, selected paths will be removed")
pars.add_argument("--strokes_to_paths", type=inkex.Boolean, default=True, help="Recommended option. Performs 'Path' > 'Stroke to Path' (CTRL + ALT + C) to convert vpype converted lines back to regular path objects")
pars.add_argument("--use_style_of_first_element", type=inkex.Boolean, default=True, help="If enabled the first element in selection is scanned and we apply it's style to all imported vpype lines (but not for trajectories). Does not work for 'Multilayer/document'")
pars.add_argument("--lines_stroke_width", type=float, default=1.0, help="Stroke width of tooling lines (px). Gets overwritten if 'Use style of first selected element' is enabled")
pars.add_argument("--trajectories_stroke_width", type=float, default=1.0, help="Stroke width of trajectory lines (px). Gets overwritten if 'Use style of first selected element' is enabled")
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
elementsToWork = [] # 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
def flatten(node):
#path = node.path.to_superpath()
path = node.path.transform(node.composed_transform()).to_superpath()
bezier.cspsubdiv(path, self.options.flatness)
newpath = []
for subpath in path:
first = True
for csp in subpath:
cmd = 'L'
if first:
cmd = 'M'
first = False
newpath.append([cmd, [csp[1][0], csp[1][1]]])
node.path = newpath
# flatten the node's path to linearize, split up the path to it's subpaths (break apart) and add all points to the vpype lines collection
def convertPath(node, nodes = None):
if nodes is None:
nodes = []
if node.tag == inkex.addNS('path','svg'):
nodes.append(node)
if self.options.flattenbezier is True:
flatten(node)
raw = node.path.to_arrays()
subPaths, prev = [], 0
for i in range(len(raw)): # Breaks compound paths into simple paths
if raw[i][0] == 'M' and i != 0:
subPaths.append(raw[prev:i])
prev = i
subPaths.append(raw[prev:])
for subPath in subPaths:
points = []
for csp in subPath:
if len(csp[1]) > 0: #we need exactly two points per straight line segment
points.append(Point(round(csp[1][0], self.options.decimals), round(csp[1][1], self.options.decimals)))
if len(subPath) > 2 and (subPath[-1][0] == 'Z' or subPath[0][1] == subPath[-1][1]): #check if path has more than 2 points and is closed by Z or first pont == last point
points.append(Point(round(subPath[0][1][0], self.options.decimals), round(subPath[0][1][1], self.options.decimals))) #if closed, we add the first point again
lc.append(LineString(points))
children = node.getchildren()
if children is not None:
for child in children:
convertPath(child, nodes)
return nodes
doc = None #create a vpype document
'''
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 layers in the complete document
'''
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:
elementsToWork = convertPath(self.document.getroot())
for element in elementsToWork:
input_bbox += element.bounding_box()
else:
elementsToWork = None
for element in self.svg.selected.values():
elementsToWork = convertPath(element, elementsToWork)
#input_bbox = inkex.elements._selected.ElementList.bounding_box(self.svg.selected) # get BoundingBox for selection
input_bbox = self.svg.selection.bounding_box() # get BoundingBox for selection
if len(lc) == 0:
self.msg('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 element in elementsToWork:
if element.attrib.has_key('style'):
firstElementStyle = element.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
elif self.options.input_handling == "layers":
doc = vpype.read_multilayer_svg(self.options.input_file, quantization = self.options.flatness, crop = False, simplify = self.options.simplify, parallel = self.options.parallel, default_width = self.document.getroot().get('width'), default_height = self.document.getroot().get('height'))
for element in self.document.getroot().xpath("//svg:g", namespaces=inkex.NSS): #all groups/layers
elementsToWork.append(element)
tooling_length_before = doc.length()
traveling_length_before = doc.pen_up_length()
# build and execute the conversion command
# the following code block is not intended to sum up the commands to build a series (pipe) of commands!
##########################################
# Line Sorting
if self.options.linesort is True:
command = "linesort "
if self.options.linesort_no_flip is True:
command += " --no-flip"
# Line Merging
if self.options.linemerge is True:
command = "linemerge --tolerance " + str(self.options.linemerge_tolerance)
if self.options.linemerge_no_flip is True:
command += " --no-flip"
# Trimming
if self.options.trim is True:
command = "trim " + str(self.options.trim_x_margin) + " " + str(self.options.trim_y_margin)
# Relooping
if self.options.reloop is True:
command = "reloop --tolerance " + str(self.options.reloop_tolerance)
# Multipass
if self.options.multipass is True:
command = "multipass --count " + str(self.options.multipass_count)
# Filter
if self.options.filter is True:
command = "filter --tolerance " + str(self.options.filter_tolerance)
if self.options.filter_min_length_enabled is True:
command += " --min-length " + str(self.options.filter_min_length)
if self.options.filter_max_length_enabled is True:
command += " --max-length " + str(self.options.filter_max_length)
if self.options.filter_closed is True and self.options.filter_not_closed is False:
command += " --closed"
if self.options.filter_not_closed is True and self.options.filter_closed is False:
command += " --not-closed"
if self.options.filter_closed is False and \
self.options.filter_not_closed is False and \
self.options.filter_min_length_enabled is False and \
self.options.filter_max_length_enabled is False:
self.msg('No filters to apply. Please select at least one filter.')
return
# Plugin Occult
if self.options.plugin_occult is True:
command = "occult --tolerance " + str(self.options.plugin_occult_tolerance)
if self.options.plugin_occult_keepseparatelayer is True:
command += " --keep-occulted"
# Plugin Deduplicate
if self.options.plugin_deduplicate is True:
command = "deduplicate --tolerance " + str(self.options.plugin_deduplicate_tolerance)
if self.options.plugin_deduplicate_keepseparatelayer is True:
command += " --keep-duplicates"
# Split All
if self.options.splitall is True:
command = " splitall"
# Free Mode
if self.options.freemode is True:
command = ""
if self.options.freemode_cmd1_enabled is True:
command += " " + self.options.freemode_cmd1.strip()
if self.options.freemode_cmd2_enabled is True:
command += " " + self.options.freemode_cmd2.strip()
if self.options.freemode_cmd3_enabled is True:
command += " " + self.options.freemode_cmd3.strip()
if self.options.freemode_cmd4_enabled is True:
command += " " + self.options.freemode_cmd4.strip()
if self.options.freemode_cmd5_enabled is True:
command += " " + self.options.freemode_cmd5.strip()
if self.options.freemode_cmd1_enabled is False and \
self.options.freemode_cmd2_enabled is False and \
self.options.freemode_cmd3_enabled is False and \
self.options.freemode_cmd4_enabled is False and \
self.options.freemode_cmd5_enabled is False:
self.msg("Warning: empty vpype pipeline. With this you are just getting read-write layerset/lineset.")
else:
if self.options.freemode_show_cmd is True:
self.msg("Your command pipe will be the following:")
self.msg(command)
# self.msg(command)
try:
warnings.filterwarnings('ignore', 'SelectableGroups dict interface')
doc = execute(command, doc)
except Exception as e:
self.msg("Error in vpype:\n" + str(e))
return
##########################################
tooling_length_after = doc.length()
traveling_length_after = doc.pen_up_length()
if tooling_length_before > 0:
tooling_length_saving = (1.0 - tooling_length_after / tooling_length_before) * 100.0
else:
tooling_length_saving = 0.0
if traveling_length_before > 0:
traveling_length_saving = (1.0 - traveling_length_after / traveling_length_before) * 100.0
else:
traveling_length_saving = 0.0
if self.options.output_stats is True:
self.msg('Total tooling length before vpype conversion: ' + str('{:0.2f}'.format(tooling_length_before)) + ' mm')
self.msg('Total traveling length before vpype conversion: ' + str('{:0.2f}'.format(traveling_length_before)) + ' mm')
self.msg('Total tooling length after vpype conversion: ' + str('{:0.2f}'.format(tooling_length_after)) + ' mm')
self.msg('Total traveling length after vpype conversion: ' + str('{:0.2f}'.format(traveling_length_after)) + ' mm')
self.msg('Total tooling length optimized: ' + str('{:0.2f}'.format(tooling_length_saving)) + ' %')
self.msg('Total traveling length optimized: ' + str('{:0.2f}'.format(traveling_length_saving)) + ' %')
if tooling_length_after == 0:
self.msg('No lines left after vpype conversion. Conversion result is empty. Cannot continue. Check your document about containing any svg:path elements. You will need to convert objects and strokes to paths first! Vpype command chain was:')
self.msg(command)
return
# 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=self.options.output_show_points, 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=self.options.output_show_points, argv=None) # https://vpype.readthedocs.io/en/stable/api/vpype_viewer.ViewMode.html
warnings.filterwarnings("default") # reset warning filter
exit(0) #we leave the code loop because we only want to preview. We don't want to import the geometry
# 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', single_path = 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()
# parse the SVG file
try:
stream = open(output_file, 'r')
except FileNotFoundError as e:
self.msg("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:
if len(trajectoriesLayer) > 0:
trajectoriesLayer[0].set('style', 'stroke:#0000ff;stroke-width:{:0.2f}px;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill:none'.format(self.options.trajectories_stroke_width))
trajectoriesLayer[0].attrib.pop('stroke') # remove unneccesary stroke attribute
trajectoriesLayer[0].attrib.pop('fill') # remove unneccesary fill attribute
else:
if len(trajectoriesLayer) > 0:
trajectoriesLayer[0].delete()
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.use_style_of_first_element is True and self.options.input_handling == "paths" and firstElementStyle is not None:
# if we remove the fill property and use "Use style of first element in layer" the conversion will just crash with an unknown reason
#declarations = firstElementStyle.split(';')
#for i, decl in enumerate(declarations):
# parts = decl.split(':', 2)
# if len(parts) == 2:
# (prop, val) = parts
# prop = prop.strip().lower()
# #if prop == 'fill':
# # declarations[i] = prop + ':none'
for lineLayer in lineLayers:
#lineLayer.set('style', ';'.join(declarations))
lineLayer.set('style', firstElementStyle)
lineLayer.attrib.pop('stroke') # remove unneccesary stroke attribute
lineLayer.attrib.pop('fill') # remove unneccesary fill attribute
else:
for lineLayer in lineLayers:
if lineLayer.attrib.has_key('stroke'):
color = lineLayer.get('stroke')
lineLayer.set('style', 'stroke:' + color + ';stroke-width:{:0.2f}px;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill:none'.format(self.options.lines_stroke_width))
lineLayer.attrib.pop('stroke') # remove unneccesary stroke attribute
lineLayer.attrib.pop('fill') # remove unneccesary fill attribute
import_viewBox = import_doc.getroot().get('viewBox').split(" ")
self_viewBox = self.document.getroot().get('viewBox')
if self_viewBox is not None: #some SVG files do not have this attribute
self_viewBoxValues = self_viewBox.split(" ")
scaleX = self.svg.unittouu(self_viewBoxValues[2]) / self.svg.unittouu(import_viewBox[2])
scaleY = self.svg.unittouu(self_viewBoxValues[3]) / self.svg.unittouu(import_viewBox[3])
for element in import_doc.getroot().iter("{http://www.w3.org/2000/svg}g"):
e = self.document.getroot().append(element)
if self.options.input_handling == "layers":
if self_viewBox is not None:
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
# convert vpype polylines/lines/polygons to regular paths again (objects to paths)
if self.options.strokes_to_paths is True:
for line in element.iter("{http://www.w3.org/2000/svg}line"):
newLine = PathElement()
newLine.path = Path("M {},{} L {},{}".format(line.attrib['x1'], line.attrib['y1'], line.attrib['x2'], line.attrib['y2']))
element.append(newLine)
line.delete()
for polyline in element.iter("{http://www.w3.org/2000/svg}polyline"):
newPolyLine = PathElement()
newPolyLine.path = Path('M' + polyline.attrib['points'])
element.append(newPolyLine)
polyline.delete()
for polygon in element.iter("{http://www.w3.org/2000/svg}polygon"):
newPolygon = PathElement()
#newPolygon.path = Path('M' + " ".join(polygon.attrib['points'].split(' ')[:-1]) + ' Z') #remove the last point of the points string by splitting at whitespace, converting to array and removing the last item. then converting back to string
newPolygon.path = Path('M' + " ".join(polygon.attrib['points'].split(' ')) + ' Z')
element.append(newPolygon)
polygon.delete()
# Delete the temporary file again because we do not need it anymore
if os.path.exists(output_file):
os.remove(output_file)
# Remove selection objects to do a real replace with new objects from vpype document
if self.options.keep_objects is False:
for element in elementsToWork:
element.delete()
if __name__ == '__main__':
vpypetools().run()

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Deduplicate Plugin</name>
<id>fablabchemnitz.de.vpype_plugin_deduplicate</id>
<param name="tab" type="notebook">
<page name="tab_settings_deduplicate" gui-text="vpypetools Deduplicate Plugin">
<image>vpype_logo.svg</image>
<label>Plug-in to remove overlapping lines in SVG files. You need to install deduplicate plugin for vpype. See vpypetools documentation.</label>
<param name="plugin_deduplicate" type="bool" gui-hidden="true">true</param>
<param name="plugin_deduplicate_tolerance" type="float" min="0.000" max="99999.000" precision="3" gui-text="tolerance (mm)" gui-description="Max distance between points to consider them equal (default 0.01 mm)">0.01</param>
<param name="plugin_deduplicate_keepseparatelayer" gui-text="Put duplicate lines to separate layer" type="bool">false</param>
</page>
<page name="tab_settings_general" gui-text="General Settings">
<label appearance="header">Input (InkScape to vpype)</label>
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input/Layer handling">
<option value="layers">Multilayer/document (all layers/complete document)</option>
<option value="paths">Singlelayer/paths (a single layer/paths in selection or all paths in document)</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Quantization (flatten bezier curves to polylines)" gui-description="Convert bezier curves to polylines. Automatically enabled if 'Multilayer/document' (quantization parameter)">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get. This value is also used by option 'simplify'">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'">3</param>
<param name="simplify" type="bool" gui-text="Simplify geometry" gui-description="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'">false</param>
<param name="parallel" type="bool" gui-text="Parallelize Simplify geometry" gui-description="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'">false</param>
<spacer/>
<label appearance="header">Validation (vpype pipeline)</label>
<param name="output_show" type="bool" gui-text="Preview only (debug output)" gui-description="This will open a separate window showing the finished SVG data. If enabled output, is not applied to InkScape canvas (only for preview)!">false</param>
<param name="output_show_points" type="bool" gui-text="Enable point display in viewer">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion. Helps to find out length savings.">false</param>
<spacer/>
<label appearance="header">Output (vpype to InkScape)</label>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_objects" type="bool" gui-text="Keep original objects" gui-description="If false, original (selected) 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. Warning: increases import time significantly.">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element in layer" 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). Does not work for 'Multilayer/document'">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="tab_about" gui-text="About">
<label appearance="header">vpypetools Deduplicate Plugin</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/vpypetools</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/abey79/vpype</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="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 needs-live-preview="true">
<object-type>all</object-type>
<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,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Filter</name>
<id>fablabchemnitz.de.vpype_filter</id>
<param name="tab" type="notebook">
<page name="tab_settings_filter" gui-text="vpypetools Filter">
<image>vpype_logo.svg</image>
<label>Filter paths according to specified criterion. When an option is provided the corresponding criterion is applied and paths which do not respect the criterion are rejected.</label>
<param name="filter" type="bool" gui-hidden="true">true</param>
<param name="filter_tolerance" type="float" min="0.000" max="99999.000" precision="3" gui-text="tolerance (mm)" gui-description="Tolerance used to determined if a line is closed or not (default 0.050 mm)">0.050</param>
<param name="filter_closed" type="bool" gui-text="Keep closed lines">false</param>
<param name="filter_not_closed" type="bool" gui-text="Keep open lines">false</param>
<param name="filter_min_length_enabled" type="bool" gui-text="filter by min length">false</param>
<param name="filter_min_length" type="float" min="0.000" max="99999.000" precision="3" gui-text="minimum length (mm)" gui-description="Keep lines whose length isn't shorter than value">0.000</param>
<param name="filter_max_length_enabled" type="bool" gui-text="filter by max length">false</param>
<param name="filter_max_length" type="float" min="0.000" max="99999.000" precision="3" gui-text="maximum length (mm)" gui-description="Keep lines whose length isn't greater than value">0.000</param>
</page>
<page name="tab_general_settings" gui-text="General Settings">
<label appearance="header">Input (InkScape to vpype)</label>
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input/Layer handling">
<option value="layers">Multilayer/document (all layers/complete document)</option>
<option value="paths">Singlelayer/paths (a single layer/paths in selection or all paths in document)</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Quantization (flatten bezier curves to polylines)" gui-description="Convert bezier curves to polylines. Automatically enabled if 'Multilayer/document' (quantization parameter)">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get. This value is also used by option 'simplify'">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'">3</param>
<param name="simplify" type="bool" gui-text="Simplify geometry" gui-description="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'">false</param>
<param name="parallel" type="bool" gui-text="Parallelize Simplify geometry" gui-description="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'">false</param>
<spacer/>
<label appearance="header">Validation (vpype pipeline)</label>
<param name="output_show" type="bool" gui-text="Preview only (debug output)" gui-description="This will open a separate window showing the finished SVG data. If enabled output, is not applied to InkScape canvas (only for preview)!">false</param>
<param name="output_show_points" type="bool" gui-text="Enable point display in viewer">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion. Helps to find out length savings.">false</param>
<spacer/>
<label appearance="header">Output (vpype to InkScape)</label>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_objects" type="bool" gui-text="Keep original objects" gui-description="If false, original (selected) 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. Warning: increases import time significantly.">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element in layer" 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). Does not work for 'Multilayer/document'">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="tab_about" gui-text="About">
<label appearance="header">vpypetools Filter</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/vpypetools</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/abey79/vpype</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="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 needs-live-preview="true">
<object-type>all</object-type>
<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,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>vpype Free Mode</name>
<id>fablabchemnitz.de.vpype_free_mode</id>
<param name="tab" type="notebook">
<page name="tab_settings_freemode" gui-text="vpypetools Free Mode Entries">
<image>vpype_logo.svg</image>
<label>Enter your desired command pipes and enable/disable as much fields as you like.</label>
<param name="freemode" type="bool" gui-hidden="true">true</param>
<param name="freemode_cmd1" type="string" appearance="multiline" gui-text="Command Set 1">layout 1024x768 translate 150px 50px</param>
<param name="freemode_cmd1_enabled" type="bool" gui-text="Enable Command Set 1">true</param>
<param name="freemode_cmd2" type="string" appearance="multiline" gui-text="Command Set 2">scaleto -o 0 0 330 200</param>
<param name="freemode_cmd2_enabled" type="bool" gui-text="Enable Command Set 2">false</param>
<param name="freemode_cmd3" type="string" appearance="multiline" gui-text="Command Set 3">rotate 30.0</param>
<param name="freemode_cmd3_enabled" type="bool" gui-text="Enable Command Set 3">false</param>
<param name="freemode_cmd4" type="string" appearance="multiline" gui-text="Command Set 4">skew 45.0 60.0</param>
<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="tab_settings_general" gui-text="General Settings">
<label appearance="header">Input (InkScape to vpype)</label>
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input/Layer handling">
<option value="layers">Multilayer/document (all layers/complete document)</option>
<option value="paths">Singlelayer/paths (a single layer/paths in selection or all paths in document)</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Quantization (flatten bezier curves to polylines)" gui-description="Convert bezier curves to polylines. Automatically enabled if 'Multilayer/document' (quantization parameter)">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get. This value is also used by option 'simplify'">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'">3</param>
<param name="simplify" type="bool" gui-text="Simplify geometry" gui-description="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'">false</param>
<param name="parallel" type="bool" gui-text="Parallelize Simplify geometry" gui-description="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'">false</param>
<spacer/>
<label appearance="header">Validation (vpype pipeline)</label>
<param name="output_show" type="bool" gui-text="Preview only (debug output)" gui-description="This will open a separate window showing the finished SVG data. If enabled output, is not applied to InkScape canvas (only for preview)!">false</param>
<param name="output_show_points" type="bool" gui-text="Enable point display in viewer">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion. Helps to find out length savings.">false</param>
<spacer/>
<label appearance="header">Output (vpype to InkScape)</label>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_objects" type="bool" gui-text="Keep original objects" gui-description="If false, original (selected) 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. Warning: increases import time significantly.">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element in layer" 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). Does not work for 'Multilayer/document'">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="tab_reference" gui-text="CLI Reference">
<label appearance="header">CLI reference (command list)</label>
<label appearance="url">https://vpype.readthedocs.io/en/stable/reference.html</label>
<separator/>
<label appearance="header">General:</label>
<label xml:space="preserve"> lmove, lcopy, ldelete, show, stat</label>
<label appearance="header">Input/Output (*):</label>
<label xml:space="preserve">* works but not recommended to use inside Inkscape</label>
<label xml:space="preserve"> read, write (we use Inkscape's current canvas for this)</label>
<label appearance="header">Layout and transforms:</label>
<label xml:space="preserve"> layout, scale, translate, skew, rotate, scaleto, snap, crop, trim, pagesize</label>
<label appearance="header">Plotting optimization:</label>
<label xml:space="preserve"> linemerge, linesort, linesimplify, reloop, multipass</label>
<label appearance="header">Filters:</label>
<label xml:space="preserve"> filter, squiggles, splitall, reverse</label>
<label appearance="header">Generation:</label>
<label xml:space="preserve"> begin, end, line, rect, circle, ellipse, arc, text, grid, frame, random</label>
<label appearance="header">External:</label>
<label xml:space="preserve"> vpype-text, hatched, occult, deduplicate, script, ...</label>
<separator/>
<label appearance="header">Supported basis units:</label>
<label xml:space="preserve"> cm, mm, in, pt, pc, px | not supported: %, m, em, ex, ft</label>
<label appearance="header">Supported angle units:</label>
<label xml:space="preserve"> deg, grad, rad, turn</label>
</page>
<page name="tab_about" gui-text="About">
<label appearance="header">vpypetools Free Mode</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/vpypetools</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/abey79/vpype</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="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 needs-live-preview="true">
<object-type>all</object-type>
<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,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Line Merging (Combine Paths)</name>
<id>fablabchemnitz.de.vpype_linemerging</id>
<param name="tab" type="notebook">
<page name="tab_settings_linemerge" gui-text="vpypetools Line Merging (Combine Paths)">
<image>vpype_logo.svg</image>
<label>Merge lines whose endings and starts overlap or are very close.</label>
<param name="linemerge" type="bool" gui-hidden="true">true</param>
<param name="linemerge_tolerance" type="float" min="0.000" max="99999.000" precision="3" gui-text="tolerance (mm)" gui-description="Maximum distance between two line endings that should be merged (default 0.500 mm)">0.500</param>
<param name="linemerge_no_flip" type="bool" gui-text="no flip" gui-description="Disable reversing stroke direction for merging">false</param>
</page>
<page name="tab_settings_general" gui-text="General Settings">
<label appearance="header">Input (InkScape to vpype)</label>
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input/Layer handling">
<option value="layers">Multilayer/document (all layers/complete document)</option>
<option value="paths">Singlelayer/paths (a single layer/paths in selection or all paths in document)</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Quantization (flatten bezier curves to polylines)" gui-description="Convert bezier curves to polylines. Automatically enabled if 'Multilayer/document' (quantization parameter)">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get. This value is also used by option 'simplify'">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'">3</param>
<param name="simplify" type="bool" gui-text="Simplify geometry" gui-description="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'">false</param>
<param name="parallel" type="bool" gui-text="Parallelize Simplify geometry" gui-description="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'">false</param>
<spacer/>
<label appearance="header">Validation (vpype pipeline)</label>
<param name="output_show" type="bool" gui-text="Preview only (debug output)" gui-description="This will open a separate window showing the finished SVG data. If enabled output, is not applied to InkScape canvas (only for preview)!">false</param>
<param name="output_show_points" type="bool" gui-text="Enable point display in viewer">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion. Helps to find out length savings.">false</param>
<spacer/>
<label appearance="header">Output (vpype to InkScape)</label>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_objects" type="bool" gui-text="Keep original objects" gui-description="If false, original (selected) 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. Warning: increases import time significantly.">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element in layer" 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). Does not work for 'Multilayer/document'">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="tab_about" gui-text="About">
<label appearance="header">vpypetools Line Merging (Combine Paths)</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/vpypetools</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/abey79/vpype</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="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 needs-live-preview="true">
<object-type>all</object-type>
<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,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Line Sorting</name>
<id>fablabchemnitz.de.vpype_linesorting</id>
<param name="tab" type="notebook">
<page name="tab_settings_linesort" gui-text="vpypetools Line Sorting">
<image>vpype_logo.svg</image>
<label>Sort lines to minimize travel distances.</label>
<param name="linesort" type="bool" gui-hidden="true">true</param>
<param name="linesort_no_flip" type="bool" gui-text="Disable flipping" gui-description="Disable reversing stroke direction for optimization '--no-flip'">false</param>
</page>
<page name="tab_settings_general" gui-text="General Settings">
<label appearance="header">Input (InkScape to vpype)</label>
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input/Layer handling">
<option value="layers">Multilayer/document (all layers/complete document)</option>
<option value="paths">Singlelayer/paths (a single layer/paths in selection or all paths in document)</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Quantization (flatten bezier curves to polylines)" gui-description="Convert bezier curves to polylines. Automatically enabled if 'Multilayer/document' (quantization parameter)">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get. This value is also used by option 'simplify'">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'">3</param>
<param name="simplify" type="bool" gui-text="Simplify geometry" gui-description="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'">false</param>
<param name="parallel" type="bool" gui-text="Parallelize Simplify geometry" gui-description="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'">false</param>
<spacer/>
<label appearance="header">Validation (vpype pipeline)</label>
<param name="output_show" type="bool" gui-text="Preview only (debug output)" gui-description="This will open a separate window showing the finished SVG data. If enabled output, is not applied to InkScape canvas (only for preview)!">false</param>
<param name="output_show_points" type="bool" gui-text="Enable point display in viewer">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion. Helps to find out length savings.">false</param>
<spacer/>
<label appearance="header">Output (vpype to InkScape)</label>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_objects" type="bool" gui-text="Keep original objects" gui-description="If false, original (selected) 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. Warning: increases import time significantly.">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element in layer" 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). Does not work for 'Multilayer/document'">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="tab_about" gui-text="About">
<label appearance="header">vpypetools Line Sorting</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/vpypetools</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/abey79/vpype</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="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 needs-live-preview="true">
<object-type>all</object-type>
<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,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Multipass</name>
<id>fablabchemnitz.de.vpype_multipass</id>
<param name="tab" type="notebook">
<page name="tab_settings_multipass" gui-text="vpypetools Multipass">
<image>vpype_logo.svg</image>
<label>Add multiple passes to each line. Each line is extended with a mirrored copy of itself, optionally multiple times. This is useful for pens that need several passes to ensure a good quality.</label>
<param name="multipass" type="bool" gui-hidden="true">true</param>
<param name="multipass_count" type="int" min="2" max="9999" gui-text="count" gui-description="How many passes for each line (default 2)">2</param>
</page>
<page name="tab_settings_general" gui-text="General Settings">
<label appearance="header">Input (InkScape to vpype)</label>
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input/Layer handling">
<option value="layers">Multilayer/document (all layers/complete document)</option>
<option value="paths">Singlelayer/paths (a single layer/paths in selection or all paths in document)</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Quantization (flatten bezier curves to polylines)" gui-description="Convert bezier curves to polylines. Automatically enabled if 'Multilayer/document' (quantization parameter)">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get. This value is also used by option 'simplify'">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'">3</param>
<param name="simplify" type="bool" gui-text="Simplify geometry" gui-description="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'">false</param>
<param name="parallel" type="bool" gui-text="Parallelize Simplify geometry" gui-description="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'">false</param>
<spacer/>
<label appearance="header">Validation (vpype pipeline)</label>
<param name="output_show" type="bool" gui-text="Preview only (debug output)" gui-description="This will open a separate window showing the finished SVG data. If enabled output, is not applied to InkScape canvas (only for preview)!">false</param>
<param name="output_show_points" type="bool" gui-text="Enable point display in viewer">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion. Helps to find out length savings.">false</param>
<spacer/>
<label appearance="header">Output (vpype to InkScape)</label>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_objects" type="bool" gui-text="Keep original objects" gui-description="If false, original (selected) 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. Warning: increases import time significantly.">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element in layer" 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). Does not work for 'Multilayer/document'">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="tab_about" gui-text="About">
<label appearance="header">vpypetools Multipass</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/vpypetools</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/abey79/vpype</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="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 needs-live-preview="true">
<object-type>all</object-type>
<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,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Occult Plugin (HLR)</name>
<id>fablabchemnitz.de.vpype_plugin_occult</id>
<param name="tab" type="notebook">
<page name="tab_settings_occult" gui-text="vpypetools Occult Plugin (HLR)">
<image>vpype_logo.svg</image>
<label>This plugin command removes lines hidden by polygons. It acts like a trimmer to receive a clean set of visible paths only. It does not remove common lines between opened paths, but for closed polygons. You need to install occult plugin for vpype. See vpypetools documentation.</label>
<param name="plugin_occult" type="bool" gui-hidden="true">true</param>
<param name="plugin_occult_tolerance" type="float" min="0.000" max="99999.000" precision="3" gui-text="tolerance (mm)" gui-description="Max distance between start and end point to consider a path closed (default 0.01 mm)">0.01</param>
<param name="plugin_occult_keepseparatelayer" gui-text="Put occulted lines to separate layer" type="bool">false</param>
</page>
<page name="tab_settings_general" gui-text="General Settings">
<label appearance="header">Input (InkScape to vpype)</label>
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input/Layer handling">
<option value="layers">Multilayer/document (all layers/complete document)</option>
<option value="paths">Singlelayer/paths (a single layer/paths in selection or all paths in document)</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Quantization (flatten bezier curves to polylines)" gui-description="Convert bezier curves to polylines. Automatically enabled if 'Multilayer/document' (quantization parameter)">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get. This value is also used by option 'simplify'">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'">3</param>
<param name="simplify" type="bool" gui-text="Simplify geometry" gui-description="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'">false</param>
<param name="parallel" type="bool" gui-text="Parallelize Simplify geometry" gui-description="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'">false</param>
<spacer/>
<label appearance="header">Validation (vpype pipeline)</label>
<param name="output_show" type="bool" gui-text="Preview only (debug output)" gui-description="This will open a separate window showing the finished SVG data. If enabled output, is not applied to InkScape canvas (only for preview)!">false</param>
<param name="output_show_points" type="bool" gui-text="Enable point display in viewer">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion. Helps to find out length savings.">false</param>
<spacer/>
<label appearance="header">Output (vpype to InkScape)</label>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_objects" type="bool" gui-text="Keep original objects" gui-description="If false, original (selected) 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. Warning: increases import time significantly.">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element in layer" 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). Does not work for 'Multilayer/document'">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="tab_about" gui-text="About">
<label appearance="header">vpypetools Occult Plugin (HLR)</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/vpypetools</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/abey79/vpype</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="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 needs-live-preview="true">
<object-type>all</object-type>
<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,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Relooping</name>
<id>fablabchemnitz.de.vpype_relooping</id>
<param name="tab" type="notebook">
<page name="tab_settings_relooping" gui-text="vpypetools Relooping">
<image>vpype_logo.svg</image>
<label>Randomize the seam location of closed paths.</label>
<param name="reloop" type="bool" gui-hidden="true">true</param>
<param name="reloop_tolerance" type="float" min="0.000" max="99999.000" precision="3" gui-text="tolerance (mm)" gui-description="Controls how close the path beginning and end must be to consider it closed (default 0.500 mm)">0.500</param>
</page>
<page name="tab_settings_general" gui-text="General Settings">
<label appearance="header">Input (InkScape to vpype)</label>
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input/Layer handling">
<option value="layers">Multilayer/document (all layers/complete document)</option>
<option value="paths">Singlelayer/paths (a single layer/paths in selection or all paths in document)</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Quantization (flatten bezier curves to polylines)" gui-description="Convert bezier curves to polylines. Automatically enabled if 'Multilayer/document' (quantization parameter)">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get. This value is also used by option 'simplify'">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'">3</param>
<param name="simplify" type="bool" gui-text="Simplify geometry" gui-description="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'">false</param>
<param name="parallel" type="bool" gui-text="Parallelize Simplify geometry" gui-description="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'">false</param>
<spacer/>
<label appearance="header">Validation (vpype pipeline)</label>
<param name="output_show" type="bool" gui-text="Preview only (debug output)" gui-description="This will open a separate window showing the finished SVG data. If enabled output, is not applied to InkScape canvas (only for preview)!">false</param>
<param name="output_show_points" type="bool" gui-text="Enable point display in viewer">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion. Helps to find out length savings.">false</param>
<spacer/>
<label appearance="header">Output (vpype to InkScape)</label>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_objects" type="bool" gui-text="Keep original objects" gui-description="If false, original (selected) 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. Warning: increases import time significantly.">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element in layer" 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). Does not work for 'Multilayer/document'">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="tab_about" gui-text="About">
<label appearance="header">vpypetools Relooping</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/vpypetools</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/abey79/vpype</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="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 needs-live-preview="true">
<object-type>all</object-type>
<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,76 @@
<?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>
<param name="tab" type="notebook">
<page name="tab_settings_splitall" gui-text="vpypetools Split All (Break Paths)">
<image>vpype_logo.svg</image>
<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>
</page>
<page name="tab_settings_general" gui-text="General Settings">
<label appearance="header">Input (InkScape to vpype)</label>
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input/Layer handling">
<option value="layers">Multilayer/document (all layers/complete document)</option>
<option value="paths">Singlelayer/paths (a single layer/paths in selection or all paths in document)</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Quantization (flatten bezier curves to polylines)" gui-description="Convert bezier curves to polylines. Automatically enabled if 'Multilayer/document' (quantization parameter)">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get. This value is also used by option 'simplify'">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'">3</param>
<param name="simplify" type="bool" gui-text="Simplify geometry" gui-description="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'">false</param>
<param name="parallel" type="bool" gui-text="Parallelize Simplify geometry" gui-description="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'">false</param>
<spacer/>
<label appearance="header">Validation (vpype pipeline)</label>
<param name="output_show" type="bool" gui-text="Preview only (debug output)" gui-description="This will open a separate window showing the finished SVG data. If enabled output, is not applied to InkScape canvas (only for preview)!">false</param>
<param name="output_show_points" type="bool" gui-text="Enable point display in viewer">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion. Helps to find out length savings.">false</param>
<spacer/>
<label appearance="header">Output (vpype to InkScape)</label>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_objects" type="bool" gui-text="Keep original objects" gui-description="If false, original (selected) 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. Warning: increases import time significantly.">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element in layer" 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). Does not work for 'Multilayer/document'">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="tab_about" gui-text="About">
<label appearance="header">vpypetools Split All (Break Paths)</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/vpypetools</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/abey79/vpype</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="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 needs-live-preview="true">
<object-type>all</object-type>
<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,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Trimming</name>
<id>fablabchemnitz.de.vpype_trimming</id>
<param name="tab" type="notebook">
<page name="tab_settings_trimming" gui-text="vpypetools Trimming">
<image>vpype_logo.svg</image>
<label>This command trims the geometries by the provided X and Y margins with respect to the current bounding box.</label>
<param name="trim" type="bool" gui-hidden="true">true</param>
<param name="trim_x_margin" type="float" min="0.000" max="99999.000" precision="3" gui-text="Trim margin - x direction (mm)">0.000</param>
<param name="trim_y_margin" type="float" min="0.000" max="99999.000" precision="3" gui-text="Trim margin - y direction (mm)">0.000</param>
</page>
<page name="tab_settings_general" gui-text="General Settings">
<label appearance="header">Input (InkScape to vpype)</label>
<param name="input_handling" type="optiongroup" appearance="radio" gui-text="Input/Layer handling">
<option value="layers">Multilayer/document (all layers/complete document)</option>
<option value="paths">Singlelayer/paths (a single layer/paths in selection or all paths in document)</option>
</param>
<param name="flattenbezier" type="bool" gui-text="Quantization (flatten bezier curves to polylines)" gui-description="Convert bezier curves to polylines. Automatically enabled if 'Multilayer/document' (quantization parameter)">true</param>
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get. This value is also used by option 'simplify'">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for imported lines' coordinates into vpype. Does not work for 'Multilayer/document'">3</param>
<param name="simplify" type="bool" gui-text="Simplify geometry" gui-description="Reduces significantly the number of segments used to approximate the curve while still guaranteeing an accurate conversion, but may increase the execution time. Does not work for 'Singlelayer/paths'">false</param>
<param name="parallel" type="bool" gui-text="Parallelize Simplify geometry" gui-description="Enables multiprocessing for the SVG conversion. This is recommended ONLY when using 'Simplify geometry' on large SVG files with many curved elements. Does not work for 'Singlelayer/paths'">false</param>
<spacer/>
<label appearance="header">Validation (vpype pipeline)</label>
<param name="output_show" type="bool" gui-text="Preview only (debug output)" gui-description="This will open a separate window showing the finished SVG data. If enabled output, is not applied to InkScape canvas (only for preview)!">false</param>
<param name="output_show_points" type="bool" gui-text="Enable point display in viewer">false</param>
<param name="output_stats" type="bool" gui-text="Show conversion statistics" gui-description="Show output statistics before/after conversion. Helps to find out length savings.">false</param>
<spacer/>
<label appearance="header">Output (vpype to InkScape)</label>
<param name="output_trajectories" type="bool" gui-text="Import travel trajectories" gui-description="Add paths for the travel trajectories">false</param>
<param name="keep_objects" type="bool" gui-text="Keep original objects" gui-description="If false, original (selected) 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. Warning: increases import time significantly.">true</param>
<param name="use_style_of_first_element" type="bool" gui-text="Use style of first element in layer" 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). Does not work for 'Multilayer/document'">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="tab_about" gui-text="About">
<label appearance="header">vpypetools Trimming</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/vpypetools</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/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://github.com/abey79/vpype</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="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 needs-live-preview="true">
<object-type>all</object-type>
<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>