index 3901069c9089976c00f7c429ee1fe54235758db8..7a2fa09eabffe05ec601bc3a1b84a713dad89fed 100644 (file)
-#!/usr/bin/env python\r
-'''\r
-Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr\r
-\r
-This program is free software; you can redistribute it and/or modify\r
-it under the terms of the GNU General Public License as published by\r
-the Free Software Foundation; either version 2 of the License, or\r
-(at your option) any later version.\r
-\r
-This program is distributed in the hope that it will be useful,\r
-but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
-GNU General Public License for more details.\r
-\r
-You should have received a copy of the GNU General Public License\r
-along with this program; if not, write to the Free Software\r
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
-barraud@math.univ-lille1.fr\r
-\r
-Quick description:\r
-This script deforms an object (the pattern) along other paths (skeletons)...\r
-The first selected object is the pattern\r
-the last selected ones are the skeletons.\r
-\r
-Imagine a straight horizontal line L in the middle of the bounding box of the pattern.\r
-Consider the normal bundle of L: the collection of all the vertical lines meeting L.\r
-Consider this as the initial state of the plane; in particular, think of the pattern\r
-as painted on these lines.\r
-\r
-Now move and bend L to make it fit a skeleton, and see what happens to the normals:\r
-they move and rotate, deforming the pattern.\r
-'''\r
-\r
-import inkex, cubicsuperpath, bezmisc\r
-import pathmodifier \r
-\r
-import copy, math, re, random\r
-\r
-def flipxy(path):\r
- for pathcomp in path:\r
- for ctl in pathcomp:\r
- for pt in ctl:\r
- tmp=pt[0]\r
- pt[0]=-pt[1]\r
- pt[1]=-tmp\r
-\r
-def offset(pathcomp,dx,dy):\r
- for ctl in pathcomp:\r
- for pt in ctl:\r
- pt[0]+=dx\r
- pt[1]+=dy\r
-\r
-def stretch(pathcomp,xscale,yscale,org):\r
- for ctl in pathcomp:\r
- for pt in ctl:\r
- pt[0]=org[0]+(pt[0]-org[0])*xscale\r
- pt[1]=org[1]+(pt[1]-org[1])*yscale\r
-\r
-def linearize(p,tolerance=0.001):\r
- '''\r
- This function recieves a component of a 'cubicsuperpath' and returns two things:\r
- The path subdivided in many straight segments, and an array containing the length of each segment.\r
- \r
- We could work with bezier path as well, but bezier arc lengths are (re)computed for each point \r
- in the deformed object. For complex paths, this might take a while.\r
- '''\r
- zero=0.000001\r
- i=0\r
- d=0\r
- lengths=[]\r
- while i<len(p)-1:\r
- box = bezmisc.pointdistance(p[i ][1],p[i ][2])\r
- box += bezmisc.pointdistance(p[i ][2],p[i+1][0])\r
- box += bezmisc.pointdistance(p[i+1][0],p[i+1][1])\r
- chord = bezmisc.pointdistance(p[i][1], p[i+1][1])\r
- if (box - chord) > tolerance:\r
- b1, b2 = bezmisc.beziersplitatt([p[i][1],p[i][2],p[i+1][0],p[i+1][1]], 0.5)\r
- p[i ][2][0],p[i ][2][1]=b1[1]\r
- p[i+1][0][0],p[i+1][0][1]=b2[2]\r
- p.insert(i+1,[[b1[2][0],b1[2][1]],[b1[3][0],b1[3][1]],[b2[1][0],b2[1][1]]])\r
- else:\r
- d=(box+chord)/2\r
- lengths.append(d)\r
- i+=1\r
- new=[p[i][1] for i in range(0,len(p)-1) if lengths[i]>zero]\r
- new.append(p[-1][1])\r
- lengths=[l for l in lengths if l>zero]\r
- return(new,lengths)\r
-\r
-class PathAlongPath(pathmodifier.Diffeo):\r
- def __init__(self):\r
- pathmodifier.Diffeo.__init__(self)\r
- self.OptionParser.add_option("--title")\r
- self.OptionParser.add_option("-n", "--noffset",\r
- action="store", type="float", \r
- dest="noffset", default=0.0, help="normal offset")\r
- self.OptionParser.add_option("-t", "--toffset",\r
- action="store", type="float", \r
- dest="toffset", default=0.0, help="tangential offset")\r
- self.OptionParser.add_option("-k", "--kind",\r
- action="store", type="string", \r
- dest="kind", default=True,\r
- help="choose between wave or snake effect")\r
- self.OptionParser.add_option("-c", "--copymode",\r
- action="store", type="string", \r
- dest="copymode", default=True,\r
- help="repeat the path to fit deformer's length")\r
- self.OptionParser.add_option("-p", "--space",\r
- action="store", type="float", \r
- dest="space", default=0.0)\r
- self.OptionParser.add_option("-v", "--vertical",\r
- action="store", type="inkbool", \r
- dest="vertical", default=False,\r
- help="reference path is vertical")\r
- self.OptionParser.add_option("-d", "--duplicate",\r
- action="store", type="inkbool", \r
- dest="duplicate", default=False,\r
- help="duplicate pattern before deformation")\r
-\r
- def prepareSelectionList(self):\r
- ##first selected->pattern, all but first selected-> skeletons\r
- id = self.options.ids[-1]\r
- self.patterns={id:self.selected[id]}\r
- if self.options.duplicate:\r
- self.patterns=self.duplicateNodes(self.patterns)\r
- self.expandGroupsUnlinkClones(self.patterns, True, True)\r
- self.objectsToPaths(self.patterns)\r
- del self.selected[id]\r
-\r
- self.skeletons=self.selected\r
- self.expandGroupsUnlinkClones(self.skeletons, True, False)\r
- self.objectsToPaths(self.skeletons)\r
-\r
- def lengthtotime(self,l):\r
- '''\r
- Recieves an arc length l, and returns the index of the segment in self.skelcomp \r
- containing the coresponding point, to gether with the position of the point on this segment.\r
-\r
- If the deformer is closed, do computations modulo the toal length.\r
- '''\r
- if self.skelcompIsClosed:\r
- l=l % sum(self.lengths)\r
- if l<=0:\r
- return 0,l/self.lengths[0]\r
- i=0\r
- while (i<len(self.lengths)) and (self.lengths[i]<=l):\r
- l-=self.lengths[i]\r
- i+=1\r
- t=l/self.lengths[min(i,len(self.lengths)-1)]\r
- return i, t\r
-\r
- def applyDiffeo(self,bpt,vects=()):\r
- '''\r
- The kernel of this stuff:\r
- bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt.\r
- '''\r
- s=bpt[0]-self.skelcomp[0][0]\r
- i,t=self.lengthtotime(s)\r
- if i==len(self.skelcomp)-1:\r
- x,y=bezmisc.tpoint(self.skelcomp[i-1],self.skelcomp[i],1+t)\r
- dx=(self.skelcomp[i][0]-self.skelcomp[i-1][0])/self.lengths[-1]\r
- dy=(self.skelcomp[i][1]-self.skelcomp[i-1][1])/self.lengths[-1]\r
- else:\r
- x,y=bezmisc.tpoint(self.skelcomp[i],self.skelcomp[i+1],t)\r
- dx=(self.skelcomp[i+1][0]-self.skelcomp[i][0])/self.lengths[i]\r
- dy=(self.skelcomp[i+1][1]-self.skelcomp[i][1])/self.lengths[i]\r
-\r
- vx=0\r
- vy=bpt[1]-self.skelcomp[0][1]\r
- if self.options.wave:\r
- bpt[0]=x+vx*dx\r
- bpt[1]=y+vy+vx*dy\r
- else:\r
- bpt[0]=x+vx*dx-vy*dy\r
- bpt[1]=y+vx*dy+vy*dx\r
-\r
- for v in vects:\r
- vx=v[0]-self.skelcomp[0][0]-s\r
- vy=v[1]-self.skelcomp[0][1]\r
- if self.options.wave:\r
- v[0]=x+vx*dx\r
- v[1]=y+vy+vx*dy\r
- else:\r
- v[0]=x+vx*dx-vy*dy\r
- v[1]=y+vx*dy+vy*dx\r
-\r
- def effect(self):\r
- if len(self.options.ids)<2:\r
- inkex.debug("This extension requires that you select two paths.")\r
- return\r
- self.prepareSelectionList()\r
- self.options.wave = (self.options.kind=="Ribbon")\r
- if self.options.copymode=="Single":\r
- self.options.repeat =False\r
- self.options.stretch=False\r
- elif self.options.copymode=="Repeated":\r
- self.options.repeat =True\r
- self.options.stretch=False\r
- elif self.options.copymode=="Single, stretched":\r
- self.options.repeat =False\r
- self.options.stretch=True\r
- elif self.options.copymode=="Repeated, stretched":\r
- self.options.repeat =True\r
- self.options.stretch=True\r
-\r
- bbox=self.computeBBox(self.patterns)\r
- if self.options.vertical:\r
- #flipxy(bbox)...\r
- bbox=(-bbox[3],-bbox[2],-bbox[1],-bbox[0])\r
- \r
- width=bbox[1]-bbox[0]\r
- dx=width+self.options.space\r
-\r
- for id, node in self.patterns.iteritems():\r
- if node.tag == inkex.addNS('path','svg'):\r
- d = node.get('d')\r
- p0 = cubicsuperpath.parsePath(d)\r
- if self.options.vertical:\r
- flipxy(p0)\r
-\r
- newp=[]\r
- for skelnode in self.skeletons.itervalues(): \r
- self.curSekeleton=cubicsuperpath.parsePath(skelnode.get('d'))\r
- if self.options.vertical:\r
- flipxy(self.curSekeleton)\r
- for comp in self.curSekeleton:\r
- p=copy.deepcopy(p0)\r
- self.skelcomp,self.lengths=linearize(comp)\r
- #!!!!>----> TODO: really test if path is closed! end point==start point is not enough!\r
- self.skelcompIsClosed = (self.skelcomp[0]==self.skelcomp[-1])\r
-\r
- length=sum(self.lengths)\r
- xoffset=self.skelcomp[0][0]-bbox[0]+self.options.toffset\r
- yoffset=self.skelcomp[0][1]-(bbox[2]+bbox[3])/2-self.options.noffset\r
-\r
- if self.options.repeat:\r
- NbCopies=max(1,int(round((length+self.options.space)/dx)))\r
- width=dx*NbCopies\r
- if not self.skelcompIsClosed:\r
- width-=self.options.space\r
- bbox=bbox[0],bbox[0]+width,bbox[2],bbox[3]\r
- new=[]\r
- for sub in p:\r
- for i in range(0,NbCopies,1):\r
- new.append(copy.deepcopy(sub))\r
- offset(sub,dx,0)\r
- p=new\r
-\r
- for sub in p:\r
- offset(sub,xoffset,yoffset)\r
-\r
- if self.options.stretch:\r
- for sub in p:\r
- stretch(sub,length/width,1,self.skelcomp[0])\r
-\r
- for sub in p:\r
- for ctlpt in sub:\r
- self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2]))\r
-\r
- if self.options.vertical:\r
- flipxy(p)\r
- newp+=p\r
-\r
- node.set('d', cubicsuperpath.formatPath(newp))\r
-\r
-e = PathAlongPath()\r
-e.affect()\r
-\r
- \r
+#!/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
+import copy, math, re, random
+import gettext
+_ = gettext.gettext
+
+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<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)
+
+class PathAlongPath(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("-k", "--kind",
+ action="store", type="string",
+ dest="kind", default=True,
+ help="choose between wave or snake effect")
+ self.OptionParser.add_option("-c", "--copymode",
+ action="store", type="string",
+ dest="copymode", 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")
+ self.OptionParser.add_option("--tab",
+ action="store", type="string",
+ dest="tab",
+ help="The selected UI-tab when OK was pressed")
+
+ def prepareSelectionList(self):
+
+ 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)
+ self.objectsToPaths(self.patterns)
+ del self.selected[id]
+
+ self.skeletons=self.selected
+ self.expandGroupsUnlinkClones(self.skeletons, True, False)
+ self.objectsToPaths(self.skeletons)
+
+ 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<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],1+t)
+ 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]
+ if self.options.wave:
+ bpt[0]=x+vx*dx
+ bpt[1]=y+vy+vx*dy
+ else:
+ 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]
+ if self.options.wave:
+ v[0]=x+vx*dx
+ v[1]=y+vy+vx*dy
+ else:
+ 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()
+ self.options.wave = (self.options.kind=="Ribbon")
+ if self.options.copymode=="Single":
+ self.options.repeat =False
+ self.options.stretch=False
+ elif self.options.copymode=="Repeated":
+ self.options.repeat =True
+ self.options.stretch=False
+ elif self.options.copymode=="Single, stretched":
+ self.options.repeat =False
+ self.options.stretch=True
+ elif self.options.copymode=="Repeated, stretched":
+ self.options.repeat =True
+ self.options.stretch=True
+
+ bbox=simpletransform.computeBBox(self.patterns.values())
+
+ if self.options.vertical:
+ #flipxy(bbox)...
+ bbox=(-bbox[3],-bbox[2],-bbox[1],-bbox[0])
+
+ width=bbox[1]-bbox[0]
+ dx=width+self.options.space
+ if dx < 0.01:
+ exit(_("The total length of the pattern is too small :\nPlease choose a larger object or set 'Space between copies' > 0"))
+
+ for id, node in self.patterns.iteritems():
+ if node.tag == inkex.addNS('path','svg') or node.tag=='path':
+ d = node.get('d')
+ p0 = cubicsuperpath.parsePath(d)
+ if self.options.vertical:
+ flipxy(p0)
+
+ newp=[]
+ for skelnode in self.skeletons.itervalues():
+ self.curSekeleton=cubicsuperpath.parsePath(skelnode.get('d'))
+ if self.options.vertical:
+ flipxy(self.curSekeleton)
+ for comp in self.curSekeleton:
+ p=copy.deepcopy(p0)
+ self.skelcomp,self.lengths=linearize(comp)
+ #!!!!>----> 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)
+ 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]
+ new=[]
+ for sub in p:
+ for i in range(0,NbCopies,1):
+ new.append(copy.deepcopy(sub))
+ offset(sub,dx,0)
+ p=new
+
+ for sub in p:
+ offset(sub,xoffset,yoffset)
+
+ if self.options.stretch:
+ for sub in p:
+ stretch(sub,length/width,1,self.skelcomp[0])
+
+ for sub in p:
+ for ctlpt in sub:
+ self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2]))
+
+ if self.options.vertical:
+ flipxy(p)
+ newp+=p
+
+ node.set('d', cubicsuperpath.formatPath(newp))
+
+if __name__ == '__main__':
+ e = PathAlongPath()
+ e.affect()
+
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99