uts screws

You suggested I should supply code for UTS screws.  Here is my attempt to do so.   It parses a screw spec and produces a structure with screw data.  I made a head-drawing routine but didn't try to figure out how to draw the threaded shaft, or to put a recess into the head.
Note also that there's a bunch of string stuff that belongs in a strings library.  I wonder if we should try to get permission to use the regular expression parser from the STring Theory library.   (It's GPL.)

function parse_screw_name(name) =
    name[0] == "M" || name[0] == "m" ?
       (let( split = strsplit(substr(name,1),"x"))
       len(split)==1 ? ["metric",atof(split[0]), undef] :
       len(split)==2 ? ["metric",atof(split[0]), atof(split[1])] :
       undef) :
    let(split = strsplit(name,"-"))
    len(split)<1 || len(split)>2 ? undef :
    let(
        diam = split[0][0] == "#" ? split[0] :
               substr(split[0],len(split[0])-2,2)=="''" ? atof(substr(split[0],0,len(split[0])-2)) :
               let(val=fraction2f(split[0]))
               val == floor(val) && val>=0 && val<=12 ? str("#",split[0]) :
               val != val ? atof(split[0]) : val
        )
    len(split)==1 ? ["english", diam, undef] :
    len(split)==2 ? ["english", diam, atof(split[1])] :
                    undef;

function atoi(str,base=10) =
    len(str)==0 ? 0 :
    let(str=downcase(str))
    str[0] == "-" ? -_atoi_recurse(substr(str,1),base,len(str)-2) :
    str[0] == "+" ? _atoi_recurse(substr(str,1),base,len(str)-2) :
    _atoi_recurse(str,base,len(str)-1);

function _atoi_recurse(str,base,i) =
    let( digit = search(str[i],"0123456789abcdef"),
         last_digit = digit == [] || digit[0] >= base ? (0/0) : digit[0])
    i==0 ? last_digit :
        _atoi_recurse(str,base,i-1)*base + last_digit;

// retrns substring starting from position, pos of specified length
// If length is -1 returns the rest of the string.
//
// default pos=0 (start at beginning of string)
//
function substr(str, pos=0, len=-1, substr="") =
        assert(len>=-1,"illegal value of len")
	len == 0 || pos>=len(str) ? substr :
	len == -1 ? substr(str, pos, len(str)-pos, substr) :
	substr(str, pos+1, len-1, str(substr, str[pos]));

function strcat(list,_i=0, _result="") =
    _i == len(list) ? _result :
      strcat(list,_i+1,str(_result,list[_i]));

function strsplit_recurse(str,sep,i,result) =
   i == len(sep) ? concat(result,[str]) :
    let( pos = search(sep[i], str),
         end = pos==[] ? len(str) : pos[0]
      )
    strsplit_recurse(substr(str,end+1), sep, i+1,
                    concat(result, [substr(str,0,end)]));

// splits the input string on the separator(s)
// If the separator is a string then the input is broken on any occurrence of a character in the string
// The output will be a list of strings with length n+1 if there are n occurences of separator characters
//
// If separator is a list of characters then split sequentially on each character
//
function strsplit(str,sep) =
   is_list(sep) ? strsplit_recurse(str,sep,i=0,result=[]) :
   let( cutpts = concat([-1],sort(flatten(search(sep, str,0))),[len(str)]))
   [for(i=[0:len(cutpts)-2]) substr(str,cutpts[i]+1,cutpts[i+1]-cutpts[i]-1)];

function atof(str) =
     len(str) == 0 ? 0 :
     str[0]=="-" ? -atof(substr(str,1)) :
     str[0]=="+" ? atof(substr(str,1)) :
     let(esplit = strsplit(str,"eE") )
     len(esplit)==2 ? atof(esplit[0]) * pow(10,atoi(esplit[1])) :
     let( dsplit = strsplit(str,["."]))
     atoi(dsplit[0])+atoi(dsplit[1])/pow(10,len(dsplit[1]));

function downcase(str) =
   strcat([for(char=str) let(code=ord(char)) code>=65 && code<=90 ? chr(code+32) : char]);

function fraction2f(str) =
    let( num = strsplit(str,"/"))
    len(num)==1 ? atoi(num[0]) :
    len(num)==2 ? atoi(num[0])/atoi(num[1]) :
    (0/0);

//echo(search("a","defabed"), search("a", "ffeegg"));
//echo(substr("abcdefghi",0,-2));
//echo(strsplit("abc1def2ghi3klm",["1","2","3"]));
//echo(substr("klm",0,3));
//echo(strsplit("8-32x2", ["-","x"]));
//echo(atof("-3.14159E-2"));

echo(parse_screw_name("8-32"));

function parse_drive(drive=undef, drive_size=undef) =
    is_undef(drive) ? ["none",undef] :
    let(drive = downcase(drive))
    in_list(drive,["hex","philips", "slot", "torx", "philips", "none"]) ? [drive, drive_size] :
    drive[0]=="t" ? ["torx", atoi(substr(drive,1))] :
    substr(drive,0,2)=="ph" ? ["philips", atoi(substr(drive,2))] :
    assert(str("Unknown screw drive type ",drive));

//thread can be an actual pitch in mm or it can be
//a name of the thread type to apply
//
// For English: coarse, fine, xfine, UNC, UNF, UNEF
// For Metric: coarse, fine, superfine, JIS
// Default is coarse
function get_screw_info(name, head, thread="coarse", drive, drive_size=undef) =
  let(type=parse_screw_name(name),
      drive_info = parse_drive(drive, drive_size),
      drive=drive_info[0],
    screwdata =
      type[0] == "english" ? get_screw_info_english(type[1],type[2], head, thread, drive) :
      type[0] == "metric" ? 0 :
      []
  )
  is_def(drive_info[1]) ? struct_set(screwdata, "drive_size", drive_info[1]) : screwdata;

function list_set(indices,values,list=[],dflt=0,minlen=0) =
    !is_list(indices) ? list_set(list,[indices],[values],dflt) :
    assert(len(indices)==len(values),"Index list and value list must have the same length")
    let( sortind = list_increasing(indices) ? list_range(len(indices)) : sortidx(indices),
         lastind = indices[select(sortind,-1)]
    )
    concat([for(j=[0:1:indices[sortind[0]]-1]) j>=len(list) ? dflt : list[j]], [values[sortind[0]]],
          [for(i=[1:1:len(sortind)-1])
                                        each
                                          assert(indices[sortind[i]]!=indices[sortind[i-1]],"Repeated index")
                                          concat(
                                            [for(j=[1+indices[sortind[i-1]]:1:indices[sortind[i]]-1]) j>=len(list) ? dflt : list[j]],
                                            [values[sortind[i]]]
                                         )
          ],
          slice(list,1+lastind, len(list)),
          replist(dflt, minlen-lastind-1)
    );

// True if the list is (non-strictly) increasing
function list_increasing(list,ind=0) = ind < len(list)-1 && list[ind]<=list[ind+1] ? list_increasing(list,ind+1) :
                                       (ind>=len(list)-1 ? true : false);

// True if the list is (non-strictly) decreasing
function list_decreasing(list,ind=0) = ind < len(list)-1 && list[ind]>=list[ind+1] ? list_increasing(list,ind+1) :
                                       (ind>=len(list)-1 ? true : false);

function struct_set(struct, keyword, value) =
     let(ind=search([keyword],struct,1,0)[0])
     ind==[] ? concat(struct, [[keyword,value]]) :
               list_set([ind], [[keyword,value]],struct);

function struct_val(struct,keyword) =
     let(ind = search([keyword],struct)[0])
     ind == [] ? undef : struct[ind][1];

function get_screw_info_english(diam, threadcount, head, thread, drive) =
 let(
   a=echo(xx=diam,threadcount,head,thread,drive),
   inch = 25.4,
   diameter = is_string(diam) ? atoi(substr(diam,1))*0.013 +0.06 :
              diam,
   pitch =
     is_def(threadcount) ? inch/threadcount :
     is_num(thread) ? thread :
     let(
        tind=struct_val([["coarse",0],["unc",0],
                         ["fine",1],["unf",1],
                         ["extrafine",2],["unef",2]],
                         downcase(thread)),
                 // coarse  fine  xfine
                 // UNC     UNF   UNEF
        UTS_thread = [
            ["#0", [undef,    80, undef]],
            ["#1", [   64,    72, undef]],
            ["#2", [   56,    64, undef]],
            ["#3", [   48,    56, undef]],
            ["#4", [   40,    48, undef]],
            ["#5", [   40,    44, undef]],
            ["#6", [   32,    40, undef]],
            ["#8", [   32,    36, undef]],
            ["#10",[   24,    32, undef]],
            ["#12",[   24,    28,    32]],
            [1/4,  [   20,    28,    32]],
            [5/16, [   18,    24,    32]],
            [3/8,  [   16,    24,    32]],
            [7/16, [   14,    20,    28]],
            [1/2,  [   13,    20,    28]],
            [9/16, [   12,    18,    24]],
            [5/8,  [   11,    18,    24]],
            [3/4,  [   10,    16,    20]],
            [7/8,  [    9,    14,    20]],
            [1,    [    8,    12,    20]],
            [1.125,[    7,    12, undef]],
            [1.25, [    7,    12, undef]],
            [1.375,[    6,    12, undef]],
            [1.5,  [    6,    12, undef]],
            [1.75, [    5, undef, undef]],
            [2,    [  4.5, undef, undef]],
         ]
       )
      inch / struct_val(UTS_thread, diam)[tind],
      head_data =
         head=="none" || is_undef(head) ? [["head","none"]] :
         head=="hex" ? let(
            UTS_hex = [
              // flat to flat width, height
               ["#2", [    1/8,   1/16]],
               ["#4", [   3/16,   1/16]],
               ["#6", [    1/4,   3/32]],
               ["#8", [    1/4,   7/64]],
               ["#10",[   5/16,    1/8]],
               ["#12",[   5/16,   5/32]],
               [1/4,  [   7/16,   5/32]],
               [5/16, [    1/2,  13/64]],
               [3/8,  [   9/16,    1/4]],
               [7/16, [    5/8,  19/64]],
               [1/2,  [    3/4,  11/32]],
               [9/16, [  13/16,  23/64]],
               [5/8,  [  15/16,  27/64]],
               [3/4,  [  1.125,    1/2]],
               [7/8,  [ 1+5/16,  37/64]],
               [1,    [    1.5,  43/64]],
               [1.125,[1+11/16,  11/16]],
               [1.25, [  1+7/8,  27/32]],
               [1.5,  [   2.25,  15/16]],
               [1.75, [  2+5/8, 1+3/32]],
               [2,    [      3, 1+7/32]],
            ],
            entry = struct_val(UTS_hex, diam)
           )
           [["head", "hex"], ["head_size", inch*entry[0]], ["head_height", inch*entry[1]]] :
         head=="socket" ? let(
            UTS_socket = [    // height = diameter
                       //diam, hex, torx
               ["#0", [  0.096,  0.05, undef]],
               ["#1", [  0.118,  1/16, undef]],
               ["#2", [   9/64,  5/64, undef]],
               ["#3", [  0.161,  5/64, undef]],
               ["#4", [  0.183,  3/32, 10]],
               ["#5", [  0.205,  3/32, 10]],
               ["#6", [  0.226,  7/64, 15, ]],
               ["#8", [  0.270,  9/64, 25]],
               ["#10",[   5/16,  5/32, 27]],
               ["#12",[  0.324,  5/32, undef]],
               [1/4,  [    3/8,  3/16, 30]],
               [5/16, [  15/32,   1/4, 45]],
               [3/8,  [   9/16,  5/16, 50 ]],
               [7/16, [  21/32,   3/8, undef]],
               [1/2,  [    3/4,   3/8, undef]],
               [9/16, [  27/32,  7/16, undef]],
               [5/8,  [  15/16,   1/2, undef]],
               [3/4,  [  1.125,   5/8, undef]],
               [7/8,  [ 1+5/16,   3/4, undef]],
               [1,    [    1.5,   3/4, undef]],
               [1.125,[1+11/16,   7/8, undef]],
               [1.25, [  1+7/8,   7/8, undef]],
               [1.375,[ 2+1/16,     1, undef]],
               [1.5,  [   2.25,     1, undef]],
               [1.75, [  2+5/8,  1.25, undef]],
               [2,    [      3,   1.5, undef]]
            ],
            entry = struct_val(UTS_socket, diam),
            drive_size =  drive=="hex" ? [["drive_size",inch*entry[1]]] :
                          drive=="torx" ? [["drive_size",entry[2]]] : []
            )
            concat([["head","socket"],["head_size",inch*entry[0]], ["head_height", inch*entry[0]]],drive_size) :
         head=="pan" ? let (
           UTS_pan = [  // pan head for philips or slotted
                 // diam, slotted ht, philips height, philips
               ["#0", [0.116, 0.039, 0.044, 0]],
               ["#1", [0.142, 0.046, 0.053, 0]],
               ["#2", [0.167, 0.053, 0.063, 1]],
               ["#3", [0.193, 0.060, 0.071, 1]],
               ["#4", [0.219, 0.068, 0.080, 1]],
               ["#5", [0.245, 0.075, 0.089, 2]],
               ["#6", [0.270, 0.082, 0.097, 2]],
               ["#8", [0.322, 0.096, 0.115, 2]],
               ["#10",[0.373, 0.110, 0.133, 2]],
               ["#12",[0.425, 0.125, 0.151, 3]],
               [1/4,  [0.492, 0.144, 0.175, 3]],
               [5/16, [0.615, 0.178, 0.218, 4]],
               [3/8,  [0.740, 0.212, 0.261, 4]],
            ],
            htind = drive=="slot" ? 1 : 2,
            entry = struct_val(UTS_pan, diam),
            drive_size = drive=="philips" ? [["drive_size", entry[3]]] : [])
           concat([["head","pan"], ["head_size", inch*entry[0]], ["head_height", inch*entry[htind]]], drive_size) :
         head=="button" || head=="round" ? let(
            UTS_button = [    // button, hex or torx drive
                 //   head diam, height, philips, hex, torx
               ["#0", [0.114, 0.032, undef, 0.035,5]],
               ["#1", [0.139, 0.039, undef, 3/64, undef]],
               ["#2", [0.164, 0.046, undef, 3/64, 6]],
               ["#3", [0.188, 0.052, undef, 1/16, undef]],
               ["#4", [0.213, 0.059, undef, 1/16, 8]],
               ["#5", [0.238, 0.066, undef, 5/64, undef]],
               ["#6", [0.262, 0.073, undef, 5/64, 10]],
               ["#8", [0.312, 0.087, undef, 3/32, 15]],
               ["#10",[0.361, 0.101, undef, 1/8,  25]],
               ["#12",[0.413, 0.114, undef, 1/8,  undef]],   // also 0.410, .115, 9/64
               [1/4,  [0.437, 0.132, undef, 5/32, 27]],
               [5/16, [0.547, 0.166, undef, 3/16, 40]],
               [3/8,  [0.656, 0.199, undef, 7/32, 45]],
               [7/16, [0.750, 0.220, undef, 1/4,  undef]],
               [1/2,  [0.875, 0.265, undef, 5/16, undef]],
               [5/8,  [1.000, 0.331, undef, 3/8,  undef]],
               [3/4,  [1.1,   0.375, undef, 7/16, undef]],
             ],
             UTS_round = [   // slotted, philips
                  // head diam, head height, philips drive
               ["#0", [0.113, 0.053, 0]],
               ["#1", [0.138, 0.061, 0]],
               ["#2", [0.162, 0.069, 1]],
               ["#3", [0.187, 0.078, 1]],
               ["#4", [0.211, 0.086, 1]],
               ["#5", [0.236, 0.095, 2]],
               ["#6", [0.260, 0.103, 2]],
               ["#8", [0.309, 0.120, 2]],
               ["#10",[0.359, 0.137, 2]],
               ["#12",[0.408, 0.153, 3]],
               [1/4,  [0.472, 0.175, 3]],
               [5/16, [0.590, 0.216, 3]],
               [3/8,  [0.708, 0.256, 4]],
               [1/2,  [0.813, 0.355, undef]]
             ],
             entry = struct_val(head=="button" ? UTS_button : UTS_round, diam),
             drive_index = drive=="philips" ? 2 :
                           drive=="hex" ? 3 :
                           drive=="torx" ? 4 : undef,
             drive_size = is_def(drive_index) ? [["drive_size", (drive=="hex" ? inch : 1) * entry[drive_index]]] : []
             )
             concat([["head",head],["head_size",inch*entry[0]], ["head_height", inch*entry[1]]],drive_size) :
         substr(head,0,4)=="flat" ? let(
             small = head == "flat small" || head == "flat undercut" || (head=="flat" && (drive!="hex")),
             undercut = head=="flat undercut",
             UTS_flat_small = [  // for philips drive, slotted, and torx
                    //      diam, ph drive, torx drive, undercut height
                   ["#0", [ .112,  0, undef, 0.025]],
                   ["#1", [ .137,  0, undef, 0.031]],
                   ["#2", [ .162,  1, 6    , 0.036]],
                   ["#3", [ .187,  1, undef, 0.042]],
                   ["#4", [ .212,  1, 8    , 0.047]],
                   ["#5", [ .237,  2, undef, 0.053]],  // ph#1 for undercut
                   ["#6", [ .262,  2, 10   , 0.059]],
                   ["#8", [ .312,  2, 15   , 0.070]],
                   ["#10",[ .362,  2, 20   , 0.081]],
                   ["#12",[ .412,  3, undef, 0.092]],
                   [1/4,  [ .477,  3, 27   , 0.107]],
                   [5/16, [ .597,  4, 40   , 0.134]],
                   [3/8,  [ .717,  4, 40   , 0.161]],
                   [1/2,  [ .815,  4, undef, 0.156]]
             ],
             UTS_flat_large = [   // for hex drive, torx
                     // head diam, hex drive size, torx size
                   ["#0", [ 0.138, 1/32, 3]],
                   ["#1", [ 0.168, 3/64, undef]],
                   ["#2", [ 0.197, 3/64, 6]],
                   ["#3", [ 0.226, 1/16, undef]],
                   ["#4", [ 0.255, 1/16, 10]],
                   ["#5", [ 0.281, 5/64, 10 ]],
                   ["#6", [ 0.307, 5/64, 15]],
                   ["#8", [ 0.359, 3/32, 20]],
                   ["#10",[ 0.411,  1/8, 25]],
                   ["#12",[ 0.422,  1/8, undef]],
                   [1/4,  [ 0.531, 5/32, 30]],
                   [5/16, [ 0.656, 3/16, 40]],
                   [3/8,  [ 0.810, 7/32, undef]],
                   [7/16, [ 0.844,  1/4, undef]],
                   [1/2,  [ 0.938, 5/16, undef]],
                   [5/8,  [ 1.188,  3/8, undef]],
                   [3/4,  [ 1.438,  1/2, undef]],
                   [7/8,  [ 1.688, 9/16, undef]],
                   [1,    [ 1.938,  5/8, undef]],
                   [1.125,[ 2.188,  3/4, undef]],
                   [1.25, [ 2.438,  7/8, undef]],
                   [1.5,  [ 2.938,    1, undef]],
             ],
             entry = struct_val(small ? UTS_flat_small : UTS_flat_large, diam),
             driveind = small && drive=="philips" || !small && drive=="hex" ? 1 :
                        drive=="torx" ? 2 :
                        undef
             )
             concat([["head","flat"],["head_angle",82],["head_size",inch*entry[0]]],
                    is_def(driveind) ? [["drive_size", (drive=="hex"?inch:1)*entry[driveind]]] : [],
                    undercut ? [["head_height", inch*entry[3]]] : []
                   ) : []
    )
    concat([["diameter",inch*diameter],["pitch", pitch],["drive",drive]],
            head_data
          );

