1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
#!/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()
|