#!/usr/bin/env python # coding=utf-8 # # Copyright (C) 2005 Aaron Spike, aaron@ekips.org # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # """Adds a "drop shadow" to any selected number of path objects. Optionally, the stroke color can be used for the shadow""" import math import inkex from inkex.paths import ( Move, Line, Curve, ZoneClose, Arc, Path, Vert, Horz, TepidQuadratic, Quadratic, Smooth, ) from inkex.transforms import Vector2d from inkex.bezier import beziertatslope, beziersplitatt class Motion(inkex.EffectExtension): """Generate a motion path""" def add_arguments(self, pars): pars.add_argument( "-a", "--angle", type=float, default=45.0, help="direction of the motion vector", ) pars.add_argument( "-m", "--magnitude", type=float, default=100.0, help="magnitude of the motion vector", ) pars.add_argument( "-f", "--fillwithstroke", type=inkex.Boolean, default=False, help="fill shadow with stroke color if set", ) @staticmethod def makeface(last, segment, facegroup, delx, dely): """translate path segment along vector""" elem = facegroup.add(inkex.PathElement()) npt = segment.translate([delx, dely]) # reverse direction of path segment if isinstance(segment, Curve): rev = Curve(npt.x3, npt.y3, npt.x2, npt.y2, last[0] + delx, last[1] + dely) elif isinstance(segment, Line): rev = Line(last[0] + delx, last[1] + dely) else: raise RuntimeError("Unexpected segment type {}".format(type(segment))) elem.path = inkex.Path( [ Move(last[0], last[1]), segment, npt.to_line(Vector2d()), rev, ZoneClose(), ] ) def effect(self): delx = math.cos(math.radians(self.options.angle)) * self.options.magnitude dely = math.sin(math.radians(self.options.angle)) * self.options.magnitude for node in self.svg.selection.filter_nonzero(inkex.PathElement): group = node.getparent().add(inkex.Group()) facegroup = group.add(inkex.Group()) group.append(node) # we want delx and dely values to be correct even for the transformed path, # so transform them back, but ignore the translate of the transform trans = -node.transform local_delx = trans.a * delx + trans.c * dely local_dely = trans.b * delx + trans.d * dely if node.transform: group.transform = node.transform node.transform = None facegroup.style = node.style if self.options.fillwithstroke: stroke = facegroup.style("stroke") if stroke is not None and isinstance(stroke, inkex.Color): facegroup.style["fill"] = stroke facegroup.style["fill-opacity"] = facegroup.style("stroke-opacity") reset_origin = True for cmd_proxy in node.path.to_absolute().proxy_iterator(): # for each subpath, reset the origin of the following computations to the first # node of the subpath -> i.e. after a Z command, move the origin to the end point # of the next command if reset_origin: first_point = cmd_proxy.end_point reset_origin = False if isinstance(cmd_proxy.command, ZoneClose): reset_origin = True self.process_segment( cmd_proxy, facegroup, local_delx, local_dely, first_point ) @staticmethod def process_segment(cmd_proxy, facegroup, delx, dely, first_point): """Process each segments""" segments = [] if isinstance( cmd_proxy.command, (Curve, Smooth, TepidQuadratic, Quadratic, Arc) ): prev = cmd_proxy.previous_end_point for curve in cmd_proxy.to_curves(): bez = [prev] + curve.to_bez() prev = curve.end_point(cmd_proxy.first_point, prev) tees = [t for t in beziertatslope(bez, (dely, delx)) if 0 < t < 1] tees.sort() if len(tees) == 1: one, two = beziersplitatt(bez, tees[0]) segments.append(Curve(*(one[1] + one[2] + one[3]))) segments.append(Curve(*(two[1] + two[2] + two[3]))) elif len(tees) == 2: one, two = beziersplitatt(bez, tees[0]) two, three = beziersplitatt(two, tees[1]) segments.append(Curve(*(one[1] + one[2] + one[3]))) segments.append(Curve(*(two[1] + two[2] + two[3]))) segments.append(Curve(*(three[1] + three[2] + three[3]))) else: segments.append(curve) elif isinstance(cmd_proxy.command, (Line, Curve)): segments.append(cmd_proxy.command) elif isinstance(cmd_proxy.command, ZoneClose): segments.append(Line(*first_point)) elif isinstance(cmd_proxy.command, (Vert, Horz)): segments.append(cmd_proxy.command.to_line(cmd_proxy.end_point)) for seg in Path( [Move(*cmd_proxy.previous_end_point)] + segments ).proxy_iterator(): if isinstance(seg.command, Move): continue Motion.makeface(seg.previous_end_point, seg.command, facegroup, delx, dely) if __name__ == "__main__": Motion().run()