/*
returns structure with

    diameter: (number in mm)
    pitch: (number in mm)
    head (string) "flat" "socket" "hex"
    head_size (number in mm)
    head_height (number in mm)
  */

s0 = get_screw_info("#6", thread="none");
s1 = get_screw_info("#6-32", head="pan", drive="ph2");
s2 = get_screw_info("#6-32", head="round");
s3 = get_screw_info("#6", thread="fine", head="button");
s4 = get_screw_info("#6-32", head="flat small");
s5 = get_screw_info("#6-32", head="flat large");
s6 = get_screw_info("#6-32", head="flat undercut");
s7 = get_screw_info("#6-32", head="socket");
echo(s1=s1);
echo(s2=s2);
echo(s3=s3);
echo(s4=s4);
echo(s5=s5);
echo(s6=s6);
echo(s7=s7);

$fn=32;
xdistribute(spacing=8){
  screw_head(s1);
  screw_head(s2);
  screw_head(s3);
  screw_head(s4);
  screw_head(s5);
  screw_head(s6);
  screw_head(s7);
}

// Makes screw head described by screw_info
// (No support for the recess)
// It will be located at (0,0) oriented with the head in the +Z direction
// Note that this means for flat heads that they project down (are located below
// the xy plane) and for all others, they project up and are above the xy plane.
module screw_head(screw_info) {
   head = struct_val(screw_info, "head");
   head_size = struct_val(screw_info, "head_size");
   head_height = struct_val(screw_info, "head_height");
   if (head=="flat") {
     angle = struct_val(screw_info, "head_angle")/2;
     full_height = head_size/2/tan(angle);
     height = is_def(head_height) ? head_height : full_height;
     d2 = head_size*(1-height/full_height);
     //down(height)
     zflip()
       cyl(d1=head_size, d2=d2, l=height, anchor=BOTTOM);
   }
   if (in_list(head,["round","pan","button"])) {
     base = head=="round" || head=="button" ? 0.1 * head_height : .5 * head_height;
     cyl(l=base, d=head_size, anchor=BOTTOM)
       attach(TOP)
         rotate_extrude()
           intersection(){
             arc(points=[[-head_size/2,0], [0,-base+head_height * (head=="button"?4/3:1)], [head_size/2,0]]);
             square([head_size, head_height-base]);
             }
   }
   if (head=="socket")
     cyl(l=head_height, d=head_size, anchor=BOTTOM);
}

Here's screw code that does metric and uts.  Is there a way to get internal threads?  (It looks like the existing nut code justs uses the same call to threaded_rod, which won't give the correct clearance.)  At some point I want to print some screws and see if they work.....   Also I think some of the old thread pitch tables in metric_bolts.scad are actually wrong (fine ones copies of the coarse one).
My code does not do interpolation.  If you ask for a screw that wasn't on mcmaster you'll get undef back.
// Things to do
//
// Recess size is confusing.  Does it really depend on the screw size and not just the recess type?
// The code that puts a recess into a flat head seems kind of bogus with regards to recess depth.
// Do we need hex drive size table for headless screws?

include<BOSL2/std.scad>
include<BOSL2/strings.scad>
include<BOSL2/structs.scad>
include <BOSL2/threading.scad>
include <BOSL2/phillips_drive.scad>
include <BOSL2/torx_drive.scad>

/*
http://mdmetric.com/thddata.htm#idx

Seems to show JIS has same nominal thread as others
https://www.nbk1560.com/~/media/Images/en/Product%20Site/en_technical/11_ISO%20General%20Purpose%20Metric%20Screw%20Threads.ashx?la=en

torx flat head
1.6, [3.67, 5
2, [3.8, 6
2.5, [5.5, 8
3, [6.72, 10
4, [8.96, 20
5, [10.4, 25
6, [12.6, 30

*/

/*
   template:

      ["#0", [
      ["#1", [
      ["#2", [
      ["#3", [
      ["#4", [
      ["#5", [
      ["#6", [
      ["#8", [
      ["#10",[
      ["#12",[
      [1/4,  [
      [5/16, [
      [3/8,  [
      [7/16, [
      [1/2,  [
      [9/16, [
      [5/8,  [
      [3/4,  [
      [7/8,  [
      [1,    [
      [1.125,[
      [1.25, [
      [1.375,[
      [1.5,  [
      [1.75, [
      [2,    [
*/

function parse_screw_name(name) =
    let( commasplit = str_split(name,","),
         length = str_num(commasplit[1]),
         xdash = str_split(commasplit[0], "-x"),
         type = xdash[0],
         thread = str_float(xdash[1])
    )
    type[0] == "M" || type[0] == "m" ? ["metric", str_float(substr(type,1)), thread, length] :
    let(
        diam = type[0] == "#" ? type :
               suffix(type,2)=="''" ? str_float(substr(type,0,len(type)-2)) :
               let(val=str_num(type))
               val == floor(val) && val>=0 && val<=12 ? str("#",type) : val
        )
    ["english", diam, thread, 25.4*length];

// drive can be "hex", "philips", "slot", "torx", or "none"
// or you can specify "ph0" up to "ph4" for philips and "t20" for torx 20
function parse_drive(drive=undef, drive_size=undef) =
    is_undef(drive) ? ["none",undef] :
    let(drive = downcase(drive))
    in_list(drive,["hex","philips", "slot", "torx", "philips", "none"]) ? [drive, drive_size] :
    drive[0]=="t" ? ["torx", str_int(substr(drive,1))] :
    substr(drive,0,2)=="ph" ? ["philips", str_int(substr(drive,2))] :
    assert(str("Unknown screw drive type ",drive));

//thread can be an actual pitch in mm or it can be
//a name of the thread type to apply
//
// For English: coarse, fine, xfine, UNC, UNF, UNEF
// For Metric: coarse, fine, superfine, JIS
// Default is coarse
function get_screw_info(name, head, thread="coarse", drive, drive_size=undef, oversize=0) =
  let(type=parse_screw_name(name),
      drive_info = parse_drive(drive, drive_size),
      drive=drive_info[0],
    screwdata =
      type[0] == "english" ? get_screw_info_english(type[1],type[2], head, thread, drive) :
      type[0] == "metric" ? get_screw_info_metric(type[1], type[2], head, thread, drive) :
      [],
    over_ride = concat(
                        is_def(type[3]) ? ["length",type[3]] : [],
                        is_def(drive_info[1]) ? ["drive_size", drive_info[1]] : [],
                        ["diameter", oversize+struct_val(screwdata,"diameter"),
                         "head_size", oversize+struct_val(screwdata,"head_size")]
                      )
  )
  struct_set(screwdata, over_ride);

function get_screw_info_english(diam, threadcount, head, thread, drive) =
 let(
   inch = 25.4,
   diameter = is_string(diam) ? str_int(substr(diam,1))*0.013 +0.06 :
              diam,
   pitch =
     is_def(threadcount) ? inch/threadcount :
     is_num(thread) ? thread :
     let(
        tind=struct_val([["coarse",0],["unc",0],
                         ["fine",1],["unf",1],
                         ["extra fine",2],["extrafine",2],["unef",2]],
                         downcase(thread)),
                 // coarse  fine  xfine
                 // UNC     UNF   UNEF
        UTS_thread = [
            ["#0", [undef,    80, undef]],
            ["#1", [   64,    72, undef]],
            ["#2", [   56,    64, undef]],
            ["#3", [   48,    56, undef]],
            ["#4", [   40,    48, undef]],
            ["#5", [   40,    44, undef]],
            ["#6", [   32,    40, undef]],
            ["#8", [   32,    36, undef]],
            ["#10",[   24,    32, undef]],
            ["#12",[   24,    28,    32]],
            [1/4,  [   20,    28,    32]],
            [5/16, [   18,    24,    32]],
            [3/8,  [   16,    24,    32]],
            [7/16, [   14,    20,    28]],
            [1/2,  [   13,    20,    28]],
            [9/16, [   12,    18,    24]],
            [5/8,  [   11,    18,    24]],
            [3/4,  [   10,    16,    20]],
            [7/8,  [    9,    14,    20]],
            [1,    [    8,    12,    20]],
            [1.125,[    7,    12,    18]],
            [1.25, [    7,    12,    18]],
            [1.375,[    6,    12,    18]],
            [1.5,  [    6,    12,    18]],
            [1.75, [    5, undef, undef]],
            [2,    [  4.5, undef, undef]],
         ]
       )
      inch / struct_val(UTS_thread, diam)[tind],
      head_data =
         head=="none" || is_undef(head) ? [["head","none"]] :
         head=="hex" ? let(
            UTS_hex = [
              // flat to flat width, height
               ["#2", [    1/8,   1/16]],
               ["#4", [   3/16,   1/16]],
               ["#6", [    1/4,   3/32]],
               ["#8", [    1/4,   7/64]],
               ["#10",[   5/16,    1/8]],
               ["#12",[   5/16,   5/32]],
               [1/4,  [   7/16,   5/32]],
               [5/16, [    1/2,  13/64]],
               [3/8,  [   9/16,    1/4]],
               [7/16, [    5/8,  19/64]],
               [1/2,  [    3/4,  11/32]],
               [9/16, [  13/16,  23/64]],
               [5/8,  [  15/16,  27/64]],
               [3/4,  [  1.125,    1/2]],
               [7/8,  [ 1+5/16,  37/64]],
               [1,    [    1.5,  43/64]],
               [1.125,[1+11/16,  11/16]],
               [1.25, [  1+7/8,  27/32]],
               [1.5,  [   2.25,  15/16]],
               [1.75, [  2+5/8, 1+3/32]],
               [2,    [      3, 1+7/32]],
            ],
            entry = struct_val(UTS_hex, diam)
           )
           [["head", "hex"], ["head_size", inch*entry[0]], ["head_height", inch*entry[1]]] :
         head=="socket" ? let(
            UTS_socket = [    // height = screw diameter
                       //diam, hex, torx
               ["#0", [  0.096,  0.05, undef]],
               ["#1", [  0.118,  1/16, undef]],
               ["#2", [   9/64,  5/64, undef]],
               ["#3", [  0.161,  5/64, undef]],
               ["#4", [  0.183,  3/32, 10]],
               ["#5", [  0.205,  3/32, 10]],
               ["#6", [  0.226,  7/64, 15, ]],
               ["#8", [  0.270,  9/64, 25]],
               ["#10",[   5/16,  5/32, 27]],
               ["#12",[  0.324,  5/32, undef]],
               [1/4,  [    3/8,  3/16, 30]],
               [5/16, [  15/32,   1/4, 45]],
               [3/8,  [   9/16,  5/16, 50 ]],
               [7/16, [  21/32,   3/8, undef]],
               [1/2,  [    3/4,   3/8, undef]],
               [9/16, [  27/32,  7/16, undef]],
               [5/8,  [  15/16,   1/2, undef]],
               [3/4,  [  1.125,   5/8, undef]],
               [7/8,  [ 1+5/16,   3/4, undef]],
               [1,    [    1.5,   3/4, undef]],
               [1.125,[1+11/16,   7/8, undef]],
               [1.25, [  1+7/8,   7/8, undef]],
               [1.375,[ 2+1/16,     1, undef]],
               [1.5,  [   2.25,     1, undef]],
               [1.75, [  2+5/8,  1.25, undef]],
               [2,    [      3,   1.5, undef]]
            ],
            entry = struct_val(UTS_socket, diam),
            drive_size =  drive=="hex" ? [["drive_size",inch*entry[1]]] :
                          drive=="torx" ? [["drive_size",entry[2]]] : []
            )
            concat([["head","socket"],["head_size",inch*entry[0]], ["head_height", inch*diameter]],drive_size) :
         head=="pan" ? let (
           UTS_pan = [  // pan head for philips or slotted
                 // diam, slotted ht, philips height, philips
               ["#0", [0.116, 0.039, 0.044, 0]],
               ["#1", [0.142, 0.046, 0.053, 0]],
               ["#2", [0.167, 0.053, 0.063, 1]],
               ["#3", [0.193, 0.060, 0.071, 1]],
               ["#4", [0.219, 0.068, 0.080, 1]],
               ["#5", [0.245, 0.075, 0.089, 2]],
               ["#6", [0.270, 0.082, 0.097, 2]],
               ["#8", [0.322, 0.096, 0.115, 2]],
               ["#10",[0.373, 0.110, 0.133, 2]],
               ["#12",[0.425, 0.125, 0.151, 3]],
               [1/4,  [0.492, 0.144, 0.175, 3]],
               [5/16, [0.615, 0.178, 0.218, 4]],
               [3/8,  [0.740, 0.212, 0.261, 4]],
            ],
            htind = drive=="slot" ? 1 : 2,
            entry = struct_val(UTS_pan, diam),
            drive_size = drive=="philips" ? [["drive_size", entry[3]]] : [])
           concat([["head","pan"], ["head_size", inch*entry[0]], ["head_height", inch*entry[htind]]], drive_size) :
         head=="button" || head=="round" ? let(
            UTS_button = [    // button, hex or torx drive
                 //   head diam, height, philips, hex, torx
               ["#0", [0.114, 0.032, undef, 0.035,5]],
               ["#1", [0.139, 0.039, undef, 3/64, undef]],
               ["#2", [0.164, 0.046, undef, 3/64, 6]],
               ["#3", [0.188, 0.052, undef, 1/16, undef]],
               ["#4", [0.213, 0.059, undef, 1/16, 8]],
               ["#5", [0.238, 0.066, undef, 5/64, undef]],
               ["#6", [0.262, 0.073, undef, 5/64, 10]],
               ["#8", [0.312, 0.087, undef, 3/32, 15]],
               ["#10",[0.361, 0.101, undef, 1/8,  25]],
               ["#12",[0.413, 0.114, undef, 1/8,  undef]],   // also 0.410, .115, 9/64
               [1/4,  [0.437, 0.132, undef, 5/32, 27]],
               [5/16, [0.547, 0.166, undef, 3/16, 40]],
               [3/8,  [0.656, 0.199, undef, 7/32, 45]],
               [7/16, [0.750, 0.220, undef, 1/4,  undef]],
               [1/2,  [0.875, 0.265, undef, 5/16, undef]],
               [5/8,  [1.000, 0.331, undef, 3/8,  undef]],
               [3/4,  [1.1,   0.375, undef, 7/16, undef]],
             ],
             UTS_round = [   // slotted, philips
                  // head diam, head height, philips drive
               ["#0", [0.113, 0.053, 0]],
               ["#1", [0.138, 0.061, 0]],
               ["#2", [0.162, 0.069, 1]],
               ["#3", [0.187, 0.078, 1]],
               ["#4", [0.211, 0.086, 1]],
               ["#5", [0.236, 0.095, 2]],
               ["#6", [0.260, 0.103, 2]],
               ["#8", [0.309, 0.120, 2]],
               ["#10",[0.359, 0.137, 2]],
               ["#12",[0.408, 0.153, 3]],
               [1/4,  [0.472, 0.175, 3]],
               [5/16, [0.590, 0.216, 3]],
               [3/8,  [0.708, 0.256, 4]],
               [1/2,  [0.813, 0.355, undef]]
             ],
             entry = struct_val(head=="button" ? UTS_button : UTS_round, diam),
             drive_index = drive=="philips" ? 2 :
                           drive=="hex" ? 3 :
                           drive=="torx" ? 4 : undef,
             drive_size = is_def(drive_index) ? [["drive_size", (drive=="hex" ? inch : 1) * entry[drive_index]]] : []
             )
             concat([["head",head],["head_size",inch*entry[0]], ["head_height", inch*entry[1]]],drive_size) :
         substr(head,0,4)=="flat" ? let(
             small = head == "flat small" || head == "flat undercut" || (head=="flat" && (drive!="hex")),
             undercut = head=="flat undercut",
             UTS_flat_small = [  // for philips drive, slotted, and torx
                    //      diam, ph drive, torx drive, undercut height
                   ["#0", [ .112,  0, undef, 0.025]],
                   ["#1", [ .137,  0, undef, 0.031]],
                   ["#2", [ .162,  1, 6    , 0.036]],
                   ["#3", [ .187,  1, undef, 0.042]],
                   ["#4", [ .212,  1, 8    , 0.047]],
                   ["#5", [ .237,  2, undef, 0.053]],  // ph#1 for undercut
                   ["#6", [ .262,  2, 10   , 0.059]],
                   ["#8", [ .312,  2, 15   , 0.070]],
                   ["#10",[ .362,  2, 20   , 0.081]],
                   ["#12",[ .412,  3, undef, 0.092]],
                   [1/4,  [ .477,  3, 27   , 0.107]],
                   [5/16, [ .597,  4, 40   , 0.134]],
                   [3/8,  [ .717,  4, 40   , 0.161]],
                   [1/2,  [ .815,  4, undef, 0.156]]
             ],
             UTS_flat_large = [   // for hex drive, torx
                     // head diam, hex drive size, torx size
                   ["#0", [ 0.138, 1/32, 3]],
                   ["#1", [ 0.168, 3/64, undef]],
                   ["#2", [ 0.197, 3/64, 6]],
                   ["#3", [ 0.226, 1/16, undef]],
                   ["#4", [ 0.255, 1/16, 10]],
                   ["#5", [ 0.281, 5/64, 10 ]],
                   ["#6", [ 0.307, 5/64, 15]],
                   ["#8", [ 0.359, 3/32, 20]],
                   ["#10",[ 0.411,  1/8, 25]],
                   ["#12",[ 0.422,  1/8, undef]],
                   [1/4,  [ 0.531, 5/32, 30]],
                   [5/16, [ 0.656, 3/16, 40]],
                   [3/8,  [ 0.810, 7/32, undef]],
                   [7/16, [ 0.844,  1/4, undef]],
                   [1/2,  [ 0.938, 5/16, undef]],
                   [5/8,  [ 1.188,  3/8, undef]],
                   [3/4,  [ 1.438,  1/2, undef]],
                   [7/8,  [ 1.688, 9/16, undef]],
                   [1,    [ 1.938,  5/8, undef]],
                   [1.125,[ 2.188,  3/4, undef]],
                   [1.25, [ 2.438,  7/8, undef]],
                   [1.5,  [ 2.938,    1, undef]],
             ],
             entry = struct_val(small ? UTS_flat_small : UTS_flat_large, diam),
             driveind = small && drive=="philips" || !small && drive=="hex" ? 1 :
                        drive=="torx" ? 2 :
                        undef
             )
             concat([["head","flat"],["head_angle",82],["head_size",inch*entry[0]]],
                    is_def(driveind) ? [["drive_size", (drive=="hex"?inch:1)*entry[driveind]]] : [],
                    undercut ? [["head_height", inch*entry[3]]] : []
                   ) : []
    )
    concat([["diameter",inch*diameter],["pitch", pitch],["drive",drive]],
            head_data
          );

