Code

share/extensions/*.py: Use gettext for (many) error messages.
[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
33 import gettext
34 _ = gettext.gettext
36 ####################################################################
37 ##-- zOrder computation...
38 ##-- this should be shipped out in a separate file. inkex.py?
40 def zSort(inNode,idList):
41     sortedList=[]
42     theid = inNode.get("id")
43     if theid in idList:
44         sortedList.append(theid)
45     for child in inNode:
46         if len(sortedList)==len(idList):
47             break
48         sortedList+=zSort(child,idList)
49     return sortedList
52 class PathModifier(inkex.Effect):
53     def __init__(self):
54         inkex.Effect.__init__(self)
56 ##################################
57 #-- Selectionlists manipulation --
58 ##################################
60     def duplicateNodes(self, aList):
61         clones={}
62         for id,node in aList.iteritems():
63             clone=copy.deepcopy(node)
64             #!!!--> should it be given an id?
65             #seems to work without this!?!
66             myid = node.tag.split('}')[-1]
67             clone.set("id", self.uniqueId(myid))
68             node.getparent().append(clone)
69             clones[clone.get("id")]=clone
70         return(clones)
72     def uniqueId(self, prefix):
73         id="%s%04i"%(prefix,random.randint(0,9999))
74         while len(self.document.getroot().xpath('//*[@id="%s"]' % id,namespaces=inkex.NSS)):
75             id="%s%04i"%(prefix,random.randint(0,9999))
76         return(id)
78     def expandGroups(self,aList,transferTransform=True):
79         for id, node in aList.items():      
80             if node.tag == inkex.addNS('g','svg') or node.tag=='g':
81                 mat=parseTransform(node.get("transform"))
82                 for child in node:
83                     if transferTransform:
84                         applyTransformToNode(mat,child)
85                     aList.update(self.expandGroups({child.get('id'):child}))
86                 if transferTransform and node.get("transform"):
87                     del node.attrib["transform"]
88                 del aList[id]
89         return(aList)
91     def expandGroupsUnlinkClones(self,aList,transferTransform=True,doReplace=True):
92         for id in aList.keys()[:]:     
93             node=aList[id]
94             if node.tag == inkex.addNS('g','svg') or node.tag=='g':
95                 self.expandGroups(aList,transferTransform)
96                 self.expandGroupsUnlinkClones(aList,transferTransform,doReplace)
97                 #Hum... not very efficient if there are many clones of groups...
99             elif node.tag == inkex.addNS('use','svg') or node.tag=='use':
100                 refnode=self.refNode(node)
101                 newnode=self.unlinkClone(node,doReplace)
102                 del aList[id]
104                 style = simplestyle.parseStyle(node.get('style') or "")
105                 refstyle=simplestyle.parseStyle(refnode.get('style') or "")
106                 style.update(refstyle)
107                 newnode.set('style',simplestyle.formatStyle(style))
109                 newid=newnode.get('id')
110                 aList.update(self.expandGroupsUnlinkClones({newid:newnode},transferTransform,doReplace))
111         return aList
112     
113     def recursNewIds(self,node):
114         if node.get('id'):
115             node.set('id',self.uniqueId(node.tag))
116         for child in node:
117             self.recursNewIds(child)
118             
119     def refNode(self,node):
120         if node.get(inkex.addNS('href','xlink')):
121             refid=node.get(inkex.addNS('href','xlink'))
122             path = '//*[@id="%s"]' % refid[1:]
123             newNode = self.document.getroot().xpath(path, namespaces=inkex.NSS)[0]
124             return newNode
125         else:
126             raise AssertionError, "Trying to follow empty xlink.href attribute."
128     def unlinkClone(self,node,doReplace):
129         if node.tag == inkex.addNS('use','svg') or node.tag=='use':
130             newNode = copy.deepcopy(self.refNode(node))
131             self.recursNewIds(newNode)
132             applyTransformToNode(parseTransform(node.get('transform')),newNode)
134             if doReplace:
135                 parent=node.getparent()
136                 parent.insert(parent.index(node),newNode)
137                 parent.remove(node)
139             return newNode
140         else:
141             raise AssertionError, "Only clones can be unlinked..."
145 ################################
146 #-- Object conversion ----------
147 ################################
149     def rectToPath(self,node,doReplace=True):
150         if node.tag == inkex.addNS('rect','svg'):
151             x =float(node.get('x'))
152             y =float(node.get('y'))
153             #FIXME: no exception anymore and sometimes just one
154             try:
155                 rx=float(node.get('rx'))
156                 ry=float(node.get('ry'))
157             except:
158                 rx=0
159                 ry=0
160             w =float(node.get('width' ))
161             h =float(node.get('height'))
162             d ='M %f,%f '%(x+rx,y)
163             d+='L %f,%f '%(x+w-rx,y)
164             d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+w,y+ry)
165             d+='L %f,%f '%(x+w,y+h-ry)
166             d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+w-rx,y+h)
167             d+='L %f,%f '%(x+rx,y+h)
168             d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x,y+h-ry)
169             d+='L %f,%f '%(x,y+ry)
170             d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+rx,y)
172             newnode=inkex.etree.Element('path')
173             newnode.set('d',d)
174             newnode.set('id', self.uniqueId('path'))
175             newnode.set('style',node.get('style'))
176             nnt = node.get('transform')
177             if nnt:
178                 newnode.set('transform',nnt)
179                 fuseTransform(newnode)
180             if doReplace:
181                 parent=node.getparent()
182                 parent.insert(parent.index(node),newnode)
183                 parent.remove(node)
184             return newnode
186     def groupToPath(self,node,doReplace=True):
187         if node.tag == inkex.addNS('g','svg'):
188             newNode = inkex.etree.SubElement(self.current_layer,inkex.addNS('path','svg'))    
190             newstyle = simplestyle.parseStyle(node.get('style') or "")
191             newp = []
192             for child in node:
193                 childstyle = simplestyle.parseStyle(child.get('style') or "")
194                 childstyle.update(newstyle)
195                 newstyle.update(childstyle)
196                 childAsPath = self.objectToPath(child,False)
197                 newp += cubicsuperpath.parsePath(childAsPath.get('d'))
198             newNode.set('d',cubicsuperpath.formatPath(newp))
199             newNode.set('style',simplestyle.formatStyle(newstyle))
201             self.current_layer.remove(newNode)
202             if doReplace:
203                 parent=node.getparent()
204                 parent.insert(parent.index(node),newNode)
205                 parent.remove(node)
207             return newNode
208         else:
209             raise AssertionError
210         
211     def objectToPath(self,node,doReplace=True):
212         #--TODO: support other object types!!!!
213         #--TODO: make sure cubicsuperpath supports A and Q commands... 
214         if node.tag == inkex.addNS('rect','svg'):
215             return(self.rectToPath(node,doReplace))
216         if node.tag == inkex.addNS('g','svg'):
217             return(self.groupToPath(node,doReplace))
218         elif node.tag == inkex.addNS('path','svg') or node.tag == 'path':
219             #remove inkscape attributes, otherwise any modif of 'd' will be discarded!
220             for attName in node.attrib.keys():
221                 if ("sodipodi" in attName) or ("inkscape" in attName):
222                     del node.attrib[attName]
223             fuseTransform(node)
224             return node
225         elif node.tag == inkex.addNS('use','svg') or node.tag == 'use':
226             newNode = self.unlinkClone(node,doReplace)
227             return self.objectToPath(newNode,doReplace)
228         else:
229             inkex.errormsg(_("Please first convert objects to paths!  (Got <%s>.)") % node.tag)
230             return None
232     def objectsToPaths(self,aList,doReplace=True):
233         newSelection={}
234         for id,node in aList.items():
235             newnode=self.objectToPath(node,doReplace)
236             del aList[id]
237             aList[newnode.get('id')]=newnode
240 ################################
241 #-- Action ----------
242 ################################
243         
244     #-- overwrite this method in subclasses...
245     def effect(self):
246         #self.duplicateNodes(self.selected)
247         #self.expandGroupsUnlinkClones(self.selected, True)
248         self.objectsToPaths(self.selected, True)
249         self.bbox=computeBBox(self.selected.values())
250         for id, node in self.selected.iteritems():
251             if node.tag == inkex.addNS('path','svg'):
252                 d = node.get('d')
253                 p = cubicsuperpath.parsePath(d)
255                 #do what ever you want with p!
257                 node.set('d',cubicsuperpath.formatPath(p))
260 class Diffeo(PathModifier):
261     def __init__(self):
262         inkex.Effect.__init__(self)
264     def applyDiffeo(self,bpt,vects=()):
265         '''
266         bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt. 
267         Defaults to identity!
268         '''
269         for v in vects:
270             v[0]-=bpt[0]
271             v[1]-=bpt[1]
273         #-- your transformations go here:
274         #x,y=bpt
275         #bpt[0]=f(x,y)
276         #bpt[1]=g(x,y)
277         #for v in vects:
278         #    vx,vy=v
279         #    v[0]=df/dx(x,y)*vx+df/dy(x,y)*vy
280         #    v[1]=dg/dx(x,y)*vx+dg/dy(x,y)*vy
281         #
282         #-- !caution! y-axis is pointing downward!
284         for v in vects:
285             v[0]+=bpt[0]
286             v[1]+=bpt[1]
289     def effect(self):
290         #self.duplicateNodes(self.selected)
291         self.expandGroupsUnlinkClones(self.selected, True)
292         self.expandGroups(self.selected, True)
293         self.objectsToPaths(self.selected, True)
294         self.bbox=computeBBox(self.selected.values())
295         for id, node in self.selected.iteritems():
296             if node.tag == inkex.addNS('path','svg') or node.tag=='path':
297                 d = node.get('d')
298                 p = cubicsuperpath.parsePath(d)
300                 for sub in p:
301                     for ctlpt in sub:
302                         self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2]))
304                 node.set('d',cubicsuperpath.formatPath(p))
306 #e = Diffeo()
307 #e.affect()
309     
310 # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99