Code

Extensions. XAML export improvements.
[inkscape.git] / share / extensions / pathalongpath.py
index 3901069c9089976c00f7c429ee1fe54235758db8..7a2fa09eabffe05ec601bc3a1b84a713dad89fed 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
-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\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
-        if len(self.options.ids)<2:\r
-            inkex.debug("This extension requires that you select two paths.")\r
-            return\r
-        self.prepareSelectionList()\r
-        self.options.wave = (self.options.kind=="Ribbon")\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.tag == inkex.addNS('path','svg'):\r
-                d = node.get('d')\r
-                p0 = cubicsuperpath.parsePath(d)\r
-                if self.options.vertical:\r
-                    flipxy(p0)\r
-\r
-                newp=[]\r
-                for skelnode in self.skeletons.itervalues(): \r
-                    self.curSekeleton=cubicsuperpath.parsePath(skelnode.get('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
-                node.set('d', cubicsuperpath.formatPath(newp))\r
-\r
-e = PathAlongPath()\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
+
+Quick description:
+This script deforms an object (the pattern) along other paths (skeletons)...
+The first selected object is the pattern
+the last selected ones are the skeletons.
+
+Imagine a straight horizontal line L in the middle of the bounding box of the pattern.
+Consider the normal bundle of L: the collection of all the vertical lines meeting L.
+Consider this as the initial state of the plane; in particular, think of the pattern
+as painted on these lines.
+
+Now move and bend L to make it fit a skeleton, and see what happens to the normals:
+they move and rotate, deforming the pattern.
+'''
+
+import inkex, cubicsuperpath, bezmisc
+import pathmodifier,simpletransform
+import copy, math, re, random
+import gettext
+_ = gettext.gettext
+
+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")
+        self.OptionParser.add_option("--tab",
+                        action="store", type="string",
+                        dest="tab",
+                        help="The selected UI-tab when OK was pressed")
+
+    def prepareSelectionList(self):
+
+        idList=self.options.ids
+        idList=pathmodifier.zSort(self.document.getroot(),idList)
+        id = idList[-1]
+        self.patterns={id:self.selected[id]}
+
+##        ##first selected->pattern, all but first selected-> skeletons
+##        id = self.options.ids[-1]
+##        self.patterns={id:self.selected[id]}
+
+        if self.options.duplicate:
+            self.patterns=self.duplicateNodes(self.patterns)
+        self.expandGroupsUnlinkClones(self.patterns, True, True)
+        self.objectsToPaths(self.patterns)
+        del self.selected[id]
+
+        self.skeletons=self.selected
+        self.expandGroupsUnlinkClones(self.skeletons, True, False)
+        self.objectsToPaths(self.skeletons)
+
+    def lengthtotime(self,l):
+        '''
+        Recieves an arc length l, and returns the index of the segment in self.skelcomp 
+        containing the coresponding point, to gether with the position of the point on this segment.
+
+        If the deformer is closed, do computations modulo the toal length.
+        '''
+        if self.skelcompIsClosed:
+            l=l % sum(self.lengths)
+        if l<=0:
+            return 0,l/self.lengths[0]
+        i=0
+        while (i<len(self.lengths)) and (self.lengths[i]<=l):
+            l-=self.lengths[i]
+            i+=1
+        t=l/self.lengths[min(i,len(self.lengths)-1)]
+        return i, t
+
+    def applyDiffeo(self,bpt,vects=()):
+        '''
+        The kernel of this stuff:
+        bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt.
+        '''
+        s=bpt[0]-self.skelcomp[0][0]
+        i,t=self.lengthtotime(s)
+        if i==len(self.skelcomp)-1:
+            x,y=bezmisc.tpoint(self.skelcomp[i-1],self.skelcomp[i],1+t)
+            dx=(self.skelcomp[i][0]-self.skelcomp[i-1][0])/self.lengths[-1]
+            dy=(self.skelcomp[i][1]-self.skelcomp[i-1][1])/self.lengths[-1]
+        else:
+            x,y=bezmisc.tpoint(self.skelcomp[i],self.skelcomp[i+1],t)
+            dx=(self.skelcomp[i+1][0]-self.skelcomp[i][0])/self.lengths[i]
+            dy=(self.skelcomp[i+1][1]-self.skelcomp[i][1])/self.lengths[i]
+
+        vx=0
+        vy=bpt[1]-self.skelcomp[0][1]
+        if self.options.wave:
+            bpt[0]=x+vx*dx
+            bpt[1]=y+vy+vx*dy
+        else:
+            bpt[0]=x+vx*dx-vy*dy
+            bpt[1]=y+vx*dy+vy*dx
+
+        for v in vects:
+            vx=v[0]-self.skelcomp[0][0]-s
+            vy=v[1]-self.skelcomp[0][1]
+            if self.options.wave:
+                v[0]=x+vx*dx
+                v[1]=y+vy+vx*dy
+            else:
+                v[0]=x+vx*dx-vy*dy
+                v[1]=y+vx*dy+vy*dx
+
+    def effect(self):
+        if len(self.options.ids)<2:
+            inkex.errormsg(_("This extension requires two selected paths."))
+            return
+        self.prepareSelectionList()
+        self.options.wave = (self.options.kind=="Ribbon")
+        if self.options.copymode=="Single":
+            self.options.repeat =False
+            self.options.stretch=False
+        elif self.options.copymode=="Repeated":
+            self.options.repeat =True
+            self.options.stretch=False
+        elif self.options.copymode=="Single, stretched":
+            self.options.repeat =False
+            self.options.stretch=True
+        elif self.options.copymode=="Repeated, stretched":
+            self.options.repeat =True
+            self.options.stretch=True
+
+        bbox=simpletransform.computeBBox(self.patterns.values())
+                    
+        if self.options.vertical:
+            #flipxy(bbox)...
+            bbox=(-bbox[3],-bbox[2],-bbox[1],-bbox[0])
+
+        width=bbox[1]-bbox[0]
+        dx=width+self.options.space
+        if dx < 0.01:
+            exit(_("The total length of the pattern is too small :\nPlease choose a larger object or set 'Space between copies' > 0"))
+
+        for id, node in self.patterns.iteritems():
+            if node.tag == inkex.addNS('path','svg') or node.tag=='path':
+                d = node.get('d')
+                p0 = cubicsuperpath.parsePath(d)
+                if self.options.vertical:
+                    flipxy(p0)
+
+                newp=[]
+                for skelnode in self.skeletons.itervalues(): 
+                    self.curSekeleton=cubicsuperpath.parsePath(skelnode.get('d'))
+                    if self.options.vertical:
+                        flipxy(self.curSekeleton)
+                    for comp in self.curSekeleton:
+                        p=copy.deepcopy(p0)
+                        self.skelcomp,self.lengths=linearize(comp)
+                        #!!!!>----> TODO: really test if path is closed! end point==start point is not enough!
+                        self.skelcompIsClosed = (self.skelcomp[0]==self.skelcomp[-1])
+
+                        length=sum(self.lengths)
+                        xoffset=self.skelcomp[0][0]-bbox[0]+self.options.toffset
+                        yoffset=self.skelcomp[0][1]-(bbox[2]+bbox[3])/2-self.options.noffset
+
+
+                        if self.options.repeat:
+                            NbCopies=max(1,int(round((length+self.options.space)/dx)))
+                            width=dx*NbCopies
+                            if not self.skelcompIsClosed:
+                                width-=self.options.space
+                            bbox=bbox[0],bbox[0]+width, bbox[2],bbox[3]
+                            new=[]
+                            for sub in p:
+                                for i in range(0,NbCopies,1):
+                                    new.append(copy.deepcopy(sub))
+                                    offset(sub,dx,0)
+                            p=new
+
+                        for sub in p:
+                            offset(sub,xoffset,yoffset)
+
+                        if self.options.stretch:
+                            for sub in p:
+                                stretch(sub,length/width,1,self.skelcomp[0])
+
+                        for sub in p:
+                            for ctlpt in sub:
+                                self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2]))
+
+                        if self.options.vertical:
+                            flipxy(p)
+                        newp+=p
+
+                node.set('d', cubicsuperpath.formatPath(newp))
+
+if __name__ == '__main__':
+    e = PathAlongPath()
+    e.affect()
+
+                    
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99