function get_screw_info_metric(diam, pitch, head, thread, drive) =
 let(
   a=echo(metricsi=diam,pitch,head,thread,drive),
   inch = 25.4,
   diameter = is_string(diam) ? str_int(substr(diam,1))*0.013 +0.06 :
              diam,
   pitch = is_num(thread) ? thread :
     is_def(pitch) ? pitch :
     let(
        tind=struct_val([["coarse",0],
                         ["fine",1],
                         ["extra fine",2],["extrafine",2],
			 ["super fine",3],["superfine",3]],
                         downcase(thread)),
                            // coarse  fine  xfine superfine
        ISO_thread = [
  	               [1  , [0.25,    0.2 ,   undef, undef,]],
           	       [1.2, [0.25,    0.2 ,   undef, undef,]],
           	       [1.4, [0.3 ,    0.2 ,   undef, undef,]],
           	       [1.6, [0.35,    0.2 ,   undef, undef,]],
           	       [1.7, [0.35,   undef,   undef, undef,]],
           	       [1.8, [0.35,    0.2 ,   undef, undef,]],
           	       [2  , [0.4 ,    0.25,   undef, undef,]],
           	       [2.2, [0.45,    0.25,   undef, undef,]],
           	       [2.3, [0.4 ,   undef,   undef, undef,]],
           	       [2.5, [0.45,    0.35,   undef, undef,]],
           	       [2.6, [0.45,   undef,   undef, undef,]],
           	       [3  , [0.5 ,    0.35,   undef, undef,]],
           	       [3.5, [0.6 ,    0.35,   undef, undef,]],
           	       [4  , [0.7 ,    0.5 ,   undef, undef,]],
           	       [5  , [0.8 ,    0.5 ,   undef, undef,]],
           	       [6  , [1   ,    0.75,   undef, undef,]],
           	       [7  , [1   ,    0.75,   undef, undef,]],
           	       [8  , [1.25,    1   ,    0.75, undef,]],
           	       [9  , [1.25,    1   ,    0.75, undef,]],
           	       [10 , [1.5 ,    1.25,    1   ,  0.75,]],
           	       [11 , [1.5 ,    1   ,    0.75, undef,]],
           	       [12 , [1.75,    1.5 ,    1.25,  1,   ]],
           	       [14 , [2   ,    1.5 ,    1.25,  1,   ]],
           	       [16 , [2   ,    1.5 ,    1   , undef,]],
           	       [18 , [2.5 ,    2   ,    1.5 ,  1,   ]],
           	       [20 , [2.5 ,    2   ,    1.5 ,  1,   ]],
           	       [22 , [2.5 ,    2   ,    1.5 ,  1,]],
           	       [24 , [3   ,    2   ,    1.5 ,  1,]],
           	       [27 , [3   ,    2   ,    1.5 ,  1,]],
           	       [30 , [3.5 ,    3   ,    2   ,  1.5,]],
           	       [33 , [3.5 ,    3   ,    2   ,  1.5,]],
           	       [36 , [4   ,    3   ,    2   ,  1.5,]],
           	       [39 , [4   ,    3   ,    2   ,  1.5,]],
           	       [42 , [4.5 ,    4   ,    3   ,  2,]],
           	       [45 , [4.5 ,    4   ,    3   ,  2,]],
           	       [48 , [5   ,    4   ,    3   ,  2,]],
           	       [52 , [5   ,    4   ,    3   ,  2,]],
           	       [56 , [5.5 ,    4   ,    3   ,  2,]],
           	       [60 , [5.5 ,    4   ,    3   ,  2,]],
           	       [64 , [6   ,    4   ,    3   ,  2,]],
           	       [68 , [6   ,    4   ,    3   ,  2,]],
           	       [72 , [6   ,    4   ,    3   ,  2,]],
           	       [80 , [6   ,    4   ,    3   ,  2,]],
           	       [90 , [6   ,    4   ,    3   ,  2,]],
           	       [100, [6   ,    4   ,    3   ,  2,]],
         ]
       )
      struct_val(ISO_thread, diam)[tind],
      head_data =
         head=="none" || is_undef(head) ? [["head","none"]] :
         head=="hex" ? let(
            metric_hex = [
              // flat to flat width, height
	      [5, [8, 3.5]],
	      [6, [10,4]],
	      [8, [13, 5.3]],
	      [10, [17, 6.4]],
	      [12, [19, 7.5]],
	      [14, [22, 8.8]],
	      [16, [24, 10]],
	      [18, [27,11.5]],
	      [20, [30, 12.5]],
	      [24, [36, 15]],
	      [30, [46, 18.7]],
            ],
            entry = struct_val(metric_hex, diam)
           )
           [["head", "hex"], ["head_size", entry[0]], ["head_height", entry[1]]] :
         head=="socket" ? let(
            metric_socket = [    // height = screw diameter
                      //diam, hex
                [1.4, [2.5, 1.3]],
		[1.6, [3, 1.5]],
		[2,   [3.8, 1.5]],
		[2.5, [4.5, 2]],
		[2.6, [5, 2]],
		[3,   [5.5, 2.5]],
		[3.5, [6.2, 2.5]],
		[4,   [7, 3]],
		[5,   [8.5, 4]],
		[6,   [10, 5]],
		[7,   [12, 6]],
		[8,   [13, 6]],
		[10,  [16, 8]],
		[12,  [18, 10]],
		[14,  [21, 12]],
		[16,  [24, 14]],
		[18,  [27, 14]],
		[20,  [30, 17]],
		[22,  [33, 17]],
		[24,  [36, 19]],
		[27,  [40, 19]],
		[30,  [45, 22]],
		[33,  [50, 24]],
		[36,  [54, 27]],
		[42,  [63, 32]],
		[48,  [72, 36]],
            ],
            entry = struct_val(metric_socket, diam),
            drive_size =  drive=="hex" ? [["drive_size",entry[1]]] : []
            )
            concat([["head","socket"],["head_size",entry[0]], ["head_height", diameter]],drive_size) :
         head=="pan" ? let (
           metric_pan = [  // pan head for philips or slotted
                 // diam, slotted ht, philips height, philips
      		 [1.6,[3.2, undef, 1.3,	0]],
		 [2,  [4,   1.2, 1.6,	1]],
		 [2.5,[5,   1.5, 2,  	1]],
		 [3,  [6,   1.8, 2.4,	1]],
		 [3.5,[8,   undef, 3.1,	2]],
		 [5,  [10,  3, 3.8,	2]],
		 [6,  [12,  3.6, 4.6,	3]],
		 [8,  [16,  8, 6,  	4]],
		 [10, [20,  undef, 7.5,	4]],
            ],
            htind = drive=="slot" ? 1 : 2,
            entry = struct_val(metric_pan, diam),
            drive_size = drive=="philips" ? [["drive_size", entry[3]]] : [])
           concat([["head","pan"], ["head_size", entry[0]], ["head_height", entry[htind]]], drive_size) :
         head=="button" || head=="cheese" ? let(
            metric_button = [    // button, hex drive
                 //   head diam, height, hex, philips
                 [1.6, [2.9, 0.8, 0.9, undef]],
		 [2, [3.5, 1.3, 1.3, undef]],
		 [2.2, [3.8, 0.9, 1.3, undef]],
		 [2.5, [4.6, 1.5, 1.5, undef]],
		 [3, [5.7, 1.65,2, undef]],
		 [3.5, [5.7, 1.65,2, undef]],
		 [4, [7.6, 2.2, 2.5, undef]],
		 [5, [9.5, 2.75,3, undef]],
		 [6, [10.5, 3.3,4, undef]],
		 [8, [14, 4.4, 5, undef]],
		 [10, [17.5, 5.5, 6, undef]],
		 [12, [21, 6.6, 8, undef]],
		 [16, [28, 8.8, 10, undef]],
             ],
             metric_cheese = [   // slotted, philips
                  // head diam, head height, philips drive
		[1,[2,0.7, undef, undef]],
		[1.2, [2.3, 0.8,undef,undef]],
		[1.4, [2.6, 0.9,undef,undef]],
		[1.6, [3, 1,undef,undef]],
		[2, [3.8, 1.3, undef,1]],
		[2.5, [4.5, 1.6, undef,1]],
		[3, [5.5, 2, undef, 2]],
		[3.5, [6, 2.4, undef, undef]],
		[4, [7, 2.6, undef, 2]],
		[5, [8.5, 3.3, undef, 2]],
		[6, [10, 3.9, undef, 3]],
		[8, [13, 5,undef, undef]]
             ],
             entry = struct_val(head=="button" ? metric_button : metric_cheese, diam),
             drive_index = drive=="philips" ? 3 :
                           drive=="hex" ? 2 : undef,
             drive_size = is_def(drive_index) ? [["drive_size", entry[drive_index]]] : []
             )
             concat([["head",head],["head_size",entry[0]], ["head_height", entry[1]]],drive_size) :
         substr(head,0,4)=="flat" ? let(
             small = head == "flat small" || (head=="flat" && (drive!="hex")),
             metric_flat_large = [ // for hex drive
                  [2, [4, 1.3]],
		  [2.5, [5, 1.5]],
		  [3, [6, 2]],
		  [4, [8, 2.5]],
		  [5, [10, 3]],
		  [6, [12, 4]],
		  [8, [16, 5]],
		  [10, [20, 6]],
		  [12, [24, 8]],
		  [14, [27, 10]],
		  [16, [30, 10]],
		  [18, [33, 12]],
		  [20, [36, 12]],
             ],
             metric_flat_small = [ // for philips, slotted
                  [1.6, [3, 0]],
                  [2,   [3.8, 0]],
	       	  [2.5, [4.7, 1]],
	       	  [2.6, [4.7, 1]],
	       	  [3,   [5.6, 1]],
	       	  [3.5, [7.5, 1]],
	       	  [4,   [7.5, 2]],
	       	  [5,   [9.2, 2]],
	       	  [6,   [11, 3]],
	       	  [8,   [14.5, 4]],
	       	  [10,  [18, 4]],
             ],
             entry = struct_val(small ? metric_flat_small : metric_flat_large, diam),
             driveind = small && drive=="philips" || !small && drive=="hex" ? 1 : undef
             )
             concat([["head","flat"],["head_angle",90],["head_size",entry[0]]],
                    is_def(driveind) ? [["drive_size", entry[driveind]]] : []
                   ) : []
    )
    concat([["diameter",diam],["pitch", pitch],["drive",drive]],
            head_data
          );

/*
returns structure with

    diameter: (number in mm)
    pitch: (number in mm)
    head (string) "flat" "socket" "hex"
    head_size (number in mm)
    head_height (number in mm)
  */

/*
s0 = get_screw_info("#6", thread="none");
s1 = get_screw_info("#6-32", head="pan", drive="ph2");
s1a = get_screw_info("#6-32", head="pan", drive="slot");
s2 = get_screw_info("#6-32", head="round");
s3 = get_screw_info("#6", thread="fine", head="button");
s4 = get_screw_info("#6-32", head="flat small");
s5 = get_screw_info("#6-32", head="flat large");
s6 = get_screw_info("#6-32", head="flat undercut");
s7 = get_screw_info("#6-32", head="socket");
echo(s1=s1);
echo(s2=s2);
echo(s3=s3);
echo(s4=s4);
echo(s5=s5);
echo(s6=s6);
echo(s7=s7);

m0 = get_screw_info("M3", head="button");
m1 = get_screw_info("M3", head="pan", drive="philips");
m2 = get_screw_info("M3", head="pan", drive="slot");
m3 = get_screw_info("M3", head="flat small");
m4 = get_screw_info("M3", head="flat large");
m5 = get_screw_info("M3", head="flate", drive="hex");
m6 = get_screw_info("M3", head="socket");
m7 = get_screw_info("M3", head="hex");

$fn=32;
xdistribute(spacing=8){
  screw_head(s1);
  screw_head(s1a);
  screw_head(s2);
  screw_head(s3);
  screw_head(s4);
  screw_head(s5);
  screw_head(s6);
  screw_head(s7);
}
fwd(10)
xdistribute(spacing=8){
  screw_head(m0);
  screw_head(m1);
  screw_head(m2);
  screw_head(m3);
  screw_head(m4);
  screw_head(m5);
  screw_head(m6);
  screw_head(m7);
}

*/

// Makes screw head described by screw_info
// It will be located at (0,0) oriented with the head in the +Z direction
// Note that this means for flat heads that they project down (are located below
// the xy plane) and for all others, they project up and are above the xy plane.
module screw_head(screw_info,details=false) {
   head = struct_val(screw_info, "head");
   head_size = struct_val(screw_info, "head_size");
   head_height = struct_val(screw_info, "head_height");
   if (head=="flat") {
     angle = struct_val(screw_info, "head_angle")/2;
     full_height = head_size/2/tan(angle);
     height = is_def(head_height) ? head_height : full_height;
     d2 = head_size*(1-height/full_height);
     //down(height)
     zflip()
       cyl(d1=head_size, d2=d2, l=height, anchor=BOTTOM);
   }
   if (in_list(head,["round","pan","button","fillister","cheese"])) {
     base = head=="fillister" ? 0.75*head_height :
            head=="pan" ? .5 * head_height :
            head=="cheese" ? .7 * head_height :
            0.1 * head_height;   // round and button
     head_size2 = head=="cheese" ? (24/26) * head_size : head_size;
     cyl(l=base, d1=head_size, d2=head_size2,anchor=BOTTOM, $fn=32)
       attach(TOP)
         rotate_extrude($fn=32)
           intersection(){
             arc(points=[[-head_size2/2,0], [0,-base+head_height * (head=="button"?4/3:1)], [head_size2/2,0]]);
             square([head_size2, head_height-base]);
             }
   }
   if (head=="socket")
     cyl(l=head_height, d=head_size, anchor=BOTTOM);
   if (head=="hex")
     intersection(){
       linear_extrude(height=head_height) hexagon(id=head_size);
       if (details)
         down(.01)cyl(l=head_height+.02,d=2*head_size/sqrt(3), chamfer=head_size*(1/sqrt(3)-1/2), anchor=BOTTOM);
     }
}

