1037 lines
38 KiB
Python
1037 lines
38 KiB
Python
|
#! /usr/bin/env python
|
||
|
'''
|
||
|
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.
|
||
|
|
||
|
Quick description:
|
||
|
|
||
|
'''
|
||
|
# standard library
|
||
|
from math import *
|
||
|
from copy import deepcopy
|
||
|
# local library
|
||
|
import inkex
|
||
|
import pathmodifier
|
||
|
import cubicsuperpath
|
||
|
import bezmisc
|
||
|
import simplepath
|
||
|
import simpletransform
|
||
|
|
||
|
def modifySkeletonPath(skelPath):
|
||
|
resPath = []
|
||
|
l = len(skelPath)
|
||
|
resPath += skelPath[0]
|
||
|
|
||
|
if l > 1:
|
||
|
for i in range(1, l):
|
||
|
if skelPath[i][0][1] == resPath[-1][1]:
|
||
|
skelPath[i][0][0] = resPath[-1][0]
|
||
|
del resPath[-1]
|
||
|
|
||
|
resPath += skelPath[i]
|
||
|
|
||
|
return resPath
|
||
|
|
||
|
def linearize(p, tolerance=0.001):
|
||
|
'''
|
||
|
This function receives a component of a 'cubicsuperpath' and returns two things:
|
||
|
The path subdivided in many straight segments, and an array containing the length of each segment.
|
||
|
'''
|
||
|
zero = 0.000001
|
||
|
i = 0
|
||
|
d = 0
|
||
|
lengths=[]
|
||
|
|
||
|
while i < len(p) - 1:
|
||
|
box = bezmisc.pointdistance(p[i][1], p[i][2])
|
||
|
box += bezmisc.pointdistance(p[i][2], p[i+1][0])
|
||
|
box += bezmisc.pointdistance(p[i+1][0], p[i+1][1])
|
||
|
chord = bezmisc.pointdistance(p[i][1], p[i+1][1])
|
||
|
|
||
|
if (box - chord) > tolerance:
|
||
|
b1, b2 = bezmisc.beziersplitatt([p[i][1], p[i][2], p[i + 1][0], p[i + 1][1]], 0.5)
|
||
|
p[i][2][0], p[i][2][1] = b1[1]
|
||
|
p[i + 1][0][0], p[i + 1][0][1] = b2[2]
|
||
|
p.insert(i + 1, [[b1[2][0], b1[2][1]], [b1[3][0], b1[3][1]], [b2[1][0], b2[1][1]]])
|
||
|
else:
|
||
|
d = (box + chord) / 2
|
||
|
lengths.append(d)
|
||
|
i += 1
|
||
|
|
||
|
new = [p[i][1] for i in range(0, len(p) - 1) if lengths[i] > zero]
|
||
|
new.append(p[-1][1])
|
||
|
lengths = [l for l in lengths if l > zero]
|
||
|
|
||
|
return (new, lengths)
|
||
|
|
||
|
def isSkeletonClosed(sklCmp):
|
||
|
cntOfDgts = 2
|
||
|
|
||
|
if (round(sklCmp[0][0], cntOfDgts) != round(sklCmp[-1][0], cntOfDgts) or round(sklCmp[0][1], cntOfDgts) != round(sklCmp[-1][1], cntOfDgts)):
|
||
|
return False
|
||
|
|
||
|
return True
|
||
|
|
||
|
def checkCompatibility(bbox1, bbox2, comps1, comps2):
|
||
|
cl1 = isSkeletonClosed(comps1)
|
||
|
cl2 = isSkeletonClosed(comps2)
|
||
|
|
||
|
if (cl1 and cl2):
|
||
|
if ((bbox1[0] >= bbox2[0]) and (bbox1[1] <= bbox2[1]) and (bbox1[2] >= bbox2[2]) and (bbox1[3] <= bbox2[3])):
|
||
|
return (True, False)
|
||
|
elif ((bbox1[0] <= bbox2[0]) and (bbox1[1] >= bbox2[1]) and (bbox1[2] <= bbox2[2]) and (bbox1[3] >= bbox2[3])):
|
||
|
return (True, True)
|
||
|
elif (not cl1 and not cl2):
|
||
|
if (comps1[0][0] == comps2[0][0] and comps1[-1][0] == comps2[-1][0]):
|
||
|
if ((comps1[0][0] < comps1[-1][0] and comps1[0][1] >= comps2[0][1]) or (comps1[0][0] > comps1[-1][0] and comps1[0][1] <= comps2[0][1])):
|
||
|
return (True, False)
|
||
|
else:
|
||
|
return (True, True)
|
||
|
elif (comps1[0][1] == comps2[0][1] and comps1[-1][1] == comps2[-1][1]):
|
||
|
if ((comps1[0][1] < comps1[-1][1] and comps1[0][0] <= comps2[0][0]) or (comps1[0][1] > comps1[-1][1] and comps1[0][0] >= comps2[0][0])):
|
||
|
return (True, False)
|
||
|
else:
|
||
|
return (True, True)
|
||
|
|
||
|
return (False, False)
|
||
|
|
||
|
def linearizeEnvelopes(envs):
|
||
|
bbox1 = simpletransform.computeBBox([envs[0]])
|
||
|
bbox2 = simpletransform.computeBBox([envs[1]])
|
||
|
|
||
|
comps1, lengths1 = linearize(modifySkeletonPath(cubicsuperpath.parsePath(envs[0].get('d'))))
|
||
|
comps2, lengths2 = linearize(modifySkeletonPath(cubicsuperpath.parsePath(envs[1].get('d'))))
|
||
|
|
||
|
correctness, shouldSwap = checkCompatibility(bbox1, bbox2, comps1, comps2)
|
||
|
|
||
|
if not shouldSwap:
|
||
|
return (comps1, lengths1, comps2, lengths2, bbox1, bbox2, correctness)
|
||
|
else:
|
||
|
return (comps2, lengths2, comps1, lengths1, bbox2, bbox1, correctness)
|
||
|
|
||
|
def getMidPoint(p1, p2):
|
||
|
return [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2]
|
||
|
|
||
|
def getPoint(p1, p2, x, y):
|
||
|
x1 = p1[0]
|
||
|
y1 = p1[1]
|
||
|
x2 = p2[0]
|
||
|
y2 = p2[1]
|
||
|
|
||
|
a = (y1 - y2) / (x1 - x2)
|
||
|
b = y1 - a * x1
|
||
|
|
||
|
if x == None:
|
||
|
x = (y - b) / a
|
||
|
else:
|
||
|
y = a * x + b
|
||
|
|
||
|
return [x, y]
|
||
|
|
||
|
def getPtOnSeg(p1, p2, segLen, l):
|
||
|
if p1[0] == p2[0]:
|
||
|
return [p1[0], p1[1] - l] if p1[1] > p2[1] else [p1[0], p1[1] + l]
|
||
|
|
||
|
if p1[1] == p2[1]:
|
||
|
return [p1[0] - l, p1[1]] if p1[0] > p2[0] else [p1[0] + l, p1[1]]
|
||
|
|
||
|
dy = abs(p1[1] - p2[1])
|
||
|
angle = asin(dy / segLen)
|
||
|
dx = l * cos(angle)
|
||
|
x = p1[0] - dx if p1[0] > p2[0] else p1[0] + dx
|
||
|
|
||
|
return getPoint(p1, p2, x, None)
|
||
|
|
||
|
def getPtsByX(pt, comps, isClosed):
|
||
|
res = []
|
||
|
|
||
|
for i in range(1, len(comps)):
|
||
|
if ((comps[i - 1][0] <= pt[0] and pt[0] <= comps[i][0]) or (comps[i - 1][0] >= pt[0] and pt[0] >= comps[i][0])):
|
||
|
if comps[i - 1][0] == comps[i][0]:
|
||
|
d1 = bezmisc.pointdistance(pt, comps[i - 1])
|
||
|
d2 = bezmisc.pointdistance(pt, comps[i])
|
||
|
|
||
|
if d1 < d2:
|
||
|
res.append(comps[i - 1])
|
||
|
else:
|
||
|
res.append(comps[i])
|
||
|
elif comps[i - 1][1] == comps[i][1]:
|
||
|
res.append([pt[0], comps[i - 1][1]])
|
||
|
else:
|
||
|
res.append(getPoint(comps[i - 1], comps[i], pt[0], None))
|
||
|
|
||
|
if not isClosed:
|
||
|
return res[0]
|
||
|
|
||
|
return res
|
||
|
|
||
|
def getPtsByY(pt, comps, isClosed):
|
||
|
res = []
|
||
|
|
||
|
for i in range(1, len(comps)):
|
||
|
if ((comps[i - 1][1] <= pt[1] and pt[1] <= comps[i][1]) or (comps[i - 1][1] >= pt[1] and pt[1] >= comps[i][1])):
|
||
|
if comps[i - 1][1] == comps[i][1]:
|
||
|
d1 = bezmisc.pointdistance(pt, comps[i - 1])
|
||
|
d2 = bezmisc.pointdistance(pt, comps[i])
|
||
|
|
||
|
if d1 < d2:
|
||
|
res.append(comps[i - 1])
|
||
|
else:
|
||
|
res.append(comps[i])
|
||
|
elif comps[i - 1][0] == comps[i][0]:
|
||
|
res.append([comps[i - 1][0], pt[1]])
|
||
|
else:
|
||
|
res.append(getPoint(comps[i - 1], comps[i], None, pt[1]))
|
||
|
|
||
|
if not isClosed:
|
||
|
return res[0]
|
||
|
|
||
|
return res
|
||
|
|
||
|
def pickCorrespPt(cntr, p, pts):
|
||
|
tmpPts = []
|
||
|
|
||
|
for pt in pts:
|
||
|
if (p[0] >= cntr[0] and p[1] >= cntr[1]):
|
||
|
if (pt[0] >= cntr[0] and pt[1] >= cntr[1]):
|
||
|
tmpPts.append(pt)
|
||
|
elif (p[0] <= cntr[0] and p[1] <= cntr[1]):
|
||
|
if (pt[0] <= cntr[0] and pt[1] <= cntr[1]):
|
||
|
tmpPts.append(pt)
|
||
|
elif (p[0] < cntr[0] and p[1] > cntr[1]):
|
||
|
if (pt[0] <= cntr[0] and pt[1] >= cntr[1]):
|
||
|
tmpPts.append(pt)
|
||
|
else:
|
||
|
if (pt[0] >= cntr[0] and pt[1] <= cntr[1]):
|
||
|
tmpPts.append(pt)
|
||
|
|
||
|
cntPts = len(tmpPts)
|
||
|
resPt = tmpPts[0]
|
||
|
|
||
|
if cntPts > 1:
|
||
|
shortestDist = bezmisc.pointdistance(p, tmpPts[0])
|
||
|
shortI = 0
|
||
|
|
||
|
for i in range(1, cntPts):
|
||
|
tmpDist = bezmisc.pointdistance(p, tmpPts[i])
|
||
|
|
||
|
if tmpDist < shortestDist:
|
||
|
shortestDist = tmpDist
|
||
|
shortI = i
|
||
|
|
||
|
resPt = tmpPts[shortI]
|
||
|
|
||
|
return resPt
|
||
|
|
||
|
def getIntersectionPt(cntr, p, comps):
|
||
|
obtPts = []
|
||
|
|
||
|
if cntr[0] == p[0]:
|
||
|
obtPts = getPtsByX(cntr, comps, True)
|
||
|
elif cntr[1] == p[1]:
|
||
|
obtPts = getPtsByY(cntr, comps, True)
|
||
|
else:
|
||
|
a = (cntr[1] - p[1]) / (cntr[0] - p[0])
|
||
|
b = cntr[1] - a * cntr[0]
|
||
|
|
||
|
for i in range(1, len(comps)):
|
||
|
a1 = None
|
||
|
a2 = None
|
||
|
|
||
|
if (cntr[0] != comps[i][0]):
|
||
|
a2 = (cntr[1] - comps[i][1]) / (cntr[0] - comps[i][0])
|
||
|
|
||
|
if (cntr[0] != comps[i - 1][0]):
|
||
|
a1 = (cntr[1] - comps[i - 1][1]) / (cntr[0] - comps[i - 1][0])
|
||
|
|
||
|
if ((a < 0 and not a2) or (not a1 and a > a2)):
|
||
|
continue
|
||
|
|
||
|
if (((a1 < a) and (a1 > 0) and (not a2 or a2 < 0)) or ((not a1 or a1 > 0) and (a2 < 0) and (a < a2)) or (a1 <= a and a <= a2)):
|
||
|
if a == a1:
|
||
|
obtPts.append(comps[i - 1])
|
||
|
elif a == a2:
|
||
|
obtPts.append(comps[i])
|
||
|
else:
|
||
|
x = comps[i - 1][0]
|
||
|
|
||
|
if (comps[i - 1][0] != comps[i][0]):
|
||
|
a3 = (comps[i - 1][1] - comps[i][1]) / (comps[i - 1][0] - comps[i][0])
|
||
|
b3 = comps[i - 1][1] - a3 * comps[i - 1][0]
|
||
|
x = (b3 - b) / (a - a3)
|
||
|
|
||
|
y = a * x + b
|
||
|
obtPts.append([x, y])
|
||
|
|
||
|
return pickCorrespPt(cntr, p, obtPts)
|
||
|
|
||
|
def getPolygonCentroid(polygon):
|
||
|
x = 0
|
||
|
y = 0
|
||
|
n = len(polygon)
|
||
|
|
||
|
for vert in polygon:
|
||
|
x += vert[0]
|
||
|
y += vert[1]
|
||
|
|
||
|
x = x / n
|
||
|
y = y / n
|
||
|
|
||
|
return [x, y]
|
||
|
|
||
|
def getDistBetweenFirstPts(comps1, comps2, bbox1, bbox2, nest):
|
||
|
pt1 = comps1[0]
|
||
|
pt2 = None
|
||
|
|
||
|
if (bbox1[0] == bbox2[0] and bbox1[1] == bbox2[1]):
|
||
|
pt2 = getPtsByX(pt1, comps2, False)
|
||
|
elif (bbox1[2] == bbox2[2] and bbox1[3] == bbox2[3]):
|
||
|
pt2 = getPtsByY(pt1, comps2, False)
|
||
|
elif nest:
|
||
|
centroid = getPolygonCentroid(comps1)
|
||
|
pt2 = getIntersectionPt(centroid, pt1, comps2)
|
||
|
|
||
|
dist = bezmisc.pointdistance(pt1, pt2)
|
||
|
|
||
|
return dist
|
||
|
|
||
|
def getCirclePath(startPt, rx):
|
||
|
curX = startPt[0]
|
||
|
curY = startPt[1]
|
||
|
signRX = signRY = 1
|
||
|
|
||
|
res = 'M ' + str(curX) + ',' + str(curY) + ' A '
|
||
|
|
||
|
for i in range(4):
|
||
|
res += str(rx) + ',' + str(rx) + ' 0 0 1 '
|
||
|
|
||
|
if i % 2 == 0:
|
||
|
signRX = -signRX
|
||
|
else:
|
||
|
signRY = -signRY
|
||
|
|
||
|
curX += signRX * rx
|
||
|
curY += signRY * rx
|
||
|
|
||
|
res += str(curX) + ',' + str(curY) + ' '
|
||
|
|
||
|
res += 'Z'
|
||
|
|
||
|
return res
|
||
|
|
||
|
def getClosedLinearizedSkeletonPath(comps1, comps2, offs, isLine):
|
||
|
path = []
|
||
|
lengths = []
|
||
|
|
||
|
centroid = getPolygonCentroid(comps1)
|
||
|
|
||
|
if isLine:
|
||
|
for pt2 in comps2:
|
||
|
pt1 = getIntersectionPt(centroid, pt2, comps1)
|
||
|
midPt = getMidPoint(pt1, pt2)
|
||
|
|
||
|
if offs > 0:
|
||
|
dist = bezmisc.pointdistance(pt1, pt2)
|
||
|
path.append(getPtOnSeg(pt1, pt2, dist, offs * dist))
|
||
|
else:
|
||
|
path.append(midPt)
|
||
|
|
||
|
if len(path) > 1:
|
||
|
lengths.append(bezmisc.pointdistance(path[-2], path[-1]))
|
||
|
else:
|
||
|
pt1 = comps1[0]
|
||
|
pt2 = getIntersectionPt(centroid, pt1, comps2)
|
||
|
midPt = getMidPoint(pt1, pt2)
|
||
|
rx = bezmisc.pointdistance(centroid, midPt)
|
||
|
|
||
|
svgPath = getCirclePath(midPt, rx)
|
||
|
|
||
|
path, lengths = linearize(modifySkeletonPath(cubicsuperpath.parsePath(svgPath)))
|
||
|
|
||
|
return (path, lengths)
|
||
|
|
||
|
def getHorizontalLinearizedSkeletonPath(comps1, comps2, offs, isLine):
|
||
|
path = []
|
||
|
lengths = []
|
||
|
|
||
|
if isLine:
|
||
|
for pt1 in comps1:
|
||
|
pt2 = getPtsByX(pt1, comps2, False)
|
||
|
midPt = getMidPoint(pt1, pt2)
|
||
|
|
||
|
if offs > 0:
|
||
|
dist = bezmisc.pointdistance(pt1, pt2)
|
||
|
path.append(getPtOnSeg(pt1, pt2, dist, offs * dist))
|
||
|
else:
|
||
|
path.append(midPt)
|
||
|
|
||
|
if len(path) > 1:
|
||
|
lengths.append(bezmisc.pointdistance(path[-2], path[-1]))
|
||
|
else:
|
||
|
pt1 = comps1[0]
|
||
|
pt2 = comps2[0]
|
||
|
|
||
|
firstPt = getMidPoint(pt1, pt2)
|
||
|
path.append(firstPt)
|
||
|
|
||
|
lastPt = [comps1[-1][0], firstPt[1]]
|
||
|
path.append(lastPt)
|
||
|
|
||
|
lengths.append(bezmisc.pointdistance(path[-2], path[-1]))
|
||
|
|
||
|
return (path, lengths)
|
||
|
|
||
|
def getVerticalLinearizedSkeletonPath(comps1, comps2, offs, isLine):
|
||
|
path = []
|
||
|
lengths = []
|
||
|
|
||
|
if isLine:
|
||
|
for pt1 in comps1:
|
||
|
pt2 = getPtsByY(pt1, comps2, False)
|
||
|
midPt = getMidPoint(pt1, pt2)
|
||
|
|
||
|
if offs > 0:
|
||
|
dist = bezmisc.pointdistance(pt1, pt2)
|
||
|
path.append(getPtOnSeg(pt1, pt2, dist, offs * dist))
|
||
|
else:
|
||
|
path.append(midPt)
|
||
|
|
||
|
if len(path) > 1:
|
||
|
lengths.append(bezmisc.pointdistance(path[-2], path[-1]))
|
||
|
else:
|
||
|
pt1 = comps1[0]
|
||
|
pt2 = comps2[0]
|
||
|
|
||
|
firstPt = getMidPoint(pt1, pt2)
|
||
|
path.append(firstPt)
|
||
|
|
||
|
lastPt = [firstPt[0], comps1[-1][1]]
|
||
|
path.append(lastPt)
|
||
|
|
||
|
lengths.append(bezmisc.pointdistance(path[-2], path[-1]))
|
||
|
|
||
|
return (path, lengths)
|
||
|
|
||
|
def getColorAndOpacity(longColor):
|
||
|
'''
|
||
|
Convert the long into a #rrggbb color value
|
||
|
Conversion back is A + B*256^1 + G*256^2 + R*256^3
|
||
|
'''
|
||
|
longColor = long(longColor)
|
||
|
|
||
|
if longColor < 0:
|
||
|
longColor = longColor & 0xFFFFFFFF
|
||
|
|
||
|
hexColor = hex(longColor)
|
||
|
|
||
|
hexOpacity = hexColor[-3:-1]
|
||
|
hexColor = '#' + hexColor[2:-3].rjust(6, '0')
|
||
|
|
||
|
return (hexColor, hexOpacity)
|
||
|
|
||
|
def setColorAndOpacity(style, color, opacity):
|
||
|
declarations = style.split(';')
|
||
|
strokeOpacityInStyle = False
|
||
|
newOpacity = round((int(opacity, 16) / 255.0), 8)
|
||
|
|
||
|
for i,decl in enumerate(declarations):
|
||
|
parts = decl.split(':', 2)
|
||
|
|
||
|
if len(parts) == 2:
|
||
|
(prop, val) = parts
|
||
|
prop = prop.strip().lower()
|
||
|
|
||
|
if (prop == 'stroke' and val != color):
|
||
|
declarations[i] = prop + ':' + color
|
||
|
|
||
|
if prop == 'stroke-opacity':
|
||
|
if val != newOpacity:
|
||
|
declarations[i] = prop + ':' + str(newOpacity)
|
||
|
|
||
|
strokeOpacityInStyle = True
|
||
|
|
||
|
if not strokeOpacityInStyle:
|
||
|
declarations.append('stroke-opacity' + ':' + str(newOpacity))
|
||
|
|
||
|
return ";".join(declarations)
|
||
|
|
||
|
def drawfunction(nodes, width, fx):
|
||
|
# x-bounds of the plane
|
||
|
xstart = 0.0
|
||
|
xend = 2 * pi
|
||
|
# y-bounds of the plane
|
||
|
ybottom = -1.0
|
||
|
ytop = 1.0
|
||
|
# size and location of the plane on the canvas
|
||
|
height = 2
|
||
|
left = 15
|
||
|
bottom = 15 + height
|
||
|
|
||
|
# function specified by the user
|
||
|
try:
|
||
|
if fx != "":
|
||
|
f = eval('lambda x: ' + fx.strip('"'))
|
||
|
except SyntaxError:
|
||
|
return []
|
||
|
|
||
|
scalex = width / (xend - xstart)
|
||
|
xoff = left
|
||
|
# conver x-value to coordinate
|
||
|
coordx = lambda x: (x - xstart) * scalex + xoff
|
||
|
|
||
|
scaley = height / (ytop - ybottom)
|
||
|
yoff = bottom
|
||
|
# conver y-value to coordinate
|
||
|
coordy = lambda y: (ybottom - y) * scaley + yoff
|
||
|
|
||
|
# step is the distance between nodes on x
|
||
|
step = (xend - xstart) / (nodes - 1)
|
||
|
third = step / 3.0
|
||
|
# step used in calculating derivatives
|
||
|
ds = step * 0.001
|
||
|
|
||
|
# initialize function and derivative for 0;
|
||
|
# they are carried over from one iteration to the next, to avoid extra function calculations.
|
||
|
x0 = xstart
|
||
|
y0 = f(xstart)
|
||
|
|
||
|
# numerical derivative, using 0.001*step as the small differential
|
||
|
x1 = xstart + ds # Second point AFTER first point (Good for first point)
|
||
|
y1 = f(x1)
|
||
|
|
||
|
dx0 = (x1 - x0) / ds
|
||
|
dy0 = (y1 - y0) / ds
|
||
|
|
||
|
# path array
|
||
|
a = []
|
||
|
# Start curve
|
||
|
a.append(['M ', [coordx(x0), coordy(y0)]])
|
||
|
|
||
|
for i in range(int(nodes - 1)):
|
||
|
x1 = (i + 1) * step + xstart
|
||
|
x2 = x1 - ds # Second point BEFORE first point (Good for last point)
|
||
|
y1 = f(x1)
|
||
|
y2 = f(x2)
|
||
|
|
||
|
# numerical derivative
|
||
|
dx1 = (x1 - x2) / ds
|
||
|
dy1 = (y1 - y2) / ds
|
||
|
|
||
|
# create curve
|
||
|
a.append([' C ', [coordx(x0 + (dx0 * third)), coordy(y0 + (dy0 * third)),
|
||
|
coordx(x1 - (dx1 * third)), coordy(y1 - (dy1 * third)),
|
||
|
coordx(x1), coordy(y1)]])
|
||
|
|
||
|
# Next segment's start is this segment's end
|
||
|
x0 = x1
|
||
|
y0 = y1
|
||
|
# Assume the function is smooth everywhere, so carry over the derivative too
|
||
|
dx0 = dx1
|
||
|
dy0 = dy1
|
||
|
|
||
|
return a
|
||
|
|
||
|
def offset(pathComp, dx, dy):
|
||
|
for ctl in pathComp:
|
||
|
for pt in ctl:
|
||
|
pt[0] += dx
|
||
|
pt[1] += dy
|
||
|
|
||
|
def compsToSVGd(p):
|
||
|
f = p[0]
|
||
|
p = p[1:]
|
||
|
svgd = 'M %.9f,%.9f ' % (f[0], f[1])
|
||
|
|
||
|
for x in p:
|
||
|
svgd += 'L %.9f,%.9f ' % (x[0], x[1])
|
||
|
|
||
|
return svgd
|
||
|
|
||
|
def stretchComps(skelComps, patComps, comps1, comps2, bbox1, bbox2, nest, halfHeight, ampl, offs):
|
||
|
res = []
|
||
|
|
||
|
if nest:
|
||
|
newPt = None
|
||
|
centroid = getPolygonCentroid(comps1)
|
||
|
|
||
|
for pt in patComps:
|
||
|
skelPt = getIntersectionPt(centroid, pt, skelComps)
|
||
|
pt1 = getIntersectionPt(centroid, pt, comps1)
|
||
|
pt2 = getIntersectionPt(centroid, pt, comps2)
|
||
|
midPt = getMidPoint(pt1, pt2)
|
||
|
|
||
|
dist1 = bezmisc.pointdistance(skelPt, pt)
|
||
|
dist2 = bezmisc.pointdistance(midPt, pt1) * ampl
|
||
|
dist3 = dist2 * dist1 / halfHeight
|
||
|
|
||
|
if (skelPt[0] >= centroid[0] and skelPt[1] >= centroid[1]):
|
||
|
if (pt[0] >= skelPt[0] and pt[1] >= skelPt[1]):
|
||
|
newPt = getPtOnSeg(midPt, pt2, bezmisc.pointdistance(midPt, pt2), dist3 + offs)
|
||
|
else:
|
||
|
newPt = getPtOnSeg(midPt, pt1, bezmisc.pointdistance(midPt, pt1), dist3 - offs)
|
||
|
elif (skelPt[0] <= centroid[0] and skelPt[1] <= centroid[1]):
|
||
|
if (pt[0] <= skelPt[0] and pt[1] <= skelPt[1]):
|
||
|
newPt = getPtOnSeg(midPt, pt2, bezmisc.pointdistance(midPt, pt2), dist3 + offs)
|
||
|
else:
|
||
|
newPt = getPtOnSeg(midPt, pt1, bezmisc.pointdistance(midPt, pt1), dist3 - offs)
|
||
|
elif (skelPt[0] < centroid[0] and skelPt[1] > centroid[1]):
|
||
|
if (pt[0] <= skelPt[0] and pt[1] >= skelPt[1]):
|
||
|
newPt = getPtOnSeg(midPt, pt2, bezmisc.pointdistance(midPt, pt2), dist3 + offs)
|
||
|
else:
|
||
|
newPt = getPtOnSeg(midPt, pt1, bezmisc.pointdistance(midPt, pt1), dist3 - offs)
|
||
|
else:
|
||
|
if (pt[0] >= skelPt[0] and pt[1] <= skelPt[1]):
|
||
|
newPt = getPtOnSeg(midPt, pt2, bezmisc.pointdistance(midPt, pt2), dist3 + offs)
|
||
|
else:
|
||
|
newPt = getPtOnSeg(midPt, pt1, bezmisc.pointdistance(midPt, pt1), dist3 - offs)
|
||
|
|
||
|
res.append(newPt)
|
||
|
elif (bbox1[0] == bbox2[0] and bbox1[1] == bbox2[1]):
|
||
|
midY = skelComps[0][1]
|
||
|
newPt = None
|
||
|
|
||
|
if (patComps[-1][0] != comps1[-1][0] and round(patComps[-1][0], 10) == comps1[-1][0]):
|
||
|
patComps[-1][0] = comps1[-1][0]
|
||
|
|
||
|
for pt in patComps:
|
||
|
pt1 = getPtsByX(pt, comps1, False)
|
||
|
pt2 = getPtsByX(pt, comps2, False)
|
||
|
midPt = getMidPoint(pt1, pt2)
|
||
|
|
||
|
dist1 = abs(pt[1] - midY)
|
||
|
dist2 = bezmisc.pointdistance(midPt, pt1) * ampl
|
||
|
dist3 = dist2 * dist1 / halfHeight
|
||
|
|
||
|
if bbox1[0] < bbox1[1]:
|
||
|
if pt[1] > midY:
|
||
|
newPt = getPtOnSeg(midPt, pt1, bezmisc.pointdistance(midPt, pt1), dist3 - offs)
|
||
|
else:
|
||
|
newPt = getPtOnSeg(midPt, pt2, bezmisc.pointdistance(midPt, pt2), dist3 + offs)
|
||
|
else:
|
||
|
if pt[1] < midY:
|
||
|
newPt = getPtOnSeg(midPt, pt1, bezmisc.pointdistance(midPt, pt1), dist3 - offs)
|
||
|
else:
|
||
|
newPt = getPtOnSeg(midPt, pt2, bezmisc.pointdistance(midPt, pt2), dist3 + offs)
|
||
|
|
||
|
res.append(newPt)
|
||
|
elif (bbox1[2] == bbox2[2] and bbox1[3] == bbox2[3]):
|
||
|
midX = skelComps[0][0]
|
||
|
newPt = None
|
||
|
|
||
|
if (patComps[-1][1] != comps1[-1][1] and round(patComps[-1][1], 10) == comps1[-1][1]):
|
||
|
patComps[-1][1] = comps1[-1][1]
|
||
|
|
||
|
for pt in patComps:
|
||
|
pt1 = getPtsByY(pt, comps1, False)
|
||
|
pt2 = getPtsByY(pt, comps2, False)
|
||
|
midPt = getMidPoint(pt1, pt2)
|
||
|
|
||
|
dist1 = abs(pt[0] - midX)
|
||
|
dist2 = bezmisc.pointdistance(midPt, pt1) * ampl
|
||
|
dist3 = dist2 * dist1 / halfHeight
|
||
|
|
||
|
if bbox1[2] < bbox1[3]:
|
||
|
if pt[0] < midX:
|
||
|
newPt = getPtOnSeg(midPt, pt1, bezmisc.pointdistance(midPt, pt1), dist3 - offs)
|
||
|
else:
|
||
|
newPt = getPtOnSeg(midPt, pt2, bezmisc.pointdistance(midPt, pt2), dist3 + offs)
|
||
|
else:
|
||
|
if pt[1] > midX:
|
||
|
newPt = getPtOnSeg(midPt, pt1, bezmisc.pointdistance(midPt, pt1), dist3 - offs)
|
||
|
else:
|
||
|
newPt = getPtOnSeg(midPt, pt2, bezmisc.pointdistance(midPt, pt2), dist3 + offs)
|
||
|
|
||
|
res.append(newPt)
|
||
|
|
||
|
return res
|
||
|
|
||
|
def stretch(pathComp, xscale, yscale, org):
|
||
|
for ctl in pathComp:
|
||
|
for pt in ctl:
|
||
|
pt[0] = org[0] + (pt[0] - org[0]) * xscale
|
||
|
pt[1] = org[1] + (pt[1] - org[1]) * yscale
|
||
|
|
||
|
class GuillochePattern(pathmodifier.PathModifier):
|
||
|
def __init__(self):
|
||
|
pathmodifier.PathModifier.__init__(self)
|
||
|
self.OptionParser.add_option("--tab",
|
||
|
action="store", type="string",
|
||
|
dest="tab", default="pattern",
|
||
|
help="Active tab")
|
||
|
self.OptionParser.add_option("--patternFunction",
|
||
|
action="store", type="string",
|
||
|
dest="patternFunction", default="sin",
|
||
|
help="Function of the pattern")
|
||
|
self.OptionParser.add_option("--frequency",
|
||
|
action="store", type="int",
|
||
|
dest="frequency", default=10,
|
||
|
help="Frequency of the function")
|
||
|
self.OptionParser.add_option("--amplitude",
|
||
|
action="store", type="int",
|
||
|
dest="amplitude", default=100,
|
||
|
help="Amplitude of the function")
|
||
|
self.OptionParser.add_option("--phaseOffset",
|
||
|
action="store", type="int",
|
||
|
dest="phaseOffset", default=0,
|
||
|
help="Phase offset of the function")
|
||
|
self.OptionParser.add_option("--offset",
|
||
|
action="store", type="int",
|
||
|
dest="offset", default=0,
|
||
|
help="Offset of the function")
|
||
|
self.OptionParser.add_option("--phaseCoverage",
|
||
|
action="store", type="int",
|
||
|
dest="phaseCoverage", default=100,
|
||
|
help="Phase coverage of the function")
|
||
|
self.OptionParser.add_option("--series",
|
||
|
action="store", type="int",
|
||
|
dest="series", default=1,
|
||
|
help="Series of the function")
|
||
|
self.OptionParser.add_option("--nodes",
|
||
|
action="store", type="int",
|
||
|
dest="nodes", default=20,
|
||
|
help="Count of nodes")
|
||
|
self.OptionParser.add_option("--remove",
|
||
|
action="store", type="inkbool",
|
||
|
dest="remove", default=False,
|
||
|
help="If True, control objects will be removed")
|
||
|
self.OptionParser.add_option("--strokeColor",
|
||
|
action="store", type="string",
|
||
|
dest="strokeColor", default=255,
|
||
|
help="The line's color")
|
||
|
self.OptionParser.add_option("--amplitude1",
|
||
|
action="store", type="float",
|
||
|
dest="amplitude1", default=0.0,
|
||
|
help="Amplitude of first harmonic")
|
||
|
self.OptionParser.add_option("--phase1",
|
||
|
action="store", type="int",
|
||
|
dest="phase1", default=0,
|
||
|
help="Phase offset of first harmonic")
|
||
|
self.OptionParser.add_option("--amplitude2",
|
||
|
action="store", type="float",
|
||
|
dest="amplitude2", default=0.0,
|
||
|
help="Amplitude of second harmonic")
|
||
|
self.OptionParser.add_option("--phase2",
|
||
|
action="store", type="int",
|
||
|
dest="phase2", default=0,
|
||
|
help="Phase offset of second harmonic")
|
||
|
self.OptionParser.add_option("--amplitude3",
|
||
|
action="store", type="float",
|
||
|
dest="amplitude3", default=0.0,
|
||
|
help="Amplitude of third harmonic")
|
||
|
self.OptionParser.add_option("--phase3",
|
||
|
action="store", type="int",
|
||
|
dest="phase3", default=0,
|
||
|
help="Phase offset of third harmonic")
|
||
|
self.OptionParser.add_option("--amplitude4",
|
||
|
action="store", type="float",
|
||
|
dest="amplitude4", default=0.0,
|
||
|
help="Amplitude of fourth harmonic")
|
||
|
self.OptionParser.add_option("--phase4",
|
||
|
action="store", type="int",
|
||
|
dest="phase4", default=0,
|
||
|
help="Phase offset of fourth harmonic")
|
||
|
self.OptionParser.add_option("--amplitude5",
|
||
|
action="store", type="float",
|
||
|
dest="amplitude5", default=0.0,
|
||
|
help="Amplitude of fifth harmonic")
|
||
|
self.OptionParser.add_option("--phase5",
|
||
|
action="store", type="int",
|
||
|
dest="phase5", default=0,
|
||
|
help="Phase offset of fifth harmonic")
|
||
|
|
||
|
def prepareSelectionList(self):
|
||
|
self.envelopes = self.selected
|
||
|
self.expandGroupsUnlinkClones(self.envelopes, True, False)
|
||
|
self.objectsToPaths(self.envelopes)
|
||
|
|
||
|
def getFunction(self, func, funcOffs):
|
||
|
res = ''
|
||
|
|
||
|
presetAmp1 = presetAmp2 = presetAmp3 = presetAmp4 = presetAmp5 = 0.0
|
||
|
presetPhOf1 = presetPhOf2 = presetPhOf3 = presetPhOf4 = presetPhOf5 = presetOffs = 0
|
||
|
funcOffs *= self.options.phaseCoverage / 100.0
|
||
|
|
||
|
if (func == 'sin' or func == 'cos'):
|
||
|
return func + '(x + (' + str((self.options.phaseOffset + funcOffs) / 100.0 * 2 * pi) + '))'
|
||
|
|
||
|
if func == 'env1':
|
||
|
presetAmp1 = presetAmp3 = 0.495
|
||
|
elif func == 'env2':
|
||
|
presetAmp1 = presetAmp3 = 0.65
|
||
|
presetPhOf1 = presetPhOf3 = 25
|
||
|
elif func == 'env3':
|
||
|
presetAmp1 = 0.75
|
||
|
presetPhOf1 = 25
|
||
|
presetAmp3 = 0.24
|
||
|
presetPhOf3 = -25
|
||
|
elif func == 'env4':
|
||
|
presetAmp1 = 1.105
|
||
|
presetAmp3 = 0.27625
|
||
|
presetPhOf3 = 50
|
||
|
elif func == 'env5':
|
||
|
presetAmp1 = 0.37464375
|
||
|
presetPhOf1 = 25
|
||
|
presetAmp2 = 0.5655
|
||
|
presetAmp3 = 0.37464375
|
||
|
presetPhOf3 = -25
|
||
|
elif func == 'env6':
|
||
|
presetAmp1 = 0.413725
|
||
|
presetPhOf1 = 25
|
||
|
presetAmp2 = 0.45695
|
||
|
presetPhOf2 = 50
|
||
|
presetAmp3 = 0.494
|
||
|
presetPhOf3 = -25
|
||
|
elif func == 'env7':
|
||
|
presetAmp1 = 0.624
|
||
|
presetPhOf1 = 25
|
||
|
presetAmp2 = 0.312
|
||
|
presetAmp3 = 0.624
|
||
|
presetPhOf3 = 25
|
||
|
elif func == 'env8':
|
||
|
presetAmp1 = 0.65
|
||
|
presetPhOf1 = 50
|
||
|
presetAmp2 = 0.585
|
||
|
presetAmp3 = 0.13
|
||
|
elif func == 'env9':
|
||
|
presetAmp1 = 0.07605
|
||
|
presetPhOf1 = 25
|
||
|
presetAmp2 = 0.33345
|
||
|
presetPhOf2 = 50
|
||
|
presetAmp3 = 0.468
|
||
|
presetPhOf3 = -25
|
||
|
presetAmp4 = 0.32175
|
||
|
elif func == 'env10':
|
||
|
presetAmp1 = 0.3575
|
||
|
presetPhOf1 = -25
|
||
|
presetAmp2 = 0.3575
|
||
|
presetAmp3 = 0.3575
|
||
|
presetPhOf3 = 25
|
||
|
presetAmp4 = 0.3575
|
||
|
presetPhOf4 = 50
|
||
|
elif func == 'env11':
|
||
|
presetAmp1 = 0.65
|
||
|
presetPhOf1 = 25
|
||
|
presetAmp2 = 0.13
|
||
|
presetPhOf2 = 50
|
||
|
presetAmp3 = 0.26
|
||
|
presetPhOf3 = 25
|
||
|
presetAmp4 = 0.39
|
||
|
elif func == 'env12':
|
||
|
presetAmp1 = 0.5525
|
||
|
presetPhOf1 = -25
|
||
|
presetAmp2 = 0.0414375
|
||
|
presetPhOf2 = 50
|
||
|
presetAmp3 = 0.15884375
|
||
|
presetPhOf3 = 25
|
||
|
presetAmp4 = 0.0966875
|
||
|
presetAmp5 = 0.28315625
|
||
|
presetPhOf5 = -25
|
||
|
|
||
|
harm1 = '(' + str(presetAmp1 + self.options.amplitude1) + ') * cos(1 * (x + (' + str((self.options.phaseOffset + funcOffs) / 100.0 * 2 * pi) + ')) - (' + str((presetPhOf1 + self.options.phase1) / 100.0 * 2 * pi) + '))'
|
||
|
harm2 = '(' + str(presetAmp2 + self.options.amplitude2) + ') * cos(2 * (x + (' + str((self.options.phaseOffset + funcOffs) / 100.0 * 2 * pi) + ')) - (' + str((presetPhOf2 + self.options.phase2) / 100.0 * 2 * pi) + '))'
|
||
|
harm3 = '(' + str(presetAmp3 + self.options.amplitude3) + ') * cos(3 * (x + (' + str((self.options.phaseOffset + funcOffs) / 100.0 * 2 * pi) + ')) - (' + str((presetPhOf3 + self.options.phase3) / 100.0 * 2 * pi) + '))'
|
||
|
harm4 = '(' + str(presetAmp4 + self.options.amplitude4) + ') * cos(4 * (x + (' + str((self.options.phaseOffset + funcOffs) / 100.0 * 2 * pi) + ')) - (' + str((presetPhOf4 + self.options.phase4) / 100.0 * 2 * pi) + '))'
|
||
|
harm5 = '(' + str(presetAmp5 + self.options.amplitude5) + ') * cos(5 * (x + (' + str((self.options.phaseOffset + funcOffs) / 100.0 * 2 * pi) + ')) - (' + str((presetPhOf5 + self.options.phase5) / 100.0 * 2 * pi) + '))'
|
||
|
|
||
|
res = harm1 + ' + ' + harm2 + ' + ' + harm3 + ' + ' + harm4 + ' + ' + harm5
|
||
|
|
||
|
return res
|
||
|
|
||
|
def lengthToTime(self, l):
|
||
|
'''
|
||
|
Recieves an arc length l, and returns the index of the segment in self.skelComp
|
||
|
containing the corresponding point, together with the position of the point on this segment.
|
||
|
|
||
|
If the deformer is closed, do computations modulo the total length.
|
||
|
'''
|
||
|
if self.skelCompIsClosed:
|
||
|
l = l % sum(self.lengths)
|
||
|
|
||
|
if l <= 0:
|
||
|
return 0, l / self.lengths[0]
|
||
|
|
||
|
i = 0
|
||
|
|
||
|
while (i < len(self.lengths)) and (self.lengths[i] <= l):
|
||
|
l -= self.lengths[i]
|
||
|
i += 1
|
||
|
|
||
|
t = l / self.lengths[min(i, len(self.lengths) - 1)]
|
||
|
|
||
|
return (i, t)
|
||
|
|
||
|
def applyDiffeo(self, bpt, vects=()):
|
||
|
'''
|
||
|
The kernel of this stuff:
|
||
|
bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt.
|
||
|
'''
|
||
|
s = bpt[0] - self.skelComp[0][0]
|
||
|
i, t = self.lengthToTime(s)
|
||
|
|
||
|
if i == len(self.skelComp) - 1:
|
||
|
x, y = bezmisc.tpoint(self.skelComp[i - 1], self.skelComp[i], t + 1)
|
||
|
dx = (self.skelComp[i][0] - self.skelComp[i - 1][0]) / self.lengths[-1]
|
||
|
dy = (self.skelComp[i][1] - self.skelComp[i - 1][1]) / self.lengths[-1]
|
||
|
else:
|
||
|
x, y = bezmisc.tpoint(self.skelComp[i], self.skelComp[i + 1], t)
|
||
|
dx = (self.skelComp[i + 1][0] - self.skelComp[i][0]) / self.lengths[i]
|
||
|
dy = (self.skelComp[i + 1][1] - self.skelComp[i][1]) / self.lengths[i]
|
||
|
|
||
|
vx = 0
|
||
|
vy = bpt[1] - self.skelComp[0][1]
|
||
|
bpt[0] = x + vx * dx - vy * dy
|
||
|
bpt[1] = y + vx * dy + vy * dx
|
||
|
|
||
|
for v in vects:
|
||
|
vx = v[0] - self.skelComp[0][0] - s
|
||
|
vy = v[1] - self.skelComp[0][1]
|
||
|
v[0] = x + vx * dx - vy * dy
|
||
|
v[1] = y + vx * dy + vy * dx
|
||
|
|
||
|
def effect(self):
|
||
|
if len(self.options.ids) != 2:
|
||
|
inkex.errormsg(_("This extension requires two selected paths."))
|
||
|
return
|
||
|
|
||
|
self.prepareSelectionList()
|
||
|
envs = self.envelopes.values()
|
||
|
|
||
|
s = envs[0].get('style')
|
||
|
parent = envs[0].getparent()
|
||
|
|
||
|
fstEnvComps, fstEnvLengths, sndEnvComps, sndEnvLengths, fstEnvBbox, sndEnvBbox, isCorrect = linearizeEnvelopes(envs)
|
||
|
|
||
|
if not isCorrect:
|
||
|
inkex.errormsg(_("Selected paths are not compatible."))
|
||
|
return
|
||
|
|
||
|
areNested = isSkeletonClosed(fstEnvComps) and isSkeletonClosed(sndEnvComps)
|
||
|
|
||
|
self.skelComp = None
|
||
|
self.lengths = None
|
||
|
|
||
|
countOfSkelPaths = 1
|
||
|
distBetweenFirstPts = 1
|
||
|
funcSeries = self.options.series
|
||
|
|
||
|
isLine = True if self.options.patternFunction == 'line' else False
|
||
|
|
||
|
if (isLine and self.options.offset > 0):
|
||
|
distBetweenFirstPts = getDistBetweenFirstPts(fstEnvComps, sndEnvComps, fstEnvBbox, sndEnvBbox, areNested)
|
||
|
countOfSkelPaths = int(distBetweenFirstPts / self.options.offset)
|
||
|
funcSeries = 1
|
||
|
|
||
|
for cnt in range(0, countOfSkelPaths):
|
||
|
curOffset = (cnt + 1) * self.options.offset / distBetweenFirstPts
|
||
|
|
||
|
if areNested:
|
||
|
self.skelComp, self.lengths = getClosedLinearizedSkeletonPath(fstEnvComps, sndEnvComps, curOffset, isLine)
|
||
|
elif (fstEnvBbox[0] == sndEnvBbox[0] and fstEnvBbox[1] == sndEnvBbox[1]):
|
||
|
self.skelComp, self.lengths = getHorizontalLinearizedSkeletonPath(fstEnvComps, sndEnvComps, curOffset, isLine)
|
||
|
elif (fstEnvBbox[2] == sndEnvBbox[2] and fstEnvBbox[3] == sndEnvBbox[3]):
|
||
|
self.skelComp, self.lengths = getVerticalLinearizedSkeletonPath(fstEnvComps, sndEnvComps, curOffset, isLine)
|
||
|
|
||
|
self.skelCompIsClosed = isSkeletonClosed(self.skelComp)
|
||
|
length = sum(self.lengths)
|
||
|
patternWidth = length / self.options.frequency
|
||
|
funcOffsetStep = 100 / funcSeries
|
||
|
|
||
|
resPath = ''
|
||
|
|
||
|
pattern = inkex.etree.Element(inkex.addNS('path','svg'))
|
||
|
|
||
|
self.options.strokeHexColor, self.strokeOpacity = getColorAndOpacity(self.options.strokeColor)
|
||
|
|
||
|
if s:
|
||
|
pattern.set('style', setColorAndOpacity(s, self.options.strokeHexColor, self.strokeOpacity))
|
||
|
|
||
|
for j in range(funcSeries):
|
||
|
selectedFunction = self.getFunction(self.options.patternFunction, j * funcOffsetStep)
|
||
|
|
||
|
pattern.set('d', simplepath.formatPath(drawfunction(self.options.nodes, patternWidth, selectedFunction)))
|
||
|
|
||
|
# Add path into SVG structure
|
||
|
parent.append(pattern)
|
||
|
|
||
|
# Compute bounding box
|
||
|
bbox = simpletransform.computeBBox([pattern])
|
||
|
|
||
|
width = bbox[1] - bbox[0]
|
||
|
height = bbox[3] - bbox[2]
|
||
|
dx = width
|
||
|
|
||
|
if dx < 0.01:
|
||
|
exit(_("The total length of the pattern is too small."))
|
||
|
|
||
|
patternPath = cubicsuperpath.parsePath(pattern.get('d'))
|
||
|
curPath = deepcopy(patternPath)
|
||
|
|
||
|
xoffset = self.skelComp[0][0] - bbox[0]
|
||
|
yoffset = self.skelComp[0][1] - (bbox[2] + bbox[3]) / 2
|
||
|
|
||
|
patternCopies = max(1, int(round(length / dx)))
|
||
|
width = dx * patternCopies
|
||
|
|
||
|
newPath = []
|
||
|
|
||
|
# Repeat pattern to cover whole skeleton
|
||
|
for subPath in curPath:
|
||
|
for i in range(0, patternCopies, 1):
|
||
|
newPath.append(deepcopy(subPath))
|
||
|
offset(subPath, dx, 0)
|
||
|
|
||
|
# Offset pattern to the first node of the skeleton
|
||
|
for subPath in newPath:
|
||
|
offset(subPath, xoffset, yoffset)
|
||
|
|
||
|
curPath = deepcopy(newPath)
|
||
|
|
||
|
# Stretch pattern to whole skeleton
|
||
|
for subPath in curPath:
|
||
|
stretch(subPath, length / width, 1, self.skelComp[0])
|
||
|
|
||
|
for subPath in curPath:
|
||
|
for ctlpt in subPath:
|
||
|
self.applyDiffeo(ctlpt[1], (ctlpt[0], ctlpt[2]))
|
||
|
|
||
|
# Check if there is a need to close path manually
|
||
|
if self.skelCompIsClosed:
|
||
|
firstPtX = round(curPath[0][0][1][0], 8)
|
||
|
firstPtY = round(curPath[0][0][1][1], 8)
|
||
|
finalPtX = round(curPath[-1][-1][1][0], 8)
|
||
|
finalPtY = round(curPath[-1][-1][1][1], 8)
|
||
|
|
||
|
if (firstPtX != finalPtX or firstPtY != finalPtY):
|
||
|
curPath[-1].append(curPath[0][0])
|
||
|
|
||
|
curPathComps, curPathLengths = linearize(modifySkeletonPath(curPath))
|
||
|
|
||
|
if not isLine:
|
||
|
curPathComps = stretchComps(self.skelComp, curPathComps, fstEnvComps, sndEnvComps, fstEnvBbox, sndEnvBbox, areNested, height / 2, self.options.amplitude / 100.0, self.options.offset)
|
||
|
|
||
|
resPath += compsToSVGd(curPathComps)
|
||
|
|
||
|
pattern.set('d', resPath)
|
||
|
|
||
|
if self.options.remove:
|
||
|
parent.remove(envs[0])
|
||
|
parent.remove(envs[1])
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
e = GuillochePattern()
|
||
|
e.affect()
|
||
|
|
||
|
|
||
|
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
|