Index: interp.py =================================================================== --- interp.py (revision 17172) +++ interp.py (working copy) @@ -16,7 +16,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ''' -import inkex, cubicsuperpath, simplestyle, copy, math, bezmisc +import inkex, cubicsuperpath, simplestyle, copy, math, bezmisc, pathmodifier def numsegs(csp): return sum([len(p)-1 for p in csp]) @@ -84,7 +84,7 @@ r,g,b = c[1:3],c[3:5],c[5:7] return r,g,b -class Interp(inkex.Effect): +class Interp(pathmodifier.PathModifier): def __init__(self): inkex.Effect.__init__(self) self.OptionParser.add_option("-e", "--exponent", @@ -120,19 +120,22 @@ paths = {} styles = {} - for id in self.options.ids: - node = self.selected[id] - if node.tag ==inkex.addNS('path','svg'): - paths[id] = cubicsuperpath.parsePath(node.get('d')) - styles[id] = simplestyle.parseStyle(node.get('style')) - else: - self.options.ids.remove(id) - for i in range(1,len(self.options.ids)): - start = copy.deepcopy(paths[self.options.ids[i-1]]) - end = copy.deepcopy(paths[self.options.ids[i]]) - sst = copy.deepcopy(styles[self.options.ids[i-1]]) - est = copy.deepcopy(styles[self.options.ids[i]]) + ##work around selection order unstability: use zOrder instead. + sortedids = pathmodifier.zSort(self.document.getroot(),self.selected.keys()) + ##to use selection order, replace previous line by: + #sortedids = self.options.ids + + for id in sortedids: + node = self.objectToPath(self.selected[id],False) + paths[id] = cubicsuperpath.parsePath(node.get('d')) + styles[id] = simplestyle.parseStyle(node.get('style')) + + for i in range(1,len(sortedids)): + start = copy.deepcopy(paths[sortedids[i-1]]) + end = copy.deepcopy(paths[sortedids[i]]) + sst = copy.deepcopy(styles[sortedids[i-1]]) + est = copy.deepcopy(styles[sortedids[i]]) basestyle = copy.deepcopy(sst) #prepare for experimental style tweening Index: pathalongpath.inx =================================================================== --- pathalongpath.inx (revision 17172) +++ pathalongpath.inx (working copy) @@ -4,7 +4,7 @@ pathmodifier.py pathalongpath.py inkex.py - This effect bends a pattern object along an arbitrary "skeleton" path. The pattern can be a path or a group of paths. First, select the pattern object; then add to selection the skeleton path; then call this effect. + This effect bends a pattern object along arbitrary "skeleton" paths. The pattern is the top most object in the selection. (groups of paths/shapes/clones... allowed) Single Index: pathalongpath.py =================================================================== --- pathalongpath.py (revision 17172) +++ pathalongpath.py (working copy) @@ -32,7 +32,7 @@ ''' import inkex, cubicsuperpath, bezmisc -import pathmodifier +import pathmodifier,simpletransform import copy, math, re, random @@ -118,9 +118,16 @@ help="duplicate pattern before deformation") def prepareSelectionList(self): - ##first selected->pattern, all but first selected-> skeletons - id = self.options.ids[-1] + + idList=self.options.ids + idList=pathmodifier.zSort(self.document.getroot(),idList) + id = idList[-1] self.patterns={id:self.selected[id]} + +## ##first selected->pattern, all but first selected-> skeletons +## id = self.options.ids[-1] +## self.patterns={id:self.selected[id]} + if self.options.duplicate: self.patterns=self.duplicateNodes(self.patterns) self.expandGroupsUnlinkClones(self.patterns, True, True) @@ -203,7 +210,8 @@ self.options.repeat =True self.options.stretch=True - bbox=self.computeBBox(self.patterns) + bbox=simpletransform.computeBBox(self.patterns.values()) + if self.options.vertical: #flipxy(bbox)... bbox=(-bbox[3],-bbox[2],-bbox[1],-bbox[0]) @@ -233,12 +241,13 @@ xoffset=self.skelcomp[0][0]-bbox[0]+self.options.toffset yoffset=self.skelcomp[0][1]-(bbox[2]+bbox[3])/2-self.options.noffset + if self.options.repeat: NbCopies=max(1,int(round((length+self.options.space)/dx))) width=dx*NbCopies if not self.skelcompIsClosed: width-=self.options.space - bbox=bbox[0],bbox[0]+width,bbox[2],bbox[3] + bbox=bbox[0],bbox[0]+width, bbox[2],bbox[3] new=[] for sub in p: for i in range(0,NbCopies,1): Index: pathmodifier.py =================================================================== --- pathmodifier.py (revision 17172) +++ pathmodifier.py (working copy) @@ -16,119 +16,37 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA barraud@math.univ-lille1.fr + +This code defines a basic class (PathModifier) of effects whose purpose is +to somehow deform given objects: one common tasks for all such effect is to +convert shapes, groups, clones to paths. The class has several functions to +make this (more or less!) easy. +As an exemple, a second class (Diffeo) is derived from it, +to implement deformations of the form X=f(x,y), Y=g(x,y)... + +TODO: Several handy functions are defined, that might in fact be of general +interest and that should be shipped out in separate files... ''' import inkex, cubicsuperpath, bezmisc, simplestyle +from simpletransform import * import copy, math, re, random -def parseTransform(transf,mat=[[1.0,0.0,0.0],[0.0,1.0,0.0]]): - if transf=="": - return(mat) - result=re.match("(translate|scale|rotate|skewX|skewY|matrix)\(([^)]*)\)",transf) -#-- translate -- - if result.group(1)=="translate": - args=result.group(2).split(",") - dx=float(args[0]) - if len(args)==1: - dy=0.0 - else: - dy=float(args[1]) - matrix=[[1,0,dx],[0,1,dy]] -#-- scale -- - if result.groups(1)=="scale": - args=result.group(2).split(",") - sx=float(args[0]) - if len(args)==1: - sy=sx - else: - sy=float(args[1]) - matrix=[[sx,0,0],[0,sy,0]] -#-- rotate -- - if result.groups(1)=="rotate": - args=result.group(2).split(",") - a=float(args[0])*math.pi/180 - if len(args)==1: - cx,cy=(0.0,0.0) - else: - cx,cy=args[1:] - matrix=[[math.cos(a),-math.sin(a),cx],[math.sin(a),math.cos(a),cy]] -#-- skewX -- - if result.groups(1)=="skewX": - a=float(result.group(2))*math.pi/180 - matrix=[[1,math.tan(a),0],[0,1,0]] -#-- skewX -- - if result.groups(1)=="skewX": - a=float(result.group(2))*math.pi/180 - matrix=[[1,0,0],[math.tan(a),1,0]] -#-- matrix -- - if result.group(1)=="matrix": - a11,a21,a12,a22,v1,v2=result.group(2).split(",") - matrix=[[float(a11),float(a12),float(v1)],[float(a21),float(a22),float(v2)]] - - matrix=composeTransform(mat,matrix) - if result.end() + <_name>Scatter + math.univ-lille1.barraud.pathScatter + pathmodifier.py + pathScatter.py + inkex.py + This effect scatters a pattern along arbitrary "skeleton" paths. The pattern is the top most object in the selection. (groups of paths/shapes/clones... allowed) + + false + false + + 0.0 + + 0.0 + 0.0 + + false + true + + + + + + + + + Index: pathscatter.py =================================================================== --- pathscatter.py (revision 0) +++ pathscatter.py (revision 0) @@ -0,0 +1,257 @@ +#!/usr/bin/env python +''' +Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +barraud@math.univ-lille1.fr + +Quick description: +This script deforms an object (the pattern) along other paths (skeletons)... +The first selected object is the pattern +the last selected ones are the skeletons. + +Imagine a straight horizontal line L in the middle of the bounding box of the pattern. +Consider the normal bundle of L: the collection of all the vertical lines meeting L. +Consider this as the initial state of the plane; in particular, think of the pattern +as painted on these lines. + +Now move and bend L to make it fit a skeleton, and see what happens to the normals: +they move and rotate, deforming the pattern. +''' + +import inkex, cubicsuperpath, bezmisc +import pathmodifier, simpletransform +from lxml import etree + +import copy, math, re, random + +def zSort(inNode,idList): + sortedList=[] + theid = inNode.get("id") + if theid in idList: + sortedList.append(theid) + for child in inNode: + if len(sortedList)==len(idList): + break + sortedList+=zSort(child,idList) + return sortedList + + +def flipxy(path): + for pathcomp in path: + for ctl in pathcomp: + for pt in ctl: + tmp=pt[0] + pt[0]=-pt[1] + pt[1]=-tmp + +def offset(pathcomp,dx,dy): + for ctl in pathcomp: + for pt in ctl: + pt[0]+=dx + pt[1]+=dy + +def stretch(pathcomp,xscale,yscale,org): + for ctl in pathcomp: + for pt in ctl: + pt[0]=org[0]+(pt[0]-org[0])*xscale + pt[1]=org[1]+(pt[1]-org[1])*yscale + +def linearize(p,tolerance=0.001): + ''' + This function recieves a component of a 'cubicsuperpath' and returns two things: + The path subdivided in many straight segments, and an array containing the length of each segment. + + We could work with bezier path as well, but bezier arc lengths are (re)computed for each point + in the deformed object. For complex paths, this might take a while. + ''' + zero=0.000001 + i=0 + d=0 + lengths=[] + while i tolerance: + b1, b2 = bezmisc.beziersplitatt([p[i][1],p[i][2],p[i+1][0],p[i+1][1]], 0.5) + p[i ][2][0],p[i ][2][1]=b1[1] + p[i+1][0][0],p[i+1][0][1]=b2[2] + p.insert(i+1,[[b1[2][0],b1[2][1]],[b1[3][0],b1[3][1]],[b2[1][0],b2[1][1]]]) + else: + d=(box+chord)/2 + lengths.append(d) + i+=1 + new=[p[i][1] for i in range(0,len(p)-1) if lengths[i]>zero] + new.append(p[-1][1]) + lengths=[l for l in lengths if l>zero] + return(new,lengths) + +class PathScatter(pathmodifier.Diffeo): + def __init__(self): + pathmodifier.Diffeo.__init__(self) + self.OptionParser.add_option("--title") + self.OptionParser.add_option("-n", "--noffset", + action="store", type="float", + dest="noffset", default=0.0, help="normal offset") + self.OptionParser.add_option("-t", "--toffset", + action="store", type="float", + dest="toffset", default=0.0, help="tangential offset") + self.OptionParser.add_option("-f", "--follow", + action="store", type="inkbool", + dest="follow", default=True, + help="choose between wave or snake effect") + self.OptionParser.add_option("-s", "--stretch", + action="store", type="inkbool", + dest="stretch", default=True, + help="repeat the path to fit deformer's length") + self.OptionParser.add_option("-p", "--space", + action="store", type="float", + dest="space", default=0.0) + self.OptionParser.add_option("-v", "--vertical", + action="store", type="inkbool", + dest="vertical", default=False, + help="reference path is vertical") + self.OptionParser.add_option("-d", "--duplicate", + action="store", type="inkbool", + dest="duplicate", default=False, + help="duplicate pattern before deformation") + + def prepareSelectionList(self): + + idList=self.options.ids + idList=zSort(self.document.getroot(),idList) + + ##first selected->pattern, all but first selected-> skeletons + #id = self.options.ids[-1] + id = idList[-1] + self.patterns = {id:self.selected[id]} + self.patternNode=self.selected[id] + + self.gNode = etree.Element('{http://www.w3.org/2000/svg}g') + self.patternNode.getparent().append(self.gNode) + #self.patternNode.parentNode.appendChild(self.gNode) + + + if self.options.duplicate: + self.patterns=self.duplicateNodes(self.patterns) + #self.expandGroupsUnlinkClones(self.patterns, True, True) + #self.objectsToPaths(self.patterns) + #del self.selected[id] + + self.skeletons=self.selected + del self.skeletons[id] + self.expandGroupsUnlinkClones(self.skeletons, True, False) + self.objectsToPaths(self.skeletons,False) + + def lengthtotime(self,l): + ''' + Recieves an arc length l, and returns the index of the segment in self.skelcomp + containing the coresponding point, to gether with the position of the point on this segment. + + If the deformer is closed, do computations modulo the toal length. + ''' + if self.skelcompIsClosed: + l=l % sum(self.lengths) + if l<=0: + return 0,l/self.lengths[0] + i=0 + while (i----> TODO: really test if path is closed! end point==start point is not enough! + self.skelcompIsClosed = (self.skelcomp[0]==self.skelcomp[-1]) + + length=sum(self.lengths) + if self.options.stretch: + dx=width+self.options.space + n=int((length-self.options.toffset+self.options.space)/dx) + if n>0: + dx=(length-self.options.toffset)/n + + + xoffset=self.skelcomp[0][0]-bbox[0]+self.options.toffset + yoffset=self.skelcomp[0][1]-(bbox[2]+bbox[3])/2-self.options.noffset + + s=self.options.toffset + while s<=length: + mat=self.localTransformAt(s,self.options.follow) + + clone=copy.deepcopy(self.patternNode) + #!!!--> should it be given an id? + #seems to work without this!?! + myid = self.patternNode.tag.split('}')[-1] + clone.set("id", self.uniqueId(myid)) + self.gNode.append(clone) + #self.patternNode.getparent().append(clone) + + simpletransform.applyTransformToNode(mat,clone) + + s+=dx + self.patternNode.getparent().remove(self.patternNode) + + +e = PathScatter() +e.affect() + + Index: rubberstretch.inx =================================================================== --- rubberstretch.inx (revision 0) +++ rubberstretch.inx (revision 0) @@ -0,0 +1,17 @@ + + <_name>Rubber Stretch + math.univ-lille1.barraud.spherify + pathmodifier.py + inkex.py + 25 + 25 + + path + + + + + + Index: rubberstretch.py =================================================================== --- rubberstretch.py (revision 17172) +++ rubberstretch.py (working copy) @@ -20,7 +20,7 @@ ''' import inkex, cubicsuperpath, bezmisc, pathmodifier -import copy, math, re, random, xml.xpath +import copy, math, re class RubberStretch(pathmodifier.Diffeo): def __init__(self): @@ -45,7 +45,7 @@ w,h=(self.bbox[1]-self.bbox[0])/2,(self.bbox[3]-self.bbox[2])/2 x,y=(bpt[0]-x0),(bpt[1]-y0) - sx=(1+b*(x/w+1)*(x/w-1))*2**(-a) + sx=(1+b*(x/w+1)*(x/w-1))*2**(-a) sy=(1+b*(y/h+1)*(y/h-1))*2**(-a) bpt[0]=x0+x*sy bpt[1]=y0+y/sx @@ -75,5 +75,5 @@ e = RubberStretch() e.affect() - - + + Index: simpletransform.py =================================================================== --- simpletransform.py (revision 0) +++ simpletransform.py (revision 0) @@ -0,0 +1,164 @@ +#!/usr/bin/env python +''' +Copyright (C) 2006 Jean-Francois Barraud, barraud@math.univ-lille1.fr + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +barraud@math.univ-lille1.fr + +This code defines several functions to make handling of transform +attribute easier. +''' +import inkex, cubicsuperpath, bezmisc, simplestyle +import copy, math, re + +def parseTransform(transf,mat=[[1.0,0.0,0.0],[0.0,1.0,0.0]]): + if transf=="" or transf==None: + return(mat) + result=re.match("(translate|scale|rotate|skewX|skewY|matrix)\(([^)]*)\)",transf) +#-- translate -- + if result.group(1)=="translate": + args=result.group(2).split(",") + dx=float(args[0]) + if len(args)==1: + dy=0.0 + else: + dy=float(args[1]) + matrix=[[1,0,dx],[0,1,dy]] +#-- scale -- + if result.groups(1)=="scale": + args=result.group(2).split(",") + sx=float(args[0]) + if len(args)==1: + sy=sx + else: + sy=float(args[1]) + matrix=[[sx,0,0],[0,sy,0]] +#-- rotate -- + if result.groups(1)=="rotate": + args=result.group(2).split(",") + a=float(args[0])*math.pi/180 + if len(args)==1: + cx,cy=(0.0,0.0) + else: + cx,cy=args[1:] + matrix=[[math.cos(a),-math.sin(a),cx],[math.sin(a),math.cos(a),cy]] +#-- skewX -- + if result.groups(1)=="skewX": + a=float(result.group(2))*math.pi/180 + matrix=[[1,math.tan(a),0],[0,1,0]] +#-- skewX -- + if result.groups(1)=="skewX": + a=float(result.group(2))*math.pi/180 + matrix=[[1,0,0],[math.tan(a),1,0]] +#-- matrix -- + if result.group(1)=="matrix": + a11,a21,a12,a22,v1,v2=result.group(2).split(",") + matrix=[[float(a11),float(a12),float(v1)],[float(a21),float(a22),float(v2)]] + + matrix=composeTransform(mat,matrix) + if result.end()