#! /usr/bin/env python ''' Copyright (C) 2013 Matthew Dockrey (gfish @ cyphertext.net) 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 Based on gears.py by Aaron Spike and Tavmjong Bah ''' import inkex import simplestyle import sys from math import * from fablabchemnitz_svg import * class Sprockets(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("-s", "--size", action="store", type="string", dest="size", default="ANSI #40", help="Chain size (common values ANSI #35, ANSI #40, ANSI #60)") def get_pitch(self, size): return self.unittouu({ 'ANSI #25': '6.35mm', 'ANSI #35': '9.53mm', 'ANSI #40': '12.70mm', 'ANSI #41': '12.70mm', 'ANSI #50': '15.88mm', 'ANSI #60': '19.05mm', 'ANSI #80': '25.40mm', 'ANSI #100': '31.75mm', 'ANSI #120': '38.10mm', 'ANSI #140': '44.45mm', 'ANSI #160': '50.80mm', 'ANSI #180': '57.15mm', 'ANSI #200': '63.50mm', 'ANSI #240': '76.20mm' }[size]) def get_roller_diameter(self, size): return self.unittouu({ 'ANSI #25': '3.30mm', 'ANSI #35': '5.08mm', 'ANSI #40': '7.77mm', 'ANSI #41': '7.92mm', 'ANSI #50': '10.16mm', 'ANSI #60': '11.91mm', 'ANSI #80': '15.88mm', 'ANSI #100': '19.05mm', 'ANSI #120': '22.23mm', 'ANSI #140': '25.40mm', 'ANSI #160': '28.58mm', 'ANSI #180': '37.08mm', 'ANSI #200': '39.67mm', 'ANSI #240': '47.63mm' }[size]) def invertX(self, p): return (-p[0], p[1]) def effect(self): size = self.options.size P = self.get_pitch(size) N = self.options.teeth PD = P / sin(pi / N) PR = PD / 2 # Equations taken from # http://www.gearseds.com/files/design_draw_sprocket_5.pdf # Also referenced: # http://en.wikipedia.org/wiki/Roller_chain (of course) # and # Chains for Power Transmission and Material Handling: # Design and Applications Handbook # American Chain Association, 1982 Dr = self.get_roller_diameter(size) Ds = 1.0005 * Dr + self.unittouu('0.003in') R = Ds / 2 # seating curve radius A = radians(35 + 60 / N) B = radians(18 - 56 / N) ac = 0.8 * Dr M = ac * cos(A) T = ac * sin(A) E = 1.3025 * Dr + self.unittouu('0.0015in') # transition radius ab = 1.4 * Dr W = ab * cos(pi / N) V = ab * sin(pi / N) F = Dr * (0.8 * cos(radians(18 - 56 / N)) + 1.4 * cos(radians(17 - 64 / N)) - 1.3025) - self.unittouu('0.0015in') # topping curve radius svg = "" t_inc = 2.0 * pi / float(N) thetas = [(x * t_inc) for x in range(N)] for theta in thetas: # Seating curve center seatC = (0, -PR) # Transitional curve center c = (M, -PR - T) # Calculate line cx, angle A from x axis # Y = mX + b cx_m = -tan(A) # Negative because we're in -Y space cx_b = c[1] - cx_m * c[0] # Calculate intersection of cx with circle S to get point x # http://math.stackexchange.com/questions/228841/how-do-i-calculate-the-intersections-of-a-straight-line-and-a-circle qA = cx_m * cx_m + 1 qB = 2 * (cx_m * cx_b - cx_m * seatC[1] - seatC[0]) qC = seatC[1] * seatC[1] - R * R + seatC[0] * seatC[0] - 2 * cx_b * seatC[1] + cx_b * cx_b cx_X = (-qB - sqrt(qB * qB - 4 * qA * qC)) / (2 * qA) # Seating curve/Transitional curve junction x = (cx_X, cx_m * cx_X + cx_b) # Calculate line cy, angle B past cx cy_m = -tan(A - B) cy_b = c[1] - cy_m * c[0] # Calculate point y (E along cy from c) # http://www.physicsforums.com/showthread.php?t=419561 yX = c[0] - E / sqrt(1 + cy_m * cy_m) # Transitional curve/Tangent line junction y = (yX, cy_m * yX + cy_b) # Solve for circle T with radius E which passes through x and y # http://mathforum.org/library/drmath/view/53027.html # http://stackoverflow.com/questions/12264841/determine-circle-center-based-on-two-points-radius-known-with-solve-optim z = ((x[0] + y[0]) / 2, (x[1] + y[1]) / 2) x_diff = y[0] - x[0] y_diff = y[1] - x[1] q = sqrt(x_diff * x_diff + y_diff * y_diff) tX = z[0] + sqrt(E * E - (q / 2) * (q / 2)) * (x[1] - y[1]) / q tY = z[1] + sqrt(E * E - (q / 2) * (q / 2)) * (y[0] - x[0]) / q # Transitional curve center tranC = (tX, tY) # Tangent line -- tangent to transitional curve at point y tanl_m = -(tranC[0] - y[0]) / (tranC[1] - y[1]) tanl_b = -y[0] * tanl_m + y[1] t_off = (y[0] - 10, tanl_m * (y[0] - 10) + tanl_b) # Topping curve center topC = (-W, -PR + V) # Adjust F to force topping curve tangent to tangent line F = abs(topC[1] - tanl_m * topC[0] - tanl_b) / sqrt(tanl_m * tanl_m + 1) * 1.0001 # Final fudge needed to overcome numerical instability # Find intersection point between topping curve and tangent line ttA = tanl_m * tanl_m + 1 ttB = 2 * (tanl_m * tanl_b - tanl_m * topC[1] - topC[0]) ttC = topC[1] * topC[1] - F * F + topC[0] * topC[0] - 2 * tanl_b * topC[1] + tanl_b * tanl_b tanl_X = (-ttB - sqrt(ttB * ttB - 4 * ttA * ttC)) / (2 * ttA) # Tagent line/Topping curve junction tanl = (tanl_X, tanl_m * tanl_X + tanl_b) # Calculate tip line, angle t_inc/2 from Y axis tip_m = -tan(pi / 2 + t_inc / 2) # Negative because we're in -Y space tip_b = 0 # Calculate intersection of tip line with topping curve tA = tip_m * tip_m + 1 tB = 2 * (tip_m * tip_b - tip_m * topC[1] - topC[0]) tC = topC[1] * topC[1] - F * F + topC[0] * topC[0] - 2 * tip_b * topC[1] + tip_b * tip_b tip_X = (-tB - sqrt(tB * tB - 4 * tA * tC)) / (2 * tA) # Topping curve top tip = (tip_X, tip_m * tip_X + tip_b) # Set initial location if needed if (theta == 0): svg += SVG_move(tip, theta) svg += SVG_circle(tanl, F, 1, theta) # Topping curve left svg += SVG_line(y, theta) # Tangent line left svg += SVG_circle(x, E, 0, theta) # Transitional curve left svg += SVG_circle(self.invertX(x), R, 0, theta) # Seating curve svg += SVG_circle(self.invertX(y), E, 0, theta) # Transitionl curve right svg += SVG_line(self.invertX(tanl), theta) # Tangent line right svg += SVG_circle(self.invertX(tip), F, 1, theta) # Topping curve right svg += SVG_close() # Insert as a new element sprocket_style = { 'stroke': '#000000', 'stroke-width': '0.1', 'fill': 'none' } g_attribs = {inkex.addNS('label','inkscape'): 'Sprocket ' + size + "-" + str(N), 'transform': 'translate(' + str(self.view_center[0]) + ',' + str(self.view_center[1]) + ')', 'style' : simplestyle.formatStyle(sprocket_style), 'd' : svg } g = inkex.etree.SubElement(self.current_layer, inkex.addNS('path','svg'), g_attribs) if __name__ == '__main__': e = Sprockets() e.affect()