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
34
####################################################################
35
##-- zOrder computation...
36
##-- this should be shipped out in a separate file. inkex.py?
38
def zSort(inNode,idList):
40
theid = inNode.get("id")
42
sortedList.append(theid)
44
if len(sortedList)==len(idList):
46
sortedList+=zSort(child,idList)
50
class PathModifier(inkex.Effect):
52
inkex.Effect.__init__(self)
54
##################################
55
#-- Selectionlists manipulation --
56
##################################
58
def duplicateNodes(self, aList):
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
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))
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"))
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"]
89
def expandGroupsUnlinkClones(self,aList,transferTransform=True,doReplace=True):
90
for id in aList.keys()[:]:
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)
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))
111
def recursNewIds(self,node):
113
node.set('id',self.uniqueId(node.tag))
115
self.recursNewIds(child)
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]
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)
133
parent=node.getparent()
134
parent.insert(parent.index(node),newNode)
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
153
rx=float(node.get('rx'))
154
ry=float(node.get('ry'))
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')
172
newnode.set('id', self.uniqueId('path'))
173
newnode.set('style',node.get('style'))
174
nnt = node.get('transform')
176
newnode.set('transform',nnt)
177
fuseTransform(newnode)
179
parent=node.getparent()
180
parent.insert(parent.index(node),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 "")
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)
201
parent=node.getparent()
202
parent.insert(parent.index(node),newNode)
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]
223
elif node.tag == inkex.addNS('use','svg') or node.tag == 'use':
224
newNode = self.unlinkClone(node,doReplace)
225
return self.objectToPath(newNode,doReplace)
227
inkex.debug("Please first convert objects to paths!...(got '%s')"%node.tag)
230
def objectsToPaths(self,aList,doReplace=True):
232
for id,node in aList.items():
233
newnode=self.objectToPath(node,doReplace)
235
aList[newnode.get('id')]=newnode
238
################################
239
#-- Action ----------
240
################################
242
#-- overwrite this method in subclasses...
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'):
251
p = cubicsuperpath.parsePath(d)
253
#do what ever you want with p!
255
node.set('d',cubicsuperpath.formatPath(p))
258
class Diffeo(PathModifier):
260
inkex.Effect.__init__(self)
262
def applyDiffeo(self,bpt,vects=()):
264
bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt.
265
Defaults to identity!
271
#-- your transformations go here:
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
280
#-- !caution! y-axis is pointing downward!
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':
296
p = cubicsuperpath.parsePath(d)
300
self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2]))
302
node.set('d',cubicsuperpath.formatPath(p))
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