2020-07-30 01:16:18 +02:00
#!/usr/bin/env python3
'''
Copyright ( C ) 2007 Aaron Spike ( aaron @ ekips . org )
Copyright ( C ) 2007 Tavmjong Bah ( tavmjong @ free . fr )
Copyright ( C ) http : / / cnc - club . ru / forum / viewtopic . php ? f = 33 & t = 434 & p = 2594 #p2500
Copyright ( C ) 2014 Jürgen Weigert ( juewei @fabmail.org )
Copyright ( C ) 2020 Spadino ( spada . andrea @ gmail DOT com )
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
2014 - 03 - 20 jw @suse.de 0.2 Option - - accuracy = 0 for automatic added .
2014 - 03 - 21 sent upstream : https : / / bugs . launchpad . net / inkscape / + bug / 1295641
2014 - 03 - 21 jw @suse.de 0.3 Fixed center of rotation for gears with odd number of teeth .
2014 - 04 - 04 juewei 0.7 Revamped calc_unit_factor ( ) .
2014 - 04 - 05 juewei 0.7 a Correctly positioned rack gear .
The geometry above the meshing line is wrong .
2014 - 04 - 06 juewei 0.7 b Undercut detection added . Reference :
http : / / nptel . ac . in / courses / IIT - MADRAS / Machine_Design_II / pdf / 2_2. pdf
Manually merged https : / / github . com / jnweiger / inkscape - gears - dev / pull / 15
2014 - 04 - 07 juewei 0.7 c Manually merged https : / / github . com / jnweiger / inkscape - gears - dev / pull / 17
2014 - 04 - 09 juewei 0.8 Fixed https : / / github . com / jnweiger / inkscape - gears - dev / issues / 19
Ring gears are ready for production now . Thanks neon22 for driving this .
Profile shift implemented ( Advanced Tab ) , fixing
https : / / github . com / jnweiger / inkscape - gears - dev / issues / 9
2015 - 05 - 29 juewei 0.9 ported to inkscape 0.91
AttributeError : ' module ' object inkex has no attribute ' uutounit
Fixed https : / / github . com / jnweiger / inkscape - gears - dev
2020 - 7 - 4 spadino 1.0 ported to inkscape 1.0
'''
import inkex
from lxml import etree
from os import devnull # for debugging
from math import pi , cos , sin , tan , radians , degrees , ceil , asin , acos , sqrt
two_pi = 2 * pi
__version__ = ' 1.0 '
def uutounit ( self , nn , uu ) :
return self . svg . uutounit ( nn , uu )
def linspace ( a , b , n ) :
""" return list of linear interp of a to b in n steps
- if a and b are ints - you ' ll get an int result.
- n must be an integer
"""
return [ a + x * ( b - a ) / ( n - 1 ) for x in range ( 0 , n ) ]
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 ) :
" return xy coord of the point at distance radius from origin at angle "
x = radius * cos ( angle )
y = radius * sin ( angle )
return ( x , y )
def points_to_bbox ( p ) :
""" from a list of points (x,y pairs)
- return the lower - left xy and upper - right xy
"""
llx = urx = p [ 0 ] [ 0 ]
lly = ury = p [ 0 ] [ 1 ]
for x in p [ 1 : ] :
if x [ 0 ] < llx : llx = x [ 0 ]
elif x [ 0 ] > urx : urx = x [ 0 ]
if x [ 1 ] < lly : lly = x [ 1 ]
elif x [ 1 ] > ury : ury = x [ 1 ]
return ( llx , lly , urx , ury )
def points_to_bbox_center ( p ) :
""" from a list of points (x,y pairs)
- find midpoint of bounding box around all points
- return ( x , y )
"""
bbox = points_to_bbox ( p )
return ( ( bbox [ 0 ] + bbox [ 2 ] ) / 2.0 , ( bbox [ 1 ] + bbox [ 3 ] ) / 2.0 )
def points_to_svgd ( p ) :
" convert list of points into a closed SVG path list "
f = p [ 0 ]
p = p [ 1 : ]
svgd = ' M %.4f , %.4f ' % f
for x in p :
svgd + = ' L %.4f , %.4f ' % x
svgd + = ' z '
return svgd
def draw_SVG_circle ( parent , r , cx , cy , name , style ) :
" add an SVG circle entity to parent "
circ_attribs = { ' style ' : str ( inkex . Style ( style ) ) ,
' cx ' : str ( cx ) , ' cy ' : str ( cy ) ,
' r ' : str ( r ) ,
inkex . addNS ( ' label ' , ' inkscape ' ) : name }
circle = etree . SubElement ( parent , inkex . addNS ( ' circle ' , ' svg ' ) , circ_attribs )
### Undercut support functions
def undercut_min_teeth ( pitch_angle , k = 1.0 ) :
""" computes the minimum tooth count for a
spur gear so that no undercut with the given pitch_angle ( in deg )
and an addendum = k * metric_module , where 0 < k < 1
Note :
The return value should be rounded upwards for perfect safety . E . g .
min_teeth = int ( math . ceil ( undercut_min_teeth ( 20.0 ) ) ) # 18, not 17
"""
x = sin ( radians ( pitch_angle ) )
return 2 * k / ( x * x )
def undercut_max_k ( teeth , pitch_angle = 20.0 ) :
""" computes the maximum k value for a given teeth count and pitch_angle
so that no undercut occurs .
"""
x = sin ( radians ( pitch_angle ) )
return 0.5 * teeth * x * x
def undercut_min_angle ( teeth , k = 1.0 ) :
""" computes the minimum pitch angle, to that the given teeth count (and
profile shift ) cause no undercut .
"""
return degrees ( asin ( min ( 0.856 , sqrt ( 2.0 * k / teeth ) ) ) ) # max 59.9 deg
def have_undercut ( teeth , pitch_angle = 20.0 , k = 1.0 ) :
""" returns true if the specified number of teeth would
cause an undercut .
"""
return ( teeth < undercut_min_teeth ( pitch_angle , k ) )
## gather all basic gear calculations in one place
def gear_calculations ( num_teeth , circular_pitch , pressure_angle , clearance = 0 , ring_gear = False , profile_shift = 0. ) :
""" Put base calcs for spur/ring gears in one place.
- negative profile shifting helps against undercut .
"""
diametral_pitch = pi / circular_pitch
pitch_diameter = num_teeth / diametral_pitch
pitch_radius = pitch_diameter / 2.0
addendum = 1 / diametral_pitch
#dedendum = 1.157 / diametral_pitch # auto calc clearance
dedendum = addendum
dedendum * = 1 + profile_shift
addendum * = 1 - profile_shift
if ring_gear :
addendum = addendum + clearance # our method
else :
dedendum = dedendum + clearance # our method
#
#
base_radius = pitch_diameter * cos ( radians ( pressure_angle ) ) / 2.0
outer_radius = pitch_radius + addendum
root_radius = pitch_radius - dedendum
# Tooth thickness: Tooth width along pitch circle.
tooth_thickness = ( pi * pitch_diameter ) / ( 2.0 * num_teeth )
# we don't use these
working_depth = 2 / diametral_pitch
whole_depth = 2.157 / diametral_pitch
#outside_diameter = (num_teeth + 2) / diametral_pitch
#
return ( pitch_radius , base_radius ,
addendum , dedendum , outer_radius , root_radius ,
tooth_thickness
)
def generate_rack_points ( tooth_count , pitch , addendum , pressure_angle ,
rack_base_height , tab_length , clearance = 0 , draw_guides = False ) :
""" Return path (suitable for svg) of the Rack gear.
- rack gear uses straight sides
- involute on a circle of infinite radius is a simple linear ramp
- the meshing circle touches at y = 0 ,
- the highest elevation of the teeth is at y = + addendum
- the lowest elevation of the teeth is at y = - addendum - clearance
- the rack_base_height extends downwards from the lowest elevation .
- we generate this middle tooth exactly centered on the y = 0 line .
( one extra tooth on the right hand side , if number of teeth is even )
"""
spacing = 0.5 * pitch # rolling one pitch distance on the spur gear pitch_diameter.
# roughly center rack in drawing, exact position is so that it meshes
# nicely with the spur gear.
# -0.5*spacing has a gap in the center.
# +0.5*spacing has a tooth in the center.
fudge = + 0.5 * spacing
tas = tan ( radians ( pressure_angle ) ) * addendum
tasc = tan ( radians ( pressure_angle ) ) * ( addendum + clearance )
base_top = addendum + clearance
base_bot = addendum + clearance + rack_base_height
x_lhs = - pitch * int ( 0.5 * tooth_count - .5 ) - spacing - tab_length - tasc + fudge
#inkex.utils.debug("angle=%s spacing=%s"%(pressure_angle, spacing))
# Start with base tab on LHS
points = [ ] # make list of points
points . append ( ( x_lhs , base_bot ) )
points . append ( ( x_lhs , base_top ) )
x = x_lhs + tab_length + tasc
# An involute on a circle of infinite radius is a simple linear ramp.
# We need to add curve at bottom and use clearance.
for i in range ( tooth_count ) :
# move along path, generating the next 'tooth'
# pitch line is at y=0. the left edge hits the pitch line at x
points . append ( ( x - tasc , base_top ) )
points . append ( ( x + tas , - addendum ) )
points . append ( ( x + spacing - tas , - addendum ) )
points . append ( ( x + spacing + tasc , base_top ) )
x + = pitch
x - = spacing # remove last adjustment
# add base on RHS
x_rhs = x + tasc + tab_length
points . append ( ( x_rhs , base_top ) )
points . append ( ( x_rhs , base_bot ) )
# We don't close the path here. Caller does it.
# points.append((x_lhs, base_bot))
# Draw line representing the pitch circle of infinite diameter
guide_path = None
if draw_guides :
p = [ ]
p . append ( ( x_lhs + 0.5 * tab_length , 0 ) )
p . append ( ( x_rhs - 0.5 * tab_length , 0 ) )
guide_path = points_to_svgd ( p )
# return points ready for use in an SVG 'path'
return ( points , guide_path )
def generate_spur_points ( teeth , base_radius , pitch_radius , outer_radius , root_radius , accuracy_involute , accuracy_circular ) :
""" given a set of core gear params
- generate the svg path for the gear
"""
half_thick_angle = two_pi / ( 4.0 * teeth ) #?? = pi / (2.0 * 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
start_involute_radius = max ( base_radius , root_radius )
radii = linspace ( start_involute_radius , outer_radius , accuracy_involute )
angles = [ involute_intersect_angle ( base_radius , r ) for r in radii ]
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
offsetangles1 = [ base1 + x for x in angles ]
points1 = [ point_on_circle ( radii [ i ] , offsetangles1 [ i ] ) for i in range ( 0 , len ( radii ) ) ]
pitch2 = c + half_thick_angle
base2 = pitch2 + pitch_to_base_angle
offsetangles2 = [ base2 - x for x in angles ]
points2 = [ point_on_circle ( radii [ i ] , offsetangles2 [ i ] ) for i in range ( 0 , len ( radii ) ) ]
points_on_outer_radius = [ point_on_circle ( outer_radius , x ) for x in linspace ( offsetangles1 [ - 1 ] , offsetangles2 [ - 1 ] , accuracy_circular ) ]
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
points_on_root = [ point_on_circle ( root_radius , x ) for x in linspace ( root2 , root1 + ( two_pi / float ( teeth ) ) , accuracy_circular ) ]
p_tmp = points1 + points_on_outer_radius [ 1 : - 1 ] + points2 [ : : - 1 ] + points_on_root [ 1 : - 1 ] # [::-1] reverses list; [1:-1] removes first and last element
else :
points_on_root = [ point_on_circle ( root_radius , x ) for x in linspace ( base2 , base1 + ( two_pi / float ( teeth ) ) , accuracy_circular ) ]
p_tmp = points1 + points_on_outer_radius [ 1 : - 1 ] + points2 [ : : - 1 ] + points_on_root # [::-1] reverses list
points . extend ( p_tmp )
return ( points )
def generate_spokes_path ( root_radius , spoke_width , spoke_count , mount_radius , mount_hole ,
unit_factor , unit_label ) :
""" given a set of constraints
- generate the svg path for the gear spokes
- lies between mount_radius ( inner hole ) and root_radius ( bottom of the teeth )
- spoke width also defines the spacing at the root_radius
- mount_radius is adjusted so that spokes fit if there is room
- if no room ( collision ) then spokes not drawn
"""
# Spokes
collision = False # assume we draw spokes
messages = [ ] # messages to send back about changes.
path = ' '
r_outer = root_radius - spoke_width
# checks for collision with spokes
# check for mount hole collision with inner spokes
if mount_radius < = mount_hole / 2 :
adj_factor = ( r_outer - mount_hole / 2 ) / 5
if adj_factor < 0.1 :
# not enough reasonable room
collision = True
else :
mount_radius = mount_hole / 2 + adj_factor # small fix
messages . append ( " Mount support too small. Auto increased to %2.2f %s . " % ( mount_radius / unit_factor * 2 , unit_label ) )
# then check to see if cross-over on spoke width
if spoke_width * spoke_count + 0.5 > = two_pi * mount_radius :
adj_factor = 1.2 # wrong value. its probably one of the points distances calculated below
mount_radius + = adj_factor
messages . append ( " Too many spokes. Increased Mount support by %2.3f %s " % ( adj_factor / unit_factor , unit_label ) )
# check for collision with outer rim
if r_outer < = mount_radius :
# not enough room to draw spokes so cancel
collision = True
if collision : # don't draw spokes if no room.
messages . append ( " Not enough room for Spokes. Decrease Spoke width. " )
else : # draw spokes
for i in range ( spoke_count ) :
points = [ ]
start_a , end_a = i * two_pi / spoke_count , ( i + 1 ) * two_pi / spoke_count
# inner circle around mount
asin_factor = spoke_width / mount_radius / 2
# check if need to clamp radius
asin_factor = max ( - 1.0 , min ( 1.0 , asin_factor ) ) # no longer needed - resized above
a = asin ( asin_factor )
points + = [ point_on_circle ( mount_radius , start_a + a ) , point_on_circle ( mount_radius , end_a - a ) ]
# is inner circle too small
asin_factor = spoke_width / r_outer / 2
# check if need to clamp radius
asin_factor = max ( - 1.0 , min ( 1.0 , asin_factor ) ) # no longer needed - resized above
a = asin ( asin_factor )
points + = [ point_on_circle ( r_outer , end_a - a ) , point_on_circle ( r_outer , start_a + a ) ]
path + = (
" M %f , %f " % points [ 0 ] +
" A %f , %f %s %s %s %f , %f " % tuple ( ( mount_radius , mount_radius , 0 , 0 if spoke_count != 1 else 1 , 1 ) + points [ 1 ] ) +
" L %f , %f " % points [ 2 ] +
" A %f , %f %s %s %s %f , %f " % tuple ( ( r_outer , r_outer , 0 , 0 if spoke_count != 1 else 1 , 0 ) + points [ 3 ] ) +
" Z "
)
return ( path , messages )
2021-04-04 01:51:59 +02:00
class Gears ( inkex . EffectExtension ) :
2020-07-30 01:16:18 +02:00
def __init__ ( self ) :
inkex . Effect . __init__ ( self )
# an alternate way to get debug info:
# could use inkex.utils.debug(string) instead...
# try:
# self.tty = open("/dev/tty", 'w')
# except:
# self.tty = open(devnull, 'w') # '/dev/null' for POSIX, 'nul' for Windows.
# # print >>self.tty, "gears-dev " + __version__
self . arg_parser . add_argument ( " -t " , " --teeth " , type = int , default = 24 , help = " Number of teeth " )
self . arg_parser . add_argument ( " -s " , " --system " , default = ' CP ' , help = " Select system: ' CP ' (Cyclic Pitch (default)), ' DP ' (Diametral Pitch), ' MM ' (Metric Module) " )
self . arg_parser . add_argument ( " -d " , " --dimension " , type = float , default = 1.0 , help = " Tooth size, depending on system (which defaults to CP) " )
self . arg_parser . add_argument ( " -a " , " --angle " , type = float , default = 20.0 , help = " Pressure Angle (common values: 14.5, 20, 25 degrees) " )
self . arg_parser . add_argument ( " -p " , " --profile_shift " , type = float , default = 20.0 , help = " Profile shift [in percent of the module]. Negative values help against undercut " )
self . arg_parser . add_argument ( " -u " , " --units " , default = ' mm ' , help = " Units this dialog is using " )
self . arg_parser . add_argument ( " -A " , " --accuracy " , type = int , default = 0 , help = " Accuracy of involute: automatic: 5..20 (default), best: 20(default), medium 10, low: 5; good acuracy is important with a low tooth count " )
# Clearance: Radial distance between top of tooth on one gear to bottom of gap on another.
self . arg_parser . add_argument ( " -cl " , " --clearance " , type = float , default = 0.0 , help = " Clearance between bottom of gap of this gear and top of tooth of another " )
self . arg_parser . add_argument ( " -an " , " --annotation " , type = inkex . Boolean , default = False , help = " Draw annotation text " )
self . arg_parser . add_argument ( " -i " , " --internal_ring " , type = inkex . Boolean , default = False , help = " Ring (or Internal) gear style (default: normal spur gear) " )
self . arg_parser . add_argument ( " -mh " , " --mount_hole " , type = float , default = 5 , help = " Mount hole diameter " )
self . arg_parser . add_argument ( " -md " , " --mount_diameter " , type = float , default = 15 , help = " Mount support diameter " )
self . arg_parser . add_argument ( " -sc " , " --spoke_count " , type = int , default = 3 , help = " Spokes count " )
self . arg_parser . add_argument ( " -sw " , " --spoke_width " , type = float , default = 5 , help = " Spoke width " )
self . arg_parser . add_argument ( " -hr " , " --holes_rounding " , type = float , default = 5 , help = " Holes rounding " )
self . arg_parser . add_argument ( " -at " , " --active_tab " , default = ' ' , help = " Active tab. Not used now. " )
self . arg_parser . add_argument ( " -x " , " --centercross " , type = inkex . Boolean , default = False , help = " Draw cross in center " )
self . arg_parser . add_argument ( " -c " , " --pitchcircle " , type = inkex . Boolean , default = False , help = " Draw pitch circle (for mating) " )
self . arg_parser . add_argument ( " -r " , " --draw_rack " , type = inkex . Boolean , default = False , help = " Draw rack gear instead of spur gear " )
self . arg_parser . add_argument ( " -rl " , " --rack_teeth_length " , type = int , default = 12 , help = " Length (in teeth) of rack " )
self . arg_parser . add_argument ( " -rh " , " --rack_base_height " , type = float , default = 8 , help = " Height of base of rack " )
self . arg_parser . add_argument ( " -rt " , " --rack_base_tab " , type = float , default = 14 , help = " Length of tabs on ends of rack " )
self . arg_parser . add_argument ( " -ua " , " --undercut_alert " , type = inkex . Boolean , default = False , help = " Let the user confirm a warning dialog if undercut occurs. This dialog also shows helpful hints against undercut " )
def add_text ( self , node , text , position , text_height = 12 ) :
""" Create and insert a single line of text into the svg under node.
- use ' text ' type and label as anootation
- where color is Ponoko Orange - so ignored when lasercutting
"""
line_style = { ' font-size ' : ' %d px ' % text_height , ' font-style ' : ' normal ' , ' font-weight ' : ' normal ' ,
' fill ' : ' #F6921E ' , ' font-family ' : ' Bitstream Vera Sans,sans-serif ' ,
' text-anchor ' : ' middle ' , ' text-align ' : ' center ' }
line_attribs = { inkex . addNS ( ' label ' , ' inkscape ' ) : ' Annotation ' ,
' style ' : str ( inkex . Style ( line_style ) ) ,
' x ' : str ( position [ 0 ] ) ,
' y ' : str ( ( position [ 1 ] + text_height ) * 1.2 )
}
line = etree . SubElement ( node , inkex . addNS ( ' text ' , ' svg ' ) , line_attribs )
line . text = text
def calc_unit_factor ( self ) :
""" return the scale factor for all dimension conversions.
- The document units are always irrelevant as
everything in inkscape is expected to be in 90 dpi pixel units
"""
# namedView = self.document.getroot().find(inkex.addNS('namedview', 'sodipodi'))
# doc_units = uutounit(self, 1.0, namedView.get(inkex.addNS('document-units', 'inkscape')))
dialog_units = uutounit ( self , 1.0 , self . options . units )
unit_factor = 1.0 / dialog_units
return unit_factor
def calc_circular_pitch ( self ) :
""" We use math based on circular pitch.
Expressed in inkscape units which is 90 dpi ' pixel ' units .
"""
dimension = self . options . dimension
# print >> self.tty, "unit_factor=%s, doc_units=%s, dialog_units=%s (%s), system=%s" % (unit_factor, doc_units, dialog_units, self.options.units, self.options.system)
if self . options . system == ' CP ' : # circular pitch
circular_pitch = dimension
elif self . options . system == ' DP ' : # diametral pitch
circular_pitch = pi / dimension
elif self . options . system == ' MM ' : # module (metric)
circular_pitch = dimension * pi / 25.4
else :
inkex . utils . debug ( " unknown system ' %s ' , try CP, DP, MM " % self . options . system )
# circular_pitch defines the size in inches.
# We divide the internal inch factor (px = 90dpi), to remove the inch
# unit.
# The internal inkscape unit is always px,
# it is independent of the doc_units!
return circular_pitch / uutounit ( self , 1.0 , ' in ' )
def effect ( self ) :
""" Calculate Gear factors from inputs.
- Make list of radii , angles , and centers for each tooth and
iterate through them
- Turn on other visual features e . g . cross , rack , annotations , etc
"""
path_stroke = ' #000000 ' # might expose one day
path_fill = ' none ' # no fill - just a line
path_stroke_width = uutounit ( self , 0.1 , ' mm ' ) # might expose one day
path_stroke_light = uutounit ( self , 0.05 , ' mm ' ) # guides are thinner
#
warnings = [ ] # list of extra messages to be shown in annotations
# calculate unit factor for units defined in dialog.
unit_factor = self . calc_unit_factor ( )
# User defined options
teeth = self . options . teeth
# Angle of tangent to tooth at circular pitch wrt radial line.
angle = self . options . angle
# Clearance: Radial distance between top of tooth on one gear to
# bottom of gap on another.
clearance = self . options . clearance * unit_factor
mount_hole = self . options . mount_hole * unit_factor
# for spokes
mount_radius = self . options . mount_diameter * 0.5 * unit_factor
spoke_count = self . options . spoke_count
spoke_width = self . options . spoke_width * unit_factor
holes_rounding = self . options . holes_rounding * unit_factor # unused
# visible guide lines
centercross = self . options . centercross # draw center or not (boolean)
pitchcircle = self . options . pitchcircle # draw pitch circle or not (boolean)
# Accuracy of teeth curves
accuracy_involute = 20 # Number of points of the involute curve
accuracy_circular = 9 # Number of points on circular parts
if self . options . accuracy is not None :
if self . options . accuracy == 0 :
# automatic
if teeth < 10 : accuracy_involute = 20
elif teeth < 30 : accuracy_involute = 12
else : accuracy_involute = 6
else :
accuracy_involute = self . options . accuracy
accuracy_circular = max ( 3 , int ( accuracy_involute / 2 ) - 1 ) # never less than three
# print >>self.tty, "accuracy_circular=%s accuracy_involute=%s" % (accuracy_circular, accuracy_involute)
# Pitch (circular pitch): Length of the arc from one tooth to the next)
# Pitch diameter: Diameter of pitch circle.
pitch = self . calc_circular_pitch ( )
# Replace section below with this call to get the combined gear_calculations() above
( pitch_radius , base_radius , addendum , dedendum ,
outer_radius , root_radius , tooth ) = gear_calculations ( teeth , pitch , angle , clearance , self . options . internal_ring , self . options . profile_shift * 0.01 )
# Detect Undercut of teeth
## undercut = int(ceil(undercut_min_teeth( angle )))
## needs_undercut = teeth < undercut #? no longer needed ?
if have_undercut ( teeth , angle , 1.0 ) :
min_teeth = int ( ceil ( undercut_min_teeth ( angle , 1.0 ) ) )
min_angle = undercut_min_angle ( teeth , 1.0 ) + .1
max_k = undercut_max_k ( teeth , angle )
msg = " Undercut Warning: This gear ( %d teeth) will not work well. \n Try tooth count of %d or more, \n or a pressure angle of %.1f [deg] or more, \n or try a profile shift of %d %% . \n Or other decent combinations. " % ( teeth , min_teeth , min_angle , int ( 100. * max_k ) - 100. )
# alas annotation cannot handle the degree symbol. Also it ignore
# newlines.
# so split and make a list
warnings . extend ( msg . split ( " \n " ) )
#if self.options.undercut_alert:
# inkex.utils.debug(msg)
#else:
# print >> self.tty, msg
# All base calcs done. Start building gear
points = generate_spur_points ( teeth , base_radius , pitch_radius , outer_radius , root_radius , accuracy_involute , accuracy_circular )
## half_thick_angle = two_pi / (4.0 * teeth ) #?? = pi / (2.0 * 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
##
## start_involute_radius = max(base_radius, root_radius)
## radii = linspace(start_involute_radius, outer_radius, accuracy_involute)
## angles = [involute_intersect_angle(base_radius, r) for r in radii]
##
## 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
## offsetangles1 = [ base1 + x for x in angles]
## points1 = [ point_on_circle( radii[i], offsetangles1[i]) for i in range(0,len(radii)) ]
##
## pitch2 = c + half_thick_angle
## base2 = pitch2 + pitch_to_base_angle
## offsetangles2 = [ base2 - x for x in angles]
## points2 = [ point_on_circle( radii[i], offsetangles2[i]) for i in range(0,len(radii)) ]
##
## points_on_outer_radius = [ point_on_circle(outer_radius, x) for x in linspace(offsetangles1[-1], offsetangles2[-1], accuracy_circular) ]
##
## 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
## points_on_root = [point_on_circle (root_radius, x) for x in linspace(root2, root1+(two_pi/float(teeth)), accuracy_circular) ]
## p_tmp = points1 + points_on_outer_radius[1:-1] + points2[::-1] + points_on_root[1:-1] # [::-1] reverses list; [1:-1] removes first and last element
## else:
## points_on_root = [point_on_circle (root_radius, x) for x in linspace(base2, base1+(two_pi/float(teeth)), accuracy_circular) ]
## p_tmp = points1 + points_on_outer_radius[1:-1] + points2[::-1] + points_on_root # [::-1] reverses list
##
## points.extend( p_tmp )
path = points_to_svgd ( points )
bbox_center = points_to_bbox_center ( points )
# Spokes (add to current path)
if not self . options . internal_ring : # only draw internals if spur gear
spokes_path , msg = generate_spokes_path ( root_radius , spoke_width , spoke_count , mount_radius , mount_hole ,
unit_factor , self . options . units )
warnings . extend ( msg )
path + = spokes_path
# Draw mount hole
# A : rx,ry x-axis-rotation, large-arch-flag, sweepflag x,y
r = mount_hole / 2
path + = (
" M %f , %f " % ( 0 , r ) +
" A %f , %f %s %s %s %f , %f " % ( r , r , 0 , 0 , 0 , 0 , - r ) +
" A %f , %f %s %s %s %f , %f " % ( r , r , 0 , 0 , 0 , 0 , r )
)
else :
# its a ring gear
# which only has an outer ring where width = spoke width
r = outer_radius + spoke_width
path + = (
" M %f , %f " % ( 0 , r ) +
" A %f , %f %s %s %s %f , %f " % ( r , r , 0 , 0 , 0 , 0 , - r ) +
" A %f , %f %s %s %s %f , %f " % ( r , r , 0 , 0 , 0 , 0 , r )
)
# Embed gear in group to make animation easier:
# Translate group, Rotate path.
t = ' translate( ' + str ( self . svg . namedview . center [ 0 ] ) + ' , ' + str ( self . svg . namedview . center [ 1 ] ) + ' ) '
g_attribs = { inkex . addNS ( ' label ' , ' inkscape ' ) : ' Gear ' + str ( teeth ) ,
inkex . addNS ( ' transform-center-x ' , ' inkscape ' ) : str ( - bbox_center [ 0 ] ) ,
inkex . addNS ( ' transform-center-y ' , ' inkscape ' ) : str ( - bbox_center [ 1 ] ) ,
' transform ' : t ,
' info ' : ' N: ' + str ( teeth ) + ' ; Pitch: ' + str ( pitch ) + ' ; Pressure Angle: ' + str ( angle ) }
# add the group to the current layer
g = etree . SubElement ( self . svg . get_current_layer ( ) , ' g ' , g_attribs )
# Create gear path under top level group
style = { ' stroke ' : path_stroke , ' fill ' : path_fill , ' stroke-width ' : path_stroke_width }
gear_attribs = { ' style ' : str ( inkex . Style ( style ) ) , ' d ' : path }
gear = etree . SubElement ( g , inkex . addNS ( ' path ' , ' svg ' ) , gear_attribs )
# Add center
if centercross :
style = { ' stroke ' : path_stroke , ' fill ' : path_fill ,
' stroke-width ' : path_stroke_light }
cs = str ( pitch / 3 ) # centercross length
d = ' M- ' + cs + ' ,0L ' + cs + ' ,0M0,- ' + cs + ' L0, ' + cs # 'M-10,0L10,0M0,-10L0,10'
center_attribs = { inkex . addNS ( ' label ' , ' inkscape ' ) : ' Center cross ' ,
' style ' : str ( inkex . Style ( style ) ) , ' d ' : d }
center = etree . SubElement (
g , inkex . addNS ( ' path ' , ' svg ' ) , center_attribs )
# Add pitch circle (for mating)
if pitchcircle :
style = { ' stroke ' : path_stroke , ' fill ' : path_fill , ' stroke-width ' : path_stroke_light }
draw_SVG_circle ( g , pitch_radius , 0 , 0 , ' Pitch circle ' , style )
# Add Rack (below)
if self . options . draw_rack :
rack_base_height = self . options . rack_base_height * unit_factor
tab_width = self . options . rack_base_tab * unit_factor
tooth_count = self . options . rack_teeth_length
( points , guide_path ) = generate_rack_points ( tooth_count , pitch , addendum , angle ,
rack_base_height , tab_width , clearance , pitchcircle )
path = points_to_svgd ( points )
# position below Gear, so that it meshes nicely
# xoff = 0 ## if teeth % 4 == 2.
# xoff = -0.5*pitch ## if teeth % 4 == 0.
# xoff = -0.75*pitch ## if teeth % 4 == 3.
# xoff = -0.25*pitch ## if teeth % 4 == 1.
xoff = ( - 0.5 , - 0.25 , 0 , - 0.75 ) [ teeth % 4 ] * pitch
t = ' translate( ' + str ( xoff ) + ' , ' + str ( pitch_radius ) + ' ) '
g_attribs = { inkex . addNS ( ' label ' , ' inkscape ' ) : ' RackGear ' + str ( tooth_count ) ,
' transform ' : t }
rack = etree . SubElement ( g , ' g ' , g_attribs )
# Create SVG Path for gear
style = { ' stroke ' : path_stroke , ' fill ' : ' none ' , ' stroke-width ' : path_stroke_width }
gear_attribs = { ' style ' : str ( inkex . Style ( style ) ) , ' d ' : path }
gear = etree . SubElement (
rack , inkex . addNS ( ' path ' , ' svg ' ) , gear_attribs )
if guide_path is not None :
style2 = { ' stroke ' : path_stroke , ' fill ' : ' none ' , ' stroke-width ' : path_stroke_light }
gear_attribs2 = { ' style ' : str ( inkex . Style ( style2 ) ) , ' d ' : guide_path }
gear = etree . SubElement (
rack , inkex . addNS ( ' path ' , ' svg ' ) , gear_attribs2 )
# Add Annotations (above)
if self . options . annotation :
outer_dia = outer_radius * 2
if self . options . internal_ring :
outer_dia + = 2 * spoke_width
notes = [ ]
notes . extend ( warnings )
#notes.append('Document (%s) scale conversion = %2.4f' % (self.document.getroot().find(inkex.addNS('namedview', 'sodipodi')).get(inkex.addNS('document-units', 'inkscape')), unit_factor))
notes . extend ( [ ' Teeth: %d CP: %2.4f ( %s ) ' % ( teeth , pitch / unit_factor , self . options . units ) ,
' DP: %2.3f Module: %2.4f ' % ( pi / pitch * unit_factor , pitch / pi * 25.4 ) ,
' Pressure Angle: %2.2f degrees ' % ( angle ) ,
' Pitch diameter: %2.3f %s ' % ( pitch_radius * 2 / unit_factor , self . options . units ) ,
' Outer diameter: %2.3f %s ' % ( outer_dia / unit_factor , self . options . units ) ,
' Base diameter: %2.3f %s ' % ( base_radius * 2 / unit_factor , self . options . units ) #,
#'Addendum: %2.4f %s' % (addendum / unit_factor, self.options.units),
#'Dedendum: %2.4f %s' % (dedendum / unit_factor, self.options.units)
] )
# text height relative to gear size.
# ranges from 10 to 22 over outer radius size 60 to 360
text_height = max ( 10 , min ( 10 + ( outer_dia - 60 ) / 24 , 22 ) )
# position above
y = - outer_radius - ( len ( notes ) + 1 ) * text_height * 1.2
for note in notes :
self . add_text ( g , note , [ 0 , y ] , text_height )
y + = text_height * 1.2
2020-08-31 21:25:41 +02:00
if __name__ == ' __main__ ' :
Gears ( ) . run ( )