nut catches?

https://github.com/JohK/nutsnbolts
(Iis this already around somewhere?)

I've been making nut catches each time, and not even thinking about it.  This is sensible to add.

This may have a dependency on #37 so that nut size specification is consistent.

Agreed about the dependency.  I never did a table of nut thicknesses...but nut widths are certainly there.

I've put together a personal library for creating screw holes (masks) that include nut traps of various kinds. It uses the screws module from BOSL2 extensively. The code includes a very limited table of nut dimensions for the screws I use regularly. A key part of the code positions the screw hole in various ways to make it easier to move into position in the assembly.
Here it is:
`
include <BOSL2/std.scad>
include <BOSL2/screws.scad>
$fn = $preview ? 30 : 90;
_tiny = 1/128;
/*
The default tolerance is a measured amount. Vertical dimensions
in printed holes require this much extra so that a nut will always
fit. A much smaller tolerance can work if the hole's cross section
is in the XY plane. However, the .25 value works in the worst case.
*/
defaultTolerance = .25;
/*************************************************************

Screws and holes are rendered vertically with head at the top

Slots for nut traps are in the direction of +X

extendH extend the hole at the top of the head

extendS extendthe hole at the bottom of the crew

gap is the space between head and nut

pos determines alignment with the XY plane, ie Z == 0

0 - Top of head end

1 - Screw head seat - the head side of the gap

2 - center of gap, ie half way between head and nut

3 - nut side of the gap

4 - 1 mm beyond screw end

5 - Bottom

"top" - Top of the hole above the head

"head" - Screw head seat - the head side of the gap

"gap" - center of gap, ie half way between head and nut

"nut" - nut side of the gap

"screw" - 1 mm beyond screw end

"bot" - Bottom of the screw hole

nut - "none", "hex", "square" or "self", default: "hex"

trap - "none", "side" or "inline", default: "inline"
***************************************************************/

/*
screw_context
Creates a structure that includes all the needed parameters to define
a screw and the holes necessary for it and its nut, if any. The nut
information is added automatically.
The data structure allows many calls without the overhead of
rebuilding the set of parameters each time.
The screw can be described in one of three ways, in order of precedence
specify spec (call screw_info to get the spec)
specify name (as defined by BOSL2, eg "M5,16", metric 5mm diameter, 16 mm long)
specify d and l, this assumes a name "Md,l"
For the second and third options, drive and head can also be specified.
These will be passed to screw_info to retrieve the spec

name   - BOSL2 string name of the screw, "Md,l" - may replace D and L
d      - diameter of the screw in mm
l      - length of the screw in mm
head   - string head type as defined in BOSL2, defaults to "socket"
drive  - string drive type as defined in BOSL2, defaults to "hex"
spec   - the structure returned by screw_info
pos    - position of screw relative to the XY plane, screw is assumed to be on Z axis
gap    - the distance in mm between head and nut in the assembly
nut    - "hex" (default), "square", "self", "none"
trap   - "none" (default), "inline", "side"
extendH - clearance above the head, ie additional length of the hole for the head
extendS - clearance below the screw, ie additional length of the hole for the screw
trapL  - trap length for a side trap, defaults to 25
pokeL  - poke hole length - a hole opposite the nut trap to allow the nut to be poked out
default is -1 : no poke hole
> 0 means include a poke hole
1 means use the default length of 25
pokeD  - diameter of poke hole, defaults to 2
t      - tolerance added to diameters so screws will fit in holes, default is "defaultTolerance"
*/
function screw_context(
d = 0,
l = 0,
name = 0,
head = "socket",
drive = "hex",
spec = 0,
pos = "gap",
gap = 4,
nut = "hex",
trap = "none",
extendH = 1,
extendS = 2,
pokeL = -1,
pokeD = 2,
trapL = 25,
t = defaultTolerance) =
let(
    nameFromSpec = (is_list(spec)) ? str("M", _SV1(spec, "diameter"), ",", _SV1(spec, "length")) : undef,
    nameS = (is_list(spec)) ? nameFromSpec : (is_string(name)) ? name : str("M", d, ",", l),
    info = (is_list(spec)) ? spec : screw_info(nameS, head = head, drive = drive),
    xyzzy = assert(nameS != "M0, 0")
)
[["pos", pos],                // Screw is returned head up in Z, pos determines the vertical position
 ["gap", gap],                // Gap is the length from the bottom of the head to the top of the nut
 ["nut", nut],                // Gap is the length from the bottom of the head to the top of the nut
 ["trap", trap],              // Gap is the length from the bottom of the head to the top of the nut
 ["extendh", extendH],        // The additional length of the hole above the head
 ["extends", extendS],        // The additional length of the hole below the screw
 ["pokel", pokeL],            // Poke hole length
 ["poked", pokeD],            // Poke hole diameter
 ["trapl", trapL],            // Trap hole length
 ["t", t],                    // Tolerance, defaults to defaultTolerance
 ["screwname", nameS],        // screw name string
 ["spec", info],              // technical description of the screw
 ["nutSpec", _NutInfo(info)] // technical description of the nut
 ];

/*
screw_hole(sc);
sc - screw context
Creates a screw hole includes any nut trap (including "poke hole").
It is vertically positioned as designated in the context.
If the trap is a side trap, it extends in the direction of the x axis.
ANy poke hole is opposite the side trap.

screw_from_context(sc);
sc - screw context
nut_from_context(sc);
sc - screw context
*/
/*
Nut Info information
diameter - distance face to face
height   - thickness
Hex and square nuts appear to have the same specs
This table only covers metric nuts for screws from 3 to 10 mm
*/
function _NutInfo(sc) = let (d = struct_val(sc, "diameter", 0))
(d == 3)  ? [["diameter", 5.5],  ["height", 2.4]] :
(d == 4)  ? [["diameter", 7.0],  ["height", 3.2]] :
(d == 5)  ? [["diameter", 8.0],  ["height", 4.0]] :
(d == 6)  ? [["diameter", 10.0], ["height", 5.0]] :
(d == 7)  ? [["diameter", 11.0], ["height", 5.5]] :
(d == 8)  ? [["diameter", 13.0], ["height", 6.5]] :
(d == 10) ? [["diameter", 17.0], ["height", 8.0]] : [];
/*
Functions for accessing the screw_context.
*/
function _SV1(s, n1, d = 0) = struct_val(s, n1, d);
function _SV2(s, n1, n2, d = 0) = len(struct_val(s, "spec", [])) == 0
? struct_val(s, n2, d)
: struct_val(struct_val(s, n1, ""), n2, d);
function _ScrewD(s)  = _SV2(s, "spec",    "diameter", 0);
function _ScrewL(s)  = _SV2(s, "spec",    "length", 0);
function _ScrewP(s)  = _SV2(s, "spec",    "pitch", 0);
function _HeadHV(s)  = _SV2(s, "spec",    "head_height", 0);
function _HeadD(s)   = _SV2(s, "spec",    "head_size", 0);
function _Head(s)    = _SV2(s, "spec",    "head", 0);
function _Drive(s)   = _SV2(s, "spec",    "drive", 0);
function _HeadA(s)   = _SV2(s, "spec",    "head_angle", 0);
function _NutD(s)    = _SV2(s, "nutSpec", "diameter", 0);
function _NutH(s)    = _SV2(s, "nutSpec", "height", 0);
function _Info(s)    = _SV1(s, "spec",   []);
function _Spec(s)    = _SV1(s, "spec",   []);
function _Pos(s)     = _SV1(s, "pos",    0);
function _Nut(s)     = _SV1(s, "nut",    0);
function _Trap(s)    = _SV1(s, "trap",   0);
function _Gap(s)     = _SV1(s, "gap",    0);
function _ExtendH(s) = _SV1(s, "extendh", 0);
function _ExtendS(s) = _SV1(s, "extends", 0);
function _PokeL(s)   = _SV1(s, "pokel",  0);
function _PokeD(s)   = _SV1(s, "poked",  0);
function _TrapL(s)   = _SV1(s, "trapl",  0);
function _T(s)       = _SV1(s, "t",      0);
function _ScrewName(s) = _SV1(s, "screwname", "");
/*
The head height value for flat screws in screw_info is 0.
For our purposes though, the head height is the distance
from head top surface to beginning of threads.
*/
function _HeadH(sc) = (_Head(sc) == "flat")
? (_HeadD(sc) - _ScrewD(sc)) / 2 / tan(_HeadA(sc) / 2)
: _HeadHV(sc);
function _gap_adjustment(sc) =
let (
pos = _pos_n(_Pos(sc)),
gap = _Gap(sc),
headLen = _HeadH(sc) + _ExtendH(sc),
nutLen = _ScrewL(sc) - gap + _ExtendS(sc),
xyzzy = echo(headLen)
)
(pos <= 0) ? 0 :
(pos == 1) ? headLen :
(pos == 2) ? headLen + gap/2 :
(pos == 3) ? headLen + gap :
(pos == 4) ? headLen + _ScrewL(sc):
             headLen + gap + nutLen;

module screw_hole(sc) {
headLen = _HeadH(sc) + _ExtendH(sc);
screwHoleLen = _ScrewL(sc) + _ExtendS(sc);
nutLen = _ScrewL(sc) - _Gap(sc) + _ExtendS(sc);
//struct_echo(sc, "screw_hole");
if (_Nut(sc) == "self") {
    _self_tap(sc);
} else {
    _screw_hole(sc);
}

if (_Trap(sc) == "side") {
    _side_trap(sc);
} else if (_Trap(sc) == "inline") {
    _inline_trap(sc);
}

} // screw_hole
module screw_from_context(sc) {
up(_gap_adjustment(sc) - _ExtendH(sc))
down(_ScrewL(sc) + _HeadHV(sc))
screw(_ScrewName(sc), head = _Head(sc), drive = _Drive(sc));
} // screw_from_context
module nut_from_context(sc) {
up(_gap_adjustment(sc))
down(_ExtendH(sc))
down(_HeadH(sc))
down(_Gap(sc))
down(_NutH(sc))
if (_Nut(sc) == "square") {
difference() {
cube([_NutD(sc), _NutD(sc), _NutH(sc)], anchor = BOT);
        // Remove
        down(.1)
        #screw(_ScrewName(sc), head = _Head(sc), drive = _Drive(sc));
    }
} else if (_Nut(sc) == "hex") {
    nut(diameter = _NutD(sc), thickness = _NutH(sc), spec = _Info(sc));
}

} // nut_from_context
module _screw_hole(sc) {
headLen = _HeadHV(sc) + _ExtendH(sc);
screwHoleLen = _ScrewL(sc) + _ExtendS(sc);
nutLen = _ScrewL(sc) - _Gap(sc) + _ExtendS(sc);
//struct_echo(sc, "screw_hole");
up(_gap_adjustment(sc))
union() {
    // Space for screw head
    up(_tiny)
    zcyl(h = headLen + _tiny*2, d = _HeadD(sc) + _T(sc), anchor = TOP);

    // Space between head and nut
    down(headLen)
    up(_tiny)
    zcyl(h = screwHoleLen + _tiny*2, d = _ScrewD(sc) + _T(sc), anchor = TOP);

    down(headLen)
    if (_Head(sc) == "flat") _FlatHead(sc);
}

} // _screw_hole
module _FlatHead(sc) {
head_size = _HeadD(sc) + _T(sc);
head_height = _HeadH(sc);
angle = _HeadA(sc) / 2;
full_height = head_size / 2 / tan(angle);
height = head_height > 0 ? head_height : full_height;
d2 = head_size*(1-height/full_height);
zflip()
cyl(d1 = head_size, d2 = d2, l = height, anchor = BOTTOM);
} // _FlatHead
module _self_tap(sc) {
headLen = _HeadH(sc) + _ExtendH(sc);
holeLen = _Gap(sc) + 1; // 1 mm entry hole
tapLen  = _ScrewL(sc) + _ExtendS(sc) - holeLen;
up(_gap_adjustment(sc))
union() {
    // Space for screw head
    up(.1)
    zcyl(h = headLen + .1, d = _HeadD(sc) + _T(sc), anchor = TOP);

    // Space between head and nut (self tapping surface)
    // self tap hole starts 1mm inside the surface
    down(headLen)
    up(.1)
    zcyl(h = holeLen + .1, d = _ScrewD(sc) + _T(sc), anchor = TOP);

    down(headLen + holeLen)
    up(.1)
    zcyl(h = tapLen + .1, d = _ScrewD(sc) - _ScrewP(sc), anchor = TOP);
}

} // _self_tap
module _side_trap(sc, slotLen = 15) {
headLen = _HeadH(sc) + _ExtendH(sc);
screwHoleLen = _ScrewL(sc) + _ExtendS(sc);
nutLen = _ScrewL(sc) - _Gap(sc) + _ExtendS(sc);
up(_gap_adjustment(sc))
union() {
    // Slot
    down(headLen + _Gap(sc))
    up(_T(sc)/2)
    cube([_TrapL(sc), _NutD(sc) + _T(sc), _NutH(sc) + _T(sc)], anchor = [-1, 0, 1]);

    if (_Nut(sc) == "square") {
        down(headLen + _Gap(sc))
        up(_T(sc)/2)
        cube([_NutD(sc) + _T(sc), _NutD(sc) + _T(sc), _NutH(sc) + _T(sc)], anchor = [0, 0, 1]);
    } else {
        down(headLen + _Gap(sc) + _NutH(sc))
        down(_T(sc)/2)
        _hexCyl(h = _NutH(sc) + _T(sc), id = _NutD(sc) + _T(sc));
    }

    // Poke hole
    if (_PokeL(sc) > 0) {
        down(headLen + _Gap(sc))
        down(_NutH(sc)/2)
        xcyl(h = (_PokeL(sc) == 1 ? 25 : _PokeL(sc)), d = _PokeD(sc), anchor = [1, 0, 0]);
    }
}

} // _side_trap
module _inline_trap(sc) {
headLen = _HeadH(sc) + _ExtendH(sc);
screwHoleLen = _ScrewL(sc) + _ExtendS(sc);
nutLen = _ScrewL(sc) - _Gap(sc) + _ExtendS(sc);
if (_Nut(sc) == "square") {
    up(_gap_adjustment(sc))
    down(headLen + screwHoleLen)
    cube([_NutD(sc) + _T(sc), _NutD(sc) + _T(sc), nutLen + .1], anchor = [0, 0, -1]);
    //_hexCyl(l = nutLen + .1, id = _NutD(sc) + _T(sc));
} else {
    up(_gap_adjustment(sc))
    down(headLen + screwHoleLen)
    _hexCyl(l = nutLen + .1, id = _NutD(sc) + _T(sc));
}

} // _inline_trap
function _pos_n(p) =
(!is_string(p)) ? p :
(p == "top")    ? 0 :
(p == "head")   ? 1 :
(p == "gap")    ? 2 :
(p == "nut")    ? 3 :
(p == "screw")  ? 4 :
(p == "bot")    ? 5 : undef;
module _hexCyl(l = -1, h = -1, id = -1, d = -1) {
H = (h > 0) ? h : l;
ID = (id > 0) ? id : d;
linear_extrude(H)
hexagon(id = ID);

} // _hexCyl
/***************************************************************
*

Test Code

***************************************************************/
_test();
module _test() {
showPosition = "all";
trapAngle = 135;
siName = "M5,16";
//siHead = "socket";
siHead = "flat";
//siHead = "button";
siDrive = "hex";
scGap = 4;
scNut = "hex"; // "hex, "square", "self"
scTrap = "side"; // "inline", "side", none
scPoke = 10; // -1, 1
scTrapL = 25;

scExtendH = 1;
scExtendS = 3;

si = screw_info(name = siName, head = siHead, drive = siDrive);

sc0 = screw_context(spec = si, pos = 0, gap = scGap, extendH = scExtendH, extendS = scExtendS,
      nut = scNut, trap = scTrap, pokeL = scPoke);
sc1 = screw_context(spec = si, pos = 1, gap = scGap, extendH = scExtendH, extendS = scExtendS,
      nut = scNut, trap = scTrap, pokeL = scPoke);
sc2 = screw_context(spec = si, pos = 2, gap = scGap, extendH = scExtendH, extendS = scExtendS,
      nut = scNut, trap = scTrap, pokeL = scPoke);
sc3 = screw_context(spec = si, pos = 3, gap = scGap, extendH = scExtendH, extendS = scExtendS,
      nut = scNut, trap = scTrap, pokeL = scPoke);
sc4 = screw_context(spec = si, pos = 4, gap = scGap, extendH = scExtendH, extendS = scExtendS,
      nut = scNut, trap = scTrap, pokeL = scPoke);
sc5 = screw_context(spec = si, pos = 5, gap = scGap, extendH = scExtendH, extendS = scExtendS,
      nut = scNut, trap = scTrap, pokeL = scPoke);

echo(_HeadH(sc0));

xdistribute(20) {
    ydistribute(20) {
        zrot(trapAngle) screw_hole(sc0);
        if (showPosition == "all") zrot(trapAngle) screw_hole(sc1);
        if (showPosition == "all") zrot(trapAngle) screw_hole(sc2);
        if (showPosition == "all") zrot(trapAngle) screw_hole(sc3);
        if (showPosition == "all") zrot(trapAngle) screw_hole(sc4);
        if (showPosition == "all") zrot(trapAngle) screw_hole(sc5);
    }

    ydistribute(20) {
        zrot(trapAngle) union() {screw_from_context(sc0); nut_from_context(sc0);}
        if (showPosition == "all") zrot(trapAngle) union() {screw_from_context(sc1); nut_from_context(sc1);}
        if (showPosition == "all") zrot(trapAngle) union() {screw_from_context(sc2); nut_from_context(sc2);}
        if (showPosition == "all") zrot(trapAngle) union() {screw_from_context(sc3); nut_from_context(sc3);}
        if (showPosition == "all") zrot(trapAngle) union() {screw_from_context(sc4); nut_from_context(sc4);}
        if (showPosition == "all") zrot(trapAngle) union() {screw_from_context(sc5); nut_from_context(sc5);}
    }
}

}
`