// Draw a screw based on screw specification
//
// For metric (ISO) name is M<size>x<pitch>,<length>, e.g. M6x1,10.  You can omit the pitch or length, e.g. M6x1, or M6,10, or just M6.
// For English (UTS) name is <size>-<threadcount>,<length>, e.g. #8-32,1/2, or 1/4-20,1.  Units are in inches, including the length.
//    Size can be a number from 0 to 12 with or without a leading '#' to specify a screw gauge size, or any other value to specify
//    a diameter in inches, either as a float or a fraction, so 0.5-13 and 1/2-13 are equivalent.  To force interpretation of the value
//    as inches add '' to the end, e.g. 1''-4 is a one inch screw and 1-80 is a very small 1-gauge screw.
//
// If thread pitch is not given it is taken from standard tables.
//
// Values specified as arguments to screw override specifications in the screw name.
//
// thread = type of threading, for UTS one of "coarse", "fine" and "extra fine".  Metric adds "super fine".   Note that ISO specifies 3 different
//           "fine" threadings but doesn't give them different names.  Note also that JIS specifies the same set of options as ISO, but with different
//            tolerances.  The default is "coarse" and you can use "none" for unthreaded.
//            If thread is a number it is taken as the thread pitch in mm.    If you specify thhis you can omit name, head, thread, drive and drive_size, and oversize.
//        You can also modify the screw specification before passing it in.
//
module screw(name, head, thread="coarse", drive, drive_size, length, shank=0,oversize=0, spec, details=true, anchor="base",spin=0, orient=UP)
{
   spec = is_def(spec) ? spec : get_screw_info(name, head, thread, drive, drive_size, oversize);
   head = struct_val(spec,"head");
   pitch = struct_val(spec, "pitch");
   diameter = struct_val(spec, "diameter");
   headless = head=="none" || head==undef;
   eps = headless || substr(head,len=4)=="flat" ? 0 : 0.01;
   length = first_defined([length,struct_val(spec,"length")]) + eps;
   sides = max(12, segs(diameter/2));
   unthreaded = is_undef(pitch) || pitch==0 ? length : shank;
   threaded = length - unthreaded;
   echo(t=threaded,length,unthreaded);
   anchors = [
		anchorpt("base",   [0,0,0]),
              ];
   orient_and_anchor([diameter, diameter, length], orient, anchor, spin=spin, geometry="cylinder", anchors=anchors, chain=true) {
     difference(){
       union(){
         screw_head(spec,details);
         up(eps){
           if (unthreaded>0){
              cyl(d=diameter, h=unthreaded+eps+(threaded>0?0.01:0), anchor=TOP, $fn=sides);
             }
           if (threaded>0)
             intersection(){
               down(unthreaded)
                 threaded_rod(d=diameter, l=threaded+eps, pitch=pitch, $fn=sides, anchor=TOP );
               if (details)
                 up(.01)cyl(d=diameter, l=length+.02+eps, chamfer1 = pitch/2, chamfer2 = headless ? pitch/2 : -pitch/2, anchor=TOP, $fn=sides);
            }
         }
       }
       driver(spec);
     }
     children();
   }
}

module driver(spec)
{
  head = struct_val(spec,"head");
  diameter = struct_val(spec,"diameter");
  head_height = is_def(struct_val(spec,"head_height")) ? struct_val(spec,"head_height") :
                head=="socket" ? diameter :
                substr(head,len=4)=="flat" ? .5*struct_val(spec,"head_size") :
                head=="none" ? .7*diameter : undef;
  drive = struct_val(spec,"drive");
  drive_size = struct_val(spec,"drive_size");
  echo(drive = drive);
  echo(head_height = head_height, head=head, substr(head,len=4));
  echo(down=substr("head",len=4)=="flat" ? head_height : 0);
  down(substr(head,len=4)=="flat" || head=="none" ? head_height : 0) {
    if (drive=="philips") down(head_height/6) phillips_drive(size=drive_size, shaft=diameter);
    if (drive=="torx") up(1) torx_drive(size=drive_size, l=head_height+0.1, center=false);
    if (drive=="hex") up(1) linear_extrude(height=head_height) hexagon(id=drive_size);
  }
}

screw("8,.4", head="none", drive="hex", drive_size=2, details=true);
echo(get_screw_info("8-32,1", head="hex",thread=0));

This can use a refresh, since the strings.scad, structs.scad, and other things have already been integrated.

At the moment waiting for a fix on the bug for generating the threaded rod.

Here's said bugfixes.  (Not a bug in the threading lib itself.)
function thread_profile(thread) =
    let(
        pitch = struct_val(thread,"pitch"),
        basicrad = struct_val(thread,"basic")/2,
        meanpitchrad = mean(struct_val(thread,"d_pitch"))/2,
        meanminorrad = mean(struct_val(thread,"d_minor"))/2,
        meanmajorrad = mean(struct_val(thread,"d_major"))/2,
        depth = (meanmajorrad-meanminorrad)/pitch,
        crestwidth = (pitch/2 - 2*(meanmajorrad-meanpitchrad)/sqrt(3))/pitch
    ) [
        [0, -depth],
        [depth/sqrt(3), 0],
        [depth/sqrt(3)+crestwidth, 0],
        [crestwidth + 2*depth/sqrt(3), -depth],
        [pitch, -depth]
    ];

module rod(pitch, profile, d, l) {
    trapezoidal_threaded_rod( d=d, l=l, pitch=pitch, profile = profile, bevel=false);
}

Ok.  Here's where this stands.  I think it mostly works.  Needs some better error handling for when you request something that doesn't exist.  (What happens now is a 20 line error traceback and no insight.)
// Things to do
//
// Recess size is confusing.  Does it really depend on the screw size and not just the recess type?
// The code that puts a recess into a flat head seems kind of bogus with regards to recess depth.
// Do we need hex drive size table for headless screws?

include<BOSL2/std.scad>
include<BOSL2/strings.scad>
include<BOSL2/structs.scad>
include<BOSL2/threading.scad>
include<BOSL2/phillips_drive.scad>
include<BOSL2/torx_drive.scad>

/*
http://mdmetric.com/thddata.htm#idx

Seems to show JIS has same nominal thread as others
https://www.nbk1560.com/~/media/Images/en/Product%20Site/en_technical/11_ISO%20General%20Purpose%20Metric%20Screw%20Threads.ashx?la=en

torx flat head
1.6, [3.67, 5
2, [3.8, 6
2.5, [5.5, 8
3, [6.72, 10
4, [8.96, 20
5, [10.4, 25
6, [12.6, 30

*/

function parse_screw_name(name) =
    let( commasplit = str_split(name,","),
         length = str_num(commasplit[1]),
         xdash = str_split(commasplit[0], "-x"),
         type = xdash[0],
         thread = str_float(xdash[1])
    )
    type[0] == "M" || type[0] == "m" ? ["metric", str_float(substr(type,1)), thread, length] :
    let(
        diam = type[0] == "#" ? type :
               suffix(type,2)=="''" ? str_float(substr(type,0,len(type)-2)) :
               let(val=str_num(type))
               val == floor(val) && val>=0 && val<=12 ? str("#",type) : val
        )
    ["english", diam, thread, 25.4*length];

// drive can be "hex", "philips", "slot", "torx", or "none"
// or you can specify "ph0" up to "ph4" for philips and "t20" for torx 20
function parse_drive(drive=undef, drive_size=undef) =
    is_undef(drive) ? ["none",undef] :
    let(drive = downcase(drive))
    in_list(drive,["hex","philips", "slot", "torx", "philips", "none"]) ? [drive, drive_size] :
    drive[0]=="t" ? ["torx", str_int(substr(drive,1))] :
    substr(drive,0,2)=="ph" ? ["philips", str_int(substr(drive,2))] :
    assert(str("Unknown screw drive type ",drive));

// Function: get_screw_info()
// Usage:
//   info = get_screw_info(name, [head], [thread], [drive], [drive_size], [oversize])
// Description:
//   Look up screw characteristics for the specified screw type.
//
//   For metric (ISO) `name` is M<size>x<pitch>,<length>, e.g. `"M6x1,10"` specifies a 6mm diameter screw with a thread pitch of 1mm and length of 10mm.
//   You can omit the pitch or length, e.g. `"M6x1"`, or `"M6,10"`, or just `"M6"`.
//
//   For English (UTS) name is <size>-<threadcount>,<length>, e.g. `"#8-32,1/2"`, or `"1/4-20,1"`.  Units are in inches, including the length.
//   Size can be a number from 0 to 12 with or without a leading '#' to specify a screw gauge size, or any other value to specify
//   a diameter in inches, either as a float or a fraction, so `"0.5-13"` and `"1/2-13"` are equivalent.  To force interpretation of the value
//   as inches add `''` to the end, e.g. `"1''-4"` is a one inch screw and `"1-80"` is a very small 1-gauge screw.  The pitch is specified
//   using a thread count, the number of threads per inch.  The length is in inches.
//
//   If you omit the pitch then a standard screw pitch will be supplied from lookup tables for the screw diameter you have chosen.
//   For each screw diameter, multiple standard pitches are possible.
//   For the UTS system these the availble thread types are:
//   - "coarse" or "UNC"
//   - "fine" or "UNF"
//   - "extra fine", "extrafine" or "UNEF".
//   The ISO system defines a coarse threading and three different fine threadings but does not give different names to the fine threadings.
//   The thread options for ISO are:
//   - "coarse"
//   - "fine"
//   - "extra fine" or "extrafine"
//   - "super fine" or "superfine"
//   The default pitch selection is "coarse".  Note that this selection is case independent.  Set
//   `thread` to one of these values to choose a different pitch.  Note that not every pitch category is defined at every
//   diameter.  You can also specify the thread pitch directly, for example you could set `thread=2` which would
//   produce threads with a pitch of 2mm.  The final option is to specify `thread="none"` to produce an unthreaded
//   screw either to simplify the model or to use for cutting out screw holes.  Setting the pitch to zero also produces
//   an unthreaded screw.  If you specify a numeric thread value it will override any value given in `name`.
//
//   The `head` parameter specifies the type of head the screw will have.  Options for the head are
//   - "flat"
//   - "flat small"
//   - "flat large"
//   - "flat undercut"
//   - "round"
//   - "pan"
//   - "socket"
//   - "hex"
//   - "button"
//   - "cheese"
//   - "fillister"
//   - "none"
//   Note that different sized flat heads exist for the same screw type.  Sometimes this depends on the type of recess.  If you specify "flat" then
//   the size will be chosen appropriately for the recess you specify.  The default is "none".
//
//   The `drive` parameter specifies the recess type.  Options for the drive are
//   - "none"
//   - "philips"
//   - "slot"
//   - "torx"
//   - "hex"
//   - "ph0", up to "ph4" for philips of the specified size
//   - "t<size>" for torx at a specified size, e.g. "t20"
//   The default drive is "none"
//
//   The drive size is specified appropriately to the drive type: drive number for philips or torx, and allen width in mm or inches (as appropriate) for hex.
//   Drive size is determined automatically from the screw size, but by passing the `drive_size` parameter
//   you can override the default, or in cases where no default exists you can specify it.

//   This is intended for generating clearance holes, not for dealing with printer inaccuracy.  Does not affect length, thread pitch or head height.

//
//   The output is a structure with the following fields:
//   - system: either "UTS" or "ISO"
//   - diameter: the nominal diameter of the screw shaft in mm
//   - pitch: the thread pitch in mm
//   - head: the type of head (a string from the list above)
//   - head_size: size of the head in mm
//   - head_angle: countersink angle for flat heads
//   - head_height: height of the head (when needed to specify the head)
//   - drive: the drive type ("philips", "torx", "slot", "hex", "none")
//   - drivesize: the drive size, either a drive number (philips or torx) or a dimension in mm
//   - length: length of the screw in mm measured in the customary fashion: for flat head screws the total length and for other screws, the length from the bottom of the head to the screw tip.
// Arguments:
//   name = screw specification, e.g. "M5x1" or "#8-32"
//   head = head type (see list above).  Default: none
//   thread = thread type or specification.  Default: "coarse"
//   drive = drive type.  Default: none
//   drive_size = size of drive recess to override computed value
//   oversize = amount to increase screw diameter for clearance holes.  Default: 0
function get_screw_info(name, head, thread="coarse", drive, drive_size=undef, oversize=0) =
  let(type=parse_screw_name(name),
      drive_info = parse_drive(drive, drive_size),
      drive=drive_info[0],
    screwdata =
      type[0] == "english" ? _get_screw_info_english(type[1],type[2], head, thread, drive) :
      type[0] == "metric" ? _get_screw_info_metric(type[1], type[2], head, thread, drive) :
      [],
    over_ride = concat(
                        is_def(type[3]) ? ["length",type[3]] : [],
                        is_def(drive_info[1]) ? ["drive_size", drive_info[1]] : [],
                        ["diameter", oversize+struct_val(screwdata,"diameter"),
                         "head_size", oversize+struct_val(screwdata,"head_size")]
                      )
  )
  struct_set(screwdata, over_ride);

