Skip to content

Commit a0a2d95

Browse files
committed
v0.6.0 profile controls and proof hardening
1 parent bf4ac59 commit a0a2d95

11 files changed

Lines changed: 468 additions & 128 deletions

File tree

.github/workflows/ci.yml

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99

1010
jobs:
1111
render:
12-
name: Render-proof (fast suite)
12+
name: Render proof
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@v4
@@ -20,32 +20,15 @@ jobs:
2020
sudo apt-get install -y openscad
2121
openscad --version
2222
23-
- name: Fast suite (preset assertions + smoke grid)
23+
- name: Full render proof (split fast cells + examples + negative asserts + mesh checks)
24+
shell: pwsh
2425
run: |
25-
set -e
26-
openscad -o fast.stl tq_threads_fast_tests.scad 2> fast.log || (cat fast.log; exit 1)
27-
echo "--- OpenSCAD output ---"; cat fast.log
28-
# fail on any manifold warning or error
29-
if grep -Ei "WARNING|ERROR|not a valid 2-manifold" fast.log; then
30-
echo "::error::OpenSCAD reported a warning/error"; exit 1
31-
fi
32-
test -s fast.stl
33-
echo "Fast suite passed."
26+
& ./scripts/render_proof.ps1 -OpenSCAD openscad -OutDir proof/ci
3427
35-
- name: Smoke-render a few examples
36-
run: |
37-
set -e
38-
for id in bolt nut washer csk_bolt coupler; do
39-
openscad -o "ex_$id.stl" -D "SHOW=\"$id\"" tq_threads_examples.scad 2> "ex_$id.log"
40-
grep -Ei "WARNING|ERROR|not a valid 2-manifold" "ex_$id.log" && exit 1 || true
41-
test -s "ex_$id.stl"
42-
done
43-
echo "Example smoke renders passed."
44-
45-
- name: Upload STL artifacts
28+
- name: Upload proof artifacts
4629
if: always()
4730
uses: actions/upload-artifact@v4
4831
with:
49-
name: render-output
50-
path: "*.stl"
32+
name: render-proof-output
33+
path: proof/ci
5134
if-no-files-found: ignore