What is the "nut" anchor?  It says the nut side of the gap, but I'm not sure what that means.  Does it mean right above the nut?   Also, when do you use the "gap" anchor?
You have "inline" and "side" for the nut trap.  What does that mean?

An inline trap doesn't look like it traps anything.  What is the intended use for this?

The inline trap keeps the nut from rotating. It's hex for a hex nut and square for a square nut. Once you get the screw started it will continue through and become as tight as you want.

I considered adding anchor, spin and orient to the screw_context for use when these are always or usually the same in a given application. The anchor, spin and orient arguments in screw_hole(...), etc. would override the ones in the screw_context. It seemed to me that leaving them out of the context was more consistent with the rest of BOSL2. If you like it, that is an option. It's also an option to fold this functionality into existing calls by adding a context argument. Screw_hole, or screw_mask if you prefer, is probably the only one that seems to need to be added.

The inline trap should have been more obvious to me.  It makes sense now.
Right now my thinking on this matter is that you are duplicating too much functionality that already exists for it to make sense for us to directly use your code.   You have basically created a second screw context system even though one already exists.  And you have also duplicated the head-drawing code, which is problematic, since it creates two parallel lines of code to maintain.  If I'm reading your code correctly, the flat head code will not work right on 82 deg or 100 deg angled flat heads, for example.  And if one specified an undercut screw it looks like things would also go awry.   And with my latest code to fix sharp flathead corners, I think there will also be more problems, because you need to use the sharp diameter for a hole instead of the true diameter.
But this functionality is important, so I do want to figure out a good way to incorporate it.

I relied on the BOSL2 stuff as much as I thought I could. The only code I rewrote was the flat head because I couldn't see any other way. I used the angle from screw_info so I don't see why it wouldn't be correct for an 82 or 100 degree screw. As for the other heads, AFAIK they are all round and I use the head diameter from screw_info so I don't know why that would mess up. One reason I had to write the flat head code is that my custom anchors know about the "bottom of the head". For a flat screw I treat this as the bottom of the cone (usually also the top of the thread). This seemed most intuitive to me.
If you point me in a direction where I could use more BOSL2, I can adapt the code more closely.
I'm not sure what you mean about there already being a screw context system. I assume you are talking about screw_info. Screw_info is entirely a description of the screw itself. The data call comes from BOSL2 once the screw is chosen. Screw_context is, except for the incorporated screw_info, entirely a description of what's around the screw. Screw_info is incorporated only to make it more efficient. Because of the use of structs, I think the code is pretty resilient to changes in screw_info as long as clear incompatibilities are not introduced. Because OpenSCAD effectively only has constants, a user can't really add values to a struct. Yeah I know you can make a copy, but that's inelegant at best. So if you wanted to add my context information to screw_info, you'd have to a lot of arguments to screw_info or a lot of arguments to other modules.
There's a natural trade off. You can often add more arguments to a single module or you can split the module into 2 or three and each one will have fewer arguments. I love BOSL2 but IMHO it leans too far in the former direction. Case in point, I never even noticed the oversize argument for screw. I had no idea that functionality existed. This what at a minimum I think you need to a screw_hole (or _mask) module.
Typically I find I never have a single screw hole I need, it's always several. That's why screw_context. Specifying a bunch of arguments on a call and having several identical calls with all the arguments is error prone because any change requires changing all the calls. Putting all the arguments in "variables" allows you to make a change in one place, but that's kind of messy too.

I've been working on fixing various bugs and deficiencies in screw() for the past couple months.  Revar and I had a discussion a couple weeks ago about hole making and I split off the hole making functionality into screw_hole() but I think the version currently posted has some bugs and deficiencies. I think I'm almost done, but a key thing that will be of interest to you which is still missing is flexible anchoring.  I got kind of stuck on the naming and anchoring for the threaded part of the screw (which might not be threaded), the shank, the shank + threaded part (shaft), and how to handle all the cases in a clear fashion.   Hopefully I'll finish this in the next couple days.
You're right, the head size computation in your code looks reasonable for flat heads as things stand. (I was evidently looking at the wrong thing.)  But in the new code they look like this:

Screw on the left, and matching sized clearance hole mask on the right.  That's with the ASME "normal" hole tolerance and $slop=0.    This whole business with flatheads was pretty annoying to get nailed down.
Fundamentally, I can't see a justification for generating your screw (or screw mask), including the head, by any other means than a call to screw() itself, because then you're duplicating functionality.  That was the original reason I tried to use screw() to also make masks---to avoid making the code more complex.   For uniformity of the interface the anchors should be identical between screw() and screw_hole(), so duplicating code creates an issue there as well, especially if we end up tweaking the anchors.  My new code has screw_hole() as basically a pass-through that invokes screw, and I hid the arguments to screw() that are only suited to holes, like counterbore and internal.
If you have a struct and you rewrite it to add to or change entries you're making a new data structure.  If you create a new data structure that has the struct as a sub-part you're also creating a new data structure.   Maybe OpenSCAD is clever and uses a reference to the existing struct in your new one?  But the amount of memory is insignificant.  I don't think it matters which one you do from a performance perspective.  The struct_set() function will change or extend structures, so the code to do it is simple enough.  In fact, I even imagined that a user might want to create a screw and then tweak it.
To me it seems like what you're doing with screw_context should be done with a user-defined module.  That is, if you need some specific screw arrangement a bunch of times you make a module to produce it and then you can create it as needed.
I think that designing the library interface is hard, and there's no guarantee that we get it right the first time.  I'm curious if there are other specific cases that leap to mind where you feel BOSL2 has too much functionality in one module, and it should instead have been split.  Both Revar and I think that nut traps should be a separate module from the screw_hole module, so in this instance, you're leaning the other way, and in fact actually combining two things in one module.

