Code

645b7fb6cf934530871a5630be1923d111154d59
[inkscape.git] / share / extensions / pathmodifier.py
1 #!/usr/bin/env python
2 '''
3 Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 barraud@math.univ-lille1.fr
20 This code defines a basic class (PathModifier) of effects whose purpose is
21 to somehow deform given objects: one common tasks for all such effect is to
22 convert shapes, groups, clones to paths. The class has several functions to
23 make this (more or less!) easy.
24 As an exemple, a second class (Diffeo) is derived from it,
25 to implement deformations of the form X=f(x,y), Y=g(x,y)...
27 TODO: Several handy functions are defined, that might in fact be of general
28 interest and that should be shipped out in separate files...
29 '''
30 import inkex, cubicsuperpath, bezmisc, simplestyle
31 from simpletransform import *
32 import copy, math, re, random
34 ####################################################################
35 ##-- zOrder computation...
36 ##-- this should be shipped out in a separate file. inkex.py?
38 def zSort(inNode,idList):
39     sortedList=[]
40     theid = inNode.get("id")
41     if theid in idList:
42         sortedList.append(theid)
43     for child in inNode:
44         if len(sortedList)==len(idList):
45             break
46         sortedList+=zSort(child,idList)
47     return sortedList
50 class PathModifier(inkex.Effect):
51     def __init__(self):
52         inkex.Effect.__init__(self)
54 ##################################
55 #-- Selectionlists manipulation --
56 ##################################
58     def duplicateNodes(self, aList):
59         clones={}
60         for id,node in aList.iteritems():
61             clone=copy.deepcopy(node)
62             #!!!--> should it be given an id?
63             #seems to work without this!?!
64             myid = node.tag.split('}')[-1]
65             clone.set("id", self.uniqueId(myid))
66             node.getparent().append(clone)
67             clones[clone.get("id")]=clone
68         return(clones)
70     def uniqueId(self, prefix):
71         id="%s%04i"%(prefix,random.randint(0,9999))
72         while len(self.document.getroot().xpath('//*[@id="%s"]' % id,namespaces=inkex.NSS)):
73             id="%s%04i"%(prefix,random.randint(0,9999))
74         return(id)
76     def expandGroups(self,aList,transferTransform=True):
77         for id, node in aList.items():      
78             if node.tag == inkex.addNS('g','svg') or node.tag=='g':
79                 mat=parseTransform(node.get("transform"))
80                 for child in node:
81                     if transferTransform:
82                         applyTransformToNode(mat,child)
83                     aList.update(self.expandGroups({child.get('id'):child}))
84                 if transferTransform and node.get("transform"):
85                     del node.attrib["transform"]
86                 del aList[id]
87         return(aList)
89     def expandGroupsUnlinkClones(self,aList,transferTransform=True,doReplace=True):
90         for id in aList.keys()[:]:     
91             node=aList[id]
92             if node.tag == inkex.addNS('g','svg') or node.tag=='g':
93                 self.expandGroups(aList,transferTransform)
94                 self.expandGroupsUnlinkClones(aList,transferTransform,doReplace)
95                 #Hum... not very efficient if there are many clones of groups...
97             elif node.tag == inkex.addNS('use','svg') or node.tag=='use':
98                 refnode=self.refNode(node)
99                 newnode=self.unlinkClone(node,doReplace)
100                 del aList[id]
102                 style = simplestyle.parseStyle(node.get('style') or "")
103                 refstyle=simplestyle.parseStyle(refnode.get('style') or "")
104                 style.update(refstyle)
105                 newnode.set('style',simplestyle.formatStyle(style))
107                 newid=newnode.get('id')
108                 aList.update(self.expandGroupsUnlinkClones({newid:newnode},transferTransform,doReplace))
109         return aList
110     
111     def recursNewIds(self,node):
112         if node.get('id'):
113             node.set('id',self.uniqueId(node.tag))
114         for child in node:
115             self.recursNewIds(child)
116             
117     def refNode(self,node):
118         if node.get(inkex.addNS('href','xlink')):
119             refid=node.get(inkex.addNS('href','xlink'))
120             path = '//*[@id="%s"]' % refid[1:]
121             newNode = self.document.getroot().xpath(path, namespaces=inkex.NSS)[0]
122             return newNode
123         else:
124             raise AssertionError, "Trying to follow empty xlink.href attribute."
126     def unlinkClone(self,node,doReplace):
127         if node.tag == inkex.addNS('use','svg') or node.tag=='use':
128             newNode = copy.deepcopy(self.refNode(node))
129             self.recursNewIds(newNode)
130             applyTransformToNode(parseTransform(node.get('transform')),newNode)
132             if doReplace:
133                 parent=node.getparent()
134                 parent.insert(parent.index(node),newNode)
135                 parent.remove(node)
137             return newNode
138         else:
139             raise AssertionError, "Only clones can be unlinked..."
143 ################################
144 #-- Object conversion ----------
145 ################################
147     def rectToPath(self,node,doReplace=True):
148         if node.tag == inkex.addNS('rect','svg'):
149             x =float(node.get('x'))
150             y =float(node.get('y'))
151             #FIXME: no exception anymore and sometimes just one
152             try:
153                 rx=float(node.get('rx'))
154                 ry=float(node.get('ry'))
155             except:
156                 rx=0
157                 ry=0
158             w =float(node.get('width' ))
159             h =float(node.get('height'))
160             d ='M %f,%f '%(x+rx,y)
161             d+='L %f,%f '%(x+w-rx,y)
162             d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+w,y+ry)
163             d+='L %f,%f '%(x+w,y+h-ry)
164             d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+w-rx,y+h)
165             d+='L %f,%f '%(x+rx,y+h)
166             d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x,y+h-ry)
167             d+='L %f,%f '%(x,y+ry)
168             d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+rx,y)
170             newnode=inkex.etree.Element('path')
171             newnode.set('d',d)
172             newnode.set('id', self.uniqueId('path'))
173             newnode.set('style',node.get('style'))
174             nnt = node.get('transform')
175             if nnt:
176                 newnode.set('transform',nnt)
177                 fuseTransform(newnode)
178             if doReplace:
179                 parent=node.getparent()
180                 parent.insert(parent.index(node),newnode)
181                 parent.remove(node)
182             return newnode
184     def groupToPath(self,node,doReplace=True):
185         if node.tag == inkex.addNS('g','svg'):
186             newNode = inkex.etree.SubElement(self.current_layer,inkex.addNS('path','svg'))    
188             newstyle = simplestyle.parseStyle(node.get('style') or "")
189             newp = []
190             for child in node:
191                 childstyle = simplestyle.parseStyle(child.get('style') or "")
192                 childstyle.update(newstyle)
193                 newstyle.update(childstyle)
194                 childAsPath = self.objectToPath(child,False)
195                 newp += cubicsuperpath.parsePath(childAsPath.get('d'))
196             newNode.set('d',cubicsuperpath.formatPath(newp))
197             newNode.set('style',simplestyle.formatStyle(newstyle))
199             self.current_layer.remove(newNode)
200             if doReplace:
201                 parent=node.getparent()
202                 parent.insert(parent.index(node),newNode)
203                 parent.remove(node)
205             return newNode
206         else:
207             raise AssertionError
208         
209     def objectToPath(self,node,doReplace=True):
210         #--TODO: support other object types!!!!
211         #--TODO: make sure cubicsuperpath supports A and Q commands... 
212         if node.tag == inkex.addNS('rect','svg'):
213             return(self.rectToPath(node,doReplace))
214         if node.tag == inkex.addNS('g','svg'):
215             return(self.groupToPath(node,doReplace))
216         elif node.tag == inkex.addNS('path','svg') or node.tag == 'path':
217             #remove inkscape attributes, otherwise any modif of 'd' will be discarded!
218             for attName in node.attrib.keys():
219                 if ("sodipodi" in attName) or ("inkscape" in attName):
220                     del node.attrib[attName]
221             fuseTransform(node)
222             return node
223         elif node.tag == inkex.addNS('use','svg') or node.tag == 'use':
224             newNode = self.unlinkClone(node,doReplace)
225             return self.objectToPath(newNode,doReplace)
226         else:
227             inkex.debug("Please first convert objects to paths!...(got '%s')"%node.tag)
228             return None
230     def objectsToPaths(self,aList,doReplace=True):
231         newSelection={}
232         for id,node in aList.items():
233             newnode=self.objectToPath(node,doReplace)
234             del aList[id]
235             aList[newnode.get('id')]=newnode
238 ################################
239 #-- Action ----------
240 ################################
241         
242     #-- overwrite this method in subclasses...
243     def effect(self):
244         #self.duplicateNodes(self.selected)
245         #self.expandGroupsUnlinkClones(self.selected, True)
246         self.objectsToPaths(self.selected, True)
247         self.bbox=computeBBox(self.selected.values())
248         for id, node in self.selected.iteritems():
249             if node.tag == inkex.addNS('path','svg'):
250                 d = node.get('d')
251                 p = cubicsuperpath.parsePath(d)
253                 #do what ever you want with p!
255                 node.set('d',cubicsuperpath.formatPath(p))
258 class Diffeo(PathModifier):
259     def __init__(self):
260         inkex.Effect.__init__(self)
262     def applyDiffeo(self,bpt,vects=()):
263         '''
264         bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt. 
265         Defaults to identity!
266         '''
267         for v in vects:
268             v[0]-=bpt[0]
269             v[1]-=bpt[1]
271         #-- your transformations go here:
272         #x,y=bpt
273         #bpt[0]=f(x,y)
274         #bpt[1]=g(x,y)
275         #for v in vects:
276         #    vx,vy=v
277         #    v[0]=df/dx(x,y)*vx+df/dy(x,y)*vy
278         #    v[1]=dg/dx(x,y)*vx+dg/dy(x,y)*vy
279         #
280         #-- !caution! y-axis is pointing downward!
282         for v in vects:
283             v[0]+=bpt[0]
284             v[1]+=bpt[1]
287     def effect(self):
288         #self.duplicateNodes(self.selected)
289         self.expandGroupsUnlinkClones(self.selected, True)
290         self.expandGroups(self.selected, True)
291         self.objectsToPaths(self.selected, True)
292         self.bbox=computeBBox(self.selected.values())
293         for id, node in self.selected.iteritems():
294             if node.tag == inkex.addNS('path','svg') or node.tag=='path':
295                 d = node.get('d')
296                 p = cubicsuperpath.parsePath(d)
298                 for sub in p:
299                     for ctlpt in sub:
300                         self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2]))
302                 node.set('d',cubicsuperpath.formatPath(p))
304 #e = Diffeo()
305 #e.affect()
307     
308 # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99