CHANGELOG.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,44 @@
44
All notable changes to tq-threads. Format loosely follows
55
[Keep a Changelog](https://keepachangelog.com); versions are git tags.
66

7+
## [0.6.0] - 2026-06-23
8+
9+
Flexible non-60-degree profile-control release.
10+
11+
### Added
12+
- `profile="square"` and `profile="rectangle"` thread forms.
13+
- `thread_size=` to decouple one tooth's axial width from pitch, enabling
14+
wide-pitch/narrow-tooth threads and grooves.
15+
- `rect_ratio=` for rectangular threads; `1` is square, `1/3` is shallow.
16+
- `groove=true` to make a helical channel cut into the cylinder surface as a
17+
direct manifold height-field solid.
18+
- `side_angle=` for the half-angle measured from the plane perpendicular to the
19+
axis. `side_angle=30` is the conventional 60-degree included V. Existing
20+
`angle=60` remains the backward-compatible included-angle parameter.
21+
- `lead_ends="none"|"start"|"end"|"both"` as a clearer lead chamfer selector,
22+
while retaining `lead_in` / `lead_out`.
23+
- `taper_rate=` plus `tq_npt_taper_rate()` (`1/16` diameter change per length)
24+
for rate-based taper input.
25+
26+
### Changed
27+
- The render proof now covers the new square, rectangular, groove, narrow-tooth,
28+
six-start, lead-end, taper-rate, preset-wrapper, and TPI-wrapper cases.
29+
- `profile="sharp"` is now the documented full-height pointed V form. The
30+
default flat ISO/UN profile remains unchanged for existing calls.
31+
- `tq_thread_preset()` and `tq_thread_tpi()` now forward the full v0.6 profile
32+
controls (`thread_size`, `side_angle`, `rect_ratio`, `groove`, `taper_rate`,
33+
`lead_ends`, and related overrides).
34+
- The official render proof now captures combined stdout/stderr from OpenSCAD,
35+
enforces strict explicit `-OpenSCAD` paths, runs independent STL mesh checks
36+
for every positive render, and is the CI proof used on push.
37+
38+
### Fixed
39+
- `thread_size > pitch` is rejected consistently for square and rectangular
40+
profiles before internal-thread relief or clamping is applied.
41+
- `tq_wood_screw()` now supplies an explicit printable tooth depth for its
42+
coarse sharp-profile default, so the stricter full-sharp-V semantics do not
43+
make the default/example non-renderable.
44+
745
## [0.5.0] - 2026-06-23
846

947
Printability proof release for TinkerQuarry, plus two targeted geometry fixes.

MANUAL.md

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,22 @@ default (or an oversize internal **cutter** with `internal=true`).
7777
| `hand` | `"right"` | `"right"` or `"left"`. |
7878
| `clearance` | `0.4` | **Total diametral** FDM fit gap (mm); external shrinks `clearance/2`, internal grows `clearance/2`. |
7979
| `fit` | `undef` | Optional **ISO 965** tolerance *position* (allowance): external `"e"/"f"/"g"/"h"` or internal `"G"/"H"` (also accepts `"6g"`,`"6H"`,…; the grade digit is ignored — band width is not modelled). Applied on top of `clearance`. Case must match `internal`. This is nominal intent; M8 `6g` is only about 0.029 mm diametral shift, so tune `clearance` for real FDM fit. |
80-
| `profile` | `"flat"` | `"flat"` (ISO/UN basic), `"sharp"` (full V), `"rounded"` (filleted root/crest). |
81-
| `angle` | `60` | Included flank angle (degrees). 60 = ISO/UN; e.g. 55 ≈ Whitworth. Thread height derives from it. |
80+
| `profile` | `"flat"` | `"flat"` (ISO/UN basic), `"sharp"` (full V), `"rounded"` (filleted root/crest), `"square"`, `"rectangle"`/`"rect"`. |
81+
| `angle` | `60` | Included flank angle (degrees). 60 = ISO/UN; e.g. 55 ≈ Whitworth. Kept for backward compatibility. |
82+
| `side_angle` | `undef` | Half-angle measured from the plane perpendicular to the axis; `30` gives a 60-degree included V. Overrides `angle` for V-height derivation. |
83+
| `thread_size` | `pitch` | Axial width of one tooth/profile, independent of pitch. Must be `<= pitch`. |
84+
| `rect_ratio` | `1` square, `1/3` rectangle | Rectangular radial depth as a fraction of `thread_size`. |
85+
| `groove` | `false` | Invert the profile into a helical channel cut into the cylinder surface. |
8286
| `tooth_height` | `undef` | Explicit radial flight depth (mm). Overrides the angle-derived height. |
8387
| `minor_d` | `undef` | Set the core/minor diameter directly (mm, in `(0,d)`); thread depth becomes `(d − minor_d)/2`. |
8488
| `taper` | `0` | Total **diameter** reduction over the length (mm), applied linearly (base full, top reduced) — NPT-ish tapers / auger tips. |
89+
| `taper_rate` | `undef` | Diameter reduction per axial mm. `tq_npt_taper_rate()` returns `1/16`. Added to `taper`. |
8590
| `crest_flat` | `pitch/8` | Axial crest-flat width (mm) for `flat`. |
8691
| `root_flat` | `pitch/4` | Axial root-flat width (mm) for `flat`. |
8792
| `round` | `1` | Fillet scale for `profile="rounded"` (1 = standard ISO radii). |
8893
| `lead_in` | `true` | Taper the **start** (Z=0) end so the thread begins cleanly. |
8994
| `lead_out` | `true` | Taper the **far** (Z=length) end. |
95+
| `lead_ends` | `undef` | Optional selector `"none"`, `"start"`, `"end"`, or `"both"`; overrides `lead_in`/`lead_out` when set. |
9096
| `chamfer` | `=thread height` | Axial length of the lead taper(s). |
9197
| `arc` | `360` | Angular sweep in degrees; `<360` makes a partial arc. |
9298
| `fn` | `undef` | Per-call angular segment override (else `$fn`/`$fa`/`$fs`). |
@@ -96,8 +102,10 @@ default (or an oversize internal **cutter** with `internal=true`).
96102
**Validation.** Bad inputs `assert` with a clear message instead of rendering
97103
malformed geometry: non-positive `d`/`pitch`/`length`/`steps_per_pitch`, `arc`
98104
outside `(0,360]`, `hand` not in `{right,left}`, `profile` not in
99-
`{flat,sharp,rounded}`, non-integer/`<1` `starts`, negative `clearance`/`chamfer`,
100-
flats exceeding the pitch, or a thread so deep the minor radius ≤ 0.
105+
`{flat,sharp,rounded,square,rect,rectangle}`, non-integer/`<1` `starts`,
106+
negative `clearance`/`chamfer`, invalid `side_angle`, `thread_size > pitch`,
107+
non-positive `rect_ratio`, flats exceeding the thread size, or a thread so deep
108+
the minor radius ≤ 0.
101109

102110
**Geometry produced (flat profile).** Crest at `d/2`, engaged height
103111
`0.5413·P`, minor diameter `d − 1.0825·P` (ISO/UN basic). With `clearance`, the
@@ -240,11 +248,20 @@ tq_washer(8); // ISO 7089 M8
240248
tq_thread(12, 2, 14, angle=55); // 55° flank (Whitworth-ish)
241249
tq_thread(10, 2, 14, tooth_height=1.2); // set the radial flight depth directly
242250
tq_thread(12, 1.75, 16, taper=3); // cone: 3 mm dia reduction over the length
251+
tq_thread(10, 4, 14, thread_size=1, side_angle=30, profile="sharp"); // narrow 60-degree V
252+
tq_thread(12, 3, 14, profile="square", thread_size=1.5);
253+
tq_thread(12, 6, 14, profile="rectangle", thread_size=4, rect_ratio=1/3);
254+
tq_thread(14, 4, 14, profile="square", groove=true, lead_ends="both");
255+
tq_thread(12, 2, 14, taper_rate=tq_npt_taper_rate());
243256
```
244257
`angle` (default 60) sets the included flank angle; the thread height derives
245-
from it. `tooth_height` overrides that height explicitly. `taper` shifts the
246-
whole profile inward linearly along Z (NPT-ish tapers, auger tips). The
247-
minor-radius safety assert accounts for both rounded roots and `taper`.
258+
from it. `side_angle` uses the alternate convention from the plane perpendicular
259+
to the axis, so `side_angle=30` maps to a 60-degree included V. `thread_size`
260+
decouples tooth width from pitch. `tooth_height` overrides radial height
261+
explicitly. `taper` and `taper_rate` shift the whole profile inward linearly
262+
along Z; `tq_npt_taper_rate()` is the 1:16 diameter-change reference rate only,
263+
not a full NPT profile. The minor-radius safety assert accounts for rounded
264+
roots and taper.
248265

249266
### Auger / deep coarse flight
250267
```openscad
@@ -439,6 +456,9 @@ Notes:
439456
rather than per-side tolerances.
440457
- Default profile is the ISO/UN **basic** (flat) form; use `profile="rounded"`
441458
for a deeper rounded root.
459+
- v0.6 generic profile forms map as follows: `profile="sharp"` is a full-height
460+
pointed V, `profile="square"` uses radial depth equal to `thread_size`, and
461+
`profile="rectangle"` uses radial depth `thread_size * rect_ratio`.
442462

443463
---
444464

@@ -449,12 +469,13 @@ openscad -o out.stl tq_threads_fast_tests.scad # fast: asserts + small grid
449469
openscad -o demo.stl tq_threads_heavy_tests.scad # heavy: full visual grid
450470
```
451471
```powershell
452-
pwsh scripts/render-tests.ps1 # fast suite, with manifold-warning check
453-
pwsh scripts/render-tests.ps1 -Heavy # add the heavy grid
472+
powershell -ExecutionPolicy Bypass -File scripts\render_proof.ps1 # full proof
473+
powershell -ExecutionPolicy Bypass -File scripts\render_proof.ps1 -Heavy # add heavy grid
454474
```
455-
The fast suite also runs **compile-time assertions** that every required preset
456-
(M2…M64) resolves to the correct pitch. CI (`.github/workflows/ci.yml`) runs the
457-
fast suite on every push.
475+
The proof captures both stdout and stderr from OpenSCAD, runs negative/assert
476+
tests, and checks every positive STL with the independent mesh edge-pairing
477+
checker. `scripts/render-tests.ps1` remains only as a deprecated compatibility
478+
alias. CI (`.github/workflows/ci.yml`) runs the same proof on every push.
458479

459480
---
460481

@@ -463,6 +484,10 @@ fast suite on every push.
463484
| Symptom | Cause / fix |
464485
|---|---|
465486
| `assert` failure on render | Read the message — it names the bad parameter. |
487+
| `thread_size must be <= pitch` | Narrow-tooth profiles can be smaller than pitch, but a single tooth cannot exceed one pitch. |
488+
| `side_angle must be > 0 and < 90` | `side_angle` is the half-angle from the plane perpendicular to the thread axis; use `30` for a conventional 60-degree V. |
489+
| `rect_ratio must be > 0` | Rectangular depth is `thread_size * rect_ratio`; use `1` for square or about `1/3` for shallow rectangular threads. |
490+
| `lead_ends must be none/start/end/both` | Use `lead_ends` for the common cases, or the older `lead_in` / `lead_out` booleans for compatibility. |
466491
| Thread too tight / loose when printed | Adjust `clearance` (see [FDM](#fdm-deep-dive)); recalibrate. |
467492
| Faceted / coarse flanks | Raise `$fn` or lower `$fs`; raise `steps_per_pitch`. |
468493
| Huge mesh / slow render | Lower `$fn`/`steps_per_pitch`; it's a height-field. |
@@ -477,8 +502,12 @@ fast suite on every push.
477502
**Current limitations** (see also [README](README.md#limitations)):
478503
- Internal threads via external-form cutter (pragmatic for FDM, not metrology-grade).
479504
- Countersunk head diameters are nominal ISO 10642 values (overridable).
480-
- No tapered pipe (NPT), ACME/trapezoidal, or buttress forms (60°-V only).
481-
482-
**Possible future work:** ACME/trapezoidal profile, true ISO internal profile
483-
option, more named hardware presets, and a parametric thread-relief groove. PRs
484-
and ideas welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
505+
- Square, rectangular, narrow-tooth, and groove profiles are generic printable
506+
forms, not standards-accurate ACME/trapezoidal/buttress fit-class profiles.
507+
- `taper_rate` can express a 1:16 NPT-style diameter taper reference, but the
508+
NPT truncated thread profile and sealing rules are not implemented.
509+
510+
**Possible future work:** standards-accurate ACME/trapezoidal/buttress profiles,
511+
true ISO internal profile option, more named hardware presets, and more
512+
manufacturing-oriented relief presets. PRs and ideas welcome — see
513+
[CONTRIBUTING.md](CONTRIBUTING.md).

PROVENANCE.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Fidelity classes: **EXACT** (nominal standard value used verbatim) ·
1818
| Source / spec | Used for | Class | Faithfulness & notes |
1919
|---|---|---|---|
2020
| **ISO 68-1** (metric basic profile) | 60° form, `H=(√3/2)P`, crest `P/8`, root `P/4`, engaged `0.5413P` | EXACT/DERIVED | Geometry transcribed/derived from the basic-profile definition. |
21+
| **First-principles thread geometry** | `side_angle`, `thread_size`, square/rectangular profiles, groove inversion | DERIVED/APPROX | Generic printable forms implemented from the behavioral spec and trig; no standards fit class claimed. |
2122
| **ISO 261** (general plan) | metric coarse + fine major Ø & pitch (presets) | EXACT | Nominal diameter/pitch series. Verified by `tq_presets_selfcheck()` + cross-checked online. |
2223
| **ISO 262** (selected sizes) | preferred metric subset | EXACT | Informs which presets are "common". |
2324
| **ISO 965-1 §13.1** (tolerances) | `fit=` fundamental deviation (allowance) | DERIVED (exact formula) | external e/f/g/h, internal G/H. **Position only**, not the grade/band. Formula value (unrounded); ISO tables round to whole µm. NOT metrology-grade. |
@@ -28,10 +29,31 @@ Fidelity classes: **EXACT** (nominal standard value used verbatim) ·
2829
| **ISO 4762** (socket head cap screws) | `tq_shcs_head` (dk,k), `tq_hex_key_af` | EXACT (listed) / APPROX (fallback) | |
2930
| **ISO 10642** (csk socket head) | `tq_csk_head_dia` (90°) | EXACT (listed) / APPROX (fallback) | |
3031
| **ISO 4757** (cross recesses) | Phillips recess *concept* | APPROX | Printable cruciform approximation; NOT gauge-accurate. |
31-
| **ASME B1.20.1** (NPT) | taper *context* only | | Linear `taper` is generic; NPT's truncated profile is NOT implemented. |
32+
| **ASME B1.20.1** (NPT) | taper-rate context only | DERIVED | `tq_npt_taper_rate()` returns the published 1:16 diameter taper reference. NPT's truncated profile is NOT implemented. |
3233
| *(none — generic)* | `tq_auger`, `tq_bottle_thread`, `tq_wood_screw` | APPROX/GENERIC | Printable generic forms; **no** standard claimed (explicitly documented). |
3334
| *(this library)* | `clearance` default 0.4 mm (½/½ split), `TQ_MIN_SEG`, lead-in chamfers, rounded fillets `H/6`,`H/12` | FDM/DERIVED | Printability choices / derived geometry, not standards. |
3435

36+
## v0.6 profile-control provenance
37+
38+
The v0.6 profile controls came from the local capability specification dated
39+
2026-06-23 and first-principles geometry, not from any third-party OpenSCAD
40+
library.
41+
42+
- `side_angle` uses the right-triangle relation `h = S/(2*tan(beta))`, where
43+
`S` is the axial tooth width and `beta` is the flank angle from the plane
44+
perpendicular to the axis. `beta=30` gives the familiar 60-degree included V.
45+
- `thread_size` is a caller-selected axial tooth width and is validated before
46+
internal-thread relief or square/rectangular clamping. Values greater than
47+
pitch are rejected.
48+
- `profile="sharp"` is a full-height V. The compatibility default remains the
49+
flat ISO/UN basic form with crest/root truncation.
50+
- Square, rectangular, and groove profiles are generic printable height-field
51+
shapes. They do not claim ACME, trapezoidal, buttress, or pipe-thread gauge
52+
compatibility.
53+
- `taper_rate` is just diameter change per unit length. The NPT helper returns
54+
the public 1:16 taper-rate reference only; it does not implement the NPT
55+
truncated profile or sealing/gauge requirements.
56+
3557
## What could only be validated with physical prints + calipers
3658
- Real-world **fit** of a printed bolt/nut pair at a given `clearance`/`fit`.
3759
- Whether a printed thread meets an actual **ISO/ASME tolerance class** (tq-threads

0 commit comments

Comments
 (0)