I'm in the process of simplifying the code I last posted. There is really not very much code that might need maintenance. Most of it is in setting up and accessing the screw_context. And I understand how structs work.
However, I think we are converging.
I certainly entertained the idea of making the traps a separate call and I don't think much is lost by separating it.
The real key is my custom anchors. All of them are actually found relative to the screw and nut position, rather than the entire screw hole. The only place where the anchors need to "know" about the head and screw extensions of the hole are when you use TOP or BOTTOM when placing a screw hole. In my experience, I've never found myself using those enchors except in test code.
The custom anchors "gap" and "nut" require knowledge of the nut position, AKA, the size of the gap. So in order to use those anchors with screw (and thus screw_hole in your pass thru), would you add a gap parameter? Something else I haven't thought of?
Other than gap, the entries in the context are all either not strictly needed (like spec (screw_info) and nutSpec because they can be handled by passing screw_info, OR they are only needed as arguments to nut_trap call.
So to implement my set of custom anchors and nut traps and have all the screws, nuts, holes and traps use the same set:

No screw_context is necessary.
Add a gap argument to screw() and nut().
Add screw_hole() that takes screw_info and gap arguments. Include self tapping in screw hole.
Add nut_trap() that would require screw_info and gap arguments, plus trap type, trapL, extendH, extendS, pokeL and pokeD and nut type (hex or square).

I think this will do everything my code does and within your constraints. The biggest change is the introduction of gap and the 2 anchors that depend on gap. Lest you be tempted to leave these out, they are the ones I use 90% of the time. The gap determines where the mating surface is and that is almost always the reference point I need when I move the screw hole or screw into place.
One more point. I often render a screw and nut in place in my rendering just to check for correctness, so I need the same anchors in screw, nut, screw_hole and nut_trap.
Well one more. I include a "poke hole" option. I quickly found I liked having a way to get a nut out of a trap.

I notice also that your nut info is not nearly as robust as the screw tables in the code. I think this should be fixed and square nuts accommodated.

Screw_hole also needs the extendH and extendS arguments.

Getting the data and then entering it and organizing it is a major challenge in all of this.  Part of the big problem I was trying to fix is that torx drivers didn't have valid depth info anywhere, and there was generic torx depth in screw_drive.scad that was bogus (much too deep).  I would say we don't have nut info at all...I did recently download a nut spec standard, asme b18.2.2.   But I need to nail down the screws before thinking about nuts.  For the record, the long term plan is to eliminate metric_screws() completely and have screws.scad replace it.  I think the only nut dimensions are in metric_screws.scad right now.  To do nuts seriously presumably means building a nut_info() function analogous to screw_info with lookup tables or formulas for the different nuts.  I don't know how many different sizes/types of nuts there are.  "heavy"  "jam"  hex and square.  Is that all?   One nice thing about the standard I found is that it has formulas in appendix A1 so that means a lot less data entry and tables to worry about.  I found some formulas for flat head dimensions as well when I was trying to straighten that out.   ASME seems nicer about revealing underlying formulas than ISO.
I have non-flat-head anchoring working in my screws code, but am at the moment confused about how anchors should be defined for flat heads.  Once I come to a conclusion about that, I think it shouldn't take long to implement and I can push my current code.   (Though...hmmm....maybe I should also write docs first.)
I wonder if nut_trap should be drawn by the same code that draws nuts.  That would be a way to ensure/enforce uniformity.
The poke hole seems like a great idea.  I've on occasion tried to extract hardware from nut traps with some difficulty.
So "gap" is the point right above the nut, yes?  So if you put the nut somewhere...then the "gap" is automatically right above wherever you just put the nut?   I mean, I was thinking of placing the screw and then making the nut trap relative to the screw, but there's no reason it couldn't be done in reverse: place the nut trap so the "gap" point is where you need it and then place the screw relative to the nut trap.
Could you post an example or two of your code being used to create a nut trap in an actual context that shows the typical use case and makes it clear why the gap is important?  (Something simple, not a huge model with 20 screws.)    I mean, if I understand this all (no guarantees) then you should be able to create the gap you need with something like:
screw_hole(....) position("head_bot") down(gap) nut_trap(....,anchor=TOP);
You mention screw_hole taking a gap argument.  What would it do with that argument?
With your self-tapping screws you are making the holes flat walled but sized to match d_minor instead of d_major?  This is actually a significant issue because if I need access to threading information in screw_hole() I need to change its interface somehow.  (Basically you need to be able to specify threads and then say you don't want threads.  I guess maybe a tapping=true argument could work.)

I don't understand the need for extendH and extendS.  Why not just make the screw longer?

You can't make the head longer. And wouldn't you want the code to reflect the actual length of the screw as opposed to the length of the whole you need? Also the screws come in standard lengths, like M3,12 or M3,16.
I've done designs where the screw is as much as 2-3 cm below the surface, requiring an ExtendH and S of 30. If you have a side trap a standard length for ExtendS of 2mm could be used. But when an inline trap is used you have to know who long it must be.
You could make the extensions really long and assume that could cover all cases, but what if you have an unusual case where the extensions would run into some other part as you difference it out? If you went with this option, you would make head holes, side traps and inline traps very long and standard screw holes 2 beyond the end of the screw. But how long is long enough?

So it sounds like extendH corresponds to my counterbore parameter.
I'm not sure I understand why the code shouldn't reflect the length of the hole being created.  If mean, you could always write length=12+4 or whatever to highlight the relationship to the actual screw.   The one benefit I can see to this extension idea is that it makes it easier to create the screw that goes in the hole.  This is basically a situation where we face the question: is it better to complicate the interface (add a parameter) or simplify the interface (user knows how to use "+" operation if necessary).  I wonder what @revarbat thinks about this.

Getting the data and then entering it and organizing it is a major challenge in all of this. Part of the big problem I was trying to fix is that torx drivers didn't have valid depth info anywhere, and there was generic torx depth in screw_drive.scad that was bogus (much too deep). I would say we don't have nut info at all...I did recently download a nut spec standard, asme b18.2.2. But I need to nail down the screws before thinking about nuts. For the record, the long term plan is to eliminate metric_screws() completely and have screws.scad replace it. I think the only nut dimensions are in metric_screws.scad right now. To do nuts seriously presumably means building a nut_info() function analogous to screw_info with lookup tables or formulas for the different nuts. I don't know how many different sizes/types of nuts there are. "heavy" "jam" hex and square. Is that all? One nice thing about the standard I found is that it has formulas in appendix A1 so that means a lot less data entry and tables to worry about. I found some formulas for flat head dimensions as well when I was trying to straighten that out. ASME seems nicer about revealing underlying formulas than ISO.

No doubt that's a difficult issue. I suspect that even your screw data is incomplete if looked at by an actual ME. I think nut_info should take screw_info as an argument. That specifies a LOT. Then maybe only one more argument to add the nut type.
I'm pretty sure I am getting all my information about metric screws (the only ones I use) from screw_info. I did not realize there is a metric_screws().

I have non-flat-head anchoring working in my screws code, but am at the moment confused about how anchors should be defined for flat heads. Once I come to a conclusion about that, I think it shouldn't take long to implement and I can push my current code. (Though...hmmm....maybe I should also write docs first.)

I really think a flat head should be regarded as having a "top", the flat part and a bottom, the spot where the taper intersects the shaft. That intuitively matches the definition of heads. Also, otherwise the "top" and "head" anchors would be the same and you would have no anchor and the bottom of the taper.

I wonder if nut_trap should be drawn by the same code that draws nuts. That would be a way to ensure/enforce uniformity.

I really don't like that idea. The nut trap is really easy to make. You only need the distance between faces, the nut thickness and the type, hex or square.  I haven't looked yet but I imagine the "oversize" argument to screw REALLY complicates the code. Wouldn't you rather get rid of that? What does it even mean to oversize threads? I think these are prime examples where two different calls makes the code simpler and more understandable.

The poke hole seems like a great idea. I've on occasion tried to extract hardware from nut traps with some difficulty.
So "gap" is the point right above the nut, yes? So if you put the nut somewhere...then the "gap" is automatically right above wherever you just put the nut? I mean, I was thinking of placing the screw and then making the nut trap relative to the screw, but there's no reason it couldn't be done in reverse: place the nut trap so the "gap" point is where you need it and then place the screw relative to the nut trap.

The "gap" anchor is the midpoint between the bottom of the head and the top of the nut. The "nut" anchor is the top of the nut.

Could you post an example or two of your code being used to create a nut trap in an actual context that shows the typical use case and makes it clear why the gap is important? (Something simple, not a huge model with 20 screws.) I mean, if I understand this all (no guarantees) then you should be able to create the gap you need with something like:
screw_hole(....) position("head_bot") down(gap) nut_trap(....,anchor=TOP);

I'll do that.

You mention screw_hole taking a gap argument. What would it do with that argument?

It is used in calculating the custom anchors. Assuming the same anchors are supported by screw and screw_hole uses screw, it would pass gap to screw as well. I have code for the "anchors" argument that fits in a simple function that can take just screw_info as an argument. I'll recode it to use struct_val calls and then you can just drop it into your code. Hopefully tomorrow.

With your self-tapping screws you are making the holes flat walled but sized to match d_minor instead of d_major? This is actually a significant issue because if I need access to threading information in screw_hole() I need to change its interface somehow. (Basically you need to be able to specify threads and then say you don't want threads. I guess maybe a tapping=true argument could work.)

You pass the screw_info structure to screw_hole. The thread info is in there.
Given a day or two I am pretty sure I can code up my stuff in a form pretty close to something you can just drop into yours that will conform to what I described in my long comment yesterday. I hope you'll wait to see that.

This is a simple ring that clamps onto a bar on my golf push cart. It has a mounting for a magnet.  A slice of the cube is removed from within the gap so that the screw pulls the two parts together. Crude but it works. It's on my cart now.
You can see the code that shows the screw in place and the cross section reference that lets me look at it that way.
include <BCLib/common.3.scad>

barD = 31 + 1; // +1 for pad
magnetD = 18;
magnetH = 3;

bandH = 10;
bandT = 3;

screwName = "M3,12";

screwContext = screw_context(name = screwName, gap = 6, entendH = 6, extendS = 6, trap = "inline");

//crossSection("y") {
    left(bandH/2 + 1)
    clamp();

    right(barD/2)
    band();

    fwd(magnetD/2 + 1 - bandH/2)
    right(barD)
    setting();
//}

/*
left(bandH/2 + 1) {
    screw_from_context(screwContext);
    nut_from_context(screwContext);
}
*/

module clamp() {
    difference() {
        cube([bandH, bandH, 14], anchor = CENTER);

        // Remove
        screw_hole(screwContext, anchor = "gap");

        cube([10, 10, 2], anchor = CENTER);
    }
}

module band() {
    difference() {
        ycyl(h = bandH, d = barD + bandT*2);

        // Remove
        ycyl(h = bandH + t2, d = barD);

        left(barD/2)
        cube([10, 10, 2], anchor = CENTER);
    }
}

module setting() {
    right(bandT - tiny)
    difference() {
        xcyl(d = magnetD + 2 + t2, h = 3, anchor = LEFT);

        // Remove
        xcyl(d = magnetD+ t2, h = 4, anchor = LEFT);
    }

    xcyl(d = magnetD + 2 + t2, h = bandT, anchor = LEFT);
}

I am making a PR for my current code, but I don't think anchoring works right yet.  (It turns out the existing posted version has a serious bug, so need to get something out.)   But you might want to take a look at screw_hole().
My expectation is that very few users will use the screw_info structure.  I anticipate that a user will write something like screw("M6",...) to make a screw and screw_hole("M6",...)` to make a hole  As a user, why do I want some extra data structure to hold the information "M6"?   Also, I think the common use case is going to be to use only one of screw() or screw_hole(), not both, so there's no reason to believe a screw specification structure already exists.  I mainly expose that structure for users to enable them to do more complex stuff, like making nonstandard screws, tweak head geometry, or adjusting driver settings.  So all the modules should work without needing a screw_info spec as input.
I can't think of any reason to oversize threads in a perfect world, but for 3d printing, oversizing the threaded hole may be necessary for parts to mate, depending on the accuracy of your machine, and I wouldn't be surprised if a screw needed to be undersized to mate with metal hardware, so a negative oversize.  The way this works is that the diameter just gets oversize added to it.  I don't think it's a massive complication.    The threads keep the same profile.  I don't recall at this point---I think with my printer I was able to get parts to mate by just using the tolerance settings.  But for pipe threads I know I needed to artificially adjust the size of the threads (with the $slop parameter) to mate with an existing pipe threaded fitting.   Since screw() doesn't respect $slop (because it applies to holes only) undersizing may be needed under some circumstances.
The nut_info() function should definitely not take a screw spec as a required argument.   This would lead to the mess of having to do nut_info(screw_info(.....)) to create a nut.  You should be able to write something like nut("M6") and get a nut, and someone who wants to mess around with a nut_info structure should not need to explicitly call screw_info to do it.   It does seem that the way things are currently set up, nut_info will need to call screw_info with a headless screw to get threading info.  Perhaps threading sizing info should be split off into a subfunction?
So for screw_hole and screw the anchoring has to be the same, and the anchoring alone is complicated enough that repeating it seems like a bad idea.   I didn't try to go through screw() and rip out everything that's not needed for screw holes to see if the code got significantly simpler, but why bother?  Keep in mind that users may want to make threadless screws to show in models without time consuming calculation, so we need the ability to make threadless screws anyway.  It's not like it's special for holes.   I do not understand what the point is of splitting the functionality and repeating code.  I see no way in which it doesn't result in increased complications for maintenance.
The way I have it, screw_hole() is basically a pass-through to screw().
Note that there are two different issues here, one having to do with the interface seen by the user and the other one about code internals.  You may be right that there is no benefit from combining nut trap functionality and nut functionality into a single module.

Oh, and I have literally hundreds of pages of standards documents for screws and related stuff.  There's no way that the contents of BOSL2 is even close to "complete".   Wanna go add truss heads?  Oval heads?   Revar wants shoulder screws, which is still a complication.  And note that if you want to make a screw hole for a shoulder screw you need more information now, and more complication if you're repeating it in another module.

Regarding standards, I took a look at asme b18.2.2 and it lists specs for 14 different kinds of nut.
And mysteriously Table 1-1 says "dimensions of square and hex machine screw nuts" and shows a picture of a square nut with the label "no chamfer allowed".  And then two pages later, in Table 2 we see "Dimensions of Square Nuts" and a 25 deg chamfer is required.
I haven't tried to track down the ISO standard for nuts to see what the metric scene looks like.

Ok, here it is. Screw_context is gone. There are basically 3 calls, screw_hole, screw_in_hole and nut_in_hole. All use identical anchors and produce identical positioning for all three outputs when the same inputs are used.
If you look at line 217, there is a commented out call to screw() that can replace the call above it. Note I do not use the "internal" parameter because that is not in the copy of BOSL2 I current have.
I did add the slight counterbore on the my flat head code so it should be flush or close when extendH is 0.
I have kept all the "access" functions rather than use struct_val because I like to hide details of data structure as much as possible.
//   use <../!lib/utils.0.scad>
include <BOSL2/std.scad>
include <BOSL2/screws.scad>

$fn = $preview ? 30 : 90;
_tiny = 1/128;

/*
The default tolerance is a measured amount. Vertical dimensions
in printed holes require this much extra so that a nut will always
fit. A much smaller tolerance can work if the hole's cross section
is in the XY plane. However, the .25 value works in the worst case.
*/
defaultTolerance = .25;

/*************************************************************
Screws and holes are rendered vertically with head at the top
Slots for nut traps are in the direction of +X

extendH extend the hole at the top of the head
extendS extendthe hole at the bottom of the crew
gap is the space between head and nut

The screw hole is generated vertically.
If there is a side trap, it extends toward the +X axis.
If there is a poke hole with the side trap, it extends on -X

nut - "none", "hex", "square" or "self", default: "hex"
trap - "none", "side" or "inline", default: "inline"

Several special anchors are defined. All these anchors
are on the Z axis. The designated anchor will be at the XYZ origin.

    It is vertically positioned as designated in the context.
    If the trap is a side trap, it extends in the direction of the x axis.
    ANy poke hole is opposite the side trap.

screw_from_context(sc, anchor = "gap", spin = 0, orient = UP)
    sc - screw context

nut_from_context(sc, anchor = "gap", spin = 0, orient = UP)
    sc - screw context
*/

/*

Nut Info information

diameter - distance face to face
height   - thickness

Hex and square nuts appear to have the same specs
This table only covers metric nuts for screws from 3 to 10 mm
*/

function _NutInfo(si) = let (d = struct_val(si, "diameter", 0))
           (d == 3)  ? [["diameter", 5.5],  ["height", 2.4]] :
           (d == 4)  ? [["diameter", 7.0],  ["height", 3.2]] :
           (d == 5)  ? [["diameter", 8.0],  ["height", 4.0]] :
           (d == 6)  ? [["diameter", 10.0], ["height", 5.0]] :
           (d == 7)  ? [["diameter", 11.0], ["height", 5.5]] :
           (d == 8)  ? [["diameter", 13.0], ["height", 6.5]] :
           (d == 10) ? [["diameter", 17.0], ["height", 8.0]] : [];

/*
Functions for accessing the screw_context.
*/
function _SV1(s, n1, d = 0) = struct_val(s, n1, d);
function _SV2(s, n1, n2, d = 0) = len(struct_val(s, "spec", [])) == 0
                                ? struct_val(s, n2, d)
                                : struct_val(struct_val(s, n1, ""), n2, d);

function _ScrewD(si)    = struct_val(si, "diameter", 0);
function _ScrewL(si)    = struct_val(si, "length", 0);
function _ScrewP(si)    = struct_val(si, "pitch", 0);
function _HeadHV(si)    = struct_val(si, "head_height", 0);
function _HeadDV(si)    = struct_val(si, "head_size", 0);
function _HeadType(si)  = struct_val(si, "head", 0);
function _Drive(si)     = struct_val(si, "drive", 0);
function _HeadA(si)     = struct_val(si, "head_angle", 0);

function _NutD(si)      = struct_val(_NutInfo(si), "diameter", 0);
function _NutH(si)      = struct_val(_NutInfo(si), "height", 0);

/*
    The head height value for flat screws in screw_info is 0.
    For our purposes though, the head height is the distance
    from head top surface to beginning of threads.
*/
function _HeadH(si)   = (_HeadType(si) == "flat")
                      ? (_HeadD(si) - _ScrewD(si)) / 2 / tan(_HeadA(si) / 2)
                      : _HeadHV(si);

function _HeadD(si)   = (_HeadDV(si) == undef)
                      ? _ScrewD(si)
                      : _HeadDV(si);

function _WholeScrewL(si)  = (_HeadType(si) == "flat")
                      ? _ScrewL(si)
                      : _ScrewL(si) + _HeadH(si);

function _ThreadL(si) = (_HeadType(si) == "flat")
                      ? _ScrewL(si) - _HeadH(si)
                      : _ScrewL(si);

function _HoleL(si, extendH, extendS) = extendH + extendS + _WholeScrewL(si);

// The following are all positions relative to the middle of the entire screw
// IE TOP, top of the screwr hole is 0.
function _TopOfHead(si)      = _WholeScrewL(si)/2;
function _BotOfHead(si)      = _TopOfHead(si)      - _HeadH(si);
function _CenterGap(si, gap) = _BotOfHead(si)      - gap/2;
function _TopOfNut(si, gap)  = _CenterGap(si, gap) - gap/2;
function _BotOfScrew(si)     = _TopOfHead(si)      - _WholeScrewL(si);

// Adding the adjustment gives the position of the anchors after the
// screw_hole plus extensions is centered.
// The adjustment needed is (extendH + extendS) / 2.
function _anchors(si, gap, adjust = 0) =
    [
        named_anchor("top",  [0, 0, adjust + _TopOfHead(si)]),
        named_anchor("head", [0, 0, adjust + _BotOfHead(si)]),
        named_anchor("gap",  [0, 0, adjust + _CenterGap(si, gap)]),
        named_anchor("nut",  [0, 0, adjust + _TopOfNut(si, gap)]),
        named_anchor("bot",  [0, 0, adjust + _BotOfScrew(si)]),
    ];

/*************************************************************
screw_hole

si      - the structure returned by screw_info
gap     - the distance in mm between head and nut in the assembly
nut     - "hex" (default), "square", "self", "none"
trap    - "none" (default), "inline", "side"
extendH - clearance above the head, ie additional length of the hole for the head
extendS - clearance below the screw, ie additional length of the hole for the screw
trapL   - trap length for a side trap, defaults to 25
pokeL   - poke hole length - a hole opposite the nut trap to allow the nut to be poked out
          default is -1 : no poke hole
          > 0 means include a poke hole
          1 means use the default length of 25
pokeD   - diameter of poke hole, defaults to 2
t       - tolerance added to diameters so screws will fit in holes, default is "defaultTolerance"

anchor  - anchor, The usual set plus the custom anchors described above
spin    - as usual, default to 0
orient  - as usual, default to UP
*************************************************************/

module screw_hole(si,
                  gap = 0,
                  nut = "hex",
                  trap = "none",
                  extendH = 1,
                  extendS = 1,
                  trapL = 25,
                  pokeL = 25,
                  pokeD = 2,
                  t = defaultTolerance,

                  anchor = "gap",
                  spin = 0,
                  orient = UP)
{
    // for a self tapping screw extendS must be >= 1
    eS = (nut == "self" && extendS < 1) ? 1 : extendS;
    hole_length = _WholeScrewL(si) + extendH + eS;
    adj = (eS - extendH) / 2;

    struct_echo(_anchors(si, gap = gap, adjust = adj), "_anchors");
    echo("TOP", _HoleL(si, extendH, eS)/2);
    echo("...", _WholeScrewL(si), extendH, eS, adj);

    attachable(anchor = anchor,
               spin = spin,
               orient = orient,
               d = _HeadD(si) + t,
               h = hole_length,
               anchors = _anchors(si, gap = gap, adjust = adj))
    {
        union() {
            if (nut == "self") {
                up(hole_length/2 - extendH) {
                    if (extendH > 0) {
                        zcyl(d = _HeadD(si) + t, h = extendH, anchor = BOTTOM);
                    }

                    _self_tap(si, gap = gap, extendH = extendH, extendS = eS, t = t);
                }
            } else {
                up(hole_length/2 - extendH) {
                    if (extendH > 0) {
                        #zcyl(d = _HeadD(si) + t, h = extendH, anchor = BOTTOM);
                    }

                    _screw_hole(si, t);
                    //screw(spec = si, shank = _ScrewL(si), oversize = t, anchor = TOP);

                    down(_TopOfHead(si) - _TopOfNut(si, gap))
                    if (trap == "side") {
                        _side_trap(si, nut = nut, trapL = trapL, pokeL = pokeL, pokeD = pokeD, t = t);
                    } else if (trap == "inline") {
                        inlineTrapL = extendS + _TopOfNut(si, gap = gap) - _BotOfScrew(si);
                        _inline_trap(si, nut = nut, trapL = inlineTrapL, t = t);
                    }

                    down(hole_length - extendH)
                    if (extendS > 0) {
                        zcyl(d = _ScrewD(si) + t, h = extendS + _tiny, anchor = BOTTOM);
                    }
                } // up
            }
        }

        children();
    } // attachable()
} // screw_hole

module _screw_hole(si, t) {
    union() {
        // Head
        _head(si, anchor = TOP)

        // Screw + extention
        attach(DOWN, UP)
        zcyl(h = _ThreadL(si) + _tiny, d = _ScrewD(si) + t);
    }
} // _screw_hole

module _self_tap(si, gap, extendH, extendS, t) {
    // Return centered
    union() {
        // Head extention
        up(_tiny)
        zcyl(h = extendH + _tiny, d = _HeadD(si) + t, anchor = TOP)

        // Head
        attach(DOWN, UP)
        _head(si, t = t)

        // Screw hole
        attach(DOWN, UP)
        zcyl(h = gap + 1, d = _ScrewD(si) + t) // gap + 1 extends the full size hole 1 mm into the tapped surface

        // Narrow self tapping section
        attach(DOWN, UP)
        zcyl(h = _ThreadL(si) - gap + extendS, d = _ScrewD(si) - _ScrewP(si));
        // extendS is always >= 1, along with the (gap + 1) above, the tapped hole is always >= 2 mm longer than the screw
    }
} // _self_tap

module _head(si, t = defaultTolerance, anchor = TOP, orient = UP, spin = 0) {
    head_height = _HeadH(si) + _tiny;
    head_diameter = _HeadD(si) + t;

    attachable(anchor = anchor, spin = spin, orient = orient, d = head_diameter, h = head_height) {
        if (_HeadType(si) == "flat") {
            r2 = (_HeadD(si) + t) / 2;
            r1 = (_ScrewD(si) + t) / 2;
            down(t - _tiny)
            cyl(r1 = r1, r2 = r2, h = head_height, anchor = CENTER)
            attach(TOP, BOTTOM)
            cyl(r = r2, h = t);
        } else {
            zcyl(h = _HeadHV(si) + _tiny, d = head_diameter, anchor = CENTER);
        }

        children();
    }
} // _Head

module _side_trap(si, nut, trapL, pokeL, pokeD, t) {
    // returns trap with top of trap at the origin (nominally) and the trap extending along the X axis
    // The top of the trap is actually t above the XY plane
    // Slot
    up(t/2) {
        cube([trapL, _NutD(si) + t, _NutH(si) + t], anchor = [-1, 0, 1]);

        if (nut == "square") {
            cube([_NutD(si) + t, _NutD(si) + t, _NutH(si) + t], anchor = [0, 0, 1]);
        } else {
            down(_NutH(si) + t)
            _hexCyl(h = _NutH(si) + t, id = _NutD(si) + t);
        }

        // Poke hole - extends along the -X axis
        if (pokeL > 0) {
            down(_NutH(si)/2)
            xcyl(h = (pokeL == 1 ? 25 : pokeL), d = pokeD, anchor = [1, 0, 0]);
        }
    }
} // _side_trap

module _inline_trap(si, nut, trapL, t) {
    // returns trap with top of trap at the origin
    echo(nut, trapL, t, si);

    up(_tiny)
    if (nut == "square") {
        cube([_NutD(si) + t, _NutD(si) + t,  trap_len], anchor = [0, 0, 1]);
    } else {
        down(trapL)
        _hexCyl(l = trapL, id = _NutD(si) + t);
    }
} // _inline_trap

module _hexCyl(l = -1, h = -1, id = -1, d = -1) {
    H = (h > 0) ? h : l;
    ID = (id > 0) ? id : d;

    linear_extrude(H)
    hexagon(id = ID);
} // _hexCyl

/*************************************************************
screw_in_hole
Renders a screw positioned as it would be in a screw hole using the same custom anchors

si      - the structure returned by screw_info
gap     - the distance in mm between head and nut in the assembly
trap    - "none" (default), "inline", "side"
extendH - clearance above the head, ie additional length of the hole for the head
extendS - clearance below the screw, ie additional length of the hole for the screw
t       - tolerance added to diameters so screws will fit in holes, default is "defaultTolerance"

anchor  - anchor, The usual set plus the custom anchors described above
spin    - as usual, default to 0
orient  - as usual, default to UP
*************************************************************/

module screw_in_hole(
    si,
    gap = 5,
    extendH,
    extendS,
    t = defaultTolerance,

    anchor = "gap",
    spin = 0,
    orient = UP)
{
    hole_length = _WholeScrewL(si) + extendH + extendS;
    adj = (extendS - extendH) / 2;

    attachable(anchor = anchor,
               spin = spin,
               orient = orient,
               d = _HeadD(si) + t,
               h = hole_length,
               anchors = _anchors(si, gap = gap, adjust = adj))
    {
        up(hole_length/2 - extendH - _WholeScrewL(si))
        screw(spec = si, anchor = BOT);

        children();
    } // attachable()
} // screw_from_context

/*************************************************************
nut_in_hole

si      - the structure returned by screw_info
gap     - the distance in mm between head and nut in the assembly
nut     - "hex" (default), "square", "self", "none"
extendH - clearance above the head, ie additional length of the hole for the head
extendS - clearance below the screw, ie additional length of the hole for the screw
t       - tolerance added to diameters so screws will fit in holes, default is "defaultTolerance"

anchor  - anchor, The usual set plus the custom anchors described above
spin    - as usual, default to 0
orient  - as usual, default to UP
*************************************************************/

module nut_in_hole(
    si,
    gap = 5,
    nut = "hex",
    extendH,
    extendS,
    t = defaultTolerance,

    anchor = "gap",
    spin = 0,
    orient = UP)
{
    hole_length = _WholeScrewL(si) + extendH + extendS;
    adj = (extendS - extendH) / 2;

    attachable(anchor = anchor,
               spin = spin,
               orient = orient,
               d = _HeadD(si) + t,
               h = hole_length,
               anchors = _anchors(si, gap = gap, adjust = adj))
    {
        up(hole_length/2 - extendH - _TopOfHead(si) + _TopOfNut(si, gap))
        _nut(si, nut);

        children();
    } // attachable()
} // nut_in_hole

module _nut(si, nut) {
   if (nut == "square") {
        difference() {
            cube([_NutD(si), _NutD(si), _NutH(si)], anchor = TOP);

            // Remove
            screw(spec = si);
        }
    } else if (nut == "hex") {
        nut(diameter = _NutD(si), thickness = _NutH(si), spec = si, anchor = TOP);
    }
}

/***************************************************************
*
* Test Code
*
***************************************************************/

_test();

module _test() {
    showAnchor = "all";

    trap_angle = 180;
    name = "M5,16";
    head = "socket";
    //siHead = "flat";
    //siHead = "button";
    drive = "hex";

    gap = 8;
    nut = "hex"; // "hex, "square", "self"
    trap = "side"; // "inline", "side", none
    poke = 10; // -1, 1
    trapL = 25;

    extendH = 6;
    extendS = 3;

    si = screw_info(name = name, head = head, drive = drive);

    function show(p) = (p == showAnchor || showAnchor == "all") ? true : false;

    echo(show(TOP), show("top"));

    xdistribute(20) {
        ydistribute(15) {
            if (show(TOP))
                screw_hole(si,
                           anchor = TOP,
                           spin = trap_angle,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);

            if (show("top"))
                screw_hole(si,
                           anchor = "top",
                           spin = trap_angle,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
            if (show("head"))
                screw_hole(si,
                           anchor = "head",
                           spin = trap_angle,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
            if (show("gap"))
                screw_hole(si,
                           anchor = "gap",
                           spin = trap_angle,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
            if (show("nut"))
                screw_hole(si,
                           anchor = "nut",
                           spin = trap_angle,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
            if (show("bot"))
                screw_hole(si,
                           anchor = "bot",
                           spin = trap_angle,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
            if (show(BOT))
                screw_hole(si,
                           anchor = BOT,
                           spin = trap_angle,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
        }

//*
        ydistribute(15) {
            if (show(TOP))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = TOP);
                nut_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = TOP);
            }
            if (show("top"))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "top");
                nut_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "top");
            }
            if (show("head"))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "head");
                nut_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "head");
            }
            if (show("gap"))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "gap");
                nut_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "gap");
            }
            if (show("nut"))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "nut");
                nut_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "nut");
            }
            if (show("bot"))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "bot");
                nut_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "bot");
            }
            if (show(BOT))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = BOT);
                nut_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = BOT);
            }
        }
        //*/
    }
}

I have a question about creating attachable objects. How is the problem of coincident faces handled. I made my objects larger than the outside anchor points by _tiny, which I have set to 1/128. How does the rest of BOSL2 handle it?

There is an overlap argument and also $overlap global that makes attached objects sink in by that amount.  I think Revar had it default to something nonzero, but then that lead to some kind of confusing situation.
My thinking on this matter is that if you are making a mask, then by all means, make it slightly bigger than its attached volume so that it subtracts in a well-behaved manner.  But if you are making an object, think hard before doing this kind of thing, because it means you'll be making an object explicitly different than what you said you were making.  This could result in a 3d model where objects aren't all on the same z base, which might conceivably create problems for printing.  (I once had a series of print failures that were a result of sphere() not actually producing the specified radius, resulting in a small offset of part of the model.)  We used to have $slop set with a nonzero default and that also created a problem where a user wondered why an object was the wrong size.
But consider the implications of making things oversized:  you request a 1x1x1 cube and get a 1.001x1.001x1.001 cube.  It's a little puzzling.  And if you make the cube the right size and then put the anchors inside you can end up with a cube that's not where you expected it.
I think that the majority of the objects in the BOSL2 library were designed before the attachments system was invented, so there may be room for improvement.  At this moment I still haven't quite nailed down how attachments will work for screws and screw holes, and shoulder screws are presenting various questions.   (For example on attachments, I want it to be natural to make holes that are spaced away from a wall by a specified amount, or whose heads are spaced away from a wall by a specified amount.)  Did you make "top" of a length L screw the point distance L from the screw's tip?  That is, the point above the head on a flathead and underneath the head on other screws?
Note that you can still move objects around even after using attach or position.  So something like attach() down(tiny) thing() is a way to get an overlap, though admitedly it seems somewhat clumsy.
Also I talked briefly with Revar and he does not like the idea of two length arguments (e.g. length and extendS).

First, I found some problems in the self tap code posted above. I'm fixing them.
I see the problem with making objects the "wrong" size behind the scenes. As you said, no problem for screw_hole I think.
There aren't 2 length arguments. When you call screw_info, you specify the length of the screw. When you call screw_hole then you need to specify how far above and below the hole must go. I don't see a way around that at the moment.
Currently I assume that the head height in the screw tables is the max height of a given head type. I found that for flat screws, the head height was always 0. My code treats it differently for calculating where the "head" anchor goes because by my definition it is the bottom of the head. The virtual cylinder that the screw_hole anchors are using uses head_diameter, so if you want to place a hole n mm from a wall, that should be easy. By default, the screw_hole is aligned on the Z axis so placing the center of a hole n mm from a wall is also easy.
My "top" anchor is the top of the head, wherever that is. The "head" anchor is the bottom of the head. So "head" is L above the tip of an MDxL screw. I don't have an anchor for the end of a shank, but that would be easy to add. The TOP anchor of a screw_hole is the top of the hole including the head extension.

I've realized that extendS really only has meaning when an inline trap is wanted. ExtendS could be replaced by trapL for the inline trap case and all other screw holes could be made 1 or 2 mm longer than L. 1 is probably enough in all cases though, right?

Right now you can specify a screw with screw("M6", length=25) or you can do screw("M6,25").  If we add an "extend" argument so you have length=25, extend=2 then there will be two arguments that specify length.   The head height in the tables is the height of the head above the screw, or the length the head adds to the screw length.  Maybe that's a design error and it should be the actual head height.  The original code never even computed the head height for flat heads.  Is that value in the tables the maximum height?  Maybe, but I wouldn't guarantee it.  It's possible it might be the middle height height allowed by the tolerance range or typical head type or the head type of a screw sold by McMaster.
    And I guess it seems like there should be an anchor that on a screw of length L is distance L from the tip, but I don't know what to call that anchor.   This would be on top of a flat head but underneath other heads and seems like the natural point of reference for a screw.

I think the hole should be the length the user requested.  If the user wants it longer, the user can request it longer.  This is not a case where excess length will vanish, unless the user is making a through hole.
If the inline trap is a separate module (which is how Revar and I think) then it will have its own length parameter, just like the sideways trap needs a length argument to specify how far it extends to the side.

Extend (or trapL) is not relevant to screw_info, only screw_hole.
The cylinder for the anchors should be the diameter of the head. It's really hard for a user to obtain the head diameter so having anchors there helps the user. if the user really wants to reference the shaft, the shaft diameter and its center are both known.

No, actually, shaft diameter is not known.  Only the nominal shaft diameter is known.

There will need to be multiple anchor types.  I had actually wondered if we could make the anchor type system cleaner, but it seemed like maybe it's difficult on the implementation side.  I was thinking something like anchor=["head",BOT], anchor=["threads", TOP], or whatever, as compared to the more clumsy "atype" system currently used for this.

I remembered why I didn't put the flathead height into the screw structure:  the flat head height isn't known until you determine the screw diameter, and the screw structure only contains the nominal diameter, not the actual diameter, so it's impossible to compute it.

No, actually, shaft diameter is not known. Only the nominal shaft diameter is known.

Technically, all the measurements are nominal. I don't see how that matters. The standards are mostly for maximum measurements just so you can ensure fit.

I remembered why I didn't put the flathead height into the screw structure: the flat head height isn't known until you determine the screw diameter, and the screw structure only contains the nominal diameter, not the actual diameter, so it's impossible to compute it.

I don't understand why this matters for screw holes. The nominal screw diameter is a known maximum and the hole has to be based on that.

There will need to be multiple anchor types. I had actually wondered if we could make the anchor type system cleaner, but it seemed like maybe it's difficult on the implementation side. I was thinking something like anchor=["head",BOT], anchor=["threads", TOP], or whatever, as compared to the more clumsy "atype" system currently used for this.

This seems needlessly complicated since I found a rational and so far very useful set of anchors based on the current system with a single anchor argument.
I don't understand what you mean by "atype" system.

If the inline trap is a separate module (which is how Revar and I think) then it will have its own length parameter, just like the sideways trap needs a length argument to specify how far it extends to the side.

I get the impression that your desire for this is all based on wanting to enhance the existing screw code and use it for drawing screw holes. With that starting point, I don't see how you get a total system that allows you to position screw, nut, screw_hole and trap all simply and easily. The screw interface is already very complicated and hard to understand. Case in point is that I had no idea there were features intended to let you use it for screw holes.
My API for screw_holes is ONE module that creates screw_hole and trap and positions it with a wide variety of anchor points. Two other modules can position a screw or nut within that hole, all using identical arguments and anchors. I don't see how you can achieve that with what you are contemplating.

"Technically all measurements are nominal"
Huh?  That doesn't make any sense.  In fact,  no measurement is nominal because if you have measured something then that makes it actual (with error bars if you like).  A nominal dimension is a a name, hopefully with some connection to the real size of the part, like "2x4" lumber which is actually a smaller size.  In the case of screws, if you request an M6 screw the model you get from screw() will not be 6mm in diameter, but will be adjusted according to the ISO tolerance formulas.  The actual dimension of the screw is the size that OpenSCAD actually creates the part at.  That is clearly a different thing than the nominal dimension.   Note that the nominal screw diameter is also not the same as the diameter for a hole.  ISO/ASME specify tolerances for this and the hole must be even larger.  If you base the flat head height on nominal rather than actual diameter then the flat head will have the wrong size, or the anchor point that is supposed to be under the head won't actually be at the intersection of the head and shaft, or you could potentially end up with a small gap between the shaft and head---depending on how the implementation works.  Basically something will go wrong somewhere, I think.
Avoiding this complication was actually why I originally modeled flat heads as full cones and just unioned them with the screw, but this produced bad threads at the top and for very short screws, the cone would be too long.  So it wasn't really a satisfactory solution.  But the head height is a derived value so if I'm going to compute the head height, I want it to be correct, not just sort of correct.
Regarding an anchor located at the underside of a flat head (top of the screw shaft): is there any use for this?  Neither Revar nor I could think of one.
There needs to be a way to anchor on the side of the shaft.  There needs to be a way to anchor on the side of the head.  These are two inconsistent requirements.  The solution we have for this is to allow you to select a different attachment geometry using atype, which is short for attachment type.  So you might do screw(....,atype="shaft", anchor=RIGHT) to anchor on the right of the shaft, but screw(...,atype="head", anchor=RIGHT) to anchor on the right side of the head.   There are various other places around BOSL2 where we needed multiple conflicting attachment geometries, which is why we introduced atype.  But I do think it feels like a kludge, and it's inflexible because you have to do all your anchoring relative to a single anchor type.  The idea above is cleaner because it combines the anchor type with the anchor location, so at least syntactically it would allow for multiple different attachment geometries to be used at once.
As far as I can tell, the complications for the screw model are there because modeling screws is complicated.  Modeling screw holes basically comes for free once the screw model works, so why wouldn't I take advantage?  (The one single addition needed is the table of screw hole tolerances from ASME B18.2.8 and ISO 273, and I guess forcing flatheads to have sharp heads.)  I don't understand what simplifications I could make if I decided to duplicate the code and have two entirely separate modules for screws and holes.  It's a negligible simplification of the screw() code and it means the hole making code is more complicated and creates a maintenance headache due to duplicated code.
I do not like the idea of the trap as part of the hole code, as I've mentioned before.  When I mentioned this idea to Revar he immediately said that the trap should be a separate attachable module.  I have not explored this yet to see if there is some compelling reason to bind together two separate objects like you want to do.

Note, just to be clear about screw code:  all of the threading code in the whole library is intended to make threaded rods or screw masks.  The idea that I got this idea to enhance screw() to make holes is not right.  It's more like I realized that the capability that was supposed to be there was missing.  I originally wrote the screw code a couple years ago and got burnt out on it and didn't really finish, so implementation gaps and deficiencies are no big shock.

I do not like the idea of the trap as part of the hole code, as I've mentioned before. When I mentioned this idea to Revar he immediately said that the trap should be a separate attachable module. I have not explored this yet to see if there is some compelling reason to bind together two separate objects like you want to do.

I could easily separate the trap code from the hole code. However, the trap always attaches its TOP to the hole's "nut" anchor (in my set of anchors.) Why make the user do this if it is always the same?

It seems like I might not want the trap there.  What if I have 20mm long screws and want the trap 8mm up from the bottom to work with my model?

Is it possible to use two nut traps to connect three parts together?

I suppose there's probably a better solution.

The your gap argument would be set to 12, 20 - 8.

Is it possible to use two nut traps to connect three parts together?

Can't see how.

The gap argument IS the distance between bottom of head and top of nut. By definition that's where the nut trap goes.

But this means you're making a different mechanism for positioning an object relative to another object instead of using the existing anchoring mechanism.    Also in my example I want to position the nut trap relative to the bottom of the screw, not the top.

Here's another example:  my model has some external feature that defines where the trap needs to go, so I want to position the trap relative to that feature and wherever it hits the screw, I don't much care or want to try to figure out---maybe I don't know where that point is if it's relative to other objects.

all of the threading code in the whole library is intended to make threaded rods or screw masks.

The ability to draw screws and nuts in the total assembly is valuable, what Nop Head calls vitamins (does everyone call it this?) So very precisely drawing screws using ISO tolerances is not really necessary. It's only needed for what you want to print. I see that people print larger threaded items sometimes but they are not printing M5 screws AFAIK.
We do print screw holes though. Every document I have looked at about screws has listed max values for things like head diameter and screw diameter. I don't think I have encountered a case the max of something did not match the nominal value. The docs I looked at were engineering docs not the ISO standards. I did measure all the screws I have bought and they are indeed tiny bit below nominal (max) and they are not necessarily all the same. But since I am making holes, it seems only the max is really important.
BTW, I fully feel your pain re standards. Back in the 80s and 90s I was on various standards committees both US and international for networking protocols. Seeing the sausage made is perhaps worse than having to read and interpret it. People in the standards business also have to get used the fact that their standards are often very loosely interpreted.

I printed 1/4-20 stuff and it worked fine, both screws and threaded holes.   I think M5 would work.   Would I trust it to hold my weight.  I don't think so.  But no reason someone is not printing it for light duty use. I mean, is someone printing separate screws?  Probably not a common application, though there are youtube videos about how to do this well, and the effect of print orientation on printed screw strength.   But parts that have male threaded components, it's a possibility.   Printing threaded holes seems like a better plan.  Also someone with an SLS printer could easily be printing much finer parts.  Someone printing in metal could also be printing small parts.  The point is that assuming that we can be inaccurate because "nobody cares" doesn't make sense to me.  If you want that, you don't need a screw library.  The point of having a library is that it does it in some substantially correct way---that is, it does the hard stuff.  If you just want to make 6mm holes for your M6 screws you don't need a library for that.  The other thing is that making proper threading work at large scales automatically makes it work at small scales---I mean, unless you build your thread lookup tables to fail below M16 or something.  All the core logic is all the same.
  With the "normal" tolerance the smallest allowed size is 0.281".  Smallest allowed hole for an M6 bolt is 6.4mm.
If you're drawing screws in assembly you probably don't want to waste time drawing the threads, which is one of the reason that drawing screws isn't different than drawing screw holes.  You can set tolerance to zero if you want to make them at nominal size---and if you're confident CGAL won't barf on the overlapping hole and screw surfaces.  But the point is that you're now basically arguing for a separate capability:  saying that sometimes you don't need it accurate doesn't mean that there aren't other times where you do.  It's easy to make it inaccurate.  That's not the hard part.   Also there's no harm in drawing the screws at the correct size (other than code complexity, I suppose).  It doesn't take more time.    I feel like I've been a little overly verbose here to make this main point:  there are times when you need the screws or holes to be accurate.  Therefore the code needs to be able to produce accurate screws and (possibly threaded) holes.  As a result, there's no benefit in cutting corners, because it just multiplies the number of cases: the accurate case and the corner-cut case.
I've never modeled something where I wanted to bother including the screws.  It doesn't seem like a useful thing to do from a modeling perspective.  It's seems like it would only be useful if you want a pretty picture of the assembly, put together with its screws.  I've never heard the term "vitamins" applied that way before.  Nophead builds very big, complicated assemblies with lots of parts, so he may know something I don't.  But basically if you are trying to use this scheme to "debug" your model but the screw making code is the same as the screw_hole making code then it will just automatically work, so if you have problems, it doesn't seem like they would be revealed by adding the screws.  They might even be concealed (consider screw holes that intersect each other).

I never meant to argue against that kind of accuracy and detail for modeling of screws. I just don't think it can be used as a reason not to draw screw holes in the manner that I do.
If there are standards for holes related to screws I'd be happy to incorporate them. I'd love to see such numbers in screw_info. I'd certainly use them.
I think it's also true that standards are written with machining in mind. The kind of 3D printing most of us do has errors of .1-.2 mm for features in the 1-10 mm range. I suspect the standards are aimed at machined parts, drilled, CNC, etc. Anyone doing 3D printing is probably adjusting for their particular print process. I know with my own printer, different numbers are good for a horizontally printed hole and a vertically printed hole. So a "t" or "oversize" parameter is required regardless of whether we have "min hole size" data element.
Anyway I appreciate all the discussion here. I think my code is a lot better now as a result. If you don't include something like it, it will be no problem for me to continue to use it.

If you're drawing screws in assembly you probably don't want to waste time drawing the threads,

I've never modeled something where I wanted to bother including the screws. It doesn't seem like a useful thing to do from a modeling perspective. It's seems like it would only be useful if you want a pretty picture of the assembly, put together with its screws.
I do this about half the time I'd guess, and I do like to visualize how everything fits together.  I came up with a crossSection() module so I can see inside quickly as well. I use that a lot.
You have a point about how the screw being positioned "automatically" defeats some of the purpose of this kind of visual checking. However, part of that is because the method makes it hard to make a mistake. For example if I just make a mistake positioning the screw, it's not really a mistake because the screw is just there for visualization.

You mentioned that you don't see a need for my "head" anchor, the bottom of the head. Though it would not be used often it is a key feature of the hole.  The binding force is applied at the top of the nut and the bottom of the head. Both spots are clearly important. Imagine the simplest case, a 2 mm sheet to be screwed to a 4 mm sheet. If the head is to sit right on the 2 mm sheet then you can use the "head" anchor and move it to the top surface of the 2 mm sheet. Or move the "head" anchor to the "mating surface" then up 2 mm to be at the top of the top sheet.
Anyway, the feature is important so I am pretty sure somepeopel will find uses for it.

I have a test part I printed with 10mm holes and I measured them at somewhere between 10.00 and 9.99 mm.  So my printer at least is more accurate that 0.1-0.2 mm!  But also, really, there is other tech out there.  I haven't checked the accuracy of printing in aluminum or stainless steel on Shapeways.   But I know that SLA printers are ten times more accurate than the type of printer I have.  Maybe even more accurate than that.  And while they're more expensive than the extrusion type machines, they're not out of reach for home use.  (I have no clue how strong the material is, though.)
Right now the standard hole clearance data is embedded in the screw_hole() module in my code, which is posted to github, so you can take a look if you want.   Personally I found the size of the clearance holes bigger than I would have expected, so I'm just obediently following the standards in this matter.   The problem with putting it in screw_info is that screw_info has nominal data.  The tolerances are in a different structure.  I'm not sure it makes sense to put the clearance tolerances there, though, because there's a different tolerance terminology for the clearance holes than for threading, so combining it doesn't buy you anything, really.  It may make sense to split it into a separate function, though.
I was not talking about your anchors but my anchors.  I've just been trying to figure out what I think is the fully general set of anchors.  The anchor I don't see a use for is the bottom of a flat head.  Under a regular head is very important, I totally agree.  In fact I'd think it might be the most important anchor point for a screw.   The exact location of the "bottom" of a flat head seems like it's a little bit uncertain, since it will change a bit if you change screw tolerance, or use the nominal diameter, or make a clearance hole.
How is crossSection any different than either projection() or the BOSL2 front_half(), half_space(), etc?

I'm not using accuracy as the argument against your approach but rather that your approach seems to lead to duplication, that is, we end up with two different screw_hole functions.  In the context of a library we want to try to make the interface clean and not have two modules that do the same thing.  We haven't been entirely successful at this, but mostly, I think.  I put a lot of effort into going over the library and just trying to decide whether everything was consistent and and seemed to have reasonable names and so on.  And if it seemed like we had duplicate functionality, I asked: can we merge it or eliminate one of the duplicates.   (There are places where Revar and I wrote the same code and we didn't realize it.  Revar wrote a polygon rounding and chamfering function that duplicated my round_polygon except it also did chamfers, so trying to merge that functionality was complicated.)

The problem is that your current code can make a screw hole, but only maybe 40% of what is needed. The head part of the hole must have an extension. The head (hole) height needs to be increased as well as the diameter. If a screw is to be used self tapping, the "tapping" part of those must be smaller in D than the threads and a couple of mm longer. If there is a trap, you need to have all sorts of dimensions for that trap. Both inline and side traps may need to be of arbitrary length and depend on whether a hex or square nut is being used. Lastly you need to position the trap relative to the screw hole.
I agree with you that code should exist in one place. The place for a screw_hole code is not as part of screw(). The code you have added to screw() so far only gets you part way. The only code that might be duplicated in mine is the code for heads and that appears to be less than a half dozen lines. And it would be easy to call your _head module instead if you wanted.
Since the screw() code cannot realistically be expanded to the whole screw hole job, there should be a screw_hole() module and all screw functionality should be removed from screw() to make it simpler and clarify that screw_hole is the way to do that. Two other modules, nut_in_hole() and screw_in_hole() exist only to draw a nut or screw in a screw_hole() that has  been created. There are there just for convenience and call the existing nut() and screw() modules so they do not duplicate any code.

Have you actually looked at the current code?
For screw hole you use the counterbore argument which extends a flathead or replaces the specified head for other types of head.  The clearance size by default should be adequate---I used the ISO/ASME clearance that applies to the threads on the head as well---but if it's not right you can adjust with the various oversize parameters.  And of course $slop applies to aid with printer overextrusion.
I have asked this before:  what can I delete from screw if I remove the functionality that is there only for holes?  I think the answer is I can delete two things:  the (hidden) _counterbore argument and the _internal argument.   Doing this does not create some huge simplification.  The cost of this is that I have to make screw_hole() basically a copy of all the code from screw() instead of a pass-through module, and screw() is about 135 lines of code.  I really don't understand how a second copy of that code is desirable.   Note that most of screw() is parameter wrangling, error handling, and attachment support, and a screw_hole() module needs to do a lot of that same stuff.  Your code is lacking in error handling.   My screw() implementation has 19 lines of error handling.  I wish I didn't have to write error handling, but it's essential in a library.  We don't have enough.   Attachment support is about 40 lines.   Taking out _counterbore would eliminate two lines of code I think and simplify some others.  Taking out internal has almost no impact.  So where's the big gain?
As I have said before, traps appear to me to be separate objects, and should therefore be produced by separate modules.  I do not think that screw() should produce traps.  Definitely not.   I actually think that inline traps and side traps should be two separate modules---they are totally different shapes and will have different anchoring.
Regarding what you're calling "self tapping", that is something I need to look in to.  I'm not sure what a hole like that should be called.  The hole isn't self-tapping, and neither is the screw---it's just a normal machine screw.     In any case, it seems like it should be possible to make these using the existing screw() code, though it might be annoying because of having to evaluate the tolerance outside of screw to figure out the dimensions.  That seems kind of ugly.
Hmmm.  I went and looked at your code.  One observation is that all of your extra calls like _ScrewD() conceal what's actually happening, so they function as code obfuscators.  When I see _ScrewD I don't know which of the four different screw diameters it's going to produce.  But what I see is that you have based your hole size for the "self tapping holes" on the nominal diameter.  That makes it trivial to implement this using the existing screw() module---the shoulder screw support that Revar requested can be used to make the larger top section.  I also notice that you subtract a tolerance from the head but not the shaft, which seems suspicious.  I guess it depends on the intended purpose of the tolerance parameter, but there should be a way to adjust the hole size----including the "self-tapping" part---in order to compensate for printer over-extrusion.  It appears that this capability is missing.
You are claiming that screw() can only do 40% of what is needed.  Other than traps, what can screw() not do that is needed?
The way I see things right now, the code structure would be:
screw() - core code for screw and screw hole making, exposed to users for screws only
screw_hole() - code for making screw holes, pass through to screw()
shoulder_screw() - code for shoulder screws, pass through to screw()
nut_trap_inline() - draws inline traps
nut_trap_side() - draws side traps
nut() - draws nuts

You asked before for an example. This is a portion of something I build that supports 2 water bottle cages. The type that mount on bikes.   A couple of other library modules are copied here to make the example. As it happens this is an example of a "head" anchor.
include <BOSL2/std.scad>

$fn = $preview ? 45 : 90;
t = .1;
t2 = 2 * t;
tiny = 1/128;

use <BClib/screws.4.scad>;

blockH = 12;
blockW = 28;
blockTent = 2;

mountOffset = 64;

m5x8 = screw_info(name = "M5,8", head = "socket", drive = "hex");

crossSection("y", fwd = -3)
block();

up(mountOffset)
crossSection("y", fwd = -3)
block();

module block() {
    difference() {
        hexCyl(id = blockW, h = blockH, tent = blockTent);

        // Remove
        zrot(-60)
        fwd(blockW/2 + tiny)
        screw_hole(m5x8, anchor = "head", gap = 3, nut = "hex", trap = "side", orient = FORWARD, spin = 90);

        zrot(60 + tiny)
        fwd(blockW/2 + tiny)
        screw_hole(m5x8, anchor = "head", gap = 3, nut = "hex", trap = "side", orient = FORWARD, spin = 90);
    }
}

/*
Below are library modules copied here
*/

module crossSection(view = "x", right = 0, fwd = 0, up = 0) {
    an = view == "x" ? LEFT
       : view == "y" ? FRONT
       : view == "z" ? BOTTOM
       : view == "-x" ? RIGHT
       : view == "-y" ? BACK
       : view == "-z" ? TOP
       : LEFT;

    difference() {
        union() {
            children();
        }
        right(right)
        fwd(fwd)
        up(up)
        cube(1000, anchor = an);
    }
}

/*
    hexCyl

    Produce a hexagonal cylinder along z axis

    id - face to face
    h - length of column
    tent - size of "tents" on both ends
*/

module hexCyl(l = -1, h = -1, id = -1, d = -1, tent = 0, anchor = CENTER, orient = UP, spin = 0) {
    H = (h > 0) ? h : (l > 0) ? l : 1;
    ID = (id > 0) ? id : (d > 0) ? d : 1;

    attachable(anchor = anchor,
               spin = spin,
               orient = orient,
               d = ID,
               h = H + tent*2)
    {
        hull() {
            if (tent > 0) up(H/2 + tent/2) sphere(tent);

            down(H/2)
            linear_extrude(H)
            hexagon(id = ID);

            if (tent > 0) down(H/2 + tent/2) sphere(tent);
        }

        children();
    } // attachable
} // hexCyl

And here's the current version of screw hole. You should be able to comment out the BClib references.
//   use <../!lib/utils.0.scad>
include <BOSL2/std.scad>
include <BOSL2/screws.scad>

include <BClib/values.scad>
use <BClib/basics.4.scad>

/*
The default tolerance is a measured amount. Vertical dimensions
in printed holes require this much extra so that a nut will always
fit. A much smaller tolerance can work if the hole's cross section
is in the XY plane. However, the .25 value works in the worst case.
*/
defaultTolerance = .25;

/*************************************************************
Screws and holes are rendered vertically with head at the top
Slots for nut traps are in the direction of +X

extendH extend the hole at the top of the head
extendS extendthe hole at the bottom of the crew
gap is the space between head and nut

The screw hole is generated vertically.
If there is a side trap, it extends toward the +X axis.
If there is a poke hole with the side trap, it extends on -X

nut - "none", "hex", "square" or "self", default: "hex"
trap - "none", "side" or "inline", default: "inline"

Several special anchors are defined. All these anchors
are on the Z axis. The designated anchor will be at the XYZ origin.

    It is vertically positioned as designated in the context.
    If the trap is a side trap, it extends in the direction of the x axis.
    ANy poke hole is opposite the side trap.

screw_from_context(sc, anchor = "gap", spin = 0, orient = UP)
    sc - screw context

nut_from_context(sc, anchor = "gap", spin = 0, orient = UP)
    sc - screw context
*/

/*

Nut Info information

diameter - distance face to face
height   - thickness

Hex and square nuts appear to have the same specs
This table only covers metric nuts for screws from 3 to 10 mm
*/

function _NutInfo(si) = let (d = struct_val(si, "diameter", 0))
           (d == 3)  ? [["diameter", 5.5],  ["height", 2.4]] :
           (d == 4)  ? [["diameter", 7.0],  ["height", 3.2]] :
           (d == 5)  ? [["diameter", 8.0],  ["height", 4.0]] :
           (d == 6)  ? [["diameter", 10.0], ["height", 5.0]] :
           (d == 7)  ? [["diameter", 11.0], ["height", 5.5]] :
           (d == 8)  ? [["diameter", 13.0], ["height", 6.5]] :
           (d == 10) ? [["diameter", 17.0], ["height", 8.0]] : [];

/*
Functions for accessing the screw_context.
*/
function _SV1(s, n1, d = 0) = struct_val(s, n1, d);
function _SV2(s, n1, n2, d = 0) = len(struct_val(s, "spec", [])) == 0
                                ? struct_val(s, n2, d)
                                : struct_val(struct_val(s, n1, ""), n2, d);

function _ScrewD(si)    = struct_val(si, "diameter", 0);
function _ScrewL(si)    = struct_val(si, "length", 0);
function _ScrewP(si)    = struct_val(si, "pitch", 0);
function _HeadHV(si)    = struct_val(si, "head_height", 0);
function _HeadDV(si)    = struct_val(si, "head_size", 0);
function _HeadType(si)  = struct_val(si, "head", 0);
function _Drive(si)     = struct_val(si, "drive", 0);
function _HeadA(si)     = struct_val(si, "head_angle", 0);

function _NutD(si)      = struct_val(_NutInfo(si), "diameter", 0);
function _NutH(si)      = struct_val(_NutInfo(si), "height", 0);

/*
    The head height value for flat screws in screw_info is 0.
    For our purposes though, the head height is the distance
    from head top surface to beginning of threads.
*/
function _HeadH(si)   = (_HeadType(si) == "flat")
                      ? (_HeadD(si) - _ScrewD(si)) / 2 / tan(_HeadA(si) / 2)
                      : _HeadHV(si);

function _HeadD(si)   = (_HeadDV(si) == undef)
                      ? _ScrewD(si)
                      : _HeadDV(si);

function _WholeScrewL(si)  = (_HeadType(si) == "flat")
                      ? _ScrewL(si)
                      : _ScrewL(si) + _HeadH(si);

function _ThreadL(si) = (_HeadType(si) == "flat")
                      ? _ScrewL(si) - _HeadH(si)
                      : _ScrewL(si);

function _HoleL(si, extendH, extendS) = extendH + extendS + _WholeScrewL(si);

// The following are all positions relative to the middle of the entire screw
// IE TOP, top of the screwr hole is 0.
function _TopOfHead(si)      = _WholeScrewL(si)/2;
function _BotOfHead(si)      = _TopOfHead(si)      - _HeadH(si);
function _CenterGap(si, gap) = _BotOfHead(si)      - gap/2;
function _TopOfNut(si, gap)  = _CenterGap(si, gap) - gap/2;
function _BotOfScrew(si)     = _TopOfHead(si)      - _WholeScrewL(si);

// Adding the adjustment gives the position of the anchors after the
// screw_hole plus extensions is centered.
// The adjustment needed is (extendH + extendS) / 2.
function _anchors(si, gap, adjust = 0) =
    [
        named_anchor("top",  [0, 0, adjust + _TopOfHead(si)]),
        named_anchor("head", [0, 0, adjust + _BotOfHead(si)]),
        named_anchor("gap",  [0, 0, adjust + _CenterGap(si, gap)]),
        named_anchor("nut",  [0, 0, adjust + _TopOfNut(si, gap)]),
        named_anchor("bot",  [0, 0, adjust + _BotOfScrew(si)]),
    ];

/*************************************************************
screw_hole

si      - the structure returned by screw_info
gap     - the distance in mm between head and nut in the assembly
nut     - "hex" (default), "square", "self", "none"
trap    - "none" (default), "inline", "side"
extendH - clearance above the head, ie additional length of the hole for the head
extendS - clearance below the screw, ie additional length of the hole for the screw
trapL   - trap length for a side trap, defaults to 25
pokeL   - poke hole length - a hole opposite the nut trap to allow the nut to be poked out
          default is -1 : no poke hole
          > 0 means include a poke hole
          1 means use the default length of 25
pokeD   - diameter of poke hole, defaults to 2
t       - tolerance added to diameters so screws will fit in holes, default is "defaultTolerance"

anchor  - anchor, The usual set plus the custom anchors described above
spin    - as usual, default to 0
orient  - as usual, default to UP
*************************************************************/

module screw_hole(si,
                  gap = 0,
                  nut = "hex",
                  trap = "none",
                  extendH = 1,
                  extendS = 1,
                  trapL = 25,
                  pokeL = 25,
                  pokeD = 2,
                  t = defaultTolerance,

                  anchor = "gap",
                  spin = 0,
                  orient = UP)
{
    // for a self tapping screw extendS must be >= 1
    eS = (nut == "self" && extendS < 1) ? 1 : extendS;
    hole_length = _WholeScrewL(si) + extendH + eS;
    adj = (eS - extendH) / 2;

    //struct_echo(_anchors(si, gap = gap, adjust = adj), "_anchors");
    //echo("TOP", _HoleL(si, extendH, eS)/2);
    //echo("...", _WholeScrewL(si), extendH, eS, adj);

    attachable(anchor = anchor,
               spin = spin,
               orient = orient,
               d = _HeadD(si) + t,
               h = hole_length,
               anchors = _anchors(si, gap = gap, adjust = adj))
    {
        union() {
            if (nut == "self") {
                up(hole_length/2 - extendH) {
                    if (extendH > 0) {
                        zcyl(d = _HeadD(si) + t, h = extendH, anchor = BOTTOM);
                    }

                    _self_tap(si, gap = gap, extendH = extendH, extendS = eS, t = t);
                }
            } else {
                up(hole_length/2 - extendH) {
                    if (extendH > 0) {
                        zcyl(d = _HeadD(si) + t, h = extendH, anchor = BOTTOM);
                    }

                    _screw_hole(si, t);
                    //screw(spec = si, shank = _ScrewL(si), oversize = t, anchor = TOP);

                    down(_TopOfHead(si) - _TopOfNut(si, gap))
                    if (trap == "side") {
                        _side_trap(si, nut = nut, trapL = trapL, pokeL = pokeL, pokeD = pokeD, t = t);
                    } else if (trap == "inline") {
                        inlineTrapL = extendS + _TopOfNut(si, gap = gap) - _BotOfScrew(si);
                        _inline_trap(si, nut = nut, trapL = inlineTrapL, t = t);
                    }

                    down(hole_length - extendH)
                    if (extendS > 0) {
                        zcyl(d = _ScrewD(si) + t, h = extendS + tiny, anchor = BOTTOM);
                    }
                } // up
            }
        }

        children();
    } // attachable()
} // screw_hole

module _screw_hole(si, t) {
    union() {
        // Head
        _head(si, anchor = TOP)

        // Screw + extention
        attach(DOWN, UP)
        zcyl(h = _ThreadL(si) + tiny, d = _ScrewD(si) + t);
    }
} // _screw_hole

module _self_tap(si, gap, extendH, extendS, t) {
    // Return with TOP at origin
    union() {
        // Head
        _head(si, t = t)

        // Screw hole
        attach(DOWN, UP)
        zcyl(h = gap + 1, d = _ScrewD(si) + t)
        // gap + 1 extends the full size hole 1 mm into the tapped surface

        // Narrow self tapping section
        attach(DOWN, UP)
        zcyl(h = _ThreadL(si) - gap - 1 + extendS, d = _ScrewD(si) - _ScrewP(si));
        // extendS is always >= 1, along with the (gap + 1) above, the tapped hole is always >= 2 mm longer than the screw
    }
} // _self_tap

module _head(si, t = defaultTolerance, anchor = TOP, orient = UP, spin = 0) {
    head_height = _HeadH(si) + tiny;
    head_diameter = _HeadD(si) + t;

    attachable(anchor = anchor, spin = spin, orient = orient, d = head_diameter, h = head_height) {
        if (_HeadType(si) == "flat") {
            r2 = (_HeadD(si) + t) / 2;
            r1 = (_ScrewD(si) + t) / 2;
            down(t - tiny)
            cyl(r1 = r1, r2 = r2, h = head_height, anchor = CENTER)
            attach(TOP, BOTTOM)
            cyl(r = r2, h = t);
        } else {
            zcyl(h = _HeadHV(si) + tiny, d = head_diameter, anchor = CENTER);
        }

        children();
    }
} // _Head

module _side_trap(si, nut, trapL, pokeL, pokeD, t) {
    // returns trap with top of trap at the origin (nominally) and the trap extending along the X axis
    // The top of the trap is actually t above the XY plane
    // Slot
    up(t/2) {
        cube([trapL, _NutD(si) + t, _NutH(si) + t], anchor = [-1, 0, 1]);

        if (nut == "square") {
            cube([_NutD(si) + t, _NutD(si) + t, _NutH(si) + t], anchor = [0, 0, 1]);
        } else {
            down(_NutH(si) + t)
            hexCyl(h = _NutH(si) + t, id = _NutD(si) + t, anchor = BOT);
        }

        // Poke hole - extends along the -X axis
        if (pokeL > 0) {
            down(_NutH(si)/2)
            xcyl(h = (pokeL == 1 ? 25 : pokeL), d = pokeD, anchor = [1, 0, 0]);
        }
    }
} // _side_trap

module _inline_trap(si, nut, trapL, t) {
    // returns trap with top of trap at the origin

    up(tiny)
    if (nut == "square") {
        cube([_NutD(si) + t, _NutD(si) + t,  trap_len], anchor = [0, 0, 1]);
    } else {
        down(trapL)
        hexCyl(l = trapL, id = _NutD(si) + t, anchor = BOT);
    }
} // _inline_trap

/*************************************************************
screw_in_hole
Renders a screw positioned as it would be in a screw hole using the same custom anchors

si      - the structure returned by screw_info
gap     - the distance in mm between head and nut in the assembly
trap    - "none" (default), "inline", "side"
extendH - clearance above the head, ie additional length of the hole for the head
extendS - clearance below the screw, ie additional length of the hole for the screw
t       - tolerance added to diameters so screws will fit in holes, default is "defaultTolerance"

anchor  - anchor, The usual set plus the custom anchors described above
spin    - as usual, default to 0
orient  - as usual, default to UP
*************************************************************/

module screw_in_hole(
    si,
    gap = 5,
    extendH = 1,
    extendS = 1,
    t = defaultTolerance,

    anchor = "gap",
    spin = 0,
    orient = UP)
{
    hole_length = _WholeScrewL(si) + extendH + extendS;
    adj = (extendS - extendH) / 2;

    attachable(anchor = anchor,
               spin = spin,
               orient = orient,
               d = _HeadD(si) + t,
               h = hole_length,
               anchors = _anchors(si, gap = gap, adjust = adj))
    {
        up(hole_length/2 - extendH - _WholeScrewL(si))
        screw(spec = si, length = _ScrewL(si), anchor = BOT);

        children();
    } // attachable()
} // screw_from_context

/*************************************************************
nut_in_hole

si      - the structure returned by screw_info
gap     - the distance in mm between head and nut in the assembly
nut     - "hex" (default), "square", "self", "none"
extendH - clearance above the head, ie additional length of the hole for the head
extendS - clearance below the screw, ie additional length of the hole for the screw
t       - tolerance added to diameters so screws will fit in holes, default is "defaultTolerance"

anchor  - anchor, The usual set plus the custom anchors described above
spin    - as usual, default to 0
orient  - as usual, default to UP
*************************************************************/

module nut_in_hole(
    si,
    gap = 5,
    nut = "hex",
    extendH = 1,
    extendS = 1,
    t = defaultTolerance,

    anchor = "gap",
    spin = 0,
    orient = UP)
{
    hole_length = _WholeScrewL(si) + extendH + extendS;
    adj = (extendS - extendH) / 2;

    attachable(anchor = anchor,
               spin = spin,
               orient = orient,
               d = _HeadD(si) + t,
               h = hole_length,
               anchors = _anchors(si, gap = gap, adjust = adj))
    {
        up(hole_length/2 - extendH - _TopOfHead(si) + _TopOfNut(si, gap))
        _nut(si, nut);

        children();
    } // attachable()
} // nut_in_hole

module _nut(si, nut) {
   if (nut == "square") {
        difference() {
            cube([_NutD(si), _NutD(si), _NutH(si)], anchor = TOP);

            // Remove
            screw(spec = si);
        }
    } else if (nut == "hex") {
        nut(diameter = _NutD(si), thickness = _NutH(si), spec = si, anchor = TOP);
    }
}

/***************************************************************
*
* Test Code
*
***************************************************************/

_test();

module _test() {
    showAnchor = "all";

    trap_angle = 180;
    name = "M5,8";
    head = "socket";
    //siHead = "flat";
    //siHead = "button";
    drive = "hex";

    gap = 3;
    nut = "hex"; // "hex, "square", "self"
    trap = "side"; // "inline", "side", none
    poke = 10; // -1, 1
    trapL = 25;

    extendH = 2;
    extendS = 0;

    si = screw_info(name = name, head = head, drive = drive);

    function show(p) = (p == showAnchor || showAnchor == "all") ? true : false;

    xdistribute(20) {
        ydistribute(15) {
            if (show(TOP))
                screw_hole(si,
                           anchor = TOP,
                           spin = trap_angle,
                           nut = nut,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);

            if (show("top"))
                screw_hole(si,
                           anchor = "top",
                           spin = trap_angle,
                           nut = nut,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
            if (show("head"))
                screw_hole(si,
                           anchor = "head",
                           spin = trap_angle,
                           nut = nut,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
            if (show("gap"))
                screw_hole(si,
                           anchor = "gap",
                           spin = trap_angle,
                           nut = nut,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
            if (show("nut"))
                screw_hole(si,
                           anchor = "nut",
                           spin = trap_angle,
                           nut = nut,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
            if (show("bot"))
                screw_hole(si,
                           anchor = "bot",
                           spin = trap_angle,
                           nut = nut,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
            if (show(BOT))
                screw_hole(si,
                           anchor = BOT,
                           spin = trap_angle,
                           nut = nut,
                           gap = gap,
                           trap= trap,
                           extendH = extendH,
                           extendS = extendS,
                           trapL = trapL,
                           pokeL = poke);
        }

//*
        ydistribute(15) {
            if (show(TOP))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = TOP);
                nut_in_hole(si, nut = nut, gap = gap, extendH = extendH, extendS = extendS, anchor = TOP);
            }
            if (show("top"))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "top");
                nut_in_hole(si, nut = nut, gap = gap, extendH = extendH, extendS = extendS, anchor = "top");
            }
            if (show("head"))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "head");
                nut_in_hole(si, nut = nut, gap = gap, extendH = extendH, extendS = extendS, anchor = "head");
            }
            if (show("gap"))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "gap");
                nut_in_hole(si, nut = nut, gap = gap, extendH = extendH, extendS = extendS, anchor = "gap");
            }
            if (show("nut"))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "nut");
                nut_in_hole(si, nut = nut, gap = gap, extendH = extendH, extendS = extendS, anchor = "nut");
            }
            if (show("bot"))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = "bot");
                nut_in_hole(si, nut = nut, gap = gap, extendH = extendH, extendS = extendS, anchor = "bot");
            }
            if (show(BOT))    union() {
                screw_in_hole(si, gap = gap, extendH = extendH, extendS = extendS, anchor = BOT);
                nut_in_hole(si, nut = nut, gap = gap, extendH = extendH, extendS = extendS, anchor = BOT);
            }
        }
        //*/
    }
}

Regarding what you're calling "self tapping", that is something I need to look in to. I'm not sure what a hole like that should be called. The hole isn't self-tapping, and neither is the screw---it's just a normal machine screw.  In any case, it seems like it should be possible to make these using the existing screw() code, though it might be annoying because of having to evaluate the tolerance outside of screw to figure out the dimensions. That seems kind of ugly.

I just didn't know what else to call it. But it is definitely a screw hole with a certain amount of thread end that is reduced in diameter so that the screw threads dig directly into the plastic. It has worked well for me. Some assemblies just work well that way. The receiving material is essentially the "nut".
The other 60% that your code does not handle is the traps. The traps are a big part of the holes. Properly orienting traps, screw holes, screws and nuts relative to each other is the hole point of my code. The anchors all work together.
Did you note the commented line that can be uncommented that will make my little bit of "hole code" unnecessary? The hole code is really simple except for flat heads. All other screws are just two cylinders stuck together. I am unaware of any screw type except flat that doesn't fit that.
No doubt the code could use some error handling.

Hmmm. I went and looked at your code. One observation is that all of your extra calls like _ScrewD() conceal what's actually happening, so they function as code obfuscators. When I see _ScrewD I don't know which of the four different screw diameters it's going to produce. But what I see is that you have based your hole size for the "self tapping holes" on the nominal diameter. That makes it trivial to implement this using the existing screw() module---the shoulder screw support that Revar requested can be used to make the larger top section. I also notice that you subtract a tolerance from the head but not the shaft, which seems suspicious. I guess it depends on the intended purpose of the tolerance parameter, but there should be a way to adjust the hole size----including the "self-tapping" part---in order to compensate for printer over-extrusion. It appears that this capability is missing.

You may have spotted something here, but I don't have time to look at it now. Headed out to dinner. Cheers!

I am at a point where I think I've finished up with screw() so I'll be able to give attention to the hole and trap issue this week, I think.  I was going to look at the example you previously posted (something to do with a golf cart).
I understand what you're trying to do with the "self-tapping" idea, just not sure what to call it.  It also seems that like with screw threading, this is going to be very sensitive to printer accuracy.  There's a question of whether the value used to undersize the hole should be chosen under the assumption of a non-zero value of $slop.  I'm not sure exactly what makes sense here.   Have you tested your existing scheme with M20 screws as well as M5 screws to see if it actually works across the range?

I simply subtract the thread pitch from the nominal diameter to get the "self  tap" size. This is not exact, but seems to work in the cases I have tried. The depth of the thread appears to be very close to half the pitch. Thus the pitch is roughly the same as the difference of the outer diameter and inner diameter of the threads. At least that's what I think I found when I put this in earlier.
I have only actually used self tapping on M3 and m4 screws so far. I've never used a screw bigger than M5, so I can't comment on larger screws. In any case, everyones printer and material are a little different. different materials will compress differently. They may be varying the layer thickness. A certain amout of trial and error is inevitable. No ideal calculation will work every time. That's why I think it's essential to have a t(olerance) argument.
I've never used $slop because I don't know where or how it would be applied. Just too mysterious.

One observation is that all of your extra calls like _ScrewD() conceal what's actually happening, so they function as code obfuscators. When I see _ScrewD I don't know which of the four different screw diameters it's going to produce.

I think of these as producing consistent results when the data in screw_info is very inconsistent. _ScrewD() happens to always return the screw diameter. _HeadH() produces a usable height even when the head_height entry is 0 for flat screws. For my code I need to treat the cone of a flat head screw as the head. Calling the head height zero in this case just made no sense to me. For instance the "head" anchor seemed like it should be at the bottom of the cone just as it is at the bottom of the socket for a socket head screw. You still have the "top" anchor at the top of a flat head.
I think of the screw_info structure as an object. It's fields are properties, but the object can have other properties that are calculated from the simple properties. I prefer code where the data structure of the object it is accessing is hidden as much as possible. My functions do that. When I switched away from the screw_context structure to just using the screw_info structure, it was easy to just change the access functions. The code that called then hardly changed because the details of the data structure were hidden.
I am really eager to see the object literals proposal implemented because it will be much easier to do this kind of thing and the notation will be much more OOP like.

But what I see is that you have based your hole size for the "self tapping holes" on the nominal diameter.

True, the nominal diameter - the thread pitch. It seems to work fine. No breakage when inserting screws and no screws have pulled out in use, though my uses for these are very low stress.

Using _screwX() functions as access functions to the structure but then have them do other stuff makes it even more confusing.  I don't think the screw data is "very inconsistent".  The head height is the height of the head beyond the nominal screw length.  So the full length of the screw is its nominal length plus it's head height.  For a flat head that height is zero.   There's no way to put the height of the flat head into the screw structure because it can't be correctly computed with the info in the screw structure, so there's not really an alternative.  As I noted before, the point underneath a flat head screw is not a well-defined location, and I do wonder if it's useful for anything.   (Note also that it doesn't really make sense to put the flat head height in as head height because it really does not mean the same thing.  I was thinking about putting it in as a negative number before I realized it was impossible to compute, but one way or another, flat heads need to be handled differently.)
I don't see much point in hiding an underlying structure with what is essentially a renaming of the fields.  I think the customary justification for this sort of thing is that you can do error handling (e.g. with set methods), you can have derived values, and you can change the underlying data type.   OpenSCAD doesn't have data types.  And we don't have methods.  And if you want derived values it seems best to make it clear that they are derived and not pass-through functions...though I guess nothing really clear since everything is renamed.  When I was looking at screwD() I didn't know if it pulled nominal diameter from the screw info structure or one of d_major, d_minor or d_pitch from the tolerance structure until I hunted up its definition.   I mean, if you were programming in some other language and someone gave you a object with a documented API would you write your own API layer on top of it with a bunch of differently named access functions?  Because that's basically what you're doing here.
I agree that having objects will make this sort of thing so much better.  Things do not seem to be moving very fast on this, though.  Revar created a PR for OpenSCAD that adds user creatable objects, but as far as I know it's still waiting.  There are lots of places in BOSL2 where things should be objects.  Of course...that'll be a BIG rewrite.  I don't think this will make OpenSCAD object oriented in any meaningful way, though.  For that you need methods.
  You can of course get the actual half pitch of the screw if you want from the tolerance structure.  But I don't know if that actually helps anything.  The question here is if the required undersizing is wildly uncertain due to material variation, etc, then how do you write a reasonable baseline implementation?  That suggests that you just let users undersize screws themselves for their own needs.
Revar looked up this topic and found that the "correct" way to do this is with "thread forming screws", which have a different geometry that is designed to cut into plastic.
The $slop variable is supposed to handle printer-dependent changes in the size of objects.  So if your printer needs the hole oversized by a certain amount, you're supposed to do that with $slop so that the main code doesn't have a printer dependence in it.  Your "defaultTolerance" variable is duplicating the functionality of $slop, I think.

the "correct" way to do this is with "thread forming screws"

No doubt that's true, but I'm not an ME. I would hope you would make BOSL2 usable for those of us who are fine with good enough. Meeting the needs of an exacting ME is a laudable goal, but perhaps approachable only asymptotically.

The $slop variable is supposed to handle printer-dependent changes in the size of objects. So if your printer needs the hole oversized by a certain amount, you're supposed to do that with $slop so that the main code doesn't have a printer dependence in it. Your "defaultTolerance" variable is duplicating the functionality of $slop, I think.

It probably is. I've done test prints just to determine what the default T should be. It varies depending on whether a hole printed along he XY plane or vertically on the Z axis or something else. OpenSCAD can't know the printing orientation. For that reason chasing a more accurate T doesn't help.

I don't see much point in hiding an underlying structure

This is a pretty basic and common idea in CS. If that's not your preferred style, fine. I find it very helpful.
Looking at the functions I have, most are just direct access to fields, they put the code that knows the data structure in a single place. The slightly more complicated ones consolidate code in one place that checks a condition like undef or for a flat head. Refactoring code like this into one place makes the calling code easier to understand and maintain.

I may have interpreted the flat head screw data incorrectly. The docs I've looked at aren't clear on that point. Do you have a reference that makes it clear?

I found verification that flat head screws are measured from the top of the head for "length". It turns out no code needed to be changed though I did change one name of a function. I found vendors of screws that document head height for these screws, so at least it's a valid concept. My calculated height is slightly different than what they give in their tables. I am not sure why.  The real screw exists within that envelope. The actual head diameter is a little smaller and they may be measuring the head down to where it meets the minor diameter of the thread. That would about account for the difference. Anyway, with the slight counterbore that is needed at the top and the fact that we are making a hole, I doubt the difference would cause a problem for most use cases.

I can't think of a reason to use it, but I still think the "head" anchor should be at the bottom of the flat head on a flat head screw. There is already a "top" of the head anchor.

BTW, I got the impression somehow that the names M3x8 and M3,8 were equivalent. When I use M3x8 I get a trace I can't figure out because it does not come back to my code. My copy of BOSL2 is a few months old by now.
RROR: Assertion 'all_positive(d)' failed in file ../../../../Documents/OpenSCAD/libraries/BOSL2/threading.scad, line 70
TRACE: called by 'threaded_rod' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/screws.scad, line 1244
TRACE: called by '_rod' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/screws.scad, line 1010
TRACE: called by 'children' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/transforms.scad, line 388
TRACE: called by 'translate' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/transforms.scad, line 388
TRACE: called by 'down' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/screws.scad, line 1009
TRACE: called by 'intersection' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/screws.scad, line 1008
TRACE: called by 'if' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/screws.scad, line 1007
TRACE: called by 'children' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/transforms.scad, line 429
TRACE: called by 'translate' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/transforms.scad, line 429
TRACE: called by 'up' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/screws.scad, line 1003
TRACE: called by 'union' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/screws.scad, line 1001
TRACE: called by 'difference' in file ../../../../Documents/OpenSCAD/libraries/BOSL2/screws.scad, line 1000