Skip to content

Commit 73aa7c0

Browse files
scottconverseclaude
andcommitted
Apply review fixes + publish GitHub Pages landing page
- docs/index.html: standalone landing page (hero, badges, diagram, quickstart) - tq_thread: rounded-root depth now accounted for in the minor-radius assert - tq_bolt: socket drive recess gated on drive=="hex" (was silently ignored) - make _TQ_H private (was undocumented public constant) - tq_thread_debug: integer pitch-marker loop (avoids float-range edge) - REFERENCES: name libraries NOT consulted; derive rounded fillet radii Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 3e7b06c commit 73aa7c0

3 files changed

Lines changed: 203 additions & 12 deletions

File tree

REFERENCES.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ H = (sqrt(3)/2) · P = 0.8660254 · P
3131
| Pitch-Ø offset from major | `2·(3H/8) = 0.75·H` | 0.649519·P |
3232
| Minor-Ø offset from major (basic) | `2·(5H/8) = 1.25·H` | 1.082532·P |
3333
| Rounded root radius (UNR/ISO) | `≈ 0.144·P` | 0.144·P |
34+
| Rounded crest radius (this lib) | `≈ 0.072·P` | 0.072·P |
35+
36+
**Derivation of the rounded fillet radii** (used by `profile="rounded"`,
37+
`_tq_table_round`). The root fillet is the standard UNR/ISO value
38+
`rr ≈ 0.1443·P` — i.e. the radius of a circle tangent to both 60° flanks that
39+
fits the `H/4` root truncation: `(H/4)/(1/sin60° − 1)`-class geometry gives
40+
`≈ H/6 = 0.866·P/6 = 0.1443·P`. The crest fillet is taken at **half** that,
41+
`rc = rr/2 ≈ 0.0722·P`, matching the crest truncation being half the root
42+
truncation (`H/8` vs `H/4`). Both follow from the 60° triangle and `H`; neither
43+
is copied from any library.
3444

3545
Standards:
3646

@@ -118,7 +128,12 @@ helper exposes raw diameter/pitch/depth so you can dial in a drawing you hold).
118128

119129
- No third-party OpenSCAD thread library (of any license) was opened, read,
120130
diffed, or used as a reference for naming, algorithms, comments, examples, or
121-
tests.
131+
tests. In particular, **Dan Kirshner's `threads.scad`, rcolyer's
132+
`thread_profile` / `threadlib`, and BOSL2's `threading.scad` were not opened,
133+
read, line-diffed, or used as a template.** tq-threads' construction is
134+
structurally different from each (it builds one height-field `polyhedron`
135+
rather than unioning per-tooth solids, sweeping a 2D profile along a helix, or
136+
using a VNF/spiral-sweep framework).
122137
- All module/parameter names (`tq_thread`, `clearance`, `lead_in`, `starts`,
123138
`profile`, …) and the height-field construction were chosen independently from
124139
any existing library and from the standards' vocabulary.

