''' Calculate thread properties. Copyright (C) 2005 Don Peterson 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 ''' from __future__ import division import sys, getopt, string, asme from math import cos, tan, sin, atan, pi, sqrt debug = 0 # Global variables thread_class = 2 metric = 0 internal = 0 wire_diameter = 0.0 fraction = 0.95 # Fraction of 5/8*H for max thread wire diameter mm_per_in = 25.4 # in to mm # Format strings spc = 35 vf = "%%-%ss" % spc fmt = vf + "%10.4f %8.2f" fmt1 = vf + "%7.1f %8.2f" fmt3 = vf + "%9.3f %8.2f" fmt6 = vf + "%12.6f %8.2f" # Wire diameters in mils in Pee Dee thread measuring set wires = [18, 24, 29, 32, 40, 45, 55, 63, 72, 81, 92, 108, 120, 127, 143, 185] for ix in xrange(len(wires)): wires[ix] /= 1000 wires = tuple(wires) # Common UNC and UNF sizes for -t option common_sizes = ( # Descr Diam TPI ("2-56 UNC", 0.086, 56), ("3-48 UNC", 0.099, 48), ("4-40 UNC", 0.112, 40), ("4-48 UNF", 0.112, 48), ("5-40 UNC", 0.125, 40), ("6-32 UNC", 0.138, 32), ("8-32 UNC", 0.164, 32), ("10-24 UNC", 0.190, 24), ("10-32 UNF", 0.190, 32), ("1/4-20 UNC", 0.250, 20), ("1/4-28 UNF", 0.250, 28), ("5/16-18 UNC", 0.3125, 18), ("5/16-24 UNF", 0.3125, 24), ("3/8-16 UNC", 0.375, 16), ("3/8-24 UNF", 0.375, 24), ("7/16-14 UNC", 0.4375, 14), ("7/16-20 UNF", 0.4375, 20), ("1/2-13 UNC", 0.5, 13), ("1/2-20 UNF", 0.5, 20), ("9/16-12 UNC", 0.5625, 12), ("9/16-18 UNF", 0.5625, 18), ("5/8-11 UNC", 0.625, 11), ("5/8-18 UNF", 0.625, 18), ("3/4-10 UNC", 0.75, 10), ("3/4-16 UNF", 0.75, 18), ("7/8-9 UNC", 0.875, 9), ("7/8-14 UNF", 0.875, 14), ("1-8 UNC", 1, 8), ("1-14 UNF", 1, 14) ) # Fraction, number, and letter size drills; used to select the nearest # tap and clearance drills. drills = { 0.0059 : "#97", 0.0063 : "#96", 0.0067 : "#95", 0.0071 : "#94", 0.0075 : "#93", 0.0079 : "#92", 0.0083 : "#91", 0.0087 : "#90", 0.0091 : "#89", 0.0095 : "#88", 0.0100 : "#87", 0.0105 : "#86", 0.0110 : "#85", 0.0115 : "#84", 0.0120 : "#83", 0.0125 : "#82", 0.0130 : "#81", 0.0135 : "#80", 0.0145 : "#79", 0.0156 : "1/64", 0.0160 : "#78", 0.0180 : "#77", 0.0200 : "#76", 0.0210 : "#75", 0.0225 : "#74", 0.0240 : "#73", 0.0250 : "#72", 0.0260 : "#71", 0.0280 : "#70", 0.0293 : "#69", 0.0310 : "#68", 0.0313 : "1/32", 0.0320 : "#67", 0.0330 : "#66", 0.0350 : "#65", 0.0360 : "#64", 0.0370 : "#63", 0.0380 : "#62", 0.0390 : "#61", 0.0400 : "#60", 0.0410 : "#59", 0.0420 : "#58", 0.0430 : "#57", 0.0465 : "#56", 0.0469 : "3/64", 0.0520 : "#55", 0.0550 : "#54", 0.0595 : "#53", 0.0625 : "1/16", 0.0635 : "#52", 0.0670 : "#51", 0.0700 : "#50", 0.0730 : "#49", 0.0760 : "#48", 0.0781 : "5/64", 0.0785 : "#47", 0.0810 : "#46", 0.0820 : "#45", 0.0860 : "#44", 0.0890 : "#43", 0.0935 : "#42", 0.0938 : "3/32", 0.0960 : "#41", 0.0980 : "#40", 0.0995 : "#39", 0.1015 : "#38", 0.1040 : "#37", 0.1065 : "#36", 0.1094 : "7/64", 0.1100 : "#35", 0.1110 : "#34", 0.1130 : "#33", 0.1160 : "#32", 0.1200 : "#31", 0.1250 : "1/8", 0.1285 : "#30", 0.1360 : "#29", 0.1405 : "#28", 0.1406 : "9/64", 0.1440 : "#27", 0.1470 : "#26", 0.1495 : "#25", 0.1520 : "#24", 0.1540 : "#23", 0.1563 : "5/32", 0.1570 : "#22", 0.1590 : "#21", 0.1610 : "#20", 0.1660 : "#19", 0.1695 : "#18", 0.1719 : "11/64", 0.1730 : "#17", 0.1770 : "#16", 0.1800 : "#15", 0.1820 : "#14", 0.1850 : "#13", 0.1875 : "3/16", 0.1890 : "#12", 0.1910 : "#11", 0.1935 : "#10", 0.1960 : "#9", 0.1990 : "#8", 0.2010 : "#7", 0.2031 : "13/64", 0.2040 : "#6", 0.2055 : "#5", 0.2090 : "#4", 0.2130 : "#3", 0.2188 : "7/32", 0.2210 : "#2", 0.2280 : "#1", 0.234 : "A", 0.2344 : "15/64", 0.238 : "B", 0.242 : "C", 0.246 : "D", 0.250 : "E", 0.2500 : "1/4", 0.257 : "F", 0.261 : "G", 0.2656 : "17/64", 0.266 : "H", 0.272 : "I", 0.277 : "J", 0.281 : "K", 0.2813 : "9/32", 0.290 : "L", 0.295 : "M", 0.2969 : "19/64", 0.302 : "N", 0.3125 : "5/16", 0.316 : "O", 0.323 : "P", 0.3281 : "21/64", 0.332 : "Q", 0.339 : "R", 0.3438 : "11/32", 0.348 : "S", 0.358 : "T", 0.3594 : "23/64", 0.368 : "U", 0.3750 : "3/8", 0.377 : "V", 0.386 : "W", 0.3906 : "25/64", 0.397 : "X", 0.404 : "Y", 0.4063 : "13/32", 0.413 : "Z", 0.4219 : "27/64", 0.4375 : "7/16", 0.4531 : "29/64", 0.4688 : "15/32", 0.4844 : "31/64", 0.5000 : "1/2" } manpage = '''Usage: thread [options] thread_size Prints thread information for the indicated thread. The thread size can be specified as decimal inches, a hyphen, and the threads per inch. You may also replace the hyphen with a space. The following forms are equivalent: 0.25-20 0.25 20 1/4-20 1/4 20 You can specify nominal diameters larger than one using fractional inches as follows: 1 1/2-6 1 1/2 6 Equivalently, 1.5-6 1.5 6 You can also specify a single number on the command line. This will be interpreted as threads per inch (or pitch in mm if the -m option was used). US number size threads can be designated by prefacing the diameter with the letter 'N' or 'n'. Example: Use 'n6-32' for a 6-32 thread. The number size is converted to a diameter in inches by the formula dia = 0.060 + N*0.013. While most of the most common values for N are in the range 0 to 14, the program will let you enter an N up to a value of 500. Inch-sized thread dimensions are calculated from the Unified thread formulas in the ASME B1.1-1989 document, "Unified Inch Screw Threads". The program uses the same formulas for metric threads; this may or may not be consistent with other metric standards. Options -a frac Show the minimum and maximum usable wire sizes for this thread. The fraction frac is < 1 and corresponds to the wire contacting the flank of the thread at that fraction of the radial height of the Unified thread flank, which is 5*H/8, where H is sqrt(3)/2 times the pitch. Since the minimum wire has its center on the pitch circle, the fraction must be greater than (3/8*H)/(5/8*H) = 0.40. frac defaults to 0.95. -c num Specify the class of the thread. The number num must be 1, 2, or 3. Class 2 is the default. -h Print this help out. -m The command line parameters are to be interpreted in mm. The first number is the nominal diameter in mm and the second number is the pitch in mm. Example: -m 6 1 means a 6 mm diameter thread with a pitch of 1 mm. If a single parameter is passed in when -m is used, it is interpreted as the thread's pitch in mm. -t Print data for common UNC and UNF sizes. -w size Specify the wire size to use. Normally, the results will use the best calculated wire size. See Wire Sizes below. Wire Sizes The pitch diameter of threads can be measured using thread wires (see e.g. Machinery's Handbook). The program calculates the measurement over wires for the best wire size, which contacts the thread flank at the pitch diameter. You may have a set of thread measuring wires on hand, in which case you'd want the program to use your wire sizes. You can enter your wire sizes on hand by editting the wires list. If a -w option is given on the command line, it will override the nearest wire size from the internal list. For a specified thread pitch, the given value for "const" is the number that should be subtracted from the measurement over the wires to get the pitch diameter. Warning The formulas from the ASME standard will produce numbers that may differ from what are published in the tables of that document (or Machinery's Handbook, which appears to copy the ASME tables). It is not clear to me which should be considered correct - the formulas generating the tables or the tables themselves. It's likely most people will assume the tables are correct, even if they do contain errors that don't match the formulas. Thus, use the program's output with this fact in mind. Output Notes Allowance is to preclude surface-to-surface fit between mating parts for class 1A threads. For class 2A threads, the allowance may be used to accomodate plating or coating. Thread shear areas are geometric and will somewhat overestimate the shear strength (see the ASME document). MOW = measurement over wires H = sqrt(3)*pitch/2 The minimum wire size is the wire diameter that would contact the thread at the root of a geometrically perfect Unified thread and, simultaneously, at both flanks of the thread. The maximum wire size is gotten by allowing the wire to touch the flank at a specified fraction of the height from the root to the crest, which is 5*H/8 in a Unified thread. If the fraction is 100%, the contact is at the intersection of the flank and the outside flat. The fraction is given as a percentage in the description and can be changed in the code. Both of these numbers attempt to give you the range of usable wires for measurement; bear in mind that the most accurate measurements are made with the best wire size. cos(29 degrees) is the angle to set the compound feed over for cutting threads. To cut a thread with a vee tool or a formed tool, first turn the OD to the specified major diameter. Then touch the tip of the cutting tool to the OD. When you've fed the tool in by the amount given in the "Compound feed" column, you should be at the maximum pitch diameter. If your compound slide feeds in half of the indicated distance, use the feed distance in the "Half compound feed" column. A better way to set the cutting tool is to use a thread gauge. This will set the flanks of the cutting tool a known distance away from the major diameter. You can measure this known distance on your threading gauge with a rod of appropriate size. The thread depth for a sharp vee thread is H and is the major diameter to the root of the thread. The Unified thread depth is 5*H/8. The compound feed is the double depth divided by the cosine of 29 degrees. ''' def Usage(): print '''Usage: thread [options] thread_size Prints thread information for the indicated external thread. Examples: thread n6-32 thread 0.25-20 thread 0.25 20 thread 1/4-20 thread 1/4 20 thread 1 1/2-6 thread 1 1/2 6 Options -c num Specify class 1, 2, or 3 thread (2 is default) -m Metric threads: dia in mm, pitch in mm -w dia Specify size of thread wire to use Use -h for more detailed help.''' sys.exit(1) def Error(s): sys.stderr.write(s + "\n") sys.exit(1) def ProcessCommandLine(): try: optlist, args = getopt.getopt(sys.argv[1:], "a:c:hmtw:") except getopt.error, str: print "getopt error: %s\n" % str sys.exit(1) for opt in optlist: if opt[0] == "-a": global fraction fraction = float(opt[1]) if fraction < 0.4 or fraction >= 1: Error("Value for -a option must be >= 0.4 and < 1.0.") if opt[0] == "-c": global thread_class thread_class = int(opt[1]) if thread_class < 1 or thread_class > 3: Error("Thread class must be 1, 2, or 3") if opt[0] == "-h": print manpage sys.exit(0) if opt[0] == "-m ": global metric metric = 1 if opt[0] == "-t": PrintCommon() if opt[0] == "-w": global wire_diameter wire_diameter = float(opt[1]) if len(args) < 1 or len(args) > 3: Usage() # If any elements contain a '-', increase the number of arguments # by parsing on the '-'. new_args = [] for x in args: if string.find(x, "-") != -1: new_args += string.split(x, "-") else: new_args += [x] if debug: print "thread_class =", thread_class print "metric =", metric print "wire_diameter =", wire_diameter print "Arguments:" for ix in xrange(len(new_args)): print " %d '%s'" % (ix, new_args[ix]) return new_args def PrintCommon(): for spec, diameter, tpi in common_sizes: print spec PrintResults(diameter, tpi) print "-" * 70 print sys.exit(0) def Float(x): # Interpret the string x as a float. Also allow the form "n/d". try: y = float(x) except: if "/" in x: fields = x.split("/") if len(fields) != 2: raise "Not a properly formed fraction" y = float(fields[0])/float(fields[1]) else: float(x) return y def PrintPitchProperties(arg): 'arg will either be tpi or pitch in mm' # Get pitch in inches if metric: p = Float(arg)/mm_per_in else: p = 1/Float(arg) print " " * spc, " inches mm" print " " * spc, " ------ ---" print fmt1 % ("Threads per inch or mm", 1/p, 1/(mm_per_in*p)) print fmt % ("Pitch", p, p*mm_per_in) print "PD = MOW - const" PrintBestWire(p) best_wire_size = p/(2*cos(30*pi/180)) const = 3*best_wire_size - p*sqrt(3)/2 print fmt % (" const", const, const*mm_per_in) if wire_diameter: W = wire_diameter s = "User-specified wire diameter" else: # Find the closest matching wire on hand W = FindClosestWire(best_wire_size) s = "On-hand wire size" if W: print fmt3 % (s, W, W*mm_per_in) const = 3*W - p*sqrt(3)/2 print fmt % (" const", const, const*mm_per_in) print H = p * cos(pi/6) ThreadDepths(H) sys.exit(0) def GetDiameter(s): '''Is either a number sized diameter, a decimal number, or a fraction. Return the diameter in decimal inches. ''' if s[0] == 'N' or s[0] == 'n': return 0.06 + 0.013*int(s[1:]) elif string.find(s, "/") != -1: numerator, denominator = map(float, string.split(s, "/")) return numerator/denominator else: diameter = float(s) if metric: diameter /= 25.4 return diameter def M(pitch_diameter, tpi, wire_diameter, starts=1): '''Calculate MOW given the PD, pitch, and wire diameter. starts is the number of thread starts (here, we only use 1). ''' E = pitch_diameter W = wire_diameter A = pi/6 T = 1/(2*tpi) L = starts/tpi B = atan(L/(pi*E)) # For formula, see MH, 19th ed., p 1378 M = E - T/tan(A) + W*(1 + 1/sin(A) + 0.5*tan(B)*tan(B)*cos(A)/tan(A)) return M def FindClosestWire(d): if d < wires[0] or d > wires[-1]: return 0 for ix in xrange(len(wires)): if d < wires[ix]: # Found an upper bound dlower = abs(d - wires[ix-1]) dupper = abs(d - wires[ix]) if dlower < dupper: return wires[ix-1] else: return wires[ix] def WireSizeLimits(pitch): 'Return the minimum and maximum wire diameters.' H = sqrt(3)/2*pitch min = H/2 max = 4/3*(5/8*fraction + 1/4)*H return min, max def PrintBestWire(pitch): best_wire_size = pitch/(2*cos(30*pi/180)) min, max = WireSizeLimits(pitch) print "Measurements over wires" print fmt3 % (" Min wire size", min, min*mm_per_in) s = " Max wire size (%d%%)" % (int(100*fraction)) print fmt3 % (s, max, max*mm_per_in) print fmt6 % (" Best wire size", best_wire_size, best_wire_size*mm_per_in) def GetNearestDrillSize(D): assert(0 < D <= 1/2) if D == 1/2: return "1/2" sizes = drills.keys() sizes.sort() for ix in xrange(len(sizes)): if D > sizes[ix]: continue break # ix is now the index of the size which is just larger larger = sizes[ix] smaller = sizes[ix-1] size = smaller if larger - D <= D - smaller: size = larger return drills[size] def GetNearestSixtyFourth(D): assert(D <= 1) if D == 1: return "1" denom = 64 for numer in xrange(1, 64): if numer/denom < D: continue smaller = numer - 1 larger = numer if D - smaller/denom < larger/denom - D: numer = smaller else: numer = larger while numer % 2 == 0: numer /= 2 denom /= 2 return "%d/%d" % (numer, denom) def TapDrill(nominal_diameter, percent, pitch): D = nominal_diameter - 3/4*sqrt(3)*percent/100*pitch if D <= 1/2: size = GetNearestDrillSize(D) elif D <= 1: size = GetNearestSixtyFourth(D) else: size = "" return D, size def PrintResults(diameter, tpi): A = asme.UnifiedThread(diameter, tpi, thread_class) p = 1/tpi H = p * cos(pi/6) print " " * spc, " inches mm" print " " * spc, " ------ ---" print fmt % ("Nominal diameter", diameter, diameter*mm_per_in) print fmt1 % ("Threads per inch or mm", tpi, tpi/mm_per_in) print vf % "Thread class", " %2d" % thread_class print fmt % ("Pitch", 1/tpi, mm_per_in/tpi) print fmt % ("Allowance", A.Allowance(), A.Allowance()*mm_per_in) # Thread tensile strength area (ASME 1989, Appendix B) a = diameter - 0.9743*p AS = 0.7854*a*a print fmt6 % ("Tensile area, in^2 or mm^2", AS, AS*mm_per_in*mm_per_in) # Thread shear area # xx Note: this prints out values that cannot be right. if 0: print "Min. thread shear area (in^2 or mm^2)" ASn = pi/p*A.Dmin()*(p/2 + 0.57735*(A.Dmin() - A.emax())) ASs = pi/p*A.dmax()*(p/2 + 0.57735*(A.Emin() - A.dmax())) print fmt % (" External thread", ASs, ASs*mm_per_in*mm_per_in) print fmt % (" Internal thread", ASn, ASn*mm_per_in*mm_per_in) print print "External thread" print " Major diameter" print fmt % (" Max", A.Dmax(), A.Dmax()*mm_per_in) print fmt % (" Min", A.Dmin(), A.Dmin()*mm_per_in) print fmt % (" Tolerance", A.Dmax()-A.Dmin(), (A.Dmax()-A.Dmin())*mm_per_in) print " Pitch diameter" print fmt % (" Max", A.Emax(), A.Emax()*mm_per_in) print fmt % (" Min", A.Emin(), A.Emin()*mm_per_in) print fmt % (" Tolerance", A.Emax()-A.Emin(), (A.Emax()-A.Emin())*mm_per_in) print print "Internal thread" print " Minor diameter" print fmt % (" Max", A.dmax(), A.dmax()*mm_per_in) print fmt % (" Min", A.dmin(), A.dmin()*mm_per_in) print fmt % (" Tolerance", A.dmax()-A.dmin(), (A.dmax()-A.dmin())*mm_per_in) print " Pitch diameter" print fmt % (" Max", A.emax(), A.emax()*mm_per_in) print fmt % (" Min", A.emin(), A.emin()*mm_per_in) print fmt % (" Tolerance", A.emax()-A.emin(), (A.emax()-A.emin())*mm_per_in) print PrintBestWire(1/tpi) best_wire_size = 1/(2*tpi*cos(30*pi/180)) mow_max = M(A.Emax(), tpi, best_wire_size) mow_min = M(A.Emin(), tpi, best_wire_size) print fmt % (" Meas. over best wires, max", mow_max, mow_max*mm_per_in) print fmt % (" Meas. over best wires, min", mow_min, mow_min*mm_per_in) if wire_diameter: W = wire_diameter s = "User-specified wire diameter" else: # Find the closest matching wire on hand W = FindClosestWire(best_wire_size) s = " On-hand wire size" if W: print fmt3 % (s, W, W*mm_per_in) mow_max = M(A.Emax(), tpi, W) mow_min = M(A.Emin(), tpi, W) print fmt % (" Meas. over wires, max", mow_max, mow_max*mm_per_in) print fmt % (" Meas. over wires, min", mow_min, mow_min*mm_per_in) print print "Tap drills" fmtf = fmt + " %s" for percent in xrange(50, 80, 5): D, F = TapDrill(diameter, percent, 1/tpi) print fmtf % (" %d%% thread" % percent, D, D*mm_per_in, F) print ThreadDepths(H) def ThreadDepths(H): C = cos(29*pi/180) print "Sharp V thread (H = sqrt(3)*pitch/2, C = cos(29 deg))" print fmt % (" Thread depth H (Sharp vee)", H, H*mm_per_in) print fmt3 % (" Double depth 2*H", 2*H, 2*H*mm_per_in) print fmt3 % (" Compound feed 2*H/C", 2*H/C, 2*H/C*mm_per_in) print fmt3 % (" Double compound feed 4*H/C", 4*H/C, 4*H/C*mm_per_in) print print "Unified thread" print fmt3 % (" Flat on form tool", 1/4*H, 1/4*H*mm_per_in) h = 5/8*H print fmt % (" Thread depth h = 5*H/8", h, h*mm_per_in) print fmt3 % (" Double depth 2*h", 2*h, 2*h*mm_per_in) print fmt3 % (" Compound feed 2*h/C", 2*h/C, 2*h/C*mm_per_in) print fmt3 % (" Double compound feed 4*h/C", 4*h/C, 4*h/C*mm_per_in) def main(): args = ProcessCommandLine() diameter_inches = 0.0 tpi = 0.0 if len(args) == 1: PrintPitchProperties(args[0]) else: if len(args) == 2: diameter_inches = GetDiameter(args[0]) tpi = float(args[1]) if metric: tpi = 25.4/tpi else: assert(len(args) == 3) # This is of the form 'i f tpi' where i is an integer and # f is the fraction if string.find(args[1], "/") == -1: Error("Second parameter must have '/' in it") numerator, denominator = map(float, string.split(args[1], "/")) diameter_inches = float(args[0]) + numerator/denominator tpi = float(args[2]) if debug: print "diameter, in = %.4f" % diameter_inches print "tpi = %.2f" % tpi PrintResults(diameter_inches, tpi) main()