#! /usr/bin/env python # coding=utf-8 # # Copyright (C) 2007 Joel Holdsworth joel@airwebreathe.org.uk # # 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. # import math import inkex class Spirograph(inkex.EffectExtension): def add_arguments(self, pars): pars.add_argument( "--primaryr", type=float, default=100.0, help="The radius of the outer gear" ) pars.add_argument( "--secondaryr", type=float, default=60.0, help="The radius of the inner gear", ) pars.add_argument( "--penr", type=float, default=50.0, help="The distance of the pen from the inner gear", ) pars.add_argument( "--gearplacement", default="inside", help="Selects whether the gear is inside or outside the ring", ) pars.add_argument( "--rotation", type=float, default=0.0, help="The number of degrees to rotate the image by", ) pars.add_argument( "--quality", type=int, default=16, help="The quality of the calculated output", ) def effect(self): self.options.primaryr = self.svg.unittouu(str(self.options.primaryr) + "px") self.options.secondaryr = self.svg.unittouu(str(self.options.secondaryr) + "px") self.options.penr = self.svg.unittouu(str(self.options.penr) + "px") if self.options.secondaryr == 0: return if self.options.quality == 0: return if self.options.gearplacement.strip(" ").lower().startswith("outside"): a = self.options.primaryr + self.options.secondaryr flip = -1 else: a = self.options.primaryr - self.options.secondaryr flip = 1 ratio = a / self.options.secondaryr if ratio == 0: return scale = 2 * math.pi / (ratio * self.options.quality) rotation = -math.pi * self.options.rotation / 180 new = inkex.PathElement() new.style = inkex.Style(stroke="#000000", fill="none", stroke_width="1.0") path_string = "" maxPointCount = 1000 for i in range(maxPointCount): theta = i * scale view_center = self.svg.namedview.center x = ( a * math.cos(theta + rotation) + self.options.penr * math.cos(ratio * theta + rotation) * flip + view_center[0] ) y = ( a * math.sin(theta + rotation) - self.options.penr * math.sin(ratio * theta + rotation) + view_center[1] ) dx = ( ( -a * math.sin(theta + rotation) - ratio * self.options.penr * math.sin(ratio * theta + rotation) * flip ) * scale / 3 ) dy = ( ( a * math.cos(theta + rotation) - ratio * self.options.penr * math.cos(ratio * theta + rotation) ) * scale / 3 ) if i <= 0: path_string += "M {},{} C {},{} ".format( str(x), str(y), str(x + dx), str(y + dy) ) else: path_string += "{},{} {},{}".format( str(x - dx), str(y - dy), str(x), str(y) ) if ( math.fmod(i / ratio, self.options.quality) == 0 and i % self.options.quality == 0 ): path_string += "Z" break else: if i == maxPointCount - 1: pass # we reached the allowed maximum of points, stop here else: path_string += " C {},{} ".format(str(x + dx), str(y + dy)) new.path = path_string self.svg.get_current_layer().append(new) if __name__ == "__main__": Spirograph().run()