1 #!/usr/bin/env python
2 '''
3 Copyright (C) 2007 Aaron Spike (aaron @ ekips.org)
4 Copyright (C) 2007 Tavmjong Bah (tavmjong @ free.fr)
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 '''\r
21 import inkex
22 import simplestyle, sys
23 from math import *
25 def involute_intersect_angle(Rb, R):
26 Rb, R = float(Rb), float(R)
27 return (sqrt(R**2 - Rb**2) / (Rb)) - (acos(Rb / R))
29 def point_on_circle(radius, angle):
30 x = radius * cos(angle)
31 y = radius * sin(angle)
32 return (x, y)
34 def points_to_svgd(p):
35 f = p[0]
36 p = p[1:]
37 svgd = 'M%.3f,%.3f' % f
38 for x in p:
39 svgd += 'L%.3f,%.3f' % x
40 svgd += 'z'
41 return svgd
43 class Gears(inkex.Effect):
44 def __init__(self):
45 inkex.Effect.__init__(self)
46 self.OptionParser.add_option("-t", "--teeth",
47 action="store", type="int",
48 dest="teeth", default=24,
49 help="Number of teeth")
50 self.OptionParser.add_option("-p", "--pitch",
51 action="store", type="float",
52 dest="pitch", default=20.0,
53 help="Circular Pitch (length of arc from one tooth to next)")
54 self.OptionParser.add_option("-a", "--angle",
55 action="store", type="float",
56 dest="angle", default=20.0,
57 help="Pressure Angle (common values: 14.5, 20, 25 degrees)")
58 def effect(self):
60 teeth = self.options.teeth
61 pitch = self.options.pitch
62 angle = self.options.angle # Angle of tangent to tooth at circular pitch wrt radial line.
64 # print >>sys.stderr, "Teeth: %s\n" % teeth
66 two_pi = 2.0 * pi
68 # Pitch (circular pitch): Length of the arc from one tooth to the next)
69 # Pitch diameter: Diameter of pitch circle.
70 pitch_diameter = float( teeth ) * pitch / pi
71 pitch_radius = pitch_diameter / 2.0
73 # Base Circle
74 base_diameter = pitch_diameter * cos( radians( angle ) )
75 base_radius = base_diameter / 2.0
77 # Diametrial pitch: Number of teeth per unit length.
78 pitch_diametrial = float( teeth )/ pitch_diameter
80 # Addendum: Radial distance from pitch circle to outside circle.
81 addendum = 1.0 / pitch_diametrial
83 # Outer Circle
84 outer_radius = pitch_radius + addendum
85 outer_diameter = outer_radius * 2.0
87 # Tooth thickness: Tooth width along pitch circle.
88 tooth = ( pi * pitch_diameter ) / ( 2.0 * float( teeth ) )
90 # Undercut?
91 undercut = (2.0 / ( sin( radians( angle ) ) ** 2))
92 needs_undercut = teeth < undercut
95 # Clearance: Radial distance between top of tooth on one gear to bottom of gap on another.
96 clearance = 0.0
98 # Dedendum: Radial distance from pitch circle to root diameter.
99 dedendum = addendum + clearance
101 # Root diameter: Diameter of bottom of tooth spaces.
102 root_radius = pitch_radius - dedendum
103 root_diameter = root_radius * 2.0
105 half_thick_angle = two_pi / (4.0 * float( teeth ) )
106 pitch_to_base_angle = involute_intersect_angle( base_radius, pitch_radius )
107 pitch_to_outer_angle = involute_intersect_angle( base_radius, outer_radius ) - pitch_to_base_angle
109 centers = [(x * two_pi / float( teeth) ) for x in range( teeth ) ]
111 points = []
113 for c in centers:
115 # Angles
116 pitch1 = c - half_thick_angle
117 base1 = pitch1 - pitch_to_base_angle
118 outer1 = pitch1 + pitch_to_outer_angle
120 pitch2 = c + half_thick_angle
121 base2 = pitch2 + pitch_to_base_angle
122 outer2 = pitch2 - pitch_to_outer_angle
124 # Points
125 b1 = point_on_circle( base_radius, base1 )
126 p1 = point_on_circle( pitch_radius, pitch1 )
127 o1 = point_on_circle( outer_radius, outer1 )
129 b2 = point_on_circle( base_radius, base2 )
130 p2 = point_on_circle( pitch_radius, pitch2 )
131 o2 = point_on_circle( outer_radius, outer2 )
133 if root_radius > base_radius:
134 pitch_to_root_angle = pitch_to_base_angle - involute_intersect_angle(base_radius, root_radius )
135 root1 = pitch1 - pitch_to_root_angle
136 root2 = pitch2 + pitch_to_root_angle
137 r1 = point_on_circle(root_radius, root1)
138 r2 = point_on_circle(root_radius, root2)
139 p_tmp = [r1,p1,o1,o2,p2,r2]
140 else:
141 r1 = point_on_circle(root_radius, base1)
142 r2 = point_on_circle(root_radius, base2)
143 p_tmp = [r1,b1,p1,o1,o2,p2,b2,r2]
145 points.extend( p_tmp )
147 path = points_to_svgd( points )
149 # Embed gear in group to make animation easier:
150 # Translate group, Rotate path.\r
151 t = 'translate(' + str( self.view_center[0] ) + ',' + str( self.view_center[1] ) + ')'\r
152 g_attribs = {inkex.addNS('label','inkscape'):'Gear' + str( teeth ),\r
153 'transform':t }
154 g = inkex.etree.SubElement(self.current_layer, 'g', g_attribs)
156 # Create SVG Path for gear\r
157 style = { 'stroke': '#000000', 'fill': 'none' }\r
158 gear_attribs = {'style':simplestyle.formatStyle(style), 'd':path}
159 gear = inkex.etree.SubElement(g, inkex.addNS('path','svg'), gear_attribs )\r
161 e = Gears()
162 e.affect()