From 801127bca2d92350331d75f9c20e4b2e6a280259 Mon Sep 17 00:00:00 2001 From: jfbarraud Date: Sat, 23 Feb 2008 23:11:19 +0000 Subject: [PATCH] Added pathscatter effect + slight modif of simpletransform... to compute bbox of rects!! --- share/extensions/Makefile.am | 2 + share/extensions/pathscatter.inx | 34 ++++ share/extensions/pathscatter.py | 261 ++++++++++++++++++++++++++++ share/extensions/simpletransform.py | 17 +- 4 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 share/extensions/pathscatter.inx create mode 100644 share/extensions/pathscatter.py diff --git a/share/extensions/Makefile.am b/share/extensions/Makefile.am index e6dc859fb..2f25f779c 100644 --- a/share/extensions/Makefile.am +++ b/share/extensions/Makefile.am @@ -67,6 +67,7 @@ extensions = \ motion.py \ outline2svg.pl \ pathalongpath.py\ + pathscatter.py\ pathmodifier.py\ perfectboundcover.py \ perspective.py \ @@ -174,6 +175,7 @@ modules = \ motion.inx \ outline2svg.inx \ pathalongpath.inx\ + pathscatter.inx\ pdf_output.inx.txt \ pdf_output_via_gs_on_win32.inx.txt \ perfectboundcover.inx \ diff --git a/share/extensions/pathscatter.inx b/share/extensions/pathscatter.inx new file mode 100644 index 000000000..c2618d61a --- /dev/null +++ b/share/extensions/pathscatter.inx @@ -0,0 +1,34 @@ + + <_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 + + + + + + + + + diff --git a/share/extensions/pathscatter.py b/share/extensions/pathscatter.py new file mode 100644 index 000000000..26df9553a --- /dev/null +++ b/share/extensions/pathscatter.py @@ -0,0 +1,261 @@ +#!/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") + self.OptionParser.add_option("-c", "--copymode", + action="store", type="string", + dest="copymode", default="clone", + 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.patternNode=self.selected[id] + + self.gNode = etree.Element('{http://www.w3.org/2000/svg}g') + self.patternNode.getparent().append(self.gNode) + + if self.options.copymode=="copy": + duplist=self.duplicateNodes({id:self.patternNode}) + self.patternNode = duplist.values()[0] + + #TODO: allow 4th option: duplicate the first copy and clone the next ones. + if "%s"%self.options.copymode=="clone": + self.patternNode = etree.Element('{http://www.w3.org/2000/svg}use') + self.patternNode.set('{http://www.w3.org/1999/xlink}href',"#%s"%id) + self.gNode.append(self.patternNode) + + 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) + + simpletransform.applyTransformToNode(mat,clone) + + s+=dx + self.patternNode.getparent().remove(self.patternNode) + + +e = PathScatter() +e.affect() + + diff --git a/share/extensions/simpletransform.py b/share/extensions/simpletransform.py index cf0751ca8..f434d80ed 100644 --- a/share/extensions/simpletransform.py +++ b/share/extensions/simpletransform.py @@ -152,10 +152,23 @@ def computeBBox(aList,mat=[[1,0,0],[0,1,0]]): applyTransformToPath(m,p) bbox=boxunion(roughBBox(p),bbox) - if node.tag == inkex.addNS('use','svg') or node.tag=='use': + elif node.tag == inkex.addNS('rect','svg') or node.tag=='rect': + w = float(node.get('width'))/2. + h = float(node.get('height'))/2. + x = float(node.get('x')) + y = float(node.get('y')) + C = [x + w , y + h ] + applyTransformToPoint(mat,C) + xmin = C[0] - abs(m[0][0]) * w - abs(m[0][1]) * h + xmax = C[0] + abs(m[0][0]) * w + abs(m[0][1]) * h + ymin = C[1] - abs(m[1][0]) * w - abs(m[1][1]) * h + ymax = C[1] + abs(m[1][0]) * w + abs(m[1][1]) * h + bbox = xmin,xmax,ymin,ymax + + elif node.tag == inkex.addNS('use','svg') or node.tag=='use': refid=node.get(inkex.addNS('href','xlink')) path = '//*[@id="%s"]' % refid[1:] - refnode = node.getroottree().xpath(path, namespaces=inkex.NSS) + refnode = node.xpath(path) bbox=boxunion(computeBBox(refnode,m),bbox) bbox=boxunion(computeBBox(node,m),bbox) -- 2.39.5