edge3d.py revision a223910930e4cf964962a08cf1d30928395652c5
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal#!/usr/bin/env python
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal'''
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalCopyright (C) 2007 Terry Brown, terry_n_brown@yahoo.com
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalThis program is free software; you can redistribute it and/or modify
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalit under the terms of the GNU General Public License as published by
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalthe Free Software Foundation; either version 2 of the License, or
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal(at your option) any later version.
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalThis program is distributed in the hope that it will be useful,
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalbut WITHOUT ANY WARRANTY; without even the implied warranty of
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalGNU General Public License for more details.
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalYou should have received a copy of the GNU General Public License
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalalong with this program; if not, write to the Free Software
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalFoundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal'''
5b2208f64b606f78f10d1136d933135a9d70a11aacspikeimport inkex, simplepath, sys, copy
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalfrom math import degrees, atan2
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmalclass Edge3d(inkex.Effect):
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal def __init__(self):
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal inkex.Effect.__init__(self)
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal opts = [('-a', '--angle', 'float', 'angle', 45.0,
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal 'angle of illumination, clockwise, 45 = upper right'),
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal ('-d', '--stddev', 'float', 'stddev', 5.0,
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal 'stdDeviation for Gaussian Blur'),
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal ('-H', '--blurheight', 'float', 'blurheight', 2.0,
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal 'height for Gaussian Blur'),
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal ('-W', '--blurwidth', 'float', 'blurwidth', 2.0,
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal 'width for Gaussian Blur'),
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal ('-s', '--shades', 'int', 'shades', 2,
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal 'shades, 2 = black and white, 3 = black, grey, white, etc.'),
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal ('-b', '--bw', 'inkbool', 'bw', False,
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal 'black and white, create only the fully black and white wedges'),
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal ('-p', '--thick', 'float', 'thick', 10.,
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal 'stroke-width for path pieces'),
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal ]
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal for o in opts:
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.OptionParser.add_option(o[0], o[1], action="store", type=o[2],
e9df3bcdd3387ec845d1fea626de245aa8d7e93bpjrm dest=o[3], default=o[4], help=o[5])
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike self.filtId = ''
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal def angleBetween(self, start, end, angle):
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal """Return true if angle (degrees, clockwise, 0 = up/north) is between
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal angles start and end"""
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal def f(x):
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal """Maybe add 360 to x"""
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal if x < 0: return x + 360.
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal return x
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal # rotate all inputs by start, => start = 0
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal a = f(f(angle) - f(start))
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal e = f(f(end) - f(start))
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal return a < e
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal def effectX(self):
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal # size of a wedge for shade i, wedges come in pairs
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal delta = 360. / self.options.shades / 2.
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal for shade in range(0, self.options.shades):
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal if self.options.bw and shade > 0 and shade < self.options.shades-1:
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal continue
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.start = [self.options.angle - delta * (shade+1)]
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.end = [self.options.angle - delta * (shade)]
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.start.append( self.options.angle + delta * (shade) )
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.end.append( self.options.angle + delta * (shade+1) )
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.makeShade(float(shade)/float(self.options.shades-1))
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal def effect(self):
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal """Check each internode to see if it's in one of the wedges
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal for the current shade. shade is a floating point 0-1 white-black"""
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal # size of a wedge for shade i, wedges come in pairs
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal delta = 360. / self.options.shades / 2.
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal for id, node in self.selected.iteritems():
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike if node.tag == inkex.addNS('path','svg'):
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike d = node.get('d')
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike p = simplepath.parsePath(d)
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal g = None
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal for shade in range(0, self.options.shades):
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal if (self.options.bw and shade > 0 and
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal shade < self.options.shades - 1):
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal continue
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.start = [self.options.angle - delta * (shade+1)]
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.end = [self.options.angle - delta * (shade)]
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.start.append( self.options.angle + delta * (shade) )
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.end.append( self.options.angle + delta * (shade+1) )
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal level=float(shade)/float(self.options.shades-1)
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal last = []
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal result = []
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal for cmd,params in p:
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal if cmd == 'Z':
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal last = []
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal continue
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal if last:
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal a = degrees(atan2(params[-2:][0] - last[0],
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal params[-2:][1] - last[1]))
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal if (self.angleBetween(self.start[0], self.end[0], a) or
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal self.angleBetween(self.start[1], self.end[1], a)):
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal result.append(('M', last))
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal result.append((cmd, params))
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal last = params[-2:]
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal if result:
fc6d22228dfb86abee8182564ee3247447080e90buliabyak if g is None:
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal g = self.getGroup(node)
5b2208f64b606f78f10d1136d933135a9d70a11aacspike nn = copy.deepcopy(node)
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike nn.set('d',simplepath.formatPath(result))
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal col = 255 - int(255. * level)
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike a = 'fill:none;stroke:#%02x%02x%02x;stroke-opacity:1;stroke-width:10;%s' % ((col,)*3 + (self.filtId,))
e9df3bcdd3387ec845d1fea626de245aa8d7e93bpjrm nn.set('style',a)
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike g.append(nn)
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal def getGroup(self, node):
c07485ad7471f5e87ac31fd8e05e4d0882fd0971acspike defs = self.document.getroot().xpath('//svg:defs', namespaces=inkex.NSS)
e9df3bcdd3387ec845d1fea626de245aa8d7e93bpjrm if defs:
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal defs = defs[0]
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal # make a clipped group, clip with clone of original, clipped group
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal # include original and group of paths
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike clip = inkex.etree.SubElement(defs,inkex.addNS('clipPath','svg'))
5b2208f64b606f78f10d1136d933135a9d70a11aacspike clip.append(copy.deepcopy(node))
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal clipId = self.uniqueId('clipPath')
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike clip.set('id', clipId)
659d437ab1fa196d010c1e92b009b608b8e8b086acspike clipG = inkex.etree.SubElement(node.getparent(),inkex.addNS('g','svg'))
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike g = inkex.etree.SubElement(clipG,inkex.addNS('g','svg'))
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike clipG.set('clip-path', 'url(#'+clipId+')')
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal # make a blur filter reference by the style of each path
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike filt = inkex.etree.SubElement(defs,inkex.addNS('filter','svg'))
e9df3bcdd3387ec845d1fea626de245aa8d7e93bpjrm filtId = self.uniqueId('filter')
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike self.filtId = 'filter:url(#%s);' % filtId
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal for k, v in [('id', filtId), ('height', str(self.options.blurheight)),
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal ('width', str(self.options.blurwidth)),
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal ('x', '-0.5'), ('y', '-0.5')]:
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike filt.set(k, v)
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike fe = inkex.etree.SubElement(filt,inkex.addNS('feGaussianBlur','svg'))
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike fe.set('stdDeviation', str(self.options.stddev))
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal else:
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal # can't find defs, just group paths
e9df3bcdd3387ec845d1fea626de245aa8d7e93bpjrm g = inkex.etree.SubElement(node.getparent(),inkex.addNS('g','svg'))
cc6fa1a3e6d71df2944a8bc64ed739f1f34d0359acspike g.append(node)
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal return g
abcd8fbfef7fa11f0f88aa6966df7b7c2f851fc5ishmal
a223910930e4cf964962a08cf1d30928395652c5pjrmif __name__ == '__main__':
a223910930e4cf964962a08cf1d30928395652c5pjrm e = Edge3d()
a223910930e4cf964962a08cf1d30928395652c5pjrm e.affect()
77d65c763568495a6ffc7c15a81964448139f47apjrm
77d65c763568495a6ffc7c15a81964448139f47apjrm
77d65c763568495a6ffc7c15a81964448139f47apjrm# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 encoding=utf-8 textwidth=99