diff options
| author | bulia byak <buliabyak@gmail.com> | 2006-11-23 20:29:45 +0000 |
|---|---|---|
| committer | buliabyak <buliabyak@users.sourceforge.net> | 2006-11-23 20:29:45 +0000 |
| commit | 56551c10e1018dff822f8c63e1302f452440b8a7 (patch) | |
| tree | d5396a3c39fb22f8ec1cab5a76273e4087c235f5 | |
| parent | a couple more static libs (diff) | |
| download | inkscape-56551c10e1018dff822f8c63e1302f452440b8a7.tar.gz inkscape-56551c10e1018dff822f8c63e1302f452440b8a7.zip | |
add Pattern on Path (patch 1594529)
(bzr r2009)
| -rw-r--r-- | share/extensions/Makefile.am | 8 | ||||
| -rw-r--r-- | share/extensions/pathalongpath.inx | 38 | ||||
| -rw-r--r-- | share/extensions/pathalongpath.py | 266 | ||||
| -rw-r--r-- | share/extensions/pathmodifier.py | 376 | ||||
| -rw-r--r-- | share/extensions/rubberstretch.py | 79 |
5 files changed, 765 insertions, 2 deletions
diff --git a/share/extensions/Makefile.am b/share/extensions/Makefile.am index cbcba7f89..8a6ad980d 100644 --- a/share/extensions/Makefile.am +++ b/share/extensions/Makefile.am @@ -80,7 +80,10 @@ extensions = \ color_morelight.py\ color_lesslight.py\ color_morehue.py\ - color_lesshue.py + color_lesshue.py\ + pathalongpath.py\ + rubberstretch.py\ + pathmodifier.py otherstuff = \ @@ -158,7 +161,8 @@ modules = \ color_morelight.inx\ color_lesslight.inx\ color_morehue.inx\ - color_lesshue.inx + color_lesshue.inx\ + pathalongpath.inx extension_SCRIPTS = \ diff --git a/share/extensions/pathalongpath.inx b/share/extensions/pathalongpath.inx new file mode 100644 index 000000000..303427a82 --- /dev/null +++ b/share/extensions/pathalongpath.inx @@ -0,0 +1,38 @@ +<inkscape-extension>
+ <_name>Pattern along Path</_name>
+ <id>math.univ-lille1.barraud.pathdeform</id>
+ <dependency type="executable" location="extensions">pathmodifier.py</dependency>
+ <dependency type="executable" location="extensions">pathalongpath.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <param name="title" type="description">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.</param>
+
+ <param name="copymode" type="enum" _gui-text="Copies of the pattern:">
+ <item>Single</item>
+ <item>Single, stretched</item>
+ <item>Repeated</item>
+ <item>Repeated, stretched</item>
+ </param>
+
+ <param name="kind" type="enum" _gui-text="Deformation type:">
+ <item>Snake</item>
+ <item>Wave</item>
+ </param>
+
+ <param name="space" type="float" _gui-text="Space between copies:" min="-10000.0" max="10000.0" >0.0</param>
+
+ <param name="noffset" type="float" _gui-text="Normal offset" min="-10000.0" max="10000.0">0.0</param>
+ <param name="toffset" type="float" _gui-text="Tangential offset" min="-10000.0" max="10000.0" >0.0</param>
+
+ <param name="vertical" type="boolean" _gui-text="Pattern is vertical">false</param>
+ <param name="duplicate" type="boolean" _gui-text="Duplicate the pattern before deformation">true</param>
+ <effect>
+ <effects-menu>
+ <submenu _name="Generate from Path"/>
+ </effects-menu>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">pathalongpath.py</command>
+ </script>
+</inkscape-extension>
+
+
diff --git a/share/extensions/pathalongpath.py b/share/extensions/pathalongpath.py new file mode 100644 index 000000000..598f3c817 --- /dev/null +++ b/share/extensions/pathalongpath.py @@ -0,0 +1,266 @@ +#!/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
+
+import copy, math, re, random, xml.xpath
+
+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):
+##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):
+ self.prepareSelectionList()
+ self.options.wave = (self.options.kind=="Wave")
+ 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=self.computeBBox(self.patterns)
+ 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.tagName == 'path':
+ d = node.attributes.getNamedItem('d')
+ p0 = cubicsuperpath.parsePath(d.value)
+ if self.options.vertical:
+ flipxy(p0)
+
+ newp=[]
+ for skelnode in self.skeletons.itervalues():
+ self.curSekeleton=cubicsuperpath.parsePath(skelnode.getAttribute('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
+
+ d.value = cubicsuperpath.formatPath(newp)
+
+e = PathAlongPath()
+e.affect()
+
+
diff --git a/share/extensions/pathmodifier.py b/share/extensions/pathmodifier.py new file mode 100644 index 000000000..a27c547c1 --- /dev/null +++ b/share/extensions/pathmodifier.py @@ -0,0 +1,376 @@ +#!/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
+'''
+import inkex, cubicsuperpath, bezmisc, simplestyle
+import copy, math, re, random, xml.xpath
+
+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()<len(transf):
+ return(parseTransform(transf[result.end():],matrix))
+ else:
+ return matrix
+
+def formatTransform(mat):
+ return("matrix(%f,%f,%f,%f,%f,%f)"%(mat[0][0],mat[1][0],mat[0][1],mat[1][1],mat[0][2],mat[1][2]))
+
+def composeTransform(M1,M2):
+ a11=M1[0][0]*M2[0][0]+M1[0][1]*M2[1][0]
+ a12=M1[0][0]*M2[0][1]+M1[0][1]*M2[1][1]
+ a21=M1[1][0]*M2[0][0]+M1[1][1]*M2[1][0]
+ a22=M1[1][0]*M2[0][1]+M1[1][1]*M2[1][1]
+
+ v1=M1[0][0]*M2[0][2]+M1[0][1]*M2[1][2]+M1[0][2]
+ v2=M1[1][0]*M2[0][2]+M1[1][1]*M2[1][2]+M1[1][2]
+ return [[a11,a12,v1],[a21,a22,v2]]
+
+def applyTransformToNode(mat,node):
+ m=parseTransform(node.getAttributeNS(None,"transform"))
+ newtransf=formatTransform(composeTransform(mat,m))
+ node.setAttributeNS(None,"transform", newtransf)
+
+def applyTransformToPoint(mat,pt):
+ x=mat[0][0]*pt[0]+mat[0][1]*pt[1]+mat[0][2]
+ y=mat[1][0]*pt[0]+mat[1][1]*pt[1]+mat[1][2]
+ pt[0]=x
+ pt[1]=y
+
+def fuseTransform(node):
+ m=parseTransform(node.getAttributeNS(None,"transform"))
+ d = node.getAttributeNS(None,'d')
+ p=cubicsuperpath.parsePath(d)
+ for comp in p:
+ for ctl in comp:
+ for pt in ctl:
+ applyTransformToPoint(m,pt)
+ node.setAttributeNS(None,'d', cubicsuperpath.formatPath(p))
+ node.removeAttributeNS(None,"transform")
+
+
+def boxunion(b1,b2):
+ if b1 is None:
+ return b2
+ elif b2 is None:
+ return b1
+ else:
+ return((min(b1[0],b2[0]),max(b1[1],b2[1]),min(b1[2],b2[2]),max(b1[3],b2[3])))
+
+def roughBBox(path):
+ xmin,xMax,ymin,yMax=path[0][0][0][0],path[0][0][0][0],path[0][0][0][1],path[0][0][0][1]
+ for pathcomp in path:
+ for ctl in pathcomp:
+ for pt in ctl:
+ xmin=min(xmin,pt[0])
+ xMax=max(xMax,pt[0])
+ ymin=min(ymin,pt[1])
+ yMax=max(yMax,pt[1])
+ return xmin,xMax,ymin,yMax
+
+
+class PathModifier(inkex.Effect):
+ def __init__(self):
+ inkex.Effect.__init__(self)
+
+##################################
+#-- Selectionlists manipulation --
+##################################
+ def computeBBox(self, aList):
+ bbox=None
+ for id, node in aList.iteritems():
+ if node.tagName == 'path':
+ d = node.attributes.getNamedItem('d')
+ p = cubicsuperpath.parsePath(d.value)
+ bbox=boxunion(roughBBox(p),bbox)
+ return bbox
+
+ def duplicateNodes(self, aList):
+ clones={}
+ for id,node in aList.iteritems():
+ clone=node.cloneNode(True)
+ #!!!--> should it be given an id?
+ #seems to work without this!?!
+ clone.setAttributeNS(None,"id", self.uniqueId(node.tagName))
+ node.parentNode.appendChild(clone)
+ clones[clone.getAttributeNS(None,"id")]=clone
+ return(clones)
+
+ def uniqueId(self, prefix):
+ id="%s%04i"%(prefix,random.randint(0,9999))
+ while len(xml.xpath.Evaluate('//*[@id="%s"]' % id,self.document)):
+ id="%s%04i"%(prefix,random.randint(0,9999))
+ return(id)
+
+ def expandGroups(self,aList,transferTransform=True):
+ for id, node in aList.items():
+ if node.tagName == 'g':
+ mat=parseTransform(node.getAttributeNS(None,"transform"))
+ for child in node.childNodes:
+ if child.nodeType==child.ELEMENT_NODE:
+ if transferTransform:
+ applyTransformToNode(mat,child)
+ aList.update(self.expandGroups({child.getAttribute('id'):child}))
+ if transferTransform:
+ node.removeAttribute("transform")
+ del aList[id]
+ return(aList)
+
+ def expandGroupsUnlinkClones(self,aList,transferTransform=True,doReplace=True):
+ for id in aList.keys()[:]:
+ node=aList[id]
+ if node.tagName == 'g':
+ self.expandGroups(aList,transferTransform)
+ self.expandGroupsUnlinkClones(aList,transferTransform,doReplace)
+ #Hum... not very efficient if there are many clones of groups...
+ elif node.tagName == 'use':
+ refid=node.getAttributeNS(inkex.NSS[u'xlink'],'href')
+ path = '//*[@id="%s"]' % refid[1:]
+ refnode = xml.xpath.Evaluate(path,self.document)[0]
+ newnode=refnode.cloneNode(True)
+ self.recursNewIds(newnode)
+
+ if node.hasAttributeNS(None,u'style'):
+ style=simplestyle.parseStyle(node.getAttributeNS(None,u'style'))
+ refstyle=simplestyle.parseStyle(refnode.getAttributeNS(None,u'style'))
+ style.update(refstyle)
+ newnode.setAttributeNS(None,'style',simplestyle.formatStyle(style))
+ applyTransformToNode(parseTransform(node.getAttributeNS(None,'transform')),newnode)
+ if doReplace:
+ parent=node.parentNode
+ parent.insertBefore(newnode,node)
+ parent.removeChild(node)
+ del aList[id]
+ newid=newnode.getAttributeNS(None,'id')
+ aList.update(self.expandGroupsUnlinkClones({newid:newnode},transferTransform,doReplace))
+ return aList
+
+ def recursNewIds(self,node):
+ if node.nodeType==node.ELEMENT_NODE and node.hasAttributeNS(None,u'id'):
+ node.setAttributeNS(None,u'id',self.uniqueId(node.tagName))
+ for child in node.childNodes:
+ self.recursNewIds(child)
+
+
+
+
+# def makeClonesReal(self,aList,doReplace=True,recursivelytransferTransform=True):
+# for id in aList.keys():
+# node=aList[id]
+# if node.tagName == 'g':
+# childs={}
+# for child in node.childNodes:
+# if child.nodeType==child.ELEMENT_NODE:
+# childid=child.getAttributeNS(None,'id')
+# del aList[childid]
+# aList.update(self.makeClonesReal({childid:child},doReplace))
+# elif node.tagName == 'use':
+# refid=node.getAttributeNS(inkex.NSS[u'xlink'],'href')
+# path = '//*[@id="%s"]' % refid[1:]
+# refnode = xml.xpath.Evaluate(path,document)[0]
+# clone=refnode.cloneNode(True)
+# cloneid=self.uniqueId(clone.tagName)
+# clone.setAttributeNS(None,'id', cloneid)
+# style=simplestyle.parseStyle(node.getAttributeNS(None,u'style'))
+# refstyle=simplestyle.parseStyle(refnode.getAttributeNS(None,u'style'))
+# style.update(refstyle)
+# clone.setAttributeNS(None,'style',simplestyle.formatStyle(style))
+# applyTransformToNode(parseTransform(node.getAttributeNS(None,'transform')),clone)
+# if doReplace:
+# parent=node.parentNode
+# parent.insertBefore(clone,node)
+# parent.removeChild(node)
+# del aList[id]
+# aList.update(self.expandGroupsUnlinkClones({cloneid:clone}))
+# return aList
+
+################################
+#-- Object conversion ----------
+################################
+
+ def rectToPath(self,node,doReplace=True):
+ if node.tagName == 'rect':
+ x =float(node.getAttributeNS(None,u'x'))
+ y =float(node.getAttributeNS(None,u'y'))
+ try:
+ rx=float(node.getAttributeNS(None,u'rx'))
+ ry=float(node.getAttributeNS(None,u'ry'))
+ except:
+ rx=0
+ ry=0
+ w =float(node.getAttributeNS(None,u'width' ))
+ h =float(node.getAttributeNS(None,u'height'))
+ d ='M %f,%f '%(x+rx,y)
+ d+='L %f,%f '%(x+w-rx,y)
+ d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+w,y+ry)
+ d+='L %f,%f '%(x+w,y+h-ry)
+ d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+w-rx,y+h)
+ d+='L %f,%f '%(x+rx,y+h)
+ d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x,y+h-ry)
+ d+='L %f,%f '%(x,y+ry)
+ d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+rx,y)
+
+ newnode=self.document.createElement('path')
+ newnode.setAttributeNS(None,'d',d)
+ newnode.setAttributeNS(None,'id', self.uniqueId('path'))
+ newnode.setAttributeNS(None,'style',node.getAttributeNS(None,u'style'))
+ newnode.setAttributeNS(None,'transform',node.getAttributeNS(None,u'transform'))
+ fuseTransform(newnode)
+ if doReplace:
+ parent=node.parentNode
+ parent.insertBefore(newnode,node)
+ parent.removeChild(node)
+ return newnode
+
+ def objectToPath(self,node,doReplace=True):
+ #--TODO: support other object types!!!!
+ #--TODO: make sure cubicsuperpath supports A and Q commands...
+ if node.tagName == 'rect':
+ return(self.rectToPath(node,doReplace))
+ elif node.tagName == 'path':
+ attributes = node.attributes.keys()
+ for uri,attName in attributes:
+ if uri in [inkex.NSS[u'sodipodi'],inkex.NSS[u'inkscape']]:
+# if attName not in ["d","id","style","transform"]:
+ node.removeAttributeNS(uri,attName)
+ fuseTransform(node)
+ return node
+ else:
+ inkex.debug("Please first convert objects to paths!...(got '%s')"%node.tagName)
+ return None
+
+ def objectsToPaths(self,aList,doReplace=True):
+ newSelection={}
+ for id,node in aList.items():
+ newnode=self.objectToPath(node,self.document)
+ del aList[id]
+ aList[newnode.getAttributeNS(None,u'id')]=newnode
+
+
+################################
+#-- Action ----------
+################################
+
+ #-- overwrite this method in subclasses...
+ def effect(self):
+ #self.duplicateNodes(self.selected)
+ self.expandGroupsUnlinkClones(self.selected, True)
+ self.objectsToPaths(self.selected, True)
+ self.bbox=self.computeBBox(self.selected)
+ for id, node in self.selected.iteritems():
+ if node.tagName == 'path':
+ d = node.attributes.getNamedItem('d')
+ p = cubicsuperpath.parsePath(d.value)
+
+ #do what ever you want with p!
+
+ d.value = cubicsuperpath.formatPath(p)
+
+
+class Diffeo(PathModifier):
+ def __init__(self):
+ inkex.Effect.__init__(self)
+
+ def applyDiffeo(self,bpt,vects=()):
+ '''
+ bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt.
+ Defaults to identity!
+ '''
+ for v in vects:
+ v[0]-=bpt[0]
+ v[1]-=bpt[1]
+
+ #-- your transformations go here:
+ #x,y=bpt
+ #bpt[0]=f(x,y)
+ #bpt[1]=g(x,y)
+ #for v in vects:
+ # vx,vy=v
+ # v[0]=df/dx(x,y)*vx+df/dy(x,y)*vy
+ # v[1]=dg/dx(x,y)*vx+dg/dy(x,y)*vy
+ #
+ #-- !caution! y-axis is pointing downward!
+
+ for v in vects:
+ v[0]+=bpt[0]
+ v[1]+=bpt[1]
+
+
+ def effect(self):
+ #self.duplicateNodes(self.selected)
+ self.expandGroupsUnlinkClones(self.selected, True)
+ self.expandGroups(self.selected, True)
+ self.objectsToPaths(self.selected, True)
+ self.bbox=self.computeBBox(self.selected)
+ for id, node in self.selected.iteritems():
+ if node.tagName == 'path':
+ d = node.attributes.getNamedItem('d')
+ p = cubicsuperpath.parsePath(d.value)
+
+ for sub in p:
+ for ctlpt in sub:
+ self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2]))
+
+ d.value = cubicsuperpath.formatPath(p)
+
+#e = Diffeo()
+#e.affect()
+ + diff --git a/share/extensions/rubberstretch.py b/share/extensions/rubberstretch.py new file mode 100644 index 000000000..ae52d1c84 --- /dev/null +++ b/share/extensions/rubberstretch.py @@ -0,0 +1,79 @@ +#!/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
+
+'''
+
+import inkex, cubicsuperpath, bezmisc, pathmodifier
+import copy, math, re, random, xml.xpath
+
+class RubberStretch(pathmodifier.Diffeo):
+ def __init__(self):
+ pathmodifier.Diffeo.__init__(self)
+ self.OptionParser.add_option("-r", "--ratio",
+ action="store", type="float",
+ dest="ratio", default=0.5)
+ self.OptionParser.add_option("-c", "--curve",
+ action="store", type="float",
+ dest="curve", default=0.5)
+
+ def applyDiffeo(self,bpt,vects=()):
+ for v in vects:
+ v[0]-=bpt[0]
+ v[1]-=bpt[1]
+ v[1]*=-1
+ bpt[1]*=-1
+ a=self.options.ratio/100
+ b=min(self.options.curve/100,0.99)
+ x0= (self.bbox[0]+self.bbox[1])/2
+ y0=-(self.bbox[2]+self.bbox[3])/2
+ 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)
+ sy=(1+b*(y/h+1)*(y/h-1))*2**(-a)
+ bpt[0]=x0+x*sy
+ bpt[1]=y0+y/sx
+ for v in vects:
+ dx,dy=v
+ dXdx=sy
+ dXdy= x*2*b*y/h/h*2**(-a)
+ dYdx=-y*2*b*x/w/w*2**(-a)/sx/sx
+ dYdy=1/sx
+ v[0]=dXdx*dx+dXdy*dy
+ v[1]=dYdx*dx+dYdy*dy
+
+ #--spherify
+# s=((x*x+y*y)/(w*w+h*h))**(-a/2)
+# bpt[0]=x0+s*x
+# bpt[1]=y0+s*y
+# for v in vects:
+# dx,dy=v
+# v[0]=(1-a/2/(x*x+y*y)*2*x*x)*s*dx+( -a/2/(x*x+y*y)*2*y*x)*s*dy
+# v[1]=( -a/2/(x*x+y*y)*2*x*y)*s*dx+(1-a/2/(x*x+y*y)*2*y*y)*s*dy
+
+ for v in vects:
+ v[0]+=bpt[0]
+ v[1]+=bpt[1]
+ v[1]*=-1
+ bpt[1]*=-1
+
+e = RubberStretch()
+e.affect()
+ + |