docs/index.html

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<!DOCTYPE html>
2+
<!-- SPDX-License-Identifier: MIT -->
3+
<html lang="en">
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<title>tq-threads — printable OpenSCAD screw threads</title>
8+
<meta name="description" content="Clean-room, MIT-licensed OpenSCAD library for printable ISO metric + Unified screw threads, bolts, nuts, washers and tapped holes. Manifold by construction.">
9+
<meta property="og:title" content="tq-threads">
10+
<meta property="og:description" content="Clean-room, printable screw threads for OpenSCAD — ISO metric + Unified, plus the hardware built from them.">
11+
<meta property="og:type" content="website">
12+
<meta property="og:url" content="https://scottconverse.github.io/tq-threads/">
13+
<style>
14+
:root{
15+
--bg:#0d1117; --bg2:#11161d; --card:#161c25; --line:#243042;
16+
--ink:#e6edf3; --mut:#9fb0c3; --acc:#1b9e77; --acc2:#4a90c2; --amber:#d9a531;
17+
--code:#0b1017;
18+
}
19+
@media (prefers-color-scheme: light){
20+
:root{ --bg:#f7fafc; --bg2:#eef3f8; --card:#ffffff; --line:#dce5ee;
21+
--ink:#13283d; --mut:#52647a; --code:#0d1117; }
22+
}
23+
*{box-sizing:border-box}
24+
html{scroll-behavior:smooth}
25+
body{margin:0;background:var(--bg);color:var(--ink);
26+
font:16px/1.6 -apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased}
27+
a{color:var(--acc2);text-decoration:none}
28+
a:hover{text-decoration:underline}
29+
.wrap{max-width:1020px;margin:0 auto;padding:0 20px}
30+
header.hero{background:linear-gradient(160deg,#0b2a4a,#11405f 55%,#0e5c45);
31+
color:#fff;padding:64px 0 56px;border-bottom:1px solid var(--line)}
32+
@media (prefers-color-scheme: light){header.hero{color:#fff}}
33+
.badges{display:flex;flex-wrap:wrap;gap:8px;margin:18px 0 26px}
34+
.badges img{height:22px}
35+
h1{font-size:46px;margin:0;letter-spacing:-1px}
36+
.tag{font-size:20px;color:#cfe3f2;margin:10px 0 0;max-width:720px}
37+
.cta{display:flex;flex-wrap:wrap;gap:12px;margin-top:8px}
38+
.btn{display:inline-block;padding:11px 18px;border-radius:9px;font-weight:600;border:1px solid transparent}
39+
.btn.primary{background:var(--acc);color:#062d20}
40+
.btn.ghost{background:rgba(255,255,255,.08);color:#fff;border-color:rgba(255,255,255,.25)}
41+
.btn:hover{text-decoration:none;filter:brightness(1.08)}
42+
section{padding:46px 0;border-bottom:1px solid var(--line)}
43+
h2{font-size:26px;margin:0 0 18px}
44+
h3{margin:0 0 6px;font-size:17px}
45+
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:16px}
46+
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:18px}
47+
.card p{margin:.3em 0 0;color:var(--mut);font-size:14.5px}
48+
.mut{color:var(--mut)}
49+
pre{background:var(--code);color:#d6e2f0;border:1px solid var(--line);border-radius:12px;
50+
padding:18px;overflow:auto;font:13.5px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace}
51+
pre .c{color:#7c8aa0}
52+
pre .k{color:#7ee0bf}
53+
.diagram{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px}
54+
.diagram img{width:100%;height:auto;display:block}
55+
.two{display:grid;grid-template-columns:1fr 1fr;gap:20px}
56+
@media(max-width:760px){.two{grid-template-columns:1fr}h1{font-size:36px}}
57+
footer{padding:34px 0;color:var(--mut);font-size:14px}
58+
.pill{display:inline-block;font-size:12px;color:var(--amber);border:1px solid var(--amber);
59+
border-radius:20px;padding:2px 10px;margin-bottom:14px}
60+
ul.tight{margin:.2em 0 0;padding-left:18px;color:var(--mut);font-size:14.5px}
61+
</style>
62+
</head>
63+
<body>
64+
65+
<header class="hero">
66+
<div class="wrap">
67+
<span class="pill">clean-room · MIT · GPL-2.0 compatible</span>
68+
<h1>tq-threads</h1>
69+
<p class="tag">Printable <strong>screw threads for OpenSCAD</strong> — ISO metric &amp; Unified, plus the bolts, nuts, washers and tapped holes built from them. Every thread is a single watertight <code>polyhedron</code>, so models stay manifold and export straight to STL.</p>
70+
<div class="badges">
71+
<a href="https://github.com/scottconverse/tq-threads/blob/main/LICENSE"><img alt="MIT" src="https://img.shields.io/badge/License-MIT-green.svg"></a>
72+
<img alt="GPL-2.0 compatible" src="https://img.shields.io/badge/GPL--2.0-compatible-blue.svg">
73+
<img alt="OpenSCAD" src="https://img.shields.io/badge/OpenSCAD-2021.01%2B-f9d72c.svg">
74+
<img alt="version" src="https://img.shields.io/badge/version-0.2.0-informational.svg">
75+
<a href="https://github.com/scottconverse/tq-threads/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/scottconverse/tq-threads/actions/workflows/ci.yml/badge.svg"></a>
76+
</div>
77+
<div class="cta">
78+
<a class="btn primary" href="https://github.com/scottconverse/tq-threads">View on GitHub</a>
79+
<a class="btn ghost" href="https://github.com/scottconverse/tq-threads/releases/tag/v0.2.0">Download v0.2.0</a>
80+
<a class="btn ghost" href="https://github.com/scottconverse/tq-threads/blob/main/MANUAL.md">Manual</a>
81+
<a class="btn ghost" href="https://github.com/scottconverse/tq-threads/discussions">Discussions</a>
82+
</div>
83+
</div>
84+
</header>
85+
86+
<section>
87+
<div class="wrap">
88+
<h2>Why tq-threads</h2>
89+
<div class="grid">
90+
<div class="card"><h3>🧩 Manifold by construction</h3><p>The thread surface is a helical height-field turned into one closed <code>polyhedron</code> — no boolean unions to go non-manifold. It renders cleanly and slices.</p></div>
91+
<div class="card"><h3>📐 Standards-based, clean-room</h3><p>Built only from public standards (ISO 68-1/261/262/273/4032/4762/7089/10642, ASME&nbsp;B1.1). No third-party thread library was copied or consulted.</p></div>
92+
<div class="card"><h3>🖨️ Printable-first</h3><p>Fit clearance, internal-oversize / external-undersize compensation, lead-in chamfers, rounded roots, and real <code>$fn/$fa/$fs</code> are all parameters.</p></div>
93+
<div class="card"><h3>🔩 Batteries included</h3><p>Presets <strong>M2–M64</strong> + fine + UNC/UNF, plus bolts, nuts, washers, countersunk &amp; wood screws, clearance/counterbore/countersink holes, standoffs, couplers.</p></div>
94+
<div class="card"><h3>⚖️ MIT licensed</h3><p>Permissive <em>and</em> GPL-2.0-compatible, so it drops into a GPL-2.0-only project (e.g.&nbsp;TinkerQuarry) unchanged.</p></div>
95+
<div class="card"><h3>✅ Tested &amp; CI</h3><p>Compile-time preset assertions, split fast/heavy suites, a PowerShell render-proof, and GitHub Actions rendering on every push.</p></div>
96+
</div>
97+
</div>
98+
</section>
99+
100+
<section style="background:var(--bg2)">
101+
<div class="wrap">
102+
<h2>Architecture</h2>
103+
<div class="diagram"><img src="architecture.svg" alt="tq-threads architecture: consumers → public API → core tq_thread → geometry engine → public standards"></div>
104+
</div>
105+
</section>
106+
107+
<section>
108+
<div class="wrap two">
109+
<div>
110+
<h2>Quick start</h2>
111+
<pre><span class="c">// install: git submodule / OPENSCADPATH / vendor the .scad</span>
112+
<span class="k">include</span> &lt;tq_threads.scad&gt;;
113+
$fn = 64;
114+
115+
<span class="c">// M8 × 1.25 threaded rod, 20 mm</span>
116+
tq_thread_preset(<span class="k">"M8"</span>, 20);
117+
118+
<span class="c">// tapped M6 hole through a block</span>
119+
difference() {
120+
cube([20,20,12], center=true);
121+
tq_threaded_hole(d=6, pitch=1.0, depth=12);
122+
}
123+
124+
<span class="c">// a cap screw + matching nut (same clearance ⇒ they fit)</span>
125+
tq_bolt(8, 1.25, 20, head=<span class="k">"socket"</span>, shank=4);
126+
tq_nut(8, 1.25);</pre>
127+
</div>
128+
<div>
129+
<h2>Install</h2>
130+
<pre><span class="c"># 1) git submodule (versioned projects)</span>
131+
git submodule add https://github.com/scottconverse/tq-threads libs/tq-threads
132+
<span class="c"># include &lt;libs/tq-threads/tq_threads.scad&gt;;</span>
133+
134+
<span class="c"># 2) drop on the OpenSCAD library path</span>
135+
<span class="c"># ~/Documents/OpenSCAD/libraries/ (Win/macOS)</span>
136+
<span class="c"># ~/.local/share/OpenSCAD/libraries/ (Linux)</span>
137+
138+
<span class="c"># 3) or download the release .zip and vendor the .scad</span></pre>
139+
<p class="mut">Only <code>tq_threads.scad</code> is needed at runtime. Coexists with BOSL2, NopSCADlib, etc. (everything is namespaced <code>tq_*</code>).</p>
140+
<h3 style="margin-top:18px">Migrating from <code>threads.scad</code>?</h3>
141+
<ul class="tight">
142+
<li><code>metric_thread(d,p,l)</code><code>tq_thread(d,p,l)</code></li>
143+
<li><code>english_thread()</code><code>tq_thread_tpi()</code></li>
144+
<li><code>ScrewHole()</code><code>difference(){…; tq_threaded_hole()}</code></li>
145+
</ul>
146+
<p class="mut">Full table in the <a href="https://github.com/scottconverse/tq-threads/blob/main/MANUAL.md#migration">manual</a>.</p>
147+
</div>
148+
</div>
149+
</section>
150+
151+
<section style="background:var(--bg2)">
152+
<div class="wrap">
153+
<h2>Presets</h2>
154+
<p class="mut"><strong>Metric coarse:</strong> M2 M2.5 M3 M3.5 M4 M5 M6 M7 M8 M10 M12 M14 M16 M18 M20 M22 M24 M27 M30 M33 M36 M39 M42 M45 M48 M52 M56 M60 M64<br>
155+
<strong>Metric fine:</strong> M8x1 M10x1.25 M10x1 M12x1.5 M12x1.25 M16x1.5 M20x1.5 M24x2 &nbsp;·&nbsp; <strong>Unified:</strong> 1/4-20 1/4-28 3/8-16 3/8-24 1/2-13 1/2-20</p>
156+
</div>
157+
</section>
158+
159+
<footer>
160+
<div class="wrap">
161+
<strong>tq-threads</strong> · MIT licensed (GPL-2.0 compatible) · clean-room from public standards — see
162+
<a href="https://github.com/scottconverse/tq-threads/blob/main/REFERENCES.md">REFERENCES</a>.
163+
<br>© 2026 Scott Converse / TinkerQuarry contributors ·
164+
<a href="https://github.com/scottconverse/tq-threads">GitHub</a> ·
165+
<a href="https://github.com/scottconverse/tq-threads/blob/main/CHANGELOG.md">Changelog</a> ·
166+
<a href="https://github.com/scottconverse/tq-threads/discussions">Discussions</a>
167+
</div>
168+
</footer>
169+
170+
</body>
171+
</html>

tq_threads.scad

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
TQ_THREADS_VERSION = "0.2.0";
4747

4848
// sqrt(3)/2 : height of a sharp 60-degree V per unit pitch (H = 0.86603 * P)
49-
TQ_H = 0.86602540378;
49+
_TQ_H = 0.86602540378;
5050

5151
// quality floor for thread flanks when $fn is unset (OpenSCAD's own floor is 5)
5252
TQ_MIN_SEG = 24;
@@ -193,16 +193,19 @@ module tq_thread(d, pitch, length,
193193
P = pitch;
194194
cf = is_undef(crest_flat) ? P/8 : crest_flat;
195195
rf = is_undef(root_flat) ? P/4 : root_flat;
196-
h = (P - cf - rf) * TQ_H; // radial flank height
196+
h = (P - cf - rf) * _TQ_H; // radial flank height
197197
off = (internal ? +1 : -1) * clearance / 2; // tolerance shift (radial)
198198
Rmaj = d/2 + off;
199199
Rmin = Rmaj - h;
200+
// rounded roots dip ~0.5*rr below the flat minor radius; check the real valley
201+
rmin_floor = (profile == "rounded") ? Rmin - 0.5 * round * 0.1443 * P : Rmin;
200202

201203
assert(h > 0,
202204
"tq_thread: crest_flat + root_flat exceed pitch (no flank left)");
203-
assert(Rmin > 0.05,
205+
assert(rmin_floor > 0.05,
204206
str("tq_thread: thread too deep for d=", d, " pitch=", pitch,
205-
" (minor radius ", Rmin, " <= 0); use a bigger d or smaller pitch"));
207+
" (effective minor radius ", rmin_floor,
208+
" <= 0); use a bigger d or smaller pitch"));
206209

207210
lead = starts * P;
208211
dir = (hand == "left") ? -1 : 1;
@@ -439,7 +442,8 @@ module tq_standoff(d, pitch, length, od=undef, starts=1, hand="right",
439442

440443
// Socket / hex / plain head bolt. Thread runs Z 0..length; head sits below 0
441444
// with its drive face at z = -head_h. Components overlap by EPS so the STL is
442-
// one coherent printable solid.
445+
// one coherent printable solid. `drive="hex"` cuts a hex socket recess in the
446+
// socket head; any other value (e.g. "none") leaves a plain solid head.
443447
TQ_EPS = 0.02;
444448
module tq_bolt(d, pitch, length, head="socket", head_d=undef, head_h=undef,
445449
shank=0, drive="hex", hand="right", clearance=0.4,
@@ -459,7 +463,8 @@ module tq_bolt(d, pitch, length, head="socket", head_d=undef, head_h=undef,
459463
else if (head == "socket")
460464
difference() {
461465
translate([0,0,-hh]) cylinder(h=hh + TQ_EPS, d=hd, $fn=_tq_aseg(hd, fn));
462-
translate([0,0,-hh - TQ_EPS]) tq_hex_drive(tq_hex_key_af(d), hh*0.6 + TQ_EPS);
466+
if (drive == "hex")
467+
translate([0,0,-hh - TQ_EPS]) tq_hex_drive(tq_hex_key_af(d), hh*0.6 + TQ_EPS);
463468
}
464469
else if (head != "none")
465470
translate([0,0,-hh]) cylinder(h=hh + TQ_EPS, d=hd, $fn=_tq_aseg(hd, fn));
@@ -640,7 +645,7 @@ module tq_bottle_thread(d, pitch, length, starts=1, hand="right",
640645
module tq_thread_debug(d, pitch, length, starts=1, hand="right", profile="flat",
641646
clearance=0.4, fn=undef) {
642647
P = pitch; cf = P/8; rf = P/4;
643-
h = (P - cf - rf) * TQ_H;
648+
h = (P - cf - rf) * _TQ_H;
644649
Rmaj = d/2 - clearance/2;
645650
Rmin = Rmaj - h;
646651
lead = starts * P;
@@ -649,8 +654,8 @@ module tq_thread_debug(d, pitch, length, starts=1, hand="right", profile="flat",
649654

650655
echo(str("== tq_thread_debug: ", d, " x ", pitch, " mm =="));
651656
echo(major_dia = 2*Rmaj, minor_dia = 2*Rmin,
652-
pitch_dia = 2*(Rmaj - 0.375*TQ_H*P));
653-
echo(thread_height_h = h, sharp_V_H = TQ_H*P,
657+
pitch_dia = 2*(Rmaj - 0.375*_TQ_H*P));
658+
echo(thread_height_h = h, sharp_V_H = _TQ_H*P,
654659
lead = lead, starts = starts, hand = hand);
655660

656661
%tq_thread(d=d, pitch=pitch, length=length, starts=starts, hand=hand,
@@ -670,8 +675,8 @@ module tq_thread_debug(d, pitch, length, starts=1, hand="right", profile="flat",
670675
}
671676
}
672677
color("blue")
673-
for (z = [0 : pitch : length])
674-
translate([0, 0, z]) cylinder(h=0.2, r=Rmaj*1.05, $fn=na);
678+
for (k = [0 : floor(length/pitch)])
679+
translate([0, 0, k*pitch]) cylinder(h=0.2, r=Rmaj*1.05, $fn=na);
675680
color("green")
676681
translate([Rmaj*1.8, 0, 0]) rotate([90, 0, 0])
677682
linear_extrude(0.4)

0 commit comments

Comments
 (0)