Code

Update JF Barraud scripts to work with actual SVN see https://bugs.launchpad.net...
authorpopolon2 <popolon2@users.sourceforge.net>
Tue, 29 Jan 2008 00:23:07 +0000 (00:23 +0000)
committerpopolon2 <popolon2@users.sourceforge.net>
Tue, 29 Jan 2008 00:23:07 +0000 (00:23 +0000)
share/extensions/pathalongpath.inx
share/extensions/pathalongpath.py
share/extensions/pathmodifier.py
share/extensions/rubberstretch.py
share/extensions/simpletransform.py [new file with mode: 0644]

index ce489a9e2c2e260b458bf3b6c3e11b6a6a984f88..3011f015af36c8411c8bee3febde1a527adc4121 100644 (file)
@@ -1,38 +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
+<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 arbitrary "skeleton" paths. The pattern is the top most object in the selection. (groups of paths/shapes/clones... allowed)</param>\r
 \r
-       <param name="copymode" type="enum" _gui-text="Copies of the pattern:">\r
-              <item value="Single">Single</item>\r
-              <item value="Single, stretched">Single, stretched</item>\r
-              <item value="Repeated">Repeated</item>\r
-              <item value="Repeated, stretched">Repeated, stretched</item>\r
-       </param>\r
+<param name="copymode" type="enum" _gui-text="Copies of the pattern:">\r
+       <item value="Single">Single</item>\r
+       <item value="Single, stretched">Single, stretched</item>\r
+       <item value="Repeated">Repeated</item>\r
+       <item value="Repeated, stretched">Repeated, stretched</item>\r
+</param>\r
 \r
-       <param name="kind" type="enum" _gui-text="Deformation type:">\r
-              <item value="Snake">Snake</item>\r
-              <item value="Ribbon">Ribbon</item>\r
-       </param>\r
+<param name="kind" type="enum" _gui-text="Deformation type:">\r
+       <item value="Snake">Snake</item>\r
+       <item value="Ribbon">Ribbon</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
+<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
+<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
+<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
+<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
+    \r
index e49aadeb46ed1d43b62e259928533f8ecea4fdc2..a8285b9f875a370f009ff6637f48f53bc3880a82 100644 (file)
@@ -32,7 +32,7 @@ they move and rotate, deforming the pattern.
 '''\r
 \r
 import inkex, cubicsuperpath, bezmisc\r
-import pathmodifier \r
+import pathmodifier,simpletransform\r
 \r
 import copy, math, re, random\r
 \r
@@ -118,9 +118,16 @@ class PathAlongPath(pathmodifier.Diffeo):
                         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
+\r
+        idList=self.options.ids\r
+        idList=pathmodifier.zSort(self.document.getroot(),idList)\r
+        id = idList[-1]\r
         self.patterns={id:self.selected[id]}\r
+\r
+##        ##first selected->pattern, all but first selected-> skeletons\r
+##        id = self.options.ids[-1]\r
+##        self.patterns={id:self.selected[id]}\r
+\r
         if self.options.duplicate:\r
             self.patterns=self.duplicateNodes(self.patterns)\r
         self.expandGroupsUnlinkClones(self.patterns, True, True)\r
@@ -203,7 +210,8 @@ class PathAlongPath(pathmodifier.Diffeo):
             self.options.repeat =True\r
             self.options.stretch=True\r
 \r
-        bbox=self.computeBBox(self.patterns)\r
+        bbox=simpletransform.computeBBox(self.patterns.values())\r
+                    \r
         if self.options.vertical:\r
             #flipxy(bbox)...\r
             bbox=(-bbox[3],-bbox[2],-bbox[1],-bbox[0])\r
@@ -233,12 +241,13 @@ class PathAlongPath(pathmodifier.Diffeo):
                         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
+\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
+                            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
index baa31fca221d986901d23fa0aceb36b57a889391..a7e312ae06a6d44ec43584c91d37f227485499e5 100644 (file)
@@ -16,117 +16,35 @@ You should have received a copy of the GNU General Public License
 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
+This code defines a basic class (PathModifier) of effects whose purpose is\r
+to somehow deform given objects: one common tasks for all such effect is to\r
+convert shapes, groups, clones to paths. The class has several functions to\r
+make this (more or less!) easy.\r
+As an exemple, a second class (Diffeo) is derived from it,\r
+to implement deformations of the form X=f(x,y), Y=g(x,y)...\r
+\r
+TODO: Several handy functions are defined, that might in fact be of general\r
+interest and that should be shipped out in separate files...\r
 '''\r
 import inkex, cubicsuperpath, bezmisc, simplestyle\r
