X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=share%2Fextensions%2Fpathmodifier.py;h=bfc72f074d1c5a4eaef30c52c8b855eca7a3394c;hb=d05e3747a232f39b573747f452d1702b92d8f8b6;hp=a27c547c19789259613da30867341d405690e049;hpb=9e9083d5b1ba585665bf2f97eb2fba6a6e4d11eb;p=inkscape.git diff --git a/share/extensions/pathmodifier.py b/share/extensions/pathmodifier.py index a27c547c1..bfc72f074 100644 --- a/share/extensions/pathmodifier.py +++ b/share/extensions/pathmodifier.py @@ -1,376 +1,307 @@ -#!/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 -''' -import inkex, cubicsuperpath, bezmisc, simplestyle -import copy, math, re, random, xml.xpath - -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() should it be given an id? - #seems to work without this!?! - clone.setAttributeNS(None,"id", self.uniqueId(node.tagName)) - node.parentNode.appendChild(clone) - clones[clone.getAttributeNS(None,"id")]=clone - return(clones) - - def uniqueId(self, prefix): - id="%s%04i"%(prefix,random.randint(0,9999)) - while len(xml.xpath.Evaluate('//*[@id="%s"]' % id,self.document)): - id="%s%04i"%(prefix,random.randint(0,9999)) - return(id) - - def expandGroups(self,aList,transferTransform=True): - for id, node in aList.items(): - if node.tagName == 'g': - mat=parseTransform(node.getAttributeNS(None,"transform")) - for child in node.childNodes: - if child.nodeType==child.ELEMENT_NODE: - if transferTransform: - applyTransformToNode(mat,child) - aList.update(self.expandGroups({child.getAttribute('id'):child})) - if transferTransform: - node.removeAttribute("transform") - del aList[id] - return(aList) - - def expandGroupsUnlinkClones(self,aList,transferTransform=True,doReplace=True): - for id in aList.keys()[:]: - node=aList[id] - if node.tagName == 'g': - self.expandGroups(aList,transferTransform) - self.expandGroupsUnlinkClones(aList,transferTransform,doReplace) - #Hum... not very efficient if there are many clones of groups... - elif node.tagName == 'use': - refid=node.getAttributeNS(inkex.NSS[u'xlink'],'href') - path = '//*[@id="%s"]' % refid[1:] - refnode = xml.xpath.Evaluate(path,self.document)[0] - newnode=refnode.cloneNode(True) - self.recursNewIds(newnode) - - if node.hasAttributeNS(None,u'style'): - style=simplestyle.parseStyle(node.getAttributeNS(None,u'style')) - refstyle=simplestyle.parseStyle(refnode.getAttributeNS(None,u'style')) - style.update(refstyle) - newnode.setAttributeNS(None,'style',simplestyle.formatStyle(style)) - applyTransformToNode(parseTransform(node.getAttributeNS(None,'transform')),newnode) - if doReplace: - parent=node.parentNode - parent.insertBefore(newnode,node) - parent.removeChild(node) - del aList[id] - newid=newnode.getAttributeNS(None,'id') - aList.update(self.expandGroupsUnlinkClones({newid:newnode},transferTransform,doReplace)) - return aList - - def recursNewIds(self,node): - if node.nodeType==node.ELEMENT_NODE and node.hasAttributeNS(None,u'id'): - node.setAttributeNS(None,u'id',self.uniqueId(node.tagName)) - for child in node.childNodes: - self.recursNewIds(child) - - - - -# def makeClonesReal(self,aList,doReplace=True,recursivelytransferTransform=True): -# for id in aList.keys(): -# node=aList[id] -# if node.tagName == 'g': -# childs={} -# for child in node.childNodes: -# if child.nodeType==child.ELEMENT_NODE: -# childid=child.getAttributeNS(None,'id') -# del aList[childid] -# aList.update(self.makeClonesReal({childid:child},doReplace)) -# elif node.tagName == 'use': -# refid=node.getAttributeNS(inkex.NSS[u'xlink'],'href') -# path = '//*[@id="%s"]' % refid[1:] -# refnode = xml.xpath.Evaluate(path,document)[0] -# clone=refnode.cloneNode(True) -# cloneid=self.uniqueId(clone.tagName) -# clone.setAttributeNS(None,'id', cloneid) -# style=simplestyle.parseStyle(node.getAttributeNS(None,u'style')) -# refstyle=simplestyle.parseStyle(refnode.getAttributeNS(None,u'style')) -# style.update(refstyle) -# clone.setAttributeNS(None,'style',simplestyle.formatStyle(style)) -# applyTransformToNode(parseTransform(node.getAttributeNS(None,'transform')),clone) -# if doReplace: -# parent=node.parentNode -# parent.insertBefore(clone,node) -# parent.removeChild(node) -# del aList[id] -# aList.update(self.expandGroupsUnlinkClones({cloneid:clone})) -# return aList - -################################ -#-- Object conversion ---------- -################################ - - def rectToPath(self,node,doReplace=True): - if node.tagName == 'rect': - x =float(node.getAttributeNS(None,u'x')) - y =float(node.getAttributeNS(None,u'y')) - try: - rx=float(node.getAttributeNS(None,u'rx')) - ry=float(node.getAttributeNS(None,u'ry')) - except: - rx=0 - ry=0 - w =float(node.getAttributeNS(None,u'width' )) - h =float(node.getAttributeNS(None,u'height')) - d ='M %f,%f '%(x+rx,y) - d+='L %f,%f '%(x+w-rx,y) - d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+w,y+ry) - d+='L %f,%f '%(x+w,y+h-ry) - d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+w-rx,y+h) - d+='L %f,%f '%(x+rx,y+h) - d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x,y+h-ry) - d+='L %f,%f '%(x,y+ry) - d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+rx,y) - - newnode=self.document.createElement('path') - newnode.setAttributeNS(None,'d',d) - newnode.setAttributeNS(None,'id', self.uniqueId('path')) - newnode.setAttributeNS(None,'style',node.getAttributeNS(None,u'style')) - newnode.setAttributeNS(None,'transform',node.getAttributeNS(None,u'transform')) - fuseTransform(newnode) - if doReplace: - parent=node.parentNode - parent.insertBefore(newnode,node) - parent.removeChild(node) - return newnode - - def objectToPath(self,node,doReplace=True): - #--TODO: support other object types!!!! - #--TODO: make sure cubicsuperpath supports A and Q commands... - if node.tagName == 'rect': - return(self.rectToPath(node,doReplace)) - elif node.tagName == 'path': - attributes = node.attributes.keys() - for uri,attName in attributes: - if uri in [inkex.NSS[u'sodipodi'],inkex.NSS[u'inkscape']]: -# if attName not in ["d","id","style","transform"]: - node.removeAttributeNS(uri,attName) - fuseTransform(node) - return node - else: - inkex.debug("Please first convert objects to paths!...(got '%s')"%node.tagName) - return None - - def objectsToPaths(self,aList,doReplace=True): - newSelection={} - for id,node in aList.items(): - newnode=self.objectToPath(node,self.document) - del aList[id] - aList[newnode.getAttributeNS(None,u'id')]=newnode - - -################################ -#-- Action ---------- -################################ - - #-- overwrite this method in subclasses... - def effect(self): - #self.duplicateNodes(self.selected) - self.expandGroupsUnlinkClones(self.selected, True) - self.objectsToPaths(self.selected, True) - self.bbox=self.computeBBox(self.selected) - for id, node in self.selected.iteritems(): - if node.tagName == 'path': - d = node.attributes.getNamedItem('d') - p = cubicsuperpath.parsePath(d.value) - - #do what ever you want with p! - - d.value = cubicsuperpath.formatPath(p) - - -class Diffeo(PathModifier): - def __init__(self): - inkex.Effect.__init__(self) - - def applyDiffeo(self,bpt,vects=()): - ''' - bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt. - Defaults to identity! - ''' - for v in vects: - v[0]-=bpt[0] - v[1]-=bpt[1] - - #-- your transformations go here: - #x,y=bpt - #bpt[0]=f(x,y) - #bpt[1]=g(x,y) - #for v in vects: - # vx,vy=v - # v[0]=df/dx(x,y)*vx+df/dy(x,y)*vy - # v[1]=dg/dx(x,y)*vx+dg/dy(x,y)*vy - # - #-- !caution! y-axis is pointing downward! - - for v in vects: - v[0]+=bpt[0] - v[1]+=bpt[1] - - - def effect(self): - #self.duplicateNodes(self.selected) - self.expandGroupsUnlinkClones(self.selected, True) - self.expandGroups(self.selected, True) - self.objectsToPaths(self.selected, True) - self.bbox=self.computeBBox(self.selected) - for id, node in self.selected.iteritems(): - if node.tagName == 'path': - d = node.attributes.getNamedItem('d') - p = cubicsuperpath.parsePath(d.value) - - for sub in p: - for ctlpt in sub: - self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2])) - - d.value = cubicsuperpath.formatPath(p) - -#e = Diffeo() -#e.affect() - - +#!/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 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 + +#################################################################### +##-- zOrder computation... +##-- this should be shipped out in a separate file. inkex.py? + +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 + + +class PathModifier(inkex.Effect): + def __init__(self): + inkex.Effect.__init__(self) + +################################## +#-- Selectionlists manipulation -- +################################## + + def duplicateNodes(self, aList): + clones={} + for id,node in aList.iteritems(): + clone=copy.deepcopy(node) + #!!!--> should it be given an id? + #seems to work without this!?! + myid = node.tag.split('}')[-1] + clone.set("id", self.uniqueId(myid)) + node.getparent().append(clone) + clones[clone.get("id")]=clone + return(clones) + + def uniqueId(self, prefix): + id="%s%04i"%(prefix,random.randint(0,9999)) + while len(self.document.getroot().xpath('//*[@id="%s"]' % id,namespaces=inkex.NSS)): + id="%s%04i"%(prefix,random.randint(0,9999)) + return(id) + + def expandGroups(self,aList,transferTransform=True): + for id, node in aList.items(): + if node.tag == inkex.addNS('g','svg') or node.tag=='g': + mat=parseTransform(node.get("transform")) + for child in node: + if transferTransform: + applyTransformToNode(mat,child) + aList.update(self.expandGroups({child.get('id'):child})) + if transferTransform and node.get("transform"): + del node.attrib["transform"] + del aList[id] + return(aList) + + def expandGroupsUnlinkClones(self,aList,transferTransform=True,doReplace=True): + for id in aList.keys()[:]: + node=aList[id] + if node.tag == inkex.addNS('g','svg') or node.tag=='g': + self.expandGroups(aList,transferTransform) + self.expandGroupsUnlinkClones(aList,transferTransform,doReplace) + #Hum... not very efficient if there are many clones of groups... + + elif node.tag == inkex.addNS('use','svg') or node.tag=='use': + refnode=self.refNode(node) + newnode=self.unlinkClone(node,doReplace) + del aList[id] + + style = simplestyle.parseStyle(node.get('style') or "") + refstyle=simplestyle.parseStyle(refnode.get('style') or "") + style.update(refstyle) + newnode.set('style',simplestyle.formatStyle(style)) + + newid=newnode.get('id') + aList.update(self.expandGroupsUnlinkClones({newid:newnode},transferTransform,doReplace)) + return aList + + def recursNewIds(self,node): + if node.get('id'): + node.set('id',self.uniqueId(node.tag)) + for child in node: + self.recursNewIds(child) + + def refNode(self,node): + if node.get(inkex.addNS('href','xlink')): + refid=node.get(inkex.addNS('href','xlink')) + path = '//*[@id="%s"]' % refid[1:] + newNode = self.document.getroot().xpath(path, namespaces=inkex.NSS)[0] + return newNode + else: + raise AssertionError, "Trying to follow empty xlink.href attribute." + + def unlinkClone(self,node,doReplace): + if node.tag == inkex.addNS('use','svg') or node.tag=='use': + newNode = copy.deepcopy(self.refNode(node)) + self.recursNewIds(newNode) + applyTransformToNode(parseTransform(node.get('transform')),newNode) + + if doReplace: + parent=node.getparent() + parent.insert(parent.index(node),newNode) + parent.remove(node) + + return newNode + else: + raise AssertionError, "Only clones can be unlinked..." + + + +################################ +#-- Object conversion ---------- +################################ + + def rectToPath(self,node,doReplace=True): + if node.tag == inkex.addNS('rect','svg'): + x =float(node.get('x')) + y =float(node.get('y')) + #FIXME: no exception anymore and sometimes just one + try: + rx=float(node.get('rx')) + ry=float(node.get('ry')) + except: + rx=0 + ry=0 + w =float(node.get('width' )) + h =float(node.get('height')) + d ='M %f,%f '%(x+rx,y) + d+='L %f,%f '%(x+w-rx,y) + d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+w,y+ry) + d+='L %f,%f '%(x+w,y+h-ry) + d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+w-rx,y+h) + d+='L %f,%f '%(x+rx,y+h) + d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x,y+h-ry) + d+='L %f,%f '%(x,y+ry) + d+='A %f,%f,%i,%i,%i,%f,%f '%(rx,ry,0,0,1,x+rx,y) + + newnode=inkex.etree.Element('path') + newnode.set('d',d) + newnode.set('id', self.uniqueId('path')) + newnode.set('style',node.get('style')) + nnt = node.get('transform') + if nnt: + newnode.set('transform',nnt) + fuseTransform(newnode) + if doReplace: + parent=node.getparent() + parent.insert(parent.index(node),newnode) + parent.remove(node) + return newnode + + def groupToPath(self,node,doReplace=True): + if node.tag == inkex.addNS('g','svg'): + newNode = inkex.etree.SubElement(self.current_layer,inkex.addNS('path','svg')) + + newstyle = simplestyle.parseStyle(node.get('style') or "") + newp = [] + for child in node: + childstyle = simplestyle.parseStyle(child.get('style') or "") + childstyle.update(newstyle) + newstyle.update(childstyle) + childAsPath = self.objectToPath(child,False) + newp += cubicsuperpath.parsePath(childAsPath.get('d')) + newNode.set('d',cubicsuperpath.formatPath(newp)) + newNode.set('style',simplestyle.formatStyle(newstyle)) + + self.current_layer.remove(newNode) + if doReplace: + parent=node.getparent() + parent.insert(parent.index(node),newNode) + parent.remove(node) + + return newNode + else: + raise AssertionError + + def objectToPath(self,node,doReplace=True): + #--TODO: support other object types!!!! + #--TODO: make sure cubicsuperpath supports A and Q commands... + if node.tag == inkex.addNS('rect','svg'): + return(self.rectToPath(node,doReplace)) + if node.tag == inkex.addNS('g','svg'): + return(self.groupToPath(node,doReplace)) + elif node.tag == inkex.addNS('path','svg') or node.tag == 'path': + #remove inkscape attributes, otherwise any modif of 'd' will be discarded! + for attName in node.attrib.keys(): + if ("sodipodi" in attName) or ("inkscape" in attName): + del node.attrib[attName] + fuseTransform(node) + return node + elif node.tag == inkex.addNS('use','svg') or node.tag == 'use': + newNode = self.unlinkClone(node,doReplace) + return self.objectToPath(newNode,doReplace) + else: + inkex.debug("Please first convert objects to paths!...(got '%s')"%node.tag) + return None + + def objectsToPaths(self,aList,doReplace=True): + newSelection={} + for id,node in aList.items(): + newnode=self.objectToPath(node,doReplace) + del aList[id] + aList[newnode.get('id')]=newnode + + +################################ +#-- Action ---------- +################################ + + #-- overwrite this method in subclasses... + def effect(self): + #self.duplicateNodes(self.selected) + #self.expandGroupsUnlinkClones(self.selected, True) + self.objectsToPaths(self.selected, True) + self.bbox=computeBBox(self.selected.values()) + for id, node in self.selected.iteritems(): + if node.tag == inkex.addNS('path','svg'): + d = node.get('d') + p = cubicsuperpath.parsePath(d) + + #do what ever you want with p! + + node.set('d',cubicsuperpath.formatPath(p)) + + +class Diffeo(PathModifier): + def __init__(self): + inkex.Effect.__init__(self) + + def applyDiffeo(self,bpt,vects=()): + ''' + bpt is a base point and for v in vectors, v'=v-p is a tangent vector at bpt. + Defaults to identity! + ''' + for v in vects: + v[0]-=bpt[0] + v[1]-=bpt[1] + + #-- your transformations go here: + #x,y=bpt + #bpt[0]=f(x,y) + #bpt[1]=g(x,y) + #for v in vects: + # vx,vy=v + # v[0]=df/dx(x,y)*vx+df/dy(x,y)*vy + # v[1]=dg/dx(x,y)*vx+dg/dy(x,y)*vy + # + #-- !caution! y-axis is pointing downward! + + for v in vects: + v[0]+=bpt[0] + v[1]+=bpt[1] + + + def effect(self): + #self.duplicateNodes(self.selected) + self.expandGroupsUnlinkClones(self.selected, True) + self.expandGroups(self.selected, True) + self.objectsToPaths(self.selected, True) + self.bbox=computeBBox(self.selected.values()) + for id, node in self.selected.iteritems(): + if node.tag == inkex.addNS('path','svg') or node.tag=='path': + d = node.get('d') + p = cubicsuperpath.parsePath(d) + + for sub in p: + for ctlpt in sub: + self.applyDiffeo(ctlpt[1],(ctlpt[0],ctlpt[2])) + + node.set('d',cubicsuperpath.formatPath(p)) + +#e = Diffeo() +#e.affect() + +