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...
30
import inkex, cubicsuperpath, bezmisc, simplestyle
31
from simpletransform import *
32
import copy, math, re, random
36
####################################################################
37
##-- zOrder computation...
38
##-- this should be shipped out in a separate file. inkex.py?
40
def zSort(inNode,idList):
42
theid = inNode.get("id")
44
sortedList.append(theid)
46
if len(sortedList)==len(idList):
48
sortedList+=zSort(child,idList)
52
class PathModifier(inkex.Effect):
54
inkex.Effect.__init__(self)
56
##################################
57
#-- Selectionlists manipulation --
58
##################################
60
def duplicateNodes(self, aList):
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
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))
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"))
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"]
91
def expandGroupsUnlinkClones(self,aList,transferTransform=True,doReplace=True):
92
for id in aList.keys()[:]:
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)
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))
113
def recursNewIds(self,node):
115
node.set('id',self.uniqueId(node.tag))
117
self.recursNewIds(child)
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]
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)
135
parent=node.getparent()
136
parent.insert(parent.index(node),newNode)
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
155
rx=float(node.get('rx'))
156
ry=float(node.get('ry'))
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')
174
newnode.set('id', self.uniqueId('path'))
175
newnode.set('style',node.get('style'))
176
nnt = node.get('transform')
178
newnode.set('transform',nnt)
179
fuseTransform(newnode)
181
parent=node.getparent()
182
parent.insert(parent.index(node),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 "")
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)
203
parent=node.getparent()
204
parent.insert(parent.index(node),newNode)
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]
225
elif node.tag == inkex.addNS('use','svg') or node.tag == 'use':
226
newNode = self.unlinkClone(node,doReplace)
227
return self.objectToPath(newNode,doReplace)
229
inkex.errormsg(_("Please first convert objects to paths! (Got [%s].)") % node.tag)
232
def objectsToPaths(self,aList,doReplace=True):
234
for id,node in aList.items():
235
newnode=self.objectToPath(node,doReplace)
237
aList[newnode.get('id')]=newnode
240
################################
241
#-- Action ----------
242
################################
244
#-- overwrite this method in subclasses...
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'):
253
p = cubicsuperpath.parsePath(d)
255
#do what ever you want with p!
257
node.set('d',cubicsuperpath.formatPath(p))
260
class Diffeo(PathModifier):
262
inkex.Effect.__init__(self)
264
def applyDiffeo(self,bpt,vects=()):
266
bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt.
267
Defaults to identity!
273
#-- your transformations go here:
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
282
#-- !caution! y-axis is pointing downward!
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':
298
p = cubicsuperpath.parsePath(d)
302
self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2]))
304
node.set('d',cubicsuperpath.formatPath(p))
310
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99