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
+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.
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
@@ -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.
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`.
## Regeneration and checks
@@ -133,10 +135,10 @@ python3 validate.py
/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 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.
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() {
label_at(module_cx,7,"RS-232",3);
label_at(20.93,74,"USB",3); // native vs UART left/right NOT established
label_at(module_cx,35,"DISPLAY",2.4);
// One grouped bay label: physical UART0/CDC left/right is not established.
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);
}
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)
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():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--source", action="store_true", help="also check saved KiCad/STEP registration")
@@ -246,6 +268,7 @@ def main():
if args.installation:
installation_check()
geometry_check()
label_check()
if args.source:
source_check()
reports = [mesh_check(ROOT / name, n) for name, n in [