More adds and fixes
This commit is contained in:
parent
3df556e4fa
commit
fed542ba37
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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)
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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)
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
673
extensions/fablabchemnitz/gcode_import/gcode_import.py
Normal file
673
extensions/fablabchemnitz/gcode_import/gcode_import.py
Normal 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)
|
@ -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>
|
54
extensions/fablabchemnitz/gcode_import/gcode_import_nc.inx
Normal file
54
extensions/fablabchemnitz/gcode_import/gcode_import_nc.inx
Normal 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>
|
21
extensions/fablabchemnitz/gcode_import/meta.json
Normal file
21
extensions/fablabchemnitz/gcode_import/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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>
|
652
extensions/fablabchemnitz/inventory_sticker/inventory_sticker.py
Normal file
652
extensions/fablabchemnitz/inventory_sticker/inventory_sticker.py
Normal 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()
|
20
extensions/fablabchemnitz/inventory_sticker/meta.json
Normal file
20
extensions/fablabchemnitz/inventory_sticker/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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>
|
507
extensions/fablabchemnitz/lasercut_jigsaw/lasercut_jigsaw.py
Normal file
507
extensions/fablabchemnitz/lasercut_jigsaw/lasercut_jigsaw.py
Normal 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()
|
25
extensions/fablabchemnitz/lasercut_jigsaw/meta.json
Normal file
25
extensions/fablabchemnitz/lasercut_jigsaw/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
21
extensions/fablabchemnitz/paperfold/meta.json
Normal file
21
extensions/fablabchemnitz/paperfold/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
108
extensions/fablabchemnitz/paperfold/paperfold.inx
Normal file
108
extensions/fablabchemnitz/paperfold/paperfold.inx
Normal 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>
|
1021
extensions/fablabchemnitz/paperfold/paperfold.py
Normal file
1021
extensions/fablabchemnitz/paperfold/paperfold.py
Normal file
File diff suppressed because it is too large
Load Diff
23
extensions/fablabchemnitz/styles_to_layers/meta.json
Normal file
23
extensions/fablabchemnitz/styles_to_layers/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
@ -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>
|
308
extensions/fablabchemnitz/styles_to_layers/styles_to_layers.py
Normal file
308
extensions/fablabchemnitz/styles_to_layers/styles_to_layers.py
Normal 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()
|
21
extensions/fablabchemnitz/sudoku/meta.json
Normal file
21
extensions/fablabchemnitz/sudoku/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
210
extensions/fablabchemnitz/sudoku/qqwing
Executable file
210
extensions/fablabchemnitz/sudoku/qqwing
Executable 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
|
BIN
extensions/fablabchemnitz/sudoku/qqwing.exe
Normal file
BIN
extensions/fablabchemnitz/sudoku/qqwing.exe
Normal file
Binary file not shown.
47
extensions/fablabchemnitz/sudoku/sudoku.inx
Normal file
47
extensions/fablabchemnitz/sudoku/sudoku.inx
Normal 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>
|
119
extensions/fablabchemnitz/sudoku/sudoku.py
Normal file
119
extensions/fablabchemnitz/sudoku/sudoku.py
Normal 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()
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
20
extensions/fablabchemnitz/vpypetools/meta.json
Normal file
20
extensions/fablabchemnitz/vpypetools/meta.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
392
extensions/fablabchemnitz/vpypetools/vpype_logo.svg
Normal file
392
extensions/fablabchemnitz/vpypetools/vpype_logo.svg
Normal 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 |
453
extensions/fablabchemnitz/vpypetools/vpypetools.py
Normal file
453
extensions/fablabchemnitz/vpypetools/vpypetools.py
Normal 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()
|
@ -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>
|
83
extensions/fablabchemnitz/vpypetools/vpypetools_filter.inx
Normal file
83
extensions/fablabchemnitz/vpypetools/vpypetools_filter.inx
Normal 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>
|
113
extensions/fablabchemnitz/vpypetools/vpypetools_freemode.inx
Normal file
113
extensions/fablabchemnitz/vpypetools/vpypetools_freemode.inx
Normal 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>
|
@ -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>
|
77
extensions/fablabchemnitz/vpypetools/vpypetools_linesort.inx
Normal file
77
extensions/fablabchemnitz/vpypetools/vpypetools_linesort.inx
Normal 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>
|
@ -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>
|
78
extensions/fablabchemnitz/vpypetools/vpypetools_occult.inx
Normal file
78
extensions/fablabchemnitz/vpypetools/vpypetools_occult.inx
Normal 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>
|
@ -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>
|
76
extensions/fablabchemnitz/vpypetools/vpypetools_splitall.inx
Normal file
76
extensions/fablabchemnitz/vpypetools/vpypetools_splitall.inx
Normal 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>
|
78
extensions/fablabchemnitz/vpypetools/vpypetools_trim.inx
Normal file
78
extensions/fablabchemnitz/vpypetools/vpypetools_trim.inx
Normal 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>
|
Loading…
Reference in New Issue
Block a user