function _get_screw_info_english(diam, threadcount, head, thread, drive) =
 let(
   inch = 25.4,
   diameter = is_string(diam) ? str_int(substr(diam,1))*0.013 +0.06 :
              diam,
   pitch =
     is_def(threadcount) ? inch/threadcount :
     is_num(thread) ? thread :
     let(
        tind=struct_val([["coarse",0],["unc",0],
                         ["fine",1],["unf",1],
                         ["extra fine",2],["extrafine",2],["unef",2]],
                         downcase(thread)),
                 // coarse  fine  xfine
                 // UNC     UNF   UNEF
        UTS_thread = [
            ["#0", [undef,    80, undef]],
            ["#1", [   64,    72, undef]],
            ["#2", [   56,    64, undef]],
            ["#3", [   48,    56, undef]],
            ["#4", [   40,    48, undef]],
            ["#5", [   40,    44, undef]],
            ["#6", [   32,    40, undef]],
            ["#8", [   32,    36, undef]],
            ["#10",[   24,    32, undef]],
            ["#12",[   24,    28,    32]],
            [1/4,  [   20,    28,    32]],
            [5/16, [   18,    24,    32]],
            [3/8,  [   16,    24,    32]],
            [7/16, [   14,    20,    28]],
            [1/2,  [   13,    20,    28]],
            [9/16, [   12,    18,    24]],
            [5/8,  [   11,    18,    24]],
            [3/4,  [   10,    16,    20]],
            [7/8,  [    9,    14,    20]],
            [1,    [    8,    12,    20]],
            [1.125,[    7,    12,    18]],
            [1.25, [    7,    12,    18]],
            [1.375,[    6,    12,    18]],
            [1.5,  [    6,    12,    18]],
            [1.75, [    5, undef, undef]],
            [2,    [  4.5, undef, undef]],
         ]
       )
      inch / struct_val(UTS_thread, diam)[tind],
      head_data =
         head=="none" || is_undef(head) ? [["head","none"]] :
         head=="hex" ? let(
            UTS_hex = [
              // flat to flat width, height
               ["#2", [    1/8,   1/16]],
               ["#4", [   3/16,   1/16]],
               ["#6", [    1/4,   3/32]],
               ["#8", [    1/4,   7/64]],
               ["#10",[   5/16,    1/8]],
               ["#12",[   5/16,   5/32]],
               [1/4,  [   7/16,   5/32]],
               [5/16, [    1/2,  13/64]],
               [3/8,  [   9/16,    1/4]],
               [7/16, [    5/8,  19/64]],
               [1/2,  [    3/4,  11/32]],
               [9/16, [  13/16,  23/64]],
               [5/8,  [  15/16,  27/64]],
               [3/4,  [  1.125,    1/2]],
               [7/8,  [ 1+5/16,  37/64]],
               [1,    [    1.5,  43/64]],
               [1.125,[1+11/16,  11/16]],
               [1.25, [  1+7/8,  27/32]],
               [1.5,  [   2.25,  15/16]],
               [1.75, [  2+5/8, 1+3/32]],
               [2,    [      3, 1+7/32]],
            ],
            entry = struct_val(UTS_hex, diam)
           )
           [["head", "hex"], ["head_size", inch*entry[0]], ["head_height", inch*entry[1]]] :
         head=="socket" ? let(
            UTS_socket = [    // height = screw diameter
                       //diam, hex, torx
               ["#0", [  0.096,  0.05, undef]],
               ["#1", [  0.118,  1/16, undef]],
               ["#2", [   9/64,  5/64, undef]],
               ["#3", [  0.161,  5/64, undef]],
               ["#4", [  0.183,  3/32, 10]],
               ["#5", [  0.205,  3/32, 10]],
               ["#6", [  0.226,  7/64, 15, ]],
               ["#8", [  0.270,  9/64, 25]],
               ["#10",[   5/16,  5/32, 27]],
               ["#12",[  0.324,  5/32, undef]],
               [1/4,  [    3/8,  3/16, 30]],
               [5/16, [  15/32,   1/4, 45]],
               [3/8,  [   9/16,  5/16, 50 ]],
               [7/16, [  21/32,   3/8, undef]],
               [1/2,  [    3/4,   3/8, undef]],
               [9/16, [  27/32,  7/16, undef]],
               [5/8,  [  15/16,   1/2, undef]],
               [3/4,  [  1.125,   5/8, undef]],
               [7/8,  [ 1+5/16,   3/4, undef]],
               [1,    [    1.5,   3/4, undef]],
               [1.125,[1+11/16,   7/8, undef]],
               [1.25, [  1+7/8,   7/8, undef]],
               [1.375,[ 2+1/16,     1, undef]],
               [1.5,  [   2.25,     1, undef]],
               [1.75, [  2+5/8,  1.25, undef]],
               [2,    [      3,   1.5, undef]]
            ],
            entry = struct_val(UTS_socket, diam),
            drive_size =  drive=="hex" ? [["drive_size",inch*entry[1]]] :
                          drive=="torx" ? [["drive_size",entry[2]]] : []
            )
            concat([["head","socket"],["head_size",inch*entry[0]], ["head_height", inch*diameter]],drive_size) :
         head=="pan" ? let (
           UTS_pan = [  // pan head for philips or slotted
                 // diam, slotted ht, philips height, philips
               ["#0", [0.116, 0.039, 0.044, 0]],
               ["#1", [0.142, 0.046, 0.053, 0]],
               ["#2", [0.167, 0.053, 0.063, 1]],
               ["#3", [0.193, 0.060, 0.071, 1]],
               ["#4", [0.219, 0.068, 0.080, 1]],
               ["#5", [0.245, 0.075, 0.089, 2]],
               ["#6", [0.270, 0.082, 0.097, 2]],
               ["#8", [0.322, 0.096, 0.115, 2]],
               ["#10",[0.373, 0.110, 0.133, 2]],
               ["#12",[0.425, 0.125, 0.151, 3]],
               [1/4,  [0.492, 0.144, 0.175, 3]],
               [5/16, [0.615, 0.178, 0.218, 4]],
               [3/8,  [0.740, 0.212, 0.261, 4]],
            ],
            htind = drive=="slot" ? 1 : 2,
            entry = struct_val(UTS_pan, diam),
            drive_size = drive=="philips" ? [["drive_size", entry[3]]] : [])
           concat([["head","pan"], ["head_size", inch*entry[0]], ["head_height", inch*entry[htind]]], drive_size) :
         head=="button" || head=="round" ? let(
            UTS_button = [    // button, hex or torx drive
                 //   head diam, height, philips, hex, torx
               ["#0", [0.114, 0.032, undef, 0.035,5]],
               ["#1", [0.139, 0.039, undef, 3/64, undef]],
               ["#2", [0.164, 0.046, undef, 3/64, 6]],
               ["#3", [0.188, 0.052, undef, 1/16, undef]],
               ["#4", [0.213, 0.059, undef, 1/16, 8]],
               ["#5", [0.238, 0.066, undef, 5/64, undef]],
               ["#6", [0.262, 0.073, undef, 5/64, 10]],
               ["#8", [0.312, 0.087, undef, 3/32, 15]],
               ["#10",[0.361, 0.101, undef, 1/8,  25]],
               ["#12",[0.413, 0.114, undef, 1/8,  undef]],   // also 0.410, .115, 9/64
               [1/4,  [0.437, 0.132, undef, 5/32, 27]],
               [5/16, [0.547, 0.166, undef, 3/16, 40]],
               [3/8,  [0.656, 0.199, undef, 7/32, 45]],
               [7/16, [0.750, 0.220, undef, 1/4,  undef]],
               [1/2,  [0.875, 0.265, undef, 5/16, undef]],
               [5/8,  [1.000, 0.331, undef, 3/8,  undef]],
               [3/4,  [1.1,   0.375, undef, 7/16, undef]],
             ],
             UTS_round = [   // slotted, philips
                  // head diam, head height, philips drive
               ["#0", [0.113, 0.053, 0]],
               ["#1", [0.138, 0.061, 0]],
               ["#2", [0.162, 0.069, 1]],
               ["#3", [0.187, 0.078, 1]],
               ["#4", [0.211, 0.086, 1]],
               ["#5", [0.236, 0.095, 2]],
               ["#6", [0.260, 0.103, 2]],
               ["#8", [0.309, 0.120, 2]],
               ["#10",[0.359, 0.137, 2]],
               ["#12",[0.408, 0.153, 3]],
               [1/4,  [0.472, 0.175, 3]],
               [5/16, [0.590, 0.216, 3]],
               [3/8,  [0.708, 0.256, 4]],
               [1/2,  [0.813, 0.355, undef]]
             ],
             entry = struct_val(head=="button" ? UTS_button : UTS_round, diam),
             drive_index = drive=="philips" ? 2 :
                           drive=="hex" ? 3 :
                           drive=="torx" ? 4 : undef,
             drive_size = is_def(drive_index) ? [["drive_size", (drive=="hex" ? inch : 1) * entry[drive_index]]] : []
             )
             concat([["head",head],["head_size",inch*entry[0]], ["head_height", inch*entry[1]]],drive_size) :
         substr(head,0,4)=="flat" ? let(
             small = head == "flat small" || head == "flat undercut" || (head=="flat" && (drive!="hex")),
             undercut = head=="flat undercut",
             UTS_flat_small = [  // for philips drive, slotted, and torx
                    //      diam, ph drive, torx drive, undercut height
                   ["#0", [ .112,  0, undef, 0.025]],
                   ["#1", [ .137,  0, undef, 0.031]],
                   ["#2", [ .162,  1, 6    , 0.036]],
                   ["#3", [ .187,  1, undef, 0.042]],
                   ["#4", [ .212,  1, 8    , 0.047]],
                   ["#5", [ .237,  2, undef, 0.053]],  // ph#1 for undercut
                   ["#6", [ .262,  2, 10   , 0.059]],
                   ["#8", [ .312,  2, 15   , 0.070]],
                   ["#10",[ .362,  2, 20   , 0.081]],
                   ["#12",[ .412,  3, undef, 0.092]],
                   [1/4,  [ .477,  3, 27   , 0.107]],
                   [5/16, [ .597,  4, 40   , 0.134]],
                   [3/8,  [ .717,  4, 40   , 0.161]],
                   [1/2,  [ .815,  4, undef, 0.156]]
             ],
             UTS_flat_large = [   // for hex drive, torx
                     // head diam, hex drive size, torx size
                   ["#0", [ 0.138, 1/32, 3]],
                   ["#1", [ 0.168, 3/64, undef]],
                   ["#2", [ 0.197, 3/64, 6]],
                   ["#3", [ 0.226, 1/16, undef]],
                   ["#4", [ 0.255, 1/16, 10]],
                   ["#5", [ 0.281, 5/64, 10 ]],
                   ["#6", [ 0.307, 5/64, 15]],
                   ["#8", [ 0.359, 3/32, 20]],
                   ["#10",[ 0.411,  1/8, 25]],
                   ["#12",[ 0.422,  1/8, undef]],
                   [1/4,  [ 0.531, 5/32, 30]],
                   [5/16, [ 0.656, 3/16, 40]],
                   [3/8,  [ 0.810, 7/32, undef]],
                   [7/16, [ 0.844,  1/4, undef]],
                   [1/2,  [ 0.938, 5/16, undef]],
                   [5/8,  [ 1.188,  3/8, undef]],
                   [3/4,  [ 1.438,  1/2, undef]],
                   [7/8,  [ 1.688, 9/16, undef]],
                   [1,    [ 1.938,  5/8, undef]],
                   [1.125,[ 2.188,  3/4, undef]],
                   [1.25, [ 2.438,  7/8, undef]],
                   [1.5,  [ 2.938,    1, undef]],
             ],
             entry = struct_val(small ? UTS_flat_small : UTS_flat_large, diam),
             driveind = small && drive=="philips" || !small && drive=="hex" ? 1 :
                        drive=="torx" ? 2 :
                        undef
             )
             concat([["head","flat"],["head_angle",82],["head_size",inch*entry[0]]],
                    is_def(driveind) ? [["drive_size", (drive=="hex"?inch:1)*entry[driveind]]] : [],
                    undercut ? [["head_height", inch*entry[3]]] : []
                   ) : []
    )
    concat([["system","UTS"],["diameter",inch*diameter],["pitch", pitch],["drive",drive]],
            head_data
          );

function _get_screw_info_metric(diam, pitch, head, thread, drive) =
 let(
   a=echo(metricsi=diam,pitch,head,thread,drive),
   pitch = is_num(thread) ? thread :
     is_def(pitch) ? pitch :
     let(
        tind=struct_val([["coarse",0],
                         ["fine",1],
                         ["extra fine",2],["extrafine",2],
			 ["super fine",3],["superfine",3]],
                         downcase(thread)),
                            // coarse  fine  xfine superfine
        ISO_thread = [
  	               [1  , [0.25,    0.2 ,   undef, undef,]],
           	       [1.2, [0.25,    0.2 ,   undef, undef,]],
           	       [1.4, [0.3 ,    0.2 ,   undef, undef,]],
           	       [1.6, [0.35,    0.2 ,   undef, undef,]],
           	       [1.7, [0.35,   undef,   undef, undef,]],
           	       [1.8, [0.35,    0.2 ,   undef, undef,]],
           	       [2  , [0.4 ,    0.25,   undef, undef,]],
           	       [2.2, [0.45,    0.25,   undef, undef,]],
           	       [2.3, [0.4 ,   undef,   undef, undef,]],
           	       [2.5, [0.45,    0.35,   undef, undef,]],
           	       [2.6, [0.45,   undef,   undef, undef,]],
           	       [3  , [0.5 ,    0.35,   undef, undef,]],
           	       [3.5, [0.6 ,    0.35,   undef, undef,]],
           	       [4  , [0.7 ,    0.5 ,   undef, undef,]],
           	       [5  , [0.8 ,    0.5 ,   undef, undef,]],
           	       [6  , [1   ,    0.75,   undef, undef,]],
           	       [7  , [1   ,    0.75,   undef, undef,]],
           	       [8  , [1.25,    1   ,    0.75, undef,]],
           	       [9  , [1.25,    1   ,    0.75, undef,]],
           	       [10 , [1.5 ,    1.25,    1   ,  0.75,]],
           	       [11 , [1.5 ,    1   ,    0.75, undef,]],
           	       [12 , [1.75,    1.5 ,    1.25,  1,   ]],
           	       [14 , [2   ,    1.5 ,    1.25,  1,   ]],
           	       [16 , [2   ,    1.5 ,    1   , undef,]],
           	       [18 , [2.5 ,    2   ,    1.5 ,  1,   ]],
           	       [20 , [2.5 ,    2   ,    1.5 ,  1,   ]],
           	       [22 , [2.5 ,    2   ,    1.5 ,  1,]],
           	       [24 , [3   ,    2   ,    1.5 ,  1,]],
           	       [27 , [3   ,    2   ,    1.5 ,  1,]],
           	       [30 , [3.5 ,    3   ,    2   ,  1.5,]],
           	       [33 , [3.5 ,    3   ,    2   ,  1.5,]],
           	       [36 , [4   ,    3   ,    2   ,  1.5,]],
           	       [39 , [4   ,    3   ,    2   ,  1.5,]],
           	       [42 , [4.5 ,    4   ,    3   ,  2,]],
           	       [45 , [4.5 ,    4   ,    3   ,  2,]],
           	       [48 , [5   ,    4   ,    3   ,  2,]],
           	       [52 , [5   ,    4   ,    3   ,  2,]],
           	       [56 , [5.5 ,    4   ,    3   ,  2,]],
           	       [60 , [5.5 ,    4   ,    3   ,  2,]],
           	       [64 , [6   ,    4   ,    3   ,  2,]],
           	       [68 , [6   ,    4   ,    3   ,  2,]],
           	       [72 , [6   ,    4   ,    3   ,  2,]],
           	       [80 , [6   ,    4   ,    3   ,  2,]],
           	       [90 , [6   ,    4   ,    3   ,  2,]],
           	       [100, [6   ,    4   ,    3   ,  2,]],
         ]
       )
      struct_val(ISO_thread, diam)[tind],
      head_data =
         head=="none" || is_undef(head) ? [["head","none"]] :
         head=="hex" ? let(
            metric_hex = [
              // flat to flat width, height
	      [5, [8, 3.5]],
	      [6, [10,4]],
	      [8, [13, 5.3]],
	      [10, [17, 6.4]],
	      [12, [19, 7.5]],
	      [14, [22, 8.8]],
	      [16, [24, 10]],
	      [18, [27,11.5]],
	      [20, [30, 12.5]],
	      [24, [36, 15]],
	      [30, [46, 18.7]],
            ],
            entry = struct_val(metric_hex, diam)
           )
           [["head", "hex"], ["head_size", entry[0]], ["head_height", entry[1]]] :
         head=="socket" ? let(
            metric_socket = [    // height = screw diameter
                      //diam, hex
                [1.4, [2.5, 1.3]],
		[1.6, [3, 1.5]],
		[2,   [3.8, 1.5]],
		[2.5, [4.5, 2]],
		[2.6, [5, 2]],
		[3,   [5.5, 2.5]],
		[3.5, [6.2, 2.5]],
		[4,   [7, 3]],
		[5,   [8.5, 4]],
		[6,   [10, 5]],
		[7,   [12, 6]],
		[8,   [13, 6]],
		[10,  [16, 8]],
		[12,  [18, 10]],
		[14,  [21, 12]],
		[16,  [24, 14]],
		[18,  [27, 14]],
		[20,  [30, 17]],
		[22,  [33, 17]],
		[24,  [36, 19]],
		[27,  [40, 19]],
		[30,  [45, 22]],
		[33,  [50, 24]],
		[36,  [54, 27]],
		[42,  [63, 32]],
		[48,  [72, 36]],
            ],
            entry = struct_val(metric_socket, diam),
            drive_size =  drive=="hex" ? [["drive_size",entry[1]]] : []
            )
            concat([["head","socket"],["head_size",entry[0]], ["head_height", diam]],drive_size) :
         head=="pan" ? let (
           metric_pan = [  // pan head for philips or slotted
                 // diam, slotted ht, philips height, philips
      		 [1.6,[3.2, undef, 1.3,	0]],
		 [2,  [4,   1.2, 1.6,	1]],
		 [2.5,[5,   1.5, 2,  	1]],
		 [3,  [6,   1.8, 2.4,	1]],
		 [3.5,[8,   undef, 3.1,	2]],
		 [5,  [10,  3, 3.8,	2]],
		 [6,  [12,  3.6, 4.6,	3]],
		 [8,  [16,  8, 6,  	4]],
		 [10, [20,  undef, 7.5,	4]],
            ],
            htind = drive=="slot" ? 1 : 2,
            entry = struct_val(metric_pan, diam),
            drive_size = drive=="philips" ? [["drive_size", entry[3]]] : [])
           concat([["head","pan"], ["head_size", entry[0]], ["head_height", entry[htind]]], drive_size) :
         head=="button" || head=="cheese" ? let(
            metric_button = [    // button, hex drive
                 //   head diam, height, hex, philips
                 [1.6, [2.9, 0.8, 0.9, undef]],
		 [2, [3.5, 1.3, 1.3, undef]],
		 [2.2, [3.8, 0.9, 1.3, undef]],
		 [2.5, [4.6, 1.5, 1.5, undef]],
		 [3, [5.7, 1.65,2, undef]],
		 [3.5, [5.7, 1.65,2, undef]],
		 [4, [7.6, 2.2, 2.5, undef]],
		 [5, [9.5, 2.75,3, undef]],
		 [6, [10.5, 3.3,4, undef]],
		 [8, [14, 4.4, 5, undef]],
		 [10, [17.5, 5.5, 6, undef]],
		 [12, [21, 6.6, 8, undef]],
		 [16, [28, 8.8, 10, undef]],
             ],
             metric_cheese = [   // slotted, philips
                  // head diam, head height, philips drive
		[1,[2,0.7, undef, undef]],
		[1.2, [2.3, 0.8,undef,undef]],
		[1.4, [2.6, 0.9,undef,undef]],
		[1.6, [3, 1,undef,undef]],
		[2, [3.8, 1.3, undef,1]],
		[2.5, [4.5, 1.6, undef,1]],
		[3, [5.5, 2, undef, 2]],
		[3.5, [6, 2.4, undef, undef]],
		[4, [7, 2.6, undef, 2]],
		[5, [8.5, 3.3, undef, 2]],
		[6, [10, 3.9, undef, 3]],
		[8, [13, 5,undef, undef]]
             ],
             entry = struct_val(head=="button" ? metric_button : metric_cheese, diam),
             drive_index = drive=="philips" ? 3 :
                           drive=="hex" ? 2 : undef,
             drive_size = is_def(drive_index) ? [["drive_size", entry[drive_index]]] : []
             )
             concat([["head",head],["head_size",entry[0]], ["head_height", entry[1]]],drive_size) :
         substr(head,0,4)=="flat" ? let(
             small = head == "flat small" || (head=="flat" && (drive!="hex")),
             metric_flat_large = [ // for hex drive
                  [2, [4, 1.3]],
		  [2.5, [5, 1.5]],
		  [3, [6, 2]],
		  [4, [8, 2.5]],
		  [5, [10, 3]],
		  [6, [12, 4]],
		  [8, [16, 5]],
		  [10, [20, 6]],
		  [12, [24, 8]],
		  [14, [27, 10]],
		  [16, [30, 10]],
		  [18, [33, 12]],
		  [20, [36, 12]],
             ],
             metric_flat_small = [ // for philips, slotted
                  [1.6, [3, 0]],
                  [2,   [3.8, 0]],
	       	  [2.5, [4.7, 1]],
	       	  [2.6, [4.7, 1]],
	       	  [3,   [5.6, 1]],
	       	  [3.5, [7.5, 1]],
	       	  [4,   [7.5, 2]],
	       	  [5,   [9.2, 2]],
	       	  [6,   [11, 3]],
	       	  [8,   [14.5, 4]],
	       	  [10,  [18, 4]],
             ],
             entry = struct_val(small ? metric_flat_small : metric_flat_large, diam),
             driveind = small && drive=="philips" || !small && drive=="hex" ? 1 : undef
             )
             concat([["head","flat"],["head_angle",90],["head_size",entry[0]]],
                    is_def(driveind) ? [["drive_size", entry[driveind]]] : []
                   ) : []
    )
    concat([["system","ISO"],["diameter",diam],["pitch", pitch],["drive",drive]],
            head_data
          );