+from simpletransform import *\r
 import copy, math, re, random\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.get("transform"))\r
-    newtransf=formatTransform(composeTransform(mat,m))\r
-    node.set("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
-    t = node.get("transform")\r
-    if t == None:\r
-        return\r
-    m=parseTransform(t)\r
-    d = node.get('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.set('d', cubicsuperpath.formatPath(p))\r
-    del node.attrib["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
+##-- zOrder computation...\r
+##-- this should be shipped out in a separate file. inkex.py?\r
+\r
+def zSort(inNode,idList):\r
+    sortedList=[]\r
+    theid = inNode.get("id")\r
+    if theid in idList:\r
+        sortedList.append(theid)\r
+    for child in inNode:\r
+        if len(sortedList)==len(idList):\r
+            break\r
+        sortedList+=zSort(child,idList)\r
+    return sortedList\r
 \r
 \r
 class PathModifier(inkex.Effect):\r
@@ -136,14 +54,6 @@ class PathModifier(inkex.Effect):
 ##################################\r
 #-- Selectionlists manipulation --\r
 ##################################\r
-    def computeBBox(self, aList):\r
-        bbox=None\r
-        for id, node in aList.iteritems():\r
-            if node.tag == inkex.addNS('path','svg') or node.tag == 'path':\r
-                d = node.get('d')\r
-                p = cubicsuperpath.parsePath(d)\r
-                bbox=boxunion(roughBBox(p),bbox)\r
-        return bbox\r
 \r
     def duplicateNodes(self, aList):\r
         clones={}\r
@@ -171,7 +81,7 @@ class PathModifier(inkex.Effect):
                     if transferTransform:\r
                         applyTransformToNode(mat,child)\r
                     aList.update(self.expandGroups({child.get('id'):child}))\r
-                if transferTransform:\r
+                if transferTransform and node.get("transform"):\r
                     del node.attrib["transform"]\r
                 del aList[id]\r
         return(aList)\r
@@ -183,25 +93,17 @@ class PathModifier(inkex.Effect):
                 self.expandGroups(aList,transferTransform)\r
                 self.expandGroupsUnlinkClones(aList,transferTransform,doReplace)\r
                 #Hum... not very efficient if there are many clones of groups...\r
+\r
             elif node.tag == inkex.addNS('use','svg') or node.tag=='use':\r
-                refid=node.get(inkex.addNS('href','xlink'))\r
-                path = '//*[@id="%s"]' % refid[1:]\r
-                refnode = self.document.getroot().xpath(path,inkex.NSS)\r
-                newnode=copy.deepcopy(refnode)\r
-                self.recursNewIds(newnode)\r
-\r
-                s = node.get('style')\r
-                if s:\r
-                    style=simplestyle.parseStyle(s)\r
-                    refstyle=simplestyle.parseStyle(refnode.get('style'))\r
-                    style.update(refstyle)\r
-                    newnode.set('style',simplestyle.formatStyle(style))\r
-                    applyTransformToNode(parseTransform(node.get('transform')),newnode)\r
-                    if doReplace:\r
-                        parent=node.getparent()\r
-                        parent.insert(node.index,newnode)\r
-                        parent.remove(node)\r
-                    del aList[id]\r
+                refnode=self.refNode(node)\r
+                newnode=self.unlinkClone(node,doReplace)\r
+                del aList[id]\r
+\r
+                style = simplestyle.parseStyle(node.get('style') or "")\r
+                refstyle=simplestyle.parseStyle(refnode.get('style') or "")\r
+                style.update(refstyle)\r
+                newnode.set('style',simplestyle.formatStyle(style))\r
+\r
                 newid=newnode.get('id')\r
                 aList.update(self.expandGroupsUnlinkClones({newid:newnode},transferTransform,doReplace))\r
         return aList\r
@@ -212,38 +114,31 @@ class PathModifier(inkex.Effect):
         for child in node:\r
             self.recursNewIds(child)\r
             \r
+    def refNode(self,node):\r
+        if node.get(inkex.addNS('href','xlink')):\r
+            refid=node.get(inkex.addNS('href','xlink'))\r
+            path = '//*[@id="%s"]' % refid[1:]\r
+            newNode = self.document.getroot().xpath(path,inkex.NSS)[0]\r
+            return newNode\r
+        else:\r
+            raise AssertionError, "Trying to follow empty xlink.href attribute."\r
+\r
+    def unlinkClone(self,node,doReplace):\r
+        if node.tag == inkex.addNS('use','svg') or node.tag=='use':\r
+            newNode = copy.deepcopy(self.refNode(node))\r
+            self.recursNewIds(newNode)\r
+            applyTransformToNode(parseTransform(node.get('transform')),newNode)\r
+\r
+            if doReplace:\r
+                parent=node.getparent()\r
+                parent.insert(parent.index(node),newNode)\r
+                parent.remove(node)\r
+\r
+            return newNode\r
+        else:\r
+            raise AssertionError, "Only clones can be unlinked..."\r
+\r
 \r
-       \r
-# Had not been rewritten for ElementTree\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
@@ -282,27 +177,52 @@ class PathModifier(inkex.Effect):
                 fuseTransform(newnode)\r
             if doReplace:\r
                 parent=node.getparent()\r
-                parent.insert(node.index,newnode)\r
+                parent.insert(parent.index(node),newnode)\r
                 parent.remove(node)\r
             return newnode\r
 \r
+    def groupToPath(self,node,doReplace=True):\r
+        if node.tag == inkex.addNS('g','svg'):\r
+            newNode = inkex.etree.SubElement(self.current_layer,inkex.addNS('path','svg'))    \r
+\r
+            newstyle = simplestyle.parseStyle(node.get('style') or "")\r
+            newp = []\r
+            for child in node:\r
+                childstyle = simplestyle.parseStyle(child.get('style') or "")\r
+                childstyle.update(newstyle)\r
+                newstyle.update(childstyle)\r
+                childAsPath = self.objectToPath(child,False)\r
+                newp += cubicsuperpath.parsePath(childAsPath.get('d'))\r
+            newNode.set('d',cubicsuperpath.formatPath(newp))\r
+            newNode.set('style',simplestyle.formatStyle(newstyle))\r
+\r
+            self.current_layer.remove(newNode)\r
+            if doReplace:\r
+                parent=node.getparent()\r
+                parent.insert(parent.index(node),newNode)\r
+                parent.remove(node)\r
+\r
+            return newNode\r
+        else:\r
+            raise AssertionError\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.tag == inkex.addNS('rect','svg'):\r
             return(self.rectToPath(node,doReplace))\r
+        if node.tag == inkex.addNS('g','svg'):\r
+            return(self.groupToPath(node,doReplace))\r
         elif node.tag == inkex.addNS('path','svg') or node.tag == 'path':\r
-            attributes = node.keys()\r
-            for attName in attributes:\r
-                uri = None\r
-                if attName[0] == '{':\r
-                    uri,attName = attName.split('}')\r
-                    uri = uri[1:]\r
-                if uri in [inkex.NSS[u'sodipodi'],inkex.NSS[u'inkscape']]:\r
-                    #if attName not in ["d","id","style","transform"]:\r
-                    del node.attrib[inkex.addNS(attName,uri)]\r
+            #remove inkscape attributes, otherwise any modif of 'd' will be discarded!\r
+            for attName in node.attrib.keys():\r
+                if ("sodipodi" in attName) or ("inkscape" in attName):\r
+                    del node.attrib[attName]\r
             fuseTransform(node)\r
             return node\r
+        elif node.tag == inkex.addNS('use','svg') or node.tag == 'use':\r
+            newNode = self.unlinkClone(node,doReplace)\r
+            return self.objectToPath(newNode,doReplace)\r
         else:\r
             inkex.debug("Please first convert objects to paths!...(got '%s')"%node.tag)\r
             return None\r
@@ -310,7 +230,7 @@ class PathModifier(inkex.Effect):
     def objectsToPaths(self,aList,doReplace=True):\r
         newSelection={}\r
         for id,node in aList.items():\r
-            newnode=self.objectToPath(node,self.document)\r
+            newnode=self.objectToPath(node,doReplace)\r
             del aList[id]\r
             aList[newnode.get('id')]=newnode\r
 \r
@@ -322,17 +242,17 @@ class PathModifier(inkex.Effect):
     #-- overwrite this method in subclasses...\r
     def effect(self):\r
         #self.duplicateNodes(self.selected)\r
-        self.expandGroupsUnlinkClones(self.selected, True)\r
+        #self.expandGroupsUnlinkClones(self.selected, True)\r
         self.objectsToPaths(self.selected, True)\r
-        self.bbox=self.computeBBox(self.selected)\r
+        self.bbox=computeBBox(self.selected.values())\r
         for id, node in self.selected.iteritems():\r
             if node.tag == inkex.addNS('path','svg'):\r
                 d = node.get('d')\r
                 p = cubicsuperpath.parsePath(d)\r
 \r
-               #do what ever you want with p!\r
+                #do what ever you want with p!\r
 \r
-               node.set('d',cubicsuperpath.formatPath(p))\r
+                node.set('d',cubicsuperpath.formatPath(p))\r
 \r
 \r
 class Diffeo(PathModifier):\r
@@ -369,7 +289,7 @@ class Diffeo(PathModifier):
         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
+        self.bbox=computeBBox(self.selected.values())\r
         for id, node in self.selected.iteritems():\r
             if node.tag == inkex.addNS('path','svg') or node.tag=='path':\r
                 d = node.get('d')\r
@@ -383,5 +303,5 @@ class Diffeo(PathModifier):
 \r
 #e = Diffeo()\r
 #e.affect()\r
-
-                
+\r
+    \r
index 1ea533cf0c8dd7fb63c0796a18d890f49b807101..60b1738c63bea7ac2a8a8d144628ccc18c187c32 100644 (file)
@@ -20,7 +20,7 @@ barraud@math.univ-lille1.fr
 '''\r
 \r
 import inkex, cubicsuperpath, bezmisc, pathmodifier\r
-import copy, math, re, random, xml.xpath\r
+import copy, math, re\r
 \r
 class RubberStretch(pathmodifier.Diffeo):\r
     def __init__(self):\r
@@ -45,7 +45,7 @@ class RubberStretch(pathmodifier.Diffeo):
         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
+        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
@@ -75,5 +75,5 @@ class RubberStretch(pathmodifier.Diffeo):
 \r
 e = RubberStretch()\r
 e.affect()\r
-
-                
+\r
+    \r
diff --git a/share/extensions/simpletransform.py b/share/extensions/simpletransform.py
new file mode 100644 (file)
index 0000000..983874f
--- /dev/null
@@ -0,0 +1,164 @@
+#!/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
+This code defines several functions to make handling of transform\r
+attribute easier.\r
+'''\r
+import inkex, cubicsuperpath, bezmisc, simplestyle\r
+import copy, math, re\r
+\r
+def parseTransform(transf,mat=[[1.0,0.0,0.0],[0.0,1.0,0.0]]):\r
+    if transf=="" or transf==None:\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.get("transform"))\r
+    newtransf=formatTransform(composeTransform(mat,m))\r
+    node.set("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 applyTransformToPath(mat,path):\r
+    for comp in path:\r
+        for ctl in comp:\r
+            for pt in ctl:\r
+                applyTransformToPoint(mat,pt)\r
+\r
+def fuseTransform(node):\r
+    if node.get('d')==None:\r
+        #FIX ME: how do you raise errors?\r
+        raise AssertionError, 'can not fuse "transform" of elements that have no "d" attribute'\r
+    t = node.get("transform")\r
+    if t == None:\r
+        return\r
+    m = parseTransform(t)\r
+    d = node.get('d')\r
+    p = cubicsuperpath.parsePath(d)\r
+    applyTransformToPath(m,p)\r
+    node.set('d', cubicsuperpath.formatPath(p))\r
+    del node.attrib["transform"]\r
+\r
+####################################################################\r
+##-- Some functions to compute a rough bbox of a given list of objects.\r
+##-- this should be shipped out in an separate file...\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
+def computeBBox(aList,mat=[[1,0,0],[0,1,0]]):\r
+    bbox=None\r
+    for node in aList:\r
+        m = parseTransform(node.get('transform'))\r
+        m = composeTransform(mat,m)\r
+        #TODO: text not supported!\r
+        if node.get("d"):\r
+            d = node.get('d')\r
+            p = cubicsuperpath.parsePath(d)\r
+            applyTransformToPath(m,p)\r
+            bbox=boxunion(roughBBox(p),bbox)\r
+\r
+        if  node.tag == inkex.addNS('use','svg') or node.tag=='use':\r
+            refid=node.get(inkex.addNS('href','xlink'))\r
+            path = '//*[@id="%s"]' % refid[1:]\r
+            refnode = node.getroottree().xpath(path,inkex.NSS)\r
+            bbox=boxunion(computeBBox(refnode,m),bbox)\r
+            \r
+        bbox=boxunion(computeBBox(node,m),bbox)\r
+    return bbox\r
+\r
+\r