8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm#! /usr/bin/env python
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak'''
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakCopyright (C) 2007 Joel Holdsworth joel@airwebreathe.org.uk
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakThis program is free software; you can redistribute it and/or modify
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakit under the terms of the GNU General Public License as published by
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakthe Free Software Foundation; either version 2 of the License, or
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak(at your option) any later version.
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakThis program is distributed in the hope that it will be useful,
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakbut WITHOUT ANY WARRANTY; without even the implied warranty of
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakGNU General Public License for more details.
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakYou should have received a copy of the GNU General Public License
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakalong with this program; if not, write to the Free Software
107e00c8104649437b9520d0ba298dba659e7cd7JazzyNicoFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak'''
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakimport inkex, simplestyle, math
34401423d53f5ca799a8bda956328167fadcc58b~suvfrom simpletransform import computePointInNode
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakclass Spirograph(inkex.Effect):
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak def __init__(self):
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak inkex.Effect.__init__(self)
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak self.OptionParser.add_option("-R", "--primaryr",
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm action="store", type="float",
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak dest="primaryr", default=60.0,
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak help="The radius of the outer gear")
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak self.OptionParser.add_option("-r", "--secondaryr",
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm action="store", type="float",
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak dest="secondaryr", default=100.0,
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak help="The radius of the inner gear")
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak self.OptionParser.add_option("-d", "--penr",
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm action="store", type="float",
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak dest="penr", default=50.0,
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak help="The distance of the pen from the inner gear")
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak self.OptionParser.add_option("-p", "--gearplacement",
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm action="store", type="string",
871e6dec0a43899a0031100adcf3343a7746bfa5Alex Valavanis dest="gearplacement", default="inside",
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak help="Selects whether the gear is inside or outside the ring")
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak self.OptionParser.add_option("-a", "--rotation",
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm action="store", type="float",
48eeda3eb22fd164613d5d9538c6e720cc92ef01buliabyak dest="rotation", default=0.0,
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm help="The number of degrees to rotate the image by")
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak self.OptionParser.add_option("-q", "--quality",
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm action="store", type="int",
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak dest="quality", default=16,
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm help="The quality of the calculated output")
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak def effect(self):
bcceefd876f22bacb6aeb206512ddb1539d2ced8apenner self.options.primaryr = self.unittouu(str(self.options.primaryr) + 'px')
bcceefd876f22bacb6aeb206512ddb1539d2ced8apenner self.options.secondaryr = self.unittouu(str(self.options.secondaryr) + 'px')
bcceefd876f22bacb6aeb206512ddb1539d2ced8apenner self.options.penr = self.unittouu(str(self.options.penr) + 'px')
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak if self.options.secondaryr == 0:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak return
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak if self.options.quality == 0:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak return
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak if(self.options.gearplacement.strip(' ').lower().startswith('outside')):
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak a = self.options.primaryr + self.options.secondaryr
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak flip = -1
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak else:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak a = self.options.primaryr - self.options.secondaryr
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak flip = 1
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak ratio = a / self.options.secondaryr
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak if ratio == 0:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak return
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak scale = 2 * math.pi / (ratio * self.options.quality)
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
48eeda3eb22fd164613d5d9538c6e720cc92ef01buliabyak rotation = - math.pi * self.options.rotation / 180;
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
60bbcba041e80a4b29118269c0897df5c068563eacspike new = inkex.etree.Element(inkex.addNS('path','svg'))
bcceefd876f22bacb6aeb206512ddb1539d2ced8apenner s = { 'stroke': '#000000', 'fill': 'none', 'stroke-width': str(self.unittouu('1px')) }
60bbcba041e80a4b29118269c0897df5c068563eacspike new.set('style', simplestyle.formatStyle(s))
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak pathString = ''
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak maxPointCount = 1000
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak for i in range(maxPointCount):
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak theta = i * scale
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
34401423d53f5ca799a8bda956328167fadcc58b~suv view_center = computePointInNode(list(self.view_center), self.current_layer)
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak x = a * math.cos(theta + rotation) + \
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak self.options.penr * math.cos(ratio * theta + rotation) * flip + \
34401423d53f5ca799a8bda956328167fadcc58b~suv view_center[0]
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak y = a * math.sin(theta + rotation) - \
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak self.options.penr * math.sin(ratio * theta + rotation) + \
34401423d53f5ca799a8bda956328167fadcc58b~suv view_center[1]
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak dx = (-a * math.sin(theta + rotation) - \
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak ratio * self.options.penr * math.sin(ratio * theta + rotation) * flip) * scale / 3
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak dy = (a * math.cos(theta + rotation) - \
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak ratio * self.options.penr * math.cos(ratio * theta + rotation)) * scale / 3
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak if i <= 0:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak pathString += 'M ' + str(x) + ',' + str(y) + ' C ' + str(x + dx) + ',' + str(y + dy) + ' '
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak else:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak pathString += str(x - dx) + ',' + str(y - dy) + ' ' + str(x) + ',' + str(y)
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak if math.fmod(i / ratio, self.options.quality) == 0 and i % self.options.quality == 0:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak pathString += 'Z'
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak break
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak else:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak if i == maxPointCount - 1:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak pass # we reached the allowed maximum of points, stop here
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak else:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak pathString += ' C ' + str(x + dx) + ',' + str(y + dy) + ' '
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
60bbcba041e80a4b29118269c0897df5c068563eacspike new.set('d', pathString)
60bbcba041e80a4b29118269c0897df5c068563eacspike self.current_layer.append(new)
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
a223910930e4cf964962a08cf1d30928395652c5pjrmif __name__ == '__main__':
a223910930e4cf964962a08cf1d30928395652c5pjrm e = Spirograph()
a223910930e4cf964962a08cf1d30928395652c5pjrm e.affect()
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
77d65c763568495a6ffc7c15a81964448139f47apjrm
a4030d5ca449e7e384bc699cd249ee704faaeab0Chris Morgan# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99