Code

svn propset svn:eol-style native *.py
[inkscape.git] / share / extensions / pathmodifier.py
index 76b9d77b133f06876ed1337426b8fe7eee5ce08e..bfc72f074d1c5a4eaef30c52c8b855eca7a3394c 100644 (file)
-#!/usr/bin/env python\r
-'''\r
-Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr\r
-\r
-This program is free software; you can redistribute it and/or modify\r
-it under the terms of the GNU General Public License as published by\r
-the Free Software Foundation; either version 2 of the License, or\r
-(at your option) any later version.\r
-\r
-This program is distributed in the hope that it will be useful,\r
-but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
-GNU General Public License for more details.\r
-\r
-You should have received a copy of the GNU General Public License\r
-along with this program; if not, write to the Free Software\r
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
-barraud@math.univ-lille1.fr\r
-\r
-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
-####################################################################\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
-    def __init__(self):\r
-        inkex.Effect.__init__(self)\r
-\r
-##################################\r
-#-- Selectionlists manipulation --\r
-##################################\r
-\r
-    def duplicateNodes(self, aList):\r
-        clones={}\r
-        for id,node in aList.iteritems():\r
-            clone=copy.deepcopy(node)\r
-            #!!!--> should it be given an id?\r
-            #seems to work without this!?!\r
-            myid = node.tag.split('}')[-1]\r
-            clone.set("id", self.uniqueId(myid))\r
-            node.getparent().append(clone)\r
-            clones[clone.get("id")]=clone\r
-        return(clones)\r
-\r
-    def uniqueId(self, prefix):\r
-        id="%s%04i"%(prefix,random.randint(0,9999))\r
-        while len(self.document.getroot().xpath('//*[@id="%s"]' % id,namespaces=inkex.NSS)):\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.tag == inkex.addNS('g','svg') or node.tag=='g':\r
-                mat=parseTransform(node.get("transform"))\r
-                for child in node:\r
-                    if transferTransform:\r
-                        applyTransformToNode(mat,child)\r
-                    aList.update(self.expandGroups({child.get('id'):child}))\r
-                if transferTransform and node.get("transform"):\r
-                    del node.attrib["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.tag == inkex.addNS('g','svg') or node.tag=='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
-\r
-            elif node.tag == inkex.addNS('use','svg') or node.tag=='use':\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
-    \r
-    def recursNewIds(self,node):\r
-        if node.get('id'):\r
-            node.set('id',self.uniqueId(node.tag))\r
-        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, namespaces=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
-################################\r
-#-- Object conversion ----------\r
-################################\r
-\r
-    def rectToPath(self,node,doReplace=True):\r
-        if node.tag == inkex.addNS('rect','svg'):\r
-            x =float(node.get('x'))\r
-            y =float(node.get('y'))\r
-            #FIXME: no exception anymore and sometimes just one\r
-            try:\r
-                rx=float(node.get('rx'))\r
-                ry=float(node.get('ry'))\r
-            except:\r
-                rx=0\r
-                ry=0\r
-            w =float(node.get('width' ))\r
-            h =float(node.get('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=inkex.etree.Element('path')\r
-            newnode.set('d',d)\r
-            newnode.set('id', self.uniqueId('path'))\r
-            newnode.set('style',node.get('style'))\r
-            nnt = node.get('transform')\r
-            if nnt:\r
-                newnode.set('transform',nnt)\r
-                fuseTransform(newnode)\r
-            if doReplace:\r
-                parent=node.getparent()\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
-            #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
-\r
-    def objectsToPaths(self,aList,doReplace=True):\r
-        newSelection={}\r
-        for id,node in aList.items():\r
-            newnode=self.objectToPath(node,doReplace)\r
-            del aList[id]\r
-            aList[newnode.get('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=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
-\r
-                node.set('d',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=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
-                p = cubicsuperpath.parsePath(d)\r
-\r
-                for sub in p:\r
-                    for ctlpt in sub:\r
-                        self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2]))\r
-\r
-                node.set('d',cubicsuperpath.formatPath(p))\r
-\r
-#e = Diffeo()\r
-#e.affect()\r
-\r
-    \r
+#!/usr/bin/env python
+'''
+Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+barraud@math.univ-lille1.fr
+
+This code defines a basic class (PathModifier) of effects whose purpose is
+to somehow deform given objects: one common tasks for all such effect is to
+convert shapes, groups, clones to paths. The class has several functions to
+make this (more or less!) easy.
+As an exemple, a second class (Diffeo) is derived from it,
+to implement deformations of the form X=f(x,y), Y=g(x,y)...
+
+TODO: Several handy functions are defined, that might in fact be of general
+interest and that should be shipped out in separate files...
+'''
+import inkex, cubicsuperpath, bezmisc, simplestyle
+from simpletransform import *
+import copy, math, re, random
+
+####################################################################
+##-- zOrder computation...
+##-- this should be shipped out in a separate file. inkex.py?
+
+def zSort(inNode,idList):
+    sortedList=[]
+    theid = inNode.get("id")
+    if theid in idList:
+        sortedList.append(theid)
+    for child in inNode:
+        if len(sortedList)==len(idList):
+            break
+        sortedList+=zSort(child,idList)
+    return sortedList
+
+
+class PathModifier(inkex.Effect):
+    def __init__(self):
+        inkex.Effect.__init__(self)
+
+##################################
+#-- Selectionlists manipulation --
+##################################
+
+    def duplicateNodes(self, aList):
+        clones={}
+        for id,node in aList.iteritems():
+            clone=copy.deepcopy(node)
+            #!!!--> should it be given an id?
+            #seems to work without this!?!
+            myid = node.tag.split('}')[-1]
+            clone.set("id", self.uniqueId(myid))
+            node.getparent().append(clone)
+            clones[clone.get("id")]=clone
+        return(clones)
+
+    def uniqueId(self, prefix):
+        id="%s%04i"%(prefix,random.randint(0,9999))
+        while len(self.document.getroot().xpath('//*[@id="%s"]' % id,namespaces=inkex.NSS)):
+            id="%s%04i"%(prefix,random.randint(0,9999))
+        return(id)
+
+    def expandGroups(self,aList,transferTransform=True):
+        for id, node in aList.items():      
+            if node.tag == inkex.addNS('g','svg') or node.tag=='g':
+                mat=parseTransform(node.get("transform"))
+                for child in node:
+                    if transferTransform:
+                        applyTransformToNode(mat,child)
+                    aList.update(self.expandGroups({child.get('id'):child}))
+                if transferTransform and node.get("transform"):
+                    del node.attrib["transform"]
+                del aList[id]
+        return(aList)
+
+    def expandGroupsUnlinkClones(self,aList,transferTransform=True,doReplace=True):
+        for id in aList.keys()[:]:     
+            node=aList[id]
+            if node.tag == inkex.addNS('g','svg') or node.tag=='g':
+                self.expandGroups(aList,transferTransform)
+                self.expandGroupsUnlinkClones(aList,transferTransform,doReplace)
+                #Hum... not very efficient if there are many clones of groups...
+
+            elif node.tag == inkex.addNS('use','svg') or node.tag=='use':
+                refnode=self.refNode(node)
+                newnode=self.unlinkClone(node,doReplace)
+                del aList[id]
+
+                style = simplestyle.parseStyle(node.get('style') or "")
+                refstyle=simplestyle.parseStyle(refnode.get('style') or "")
+                style.update(refstyle)
+                newnode.set('style',simplestyle.formatStyle(style))
+
+                newid=newnode.get('id')
+                aList.update(self.expandGroupsUnlinkClones({newid:newnode},transferTransform,doReplace))
+        return aList
+    
+    def recursNewIds(self,node):
+        if node.get('id'):
+            node.set('id',self.uniqueId(node.tag))
+        for child in node:
+            self.recursNewIds(child)
+            
+    def refNode(self,node):
+        if node.get(inkex.addNS('href','xlink')):
+            refid=node.get(inkex.addNS('href','xlink'))
+            path = '//*[@id="%s"]' % refid[1:]
+            newNode = self.document.getroot().xpath(path, namespaces=inkex.NSS)[0]
+            return newNode
+        else:
+            raise AssertionError, "Trying to follow empty xlink.href attribute."
+
+    def unlinkClone(self,node,doReplace):
+        if node.tag == inkex.addNS('use','svg') or node.tag=='use':
+            newNode = copy.deepcopy(self.refNode(node))
+            self.recursNewIds(newNode)
+            applyTransformToNode(parseTransform(node.get('transform')),newNode)
+
+            if doReplace:
+                parent=node.getparent()
+                parent.insert(parent.index(node),newNode)
+                parent.remove(node)
+
+            return newNode
+        else:
+            raise AssertionError, "Only clones can be unlinked..."
+
+
+
+################################
+#-- Object conversion ----------
+################################
+
+    def rectToPath(self,node,doReplace=True):
+        if node.tag == inkex.addNS('rect','svg'):
+            x =float(node.get('x'))
+            y =float(node.get('y'))
+            #FIXME: no exception anymore and sometimes just one
+            try:
+                rx=float(node.get('rx'))
+                ry=float(node.get('ry'))
+            except:
+                rx=0
+                ry=0
+            w =float(node.get('width' ))
+            h =float(node.get('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=inkex.etree.Element('path')
+            newnode.set('d',d)
+            newnode.set('id', self.uniqueId('path'))
+            newnode.set('style',node.get('style'))
+            nnt = node.get('transform')
+            if nnt:
+                newnode.set('transform',nnt)
+                fuseTransform(newnode)
+            if doReplace:
+                parent=node.getparent()
+                parent.insert(parent.index(node),newnode)
+                parent.remove(node)
+            return newnode
+
+    def groupToPath(self,node,doReplace=True):
+        if node.tag == inkex.addNS('g','svg'):
+            newNode = inkex.etree.SubElement(self.current_layer,inkex.addNS('path','svg'))    
+
+            newstyle = simplestyle.parseStyle(node.get('style') or "")
+            newp = []
+            for child in node:
+                childstyle = simplestyle.parseStyle(child.get('style') or "")
+                childstyle.update(newstyle)
+                newstyle.update(childstyle)
+                childAsPath = self.objectToPath(child,False)
+                newp += cubicsuperpath.parsePath(childAsPath.get('d'))
+            newNode.set('d',cubicsuperpath.formatPath(newp))
+            newNode.set('style',simplestyle.formatStyle(newstyle))
+
+            self.current_layer.remove(newNode)
+            if doReplace:
+                parent=node.getparent()
+                parent.insert(parent.index(node),newNode)
+                parent.remove(node)
+
+            return newNode
+        else:
+            raise AssertionError
+        
+    def objectToPath(self,node,doReplace=True):
+        #--TODO: support other object types!!!!
+        #--TODO: make sure cubicsuperpath supports A and Q commands... 
+        if node.tag == inkex.addNS('rect','svg'):
+            return(self.rectToPath(node,doReplace))
+        if node.tag == inkex.addNS('g','svg'):
+            return(self.groupToPath(node,doReplace))
+        elif node.tag == inkex.addNS('path','svg') or node.tag == 'path':
+            #remove inkscape attributes, otherwise any modif of 'd' will be discarded!
+            for attName in node.attrib.keys():
+                if ("sodipodi" in attName) or ("inkscape" in attName):
+                    del node.attrib[attName]
+            fuseTransform(node)
+            return node
+        elif node.tag == inkex.addNS('use','svg') or node.tag == 'use':
+            newNode = self.unlinkClone(node,doReplace)
+            return self.objectToPath(newNode,doReplace)
+        else:
+            inkex.debug("Please first convert objects to paths!...(got '%s')"%node.tag)
+            return None
+
+    def objectsToPaths(self,aList,doReplace=True):
+        newSelection={}
+        for id,node in aList.items():
+            newnode=self.objectToPath(node,doReplace)
+            del aList[id]
+            aList[newnode.get('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=computeBBox(self.selected.values())
+        for id, node in self.selected.iteritems():
+            if node.tag == inkex.addNS('path','svg'):
+                d = node.get('d')
+                p = cubicsuperpath.parsePath(d)
+
+                #do what ever you want with p!
+
+                node.set('d',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=computeBBox(self.selected.values())
+        for id, node in self.selected.iteritems():
+            if node.tag == inkex.addNS('path','svg') or node.tag=='path':
+                d = node.get('d')
+                p = cubicsuperpath.parsePath(d)
+
+                for sub in p:
+                    for ctlpt in sub:
+                        self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2]))
+
+                node.set('d',cubicsuperpath.formatPath(p))
+
+#e = Diffeo()
+#e.affect()
+
+