Code

add Pattern on Path (patch 1594529)
authorbuliabyak <buliabyak@users.sourceforge.net>
Thu, 23 Nov 2006 20:29:45 +0000 (20:29 +0000)
committerbuliabyak <buliabyak@users.sourceforge.net>
Thu, 23 Nov 2006 20:29:45 +0000 (20:29 +0000)
share/extensions/Makefile.am
share/extensions/pathalongpath.inx [new file with mode: 0644]
share/extensions/pathalongpath.py [new file with mode: 0644]
share/extensions/pathmodifier.py [new file with mode: 0644]
share/extensions/rubberstretch.py [new file with mode: 0644]

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