~valavanisalex/ubuntu/precise/inkscape/fix-943984

« back to all changes in this revision

Viewing changes to inkscape-0.47pre1/share/extensions/pathscatter.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2009-07-02 17:09:45 UTC
  • mfrom: (1.1.9 upstream)
  • Revision ID: james.westby@ubuntu.com-20090702170945-nn6d6zswovbwju1t
Tags: 0.47~pre1-0ubuntu1
* New upstream release.
  - Don't constrain maximization on small resolution devices (pre0)
    (LP: #348842)
  - Fixes segfault on startup (pre0)
    (LP: #391149)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
'''
 
3
Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr
 
4
 
 
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.
 
9
 
 
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.
 
14
 
 
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
 
19
 
 
20
Quick description:
 
21
This script deforms an object (the pattern) along other paths (skeletons)...
 
22
The first selected object is the pattern
 
23
the last selected ones are the skeletons.
 
24
 
 
25
Imagine a straight horizontal line L in the middle of the bounding box of the pattern.
 
26
Consider the normal bundle of L: the collection of all the vertical lines meeting L.
 
27
Consider this as the initial state of the plane; in particular, think of the pattern
 
28
as painted on these lines.
 
29
 
 
30
Now move and bend L to make it fit a skeleton, and see what happens to the normals:
 
31
they move and rotate, deforming the pattern.
 
32
'''
 
33
 
 
34
import inkex, cubicsuperpath, bezmisc
 
35
import pathmodifier, simpletransform 
 
36
from lxml import etree
 
37
import copy, math, re, random
 
38
import gettext
 
39
_ = gettext.gettext
 
40
 
 
41
def zSort(inNode,idList):
 
42
    sortedList=[]
 
43
    theid = inNode.get("id")
 
44
    if theid in idList:
 
45
        sortedList.append(theid)
 
46
    for child in inNode:
 
47
        if len(sortedList)==len(idList):
 
48
            break
 
49
        sortedList+=zSort(child,idList)
 
50
    return sortedList
 
51
            
 
52
 
 
53
def flipxy(path):
 
54
    for pathcomp in path:
 
55
        for ctl in pathcomp:
 
56
            for pt in ctl:
 
57
                tmp=pt[0]
 
58
                pt[0]=-pt[1]
 
59
                pt[1]=-tmp
 
60
 
 
61
def offset(pathcomp,dx,dy):
 
62
    for ctl in pathcomp:
 
63
        for pt in ctl:
 
64
            pt[0]+=dx
 
65
            pt[1]+=dy
 
66
 
 
67
def stretch(pathcomp,xscale,yscale,org):
 
68
    for ctl in pathcomp:
 
69
        for pt in ctl:
 
70
            pt[0]=org[0]+(pt[0]-org[0])*xscale
 
71
            pt[1]=org[1]+(pt[1]-org[1])*yscale
 
72
 
 
73
def linearize(p,tolerance=0.001):
 
74
    '''
 
75
    This function recieves a component of a 'cubicsuperpath' and returns two things:
 
76
    The path subdivided in many straight segments, and an array containing the length of each segment.
 
77
    
 
78
    We could work with bezier path as well, but bezier arc lengths are (re)computed for each point 
 
79
    in the deformed object. For complex paths, this might take a while.
 
80
    '''
 
81
    zero=0.000001
 
82
    i=0
 
83
    d=0
 
84
    lengths=[]
 
85
    while i<len(p)-1:
 
86
        box  = bezmisc.pointdistance(p[i  ][1],p[i  ][2])
 
87
        box += bezmisc.pointdistance(p[i  ][2],p[i+1][0])
 
88
        box += bezmisc.pointdistance(p[i+1][0],p[i+1][1])
 
89
        chord = bezmisc.pointdistance(p[i][1], p[i+1][1])
 
90
        if (box - chord) > tolerance:
 
91
            b1, b2 = bezmisc.beziersplitatt([p[i][1],p[i][2],p[i+1][0],p[i+1][1]], 0.5)
 
92
            p[i  ][2][0],p[i  ][2][1]=b1[1]
 
93
            p[i+1][0][0],p[i+1][0][1]=b2[2]
 
94
            p.insert(i+1,[[b1[2][0],b1[2][1]],[b1[3][0],b1[3][1]],[b2[1][0],b2[1][1]]])
 
95
        else:
 
96
            d=(box+chord)/2
 
97
            lengths.append(d)
 
98
            i+=1
 
99
    new=[p[i][1] for i in range(0,len(p)-1) if lengths[i]>zero]
 
100
    new.append(p[-1][1])
 
101
    lengths=[l for l in lengths if l>zero]
 
102
    return(new,lengths)
 
103
 
 
104
class PathScatter(pathmodifier.Diffeo):
 
105
    def __init__(self):
 
106
        pathmodifier.Diffeo.__init__(self)
 
107
        self.OptionParser.add_option("--title")
 
108
        self.OptionParser.add_option("-n", "--noffset",
 
109
                        action="store", type="float", 
 
110
                        dest="noffset", default=0.0, help="normal offset")
 
111
        self.OptionParser.add_option("-t", "--toffset",
 
112
                        action="store", type="float", 
 
113
                        dest="toffset", default=0.0, help="tangential offset")
 
114
        self.OptionParser.add_option("-f", "--follow",
 
115
                        action="store", type="inkbool", 
 
116
                        dest="follow", default=True,
 
117
                        help="choose between wave or snake effect")
 
118
        self.OptionParser.add_option("-s", "--stretch",
 
119
                        action="store", type="inkbool", 
 
120
                        dest="stretch", default=True,
 
121
                        help="repeat the path to fit deformer's length")
 
122
        self.OptionParser.add_option("-p", "--space",
 
123
                        action="store", type="float", 
 
124
                        dest="space", default=0.0)
 
125
        self.OptionParser.add_option("-v", "--vertical",
 
126
                        action="store", type="inkbool", 
 
127
                        dest="vertical", default=False,
 
128
                        help="reference path is vertical")
 
129
        self.OptionParser.add_option("-d", "--duplicate",
 
130
                        action="store", type="inkbool", 
 
131
                        dest="duplicate", default=False,
 
132
                        help="duplicate pattern before deformation")
 
133
        self.OptionParser.add_option("-c", "--copymode",
 
134
                        action="store", type="string", 
 
135
                        dest="copymode", default="clone",
 
136
                        help="duplicate pattern before deformation")
 
137
 
 
138
    def prepareSelectionList(self):
 
139
 
 
140
        idList=self.options.ids
 
141
        idList=zSort(self.document.getroot(),idList)
 
142
                
 
143
        ##first selected->pattern, all but first selected-> skeletons
 
144
        #id = self.options.ids[-1]
 
145
        id = idList[-1]
 
146
        self.patternNode=self.selected[id]
 
147
 
 
148
        self.gNode = etree.Element('{http://www.w3.org/2000/svg}g')
 
149
        self.patternNode.getparent().append(self.gNode)
 
150
 
 
151
        if self.options.copymode=="copy":
 
152
            duplist=self.duplicateNodes({id:self.patternNode})
 
153
            self.patternNode = duplist.values()[0]
 
154
 
 
155
        #TODO: allow 4th option: duplicate the first copy and clone the next ones.
 
156
        if "%s"%self.options.copymode=="clone":
 
157
            self.patternNode = etree.Element('{http://www.w3.org/2000/svg}use')
 
158
            self.patternNode.set('{http://www.w3.org/1999/xlink}href',"#%s"%id)
 
159
            self.gNode.append(self.patternNode)
 
160
 
 
161
        self.skeletons=self.selected
 
162
        del self.skeletons[id]
 
163
        self.expandGroupsUnlinkClones(self.skeletons, True, False)
 
164
        self.objectsToPaths(self.skeletons,False)
 
165
 
 
166
    def lengthtotime(self,l):
 
167
        '''
 
168
        Recieves an arc length l, and returns the index of the segment in self.skelcomp 
 
169
        containing the coresponding point, to gether with the position of the point on this segment.
 
170
 
 
171
        If the deformer is closed, do computations modulo the toal length.
 
172
        '''
 
173
        if self.skelcompIsClosed:
 
174
            l=l % sum(self.lengths)
 
175
        if l<=0:
 
176
            return 0,l/self.lengths[0]
 
177
        i=0
 
178
        while (i<len(self.lengths)) and (self.lengths[i]<=l):
 
179
            l-=self.lengths[i]
 
180
            i+=1
 
181
        t=l/self.lengths[min(i,len(self.lengths)-1)]
 
182
        return i, t
 
183
 
 
184
    def localTransformAt(self,s,follow=True):
 
185
        '''
 
186
        recieves a length, and returns the coresponding point and tangent of self.skelcomp
 
187
        if follow is set to false, returns only the translation
 
188
        '''
 
189
        i,t=self.lengthtotime(s)
 
190
        if i==len(self.skelcomp)-1:
 
191
            x,y=bezmisc.tpoint(self.skelcomp[i-1],self.skelcomp[i],1+t)
 
192
            dx=(self.skelcomp[i][0]-self.skelcomp[i-1][0])/self.lengths[-1]
 
193
            dy=(self.skelcomp[i][1]-self.skelcomp[i-1][1])/self.lengths[-1]
 
194
        else:
 
195
            x,y=bezmisc.tpoint(self.skelcomp[i],self.skelcomp[i+1],t)
 
196
            dx=(self.skelcomp[i+1][0]-self.skelcomp[i][0])/self.lengths[i]
 
197
            dy=(self.skelcomp[i+1][1]-self.skelcomp[i][1])/self.lengths[i]
 
198
        if follow:
 
199
            mat=[[dx,-dy,x],[dy,dx,y]]
 
200
        else:
 
201
            mat=[[1,0,x],[0,1,y]]
 
202
        return mat
 
203
 
 
204
 
 
205
    def effect(self):
 
206
 
 
207
        if len(self.options.ids)<2:
 
208
            inkex.errormsg(_("This extension requires two selected paths."))
 
209
            return
 
210
        self.prepareSelectionList()
 
211
 
 
212
        #center at (0,0)
 
213
        bbox=pathmodifier.computeBBox([self.patternNode])
 
214
        mat=[[1,0,-(bbox[0]+bbox[1])/2],[0,1,-(bbox[2]+bbox[3])/2]]
 
215
        if self.options.vertical:
 
216
            bbox=[-bbox[3],-bbox[2],bbox[0],bbox[1]]
 
217
            mat=simpletransform.composeTransform([[0,-1,0],[1,0,0]],mat)
 
218
        mat[1][2] += self.options.noffset
 
219
        simpletransform.applyTransformToNode(mat,self.patternNode)
 
220
                
 
221
        width=bbox[1]-bbox[0]
 
222
        dx=width+self.options.space
 
223
 
 
224
        for skelnode in self.skeletons.itervalues(): 
 
225
            self.curSekeleton=cubicsuperpath.parsePath(skelnode.get('d'))
 
226
            for comp in self.curSekeleton:
 
227
                self.skelcomp,self.lengths=linearize(comp)
 
228
                #!!!!>----> TODO: really test if path is closed! end point==start point is not enough!
 
229
                self.skelcompIsClosed = (self.skelcomp[0]==self.skelcomp[-1])
 
230
 
 
231
                length=sum(self.lengths)
 
232
                if self.options.stretch:
 
233
                    dx=width+self.options.space
 
234
                    n=int((length-self.options.toffset+self.options.space)/dx)
 
235
                    if n>0:
 
236
                        dx=(length-self.options.toffset)/n
 
237
 
 
238
 
 
239
                xoffset=self.skelcomp[0][0]-bbox[0]+self.options.toffset
 
240
                yoffset=self.skelcomp[0][1]-(bbox[2]+bbox[3])/2-self.options.noffset
 
241
 
 
242
                s=self.options.toffset
 
243
                while s<=length:
 
244
                    mat=self.localTransformAt(s,self.options.follow)
 
245
 
 
246
                    clone=copy.deepcopy(self.patternNode)
 
247
                    #!!!--> should it be given an id?
 
248
                    #seems to work without this!?!
 
249
                    myid = self.patternNode.tag.split('}')[-1]
 
250
                    clone.set("id", self.uniqueId(myid))
 
251
                    self.gNode.append(clone)
 
252
                    
 
253
                    simpletransform.applyTransformToNode(mat,clone)
 
254
 
 
255
                    s+=dx
 
256
        self.patternNode.getparent().remove(self.patternNode)
 
257
 
 
258
if __name__ == '__main__':
 
259
    e = PathScatter()
 
260
    e.affect()
 
261
 
 
262
 
 
263
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99