]> git.tokkee.org Git - inkscape.git/commitdiff

Code

Added pathscatter effect + slight modif of simpletransform... to compute bbox of...
authorjfbarraud <jfbarraud@users.sourceforge.net>
Sat, 23 Feb 2008 23:11:19 +0000 (23:11 +0000)
committerjfbarraud <jfbarraud@users.sourceforge.net>
Sat, 23 Feb 2008 23:11:19 +0000 (23:11 +0000)
share/extensions/Makefile.am
share/extensions/pathscatter.inx [new file with mode: 0644]
share/extensions/pathscatter.py [new file with mode: 0644]
share/extensions/simpletransform.py

index e6dc859fb3e1ccc3f59fcaad3b96e0d79cd88b0e..2f25f779c07be525ad2528b3244a77165dbfb28d 100644 (file)
@@ -67,6 +67,7 @@ extensions = \
        motion.py \
        outline2svg.pl  \
        pathalongpath.py\
+       pathscatter.py\
        pathmodifier.py\
        perfectboundcover.py \
        perspective.py \
@@ -174,6 +175,7 @@ modules = \
        motion.inx \
        outline2svg.inx \
        pathalongpath.inx\
+       pathscatter.inx\
        pdf_output.inx.txt \
        pdf_output_via_gs_on_win32.inx.txt \
        perfectboundcover.inx \