// Module: screw_head()
// Usage:
//    screw_head(screw_info, [details])
// Description:
//    Draws the screw head described by the data structure `screw_info`, which
//    should have the fields produced by `get_screw_info()`.  See that function for
//    details on the fields.  Standard orientation is with the head centered at (0,0)
//    and oriented in the +z direction.  Flat heads project down, so they are located below the xy plane.
//    Others project up from the xy plane.
module screw_head(screw_info,details=false) {
   head = struct_val(screw_info, "head");
   head_size = struct_val(screw_info, "head_size");
   head_height = struct_val(screw_info, "head_height");
   if (head=="flat") {
     angle = struct_val(screw_info, "head_angle")/2;
     full_height = head_size/2/tan(angle);
     height = is_def(head_height) ? head_height : full_height;
     d2 = head_size*(1-height/full_height);
     //down(height)
     zflip()
       cyl(d1=head_size, d2=d2, l=height, anchor=BOTTOM);
   }
   if (in_list(head,["round","pan","button","fillister","cheese"])) {
     base = head=="fillister" ? 0.75*head_height :
            head=="pan" ? .5 * head_height :
            head=="cheese" ? .7 * head_height :
            0.1 * head_height;   // round and button
     head_size2 = head=="cheese" ? (24/26) * head_size : head_size;
     cyl(l=base, d1=head_size, d2=head_size2,anchor=BOTTOM, $fn=32)
       attach(TOP)
         rotate_extrude($fn=32)
           intersection(){
             arc(points=[[-head_size2/2,0], [0,-base+head_height * (head=="button"?4/3:1)], [head_size2/2,0]]);
             square([head_size2, head_height-base]);
             }
   }
   if (head=="socket")
     cyl(l=head_height, d=head_size, anchor=BOTTOM);
   if (head=="hex")
     intersection(){
       linear_extrude(height=head_height) hexagon(id=head_size);
       if (details)
         down(.01)cyl(l=head_height+.02,d=2*head_size/sqrt(3), chamfer=head_size*(1/sqrt(3)-1/2), anchor=BOTTOM);
     }
}

// Module: screw()
// Usage:
//   screw([name],[head],[thread],[drive],[drive_size], [length], [shank], [oversize], [tolerance], [spec], [details])
// Description:
//   Create a screw
//
//   Most of these parameters are described in the entry for `get_screw_info()`.
//
//   The tolerance determines the actual thread sizing based on the
//   nominal size.  For UTS threads it is either "1A", "2A" or "3A", in
//   order of increasing tightness.  The default tolerance is "2A", which
//   is the general standard for manufactured bolts.  For ISO the tolerance
//   has the form of a number and letter.  The letter specifies the "fundamental deviation", also called the "tolerance position", the gap
//   from the nominal size, and must be "e", "f", "g", or "h", where "e" is
//   the loosest and "h" means no gap.  The number specifies the allowed
//   range (variability) of the thread heights.  It must be a value from
//   3-9 for crest diameter and one of 4, 6, or 8 for pitch diameter.  A
//   tolerance "6g" specifies both pitch and crest diameter to be the same,
//   but they can be different, with a tolerance like "5g6g" specifies a pitch diameter tolerance of "5g" and a crest diameter tolerance of "6g".
//   The default ISO tolerance is "6g".
// Arguments:
//   name = screw specification, e.g. "M5x1" or "#8-32"
//   head = head type (see list above).  Default: none
//   thread = thread type or specification.  Default: "coarse"
//   drive = drive type.  Default: none
//   drive_size = size of drive recess to override computed value
//   oversize = amount to increase screw diameter for clearance holes.  Default: 0
//   spec = screw specification from `get_screw_info()`.  If you specify this you can omit all the preceeding parameters.
//   length = length of screw (in mm)
//   shank = length of unthreaded portion of screw (in mm).  Default: 0
//   details = toggle some details in rendering.  Default: false
//   tolerance = screw tolerance.  Determines actual screw thread geometry based on nominal sizing.  Default is "2A" for UTS and "6g" for ISO.
module screw(name, head, thread="coarse", drive, drive_size, oversize=0, spec, length, shank=0, tolerance=undef, details=true, anchor="base",spin=0, orient=DOWN)
{
   spec = is_def(spec) ? spec : get_screw_info(name, head, thread, drive, drive_size, oversize);
   head = struct_val(spec,"head");
   pitch = struct_val(spec, "pitch");
   diameter = struct_val(spec, "diameter");
   headless = head=="none" || head==undef;
   eps = headless || substr(head,len=4)=="flat" ? 0 : 0.01;
   length = first_defined([length,struct_val(spec,"length")]) + eps;
   assert(length>0, "Must specify positive length");
   sides = max(12, segs(diameter/2));
   unthreaded = is_undef(pitch) || pitch==0 ? length : shank;
   threaded = length - unthreaded;
   echo(t=threaded,length,unthreaded);
   anchors = [
		anchorpt("base",   [0,0,0]),
              ];
   orient_and_anchor([diameter, diameter, length], orient, anchor, spin=spin, geometry="cylinder", anchors=anchors, chain=true) {
     zflip()
     difference(){
       union(){
         screw_head(spec,details);
         up(eps){
           if (unthreaded>0){
              cyl(d=diameter, h=unthreaded+eps+(threaded>0?0.01:0), anchor=TOP, $fn=sides);
             }
           if (threaded>0)
             intersection(){
               down(unthreaded)
                 rod(spec, length=threaded+eps, tolerance=tolerance, $fn=sides, anchor=TOP );
               if (details)
                 up(.01)cyl(d=diameter, l=length+.02+eps, chamfer1 = pitch/2, chamfer2 = headless ? pitch/2 : -pitch/2, anchor=TOP, $fn=sides);
            }
         }
       }
       driver(spec);
     }
     children();
   }
}

module driver(spec)
{
  drive = struct_val(spec,"drive");
  drive_size = struct_val(spec,"drive_size");
  if (is_def(drive) && is_num(drive_size)) {
  head = struct_val(spec,"head");
  diameter = struct_val(spec,"diameter");
  head_height = is_def(struct_val(spec,"head_height")) ? struct_val(spec,"head_height") :
                head=="socket" ? diameter :
                substr(head,len=4)=="flat" ? .5*struct_val(spec,"head_size") :
                head=="none" ? .7*diameter : undef;
  echo(head_height = head_height, head=head, substr(head,len=4));
  echo(down=substr("head",len=4)=="flat" ? head_height : 0);
  down(substr(head,len=4)=="flat" || head=="none" ? head_height : 0) {
    if (drive=="philips") down(head_height/6) phillips_drive(size=drive_size, shaft=diameter);
    if (drive=="torx") up(1) torx_drive(size=drive_size, l=head_height+0.1, center=false);
    if (drive=="hex") up(1) linear_extrude(height=head_height) hexagon(id=drive_size);
  }
  }
}

function ISO_thread_tolerance(diameter, pitch, internal=false, tolerance=undef) =
  let(
    P = pitch,
    H = P*sqrt(3)/2,
    tolerance = first_defined([tolerance, internal?"6H":"6g"]),
    fda=echo(P=P, H=H, tolerance=tolerance, "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"),

    pdiam = diameter - 2*3/8*H,          // nominal pitch diameter
    mindiam = diameter - 2*5/8*H,        // nominal minimum diameter

    EI = [   // Fundamental deviations for nut thread
          ["G", 15+11*P],
          ["H", 0],            // Standard practice
         ],

    es = [    // Fundamental deviations for bolt thread
          ["e", -(50+11*P)],   // Exceptions if P<=0.45mm
          ["f", -(30+11*P)],
          ["g", -(15+11*P)],   // Standard practice
          ["h", 0]             // Standard practice for tight fit
         ],

    T_d6 = 180*pow(P,2/3)-3.15/sqrt(P),
    T_d = [  // Crest diameter tolerance for major diameter of bolt thread
           [4, 0.63*T_d6],
           [6, T_d6],
           [8, 1.6*T_d6]
          ],

    T_D1_6 = 0.2 <= P && P <= 0.8 ? 433*P - 190*pow(P,1.22) :
             P > .8 ? 230 * pow(P,0.7) : undef,
    T_D1 = [ // Crest diameter tolerance for minor diameter of nut thread
             [4, 0.63*T_D1_6],
             [5, 0.8*T_D1_6],
             [6, T_D1_6],
             [7, 1.25*T_D1_6],
             [8, 1.6*T_D1_6]
           ],

    rangepts = [0.99, 1.4, 2.8, 5.6, 11.2, 22.4, 45, 90, 180, 300],
    d_ind = floor(lookup(diameter,zip(rangepts,list_range(len(rangepts))))),
    avgd = sqrt(rangepts[d_ind]* rangepts[d_ind+1]),

    T_d2_6 = 90*pow(P, 0.4)*pow(avgd,0.1),
    T_d2 = [ // Pitch diameter tolerance for bolt thread
             [3, 0.5*T_d2_6],
             [4, 0.63*T_d2_6],
             [5, 0.8*T_d2_6],
             [6, T_d2_6],
             [7, 1.25*T_d2_6],
             [8, 1.6*T_d2_6],
             [9, 2*T_d2_6],
           ],

    T_D2 = [  // Tolerance for pitch diameter of nut thread
              [4, 0.85*T_d2_6],
              [5, 1.06*T_d2_6],
              [6, 1.32*T_d2_6],
              [7, 1.7*T_d2_6],
              [8, 2.12*T_d2_6]
           ],

    internal = is_def(internal) ? internal : tolerance[1] != downcase(tolerance[1]),

    internalok = !internal || (
                               len(tolerance)==2 && str_find("GH",tolerance[1])!=undef && str_find("45678",tolerance[0])),
    tol_str = str(tolerance,tolerance),
    externalok = internal || (
                              (len(tolerance)==2 || len(tolerance)==4)
                                                          && str_find("efgh", tol_str[1])!=undef
                                                          && str_find("efgh", tol_str[3])!=undef
                                                          && str_find("3456789", tol_str[0]) != undef
                                                          && str_find("468", tol_str[2]) !=undef)
  )
  assert(internalok,str("Invalid internal thread tolerance, ",tolerance,".  Must have form <digit><letter>"))
  assert(externalok,str("invalid external thread tolerance, ",tolerance,".  Must have form <digit><letter> or <digit><letter><digit><letter>"))
  let(
    tol_num_pitch = str_num(tol_str[0]),
    tol_num_crest = str_num(tol_str[2]),
    tol_letter = tol_str[1]
  )
  assert(tol_letter==tol_str[3],str("Invalid tolerance, ",tolerance,".  Cannot mix different letters"))
  internal ?
    let(  // Nut case
      a=echo("nut", tol_letter, tol_num_pitch, tol_num_crest),
      fdev = struct_val(EI,tol_letter)/1000,
      Tdval = struct_val(T_D1, tol_num_crest)/1000,
      df=     echo(T_D1=T_D1),
      Td2val = struct_val(T_D2, tol_num_pitch)/1000,
      fe=   echo("nut",P,fdev=fdev, Tdval=Tdval, Td2val=Td2val),
      bot=[diameter+fdev, diameter+fdev+Td2val+H/6],
      xdiam = [mindiam+fdev,mindiam+fdev+Tdval],
      pitchdiam = [pdiam + fdev, pdiam+fdev+Td2val]
    )
    [["pitch",P],["d_minor",xdiam], ["d_pitch",pitchdiam], ["d_major",bot],["basic",[mindiam,pdiam,d]]]
  :
    let( // Bolt case
      a=echo("bolt"),
      fdev = struct_val(es,tol_letter)/1000,
      Tdval = struct_val(T_d, tol_num_crest)/1000,
      Td2val = struct_val(T_d2, tol_num_pitch)/1000,
      mintrunc = P/8,
      d1 = diameter-5*H/4,
      maxtrunc = H/4 - mintrunc * (1-cos(60-acos(1-Td2val/4/mintrunc)))+Td2val/2,
      cc=echo("bolt",P,fdev=fdev, Tdval=Tdval, Td2val=Td2val),
      bot = [diameter-2*H+2*mintrunc+fdev, diameter-2*H+2*maxtrunc+fdev],
      xdiam = [diameter+fdev,diameter+fdev-Tdval],
      pitchdiam = [pdiam + fdev, pdiam+fdev-Td2val]
    )
    [["pitch",P],["d_major",xdiam], ["d_pitch",pitchdiam], ["d_minor",bot],["basic",[mindiam,pdiam,diameter]]];

function UTS_thread_tolerance(diam, pitch, internal=false, tolerance=undef) =
  let(
    inch = 25.4,
    d = diam/inch,   // diameter in inches
    P = pitch/inch,  // pitch in inches
    H = P*sqrt(3)/2,
    tolerance = first_defined([tolerance, internal?"2B":"2A"]),
    tolOK = in_list(tolerance, ["1A","1B","2A","2B","3A","3B"]),
    internal = tolerance[1]=="B"
  )
  assert(tolOK,str("Tolerance was ",tolerance,". Must be one of 1A, 2A, 3A, 1B, 2B, 3B"))
  let(
    LE = 9*P,   // length of engagement.  Is this right?
    pitchtol_2A = 0.0015*pow(d,1/3) + 0.0015*sqrt(LE) + 0.015*pow(P,2/3),
    pitchtol_table = [
                 ["1A", 1.500*pitchtol_2A],
                 ["2A",       pitchtol_2A],
                 ["3A", 0.750*pitchtol_2A],
                 ["1B", 1.950*pitchtol_2A],
                 ["2B", 1.300*pitchtol_2A],
                 ["3B", 0.975*pitchtol_2A]
               ],
     pitchtol = struct_val(pitchtol_table, tolerance),
     allowance = tolerance=="1A" || tolerance=="2A" ? 0.3 * pitchtol_2A : 0,
     majortol = tolerance == "1A" ? 0.090*pow(P,2/3) :
                tolerance == "2A" || tolerance == "2B" ? 0.060*pow(P,2/3) :
                pitchtol+pitch/4/sqrt(3),    // Internal case
     minortol = tolerance=="1B" || tolerance=="2B" ?
                    (
                      d < 0.25 ? constrain(0.05*pow(P,2/3)+0.03*P/d - 0.002, 0.25*P-0.4*P*P, 0.394*P)
                               : (P > 0.25 ? 0.15*P : 0.25*P-0.4*P*P)
                    ) :
                tolerance=="3B" ? constrain(0.05*pow(P,2/3)+0.03*P/d - 0.002, P<1/13 ? 0.12*P : 0.23*P-1.5*P*P, 0.394*P)
                     :0, // not used for external threads
f=echo(allowance=allowance),
g=echo(pta2 = pitchtol_2A),
ff=echo(minortol=minortol, pitchtol=pitchtol, majortol=majortol),
     basic_minordiam = d - 5/4*H,
     basic_pitchdiam = d - 3/4*H,
     majordiam = internal ? [d,d] :          // A little confused here, paragraph 8.3.2
                          [d-allowance-majortol, d-allowance],
     pitchdiam = internal ? [basic_pitchdiam, basic_pitchdiam + pitchtol]
                          : [majordiam[1] - 3/4*H-pitchtol, majordiam[1]-3/4*H],
     minordiam = internal ? [basic_minordiam, basic_minordiam + minortol]
                          : [pitchdiam[0] - 3/4*H, basic_minordiam - allowance - H/8]   // the -H/8 is for the UNR case, 0 for UN case
    )
    [["pitch",P*inch],["d_major",majordiam*inch], ["d_pitch", pitchdiam*inch], ["d_minor",minordiam*inch],
     ["basic", inch*[basic_minordiam, basic_pitchdiam, d]]];

function _exact_thread_tolerance(d,P) =
   let(
       H = P*sqrt(3)/2,
       basic_minordiam = d - 5/4*H,
       basic_pitchdiam = d - 3/4*H
      )
    [["pitch", P], ["d_major", d], ["d_pitch", basic_pitchdiam], ["d_minor", basic_minordiam],
     ["basic", [basic_minordiam, basic_pitchdiam, d]]];

// Function: thread_specification()
// Usage:
//   thread_specification(screw_spec, [tolerance], [internal])
// Description:
//   Determines actual thread geometry for a given screw with specified tolerance.  If tolerance is omitted the default is used.  If tolerance
//   is "none" or 0 then return the nominal thread geometry.
//
//   The return value is a structure with the following fields:
//   - pitch: the thread pitch
//   - d_major: major diameter range
//   - d_pitch: pitch diameter range
//   - d_minor: minor diameter range
//   - basic: vector `[minor, pitch, major]` of the nominal or "basic" diameters for the threads
function thread_specification(screw_spec, internal=false, tolerance=undef) =
  let( diam = struct_val(screw_spec, "diameter"),
       pitch = struct_val(screw_spec, "pitch")
     ,k=
  tolerance == 0 || tolerance=="none" ? _exact_thread_tolerance(diam, pitch) :
  struct_val(screw_spec,"system") == "ISO" ? ISO_thread_tolerance(diam, pitch, internal, tolerance) :
  struct_val(screw_spec,"system") == "UTS" ? UTS_thread_tolerance(diam, pitch, internal, tolerance) :
  assert(false,"Unknown screw system ",struct_val(screw_spec,"system")),
    fff=echo(k))
    k;

function thread_profile(thread) =
  let(
     pitch = struct_val(thread,"pitch"),
     basicrad = struct_val(thread,"basic")/2,
     meanpitchrad = mean(struct_val(thread,"d_pitch"))/2,
     meanminorrad = mean(struct_val(thread,"d_minor"))/2,
     meanmajorrad = mean(struct_val(thread,"d_major"))/2,
     depth = (meanmajorrad-meanminorrad)/pitch,
     crestwidth = (pitch/2 - 2*(meanmajorrad-meanpitchrad)/sqrt(3))/pitch

  )
    [
     [-1/2,-depth],
     [depth/sqrt(3)-1/2,0],
     [depth/sqrt(3)+crestwidth-1/2, 0],
     [crestwidth + 2*depth/sqrt(3)-1/2,-depth]
    ];

module rod(spec, length, tolerance, orient=UP, spin=0, anchor=CENTER)
{
      threadspec = thread_specification(spec, internal=false, tolerance=tolerance);
      trapezoidal_threaded_rod( d=mean(struct_val(threadspec, "d_major")),
                                l=length,
                                pitch = struct_val(threadspec, "pitch"),
                                profile = thread_profile(threadspec),
                                bevel=false, orient=orient, anchor=anchor, spin=spin);
}

