#!/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... ''' # standard library import copy import math import re import random # local library import inkex import cubicsuperpath import bezmisc import simplestyle from simpletransform import * inkex.localize() #################################################################### ##-- 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.errormsg(_("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() # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99