Clarify Enclosure Labels And Validate Clearance

This commit is contained in:
2026-09-28 12:13:46 +02:00
parent c5bf3e32f5
commit 26ed569fcb
8 changed files with 90704 additions and 18349 deletions
+2
View File
@@ -10,6 +10,8 @@ Working memory, not an implementation timeline. Source is authoritative; begin w
## Parametric enclosure — initial concept, physical validation pending ## Parametric enclosure — initial concept, physical validation pending
- Label-only follow-up: removed DISPLAY, grouped `USB: UART0 / CDC` (local wiring/measurement evidence does not establish physical left/right roles), and raised `ESP32 / Serial Swiss / Army Knife` on the blank roof left of the display. Shared labels export retained; unlabeled lid CSG exactly matches the pre-change source. Lid STL and three previews refreshed; base/plungers and PCB unchanged. CGAL simple lid/labels exports, source/datum/mesh checks and actual-glyph roof clearance >=1 mm passed; no physical print/test. Lid detail now views readable branding from the USB side.
- `hardware/case/` corrected per user: 5 mm base insert bosses directly support carrier underside Z7.4/top Z9; two M3 RS-232 and four M2 OLED brass male/female bodies ABOVE carrier span 10 mm to module underside Z19. Studs cross actual 1.6 mm carrier holes into respective inserts; four separate carrier holes take direct M3 screws. ESP32 has no supplied mounting holes and remains header-supported. No under-carrier brass or printable spacers remain. Shell now **106 × 86.8 × 37.4 mm**, roof underside Z35 (10 mm lower); ports/display/DB9 panel/hangers/buttons follow the corrected datums. Red RF overlay/enclosure reserve restriction removed; physical antenna retained, no Wi-Fi guarantee and no PCB/schematic/copper/reserve/layout/export changes. - `hardware/case/` corrected per user: 5 mm base insert bosses directly support carrier underside Z7.4/top Z9; two M3 RS-232 and four M2 OLED brass male/female bodies ABOVE carrier span 10 mm to module underside Z19. Studs cross actual 1.6 mm carrier holes into respective inserts; four separate carrier holes take direct M3 screws. ESP32 has no supplied mounting holes and remains header-supported. No under-carrier brass or printable spacers remain. Shell now **106 × 86.8 × 37.4 mm**, roof underside Z35 (10 mm lower); ports/display/DB9 panel/hangers/buttons follow the corrected datums. Red RF overlay/enclosure reserve restriction removed; physical antenna retained, no Wi-Fi guarantee and no PCB/schematic/copper/reserve/layout/export changes.
- Superseding DB9 change: separate plate/export/mode and M3 insert hangers removed. Cover now has integral nominal 48 mm-wide × 2.4 mm-thick connector panel at Y−0.47…1.93, Z21.07…35, continuous roof connection and two nominal 6 mm forward ribs outside flange bounds, extended into the wall to close former 1 mm side slits. Bottom-open 18.078 mm shell slot and closed Ø3.6 post holes at real 25 mm spacing permit straight vertical lowering **after removing both screw posts and all cables**; front-wall recess also opens to seam. Panel clears PCB by 0.5 mm and anchors by 3.10 mm in Y. No horizontal-slide claim. Insert count now 10 M3 + 4 M2, all base. Added forward-only base closure Y−3.4…−0.5: shoulders Z20.82, 17.578038-wide tongue Z21.69762024, 0.25 mm seams, 0.6 below STEP shell, 0.5 forward of module PCB. PCB files untouched. - Superseding DB9 change: separate plate/export/mode and M3 insert hangers removed. Cover now has integral nominal 48 mm-wide × 2.4 mm-thick connector panel at Y−0.47…1.93, Z21.07…35, continuous roof connection and two nominal 6 mm forward ribs outside flange bounds, extended into the wall to close former 1 mm side slits. Bottom-open 18.078 mm shell slot and closed Ø3.6 post holes at real 25 mm spacing permit straight vertical lowering **after removing both screw posts and all cables**; front-wall recess also opens to seam. Panel clears PCB by 0.5 mm and anchors by 3.10 mm in Y. No horizontal-slide claim. Insert count now 10 M3 + 4 M2, all base. Added forward-only base closure Y−3.4…−0.5: shoulders Z20.82, 17.578038-wide tongue Z21.69762024, 0.25 mm seams, 0.6 below STEP shell, 0.5 forward of module PCB. PCB files untouched.
- Three default CGAL exports, native registration (including flange plane, hole diameters/no ESP32 NPTH), evaluated stack/connector/stud/web fixtures, edge-manifold meshes, three parameter CGAL smoke tests and continuous 60 mm vertical envelope sweeps passed; restored-post negative control detects blockage. Thicker-panel sweep caught and fixed residual inner-wall strip; default and raised/thicker variant now pass. Closure follow-up regenerated base/lid and all three previews (unchanged plungers revalidated); default plus two variant sweeps include per-step upstand/lid travel, base/lid seating and electronics insertion. Four parameter mesh tests pass; closure guards and STEP shell-minimum registration added. Three regenerated previews visually inspected, including bare-lid detail. This is selected-envelope CAD clearance, not complete hardware or strength qualification. - Three default CGAL exports, native registration (including flange plane, hole diameters/no ESP32 NPTH), evaluated stack/connector/stud/web fixtures, edge-manifold meshes, three parameter CGAL smoke tests and continuous 60 mm vertical envelope sweeps passed; restored-post negative control detects blockage. Thicker-panel sweep caught and fixed residual inner-wall strip; default and raised/thicker variant now pass. Closure follow-up regenerated base/lid and all three previews (unchanged plungers revalidated); default plus two variant sweeps include per-step upstand/lid travel, base/lid seating and electronics insertion. Four parameter mesh tests pass; closure guards and STEP shell-minimum registration added. Three regenerated previews visually inspected, including bare-lid detail. This is selected-envelope CAD clearance, not complete hardware or strength qualification.
+5 -3
View File
@@ -71,7 +71,7 @@ Measured shell centres **X15.23 and 26.63**, widths **8.87**, mouth plane **Y77.
A single joined bay spans **X9.595…32.265**, **Z19.6…26.6** (22.67 × 7), through the bottom-end wall. It admits both plug overmoulds instead of adding fragile individual webs. There are no internal bosses or tunnels between the wall and mouths. Mouth-to-exterior-wall distance is **5.695**: the plug must reach through that recess. The opening is deliberately generous relative to measured *widths*; shell height/depth and both plug overmoulds still need measurement. `usb_below_module_top`, `usb_above_module_top`, `usb_extra_width` are cutout controls; `usb_reference_depth/h` affect only the approximate preview. Enlarge for the actual plugs rather than moving U1 or growing Y without justification. A single joined bay spans **X9.595…32.265**, **Z19.6…26.6** (22.67 × 7), through the bottom-end wall. It admits both plug overmoulds instead of adding fragile individual webs. There are no internal bosses or tunnels between the wall and mouths. Mouth-to-exterior-wall distance is **5.695**: the plug must reach through that recess. The opening is deliberately generous relative to measured *widths*; shell height/depth and both plug overmoulds still need measurement. `usb_below_module_top`, `usb_above_module_top`, `usb_extra_width` are cutout controls; `usb_reference_depth/h` affect only the approximate preview. Enlarge for the actual plugs rather than moving U1 or growing Y without justification.
Signage says **USB**, not a guessed left/right native-USB vs USB-UART mapping. Both connections remain physically available independent of networking. One grouped two-line label, **USB** above **CDC | UART0**, spans the combined bay; it does **not** assign left/right ports. The [wiring guide](../../docs/wiring.md#administration-and-native-usb-connectors) establishes bridge/UART0 and native/CDC roles, but neither it nor the measured HW678 mouth geometry establishes physical connector-to-role order. Seller labels are not used as mapping evidence. Both connections remain physically available independent of networking.
### DB9: external flange retention, not a guessed thread ### DB9: external flange retention, not a guessed thread
@@ -117,6 +117,8 @@ Reseating a same-length **4.8 mm** post body puts its mouth at **Y−5.270**, **
8. Insert verified stock or replacement post studs from outside through the two closed cover holes into the connector. Check clamp contact, engagement and bottoming independently; do not use them to pull a misaligned flange/PCB into position. Tighten case/post hardware cautiously to verified values. Test full USB/DB9 mating and cable locks, particularly the changed post-mouth reach; perform a restrained cable-load test before trusting retention. 8. Insert verified stock or replacement post studs from outside through the two closed cover holes into the connector. Check clamp contact, engagement and bottoming independently; do not use them to pull a misaligned flange/PCB into position. Tighten case/post hardware cautiously to verified values. Test full USB/DB9 mating and cable locks, particularly the changed post-mouth reach; perform a restrained cable-load test before trusting retention.
9. To remove the cover, unplug every cable, unscrew both connector posts completely out of the cover holes and remove the four case screws; lift vertically, retaining the plungers. The electronics remain secured by their independent carrier/daughterboard fasteners. 9. To remove the cover, unplug every cable, unscrew both connector posts completely out of the cover holes and remove the four case screws; lift vertically, retaining the plungers. The electronics remain secured by their independent carrier/daughterboard fasteners.
The roof prominently carries the README project name on three lines, **ESP32 / Serial Swiss / Army Knife**, left of the display and readable from the USB end. The former DISPLAY caption is removed; RS-232 and button captions are unchanged. All text comes from the shared `labels()` module, including `part="labels"`; `emboss=false` omits it only from the lid.
Raised labels are positive **0.6 mm emboss**, not engraving. Single-colour is the default; roof-up printing allows a filament change just above the roof for contrasting text (check actual sliced layers). For a separate material export `labels`, and export the lid with `emboss=false`. In a multi-object slicer, preserve XY origin and translate the flat text object up **22.13 mm** relative to the default lid export (its initial 0.02 overlaps the roof). Do not auto-centre individual letters; STL carries neither colour nor assembly transforms. Changes to stack require recalculating that offset: `top_z - eps - split_z - seam_clearance`. Raised labels are positive **0.6 mm emboss**, not engraving. Single-colour is the default; roof-up printing allows a filament change just above the roof for contrasting text (check actual sliced layers). For a separate material export `labels`, and export the lid with `emboss=false`. In a multi-object slicer, preserve XY origin and translate the flat text object up **22.13 mm** relative to the default lid export (its initial 0.02 overlaps the roof). Do not auto-centre individual letters; STL carries neither colour nor assembly transforms. Changes to stack require recalculating that offset: `top_z - eps - split_z - seam_clearance`.
## Regeneration and checks ## Regeneration and checks
@@ -133,10 +135,10 @@ python3 validate.py
/usr/bin/python3 -B validate.py --source --installation --variants /usr/bin/python3 -B validate.py --source --installation --variants
QT_QPA_PLATFORM=offscreen openscad -o assembly-preview.png --imgsize=1200,1000 --viewall --autocenter --projection=o --camera=0,0,0,55,0,25,200 -D 'part="exploded"' enclosure.scad QT_QPA_PLATFORM=offscreen openscad -o assembly-preview.png --imgsize=1200,1000 --viewall --autocenter --projection=o --camera=0,0,0,55,0,25,200 -D 'part="exploded"' enclosure.scad
QT_QPA_PLATFORM=offscreen openscad -o db9-preview.png --imgsize=1200,900 --viewall --autocenter --projection=o --camera=0,0,0,80,0,180,200 -D 'part="assembly"' enclosure.scad QT_QPA_PLATFORM=offscreen openscad -o db9-preview.png --imgsize=1200,900 --viewall --autocenter --projection=o --camera=0,0,0,80,0,180,200 -D 'part="assembly"' enclosure.scad
QT_QPA_PLATFORM=offscreen openscad -o lid-detail.png --render --imgsize=1200,900 --viewall --autocenter --projection=o --camera=0,0,0,100,0,155,200 -D 'part="lid"' enclosure.scad QT_QPA_PLATFORM=offscreen openscad -o lid-detail.png --render --imgsize=1200,900 --viewall --autocenter --projection=o --colorscheme=Metallic --camera=0,0,0,55,0,25,200 -D 'part="lid"' enclosure.scad
``` ```
`validate.py` uses Python's standard library and installed OpenSCAD for evaluated datum, stud-engagement and nozzle-web fixtures, plus ASCII STL checks; `--source` additionally uses the **already installed system KiCad `pcbnew` binding**, read-only. It verifies current outline/holes/placements, measured U1 USB mouth graphics and STEP-derived lock/shell data against case constants. It is a drift tripwire, not a full source parser for every SCAD expression. It checks each STL has two oppositely directed uses of every triangle edge, nonzero triangle area, the expected connected component count (1 base, 1 lid, 3 plungers), positive total signed volume and minimum Z≈0. It does **not** prove vertex-manifoldness, absence of self-intersections, full component collision clearance or physical fit. `validate.py` uses Python's standard library and installed OpenSCAD for evaluated datum, stud-engagement and nozzle-web fixtures, plus ASCII STL checks; `--source` additionally uses the **already installed system KiCad `pcbnew` binding**, read-only. It verifies current outline/holes/placements, measured U1 USB mouth graphics and STEP-derived lock/shell data against case constants. The default checks also project the actual label glyphs against the roof, requiring at least 1 mm clearance from its edges, display/button openings and screw-head recesses. It is a drift tripwire, not a full source parser for every SCAD expression. It checks each STL has two oppositely directed uses of every triangle edge, nonzero triangle area, the expected connected component count (1 base, 1 lid, 3 plungers), positive total signed volume and minimum Z≈0. It does **not** prove vertex-manifoldness, absence of self-intersections, full component collision clearance or physical fit.
Validation results and bounded parameter/sweep tests are recorded in [checks.md](checks.md). The remaining exports are base, integral lid and three plungers; the corrected 37.4 mm shell / 7.4–9–19 mm stack is unchanged. No slicer, physical print, screw-fit, cable-load, hardware/firmware, thermal or RF acceptance is implied. Validation results and bounded parameter/sweep tests are recorded in [checks.md](checks.md). The remaining exports are base, integral lid and three plungers; the corrected 37.4 mm shell / 7.4–9–19 mm stack is unchanged. No slicer, physical print, screw-fit, cable-load, hardware/firmware, thermal or RF acceptance is implied.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+6 -2
View File
@@ -303,8 +303,12 @@ module label_at(x,y,s,size=2.5) {
} }
module labels() { module labels() {
label_at(module_cx,7,"RS-232",3); label_at(module_cx,7,"RS-232",3);
label_at(20.93,74,"USB",3); // native vs UART left/right NOT established // One grouped bay label: physical UART0/CDC left/right is not established.
label_at(module_cx,35,"DISPLAY",2.4); label_at(20.93,71,"USB",3);
label_at(20.93,76,"CDC | UART0",2.4);
label_at(20.93,32,"ESP32",5);
label_at(20.93,41,"Serial Swiss",4);
label_at(20.93,50,"Army Knife",4);
for(i=[0:2]) label_at(button_centres[i][0],77,["BACK","SELECT","NEXT"][i],2.1); for(i=[0:2]) label_at(button_centres[i][0],77,["BACK","SELECT","NEXT"][i],2.1);
} }
module lid() { module lid() {
+90668 -18344
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 32 KiB

+23
View File
@@ -235,6 +235,28 @@ def variant_check():
print('PASS parameter CGAL mesh', part, defines) print('PASS parameter CGAL mesh', part, defines)
def label_check():
"""Actual glyph solids must stay on roof, 1 mm clear of all roof openings."""
with tempfile.TemporaryDirectory(prefix="case-labels-") as temp:
wrapper = Path(temp)/"labels.scad"
wrapper.write_text(
f'include <{ROOT / "enclosure.scad"}>\n'
'cube(1);\n'
'translate([0,0,80]) linear_extrude(1) difference() {\n'
' projection() labels();\n'
' offset(delta=-1) projection(cut=true)\n'
' translate([0,0,-top_z+eps/2]) lid_body();\n'
'}\n')
path = Path(temp)/"clearance.stl"
result = subprocess.run(
["openscad", "-o", str(path), "-D", 'part="check"', str(wrapper)],
env={**os.environ, "QT_QPA_PLATFORM": "offscreen"},
capture_output=True, text=True, timeout=180)
assert result.returncode == 0 and "ERROR:" not in result.stderr, result.stderr
near([mesh_check(path, 1)["volume_mm3"]], [1])
print("PASS actual label glyphs within roof, >=1 mm from edges, display, buttons and screw-head openings")
def main(): def main():
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--source", action="store_true", help="also check saved KiCad/STEP registration") parser.add_argument("--source", action="store_true", help="also check saved KiCad/STEP registration")
@@ -246,6 +268,7 @@ def main():
if args.installation: if args.installation:
installation_check() installation_check()
geometry_check() geometry_check()
label_check()
if args.source: if args.source:
source_check() source_check()
reports = [mesh_check(ROOT / name, n) for name, n in [ reports = [mesh_check(ROOT / name, n) for name, n in [