module nut(name, thread="coarse", oversize=0, spec, diameter, thickness, tolerance=undef, details=true, anchor=BOTTOM,spin=0, orient=UP)
{
   spec = is_def(spec) ? spec : get_screw_info(name, thread=thread, oversize=oversize);
   threadspec = thread_specification(spec, internal=true, tolerance=tolerance);
   trapezoidal_threaded_nut(
		od=diameter, id=mean(struct_val(threadspec, "d_major")), h=thickness,
		pitch=struct_val(threadspec, "pitch"),
		profile=thread_profile(threadspec),
		bevel=false,anchor=anchor,spin=spin,orient=orient);
}

////////////////////////////////////////////////////////////////////////////////////////////////////

inch=25.4;

xdistribute(spacing=8){
 screw("#6", thread="none",length=12);
 screw("#6-32", head="pan", drive="ph2",length=12);
 screw("#6-32", head="pan", drive="slot",length=12);
 screw("#6-32", head="round",length=12);
 screw("#6", thread="fine", head="button",length=12, drive="torx");
 screw("#6-32", head="flat small",length=12,drive="torx");
 screw("#6-32,1/2", head="flat large",length=12);
 screw("#6-32", head="flat undercut",length=12);
 screw("#6-32", head="socket",length=12);
 screw("#6-32", drive="hex", drive_size=1.5, length=12);
}

fwd(10)xdistribute(spacing=8){
 screw("M3", head="flat small",length=12);
 screw("M3", head="button",length=12);
 screw("M3", head="pan", drive="philips",length=12);
 screw("M3", head="pan", drive="slot",length=12);
 screw("M3", head="flat large",length=12);
 screw("M3", head="flat", drive="hex",length=12);
 screw("M3", head="socket",length=12);
 screw("M5", head="hex", length=12);
}

/*
screw("1/4-20,1/2", head="hex",orient=UP,anchor="base");
right(15) nut("1/4-20", thickness=8, diameter=1/2*inch);
*/

The phillips recess code is pretty wrong.  I have access to recess diameter, the width of the cross, and the depth of the recess.  We need a working recess maker.
anchor=CENTER is broken.  No clue why.
No slot for flat heads yet.
// Things to do
//
// Recess size is confusing.  Does it really depend on the screw size and not just the recess type?
// The code that puts a recess into a flat head seems kind of bogus with regards to recess depth.
// Do we need hex drive size table for headless screws?

include<BOSL2/std.scad>
include<BOSL2/strings.scad>
include<BOSL2/structs.scad>
include<BOSL2/threading.scad>
include<BOSL2/phillips_drive.scad>
include<BOSL2/torx_drive.scad>

/*
http://mdmetric.com/thddata.htm#idx

Seems to show JIS has same nominal thread as others
https://www.nbk1560.com/~/media/Images/en/Product%20Site/en_technical/11_ISO%20General%20Purpose%20Metric%20Screw%20Threads.ashx?la=en

torx flat head
1.6, [3.67, 5
2, [3.8, 6
2.5, [5.5, 8
3, [6.72, 10
4, [8.96, 20
5, [10.4, 25
6, [12.6, 30

*/

function parse_screw_name(name) =
    let( commasplit = str_split(name,","),
         length = str_num(commasplit[1]),
         xdash = str_split(commasplit[0], "-x"),
         type = xdash[0],
         thread = str_float(xdash[1])
    )
    type[0] == "M" || type[0] == "m" ? ["metric", str_float(substr(type,1)), thread, length] :
    let(
        diam = type[0] == "#" ? type :
               suffix(type,2)=="''" ? str_float(substr(type,0,len(type)-2)) :
               let(val=str_num(type))
               val == floor(val) && val>=0 && val<=12 ? str("#",type) : val
        )
    ["english", diam, thread, 25.4*length];

// drive can be "hex", "philips", "slot", "torx", or "none"
// or you can specify "ph0" up to "ph4" for philips and "t20" for torx 20
function parse_drive(drive=undef, drive_size=undef) =
    is_undef(drive) ? ["none",undef] :
    let(drive = downcase(drive))
    in_list(drive,["hex","philips", "slot", "torx", "philips", "none"]) ? [drive, drive_size] :
    drive[0]=="t" ? ["torx", str_int(substr(drive,1))] :
    substr(drive,0,2)=="ph" ? ["philips", str_int(substr(drive,2))] :
    assert(str("Unknown screw drive type ",drive));

// Function: screw_info()
// Usage:
//   info = screw_info(name, [head], [thread], [drive], [drive_size], [oversize])
// Description:
//   Look up screw characteristics for the specified screw type.
//
//   For metric (ISO) `name` is M<size>x<pitch>,<length>, e.g. `"M6x1,10"` specifies a 6mm diameter screw with a thread pitch of 1mm and length of 10mm.
//   You can omit the pitch or length, e.g. `"M6x1"`, or `"M6,10"`, or just `"M6"`.
//
//   For English (UTS) name is <size>-<threadcount>,<length>, e.g. `"#8-32,1/2"`, or `"1/4-20,1"`.  Units are in inches, including the length.
//   Size can be a number from 0 to 12 with or without a leading '#' to specify a screw gauge size, or any other value to specify
//   a diameter in inches, either as a float or a fraction, so `"0.5-13"` and `"1/2-13"` are equivalent.  To force interpretation of the value
//   as inches add `''` to the end, e.g. `"1''-4"` is a one inch screw and `"1-80"` is a very small 1-gauge screw.  The pitch is specified
//   using a thread count, the number of threads per inch.  The length is in inches.
//
//   If you omit the pitch then a standard screw pitch will be supplied from lookup tables for the screw diameter you have chosen.
//   For each screw diameter, multiple standard pitches are possible.
//   For the UTS system these the availble thread types are:
//   - "coarse" or "UNC"
//   - "fine" or "UNF"
//   - "extra fine", "extrafine" or "UNEF".
//   The ISO system defines a coarse threading and three different fine threadings but does not give different names to the fine threadings.
//   The thread options for ISO are:
//   - "coarse"
//   - "fine"
//   - "extra fine" or "extrafine"
//   - "super fine" or "superfine"
//   The default pitch selection is "coarse".  Note that this selection is case independent.  Set
//   `thread` to one of these values to choose a different pitch.  Note that not every pitch category is defined at every
//   diameter.  You can also specify the thread pitch directly, for example you could set `thread=2` which would
//   produce threads with a pitch of 2mm.  The final option is to specify `thread="none"` to produce an unthreaded
//   screw either to simplify the model or to use for cutting out screw holes.  Setting the pitch to zero also produces
//   an unthreaded screw.  If you specify a numeric thread value it will override any value given in `name`.
//
//   The `head` parameter specifies the type of head the screw will have.  Options for the head are
//   - "flat"
//   - "flat small"
//   - "flat large"
//   - "flat undercut"
//   - "round"
//   - "pan"
//   - "socket"
//   - "hex"
//   - "button"
//   - "cheese"
//   - "fillister"
//   - "none"
//   Note that different sized flat heads exist for the same screw type.  Sometimes this depends on the type of recess.  If you specify "flat" then
//   the size will be chosen appropriately for the recess you specify.  The default is "none".
//
//   The `drive` parameter specifies the recess type.  Options for the drive are
//   - "none"
//   - "philips"
//   - "slot"
//   - "torx"
//   - "hex"
//   - "ph0", up to "ph4" for philips of the specified size
//   - "t<size>" for torx at a specified size, e.g. "t20"
//   The default drive is "none"
//
//   The drive size is specified appropriately to the drive type: drive number for philips or torx, and allen width in mm or inches (as appropriate) for hex.
//   Drive size is determined automatically from the screw size, but by passing the `drive_size` parameter
//   you can override the default, or in cases where no default exists you can specify it.

//   This is intended for generating clearance holes, not for dealing with printer inaccuracy.  Does not affect length, thread pitch or head height.

//
//   The output is a structure with the following fields:
//   - system: either "UTS" or "ISO"
//   - diameter: the nominal diameter of the screw shaft in mm
//   - pitch: the thread pitch in mm
//   - head: the type of head (a string from the list above)
//   - head_size: size of the head in mm
//   - head_angle: countersink angle for flat heads
//   - head_height: height of the head (when needed to specify the head)
//   - drive: the drive type ("philips", "torx", "slot", "hex", "none")
//   - drivesize: the drive size, either a drive number (philips or torx) or a dimension in mm
//   - length: length of the screw in mm measured in the customary fashion: for flat head screws the total length and for other screws, the length from the bottom of the head to the screw tip.
// Arguments:
//   name = screw specification, e.g. "M5x1" or "#8-32"
//   head = head type (see list above).  Default: none
//   thread = thread type or specification.  Default: "coarse"
//   drive = drive type.  Default: none
//   drive_size = size of drive recess to override computed value
//   oversize = amount to increase screw diameter for clearance holes.  Default: 0
function screw_info(name, head, thread="coarse", drive, drive_size=undef, oversize=0) =
  let(type=parse_screw_name(name),
      drive_info = parse_drive(drive, drive_size),
      drive=drive_info[0],
    screwdata =
      type[0] == "english" ? _screw_info_english(type[1],type[2], head, thread, drive) :
      type[0] == "metric" ? _screw_info_metric(type[1], type[2], head, thread, drive) :
      [],
    over_ride = concat(
                        is_def(type[3]) ? ["length",type[3]] : [],
                        is_def(drive_info[1]) ? ["drive_size", drive_info[1]] : [],
                        ["diameter", oversize+struct_val(screwdata,"diameter"),
                         "head_size", oversize+struct_val(screwdata,"head_size")]
                      )
  )
  struct_set(screwdata, over_ride);

function _screw_info_english(diam, threadcount, head, thread, drive) =
 let(
   inch = 25.4,
   diameter = is_string(diam) ? str_int(substr(diam,1))*0.013 +0.06 :
              diam,
   pitch =
     is_def(threadcount) ? inch/threadcount :
     is_num(thread) ? thread :
     let(
        tind=struct_val([["coarse",0],["unc",0],
                         ["fine",1],["unf",1],
                         ["extra fine",2],["extrafine",2],["unef",2]],
                         downcase(thread)),
                 // coarse  fine  xfine
                 // UNC     UNF   UNEF
        UTS_thread = [
            ["#0", [undef,    80, undef]],
            ["#1", [   64,    72, undef]],
            ["#2", [   56,    64, undef]],
            ["#3", [   48,    56, undef]],
            ["#4", [   40,    48, undef]],
            ["#5", [   40,    44, undef]],
            ["#6", [   32,    40, undef]],
            ["#8", [   32,    36, undef]],
            ["#10",[   24,    32, undef]],
            ["#12",[   24,    28,    32]],
            [1/4,  [   20,    28,    32]],
            [5/16, [   18,    24,    32]],
            [3/8,  [   16,    24,    32]],
            [7/16, [   14,    20,    28]],
            [1/2,  [   13,    20,    28]],
            [9/16, [   12,    18,    24]],
            [5/8,  [   11,    18,    24]],
            [3/4,  [   10,    16,    20]],
            [7/8,  [    9,    14,    20]],
            [1,    [    8,    12,    20]],
            [1.125,[    7,    12,    18]],
            [1.25, [    7,    12,    18]],
            [1.375,[    6,    12,    18]],
            [1.5,  [    6,    12,    18]],
            [1.75, [    5, undef, undef]],
            [2,    [  4.5, undef, undef]],
         ]
       )
      inch / struct_val(UTS_thread, diam)[tind],
      head_data =
         head=="none" || is_undef(head) ? [["head","none"]] :
         head=="hex" ? let(
            UTS_hex = [
              // flat to flat width, height
               ["#2", [    1/8,   1/16]],
               ["#4", [   3/16,   1/16]],
               ["#6", [    1/4,   3/32]],
               ["#8", [    1/4,   7/64]],
               ["#10",[   5/16,    1/8]],
               ["#12",[   5/16,   5/32]],
               [1/4,  [   7/16,   5/32]],
               [5/16, [    1/2,  13/64]],
               [3/8,  [   9/16,    1/4]],
               [7/16, [    5/8,  19/64]],
               [1/2,  [    3/4,  11/32]],
               [9/16, [  13/16,  23/64]],
               [5/8,  [  15/16,  27/64]],
               [3/4,  [  1.125,    1/2]],
               [7/8,  [ 1+5/16,  37/64]],
               [1,    [    1.5,  43/64]],
               [1.125,[1+11/16,  11/16]],
               [1.25, [  1+7/8,  27/32]],
               [1.5,  [   2.25,  15/16]],
               [1.75, [  2+5/8, 1+3/32]],
               [2,    [      3, 1+7/32]],
            ],
            entry = struct_val(UTS_hex, diam)
           )
           [["head", "hex"], ["head_size", inch*entry[0]], ["head_height", inch*entry[1]]] :
         head=="socket" ? let(
            UTS_socket = [    // height = screw diameter
                       //diam, hex, torx
               ["#0", [  0.096,  0.05, undef]],
               ["#1", [  0.118,  1/16, undef]],
               ["#2", [   9/64,  5/64, undef]],
               ["#3", [  0.161,  5/64, undef]],
               ["#4", [  0.183,  3/32, 10]],
               ["#5", [  0.205,  3/32, 10]],
               ["#6", [  0.226,  7/64, 15, ]],
               ["#8", [  0.270,  9/64, 25]],
               ["#10",[   5/16,  5/32, 27]],
               ["#12",[  0.324,  5/32, undef]],
               [1/4,  [    3/8,  3/16, 30]],
               [5/16, [  15/32,   1/4, 45]],
               [3/8,  [   9/16,  5/16, 50 ]],
               [7/16, [  21/32,   3/8, undef]],
               [1/2,  [    3/4,   3/8, undef]],
               [9/16, [  27/32,  7/16, undef]],
               [5/8,  [  15/16,   1/2, undef]],
               [3/4,  [  1.125,   5/8, undef]],
               [7/8,  [ 1+5/16,   3/4, undef]],
               [1,    [    1.5,   3/4, undef]],
               [1.125,[1+11/16,   7/8, undef]],
               [1.25, [  1+7/8,   7/8, undef]],
               [1.375,[ 2+1/16,     1, undef]],
               [1.5,  [   2.25,     1, undef]],
               [1.75, [  2+5/8,  1.25, undef]],
               [2,    [      3,   1.5, undef]]
            ],
            entry = struct_val(UTS_socket, diam),
            drive_size =  drive=="hex" ? [["drive_size",inch*entry[1]]] :
                          drive=="torx" ? [["drive_size",entry[2]]] : []
            )
            concat([["head","socket"],["head_size",inch*entry[0]], ["head_height", inch*diameter]],drive_size) :
         head=="pan" ? let (
           UTS_pan = [  // pan head for philips or slotted
                 // diam, head ht slotted, head height phillips, philips drive, philips diam, philips width, philips depth, slot width, slot depth
               ["#0", [0.116, 0.039, 0.044, 0, 0.067, 0.013, 0.039, 0.023, 0.022]],
               ["#1", [0.142, 0.046, 0.053, 0, 0.085, 0.015, 0.049, 0.027, 0.027]],
               ["#2", [0.167, 0.053, 0.063, 1, 0.104, 0.017, 0.059, 0.031, 0.031]],
               ["#3", [0.193, 0.060, 0.071, 1, 0.112, 0.019, 0.068, 0.035, 0.036]],
               ["#4", [0.219, 0.068, 0.080, 1, 0.122, 0.019, 0.078, 0.039, 0.040]],
               ["#5", [0.245, 0.075, 0.089, 2, 0.158, 0.028, 0.083, 0.043, 0.045]],
               ["#6", [0.270, 0.082, 0.097, 2, 0.166, 0.028, 0.091, 0.048, 0.050]],
               ["#8", [0.322, 0.096, 0.115, 2, 0.182, 0.030, 0.108, 0.054, 0.058]],
               ["#10",[0.373, 0.110, 0.133, 2, 0.199, 0.031, 0.124, 0.060, 0.068]],
               ["#12",[0.425, 0.125, 0.151, 3, 0.259, 0.034, 0.141, 0.067, 0.077]],
               [1/4,  [0.492, 0.144, 0.175, 3, 0.281, 0.036, 0.161, 0.075, 0.087]],
               [5/16, [0.615, 0.178, 0.218, 4, 0.350, 0.059, 0.193, 0.084, 0.106]],
               [3/8,  [0.740, 0.212, 0.261, 4, 0.389, 0.065, 0.233, 0.094, 0.124]],
            ],
            htind = drive=="slot" ? 1 : 2,
            entry = struct_val(UTS_pan, diam),
            drive_size = drive=="philips" ? [["drive_size", entry[3]], ["drive_diameter",inch*entry[4]],["drive_width",inch*entry[5]],["drive_depth",inch*entry[6]]] :
                                            [["drive_width", inch*entry[7]], ["drive_depth",inch*entry[8]]])
           concat([["head","pan"], ["head_size", inch*entry[0]], ["head_height", inch*entry[htind]]], drive_size) :
         head=="button" || head=="round" ? let(
            UTS_button = [    // button, hex or torx drive
                 //   head diam, height, philips, hex, torx
               ["#0", [0.114, 0.032, undef, 0.035,5]],
               ["#1", [0.139, 0.039, undef, 3/64, undef]],
               ["#2", [0.164, 0.046, undef, 3/64, 6]],
               ["#3", [0.188, 0.052, undef, 1/16, undef]],
               ["#4", [0.213, 0.059, undef, 1/16, 8]],
               ["#5", [0.238, 0.066, undef, 5/64, undef]],
               ["#6", [0.262, 0.073, undef, 5/64, 10]],
               ["#8", [0.312, 0.087, undef, 3/32, 15]],
               ["#10",[0.361, 0.101, undef, 1/8,  25]],
               ["#12",[0.413, 0.114, undef, 1/8,  undef]],   // also 0.410, .115, 9/64
               [1/4,  [0.437, 0.132, undef, 5/32, 27]],
               [5/16, [0.547, 0.166, undef, 3/16, 40]],
               [3/8,  [0.656, 0.199, undef, 7/32, 45]],
               [7/16, [0.750, 0.220, undef, 1/4,  undef]],
               [1/2,  [0.875, 0.265, undef, 5/16, undef]],
               [5/8,  [1.000, 0.331, undef, 3/8,  undef]],
               [3/4,  [1.1,   0.375, undef, 7/16, undef]],
             ],
             UTS_round = [   // slotted, philips
                  // head diam, head height, philips drive,
               ["#0", [0.113, 0.053, 0]],
               ["#1", [0.138, 0.061, 0]],
               ["#2", [0.162, 0.069, 1, 0.100, 0.017, 0.053, 0.031, 0.048]],
               ["#3", [0.187, 0.078, 1, 0.109, 0.018, 0.062, 0.035, 0.053]],
               ["#4", [0.211, 0.086, 1, 0.118, 0.019, 0.072, 0.039, 0.058]],
               ["#5", [0.236, 0.095, 2, 0.154, 0.027, 0.074, 0.043, 0.063]],
               ["#6", [0.260, 0.103, 2, 0.162, 0.027, 0.084, 0.048, 0.068]],
               ["#8", [0.309, 0.120, 2, 0.178, 0.030, 0.101, 0.054, 0.077]],
               ["#10",[0.359, 0.137, 2, 0.195, 0.031, 0.119, 0.060, 0.087]],
               ["#12",[0.408, 0.153, 3, 0.249, 0.032, 0.125, 0.067, 0.096]],
               [1/4,  [0.472, 0.175, 3, 0.268, 0.034, 0.147, 0.075, 0.109]],
               [5/16, [0.590, 0.216, 3, 0.308, 0.040, 0.187, 0.084, 0.132]],
               [3/8,  [0.708, 0.256, 4, 0.387, 0.064, 0.228, 0.094, 0.155]],
               [1/2,  [0.813, 0.355, 4, 0.416, 0.068, 0.256, 0.106, 0.211]]
             ],
             entry = struct_val(head=="button" ? UTS_button : UTS_round, diam),
             drive_index = drive=="philips" ? 2 :
                           drive=="hex" ? 3 :
                           drive=="torx" ? 4 : undef,
             drive_size = drive=="philips" && head=="round" ? [["drive_size", entry[2]], ["drive_diameter",inch*entry[3]],
                                              ["drive_width",inch*entry[4]],["drive_depth",inch*entry[5]]] :
                          drive=="slot" && head=="round" ?  [["drive_width", inch*entry[6]], ["drive_depth",inch*entry[7]]] :
                          is_def(drive_index) && head=="button" ? [["drive_size", (drive=="hex"? inch : 1) * entry[drive_index]]] : []
             )
             concat([["head",head],["head_size",inch*entry[0]], ["head_height", inch*entry[1]]],drive_size) :
         head=="fillister" ? let(
             UTS_fillister = [ // head diam, head height, slot width, slot depth, philips diam, philips depth, philips width, philips #
                   ["#0", [0.096, 0.055, 0.023, 0.025, 0.067, 0.039, 0.013, 0]],
                   ["#1", [0.118, 0.069, 0.027, 0.031, 0.085, 0.049, 0.015,  ]],
                   ["#2", [0.140, 0.083, 0.031, 0.037, 0.104, 0.059, 0.017,  ]],
                   ["#3", [0.161, 0.095, 0.035, 0.043, 0.112, 0.068, 0.019, 1]],
                   ["#4", [0.183, 0.107, 0.039, 0.048, 0.122, 0.078, 0.019, 1]],
                   ["#5", [0.205, 0.120, 0.043, 0.054, 0.143, 0.067, 0.027, 2]],
                   ["#6", [0.226, 0.132, 0.048, 0.060, 0.166, 0.091, 0.028, 2]],
                   ["#8", [0.270, 0.156, 0.054, 0.071, 0.182, 0.108, 0.030, 2]],
                   ["#10",[0.313, 0.180, 0.060, 0.083, 0.199, 0.124, 0.031, 2]],
                   ["#12",[0.357, 0.205, 0.067, 0.094, 0.259, 0.141, 0.034, 3]],
                   [1/4,  [0.414, 0.237, 0.075, 0.109, 0.281, 0.161, 0.036, 3]],
                   [5/16, [0.518, 0.295, 0.084, 0.137, 0.322, 0.203, 0.042, 3]],
                   [3/8,  [0.622, 0.355, 0.094, 0.164, 0.389, 0.233, 0.065, 4]],
             ],
             entry = struct_val(UTS_fillister, diam),
             drive_size = drive=="philips" ? [["drive_size", entry[7]], ["drive_diameter",inch*entry[4]],
                                              ["drive_width",inch*entry[5]],["drive_depth",inch*entry[6]]] :
                          drive=="slot"?  [["drive_width", inch*entry[2]], ["drive_depth",inch*entry[3]]] : []
             )
             concat([["head", "fillister"], ["head_size", inch*entry[0]], ["head_height", inch*entry[1]]], drive_size) :
         starts_with(head,"flat") ? let(
             small = head == "flat small" || head == "flat undercut" || (head=="flat" && (drive!="hex")),
             undercut = head=="flat undercut",
             UTS_flat_small = [  // for philips drive, slotted, and torx
                    //      diam, ph drive, torx drive, undercut height
                   ["#0", [ .112,  0, undef, 0.025]],
                   ["#1", [ .137,  0, undef, 0.031]],
                   ["#2", [ .162,  1, 6    , 0.036]],
                   ["#3", [ .187,  1, undef, 0.042]],
                   ["#4", [ .212,  1, 8    , 0.047]],
                   ["#5", [ .237,  2, undef, 0.053]],  // ph#1 for undercut
                   ["#6", [ .262,  2, 10   , 0.059]],
                   ["#8", [ .312,  2, 15   , 0.070]],
                   ["#10",[ .362,  2, 20   , 0.081]],
                   ["#12",[ .412,  3, undef, 0.092]],
                   [1/4,  [ .477,  3, 27   , 0.107]],
                   [5/16, [ .597,  4, 40   , 0.134]],
                   [3/8,  [ .717,  4, 40   , 0.161]],
                   [1/2,  [ .815,  4, undef, 0.156]]
             ],
             UTS_flat_large = [   // for hex drive, torx
                     // head diam, hex drive size, torx size
                   ["#0", [ 0.138, 1/32, 3]],
                   ["#1", [ 0.168, 3/64, undef]],
                   ["#2", [ 0.197, 3/64, 6]],
                   ["#3", [ 0.226, 1/16, undef]],
                   ["#4", [ 0.255, 1/16, 10]],
                   ["#5", [ 0.281, 5/64, 10 ]],
                   ["#6", [ 0.307, 5/64, 15]],
                   ["#8", [ 0.359, 3/32, 20]],
                   ["#10",[ 0.411,  1/8, 25]],
                   ["#12",[ 0.422,  1/8, undef]],
                   [1/4,  [ 0.531, 5/32, 30]],
                   [5/16, [ 0.656, 3/16, 40]],
                   [3/8,  [ 0.810, 7/32, undef]],
                   [7/16, [ 0.844,  1/4, undef]],
                   [1/2,  [ 0.938, 5/16, undef]],
                   [5/8,  [ 1.188,  3/8, undef]],
                   [3/4,  [ 1.438,  1/2, undef]],
                   [7/8,  [ 1.688, 9/16, undef]],
                   [1,    [ 1.938,  5/8, undef]],
                   [1.125,[ 2.188,  3/4, undef]],
                   [1.25, [ 2.438,  7/8, undef]],
                   [1.5,  [ 2.938,    1, undef]],
             ],
             entry = struct_val(small ? UTS_flat_small : UTS_flat_large, diam),
             driveind = small && drive=="philips" || !small && drive=="hex" ? 1 :
                        drive=="torx" ? 2 :
                        undef
             )
             concat([["head","flat"],["head_angle",82],["head_size",inch*entry[0]]],
                    is_def(driveind) ? [["drive_size", (drive=="hex"?inch:1)*entry[driveind]]] : [],
                    undercut ? [["head_height", inch*entry[3]]] : []
                   ) : []
    )
    concat([["system","UTS"],["diameter",inch*diameter],["pitch", pitch],["drive",drive]],
            head_data
          );

