#!/usr/bin/env python3
# twist.py -- Primarily a simple example of writing an Inkscape extension
# which manipulates objects in a drawing.
#
# For a polygon with vertices V[0], V[1], V[2], ..., V[n-1] iteratively
# move each vertex V[i] by a constant factor 0 < s < 1.0 along the edge
# between V[i] and V[i+1 modulo n] for 0 <= i <= n-1.
#
# This extension operates on every selected closed path, or, if no paths
# are selected, then every closed path in the document. Since the "twisting"
# effect only concerns itself with individual paths, no effort is made to
# worry about the transforms applied to the paths. That is, it is not
# necessary to worry about tracking SVG transforms as all the work can be
# done using the untransformed coordinates of each path.
# Written by Daniel C. Newman ( dan dot newman at mtbaldy dot us )
# 19 October 2010
# 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
import inkex
from inkex import Transform
from inkex import bezier
from inkex.paths import Path, CubicSuperPath
from lxml import etree
def subdivideCubicPath(sp, flat, i=1):
"""
[ Lifted from eggbot.py with impunity ]
Break up a bezier curve into smaller curves, each of which
is approximately a straight line within a given tolerance
(the "smoothness" defined by [flat]).
This is a modified version of cspsubdiv.cspsubdiv(): rewritten
because recursion-depth errors on complicated line segments
could occur with cspsubdiv.cspsubdiv().
"""
while True:
while True:
if i >= len(sp):
return
p0 = sp[i - 1][1]
p1 = sp[i - 1][2]
p2 = sp[i][0]
p3 = sp[i][1]
b = (p0, p1, p2, p3)
if bezier.maxdist(b) > flat:
break
i += 1
one, two = bezier.beziersplitatt(b, 0.5)
sp[i - 1][2] = one[1]
sp[i][0] = two[2]
p = [one[2], one[3], two[1]]
sp[i:1] = [p]
def distanceSquared(p1, p2):
"""
Pythagorean distance formula WITHOUT the square root. Since
we just want to know if the distance is less than some fixed
fudge factor, we can just square the fudge factor once and run
with it rather than compute square roots over and over.
"""
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
return dx * dx + dy * dy
class Twist(inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument("--nSteps", type=int, default=8, help="Number of iterations to take")
pars.add_argument("--fRatio", type=float, default=0.2, help="Some ratio")
"""
Store each path in an associative array (dictionary) indexed
by the lxml.etree pointer for the SVG document element
containing the path. Looking up the path in the dictionary
yields a list of lists. Each of these lists is a subpath
# of the path. E.g., for the SVG path
we'd have two subpaths which will be reduced to absolute
coordinates.
subpath_1 = [ [10, 10], [10, 15], [15, 15], [15, 10], [10,10] ]
subpath_2 = [ [30, 30], [30, 60] ]
self.paths[] = [ subpath_1, subpath_2 ]
All of the paths and their subpaths could be drawn as follows:
for path in self.paths:
for subpath in self.paths[path]:
first = True
for vertex in subpath:
if first:
moveto( vertex[0], vertex[1] )
first = False
else:
lineto( vertex[0], vertex[1] )
NOTE: drawing all the paths like the above would not in general
give the correct rendering of the document UNLESS path transforms
were also tracked and applied.
"""
self.paths = {}
self.paths_clone_transform = {}
def addPathVertices(self, path, node=None, transform=None, clone_transform=None):
"""
Decompose the path data from an SVG element into individual
subpaths, each subpath consisting of absolute move to and line
to coordinates. Place these coordinates into a list of polygon
vertices.
"""
if (not path) or (len(path) == 0):
# Nothing to do
return
sp = Path(path)
if (not sp) or (len(sp) == 0):
# Path must have been devoid of any real content
return
# Get a cubic super path
p = CubicSuperPath(sp)
if (not p) or (len(p) == 0):
# Probably never happens, but...
return
# Now traverse the cubic super path
subpath_list = []
subpath_vertices = []
for sp in p:
if len(subpath_vertices):
# There's a prior subpath: see if it is closed and should be saved
if distanceSquared(subpath_vertices[0], subpath_vertices[-1]) < 1:
# Keep the prior subpath: it appears to be a closed path
subpath_list.append(subpath_vertices)
subpath_vertices = []
subdivideCubicPath(sp, 0.2)
for csp in sp:
# Add this vertex to the list of vertices
subpath_vertices.append(csp[1])
# Handle final subpath
if len(subpath_vertices):
if distanceSquared(subpath_vertices[0], subpath_vertices[-1]) < 1:
# Path appears to be closed so let's keep it
subpath_list.append(subpath_vertices)
# Empty path?
if not subpath_list:
return
# Store the list of subpaths in a dictionary keyed off of the path's node pointer
self.paths[node] = subpath_list
self.paths_clone_transform[node] = clone_transform
def recursivelyTraverseSvg(self, a_node_list, mat_current=None, parent_visibility='visible', clone_transform=None):
"""
[ This too is largely lifted from eggbot.py ]
Recursively walk the SVG document, building polygon vertex lists
for each graphical element we support.
Rendered SVG elements:
, , , , , ,
Supported SVG elements:
,