#! /usr/bin/env python ''' Copyright (C) 2007 Aaron Spike (aaron @ ekips.org) Copyright (C) 2007 Tavmjong Bah (tavmjong @ free.fr) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ''' import inkex import simplestyle, sys from math import * import string def involute_intersect_angle(Rb, R): Rb, R = float(Rb), float(R) return (sqrt(R**2 - Rb**2) / (Rb)) - (acos(Rb / R)) def point_on_circle(radius, angle): x = radius * cos(angle) y = radius * sin(angle) return (x, y) def points_to_svgd(p): f = p[0] p = p[1:] svgd = 'M%.5f,%.5f' % f for x in p: svgd += ' L%.5f,%.5f' % x svgd += 'z' return svgd class Gears(inkex.Effect): def __init__(self): inkex.Effect.__init__(self) self.OptionParser.add_option("-t", "--teeth", action="store", type="int", dest="teeth", default=24, help="Number of teeth") self.OptionParser.add_option("-p", "--pitch", action="store", type="float", dest="pitch", default=20.0, help="Circular Pitch (length of arc from one tooth to next)") self.OptionParser.add_option("-a", "--angle", action="store", type="float", dest="angle", default=20.0, help="Pressure Angle (common values: 14.5, 20, 25 degrees)") self.OptionParser.add_option("-c", "--centerdiameter", action="store", type="float", dest="centerdiameter", default=10.0, help="Diameter of central hole - 0.0 for no hole") self.OptionParser.add_option("-u", "--unit", action="store", type="string", dest="unit", default="px", help="unit of measure for circular pitch and center diameter") def effect(self): teeth = self.options.teeth pitch = self.unittouu( str(self.options.pitch) + self.options.unit) angle = self.options.angle # Angle of tangent to tooth at circular pitch wrt radial line. centerdiameter = self.unittouu( str(self.options.centerdiameter) + self.options.unit) # print >>sys.stderr, "Teeth: %s\n" % teeth two_pi = 2.0 * pi # Pitch (circular pitch): Length of the arc from one tooth to the next) # Pitch diameter: Diameter of pitch circle. pitch_diameter = float( teeth ) * pitch / pi pitch_radius = pitch_diameter / 2.0 # Base Circle base_diameter = pitch_diameter * cos( radians( angle ) ) base_radius = base_diameter / 2.0 # Diametrial pitch: Number of teeth per unit length. pitch_diametrial = float( teeth )/ pitch_diameter # Addendum: Radial distance from pitch circle to outside circle. addendum = 1.0 / pitch_diametrial # Outer Circle outer_radius = pitch_radius + addendum outer_diameter = outer_radius * 2.0 # Tooth thickness: Tooth width along pitch circle. tooth = ( pi * pitch_diameter ) / ( 2.0 * float( teeth ) ) # Undercut? undercut = (2.0 / ( sin( radians( angle ) ) ** 2)) needs_undercut = teeth < undercut # Clearance: Radial distance between top of tooth on one gear to bottom of gap on another. clearance = 0.0 # Dedendum: Radial distance from pitch circle to root diameter. dedendum = addendum + clearance # Root diameter: Diameter of bottom of tooth spaces. root_radius = pitch_radius - dedendum root_diameter = root_radius * 2.0 half_thick_angle = two_pi / (4.0 * float( teeth ) ) pitch_to_base_angle = involute_intersect_angle( base_radius, pitch_radius ) pitch_to_outer_angle = involute_intersect_angle( base_radius, outer_radius ) - pitch_to_base_angle centers = [(x * two_pi / float( teeth) ) for x in range( teeth ) ] points = [] for c in centers: # Angles pitch1 = c - half_thick_angle base1 = pitch1 - pitch_to_base_angle outer1 = pitch1 + pitch_to_outer_angle pitch2 = c + half_thick_angle base2 = pitch2 + pitch_to_base_angle outer2 = pitch2 - pitch_to_outer_angle # Points b1 = point_on_circle( base_radius, base1 ) p1 = point_on_circle( pitch_radius, pitch1 ) o1 = point_on_circle( outer_radius, outer1 ) b2 = point_on_circle( base_radius, base2 ) p2 = point_on_circle( pitch_radius, pitch2 ) o2 = point_on_circle( outer_radius, outer2 ) if root_radius > base_radius: pitch_to_root_angle = pitch_to_base_angle - involute_intersect_angle(base_radius, root_radius ) root1 = pitch1 - pitch_to_root_angle root2 = pitch2 + pitch_to_root_angle r1 = point_on_circle(root_radius, root1) r2 = point_on_circle(root_radius, root2) p_tmp = [r1,p1,o1,o2,p2,r2] else: r1 = point_on_circle(root_radius, base1) r2 = point_on_circle(root_radius, base2) p_tmp = [r1,b1,p1,o1,o2,p2,b2,r2] points.extend( p_tmp ) path = points_to_svgd( points ) # Embed gear in group to make animation easier: # Translate group, Rotate path. t = 'translate(' + str( self.view_center[0] ) + ',' + str( self.view_center[1] ) + ')' g_attribs = {inkex.addNS('label','inkscape'):'Gear' + str( teeth ), 'transform':t } g = inkex.etree.SubElement(self.current_layer, 'g', g_attribs) # Create SVG Path for gear style = { 'stroke': '#000000', 'fill': 'none', 'stroke-width': str(self.unittouu('1px')) } gear_attribs = {'style':simplestyle.formatStyle(style), 'd':path} gear = inkex.etree.SubElement(g, inkex.addNS('path','svg'), gear_attribs ) if(centerdiameter > 0.0): center_attribs = {'style':simplestyle.formatStyle(style), inkex.addNS('cx','sodipodi') :'0.0', inkex.addNS('cy','sodipodi') :'0.0', inkex.addNS('rx','sodipodi') :str(centerdiameter/2), inkex.addNS('ry','sodipodi') :str(centerdiameter/2), inkex.addNS('type','sodipodi') :'arc' } center = inkex.etree.SubElement(g, inkex.addNS('path','svg'), center_attribs ) if __name__ == '__main__': e = Gears() e.affect() # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99