function _screw_info_metric(diam, pitch, head, thread, drive) =
 let(
   a=echo(metricsi=diam,pitch,head,thread,drive),
   pitch = is_num(thread) ? thread :
     is_def(pitch) ? pitch :
     let(
        tind=struct_val([["coarse",0],
                         ["fine",1],
                         ["extra fine",2],["extrafine",2],
			 ["super fine",3],["superfine",3]],
                         downcase(thread)),
                            // coarse  fine  xfine superfine
        ISO_thread = [
  	               [1  , [0.25,    0.2 ,   undef, undef,]],
           	       [1.2, [0.25,    0.2 ,   undef, undef,]],
           	       [1.4, [0.3 ,    0.2 ,   undef, undef,]],
           	       [1.6, [0.35,    0.2 ,   undef, undef,]],
           	       [1.7, [0.35,   undef,   undef, undef,]],
           	       [1.8, [0.35,    0.2 ,   undef, undef,]],
           	       [2  , [0.4 ,    0.25,   undef, undef,]],
           	       [2.2, [0.45,    0.25,   undef, undef,]],
           	       [2.3, [0.4 ,   undef,   undef, undef,]],
           	       [2.5, [0.45,    0.35,   undef, undef,]],
           	       [2.6, [0.45,   undef,   undef, undef,]],
           	       [3  , [0.5 ,    0.35,   undef, undef,]],
           	       [3.5, [0.6 ,    0.35,   undef, undef,]],
           	       [4  , [0.7 ,    0.5 ,   undef, undef,]],
           	       [5  , [0.8 ,    0.5 ,   undef, undef,]],
           	       [6  , [1   ,    0.75,   undef, undef,]],
           	       [7  , [1   ,    0.75,   undef, undef,]],
           	       [8  , [1.25,    1   ,    0.75, undef,]],
           	       [9  , [1.25,    1   ,    0.75, undef,]],
           	       [10 , [1.5 ,    1.25,    1   ,  0.75,]],
           	       [11 , [1.5 ,    1   ,    0.75, undef,]],
           	       [12 , [1.75,    1.5 ,    1.25,  1,   ]],
           	       [14 , [2   ,    1.5 ,    1.25,  1,   ]],
           	       [16 , [2   ,    1.5 ,    1   , undef,]],
           	       [18 , [2.5 ,    2   ,    1.5 ,  1,   ]],
           	       [20 , [2.5 ,    2   ,    1.5 ,  1,   ]],
           	       [22 , [2.5 ,    2   ,    1.5 ,  1,]],
           	       [24 , [3   ,    2   ,    1.5 ,  1,]],
           	       [27 , [3   ,    2   ,    1.5 ,  1,]],
           	       [30 , [3.5 ,    3   ,    2   ,  1.5,]],
           	       [33 , [3.5 ,    3   ,    2   ,  1.5,]],
           	       [36 , [4   ,    3   ,    2   ,  1.5,]],
           	       [39 , [4   ,    3   ,    2   ,  1.5,]],
           	       [42 , [4.5 ,    4   ,    3   ,  2,]],
           	       [45 , [4.5 ,    4   ,    3   ,  2,]],
           	       [48 , [5   ,    4   ,    3   ,  2,]],
           	       [52 , [5   ,    4   ,    3   ,  2,]],
           	       [56 , [5.5 ,    4   ,    3   ,  2,]],
           	       [60 , [5.5 ,    4   ,    3   ,  2,]],
           	       [64 , [6   ,    4   ,    3   ,  2,]],
           	       [68 , [6   ,    4   ,    3   ,  2,]],
           	       [72 , [6   ,    4   ,    3   ,  2,]],
           	       [80 , [6   ,    4   ,    3   ,  2,]],
           	       [90 , [6   ,    4   ,    3   ,  2,]],
           	       [100, [6   ,    4   ,    3   ,  2,]],
         ]
       )
      struct_val(ISO_thread, diam)[tind],
      head_data =
         head=="none" || is_undef(head) ? [["head","none"]] :
         head=="hex" ? let(
            metric_hex = [
              // flat to flat width, height
	      [5, [8, 3.5]],
	      [6, [10,4]],
	      [8, [13, 5.3]],
	      [10, [17, 6.4]],
	      [12, [19, 7.5]],
	      [14, [22, 8.8]],
	      [16, [24, 10]],
	      [18, [27,11.5]],
	      [20, [30, 12.5]],
	      [24, [36, 15]],
	      [30, [46, 18.7]],
            ],
            entry = struct_val(metric_hex, diam)
           )
           [["head", "hex"], ["head_size", entry[0]], ["head_height", entry[1]]] :
         head=="socket" ? let(
            metric_socket = [    // height = screw diameter
                      //diam, hex
                [1.4, [2.5, 1.3]],
		[1.6, [3, 1.5]],
		[2,   [3.8, 1.5]],
		[2.5, [4.5, 2]],
		[2.6, [5, 2]],
		[3,   [5.5, 2.5]],
		[3.5, [6.2, 2.5]],
		[4,   [7, 3]],
		[5,   [8.5, 4]],
		[6,   [10, 5]],
		[7,   [12, 6]],
		[8,   [13, 6]],
		[10,  [16, 8]],
		[12,  [18, 10]],
		[14,  [21, 12]],
		[16,  [24, 14]],
		[18,  [27, 14]],
		[20,  [30, 17]],
		[22,  [33, 17]],
		[24,  [36, 19]],
		[27,  [40, 19]],
		[30,  [45, 22]],
		[33,  [50, 24]],
		[36,  [54, 27]],
		[42,  [63, 32]],
		[48,  [72, 36]],
            ],
            entry = struct_val(metric_socket, diam),
            drive_size =  drive=="hex" ? [["drive_size",entry[1]]] : []
            )
            concat([["head","socket"],["head_size",entry[0]], ["head_height", diam]],drive_size) :
         head=="pan" ? let (
           metric_pan = [  // pan head for philips or slotted
                 // diam, slotted ht, philips height, philips
      		 [1.6,[3.2, undef, 1.3,	0]],
		 [2,  [4,   1.2, 1.6,	1]],
		 [2.5,[5,   1.5, 2,  	1]],
		 [3,  [6,   1.8, 2.4,	1]],
		 [3.5,[8,   undef, 3.1,	2]],
		 [5,  [10,  3, 3.8,	2]],
		 [6,  [12,  3.6, 4.6,	3]],
		 [8,  [16,  8, 6,  	4]],
		 [10, [20,  undef, 7.5,	4]],
            ],
            htind = drive=="slot" ? 1 : 2,
            entry = struct_val(metric_pan, diam),
            drive_size = drive=="philips" ? [["drive_size", entry[3]]] : [])
           concat([["head","pan"], ["head_size", entry[0]], ["head_height", entry[htind]]], drive_size) :
         head=="button" || head=="cheese" ? let(
            metric_button = [    // button, hex drive
                 //   head diam, height, hex, philips
                 [1.6, [2.9, 0.8, 0.9, undef]],
		 [2, [3.5, 1.3, 1.3, undef]],
		 [2.2, [3.8, 0.9, 1.3, undef]],
		 [2.5, [4.6, 1.5, 1.5, undef]],
		 [3, [5.7, 1.65,2, undef]],
		 [3.5, [5.7, 1.65,2, undef]],
		 [4, [7.6, 2.2, 2.5, undef]],
		 [5, [9.5, 2.75,3, undef]],
		 [6, [10.5, 3.3,4, undef]],
		 [8, [14, 4.4, 5, undef]],
		 [10, [17.5, 5.5, 6, undef]],
		 [12, [21, 6.6, 8, undef]],
		 [16, [28, 8.8, 10, undef]],
             ],
             metric_cheese = [   // slotted, philips
                  // head diam, head height, philips drive
		[1,[2,0.7, undef, undef]],
		[1.2, [2.3, 0.8,undef,undef]],
		[1.4, [2.6, 0.9,undef,undef]],
		[1.6, [3, 1,undef,undef]],
		[2, [3.8, 1.3, undef,1]],
		[2.5, [4.5, 1.6, undef,1]],
		[3, [5.5, 2, undef, 2]],
		[3.5, [6, 2.4, undef, undef]],
		[4, [7, 2.6, undef, 2]],
		[5, [8.5, 3.3, undef, 2]],
		[6, [10, 3.9, undef, 3]],
		[8, [13, 5,undef, undef]]
             ],
             entry = struct_val(head=="button" ? metric_button : metric_cheese, diam),
             drive_index = drive=="philips" ? 3 :
                           drive=="hex" ? 2 : undef,
             drive_size = is_def(drive_index) ? [["drive_size", entry[drive_index]]] : []
             )
             concat([["head",head],["head_size",entry[0]], ["head_height", entry[1]]],drive_size) :
         substr(head,0,4)=="flat" ? let(
             small = head == "flat small" || (head=="flat" && (drive!="hex")),
             metric_flat_large = [ // for hex drive
                  [2, [4, 1.3]],
		  [2.5, [5, 1.5]],
		  [3, [6, 2]],
		  [4, [8, 2.5]],
		  [5, [10, 3]],
		  [6, [12, 4]],
		  [8, [16, 5]],
		  [10, [20, 6]],
		  [12, [24, 8]],
		  [14, [27, 10]],
		  [16, [30, 10]],
		  [18, [33, 12]],
		  [20, [36, 12]],
             ],
             metric_flat_small = [ // for philips, slotted
                  [1.6, [3, 0]],
                  [2,   [3.8, 0]],
	       	  [2.5, [4.7, 1]],
	       	  [2.6, [4.7, 1]],
	       	  [3,   [5.6, 1]],
	       	  [3.5, [7.5, 1]],
	       	  [4,   [7.5, 2]],
	       	  [5,   [9.2, 2]],