kernel-ladder: tier 06 grades the threaded boss; both hands and Tr40x14(P7) added

010 passed on one valid body alone: it now checks the tier-05 part less
the M16 groove (stopped w above the boss's shoulder, run out at its free
top) and the bbox. Two new cases with closed forms: 018, an M10 cut both
hands, whose crossing grooves leave the material fraction squared
(2 pi L int r m^2 dr, exact for L a whole number of pitches); 019, a
two-start Tr40x14(P7), six pitches of the Tr40x7 section. All hold on
SMLib (worst 2e-6 relative).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-26 11:13:53 -04:00
parent 977230b7c3
commit 84473f61fa
7 changed files with 147 additions and 6 deletions
@@ -1,7 +1,23 @@
{
"sources": [],
"what": "the running part with a modelled M16 thread on its d16 boss",
"source": "invariants",
"method": "the boss stands on the base (thread to a shoulder) and has an M5 hole in its top; modelled threads are faceted, so later tiers build on the part before this step and 11-cumulative-parts adds it last",
"body_count": 1
"source": "analytic",
"method": "the boss (r8, z 20..30) stands on the base's top face, a shoulder: the groove stops with its centre w = 0.932735 above it (004's rule, M16 P = 2) and runs out past the free top end; its root r = 8 - 5H/8 = 6.917 stays clear of the M5 tap drill (r 2.1) in the boss. V = the tier-05 part - (pi 8^2 (30 - 20 - w) - 2 pi int_{20+w}^30 int r m(r) dr dz), m as in 001; the bbox is the part's. Later tiers build on the part before this step and 11-cumulative-parts adds the thread last",
"body_count": 1,
"volume_mm3": 53208.190964337,
"bbox": {
"min": [
0,
0,
0
],
"max": [
60,
40,
35
]
},
"tolerance": {
"rel": 0.0001
}
}
@@ -0,0 +1,11 @@
{
"sources": [],
"what": "001's modelled M10 thread cut both ways: a right- and a left-handed groove crossing on a d10 x 30 cylinder",
"source": "analytic",
"method": "each groove alone leaves 001's material fraction m(r) (ISO 68-1 basic profile; 016 is the left hand alone). Along a line parallel to the axis at radius r each groove takes the fraction g = 1 - m of every pitch, the two phases drifting apart by two pitches a turn; over a turn the overlap of the two takes g^2 (exact here: in the two grooves' phases the (theta, z) domain is spanned by (L, L) and (-P, P), both periods, L = 20 P), so the material left is 1 - 2g + g^2 = m^2: V = 2 pi L int_0^5 r m(r)^2 dr (= stock - 2 x groove + crossing). Both grooves run out past the free ends. No bbox: the crest survives only in diamonds, two windows of 45 deg a turn where the two crests meet, so how far the part reaches along x and y depends on where the grooves' phases line up round the axis. Tolerance 0.01%: SMLib's exact screw sweeps",
"body_count": 1,
"volume_mm3": 1804.920030661,
"tolerance": {
"rel": 0.0001
}
}
@@ -0,0 +1,30 @@
{
"commands": [
{
"command": "solid.cylinder",
"params": {
"base": [
0,
0,
0
],
"radius": 5,
"height": 30,
"body_name": "Part"
}
},
{
"command": "solid.thread",
"params": {
"face": [
5,
0,
15
],
"modeled": true,
"designation": "M10",
"hand": "both"
}
}
]
}
@@ -0,0 +1,23 @@
{
"sources": [],
"what": "a modelled two-start trapezoidal Tr40x14(P7) thread (ISO 2904, ISO 2903) on a d40 shaft, three leads long",
"source": "analytic",
"method": "groove volume per turn 2 pi int r w(r) dr over the material it cuts (Pappus on the axial section; a screw motion), w(r) = P - flat - depth (tan a + tan b); two grooves of lead 14 a half turn apart, so every meridian half-plane holds a groove section every pitch 7, as one groove of pitch 7 would: over 42 mm (three leads, six pitches) between free ends, so V = stock - 6 x that. Tolerance 0.01%: SMLib's exact screw sweep. ISO 2904 for P 7: 30 deg, H1 = P/2, crest flat 0.366 P, ac = 0.5 (P 6..12), external minor d3 = d - 2 (H1 + ac) = 32",
"body_count": 1,
"volume_mm3": 43534.483709054,
"bbox": {
"min": [
-20,
-20,
0
],
"max": [
20,
20,
42
]
},
"tolerance": {
"rel": 0.0001
}
}
@@ -0,0 +1,29 @@
{
"commands": [
{
"command": "solid.cylinder",
"params": {
"base": [
0,
0,
0
],
"radius": 20.0,
"height": 42,
"body_name": "Part"
}
},
{
"command": "solid.thread",
"params": {
"face": [
20.0,
0,
21
],
"modeled": true,
"designation": "Tr40x14(P7)"
}
}
]
}
+3 -1
View File
@@ -114,7 +114,9 @@
"ladder/06-threads/006-external-m10-into-chamfer",
"ladder/06-threads/002-external-m10x1-fine",
"ladder/06-threads/008-multi-start-thread",
"ladder/06-threads/017-external-m10-chamfered-tip"
"ladder/06-threads/017-external-m10-chamfered-tip",
"ladder/06-threads/018-external-m10-both-hands",
"ladder/06-threads/019-external-tr40x14-p7-two-start"
],
"tier": "06-threads",
"what": "modelled threads"
+32 -2
View File
@@ -680,9 +680,16 @@ case(T6, "009-thread-runout-length", "004's M10 thread run from the free top end
"the cut per radian is f(o) = int r w(r - o) dr from root + o to 5 (w the section's width at its own radius), the runout removes 2 pi (3/P) mean(f) (Simpson, exact on the cubic), "
"the full-depth groove from s + 3 to the free top end 2 pi f(0) (35 - s - 3)/P (the overrun past the free end as in 001). "
"V = pi 8^2 10 + pi 5^2 25 - both. Tolerance 0.01%: SMLib's exact radial screw sweep")
def groove(d, P, sections):
"""What a modelled external groove removes over the stock's meridian section [(z0, z1, rmax(z))]."""
return sum(PI * quad(lambda z: f(z) ** 2, z0, z1, 64) for z0, z1, f in sections) - ext_volume(d, P, sections)
w16 = ext_half_width(16, 2)
case(T6, "010-part-threaded-boss", "the running part with a modelled M16 thread on its d16 boss",
[thr([23, 15, 25], designation="M16")], {"body_count": 1},
"the boss stands on the base (thread to a shoulder) and has an M5 hole in its top; modelled threads are faceted, so later tiers build on the part before this step and 11-cumulative-parts adds it last", base=C5, source="invariants")
[thr([23, 15, 25], designation="M16")],
{"body_count": 1, "volume_mm3": V5 - groove(16, 2, [(20 + w16, 30, lambda z: 8)]), "bbox": {"min": [0, 0, 0], "max": [60, 40, 35]}, "tolerance": {"rel": 1e-4}},
"the boss (r8, z 20..30) stands on the base's top face, a shoulder: the groove stops with its centre w = %.6f above it (004's rule, M16 P = 2) and runs out past the free top end; "
"its root r = 8 - 5H/8 = 6.917 stays clear of the M5 tap drill (r 2.1) in the boss. V = the tier-05 part - (pi 8^2 (30 - 20 - w) - 2 pi int_{20+w}^30 int r m(r) dr dz), m as in 001; "
"the bbox is the part's. Later tiers build on the part before this step and 11-cumulative-parts adds the thread last" % w16, base=C5)
# The other standard forms (crates/doc/src/thread_forms.rs, docs/threads.md): a groove of a fixed
# straight-flanked section, its tooth `flat` wide at the crest radius and widening by tan a + tan b
@@ -729,6 +736,29 @@ case(T6, "016-external-m10-left-hand", "001's modelled M10 thread, left-handed:
TMETH.replace("Tolerance 0.5%: solid.thread cuts faceted by design (chords within P/200 on the body, P/100 on the tool)", "Tolerance 0.01%: SMLib's exact screw sweep") +
"; a mirror image of 001's groove, so the same volume")
# Both hands: two grooves of the same section crossing. At radius r, along every line parallel to the axis the
# right-hand groove takes a fraction g(r) = 1 - m(r) of each pitch and the left-hand one the same, their phases
# drifting apart by two pitches a turn: over the turn the overlap averages g^2 (the side vectors of the (theta, z)
# domain, (L, L) and (-P, P) in the two grooves' phases, are periods, L = 20 P), so the material left is m^2.
r1b, mb = iso_material_fraction_ext(10, 1.5)
vboth = 2 * PI * 30 * (quad(lambda r: r, 0, r1b) + quad(lambda r: r * mb(r) ** 2, r1b, 5, 64))
case(T6, "018-external-m10-both-hands", "001's modelled M10 thread cut both ways: a right- and a left-handed groove crossing on a d10 x 30 cylinder",
cylT + [thr([5, 0, 15], designation="M10", hand="both")],
{"body_count": 1, "volume_mm3": vboth, "tolerance": {"rel": 1e-4}},
"each groove alone leaves 001's material fraction m(r) (ISO 68-1 basic profile; 016 is the left hand alone). Along a line parallel to the axis at radius r each groove takes the "
"fraction g = 1 - m of every pitch, the two phases drifting apart by two pitches a turn; over a turn the overlap of the two takes g^2 (exact here: in the two grooves' phases the "
"(theta, z) domain is spanned by (L, L) and (-P, P), both periods, L = 20 P), so the material left is 1 - 2g + g^2 = m^2: V = 2 pi L int_0^5 r m(r)^2 dr "
"(= stock - 2 x groove + crossing). Both grooves run out past the free ends. No bbox: the crest survives only in diamonds, two windows of 45 deg a turn where the two crests meet, "
"so how far the part reaches along x and y depends on where the grooves' phases line up round the axis. Tolerance 0.01%: SMLib's exact screw sweeps")
P7, d40 = 7, 40
case(T6, "019-external-tr40x14-p7-two-start", "a modelled two-start trapezoidal Tr40x14(P7) thread (ISO 2904, ISO 2903) on a d40 shaft, three leads long",
[cyl([0, 0, 0], d40 / 2, 6 * P7, body_name="Part"), thr([d40 / 2, 0, 3 * P7], designation="Tr40x14(P7)")],
{"body_count": 1, "volume_mm3": PI * 400 * 6 * P7 - 6 * form_turn(P7, P7 / 2 * (1 - t15), 20, 15, 15, 20 - P7 / 2 - 0.5, 20),
"bbox": {"min": [-20, -20, 0], "max": [20, 20, 42]}, "tolerance": TOL_F},
FMETH.replace("six whole turns between free ends", "two grooves of lead 14 a half turn apart, so every meridian half-plane holds a groove section every pitch 7, as one "
"groove of pitch 7 would: over 42 mm (three leads, six pitches) between free ends") + "ISO 2904 for P 7: 30 deg, H1 = P/2, crest flat 0.366 P, "
"ac = 0.5 (P 6..12), external minor d3 = d - 2 (H1 + ac) = 32")
rf10 = 5 - 5 * math.sqrt(3) / 2 * 1.5 / 8
case(T6, "017-external-m10-chamfered-tip", "001's modelled M10 thread with ISO 4753's chamfered end at both free ends: a 45 deg cone down to the minor diameter, cut after the groove",
cylT + [thr([5, 0, 15], designation="M10", tip="chamfer")],