diff --git a/README.md b/README.md
index e99f7be3..145ec148 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# MightyScape for Inkscape 1.0+
-In short: A maintained extension collection for Inkscape 1.0+, working on Windows and Linux. There are **204 extension folders** with **366 .inx files** inside. We also take part at https://inkscape.org/gallery/=extension/ (with single extension uploads).
+In short: A maintained extension collection for Inkscape 1.0+, working on Windows and Linux. There are **210 extension folders** with **372 .inx files** inside. We also take part at https://inkscape.org/gallery/=extension/ (with single extension uploads).
# About MightyScape
diff --git a/extensions/fablabchemnitz/collar/collar.inx b/extensions/fablabchemnitz/collar/collar.inx
index 7077342b..36606465 100644
--- a/extensions/fablabchemnitz/collar/collar.inx
+++ b/extensions/fablabchemnitz/collar/collar.inx
@@ -12,7 +12,7 @@
1
45.0
0.4
- 0.1
+ 0.1
diff --git a/extensions/fablabchemnitz/extruder/.gitattributes b/extensions/fablabchemnitz/extruder/.gitattributes
new file mode 100644
index 00000000..dfe07704
--- /dev/null
+++ b/extensions/fablabchemnitz/extruder/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/extensions/fablabchemnitz/extruder/extruder.inx b/extensions/fablabchemnitz/extruder/extruder.inx
new file mode 100644
index 00000000..3c6d5102
--- /dev/null
+++ b/extensions/fablabchemnitz/extruder/extruder.inx
@@ -0,0 +1,47 @@
+
+
+ Extruder
+ fablabchemnitz.de.extruder
+
+
+
+ 1.0
+ 11.5
+ 45.0
+ 0.4
+ 0.1
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+ false
+ 4278190335
+ 65535
+
+ true
+
+
+
+
+
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/extruder/extruder.py b/extensions/fablabchemnitz/extruder/extruder.py
new file mode 100644
index 00000000..84be9c3c
--- /dev/null
+++ b/extensions/fablabchemnitz/extruder/extruder.py
@@ -0,0 +1,719 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) [2021] [Joseph Zakar], [observing@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.
+#
+"""
+Given a closed path of straight lines, this program generates a paper model of
+(1) another copy of the closed path; (2) an extrusion (or more if it exceeds the
+maximum length) represented by a strip with tabs and score lines; and (3) strips
+for covering the tabbed strips.
+"""
+
+import inkex
+from inkex import Path, Color
+import math
+import copy
+
+class pathStruct(object):
+ def __init__(self):
+ self.id="path0000"
+ self.path=[]
+ self.enclosed=False
+ def __str__(self):
+ return self.path
+
+class pnPoint(object):
+ # This class came from https://github.com/JoJocoder/PNPOLY
+ def __init__(self,p):
+ self.p=p
+ def __str__(self):
+ return self.p
+ def InPolygon(self,polygon,BoundCheck=False):
+ inside=False
+ if BoundCheck:
+ minX=polygon[0][0]
+ maxX=polygon[0][0]
+ minY=polygon[0][1]
+ maxY=polygon[0][1]
+ for p in polygon:
+ minX=min(p[0],minX)
+ maxX=max(p[0],maxX)
+ minY=min(p[1],minY)
+ maxY=max(p[1],maxY)
+ if self.p[0]maxX or self.p[1]maxY:
+ return False
+ j=len(polygon)-1
+ for i in range(len(polygon)):
+ if ((polygon[i][1]>self.p[1])!=(polygon[j][1]>self.p[1]) and (self.p[0]<(polygon[j][0]-polygon[i][0])*(self.p[1]-polygon[i][1])/( polygon[j][1] - polygon[i][1] ) + polygon[i][0])):
+ inside =not inside
+ j=i
+ return inside
+
+class Extruder(inkex.EffectExtension):
+
+ def add_arguments(self, pars):
+ pars.add_argument("--usermenu")
+ pars.add_argument("--extrude", type=float, default=1.0, help="Width of extrusion in dimensional units")
+ pars.add_argument("--maxstrip", type=float, default=11.5, help="Maximum length of extrusion in dimensional units")
+ pars.add_argument("--tabangle", type=float, default=45.0, help="Angle of tab edges in degrees")
+ pars.add_argument("--tabheight", type=float, default=0.4, help="Height of tab in dimensional units")
+ pars.add_argument("--dashlength", type=float, default=0.1, help="Length of dashline in dimensional units (zero for solid line)")
+ pars.add_argument("--generate_decorative_wrapper", type=inkex.Boolean, default=False, help="Generate decorative wrapper")
+ pars.add_argument("--cosmetic_dash_style", type=inkex.Boolean, default=False, help="Cosmetic dash lines")
+ pars.add_argument("--unit", default="in", help="Dimensional units")
+ pars.add_argument("--color_solid", type=Color, default='4278190335', help="Solid line color")
+ pars.add_argument("--color_dash", type=Color, default='65535', help="Solid line dash")
+ pars.add_argument("--print_debug", type=inkex.Boolean, default=True, help="Print debug info")
+
+ #draw SVG line segment(s) between the given (raw) points
+ def drawline(self, dstr, name, parent, sstr=None):
+ line_style = {'stroke':'{}','stroke-width':'1','fill':'none'.format(self.options.color_solid)}
+ if sstr == None:
+ stylestr = str(inkex.Style(line_style))
+ else:
+ stylestr = sstr
+ el = parent.add(inkex.PathElement())
+ el.path = dstr
+ el.style = stylestr
+ el.label = name
+
+ def add_doc(self, path, apt1, apt2, offset, layer):
+ stylestr = "font-size:{0};line-height:1.25;font-family:sans-serif;stroke-width:0.264583".format(offset*2)
+ te = layer.add(inkex.TextElement())
+ te.style = stylestr
+ te.label = te.get_id()
+ te.text = "1"
+ te.set('x', apt1.x)
+ te.set('y', apt1.y)
+ te = layer.add(inkex.TextElement())
+ te.style = stylestr
+ te.label = te.get_id()
+ te.text = "2"
+ te.set('x', apt2.x)
+ te.set('y', apt2.y)
+
+ def pathInsidePath(self, path, testpath):
+ enclosed = True
+ for tp in testpath:
+ # If any point in the testpath is outside the path, it's not enclosed
+ if self.insidePath(path, tp) == False:
+ enclosed = False
+ return enclosed # True if testpath is fully enclosed in path
+ return enclosed
+
+ def insidePath(self, path, p):
+ point = pnPoint((p.x, p.y))
+ pverts = []
+ for pnum in path:
+ pverts.append((pnum.x, pnum.y))
+ isInside = point.InPolygon(pverts, True)
+ return isInside # True if point p is inside path
+
+ def makescore(self, pt1, pt2, dashlength):
+ # Draws a dashed line of dashlength between two points
+ # Dash = dashlength (in inches) space followed by dashlength mark
+ # if dashlength is zero, we want a solid line
+ apt1 = inkex.paths.Line(0.0,0.0)
+ apt2 = inkex.paths.Line(0.0,0.0)
+ ddash = ''
+ if math.isclose(dashlength, 0.0):
+ #inkex.utils.debug("Draw solid dashline")
+ ddash = ' M '+str(pt1.x)+','+str(pt1.y)+' L '+str(pt2.x)+','+str(pt2.y)
+ else:
+ if math.isclose(pt1.y, pt2.y):
+ #inkex.utils.debug("Draw horizontal dashline")
+ if pt1.x < pt2.x:
+ xcushion = pt2.x - dashlength
+ xpt = pt1.x
+ ypt = pt1.y
+ else:
+ xcushion = pt1.x - dashlength
+ xpt = pt2.x
+ ypt = pt2.y
+ ddash = ''
+ done = False
+ while not(done):
+ if (xpt + dashlength*2) <= xcushion:
+ xpt = xpt + dashlength
+ ddash = ddash + ' M ' + str(xpt) + ',' + str(ypt)
+ xpt = xpt + dashlength
+ ddash = ddash + ' L ' + str(xpt) + ',' + str(ypt)
+ else:
+ done = True
+ elif math.isclose(pt1.x, pt2.x):
+ #inkex.utils.debug("Draw vertical dashline")
+ if pt1.y < pt2.y:
+ ycushion = pt2.y - dashlength
+ xpt = pt1.x
+ ypt = pt1.y
+ else:
+ ycushion = pt1.y - dashlength
+ xpt = pt2.x
+ ypt = pt2.y
+ ddash = ''
+ done = False
+ while not(done):
+ if(ypt + dashlength*2) <= ycushion:
+ ypt = ypt + dashlength
+ ddash = ddash + ' M ' + str(xpt) + ',' + str(ypt)
+ ypt = ypt + dashlength
+ ddash = ddash + ' L ' + str(xpt) + ',' + str(ypt)
+ else:
+ done = True
+ else:
+ #inkex.utils.debug("Draw sloping dashline")
+ if pt1.y > pt2.y:
+ apt1.x = pt1.x
+ apt1.y = pt1.y
+ apt2.x = pt2.x
+ apt2.y = pt2.y
+ else:
+ apt1.x = pt2.x
+ apt1.y = pt2.y
+ apt2.x = pt1.x
+ apt2.y = pt1.y
+ m = (apt1.y-apt2.y)/(apt1.x-apt2.x)
+ theta = math.atan(m)
+ msign = (m>0) - (m<0)
+ ycushion = apt2.y + dashlength*math.sin(theta)
+ xcushion = apt2.x + msign*dashlength*math.cos(theta)
+ ddash = ''
+ xpt = apt1.x
+ ypt = apt1.y
+ done = False
+ while not(done):
+ nypt = ypt - dashlength*2*math.sin(theta)
+ nxpt = xpt - msign*dashlength*2*math.cos(theta)
+ if (nypt >= ycushion) and (((m<0) and (nxpt <= xcushion)) or ((m>0) and (nxpt >= xcushion))):
+ # move to end of space / beginning of mark
+ xpt = xpt - msign*dashlength*math.cos(theta)
+ ypt = ypt - msign*dashlength*math.sin(theta)
+ ddash = ddash + ' M ' + str(xpt) + ',' + str(ypt)
+ # draw the mark
+ xpt = xpt - msign*dashlength*math.cos(theta)
+ ypt = ypt - msign*dashlength*math.sin(theta)
+ ddash = ddash + ' L ' + str(xpt) + ',' + str(ypt)
+ else:
+ done = True
+ return ddash
+
+ def detectIntersect(self, x1, y1, x2, y2, x3, y3, x4, y4):
+ td = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)
+ if td == 0:
+ # These line segments are parallel
+ return False
+ t = ((x1-x3)*(y3-y4)-(y1-y3)*(x3-x4))/td
+ if (0.0 <= t) and (t <= 1.0):
+ return True
+ else:
+ return False
+
+ def makeTab(self, tpath, pt1, pt2, tabht, taba):
+ # tpath - the pathstructure containing pt1 and pt2
+ # pt1, pt2 - the two points where the tab will be inserted
+ # tabht - the height of the tab
+ # taba - the angle of the tab sides
+ # returns the two tab points in order of closest to pt1
+ tpt1 = inkex.paths.Line(0.0,0.0)
+ tpt2 = inkex.paths.Line(0.0,0.0)
+ currTabHt = tabht
+ currTabAngle = taba
+ testAngle = 1.0
+ testHt = currTabHt * 0.001
+ adjustTab = 0
+ tabDone = False
+ while not tabDone:
+ # Let's find out the orientation of the tab
+ if math.isclose(pt1.x, pt2.x):
+ # It's vertical. Let's try the right side
+ if pt1.y < pt2.y:
+ tpt1.x = pt1.x + testHt
+ tpt2.x = pt2.x + testHt
+ tpt1.y = pt1.y + testHt/math.tan(math.radians(testAngle))
+ tpt2.y = pt2.y - testHt/math.tan(math.radians(testAngle))
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.x = pt1.x - currTabHt
+ tpt2.x = pt2.x - currTabHt
+ else:
+ tpt1.x = pt1.x + currTabHt
+ tpt2.x = pt2.x + currTabHt
+ tpt1.y = pt1.y + currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.y = pt2.y - currTabHt/math.tan(math.radians(currTabAngle))
+ else: # pt2.y < pt1.y
+ tpt1.x = pt1.x + testHt
+ tpt2.x = pt2.x + testHt
+ tpt1.y = pt1.y - testHt/math.tan(math.radians(testAngle))
+ tpt2.y = pt2.y + testHt/math.tan(math.radians(testAngle))
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.x = pt1.x - currTabHt
+ tpt2.x = pt2.x - currTabHt
+ else:
+ tpt1.x = pt1.x + currTabHt
+ tpt2.x = pt2.x + currTabHt
+ tpt1.y = pt1.y - currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.y = pt2.y + currTabHt/math.tan(math.radians(currTabAngle))
+ elif math.isclose(pt1.y, pt2.y):
+ # It's horizontal. Let's try the top
+ if pt1.x < pt2.x:
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x + testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x - testHt/math.tan(math.radians(testAngle))
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x + currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x - currTabHt/math.tan(math.radians(currTabAngle))
+ else: # pt2.x < pt1.x
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x - testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x + testHt/math.tan(math.radians(testAngle))
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x - currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x + currTabHt/math.tan(math.radians(currTabAngle))
+
+ else: # the orientation is neither horizontal nor vertical
+ # Let's get the slope of the line between the points
+ # Because Inkscape's origin is in the upper-left corner,
+ # a positive slope (/) will yield a negative value
+ slope = (pt2.y - pt1.y)/(pt2.x - pt1.x)
+ # Let's get the angle to the horizontal
+ theta = math.degrees(math.atan(slope))
+ # Let's construct a horizontal tab
+ seglength = math.sqrt((pt1.x-pt2.x)**2 +(pt1.y-pt2.y)**2)
+ if slope < 0.0:
+ if pt1.x < pt2.x:
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x + testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x - testHt/math.tan(math.radians(testAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x + currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x - currTabHt/math.tan(math.radians(currTabAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ else: # pt1.x > pt2.x
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x - testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x + testHt/math.tan(math.radians(testAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x - currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x + currTabHt/math.tan(math.radians(currTabAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ else: # slope > 0.0
+ if pt1.x < pt2.x:
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x + testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x - testHt/math.tan(math.radians(testAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x + currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x - currTabHt/math.tan(math.radians(currTabAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ else: # pt1.x > pt2.x
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x - testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x + testHt/math.tan(math.radians(testAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x - currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x + currTabHt/math.tan(math.radians(currTabAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ # Check to see if any tabs intersect each other
+ if self.detectIntersect(pt1.x, pt1.y, tpt1.x, tpt1.y, pt2.x, pt2.y, tpt2.x, tpt2.y):
+ # Found an intersection.
+ if adjustTab == 0:
+ # Try increasing the tab angle in one-degree increments
+ currTabAngle = currTabAngle + 1.0
+ if currTabAngle > 88.0: # We're not increasing the tab angle above 89 degrees
+ adjustTab = 1
+ currTabAngle = taba
+ if adjustTab == 1:
+ # So, try reducing the tab height in 20% increments instead
+ currTabHt = currTabHt - tabht*0.2 # Could this lead to a zero tab_height?
+ if currTabHt <= 0.0:
+ # Give up
+ currTabHt = tabht
+ adjustTab = 2
+ if adjustTab == 2:
+ tabDone = True # Just show the failure
+ else:
+ tabDone = True
+
+ return tpt1,tpt2
+
+ def effect(self):
+ layer = self.svg.get_current_layer()
+ doc_layer = self.svg.add(inkex.elements._groups.Layer.new('Layer Doc'))
+ scale = self.svg.unittouu('1'+self.options.unit)
+ extrude = float(self.options.extrude) * scale
+ maxstrip = float(self.options.maxstrip) * scale
+ tab_angle = float(self.options.tabangle)
+ tab_height = float(self.options.tabheight) * scale
+ dashlength = float(self.options.dashlength) * scale
+ #tabsets = self.options.tabsets <-- for a future feature
+ npaths = []
+ elems = []
+ for selem in self.svg.selection.filter(inkex.PathElement):
+ elems.append(selem)
+ if len(elems) == 0:
+ raise inkex.AbortExtension("Nothing selected")
+ for elem in elems:
+ backend = elem.copy() # Make a copy of it
+ layer.append(backend)
+ escale = 1.0
+ if 'transform' in elem.attrib:
+ transforms = elem.attrib['transform'].split()
+ for tf in transforms:
+ if tf.startswith('scale'):
+ escale = float(tf.split('(')[1].split(')')[0])
+ # Get style of original polygon
+ if 'style' in elem.attrib:
+ sstr = elem.attrib['style']
+ if not math.isclose(escale, 1.0):
+ lsstr = sstr.split(';')
+ for stoken in range(len(lsstr)):
+ if lsstr[stoken].startswith('stroke-width'):
+ swt = lsstr[stoken].split(':')[1]
+ swf = str(float(swt)*escale)
+ lsstr[stoken] = lsstr[stoken].replace(swt, swf)
+ if lsstr[stoken].startswith('stroke-miterlimit'):
+ swt = lsstr[stoken].split(':')[1]
+ swf = str(float(swt)*escale)
+ lsstr[stoken] = lsstr[stoken].replace(swt, swf)
+ sstr = ";".join(lsstr)
+ else:
+ sstr = None
+ last_letter = 'Z'
+ savid = elem.get_id()
+ idmod = 0
+ parent = elem.getparent()
+ #if parent != self.svg.root:
+ # elem.path.transform = elem.path.transform(parent.composed_transform())
+ elementPath = elem.path.to_non_shorthand().to_absolute()
+ isClosed = False
+ raw = elementPath.to_arrays()
+ if raw[-1][0] == 'Z' or \
+ (raw[-1][0] == 'L' and raw[0][1] == raw[-1][1]) or \
+ (raw[-1][0] == 'C' and raw[0][1] == [raw[-1][1][-2], raw[-1][1][-1]]) \
+ : #if first is last point the path is also closed. The "Z" command is not required
+ isClosed = True
+ if isClosed is False:
+ if self.options.print_debug is True:
+ self.msg("Warning! Path {} is not closed. Skipping ...".format(elem.get('id')))
+ continue
+ npaths.clear()
+
+ for ptoken in elementPath: # For each point in the path
+ ptx2 = None
+ pty2 = None
+ if ptoken.letter == 'M': # Starting point
+ # Hold this point in case we receive a Z
+ ptx1 = mx = ptoken.x * escale
+ pty1 = my = ptoken.y * escale
+ '''
+ Assign a structure to the new path. We assume that there is
+ only one path and, therefore, it isn't enclosed by a
+ sub-path. However, we'll suffix the ID, if we find a
+ sub-path.
+ '''
+ npath = pathStruct()
+ npath.enclosed = False
+ npath.id = elem.get_id()+"-"+str(idmod)
+ idmod += 1
+ npath.path.append(inkex.paths.Move(ptx1,pty1))
+ else:
+ if last_letter != 'M':
+ ptx1 = ptx2
+ pty1 = pty2
+ if ptoken.letter == 'L':
+ ptx2 = ptoken.x * escale
+ pty2 = ptoken.y * escale
+ elif ptoken.letter == 'H':
+ ptx2 = ptoken.x * escale
+ pty2 = pty1
+ elif ptoken.letter == 'V':
+ ptx2 = ptx1
+ pty2 = ptoken.y * escale
+ elif ptoken.letter == 'Z':
+ ptx2 = mx
+ pty2 = my
+ else:
+ raise inkex.AbortExtension("Unrecognized path command {0}. Please convert to polyline before!".format(ptoken.letter))
+ npath.path.append(inkex.paths.Line(ptx2,pty2))
+ if ptoken.letter == 'Z':
+ npaths.append(npath)
+ last_letter = ptoken.letter
+ # check for cutouts
+ if idmod > 1:
+ for apath in npaths: # We test these paths to see if they are fully enclosed
+ for bpath in npaths: # by these paths
+ if self.pathInsidePath(bpath.path, apath.path):
+ apath.enclosed = True
+ for opath in npaths:
+ if True: # We'll handle outside paths and the cutouts, too
+ # create the extruded path
+ xpos = ypos = 0.0
+ segs = pathStruct()
+ segs.enclosed = False
+ segs.id = opath.id+"x"
+ segs.path.append(inkex.paths.Move(xpos,ypos))
+ strips = [] # Needed because a single strip might be larger than the paper
+ scores = [] # holds lists of individual score lines per strip
+ score = [] # holds a list of individual score lines
+ spaths = ''
+ # create left edge of path
+ for jnode in range(0,len(opath.path)-1):
+ if jnode == 0:
+ # Let's draw the first two node numbers to show the starting point and direction
+ self.add_doc(opath.path, opath.path[jnode], opath.path[jnode+1], 0.5*tab_height, doc_layer)
+ # calculate length of segment between jnode and jnode+1
+ seglength = math.sqrt((opath.path[jnode].x - opath.path[jnode+1].x)**2 + (opath.path[jnode].y - opath.path[jnode+1].y)**2)
+ if ypos + seglength + tab_height >= maxstrip:
+ # have to cut it at last segment
+ strips.append(copy.deepcopy(segs))
+ segs = pathStruct() # start a new segment
+ segs.enclosed = False
+ segs.id = opath.id+"x"
+ ypos = 0
+ scores.append(copy.deepcopy(score))
+ score.clear()
+ spaths = ''
+ segs.path.append(inkex.paths.Move(xpos,ypos))
+ ypos = ypos + seglength
+ segs.path.append(inkex.paths.Move(xpos,ypos))
+ if jnode < len(opath.path)-1:
+ # Generate score lines across extrusion
+ score.append(self.makescore(inkex.paths.Move(xpos,ypos), inkex.paths.Move(extrude, ypos),dashlength))
+ strips.append(copy.deepcopy(segs))
+ scores.append(copy.deepcopy(score))
+ score.clear()
+ # create right edge of path
+ for knode in range(len(strips)):
+ rsegs = strips[knode].path[::-1]
+ for jnode in range(0,len(rsegs)):
+ strips[knode].path.append(inkex.paths.Move(extrude, rsegs[jnode].y))
+ rsegs.clear()
+ # Generate the deco strips from the extruded paths
+ for stripcnt in range(len(strips)):
+ for nodes in range(len(strips[stripcnt].path)):
+ if nodes == 0:
+ dprop = 'M '
+ else:
+ dprop = dprop + ' L '
+ dprop = dprop + str(strips[stripcnt].path[nodes].x) + ',' + str(strips[stripcnt].path[nodes].y)
+ ## and close the path
+ dprop = dprop + ' Z'
+ spaths = ''
+ for scorecnt in range(len(scores[stripcnt])-1): # each list of individual scorelines across extrusion per strip
+ scorex = scores[stripcnt][scorecnt]
+ for sc in scorex:
+ spaths += sc
+ if math.isclose(dashlength, 0.0) and spaths != '':
+ group = inkex.elements._groups.Group()
+ group.label = 'g'+opath.id+'ws'+str(stripcnt)
+ if self.options.generate_decorative_wrapper is True:
+ self.drawline(dprop,'wrapper'+str(stripcnt),group,sstr) # Output the model
+ self.drawline(spaths[1:],'score'+str(stripcnt)+'m',group,sstr) # Output the scorelines separately
+ layer.append(group)
+ else:
+ dprop = dprop + spaths
+ self.drawline(dprop,opath.id+'d'+str(stripcnt),layer,sstr)
+ # Generate the tabbed strips from the extruded paths
+ dprop = ''
+ for stripcnt in range(len(strips)):
+ strip = strips[stripcnt]
+ mpath = [strip.path[0]]
+ for ptn in range(len(strip.path)-1):
+ tabpt1, tabpt2 = self.makeTab(strip, strip.path[ptn], strip.path[ptn+1], tab_height, tab_angle)
+ mpath.append(tabpt1)
+ mpath.append(tabpt2)
+ mpath.append(strip.path[ptn+1])
+ score.append(self.makescore(strip.path[ptn], strip.path[ptn+1],dashlength))
+ scores[stripcnt].append(copy.deepcopy(score))
+ score.clear()
+ for nodes in range(len(mpath)):
+ if nodes == 0:
+ dprop = 'M '
+ else:
+ dprop = dprop + ' L '
+ dprop = dprop + str(mpath[nodes].x) + ',' + str(mpath[nodes].y)
+ ## and close the path
+ dprop = dprop + ' Z'
+ spaths = ''
+ for scorecnt in range(len(scores[stripcnt])): # each list of individual scorelines across extrusion and tabs per strip
+ scorex = scores[stripcnt][scorecnt]
+ for sc in scorex:
+ spaths += sc
+ if spaths != '':
+ group = inkex.elements._groups.Group()
+ group.label = 'g'+opath.id+'ms'+str(stripcnt)
+ self.drawline(dprop,'model'+str(stripcnt),group,sstr+';stroke:{}'.format(self.options.color_solid)) # Output the model
+ dscore_style = sstr+';stroke:{}'.format(self.options.color_dash)
+ if self.options.cosmetic_dash_style is True:
+ dscore_style += ';stroke-dasharray:{}'.format(3, 3)
+ self.drawline(spaths[1:],'score'+str(stripcnt)+'m',group,dscore_style) # Output the scorelines separately
+ layer.append(group)
+
+
+if __name__ == '__main__':
+ Extruder().run()
diff --git a/extensions/fablabchemnitz/polygen/.gitattributes b/extensions/fablabchemnitz/polygen/.gitattributes
new file mode 100644
index 00000000..dfe07704
--- /dev/null
+++ b/extensions/fablabchemnitz/polygen/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/extensions/fablabchemnitz/polygen/polygen.inx b/extensions/fablabchemnitz/polygen/polygen.inx
new file mode 100644
index 00000000..a6814ba1
--- /dev/null
+++ b/extensions/fablabchemnitz/polygen/polygen.inx
@@ -0,0 +1,44 @@
+
+
+ Polygen
+ fablabchemnitz.de.polygen
+
+
+
+ 6
+ 45.0
+ 0.4
+ 0.1
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+ false
+ 4278190335
+ 65535
+
+
+
+
+
+
+ all
+
+
+
+
+
+
+
+
diff --git a/extensions/fablabchemnitz/polygen/polygen.py b/extensions/fablabchemnitz/polygen/polygen.py
new file mode 100644
index 00000000..6cef3bea
--- /dev/null
+++ b/extensions/fablabchemnitz/polygen/polygen.py
@@ -0,0 +1,692 @@
+#!/usr/bin/env python3
+# #
+# Copyright (C) [2021] [Joseph Zakar], [observing@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.
+#
+"""
+Given the number of polygon sides, an outline to be generated perpendicular to
+each side, and a straight line whose distance is the radius of the revolved
+outline, this program generates (1) a paper model of one of the n sides with tabs
+to assemble into a full 3D model; (2) the top and bottom lids for the generated
+model; and (3) wrappers to cover each side of the generated model.
+"""
+
+import inkex
+from inkex import Color
+from lxml import etree
+import math
+import copy
+import inspect
+
+class pathStruct(object):
+ def __init__(self):
+ self.id="path0000"
+ self.path=[]
+ self.enclosed=False
+ self.style = None
+ def __str__(self):
+ return self.path
+
+class pnPoint(object):
+ # This class came from https://github.com/JoJocoder/PNPOLY
+ def __init__(self,p):
+ self.p=p
+ def __str__(self):
+ return self.p
+ def InPolygon(self,polygon,BoundCheck=False):
+ inside=False
+ if BoundCheck:
+ minX=polygon[0][0]
+ maxX=polygon[0][0]
+ minY=polygon[0][1]
+ maxY=polygon[0][1]
+ for p in polygon:
+ minX=min(p[0],minX)
+ maxX=max(p[0],maxX)
+ minY=min(p[1],minY)
+ maxY=max(p[1],maxY)
+ if self.p[0]maxX or self.p[1]maxY:
+ return False
+ j=len(polygon)-1
+ for i in range(len(polygon)):
+ if ((polygon[i][1]>self.p[1])!=(polygon[j][1]>self.p[1]) and (self.p[0]<(polygon[j][0]-polygon[i][0])*(self.p[1]-polygon[i][1])/( polygon[j][1] - polygon[i][1] ) + polygon[i][0])):
+ inside =not inside
+ j=i
+ return inside
+
+class Polygen(inkex.EffectExtension):
+
+ def add_arguments(self, pars):
+ pars.add_argument("--usermenu")
+ pars.add_argument("--polysides", type=int, default=6, help="Number of Polygon Sides")
+ pars.add_argument("--tabangle", type=float, default=45.0, help="Angle of tab edges in degrees")
+ pars.add_argument("--tabheight", type=float, default=0.4,help="Height of tab in dimensional units")
+ pars.add_argument("--dashlength", type=float, default=0.1,help="Length of dashline in dimentional units (zero for solid line)")
+ pars.add_argument("--unit", default="in", help="Dimensional units of selected paths")
+ pars.add_argument("--generate_decorative_wrapper", type=inkex.Boolean, default=False, help="Generate decorative wrapper")
+ pars.add_argument("--cosmetic_dash_style", type=inkex.Boolean, default=False, help="Cosmetic dash lines")
+ pars.add_argument("--color_solid", type=Color, default='4278190335', help="Solid line color")
+ pars.add_argument("--color_dash", type=Color, default='65535', help="Solid line dash")
+
+
+ #draw SVG line segment(s) between the given (raw) points
+ def drawline(self, dstr, name, parent, sstr=None):
+ line_style = {'stroke':'{}','stroke-width':'1','fill':'none'.format(self.options.color_solid)}
+ if sstr == None:
+ stylestr = str(inkex.Style(line_style))
+ else:
+ stylestr = sstr
+ el = parent.add(inkex.PathElement())
+ el.path = dstr
+ el.style = sstr
+ el.label = name
+
+ def makepoly(self, toplength, numpoly):
+ r = toplength/(2*math.sin(math.pi/numpoly))
+ pstr = ''
+ for ppoint in range(0,numpoly):
+ xn = r*math.cos(2*math.pi*ppoint/numpoly)
+ yn = r*math.sin(2*math.pi*ppoint/numpoly)
+ if ppoint == 0:
+ pstr = 'M '
+ else:
+ pstr += ' L '
+ pstr += str(xn) + ',' + str(yn)
+ pstr = pstr + ' Z'
+ return pstr
+
+ def insidePath(self, path, p):
+ point = pnPoint((p.x, p.y))
+ pverts = []
+ for pnum in path:
+ pverts.append((pnum.x, pnum.y))
+ isInside = point.InPolygon(pverts, True)
+ return isInside # True if point p is inside path
+
+ def makescore(self, pt1, pt2, dashlength):
+ # Draws a dashed line of dashlength between two points
+ # Dash = dashlength (in inches) space followed by dashlength mark
+ # if dashlength is zero, we want a solid line
+ apt1 = inkex.paths.Line(0.0,0.0)
+ apt2 = inkex.paths.Line(0.0,0.0)
+ ddash = ''
+ if math.isclose(dashlength, 0.0):
+ #inkex.utils.debug("Draw solid dashline")
+ ddash = ' M '+str(pt1.x)+','+str(pt1.y)+' L '+str(pt2.x)+','+str(pt2.y)
+ else:
+ if math.isclose(pt1.y, pt2.y):
+ #inkex.utils.debug("Draw horizontal dashline")
+ if pt1.x < pt2.x:
+ xcushion = pt2.x - dashlength
+ xpt = pt1.x
+ ypt = pt1.y
+ else:
+ xcushion = pt1.x - dashlength
+ xpt = pt2.x
+ ypt = pt2.y
+ ddash = ''
+ done = False
+ while not(done):
+ if (xpt + dashlength*2) <= xcushion:
+ xpt = xpt + dashlength
+ ddash = ddash + ' M ' + str(xpt) + ',' + str(ypt)
+ xpt = xpt + dashlength
+ ddash = ddash + ' L ' + str(xpt) + ',' + str(ypt)
+ else:
+ done = True
+ elif math.isclose(pt1.x, pt2.x):
+ #inkex.utils.debug("Draw vertical dashline")
+ if pt1.y < pt2.y:
+ ycushion = pt2.y - dashlength
+ xpt = pt1.x
+ ypt = pt1.y
+ else:
+ ycushion = pt1.y - dashlength
+ xpt = pt2.x
+ ypt = pt2.y
+ ddash = ''
+ done = False
+ while not(done):
+ if(ypt + dashlength*2) <= ycushion:
+ ypt = ypt + dashlength
+ ddash = ddash + ' M ' + str(xpt) + ',' + str(ypt)
+ ypt = ypt + dashlength
+ ddash = ddash + ' L ' + str(xpt) + ',' + str(ypt)
+ else:
+ done = True
+ else:
+ #inkex.utils.debug("Draw sloping dashline")
+ if pt1.y > pt2.y:
+ apt1.x = pt1.x
+ apt1.y = pt1.y
+ apt2.x = pt2.x
+ apt2.y = pt2.y
+ else:
+ apt1.x = pt2.x
+ apt1.y = pt2.y
+ apt2.x = pt1.x
+ apt2.y = pt1.y
+ m = (apt1.y-apt2.y)/(apt1.x-apt2.x)
+ theta = math.atan(m)
+ msign = (m>0) - (m<0)
+ ycushion = apt2.y + dashlength*math.sin(theta)
+ xcushion = apt2.x + msign*dashlength*math.cos(theta)
+ ddash = ''
+ xpt = apt1.x
+ ypt = apt1.y
+ done = False
+ while not(done):
+ nypt = ypt - dashlength*2*math.sin(theta)
+ nxpt = xpt - msign*dashlength*2*math.cos(theta)
+ if (nypt >= ycushion) and (((m<0) and (nxpt <= xcushion)) or ((m>0) and (nxpt >= xcushion))):
+ # move to end of space / beginning of mark
+ xpt = xpt - msign*dashlength*math.cos(theta)
+ ypt = ypt - msign*dashlength*math.sin(theta)
+ ddash = ddash + ' M ' + str(xpt) + ',' + str(ypt)
+ # draw the mark
+ xpt = xpt - msign*dashlength*math.cos(theta)
+ ypt = ypt - msign*dashlength*math.sin(theta)
+ ddash = ddash + ' L ' + str(xpt) + ',' + str(ypt)
+ else:
+ done = True
+ return ddash
+
+ def detectIntersect(self, x1, y1, x2, y2, x3, y3, x4, y4):
+ td = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)
+ if td == 0:
+ # These line segments are parallel
+ return False
+ t = ((x1-x3)*(y3-y4)-(y1-y3)*(x3-x4))/td
+ if (0.0 <= t) and (t <= 1.0):
+ return True
+ else:
+ return False
+
+ def makeTab(self, tpath, pt1, pt2, tabht, taba):
+ # tpath - the pathstructure containing pt1 and pt2
+ # pt1, pt2 - the two points where the tab will be inserted
+ # tabht - the height of the tab
+ # taba - the angle of the tab sides
+ # returns the two tab points in order of closest to pt1
+ tpt1 = inkex.paths.Line(0.0,0.0)
+ tpt2 = inkex.paths.Line(0.0,0.0)
+ currTabHt = tabht
+ currTabAngle = taba
+ testAngle = 1.0
+ testHt = currTabHt * 0.001
+ adjustTab = 0
+ tabDone = False
+ while not tabDone:
+ # Let's find out the orientation of the tab
+ if math.isclose(pt1.x, pt2.x):
+ # It's vertical. Let's try the right side
+ if pt1.y < pt2.y:
+ tpt1.x = pt1.x + testHt
+ tpt2.x = pt2.x + testHt
+ tpt1.y = pt1.y + testHt/math.tan(math.radians(testAngle))
+ tpt2.y = pt2.y - testHt/math.tan(math.radians(testAngle))
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.x = pt1.x - currTabHt
+ tpt2.x = pt2.x - currTabHt
+ else:
+ tpt1.x = pt1.x + currTabHt
+ tpt2.x = pt2.x + currTabHt
+ tpt1.y = pt1.y + currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.y = pt2.y - currTabHt/math.tan(math.radians(currTabAngle))
+ else: # pt2.y < pt1.y
+ tpt1.x = pt1.x + testHt
+ tpt2.x = pt2.x + testHt
+ tpt1.y = pt1.y - testHt/math.tan(math.radians(testAngle))
+ tpt2.y = pt2.y + testHt/math.tan(math.radians(testAngle))
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.x = pt1.x - currTabHt
+ tpt2.x = pt2.x - currTabHt
+ else:
+ tpt1.x = pt1.x + currTabHt
+ tpt2.x = pt2.x + currTabHt
+ tpt1.y = pt1.y - currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.y = pt2.y + currTabHt/math.tan(math.radians(currTabAngle))
+ elif math.isclose(pt1.y, pt2.y):
+ # It's horizontal. Let's try the top
+ if pt1.x < pt2.x:
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x + testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x - testHt/math.tan(math.radians(testAngle))
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x + currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x - currTabHt/math.tan(math.radians(currTabAngle))
+ else: # pt2.x < pt1.x
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x - testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x + testHt/math.tan(math.radians(testAngle))
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x - currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x + currTabHt/math.tan(math.radians(currTabAngle))
+
+ else: # the orientation is neither horizontal nor vertical
+ # Let's get the slope of the line between the points
+ # Because Inkscape's origin is in the upper-left corner,
+ # a positive slope (/) will yield a negative value
+ slope = (pt2.y - pt1.y)/(pt2.x - pt1.x)
+ # Let's get the angle to the horizontal
+ theta = math.degrees(math.atan(slope))
+ # Let's construct a horizontal tab
+ seglength = math.sqrt((pt1.x-pt2.x)**2 +(pt1.y-pt2.y)**2)
+ if slope < 0.0:
+ if pt1.x < pt2.x:
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x + testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x - testHt/math.tan(math.radians(testAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x + currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x - currTabHt/math.tan(math.radians(currTabAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ else: # pt1.x > pt2.x
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x - testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x + testHt/math.tan(math.radians(testAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x - currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x + currTabHt/math.tan(math.radians(currTabAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ else: # slope > 0.0
+ if pt1.x < pt2.x:
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x + testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x - testHt/math.tan(math.radians(testAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x + currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x - currTabHt/math.tan(math.radians(currTabAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ else: # pt1.x > pt2.x
+ tpt1.y = pt1.y - testHt
+ tpt2.y = pt2.y - testHt
+ tpt1.x = pt1.x - testHt/math.tan(math.radians(testAngle))
+ tpt2.x = pt2.x + testHt/math.tan(math.radians(testAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ pnpt1 = inkex.paths.Move(tpt1.x, tpt1.y)
+ pnpt2 = inkex.paths.Move(tpt2.x, tpt2.y)
+ if ((not tpath.enclosed) and (self.insidePath(tpath.path, pnpt1) or self.insidePath(tpath.path, pnpt2))) or \
+ (tpath.enclosed and ((not self.insidePath(tpath.path, pnpt1)) and (not self.insidePath(tpath.path, pnpt2)))):
+ tpt1.y = pt1.y + currTabHt
+ tpt2.y = pt2.y + currTabHt
+ else:
+ tpt1.y = pt1.y - currTabHt
+ tpt2.y = pt2.y - currTabHt
+ tpt1.x = pt1.x - currTabHt/math.tan(math.radians(currTabAngle))
+ tpt2.x = pt2.x + currTabHt/math.tan(math.radians(currTabAngle))
+ tl1 = [('M', [pt1.x,pt1.y])]
+ tl1 += [('L', [tpt1.x, tpt1.y])]
+ ele1 = inkex.Path(tl1)
+ tl2 = [('M', [pt1.x,pt1.y])]
+ tl2 += [('L', [tpt2.x, tpt2.y])]
+ ele2 = inkex.Path(tl2)
+ thetal1 = ele1.rotate(theta, [pt1.x,pt1.y])
+ thetal2 = ele2.rotate(theta, [pt2.x,pt2.y])
+ tpt1.x = thetal1[1].x
+ tpt1.y = thetal1[1].y
+ tpt2.x = thetal2[1].x
+ tpt2.y = thetal2[1].y
+ # Check to see if any tabs intersect each other
+ if self.detectIntersect(pt1.x, pt1.y, tpt1.x, tpt1.y, pt2.x, pt2.y, tpt2.x, tpt2.y):
+ # Found an intersection.
+ if adjustTab == 0:
+ # Try increasing the tab angle in one-degree increments
+ currTabAngle = currTabAngle + 1.0
+ if currTabAngle > 88.0: # We're not increasing the tab angle above 89 degrees
+ adjustTab = 1
+ currTabAngle = taba
+ if adjustTab == 1:
+ # So, try reducing the tab height in 20% increments instead
+ currTabHt = currTabHt - tabht*0.2 # Could this lead to a zero tab_height?
+ if currTabHt <= 0.0:
+ # Give up
+ currTabHt = tabht
+ adjustTab = 2
+ if adjustTab == 2:
+ tabDone = True # Just show the failure
+ else:
+ tabDone = True
+
+ return tpt1,tpt2
+
+
+ def effect(self):
+ scale = self.svg.unittouu('1'+self.options.unit)
+ layer = self.svg.get_current_layer()
+ polysides = int(self.options.polysides)
+ tab_angle = float(self.options.tabangle)
+ tab_height = float(self.options.tabheight) * scale
+ dashlength = float(self.options.dashlength) * scale
+ npaths = []
+ elems = []
+ sstr = None
+ radpath = 0 # Initial assumption is that first path is the radius
+ outlpath = 1 # and second path is the outline
+ yorient = True # assuming we are revolving around the Y axis
+ for selem in self.svg.selection.filter(inkex.PathElement):
+ elems.append(selem)
+ if len(elems) == 0:
+ raise inkex.AbortExtension("ERROR: Nothing selected")
+ elif len(elems) != 2:
+ raise inkex.AbortExtension("ERROR: Select only the outline and its radius line\n"\
+ +"Nothing more or less.")
+ for elem in elems: # for each path
+ escale = 1.0
+ if 'transform' in elem.attrib:
+ transforms = elem.attrib['transform'].split()
+ for tf in transforms:
+ if tf.startswith('scale'):
+ escale = float(tf.split('(')[1].split(')')[0])
+ last_letter = 'Z'
+
+ parent = elem.getparent()
+ #if parent != self.svg.root:
+ # elem.path.transform = elem.path.transform(parent.composed_transform())
+ elementPath = elem.path.to_non_shorthand().to_absolute()
+
+ for ptoken in elementPath: # For each point in the path
+ ptx2 = None
+ pty2 = None
+ if ptoken.letter == 'M': # Starting point
+ # Hold this point in case we receive a Z
+ ptx1 = mx = ptoken.x * escale
+ pty1 = my = ptoken.y * escale
+ '''
+ Assign a structure to the new path. We assume that there is
+ only one path and, therefore, it isn't enclosed by a
+ sub-path. However, we'll suffix the ID, if we find a
+ sub-path.
+ '''
+ npath = pathStruct()
+ npath.enclosed = False
+ npath.id = elem.get_id()
+ if 'style' in elem.attrib:
+ npath.style = elem.attrib['style']
+ if not math.isclose(escale, 1.0):
+ lsstr = npath.style.split(';')
+ for stoken in range(len(lsstr)):
+ if lsstr[stoken].startswith('stroke-width'):
+ swt = lsstr[stoken].split(':')[1]
+ swf = str(float(swt)*escale)
+ lsstr[stoken] = lsstr[stoken].replace(swt, swf)
+ if lsstr[stoken].startswith('stroke-miterlimit'):
+ swt = lsstr[stoken].split(':')[1]
+ swf = str(float(swt)*escale)
+ lsstr[stoken] = lsstr[stoken].replace(swt, swf)
+ npath.style = ";".join(lsstr)
+ npath.path.append(inkex.paths.Move(ptx1,pty1))
+ else:
+ if last_letter != 'M':
+ ptx1 = ptx2
+ pty1 = pty2
+ if ptoken.letter == 'L':
+ ptx2 = ptoken.x * escale
+ pty2 = ptoken.y * escale
+ elif ptoken.letter == 'H':
+ ptx2 = ptoken.x * escale
+ pty2 = pty1
+ elif ptoken.letter == 'V':
+ ptx2 = ptx1
+ pty2 = ptoken.y * escale
+ elif ptoken.letter == 'Z':
+ raise inkex.AbortExtension("ERROR: Paths must be open")
+ else:
+ raise inkex.AbortExtension("ERROR: Unrecognized path command {0}. Please convert to polyline before!".format(ptoken.letter))
+ npath.path.append(inkex.paths.Line(ptx2,pty2))
+ last_letter = ptoken.letter
+ npaths.append(npath)
+
+ # Let's validate the input
+ if len(npaths[1].path) == 2:
+ # Our initial assumption was wrong
+ radpath = 1
+ outlpath = 0
+ if math.isclose(npaths[radpath].path[0].y,npaths[radpath].path[1].y):
+ # Guessed wrong. For now, we're just going to abort
+ # TODO: Support revolving around the X axis
+ raise inkex.AbortExtension("ERROR: This extension can only revolve about the Y axis")
+ '''
+ The model will be open at the top and the bottom, so we have to calculate
+ the size of the polygons that will cover them. We were given the number
+ of sides.
+ '''
+ dscore = '' # Used for building dashlines for model
+ dwscore = '' # Used for building dashlines for wrapper
+ if yorient:
+ # Make sure the outline's points are ordered in ascending Y
+ if npaths[outlpath].path[0].y < npaths[outlpath].path[0].y:
+ npaths[outlpath].path.reverse()
+ # construct the side panel
+ xpos = ypos = 0.0
+ lhs = [] # Left hand side of panel
+ rhs = [] # Right hand side of panel
+ for npoint in range(len(npaths[outlpath].path)):
+ pr = abs(npaths[radpath].path[0].x - npaths[outlpath].path[npoint].x)
+ pwidth = 2.0*pr*math.tan(math.pi/polysides)
+ pR = pr/math.cos(math.pi/polysides)
+ if npoint == 0:
+ topR = pR
+ topw = pwidth
+ lhs.append(inkex.paths.Move(xpos - pwidth/2,ypos))
+ rhs.append(inkex.paths.Line(xpos + pwidth/2,ypos))
+ else:
+ seglength = math.sqrt((npaths[outlpath].path[npoint-1].x - npaths[outlpath].path[npoint].x)**2 + \
+ (npaths[outlpath].path[npoint-1].y - npaths[outlpath].path[npoint].y)**2)
+ ypos += seglength
+ lhs.append(inkex.paths.Line(xpos - pwidth/2,ypos))
+ rhs.append(inkex.paths.Line(xpos + pwidth/2,ypos))
+ if npoint == len(npaths[outlpath].path)-1:
+ bottomR = pR
+ bottomw = pwidth
+ # Put score marks across the panel
+ for pcnt in range(len(lhs)):
+ if (pcnt != 0) and (pcnt != (len(lhs)-1)):
+ dscore += self.makescore(lhs[pcnt], rhs[pcnt], dashlength)
+ dwscore = dscore # wrapper only needs these scorelines
+
+ rhs.reverse() # Reverse the order so we can
+ cpath = pathStruct()
+ cpath.enclosed = False
+ cpath.id = 'panel'
+ cpath.path = lhs + rhs
+ # add tabs to panel
+ dprop = '' # Used for building the main path
+ dwrap = ''
+ for ptn in range(len(cpath.path)):
+ if ptn == 0:
+ dprop = 'M '+str(cpath.path[ptn].x)+','+str(cpath.path[ptn].y)
+ dwrap = 'M '+str(cpath.path[ptn].x)+','+str(cpath.path[ptn].y)
+ else:
+ if ptn > (len(npaths[outlpath].path)-1):
+ dscore += self.makescore(cpath.path[ptn-1], cpath.path[ptn],dashlength)
+ tabpt1, tabpt2 = self.makeTab(cpath, cpath.path[ptn-1], cpath.path[ptn], tab_height, tab_angle)
+ dprop += ' L '+str(tabpt1.x)+','+str(tabpt1.y)
+ dprop += ' L '+str(tabpt2.x)+','+str(tabpt2.y)
+ dprop += ' L '+str(cpath.path[ptn].x)+','+str(cpath.path[ptn].y)
+ dwrap += ' L '+str(cpath.path[ptn].x)+','+str(cpath.path[ptn].y)
+ if ptn == len(cpath.path)-1:
+ tabpt1, tabpt2 = self.makeTab(cpath, cpath.path[ptn], cpath.path[0], tab_height, tab_angle)
+ dprop += ' L '+str(tabpt1.x)+','+str(tabpt1.y)
+ dprop += ' L '+str(tabpt2.x)+','+str(tabpt2.y)
+ dprop += 'Z'
+ dscore += self.makescore(cpath.path[ptn], cpath.path[0],dashlength)
+ dwrap += 'Z '
+ if npaths[outlpath].style != None:
+ lsstr = npaths[outlpath].style.split(';')
+ for stoken in range(len(lsstr)):
+ if lsstr[stoken].startswith('fill'):
+ swt = lsstr[stoken].split(':')[1]
+ swf = '#eeeeee'
+ lsstr[stoken] = lsstr[stoken].replace(swt, swf)
+ else:
+ lsstr.append("\'fill\':\'#eeeeee\'")
+ sstr = ";".join(lsstr)
+ # lump together all the score lines
+ groupm = inkex.elements._groups.Group()
+ groupm.label = 'group0ms'
+ self.drawline(dprop,'model',groupm,sstr+';stroke:{}'.format(self.options.color_solid)) # Output the model
+
+ dscore_style = sstr+';stroke:{}'.format(self.options.color_dash)
+ if self.options.cosmetic_dash_style is True:
+ dscore_style += ';stroke-dasharray:{}'.format(3, 3)
+ self.drawline(dscore[1:],'mscore',groupm,dscore_style) # Output the scorelines separately
+ layer.append(groupm)
+ groupw = inkex.elements._groups.Group()
+ groupw.label = 'group0ws'
+ if self.options.generate_decorative_wrapper is True:
+ self.drawline(dwrap,'wrapper',groupw,sstr) # Output the model
+ self.drawline(dwscore[1:],'wscore',groupw,sstr) # Output the scorelines separately
+ layer.append(groupw)
+
+ # Finally, generate the top and bottom polygons
+ self.drawline(self.makepoly(topw, polysides),npaths[outlpath].id+"lid1",layer,sstr+';stroke:{}'.format(self.options.color_solid))
+ self.drawline(self.makepoly(bottomw, polysides),npaths[outlpath].id+"lid2",layer,sstr+';stroke:{}'.format(self.options.color_solid))
+
+
+if __name__ == '__main__':
+ Polygen().run()
diff --git a/extensions/fablabchemnitz/tab_generator/tab_generator.py b/extensions/fablabchemnitz/tab_generator/tab_generator.py
index 6a00eb28..2d8948c7 100644
--- a/extensions/fablabchemnitz/tab_generator/tab_generator.py
+++ b/extensions/fablabchemnitz/tab_generator/tab_generator.py
@@ -585,7 +585,7 @@ class Tabgen(inkex.EffectExtension):
ptx2 = mx
pty2 = my
else:
- raise inkex.AbortExtension("Unrecognized path command {0}".format(ptoken.letter))
+ raise inkex.AbortExtension("Unrecognized path command {0}. Please convert to polyline before!".format(ptoken.letter))
npath.path.append(inkex.paths.Line(ptx2,pty2))
if ptoken.letter == 'Z':
npaths.append(npath)