210 lines
6.8 KiB
Python
210 lines
6.8 KiB
Python
|
import sys
|
||
|
import inkex
|
||
|
from inkex import Path
|
||
|
import numpy
|
||
|
from shaperrec import manipulation
|
||
|
from lxml import etree
|
||
|
|
||
|
|
||
|
|
||
|
# *************************************************************
|
||
|
# debugging
|
||
|
def void(*l):
|
||
|
pass
|
||
|
def debug_on(*l):
|
||
|
sys.stderr.write(' '.join(str(i) for i in l) +'\n')
|
||
|
debug = void
|
||
|
#debug = debug_on
|
||
|
|
||
|
errwrite = void
|
||
|
|
||
|
|
||
|
# miscellaneous helper functions to sort
|
||
|
|
||
|
# merge consecutive segments with close angle
|
||
|
|
||
|
def mergeConsecutiveCloseAngles( segList , mangle =0.25 , q=0.5):
|
||
|
|
||
|
def toMerge(seg):
|
||
|
l=[seg]
|
||
|
setattr(seg, 'merged', True)
|
||
|
if hasattr(seg, "__next__") and seg.next.isSegment() :
|
||
|
debug('merging segs ', seg.angle, ' with : ', seg.next.point1, seg.next.pointN, ' ang=', seg.next.angle)
|
||
|
if geometric.deltaAngleAbs( seg.angle, seg.next.angle) < mangle:
|
||
|
l += toMerge(seg.next)
|
||
|
return l
|
||
|
|
||
|
updatedSegs = []
|
||
|
for i, seg in enumerate(segList[:-1]):
|
||
|
if not seg.isSegment() :
|
||
|
updatedSegs.append(seg)
|
||
|
continue
|
||
|
if hasattr(seg, 'merged'):
|
||
|
continue
|
||
|
debug(i, ' inspect merge : ', seg.point1, '-', seg.pointN, seg.angle, ' q=', seg.quality())
|
||
|
mList = toMerge(seg)
|
||
|
debug(' --> tomerge ', len(mList))
|
||
|
if len(mList)<2:
|
||
|
delattr(seg, 'merged')
|
||
|
updatedSegs.append(seg)
|
||
|
continue
|
||
|
points= numpy.concatenate( [p.points for p in mList] )
|
||
|
newseg = fitSingleSegment(points)
|
||
|
if newseg.quality()>q:
|
||
|
delattr(seg, 'merged')
|
||
|
updatedSegs.append(seg)
|
||
|
continue
|
||
|
for p in mList:
|
||
|
setattr(seg, 'merged', True)
|
||
|
newseg.sourcepoints = seg.sourcepoints
|
||
|
debug(' --> post merge qual = ', newseg.quality(), seg.pointN, ' --> ', newseg.pointN, newseg.angle)
|
||
|
newseg.prev = mList[0].prev
|
||
|
newseg.next = mList[-1].__next__
|
||
|
updatedSegs.append(newseg)
|
||
|
if not hasattr(segList[-1], 'merged') : updatedSegs.append( segList[-1])
|
||
|
return updatedSegs
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
def parametersFromPointAngle(point, angle):
|
||
|
unitv = numpy.array([ numpy.cos(angle), numpy.sin(angle) ])
|
||
|
ortangle = angle+numpy.pi/2
|
||
|
normal = numpy.array([ numpy.cos(ortangle), numpy.sin(ortangle) ])
|
||
|
genOffset = -normal.dot(point)
|
||
|
a, b = normal
|
||
|
return a, b, genOffset
|
||
|
|
||
|
|
||
|
|
||
|
def addPath(newList, refnode):
|
||
|
"""Add a node in the xml structure corresponding to the content of newList
|
||
|
newList : list of Segment or Path
|
||
|
refnode : xml node used as a reference, new point will be inserted a same level"""
|
||
|
ele = etree.Element('{http://www.w3.org/2000/svg}path')
|
||
|
errwrite("newList = " + str(newList) + "\n")
|
||
|
ele.set('d', str(Path(newList)))
|
||
|
refnode.xpath('..')[0].append(ele)
|
||
|
return ele
|
||
|
|
||
|
def reformatList( listOfPaths):
|
||
|
""" Returns a SVG paths list (same format as simplepath.parsePath) from a list of Path objects
|
||
|
- Segments in paths are added in the new list
|
||
|
- simple Path are retrieved from the original refSVGPathList and put in the new list (thus preserving original bezier curves)
|
||
|
"""
|
||
|
newList = []
|
||
|
first = True
|
||
|
for seg in listOfPaths:
|
||
|
newList += seg.asSVGCommand(first)
|
||
|
first = False
|
||
|
return newList
|
||
|
|
||
|
|
||
|
def clusterValues( values, relS=0.1 , refScaleAbs='range' ):
|
||
|
"""form clusters of similar quantities from input 'values'.
|
||
|
Clustered values are not necessarily contiguous in the input array.
|
||
|
Clusters size (that is max-min) is < relS*cluster_average """
|
||
|
if len(values)==0:
|
||
|
return []
|
||
|
if len(values.shape)==1:
|
||
|
sortedV = numpy.stack([ values, numpy.arange(len(values))], 1)
|
||
|
else:
|
||
|
# Assume value.shape = (N,2) and index are ok
|
||
|
sortedV = values
|
||
|
sortedV = sortedV[ numpy.argsort(sortedV[:, 0]) ]
|
||
|
|
||
|
sortedVV = sortedV[:, 0]
|
||
|
refScale = sortedVV[-1]-sortedVV[0]
|
||
|
#sortedVV += 2*min(sortedVV)) # shift to avoid numerical issues around 0
|
||
|
|
||
|
#print sortedVV
|
||
|
class Cluster:
|
||
|
def __init__(self, delta, sum, indices):
|
||
|
self.delta = delta
|
||
|
self.sum = sum
|
||
|
self.N=len(indices)
|
||
|
self.indices = indices
|
||
|
def size(self):
|
||
|
return self.delta/refScale
|
||
|
|
||
|
def combine(self, c):
|
||
|
#print ' combine ', self.indices[0], c.indices[-1], ' -> ', sortedVV[c.indices[-1]] - sortedVV[self.indices[0]]
|
||
|
newC = Cluster(sortedVV[c.indices[-1]] - sortedVV[self.indices[0]],
|
||
|
self.sum+c.sum,
|
||
|
self.indices+c.indices)
|
||
|
return newC
|
||
|
|
||
|
def originIndices(self):
|
||
|
return tuple(int(sortedV[i][1]) for i in self.indices)
|
||
|
|
||
|
def size_local(self):
|
||
|
return self.delta / sum( sortedVV[i] for i in self.indices) *len(self.indices)
|
||
|
def size_range(self):
|
||
|
return self.delta/refScale
|
||
|
def size_abs(self):
|
||
|
return self.delta
|
||
|
|
||
|
if refScaleAbs=='range':
|
||
|
Cluster.size = size_range
|
||
|
elif refScaleAbs=='local':
|
||
|
Cluster.size = size_local
|
||
|
elif refScaleAbs=='abs':
|
||
|
Cluster.size = size_abs
|
||
|
|
||
|
class ClusterPair:
|
||
|
next=None
|
||
|
prev=None
|
||
|
def __init__(self, c1, c2 ):
|
||
|
self.c1=c1
|
||
|
self.c2=c2
|
||
|
self.refresh()
|
||
|
def refresh(self):
|
||
|
self.potentialC =self.c1.combine(self.c2)
|
||
|
self.size = self.potentialC.size()
|
||
|
def setC1(self, c1):
|
||
|
self.c1=c1
|
||
|
self.refresh()
|
||
|
def setC2(self, c2):
|
||
|
self.c2=c2
|
||
|
self.refresh()
|
||
|
|
||
|
#ave = 0.5*(sortedVV[1:,0]+sortedV[:-1,0])
|
||
|
#deltaR = (sortedV[1:,0]-sortedV[:-1,0])/ave
|
||
|
|
||
|
cList = [Cluster(0, v, (i,)) for (i, v) in enumerate(sortedVV) ]
|
||
|
cpList = [ ClusterPair( c, cList[i+1] ) for (i, c) in enumerate(cList[:-1]) ]
|
||
|
manipulation.resetPrevNextSegment( cpList )
|
||
|
|
||
|
#print cpList
|
||
|
def reduceCL( cList ):
|
||
|
if len(cList)<=1:
|
||
|
return cList
|
||
|
cp = min(cList, key=lambda cp:cp.size)
|
||
|
#print '==', cp.size , relS, cp.c1.indices , cp.c2.indices, cp.potentialC.indices
|
||
|
|
||
|
while cp.size < relS:
|
||
|
if hasattr(cp, "__next__"):
|
||
|
cp.next.setC1(cp.potentialC)
|
||
|
cp.next.prev = cp.prev
|
||
|
if cp.prev:
|
||
|
cp.prev.setC2(cp.potentialC)
|
||
|
cp.prev.next = cp.__next__ if hasattr(cp, "__next__") else None
|
||
|
cList.remove(cp)
|
||
|
if len(cList)<2:
|
||
|
break
|
||
|
cp = min(cList, key=lambda cp:cp.size)
|
||
|
#print ' -----> ', [ (cp.c1.indices , cp.c2.indices) for cp in cList]
|
||
|
return cList
|
||
|
|
||
|
cpList = reduceCL(cpList)
|
||
|
if len(cpList)==1:
|
||
|
cp = cpList[0]
|
||
|
if cp.potentialC.size()<relS:
|
||
|
return [ cp.potentialC.originIndices() ]
|
||
|
#print cpList
|
||
|
if cpList==[]:
|
||
|
return []
|
||
|
finalCL = [ cp.c1.originIndices() for cp in cpList ]+[ cpList[-1].c2.originIndices() ]
|
||
|
return finalCL
|
||
|
|