This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz/polygen/polygen.py

693 lines
35 KiB
Python

#!/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]<minX or self.p[0]>maxX or self.p[1]<minY 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()