Index: interp.py
===================================================================
--- interp.py (revision 17172)
+++ interp.py (working copy)
@@ -16,7 +16,7 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
-import inkex, cubicsuperpath, simplestyle, copy, math, bezmisc
+import inkex, cubicsuperpath, simplestyle, copy, math, bezmisc, pathmodifier
def numsegs(csp):
return sum([len(p)-1 for p in csp])
@@ -84,7 +84,7 @@
r,g,b = c[1:3],c[3:5],c[5:7]
return r,g,b
-class Interp(inkex.Effect):
+class Interp(pathmodifier.PathModifier):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option("-e", "--exponent",
@@ -120,19 +120,22 @@
paths = {}
styles = {}
- for id in self.options.ids:
- node = self.selected[id]
- if node.tag ==inkex.addNS('path','svg'):
- paths[id] = cubicsuperpath.parsePath(node.get('d'))
- styles[id] = simplestyle.parseStyle(node.get('style'))
- 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]])
+ ##work around selection order unstability: use zOrder instead.
+ sortedids = pathmodifier.zSort(self.document.getroot(),self.selected.keys())
+ ##to use selection order, replace previous line by:
+ #sortedids = self.options.ids
+
+ for id in sortedids:
+ node = self.objectToPath(self.selected[id],False)
+ paths[id] = cubicsuperpath.parsePath(node.get('d'))
+ styles[id] = simplestyle.parseStyle(node.get('style'))
+
+ for i in range(1,len(sortedids)):
+ start = copy.deepcopy(paths[sortedids[i-1]])
+ end = copy.deepcopy(paths[sortedids[i]])
+ sst = copy.deepcopy(styles[sortedids[i-1]])
+ est = copy.deepcopy(styles[sortedids[i]])
basestyle = copy.deepcopy(sst)
#prepare for experimental style tweening
Index: pathalongpath.inx
===================================================================
--- pathalongpath.inx (revision 17172)
+++ pathalongpath.inx (working copy)
@@ -4,7 +4,7 @@
pathmodifier.py
pathalongpath.py
inkex.py
- This effect bends a pattern object along an arbitrary "skeleton" path. The pattern can be a path or a group of paths. First, select the pattern object; then add to selection the skeleton path; then call this effect.
+ This effect bends a pattern object along arbitrary "skeleton" paths. The pattern is the top most object in the selection. (groups of paths/shapes/clones... allowed)
- Single
Index: pathalongpath.py
===================================================================
--- pathalongpath.py (revision 17172)
+++ pathalongpath.py (working copy)
@@ -32,7 +32,7 @@
'''
import inkex, cubicsuperpath, bezmisc
-import pathmodifier
+import pathmodifier,simpletransform
import copy, math, re, random
@@ -118,9 +118,16 @@
help="duplicate pattern before deformation")
def prepareSelectionList(self):
- ##first selected->pattern, all but first selected-> skeletons
- id = self.options.ids[-1]
+
+ idList=self.options.ids
+ idList=pathmodifier.zSort(self.document.getroot(),idList)
+ id = idList[-1]
self.patterns={id:self.selected[id]}
+
+## ##first selected->pattern, all but first selected-> skeletons
+## id = self.options.ids[-1]
+## self.patterns={id:self.selected[id]}
+
if self.options.duplicate:
self.patterns=self.duplicateNodes(self.patterns)
self.expandGroupsUnlinkClones(self.patterns, True, True)
@@ -203,7 +210,8 @@
self.options.repeat =True
self.options.stretch=True
- bbox=self.computeBBox(self.patterns)
+ bbox=simpletransform.computeBBox(self.patterns.values())
+
if self.options.vertical:
#flipxy(bbox)...
bbox=(-bbox[3],-bbox[2],-bbox[1],-bbox[0])
@@ -233,12 +241,13 @@
xoffset=self.skelcomp[0][0]-bbox[0]+self.options.toffset
yoffset=self.skelcomp[0][1]-(bbox[2]+bbox[3])/2-self.options.noffset
+
if self.options.repeat:
NbCopies=max(1,int(round((length+self.options.space)/dx)))
width=dx*NbCopies
if not self.skelcompIsClosed:
width-=self.options.space
- bbox=bbox[0],bbox[0]+width,bbox[2],bbox[3]
+ bbox=bbox[0],bbox[0]+width, bbox[2],bbox[3]
new=[]
for sub in p:
for i in range(0,NbCopies,1):
Index: pathmodifier.py
===================================================================
--- pathmodifier.py (revision 17172)
+++ pathmodifier.py (working copy)
@@ -16,119 +16,37 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
barraud@math.univ-lille1.fr
+
+This code defines a basic class (PathModifier) of effects whose purpose is
+to somehow deform given objects: one common tasks for all such effect is to
+convert shapes, groups, clones to paths. The class has several functions to
+make this (more or less!) easy.
+As an exemple, a second class (Diffeo) is derived from it,
+to implement deformations of the form X=f(x,y), Y=g(x,y)...
+
+TODO: Several handy functions are defined, that might in fact be of general
+interest and that should be shipped out in separate files...
'''
import inkex, cubicsuperpath, bezmisc, simplestyle
+from simpletransform import *
import copy, math, re, random
-def parseTransform(transf,mat=[[1.0,0.0,0.0],[0.0,1.0,0.0]]):
- if transf=="":
- return(mat)
- result=re.match("(translate|scale|rotate|skewX|skewY|matrix)\(([^)]*)\)",transf)
-#-- translate --
- if result.group(1)=="translate":
- args=result.group(2).split(",")
- dx=float(args[0])
- if len(args)==1:
- dy=0.0
- else:
- dy=float(args[1])
- matrix=[[1,0,dx],[0,1,dy]]
-#-- scale --
- if result.groups(1)=="scale":
- args=result.group(2).split(",")
- sx=float(args[0])
- if len(args)==1:
- sy=sx
- else:
- sy=float(args[1])
- matrix=[[sx,0,0],[0,sy,0]]
-#-- rotate --
- if result.groups(1)=="rotate":
- args=result.group(2).split(",")
- a=float(args[0])*math.pi/180
- if len(args)==1:
- cx,cy=(0.0,0.0)
- else:
- cx,cy=args[1:]
- matrix=[[math.cos(a),-math.sin(a),cx],[math.sin(a),math.cos(a),cy]]
-#-- skewX --
- if result.groups(1)=="skewX":
- a=float(result.group(2))*math.pi/180
- matrix=[[1,math.tan(a),0],[0,1,0]]
-#-- skewX --
- if result.groups(1)=="skewX":
- a=float(result.group(2))*math.pi/180
- matrix=[[1,0,0],[math.tan(a),1,0]]
-#-- matrix --
- if result.group(1)=="matrix":
- a11,a21,a12,a22,v1,v2=result.group(2).split(",")
- matrix=[[float(a11),float(a12),float(v1)],[float(a21),float(a22),float(v2)]]
-
- matrix=composeTransform(mat,matrix)
- if result.end()
+ <_name>Scatter
+ math.univ-lille1.barraud.pathScatter
+ pathmodifier.py
+ pathScatter.py
+ inkex.py
+ This effect scatters a pattern along arbitrary "skeleton" paths. The pattern is the top most object in the selection. (groups of paths/shapes/clones... allowed)
+
+ false
+ false
+
+ 0.0
+
+ 0.0
+ 0.0
+
+ false
+ true
+
+
+
+
+
+
+
+
+
Index: pathscatter.py
===================================================================
--- pathscatter.py (revision 0)
+++ pathscatter.py (revision 0)
@@ -0,0 +1,257 @@
+#!/usr/bin/env python
+'''
+Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr
+
+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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+barraud@math.univ-lille1.fr
+
+Quick description:
+This script deforms an object (the pattern) along other paths (skeletons)...
+The first selected object is the pattern
+the last selected ones are the skeletons.
+
+Imagine a straight horizontal line L in the middle of the bounding box of the pattern.
+Consider the normal bundle of L: the collection of all the vertical lines meeting L.
+Consider this as the initial state of the plane; in particular, think of the pattern
+as painted on these lines.
+
+Now move and bend L to make it fit a skeleton, and see what happens to the normals:
+they move and rotate, deforming the pattern.
+'''
+
+import inkex, cubicsuperpath, bezmisc
+import pathmodifier, simpletransform
+from lxml import etree
+
+import copy, math, re, random
+
+def zSort(inNode,idList):
+ sortedList=[]
+ theid = inNode.get("id")
+ if theid in idList:
+ sortedList.append(theid)
+ for child in inNode:
+ if len(sortedList)==len(idList):
+ break
+ sortedList+=zSort(child,idList)
+ return sortedList
+
+
+def flipxy(path):
+ for pathcomp in path:
+ for ctl in pathcomp:
+ for pt in ctl:
+ tmp=pt[0]
+ pt[0]=-pt[1]
+ pt[1]=-tmp
+
+def offset(pathcomp,dx,dy):
+ for ctl in pathcomp:
+ for pt in ctl:
+ pt[0]+=dx
+ pt[1]+=dy
+
+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
+
+def linearize(p,tolerance=0.001):
+ '''
+ This function recieves 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.
+
+ We could work with bezier path as well, but bezier arc lengths are (re)computed for each point
+ in the deformed object. For complex paths, this might take a while.
+ '''
+ zero=0.000001
+ i=0
+ d=0
+ lengths=[]
+ while i 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)
+
+class PathScatter(pathmodifier.Diffeo):
+ def __init__(self):
+ pathmodifier.Diffeo.__init__(self)
+ self.OptionParser.add_option("--title")
+ self.OptionParser.add_option("-n", "--noffset",
+ action="store", type="float",
+ dest="noffset", default=0.0, help="normal offset")
+ self.OptionParser.add_option("-t", "--toffset",
+ action="store", type="float",
+ dest="toffset", default=0.0, help="tangential offset")
+ self.OptionParser.add_option("-f", "--follow",
+ action="store", type="inkbool",
+ dest="follow", default=True,
+ help="choose between wave or snake effect")
+ self.OptionParser.add_option("-s", "--stretch",
+ action="store", type="inkbool",
+ dest="stretch", default=True,
+ help="repeat the path to fit deformer's length")
+ self.OptionParser.add_option("-p", "--space",
+ action="store", type="float",
+ dest="space", default=0.0)
+ self.OptionParser.add_option("-v", "--vertical",
+ action="store", type="inkbool",
+ dest="vertical", default=False,
+ help="reference path is vertical")
+ self.OptionParser.add_option("-d", "--duplicate",
+ action="store", type="inkbool",
+ dest="duplicate", default=False,
+ help="duplicate pattern before deformation")
+
+ def prepareSelectionList(self):
+
+ idList=self.options.ids
+ idList=zSort(self.document.getroot(),idList)
+
+ ##first selected->pattern, all but first selected-> skeletons
+ #id = self.options.ids[-1]
+ id = idList[-1]
+ self.patterns = {id:self.selected[id]}
+ self.patternNode=self.selected[id]
+
+ self.gNode = etree.Element('{http://www.w3.org/2000/svg}g')
+ self.patternNode.getparent().append(self.gNode)
+ #self.patternNode.parentNode.appendChild(self.gNode)
+
+
+ if self.options.duplicate:
+ self.patterns=self.duplicateNodes(self.patterns)
+ #self.expandGroupsUnlinkClones(self.patterns, True, True)
+ #self.objectsToPaths(self.patterns)
+ #del self.selected[id]
+
+ self.skeletons=self.selected
+ del self.skeletons[id]
+ self.expandGroupsUnlinkClones(self.skeletons, True, False)
+ self.objectsToPaths(self.skeletons,False)
+
+ def lengthtotime(self,l):
+ '''
+ Recieves an arc length l, and returns the index of the segment in self.skelcomp
+ containing the coresponding point, to gether with the position of the point on this segment.
+
+ If the deformer is closed, do computations modulo the toal length.
+ '''
+ if self.skelcompIsClosed:
+ l=l % sum(self.lengths)
+ if l<=0:
+ return 0,l/self.lengths[0]
+ i=0
+ while (i----> TODO: really test if path is closed! end point==start point is not enough!
+ self.skelcompIsClosed = (self.skelcomp[0]==self.skelcomp[-1])
+
+ length=sum(self.lengths)
+ if self.options.stretch:
+ dx=width+self.options.space
+ n=int((length-self.options.toffset+self.options.space)/dx)
+ if n>0:
+ dx=(length-self.options.toffset)/n
+
+
+ xoffset=self.skelcomp[0][0]-bbox[0]+self.options.toffset
+ yoffset=self.skelcomp[0][1]-(bbox[2]+bbox[3])/2-self.options.noffset
+
+ s=self.options.toffset
+ while s<=length:
+ mat=self.localTransformAt(s,self.options.follow)
+
+ clone=copy.deepcopy(self.patternNode)
+ #!!!--> should it be given an id?
+ #seems to work without this!?!
+ myid = self.patternNode.tag.split('}')[-1]
+ clone.set("id", self.uniqueId(myid))
+ self.gNode.append(clone)
+ #self.patternNode.getparent().append(clone)
+
+ simpletransform.applyTransformToNode(mat,clone)
+
+ s+=dx
+ self.patternNode.getparent().remove(self.patternNode)
+
+
+e = PathScatter()
+e.affect()
+
+
Index: rubberstretch.inx
===================================================================
--- rubberstretch.inx (revision 0)
+++ rubberstretch.inx (revision 0)
@@ -0,0 +1,17 @@
+
+ <_name>Rubber Stretch
+ math.univ-lille1.barraud.spherify
+ pathmodifier.py
+ inkex.py
+ 25
+ 25
+
+ path
+
+
+
+
+
+
Index: rubberstretch.py
===================================================================
--- rubberstretch.py (revision 17172)
+++ rubberstretch.py (working copy)
@@ -20,7 +20,7 @@
'''
import inkex, cubicsuperpath, bezmisc, pathmodifier
-import copy, math, re, random, xml.xpath
+import copy, math, re
class RubberStretch(pathmodifier.Diffeo):
def __init__(self):
@@ -45,7 +45,7 @@
w,h=(self.bbox[1]-self.bbox[0])/2,(self.bbox[3]-self.bbox[2])/2
x,y=(bpt[0]-x0),(bpt[1]-y0)
- sx=(1+b*(x/w+1)*(x/w-1))*2**(-a)
+ sx=(1+b*(x/w+1)*(x/w-1))*2**(-a)
sy=(1+b*(y/h+1)*(y/h-1))*2**(-a)
bpt[0]=x0+x*sy
bpt[1]=y0+y/sx
@@ -75,5 +75,5 @@
e = RubberStretch()
e.affect()
-
-
+
+
Index: simpletransform.py
===================================================================
--- simpletransform.py (revision 0)
+++ simpletransform.py (revision 0)
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+'''
+Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr
+
+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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+barraud@math.univ-lille1.fr
+
+This code defines several functions to make handling of transform
+attribute easier.
+'''
+import inkex, cubicsuperpath, bezmisc, simplestyle
+import copy, math, re
+
+def parseTransform(transf,mat=[[1.0,0.0,0.0],[0.0,1.0,0.0]]):
+ if transf=="" or transf==None:
+ return(mat)
+ result=re.match("(translate|scale|rotate|skewX|skewY|matrix)\(([^)]*)\)",transf)
+#-- translate --
+ if result.group(1)=="translate":
+ args=result.group(2).split(",")
+ dx=float(args[0])
+ if len(args)==1:
+ dy=0.0
+ else:
+ dy=float(args[1])
+ matrix=[[1,0,dx],[0,1,dy]]
+#-- scale --
+ if result.groups(1)=="scale":
+ args=result.group(2).split(",")
+ sx=float(args[0])
+ if len(args)==1:
+ sy=sx
+ else:
+ sy=float(args[1])
+ matrix=[[sx,0,0],[0,sy,0]]
+#-- rotate --
+ if result.groups(1)=="rotate":
+ args=result.group(2).split(",")
+ a=float(args[0])*math.pi/180
+ if len(args)==1:
+ cx,cy=(0.0,0.0)
+ else:
+ cx,cy=args[1:]
+ matrix=[[math.cos(a),-math.sin(a),cx],[math.sin(a),math.cos(a),cy]]
+#-- skewX --
+ if result.groups(1)=="skewX":
+ a=float(result.group(2))*math.pi/180
+ matrix=[[1,math.tan(a),0],[0,1,0]]
+#-- skewX --
+ if result.groups(1)=="skewX":
+ a=float(result.group(2))*math.pi/180
+ matrix=[[1,0,0],[math.tan(a),1,0]]
+#-- matrix --
+ if result.group(1)=="matrix":
+ a11,a21,a12,a22,v1,v2=result.group(2).split(",")
+ matrix=[[float(a11),float(a12),float(v1)],[float(a21),float(a22),float(v2)]]
+
+ matrix=composeTransform(mat,matrix)
+ if result.end()