index 5edbf026ddc34366f4b706d83c199194dfd72ae5..ba8d8d7f40f6ec8b2e1494c5f4cc871b2704bc09 100755 (executable)
uuconv = {'in':90.0, 'pt':1.25, 'px':1, 'mm':3.5433070866, 'cm':35.433070866, 'pc':15.0}
def numsegs(csp):
- return sum([len(p)-1 for p in csp])
+ return sum([len(p)-1 for p in csp])
def interpcoord(v1,v2,p):
- return v1+((v2-v1)*p)
+ return v1+((v2-v1)*p)
def interppoints(p1,p2,p):
- return [interpcoord(p1[0],p2[0],p),interpcoord(p1[1],p2[1],p)]
+ return [interpcoord(p1[0],p2[0],p),interpcoord(p1[1],p2[1],p)]
def pointdistance((x1,y1),(x2,y2)):
- return math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2))
+ return math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2))
def bezlenapprx(sp1, sp2):
- return pointdistance(sp1[1], sp1[2]) + pointdistance(sp1[2], sp2[0]) + pointdistance(sp2[0], sp2[1])
+ return pointdistance(sp1[1], sp1[2]) + pointdistance(sp1[2], sp2[0]) + pointdistance(sp2[0], sp2[1])
def tpoint((x1,y1), (x2,y2), t = 0.5):
- return [x1+t*(x2-x1),y1+t*(y2-y1)]
+ return [x1+t*(x2-x1),y1+t*(y2-y1)]
def cspbezsplit(sp1, sp2, t = 0.5):
- m1=tpoint(sp1[1],sp1[2],t)
- m2=tpoint(sp1[2],sp2[0],t)
- m3=tpoint(sp2[0],sp2[1],t)
- m4=tpoint(m1,m2,t)
- m5=tpoint(m2,m3,t)
- m=tpoint(m4,m5,t)
- return [[sp1[0][:],sp1[1][:],m1], [m4,m,m5], [m3,sp2[1][:],sp2[2][:]]]
+ m1=tpoint(sp1[1],sp1[2],t)
+ m2=tpoint(sp1[2],sp2[0],t)
+ m3=tpoint(sp2[0],sp2[1],t)
+ m4=tpoint(m1,m2,t)
+ m5=tpoint(m2,m3,t)
+ m=tpoint(m4,m5,t)
+ return [[sp1[0][:],sp1[1][:],m1], [m4,m,m5], [m3,sp2[1][:],sp2[2][:]]]
def cspbezsplitatlength(sp1, sp2, l = 0.5, tolerance = 0.001):
- bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
- t = bezmisc.beziertatlength(bez, l, tolerance)
- return cspbezsplit(sp1, sp2, t)
+ bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
+ t = bezmisc.beziertatlength(bez, l, tolerance)
+ return cspbezsplit(sp1, sp2, t)
def cspseglength(sp1,sp2, tolerance = 0.001):
- bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
- return bezmisc.bezierlength(bez, tolerance)
+ bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
+ return bezmisc.bezierlength(bez, tolerance)
def csplength(csp):
- total = 0
- lengths = []
- for sp in csp:
- lengths.append([])
- for i in xrange(1,len(sp)):
- l = cspseglength(sp[i-1],sp[i])
- lengths[-1].append(l)
- total += l
- return lengths, total
+ total = 0
+ lengths = []
+ for sp in csp:
+ lengths.append([])
+ for i in xrange(1,len(sp)):
+ l = cspseglength(sp[i-1],sp[i])
+ lengths[-1].append(l)
+ total += l
+ return lengths, total
def styleunittouu(string):
- unit = re.compile('(%s)$' % '|'.join(uuconv.keys()))
- param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
+ unit = re.compile('(%s)$' % '|'.join(uuconv.keys()))
+ param = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
- p = param.match(string)
- u = unit.search(string)
- if p:
- retval = float(p.string[p.start():p.end()])
- else:
- retval = 0.0
- if u:
- try:
- return retval * uuconv[u.string[u.start():u.end()]]
- except KeyError:
- pass
- return retval
-
+ p = param.match(string)
+ u = unit.search(string)
+ if p:
+ retval = float(p.string[p.start():p.end()])
+ else:
+ retval = 0.0
+ if u:
+ try:
+ return retval * uuconv[u.string[u.start():u.end()]]
+ except KeyError:
+ pass
+ return retval
+
def tweenstylefloat(property, start, end, time):
- sp = float(start[property])
- ep = float(end[property])
- return str(sp + (time * (ep - sp)))
+ sp = float(start[property])
+ ep = float(end[property])
+ return str(sp + (time * (ep - sp)))
def tweenstyleunit(property, start, end, time):
- sp = styleunittouu(start[property])
- ep = styleunittouu(end[property])
- return str(sp + (time * (ep - sp)))
+ sp = styleunittouu(start[property])
+ ep = styleunittouu(end[property])
+ return str(sp + (time * (ep - sp)))
def tweenstylecolor(property, start, end, time):
- sr,sg,sb = parsecolor(start[property])
- er,eg,eb = parsecolor(end[property])
- return '#%s%s%s' % (tweenhex(time,sr,er),tweenhex(time,sg,eg),tweenhex(time,sb,eb))
+ sr,sg,sb = parsecolor(start[property])
+ er,eg,eb = parsecolor(end[property])
+ return '#%s%s%s' % (tweenhex(time,sr,er),tweenhex(time,sg,eg),tweenhex(time,sb,eb))
def tweenhex(time,s,e):
- s = float(int(s,16))
- e = float(int(e,16))
- retval = hex(int(math.floor(s + (time * (e - s)))))[2:]
- if len(retval)==1:
- retval = '0%s' % retval
- return retval
+ s = float(int(s,16))
+ e = float(int(e,16))
+ retval = hex(int(math.floor(s + (time * (e - s)))))[2:]
+ if len(retval)==1:
+ retval = '0%s' % retval
+ return retval
def parsecolor(c):
- r,g,b = '0','0','0'
- if c[:1]=='#':
- if len(c)==4:
- r,g,b = c[1:2],c[2:3],c[3:4]
- elif len(c)==7:
- r,g,b = c[1:3],c[3:5],c[5:7]
- return r,g,b
+ r,g,b = '0','0','0'
+ if c[:1]=='#':
+ if len(c)==4:
+ r,g,b = c[1:2],c[2:3],c[3:4]
+ elif len(c)==7:
+ r,g,b = c[1:3],c[3:5],c[5:7]
+ return r,g,b
class Interp(inkex.Effect):
- def __init__(self):
- inkex.Effect.__init__(self)
- self.OptionParser.add_option("-e", "--exponent",
- action="store", type="float",
- dest="exponent", default=0.0,
- help="values other than zero give non linear interpolation")
- self.OptionParser.add_option("-s", "--steps",
- action="store", type="int",
- dest="steps", default=5,
- help="number of interpolation steps")
- self.OptionParser.add_option("-m", "--method",
- action="store", type="int",
- dest="method", default=2,
- help="method of interpolation")
- self.OptionParser.add_option("-d", "--dup",
- action="store", type="inkbool",
- dest="dup", default=True,
- help="duplicate endpaths")
- self.OptionParser.add_option("--style",
- action="store", type="inkbool",
- dest="style", default=True,
- help="try interpolation of some style properties")
- def effect(self):
- exponent = self.options.exponent
- if exponent>= 0:
- exponent = 1.0 + exponent
- else:
- exponent = 1.0/(1.0 - exponent)
- steps = [1.0/(self.options.steps + 1.0)]
- for i in range(self.options.steps - 1):
- steps.append(steps[0] + steps[-1])
- steps = [step**exponent for step in steps]
-
- paths = {}
- styles = {}
- for id in self.options.ids:
- node = self.selected[id]
- if node.tagName =='path':
- paths[id] = cubicsuperpath.parsePath(node.attributes.getNamedItem('d').value)
- styles[id] = simplestyle.parseStyle(node.attributes.getNamedItem('style').value)
- else:
- self.options.ids.remove(id)
+ def __init__(self):
+ inkex.Effect.__init__(self)
+ self.OptionParser.add_option("-e", "--exponent",
+ action="store", type="float",
+ dest="exponent", default=0.0,
+ help="values other than zero give non linear interpolation")
+ self.OptionParser.add_option("-s", "--steps",
+ action="store", type="int",
+ dest="steps", default=5,
+ help="number of interpolation steps")
+ self.OptionParser.add_option("-m", "--method",
+ action="store", type="int",
+ dest="method", default=2,
+ help="method of interpolation")
+ self.OptionParser.add_option("-d", "--dup",
+ action="store", type="inkbool",
+ dest="dup", default=True,
+ help="duplicate endpaths")
+ self.OptionParser.add_option("--style",
+ action="store", type="inkbool",
+ dest="style", default=True,
+ help="try interpolation of some style properties")
+ def effect(self):
+ exponent = self.options.exponent
+ if exponent>= 0:
+ exponent = 1.0 + exponent
+ else:
+ exponent = 1.0/(1.0 - exponent)
+ steps = [1.0/(self.options.steps + 1.0)]
+ for i in range(self.options.steps - 1):
+ steps.append(steps[0] + steps[-1])
+ steps = [step**exponent for step in steps]
+
+ paths = {}
+ styles = {}
+ for id in self.options.ids:
+ node = self.selected[id]
+ if node.tagName =='path':
+ paths[id] = cubicsuperpath.parsePath(node.attributes.getNamedItem('d').value)
+ styles[id] = simplestyle.parseStyle(node.attributes.getNamedItem('style').value)
+ else:
+ self.options.ids.remove(id)
- for i in range(1,len(self.options.ids)):
- start = copy.deepcopy(paths[self.options.ids[i-1]])
- end = copy.deepcopy(paths[self.options.ids[i]])
- sst = copy.deepcopy(styles[self.options.ids[i-1]])
- est = copy.deepcopy(styles[self.options.ids[i]])
- basestyle = copy.deepcopy(sst)
+ for i in range(1,len(self.options.ids)):
+ start = copy.deepcopy(paths[self.options.ids[i-1]])
+ end = copy.deepcopy(paths[self.options.ids[i]])
+ sst = copy.deepcopy(styles[self.options.ids[i-1]])
+ est = copy.deepcopy(styles[self.options.ids[i]])
+ basestyle = copy.deepcopy(sst)
- #prepare for experimental style tweening
- if self.options.style:
- dostroke = True
- dofill = True
- styledefaults = {'opacity':'1.0', 'stroke-opacity':'1.0', 'fill-opacity':'1.0',
- 'stroke-width':'1.0', 'stroke':'none', 'fill':'none'}
- for key in styledefaults.keys():
- sst.setdefault(key,styledefaults[key])
- est.setdefault(key,styledefaults[key])
- isnotplain = lambda x: not (x=='none' or x[:1]=='#')
- if isnotplain(sst['stroke']) or isnotplain(est['stroke']) or (sst['stroke']=='none' and est['stroke']=='none'):
- dostroke = False
- if isnotplain(sst['fill']) or isnotplain(est['fill']) or (sst['fill']=='none' and est['fill']=='none'):
- dofill = False
- if dostroke:
- if sst['stroke']=='none':
- sst['stroke-width'] = '0.0'
- sst['stroke-opacity'] = '0.0'
- sst['stroke'] = est['stroke']
- elif est['stroke']=='none':
- est['stroke-width'] = '0.0'
- est['stroke-opacity'] = '0.0'
- est['stroke'] = sst['stroke']
- if dofill:
- if sst['fill']=='none':
- sst['fill-opacity'] = '0.0'
- sst['fill'] = est['fill']
- elif est['fill']=='none':
- est['fill-opacity'] = '0.0'
- est['fill'] = sst['fill']
+ #prepare for experimental style tweening
+ if self.options.style:
+ dostroke = True
+ dofill = True
+ styledefaults = {'opacity':'1.0', 'stroke-opacity':'1.0', 'fill-opacity':'1.0',
+ 'stroke-width':'1.0', 'stroke':'none', 'fill':'none'}
+ for key in styledefaults.keys():
+ sst.setdefault(key,styledefaults[key])
+ est.setdefault(key,styledefaults[key])
+ isnotplain = lambda x: not (x=='none' or x[:1]=='#')
+ if isnotplain(sst['stroke']) or isnotplain(est['stroke']) or (sst['stroke']=='none' and est['stroke']=='none'):
+ dostroke = False
+ if isnotplain(sst['fill']) or isnotplain(est['fill']) or (sst['fill']=='none' and est['fill']=='none'):
+ dofill = False
+ if dostroke:
+ if sst['stroke']=='none':
+ sst['stroke-width'] = '0.0'
+ sst['stroke-opacity'] = '0.0'
+ sst['stroke'] = est['stroke']
+ elif est['stroke']=='none':
+ est['stroke-width'] = '0.0'
+ est['stroke-opacity'] = '0.0'
+ est['stroke'] = sst['stroke']
+ if dofill:
+ if sst['fill']=='none':
+ sst['fill-opacity'] = '0.0'
+ sst['fill'] = est['fill']
+ elif est['fill']=='none':
+ est['fill-opacity'] = '0.0'
+ est['fill'] = sst['fill']
-
+
- if self.options.method == 2:
- #subdivide both paths into segments of relatively equal lengths
- slengths, stotal = csplength(start)
- elengths, etotal = csplength(end)
- lengths = {}
- t = 0
- for sp in slengths:
- for l in sp:
- t += l / stotal
- lengths.setdefault(t,0)
- lengths[t] += 1
- t = 0
- for sp in elengths:
- for l in sp:
- t += l / etotal
- lengths.setdefault(t,0)
- lengths[t] += -1
- sadd = [k for (k,v) in lengths.iteritems() if v < 0]
- sadd.sort()
- eadd = [k for (k,v) in lengths.iteritems() if v > 0]
- eadd.sort()
+ if self.options.method == 2:
+ #subdivide both paths into segments of relatively equal lengths
+ slengths, stotal = csplength(start)
+ elengths, etotal = csplength(end)
+ lengths = {}
+ t = 0
+ for sp in slengths:
+ for l in sp:
+ t += l / stotal
+ lengths.setdefault(t,0)
+ lengths[t] += 1
+ t = 0
+ for sp in elengths:
+ for l in sp:
+ t += l / etotal
+ lengths.setdefault(t,0)
+ lengths[t] += -1
+ sadd = [k for (k,v) in lengths.iteritems() if v < 0]
+ sadd.sort()
+ eadd = [k for (k,v) in lengths.iteritems() if v > 0]
+ eadd.sort()
- t = 0
- s = [[]]
- for sp in slengths:
- if not start[0]:
- s.append(start.pop(0))
- s[-1].append(start[0].pop(0))
- for l in sp:
- pt = t
- t += l / stotal
- if sadd and t > sadd[0]:
- while sadd and sadd[0] < t:
- nt = (sadd[0] - pt) / (t - pt)
- bezes = cspbezsplitatlength(s[-1][-1][:],start[0][0][:], nt)
- s[-1][-1:] = bezes[:2]
- start[0][0] = bezes[2]
- pt = sadd.pop(0)
- s[-1].append(start[0].pop(0))
- t = 0
- e = [[]]
- for sp in elengths:
- if not end[0]:
- e.append(end.pop(0))
- e[-1].append(end[0].pop(0))
- for l in sp:
- pt = t
- t += l / etotal
- if eadd and t > eadd[0]:
- while eadd and eadd[0] < t:
- nt = (eadd[0] - pt) / (t - pt)
- bezes = cspbezsplitatlength(e[-1][-1][:],end[0][0][:], nt)
- e[-1][-1:] = bezes[:2]
- end[0][0] = bezes[2]
- pt = eadd.pop(0)
- e[-1].append(end[0].pop(0))
- start = s[:]
- end = e[:]
- else:
- #which path has fewer segments?
- lengthdiff = numsegs(start) - numsegs(end)
- #swap shortest first
- if lengthdiff > 0:
- start, end = end, start
- #subdivide the shorter path
- for x in range(abs(lengthdiff)):
- maxlen = 0
- subpath = 0
- segment = 0
- for y in range(len(start)):
- for z in range(1, len(start[y])):
- leng = bezlenapprx(start[y][z-1], start[y][z])
- if leng > maxlen:
- maxlen = leng
- subpath = y
- segment = z
- sp1, sp2 = start[subpath][segment - 1:segment + 1]
- start[subpath][segment - 1:segment + 1] = cspbezsplit(sp1, sp2)
- #if swapped, swap them back
- if lengthdiff > 0:
- start, end = end, start
-
- #break paths so that corresponding subpaths have an equal number of segments
- s = [[]]
- e = [[]]
- while start and end:
- if start[0] and end[0]:
- s[-1].append(start[0].pop(0))
- e[-1].append(end[0].pop(0))
- elif end[0]:
- s.append(start.pop(0))
- e[-1].append(end[0][0])
- e.append([end[0].pop(0)])
- elif start[0]:
- e.append(end.pop(0))
- s[-1].append(start[0][0])
- s.append([start[0].pop(0)])
- else:
- s.append(start.pop(0))
- e.append(end.pop(0))
-
- if self.options.dup:
- steps = [0] + steps + [1]
- #create an interpolated path for each interval
- group = self.document.createElement('svg:g')
- self.document.documentElement.appendChild(group)
- for time in steps:
- interp = []
- #process subpaths
- for ssp,esp in zip(s, e):
- if not (ssp or esp):
- break
- interp.append([])
- #process superpoints
- for sp,ep in zip(ssp, esp):
- if not (sp or ep):
- break
- interp[-1].append([])
- #process points
- for p1,p2 in zip(sp, ep):
- if not (sp or ep):
- break
- interp[-1][-1].append(interppoints(p1, p2, time))
+ t = 0
+ s = [[]]
+ for sp in slengths:
+ if not start[0]:
+ s.append(start.pop(0))
+ s[-1].append(start[0].pop(0))
+ for l in sp:
+ pt = t
+ t += l / stotal
+ if sadd and t > sadd[0]:
+ while sadd and sadd[0] < t:
+ nt = (sadd[0] - pt) / (t - pt)
+ bezes = cspbezsplitatlength(s[-1][-1][:],start[0][0][:], nt)
+ s[-1][-1:] = bezes[:2]
+ start[0][0] = bezes[2]
+ pt = sadd.pop(0)
+ s[-1].append(start[0].pop(0))
+ t = 0
+ e = [[]]
+ for sp in elengths:
+ if not end[0]:
+ e.append(end.pop(0))
+ e[-1].append(end[0].pop(0))
+ for l in sp:
+ pt = t
+ t += l / etotal
+ if eadd and t > eadd[0]:
+ while eadd and eadd[0] < t:
+ nt = (eadd[0] - pt) / (t - pt)
+ bezes = cspbezsplitatlength(e[-1][-1][:],end[0][0][:], nt)
+ e[-1][-1:] = bezes[:2]
+ end[0][0] = bezes[2]
+ pt = eadd.pop(0)
+ e[-1].append(end[0].pop(0))
+ start = s[:]
+ end = e[:]
+ else:
+ #which path has fewer segments?
+ lengthdiff = numsegs(start) - numsegs(end)
+ #swap shortest first
+ if lengthdiff > 0:
+ start, end = end, start
+ #subdivide the shorter path
+ for x in range(abs(lengthdiff)):
+ maxlen = 0
+ subpath = 0
+ segment = 0
+ for y in range(len(start)):
+ for z in range(1, len(start[y])):
+ leng = bezlenapprx(start[y][z-1], start[y][z])
+ if leng > maxlen:
+ maxlen = leng
+ subpath = y
+ segment = z
+ sp1, sp2 = start[subpath][segment - 1:segment + 1]
+ start[subpath][segment - 1:segment + 1] = cspbezsplit(sp1, sp2)
+ #if swapped, swap them back
+ if lengthdiff > 0:
+ start, end = end, start
+
+ #break paths so that corresponding subpaths have an equal number of segments
+ s = [[]]
+ e = [[]]
+ while start and end:
+ if start[0] and end[0]:
+ s[-1].append(start[0].pop(0))
+ e[-1].append(end[0].pop(0))
+ elif end[0]:
+ s.append(start.pop(0))
+ e[-1].append(end[0][0])
+ e.append([end[0].pop(0)])
+ elif start[0]:
+ e.append(end.pop(0))
+ s[-1].append(start[0][0])
+ s.append([start[0].pop(0)])
+ else:
+ s.append(start.pop(0))
+ e.append(end.pop(0))
+
+ if self.options.dup:
+ steps = [0] + steps + [1]
+ #create an interpolated path for each interval
+ group = self.document.createElement('svg:g')
+ self.current_layer.appendChild(group)
+ for time in steps:
+ interp = []
+ #process subpaths
+ for ssp,esp in zip(s, e):
+ if not (ssp or esp):
+ break
+ interp.append([])
+ #process superpoints
+ for sp,ep in zip(ssp, esp):
+ if not (sp or ep):
+ break
+ interp[-1].append([])
+ #process points
+ for p1,p2 in zip(sp, ep):
+ if not (sp or ep):
+ break
+ interp[-1][-1].append(interppoints(p1, p2, time))
- #remove final subpath if empty.
- if not interp[-1]:
- del interp[-1]
- new = self.document.createElement('svg:path')
+ #remove final subpath if empty.
+ if not interp[-1]:
+ del interp[-1]
+ new = self.document.createElement('svg:path')
- #basic style tweening
- if self.options.style:
- basestyle['opacity'] = tweenstylefloat('opacity',sst,est,time)
- if dostroke:
- basestyle['stroke-opacity'] = tweenstylefloat('stroke-opacity',sst,est,time)
- basestyle['stroke-width'] = tweenstyleunit('stroke-width',sst,est,time)
- basestyle['stroke'] = tweenstylecolor('stroke',sst,est,time)
- if dofill:
- basestyle['fill-opacity'] = tweenstylefloat('fill-opacity',sst,est,time)
- basestyle['fill'] = tweenstylecolor('fill',sst,est,time)
- new.setAttribute('style', simplestyle.formatStyle(basestyle))
- new.setAttribute('d', cubicsuperpath.formatPath(interp))
- group.appendChild(new)
+ #basic style tweening
+ if self.options.style:
+ basestyle['opacity'] = tweenstylefloat('opacity',sst,est,time)
+ if dostroke:
+ basestyle['stroke-opacity'] = tweenstylefloat('stroke-opacity',sst,est,time)
+ basestyle['stroke-width'] = tweenstyleunit('stroke-width',sst,est,time)
+ basestyle['stroke'] = tweenstylecolor('stroke',sst,est,time)
+ if dofill:
+ basestyle['fill-opacity'] = tweenstylefloat('fill-opacity',sst,est,time)
+ basestyle['fill'] = tweenstylecolor('fill',sst,est,time)
+ new.setAttribute('style', simplestyle.formatStyle(basestyle))
+ new.setAttribute('d', cubicsuperpath.formatPath(interp))
+ group.appendChild(new)
e = Interp()
e.affect()