diff options
| author | Peter Moulder <peter.moulder@monash.edu> | 2008-03-29 10:21:03 +0000 |
|---|---|---|
| committer | pjrm <pjrm@users.sourceforge.net> | 2008-03-29 10:21:03 +0000 |
| commit | 48cfa43e90e580b90d9d8ce37524cf79a06d56be (patch) | |
| tree | 6c323130b2f0c2fb32503513474fb36d84fc7ab9 /share/extensions/pathalongpath.py | |
| parent | svn propset svn:eol-style native *.[ch] *.cpp (including buildtool.cpp, packa... (diff) | |
| download | inkscape-48cfa43e90e580b90d9d8ce37524cf79a06d56be.tar.gz inkscape-48cfa43e90e580b90d9d8ce37524cf79a06d56be.zip | |
svn propset svn:eol-style native *.py
(bzr r5228)
Diffstat (limited to 'share/extensions/pathalongpath.py')
| -rw-r--r-- | share/extensions/pathalongpath.py | 556 |
1 files changed, 278 insertions, 278 deletions
diff --git a/share/extensions/pathalongpath.py b/share/extensions/pathalongpath.py index a8285b9f8..5bac2550b 100644 --- a/share/extensions/pathalongpath.py +++ b/share/extensions/pathalongpath.py @@ -1,278 +1,278 @@ -#!/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
-
-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")
-
- 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.debug("This extension requires that you select two 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
-
- 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))
-
-e = PathAlongPath()
-e.affect()
-
-
+#!/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 + +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") + + 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.debug("This extension requires that you select two 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 + + 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)) + +e = PathAlongPath() +e.affect() + + |