diff --git a/share/extensions/pathscatter.inx b/share/extensions/pathscatter.inx
new file mode 100644 (file)
index 0000000..c2618d6
--- /dev/null
@@ -0,0 +1,34 @@
+<inkscape-extension>\r
+    <_name>Scatter</_name>\r
+    <id>math.univ-lille1.barraud.pathScatter</id>\r
+       <dependency type="executable" location="extensions">pathmodifier.py</dependency>\r
+       <dependency type="executable" location="extensions">pathScatter.py</dependency>\r
+       <dependency type="executable" location="extensions">inkex.py</dependency>\r
+       <param name="title" type="description">This effect scatters a pattern 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="follow" type="boolean" _gui-text="Follow path orientation.">false</param>\r
+       <param name="stretch" type="boolean" _gui-text="Stretch spaces to fit skeleton length">false</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="copymode" type="optiongroup" _gui-text="Original pattern will be:">\r
+               <option value="move">Moved</option>\r
+       <option value="copy">Copied</option>\r
+               <option value="clone">Cloned</option>\r
+       </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">pathScatter.py</command>\r
+    </script>\r
+</inkscape-extension>\r
+\r
+                \r
diff --git a/share/extensions/pathscatter.py b/share/extensions/pathscatter.py
new file mode 100644 (file)
index 0000000..26df955
--- /dev/null
@@ -0,0 +1,261 @@
+#!/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, simpletransform \r
+from lxml import etree\r
+\r
+import copy, math, re, random\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
+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 PathScatter(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("-f", "--follow",\r
+                        action="store", type="inkbool", \r
+                        dest="follow", default=True,\r
+                        help="choose between wave or snake effect")\r
+        self.OptionParser.add_option("-s", "--stretch",\r
+                        action="store", type="inkbool", \r
+                        dest="stretch", 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
+        self.OptionParser.add_option("-c", "--copymode",\r
+                        action="store", type="string", \r
+                        dest="copymode", default="clone",\r
+                        help="duplicate pattern before deformation")\r
+\r
+    def prepareSelectionList(self):\r
+\r
+        idList=self.options.ids\r
+        idList=zSort(self.document.getroot(),idList)\r
+                \r
+        ##first selected->pattern, all but first selected-> skeletons\r
+        #id = self.options.ids[-1]\r
+        id = idList[-1]\r
+        self.patternNode=self.selected[id]\r
+\r
+        self.gNode = etree.Element('{http://www.w3.org/2000/svg}g')\r
+        self.patternNode.getparent().append(self.gNode)\r
+\r
+        if self.options.copymode=="copy":\r
+            duplist=self.duplicateNodes({id:self.patternNode})\r
+            self.patternNode = duplist.values()[0]\r
+\r
+        #TODO: allow 4th option: duplicate the first copy and clone the next ones.\r
+        if "%s"%self.options.copymode=="clone":\r
+            self.patternNode = etree.Element('{http://www.w3.org/2000/svg}use')\r
+            self.patternNode.set('{http://www.w3.org/1999/xlink}href',"#%s"%id)\r
+            self.gNode.append(self.patternNode)\r
+\r
+        self.skeletons=self.selected\r
+        del self.skeletons[id]\r
+        self.expandGroupsUnlinkClones(self.skeletons, True, False)\r
+        self.objectsToPaths(self.skeletons,False)\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 localTransformAt(self,s,follow=True):\r
+        '''\r
+        recieves a length, and returns the coresponding point and tangent of self.skelcomp\r
+        if follow is set to false, returns only the translation\r
+        '''\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
+        if follow:\r
+            mat=[[dx,-dy,x],[dy,dx,y]]\r
+        else:\r
+            mat=[[1,0,x],[0,1,y]]\r
+        return mat\r
+\r
+\r
+    def effect(self):\r
+\r
+        if len(self.options.ids)<2:\r
+            inkex.debug("This extension requires that you select two paths.")\r
+            return\r
+        self.prepareSelectionList()\r
+\r
+        #center at (0,0)\r
+        bbox=pathmodifier.computeBBox([self.patternNode])\r
+        mat=[[1,0,-(bbox[0]+bbox[1])/2],[0,1,-(bbox[2]+bbox[3])/2]]\r
+        if self.options.vertical:\r
+            bbox=[-bbox[3],-bbox[2],bbox[0],bbox[1]]\r
+            mat=simpletransform.composeTransform([[0,-1,0],[1,0,0]],mat)\r
+        mat[1][2] += self.options.noffset\r
+        simpletransform.applyTransformToNode(mat,self.patternNode)\r
+                \r
+        width=bbox[1]-bbox[0]\r
+        dx=width+self.options.space\r
+\r
+        for skelnode in self.skeletons.itervalues(): \r
+            self.curSekeleton=cubicsuperpath.parsePath(skelnode.get('d'))\r
+            for comp in self.curSekeleton:\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
+                if self.options.stretch:\r
+                    dx=width+self.options.space\r
+                    n=int((length-self.options.toffset+self.options.space)/dx)\r
+                    if n>0:\r
+                        dx=(length-self.options.toffset)/n\r
+\r
+\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
+                s=self.options.toffset\r
+                while s<=length:\r
+                    mat=self.localTransformAt(s,self.options.follow)\r
+\r
+                    clone=copy.deepcopy(self.patternNode)\r
+                    #!!!--> should it be given an id?\r
+                    #seems to work without this!?!\r
+                    myid = self.patternNode.tag.split('}')[-1]\r
+                    clone.set("id", self.uniqueId(myid))\r
+                    self.gNode.append(clone)\r
+                    \r
+                    simpletransform.applyTransformToNode(mat,clone)\r
+\r
+                    s+=dx\r
+        self.patternNode.getparent().remove(self.patternNode)\r
+\r
+\r
+e = PathScatter()\r
+e.affect()\r
+\r
+                    \r
index cf0751ca85df2f4ecc42d4d65a983c6c69c713d5..f434d80ed76c722fe47992596c59d28bf2c448ef 100644 (file)
@@ -152,10 +152,23 @@ def computeBBox(aList,mat=[[1,0,0],[0,1,0]]):
             applyTransformToPath(m,p)\r
             bbox=boxunion(roughBBox(p),bbox)\r
 \r
-        if  node.tag == inkex.addNS('use','svg') or node.tag=='use':\r
+        elif node.tag == inkex.addNS('rect','svg') or node.tag=='rect':\r
+            w = float(node.get('width'))/2.\r
+            h = float(node.get('height'))/2.\r
+            x = float(node.get('x'))\r
+            y = float(node.get('y'))\r
+            C = [x + w , y + h ]\r
+            applyTransformToPoint(mat,C)\r
+            xmin = C[0] - abs(m[0][0]) * w - abs(m[0][1]) * h\r
+            xmax = C[0] + abs(m[0][0]) * w + abs(m[0][1]) * h\r
+            ymin = C[1] - abs(m[1][0]) * w - abs(m[1][1]) * h\r
+            ymax = C[1] + abs(m[1][0]) * w + abs(m[1][1]) * h\r
+            bbox = xmin,xmax,ymin,ymax\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 = node.getroottree().xpath(path, namespaces=inkex.NSS)\r
+            refnode = node.xpath(path)\r
             bbox=boxunion(computeBBox(refnode,m),bbox)\r
             \r
         bbox=boxunion(computeBBox(node,m),bbox)\r