Integrate DB9 mounting into enclosure lid

This commit is contained in:
2026-09-26 13:50:55 +02:00
parent f925293d3f
commit c5bf3e32f5
12 changed files with 7349 additions and 15164 deletions
+3 -1
View File
@@ -11,7 +11,9 @@ 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
- `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.
- Four default CGAL exports, native registration (including hole diameters/no ESP32 NPTH), evaluated stack/stud/nozzle-web fixtures, closed-edge mesh checks and two parameter CGAL smoke tests passed; both updated previews visually inspected. Not printed, assembled or physically tested. Actual underside solder/prong clearance within 5 mm, brass shoulder/insert lands, unknown stud lengths (4 mm reference gives 2.4 mm engagement), insert usable threads, header mating height, USB Z/plug envelopes, OLED height, switches and DB9 panel-jackscrew thread/removability/reach remain explicit gates. Panel cable-lock axes are 25 mm, module holes 24.13 mm; no M3 inference from STEP bores. See `hardware/case/README.md` and `checks.md` before printing. - 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.
- DB9 hardware remains a gate: no inferred M3/4-40 thread; removability, stud length, engagement and cable reach unmeasured. Panel consumes 2.4 mm of stud reach; verified longer studs/extensions may be needed. Reseated 4.8 mm bodies move mouths to Y−5.27, **1.4 mm ahead of nose**: full mating may require compatible shorter-body hardware and must be physically verified. Nose remains 0.47 mm beyond outer wall / 3.4 mm beyond panel. Do not force an incompatible post/cable combination or claim stock threads sufficient. Actual underside solder/prong clearance within 5 mm, brass lands/studs/inserts, header height, USB/glass/buttons and load strength also remain gates. See `hardware/case/README.md` and `checks.md`; no print, physical assembly or hardware test performed.
## PCB accepted-fit labels and worksheet — complete ## PCB accepted-fit labels and worksheet — complete
+29 -23
View File
@@ -12,14 +12,13 @@ Open `enclosure.scad` in OpenSCAD (tested here with **2021.01**). Set `part` to:
|---|---| |---|---|
| `assembly` / `exploded` | Assembled / exploded reference preview; **not printable combined parts** | | `assembly` / `exploded` | Assembled / exploded reference preview; **not printable combined parts** |
| `base` | Floor, carrier insert bosses, daughterboard insert bosses, side assembly bosses | | `base` | Floor, carrier insert bosses, daughterboard insert bosses, side assembly bosses |
| `lid` | Roof, skirt, screw head wells, button guides, DB9 panel attachment hangers, raised labels | | `lid` | Roof, skirt, screw head wells, button guides, integral bottom-open DB9 retaining panel and ribs, raised labels |
| `panel` | Separate externally installed DB9 retaining panel, oriented flat for printing |
| `plunger` / `plungers` | One / three identical captive button actuators | | `plunger` / `plungers` | One / three identical captive button actuators |
| `labels` | Raised text alone for a second material or separately attached lettering | | `labels` | Raised text alone for a second material or separately attached lettering |
| `layout` | Spaced parts for inspection; orient/support each part separately in the slicer | | `layout` | Spaced parts for inspection; orient/support each part separately in the slicer |
`show_references` displays simplified hardware only in **F5 preview** (`$preview` and `%` background geometry); it is excluded from STL/F6 geometry. The reference boxes are not complete component models. Orange/brown stock DB9 post envelopes deliberately reveal the conflict with a panel fitted without replacing/reseating the posts. Gold dummies represent **two M3 and four M2 male/female brass standoffs above the carrier**, with lower studs and blind upper bores; their OD, stud length and bore depth are provisional. The red RF overlay and enclosure RF-reserve restriction have been removed at the user's request. Actual antenna geometry remains; no PCB reserve/copper/layout was changed and no Wi-Fi performance guarantee is implied. `show_references` displays simplified hardware only in **F5 preview** (`$preview` and `%` background geometry); it is excluded from STL/F6 geometry. The reference boxes are not complete component models. Orange/brown DB9 post-body envelopes are shown reseated **outside** the integral panel; they illustrate unverified hardware, not guaranteed stock-thread or cable fit. Gold dummies represent **two M3 and four M2 male/female brass standoffs above the carrier**, with lower studs and blind upper bores; their OD, stud length and bore depth are provisional. The red RF overlay and enclosure RF-reserve restriction have been removed at the user's request. Actual antenna geometry remains; no PCB reserve/copper/layout was changed and no Wi-Fi performance guarantee is implied.
All dimensions are **mm**. Parameter/source coordinates are carrier-local, X right and Y down toward USB; add `(100,100)` for KiCad coordinates. The rendered model maps `Y_world = 80 - Y_carrier`, so the DB9 is above the USB end in top view. Text is separately transformed to remain readable from the USB end. All dimensions are **mm**. Parameter/source coordinates are carrier-local, X right and Y down toward USB; add `(100,100)` for KiCad coordinates. The rendered model maps `Y_world = 80 - Y_carrier`, so the DB9 is above the USB end in top view. Text is separately transformed to remain readable from the USB end.
@@ -55,14 +54,14 @@ The user's intended/reported module elevation is **10 mm**. The descriptions “
**Insert pockets are examples, not validated fits:** M3 Ø4.2 × 4 deep; M2 Ø3.2 × 3 deep, 0.3 entry flare. These numbers do not specify insert OD/length, thread engagement or thermal process. Choose the actual insert family and set diameter/depth/boss OD using its supplier recommendations and a material-specific test coupon first. Install inserts before electronics. Default carrier pockets leave 3.4 mm solid material to the outside bottom (1 mm boss web plus 2.4 floor). **Insert pockets are examples, not validated fits:** M3 Ø4.2 × 4 deep; M2 Ø3.2 × 3 deep, 0.3 entry flare. These numbers do not specify insert OD/length, thread engagement or thermal process. Choose the actual insert family and set diameter/depth/boss OD using its supplier recommendations and a material-specific test coupon first. Install inserts before electronics. Default carrier pockets leave 3.4 mm solid material to the outside bottom (1 mm boss web plus 2.4 floor).
Default insert count with all options enabled: **12 M3** (4 carrier, 4 case, 2 RS-232, 2 panel hangers) and **4 M2** (OLED). The last two M3 pockets face outward horizontally in the lid; install with the lid removed. These M3s are **not** the connector cable-lock threads. Default insert count with all options enabled: **10 M3** (4 carrier, 4 case, 2 RS-232) and **4 M2** (OLED), all in the base. **No DB9 panel insert hangers, panel-to-lid screws or separate printable plate remain.** Connector screw posts use their own verified thread, not an inferred M3 designation.
Screw-length selection is by measured engagement, not a fixed BOM promise: Screw-length selection is by measured engagement, not a fixed BOM promise:
- Carrier screws: **1.6 mm** carrier thickness + washers + engagement into the base insert. Keep the head and shoulder clear of nearby components/copper. - Carrier screws: **1.6 mm** carrier thickness + washers + engagement into the base insert. Keep the head and shoulder clear of nearby components/copper.
- Case screw under-head plane Z34.6 to insert mouth Z15 is **19.6**, plus chosen engagement (e.g. 3 gives 22.6); do not blindly use an overlong 25 mm screw in a 4 mm pocket. - Case screw under-head plane Z34.6 to insert mouth Z15 is **19.6**, plus chosen engagement (e.g. 3 gives 22.6); do not blindly use an overlong 25 mm screw in a 4 mm pocket.
- Lower brass studs first traverse **1.6 mm carrier**, then engage the base inserts. `brass_m3_stud_l` / `brass_m2_stud_l` default to **4 mm reference lengths**, not measured hardware: tips at Z5.0 give **2.4 mm engagement**, leaving 1.6 mm to the M3 pocket bottom / 0.6 mm to the M2 pocket bottom. Actual insert usable threads may differ from pocket depth: measure before selecting. Upper daughterboard screws traverse **1.57 RS-232 / 1.6 OLED**, plus washers and chosen engagement into the **top female**, not the base. Preview female depths are 4/3 mm, also unknown. Verify studs/screws pass the nominal 3/2 mm holes without force; these are not generous M3/M2 clearance holes. - Lower brass studs first traverse **1.6 mm carrier**, then engage the base inserts. `brass_m3_stud_l` / `brass_m2_stud_l` default to **4 mm reference lengths**, not measured hardware: tips at Z5.0 give **2.4 mm engagement**, leaving 1.6 mm to the M3 pocket bottom / 0.6 mm to the M2 pocket bottom. Actual insert usable threads may differ from pocket depth: measure before selecting. Upper daughterboard screws traverse **1.57 RS-232 / 1.6 OLED**, plus washers and chosen engagement into the **top female**, not the base. Preview female depths are 4/3 mm, also unknown. Verify studs/screws pass the nominal 3/2 mm holes without force; these are not generous M3/M2 clearance holes.
- Panel-to-lid screws traverse **2 mm** panel plus selected M3 engagement. Round/pan head clearance is open from outside; use small heads/washers which do not foul cable hardware. - Connector posts traverse **2.4 mm** integral panel plus any washers/gap before engaging the connector flange. See the thread-reach and cable-mating gates below; these are not panel-to-lid screws.
## Cable interfaces ## Cable interfaces
@@ -78,19 +77,25 @@ Signage says **USB**, not a guessed left/right native-USB vs USB-UART mapping. B
![DB9-side reference preview](db9-preview.png) ![DB9-side reference preview](db9-preview.png)
The STEP source registers the flange front at **Y1.930**, stock lock mouths **Y−2.870**, shell nose **Y−3.870**, with module front at Y0. The flange is **30.8 × 12.55**. Shell maximum root/lip envelope is **16.878038 × 9.1**; the panel hole is an intentionally rectangular **18.078038 × 10.3** envelope with 0.6 margin per side, not a reconstructed D-profile. ![Integral lid underside: closed post holes and bottom-open shell slot](lid-detail.png)
The **separate 46 × 13.68 × 2 panel** sits at **Y−0.070…1.930**, above the module PCB at Z21.07…34.75. The module PCB extends under/forward of it; the panel must not be extended down through that PCB. A **48 mm wide** main-wall service recess is open at X33…81, Z18.4…35, making the connector and panel screws externally reachable. Shell nose remains 0.47 beyond the ordinary outer wall plane. Check your cable hood against this recess; no universal connector/cable compatibility is claimed. The STEP registers flange front **Y1.930**, original post mouths **Y−2.870**, shell nose **Y−3.870**, with module front Y0. Flange bounds are **X41.57…72.37**, Z20.57262024…33.12262024, thickness 0.4. Shell maximum root/lip envelope is **16.878038 × 9.1**; the design uses a conservative rectangular clearance, not a reconstructed D-profile.
There are **three independent fastener systems**: **Integral cover mounting:** a **48 mm nominal wide × 2.4 mm axial-thickness** panel spans X33…81 (0.02 mm union overlap at each wall), Y−0.470…1.930, Z21.07…35 and joins the roof directly. Two **6 mm nominal wide full-height ribs**, X33…39 and X75…81 (outer union overlap 0.02 mm), run forward to the outer wall plane Y−3.4. They provide broad roof load paths rather than small insert posts, with 2.57/2.63 mm lateral clearance to the flange. The ribs are forward of the connector anchors (nearest anchor Y5.03; panel rear Y1.93 gives 3.10 mm separation). Panel bottom remains **0.5 mm above the module PCB top**; neither wings nor ribs extend through the PCB. Load/print strength still needs testing.
1. **Connector flange/cable lock axes X44.47 / 69.47, spacing 25**, parallel to Y, Z26.84762024. Panel holes default to Ø3.6 clearance, adjustable with `external_fastener_clearance`. The source models Ø3 cylindrical bores/Ø3.4 mouth chamfers, **no threads**. This does not establish M3, 4-40, pitch, removable posts, or usable engagement. The shell slot is **18.078038 mm wide**, X47.930981…66.009019, **open through the panel bottom**, with top Z31.99762024 (0.6 above shell top). There is no lower crossbar to trap the connector. The upper rail is **3.00238 mm high**, continuous with the roof. Two **closed Ø3.6 through holes** retain screw posts on **X44.47 / 69.47, Z26.84762024, 25 mm spacing**, parallel to Y. The minimum slot-to-hole ligament is **1.660981 mm**. `external_fastener_clearance` is clearance only, **not a thread designation**. The module mounting holes remain a separate **24.13 mm** Z-axis system; anchor/prong holes are not new mounting points.
2. **Panel-to-lid M3 screws X38 / 76**, spacing 38, same height, into forward-facing insert hangers. These carry panel loads to the case and can be serviced externally.
3. **Module-to-carrier M3 screws**, Z axes, **24.13 spacing**, as above. Connector anchor/prong holes are not used as mounting screws.
**Required unresolved hardware selection:** confirm the actual connector posts can be removed/reseated, determine their real thread and required engagement, and fit suitable panel-mount replacement jackscrews (or a verified equivalent) that sandwich this panel against the flange **while preserving the cable's mating screw receptacles and nose reach**. Stock 4.8 mm posts occupy the panel volume; simply passing ordinary screws into their front bores does not clamp the panel to the flange and consumes the cable locks. **Do not do that. Do not assume an M3 screw fits.** `panel_t`, `panel_flange_gap` and clearance are adjustable for verified hardware; keeping the replacement posts too far forward can prevent full cable mating even if threads engage. The outer-wall service recess is **X33…81**, open down to the lid seam (**Z15.25**) so no lid crossbar sweeps over the shell. The former 1 mm side slits are closed by extending the integral panel/ribs to join the wall boundaries. A **base-only stepped upstand** fills the lower opening: shoulders span X33.25…80.75 and rise from Z15 to **Z20.82**; the **17.578038 mm-wide central tongue** rises to **Z21.69762024**, leaving **0.6 mm below the STEP shell minimum Z22.29762024**. The steps interfit without an undercut, retaining **0.25 mm** horizontal edge/tongue seams and the vertical shoulder-to-panel seam. The upstand is **2.9 mm deep**, Y−3.4…−0.5: it stays **0.5 mm forward of the PCB edge Y0** and 5.53 mm forward of the nearest anchor Y5.03, rather than filling through the PCB/underside. This closes unwanted openings, not the required manufacturing/cable clearances, and is not a sealed enclosure. Forward ribs narrow the unobstructed central cable approach to **X39…75 (36 mm)**. Shell nose stays **0.47 mm beyond the ordinary outer wall** and **3.4 mm beyond the panel front**. Full cable-hood, tool and mating clearance must be checked against that recess, not assumed from nose protrusion alone.
The detachable panel is intentional: an integral panel with closed shell hole could not be lowered vertically over the installed horizontal connector. Lower the lid first, then introduce the panel along the mating axis from outside and fasten it. `db_panel=false` removes lid hangers and assembled panel for a temporary access-only configuration — **not mechanically equivalent to external flange retention**. If the stock connector cannot accept suitable panel hardware, this retention concept must be revised, not declared validated. **Installation path, not a horizontal-slide claim:** remove both connector screw posts and unplug *all* cables. Hold the lid at its final XY registration and lower it straight down. The shell enters the bottom-open slot; the closed side holes sweep beside the shell, where the removed posts used to obstruct them. The panel remains forward of the flange and above the PCB for the entire descent. The USB shells end at Y77.705, inboard of the rear inner wall Y81 by **3.295 mm**, so a closed USB bay does not require a horizontal slide with cables removed. Downward button guides stay on their final XY axes, stop above switch tops, and case bosses approach their corresponding base bosses without a lateral move. The nominal continuous 60 mm sweep is tested in `validate.py --installation`, including a negative control that detects unremoved posts. This checks selected envelopes, not unmodeled components or manufacturing tolerances.
**Hardware gate — do not skip:** remove stock posts only if the real connector permits it without damaging captive nuts/anchors. Screw their male studs **from outside through the cover holes into the connector**, leaving post bodies outside and female cable-lock receptacles available. Actual thread/pitch, removability, stud length, usable female depth and clamp torque are **unmeasured**; STEP Ø3 bores/Ø3.4 chamfers prove neither M3 nor 4-40. Ordinary screws inserted into the original front bores do not implement this clamp.
For under-shoulder stud length `L`, washer thickness `w`, and flange gap `g`, available engagement is **`L − panel_t − w − g`**: default **`L − 2.4 − w`**. To retain the previous engagement, studs must provide **2.4 mm more reach** (plus any washer/gap). Reusing stock posts is conditional; verified longer-stud replacements or compatible extensions may be required, and must neither bottom nor interfere internally. Do not order a guessed thread. Default `panel_flange_gap=0` registers direct flange contact; any positive gap needs a measured, supported clamp stack, not tightening across air and bending the PCB.
Reseating a same-length **4.8 mm** post body puts its mouth at **Y−5.270**, **1.4 mm forward of the shell nose**, instead of 1 mm behind it. This is a significant **full-mating risk**, not validated by thread engagement. A verified shorter shoulder/body, recessed cable hardware or another compatible panel-mount post geometry may be necessary; retaining the original mouth plane would require a **2.4 mm body length** at the default panel face, whose female thread depth is not established. Keep full shell mating and functional cable locks as acceptance limits. If no compatible hardware exists, revise the cover/hardware design rather than force it. `panel_t`, `panel_flange_gap`, `external_fastener_clearance` and `panel_rib_w` expose the relevant choices; do not thin structural webs just to make stock threads reach.
`db_panel=false` omits the integral panel/ribs and base upstand for temporary access only, **not equivalent connector retention**. The obsolete `panel` export mode and `panel-default.stl` are removed; there is no duplicate printable plate.
## Display, buttons and antenna ## Display, buttons and antenna
@@ -104,13 +109,13 @@ The detachable panel is intentional: an integral panel with closed shell hole co
1. Measure insert family, actual assembled stack and connector hardware before committing to a full print. Inspect the rendered assembly against physical parts. Print cutout/insert/guide coupons or slicer-cut samples first. 1. Measure insert family, actual assembled stack and connector hardware before committing to a full print. Inspect the rendered assembly against physical parts. Print cutout/insert/guide coupons or slicer-cut samples first.
2. Print the base floor down. Indicative starting point only: 0.4 mm nozzle, 0.2 mm layers, 4 perimeters, 4–5 top/bottom layers, 25–40% infill with local solid regions around inserts. PETG is a reasonable prototype option; PLA is for fit checks away from heat. Determine material/thermal suitability yourself. No sealing, flame or temperature rating is established. 2. Print the base floor down. Indicative starting point only: 0.4 mm nozzle, 0.2 mm layers, 4 perimeters, 4–5 top/bottom layers, 25–40% infill with local solid regions around inserts. PETG is a reasonable prototype option; PLA is for fit checks away from heat. Determine material/thermal suitability yourself. No sealing, flame or temperature rating is established.
3. Lid can be printed roof-up with supports beneath the roof, guides, panel hangers and cable openings, avoiding supports in bores where practical. Alternatively print roof-down with `emboss=false`, attach separately printed text, or use a proper multi-material setup. **Integrated raised text prevents a flat roof-down first layer**; do not ignore this 0.6 mm offset. No support-free claim for the supplied embossed lid. 3. Lid can be printed roof-up with supports beneath the roof, guides, integral panel/ribs and cable openings, avoiding supports in bores where practical. Alternatively print roof-down with `emboss=false`, attach separately printed text, or use a proper multi-material setup. **Integrated raised text prevents a flat roof-down first layer**; do not ignore this 0.6 mm offset. No support-free claim for the supplied embossed lid.
4. Print panel flat in its supplied orientation. **No plastic spacers are printed**; the brass dummies are preview-only purchased hardware. Print plungers with brim and support under their low retaining flange, or choose a slicer orientation that yields smooth, strong shafts. Deburr guides without opening them so far that stems tilt/jam. Test all three independently. 4. There is **no separate DB9 plate** to print. **No plastic spacers are printed**; the brass dummies are preview-only purchased hardware. Print plungers with brim and support under their low retaining flange, or choose a slicer orientation that yields smooth, strong shafts. Deburr guides without opening them so far that stems tilt/jam. Test all three independently.
5. Heat-set inserts with a controlled tool to the selected supplier process. Panel hanger inserts install horizontally from the front. Remove strings/debris; inspect for splits, exposed insert metal and screw bottoming. 5. Heat-set inserts with a controlled tool to the selected supplier process. All inserts are in the base; none are used for the DB9 panel. Remove strings/debris; inspect for splits, exposed insert metal and screw bottoming.
6. Seat the carrier directly on all base support bosses and secure its four M3 mounting holes. Install the two M3 and four M2 **10 mm brass standoffs above the carrier**, with lower studs through the corresponding existing carrier holes into inserts. Seat the RS-232/OLED on them and fasten with short screws into the top females; seat the ESP32 on its headers. Check that brass body height matches the header's seated height, neither bending boards nor lifting connectors; check DB9 prongs and all underside solder joints against the 5 mm floor clearance. 6. Seat the carrier directly on all base support bosses and secure its four M3 mounting holes. Install the two M3 and four M2 **10 mm brass standoffs above the carrier**, with lower studs through the corresponding existing carrier holes into inserts. Seat the RS-232/OLED on them and fasten with short screws into the top females; seat the ESP32 on its headers. Check that brass body height matches the header's seated height, neither bending boards nor lifting connectors; check DB9 prongs and all underside solder joints against the 5 mm floor clearance.
7. Insert plungers from under the lid. Lower lid onto the base with DB9 panel **off**. Verify actuation/return before fastening the four case screws. Check screw heads fit the wells and screws engage without bottoming. 7. Disconnect all cables and remove the two verified-removable DB9 screw posts **before lowering the lid**. Insert plungers from under the lid and hold with removable tape. Align the cover in its final XY position and lower vertically: shell enters the bottom-open slot, panel wings clear the PCB, guides align over buttons. Do not slide horizontally, tilt around the USB end or force the shell. Verify free seating and button return; start the four case screws loosely.
8. After validating/removing the stock connector posts as required, slide the panel in from outside. Fit verified replacement connector jackscrews to the flange and the two independent M3 panel-to-lid screws. Do not use assembly screws to pull a misaligned flange/PCB into position. Test full USB/DB9 mating and cable locks; 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 lid, disconnect cables and remove panel/connector retention hardware first. The full electronics assembly should remain independently secured by 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.
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`.
@@ -121,18 +126,19 @@ Run from `hardware/case`; no network or added dependencies are needed. Shell com
```sh ```sh
QT_QPA_PLATFORM=offscreen openscad -o base-default.stl -D 'part="base"' enclosure.scad QT_QPA_PLATFORM=offscreen openscad -o base-default.stl -D 'part="base"' enclosure.scad
QT_QPA_PLATFORM=offscreen openscad -o lid-default.stl -D 'part="lid"' enclosure.scad QT_QPA_PLATFORM=offscreen openscad -o lid-default.stl -D 'part="lid"' enclosure.scad
QT_QPA_PLATFORM=offscreen openscad -o panel-default.stl -D 'part="panel"' enclosure.scad
QT_QPA_PLATFORM=offscreen openscad -o plungers-default.stl -D 'part="plungers"' enclosure.scad QT_QPA_PLATFORM=offscreen openscad -o plungers-default.stl -D 'part="plungers"' enclosure.scad
python3 validate.py python3 validate.py
/usr/bin/python3 validate.py --source /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
``` ```
`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, 1 panel, 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. 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.
Executed for this corrected design: all four default CGAL exports completed with `Simple: yes`; native-source registration, evaluated geometry fixtures and the mesh checks passed; exploded and connector-side PNGs rendered and were visually inspected. `spacers-default.stl` and the `spacers` mode/layout objects were removed. The base is 10 mm shorter; lid/panel/plunger print-local dimensions stay unchanged because their assembly datums all descend together. All four exports were regenerated. KiCad printed its existing enum-property assertions but completed the read-only checks. No slicer, physical print, screw-fit, cable-load, hardware/firmware, thermal or RF tests were performed. Additional geometry regression results, if any, are recorded in [checks.md](checks.md). 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.
## Evidence and remaining measurements ## Evidence and remaining measurements
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 79 KiB

File diff suppressed because it is too large Load Diff
+59 -26
View File
@@ -1,46 +1,79 @@
# Corrected enclosure checks — 2026-09-26 # Integral-cover connector mounting checks — 2026-09-26
This supersedes the initial under-carrier-brass stack. It is a CAD validation record, **not physical acceptance**. Tools already installed: OpenSCAD 2021.01, system Python 3 and KiCad `pcbnew`. No packages downloaded/installed. PCB hardware files were read only. This supersedes the separate DB9 panel/insert-hanger design. **CAD evidence, not physical acceptance.** Installed OpenSCAD 2021.01 and system Python/KiCad were used without downloads. PCB files were read only; changes are confined to the case and agent current-state record.
## Default design ## Geometry and outputs
Commands are in [README.md](README.md). All four printable part exports completed within bounded 180-second command timeouts, with OpenSCAD CGAL reporting `Simple: yes`: Shell remains **106 × 86.8 × 37.4 mm**; carrier underside/top **Z7.4/9**, brass bodies 10 mm above carrier, module underside **Z19**, roof underside **Z35**. RF overlay remains removed; PCB layout/copper/reserve unchanged.
| Export | Connected triangle components | Triangle count | - Integral panel: nominal X33…81 (0.02 mm union overlap into each wall), Y−0.47…1.93, Z21.07…35, **2.4 mm clamp thickness**. Direct roof connection, two **6 mm nominal** forward ribs (inner edges unchanged) outside the flange; no DB9 heat inserts or independent attachment screws.
- Closed **Ø3.6** post-clearance holes, **25 mm** spacing, axis Z26.84762024. No M3/4-40 inference.
- Bottom-open **18.078038 mm** shell slot; top Z31.99762024. Minimum hole-to-slot web **1.660981 mm**; top rail **3.00238 mm**. Panel bottom clears module PCB by 0.5 mm; anchors begin 3.10 mm behind the panel rear. Flange registration comes from extracted STEP faces, not the module mounting-hole pitch.
- Former 1 mm side slits closed by panel/rib extensions into wall boundaries. Base shoulders: X33.25…80.75, top Z20.82; central tongue width 17.578038, top Z21.69762024 (0.6 below STEP shell). Upstand Y−3.4…−0.5, depth 2.9: PCB-edge clearance 0.5, anchor separation 5.53. Shoulder/edge/tongue seams 0.25. No undercut, no change to 36 mm cable approach width.
- Front-wall service recess extends down to lid seam. USB opening stays unchanged; installation is vertical with all cables and both connector posts removed, not a horizontal slide under button guides.
- Deleted `panel-default.stl`, panel mode and layout object. `db_panel=false` is access-only. Base insert count is **10 M3 + 4 M2**.
Affected base/lid exports regenerated; unchanged plungers retained and revalidated under bounded 180-second command timeouts; CGAL reported `Simple: yes`:
| Export | Connected triangle components | Triangles |
|---|---:|---:| |---|---:|---:|
| `base-default.stl` | 1 | 8364 | | `base-default.stl` | 1 | 8404 |
| `lid-default.stl` | 1 | 13080 | | `lid-default.stl` | 1 | 12712 |
| `panel-default.stl` | 1 | 816 |
| `plungers-default.stl` | 3 | 1716 | | `plungers-default.stl` | 3 | 1716 |
The shell is **106 × 86.8 × 37.4 mm** (roof), 10 mm lower than the initial mistaken stack. Carrier underside/top are **Z7.4/9**, module underside **Z19**, roof underside **Z35**. The base top descends to Z15. The lid, panel, plungers, USB bay, DB9 hangers and display all descend with their parent datums; their relative fit remains unchanged. Print-local lid/panel/plunger nominal dimensions therefore remain unchanged, although all exports were regenerated. Gold hardware references replace printed spacers; no under-carrier brass, printable spacer mode, layout spacers or active spacer STL remains. The obsolete initial spacer STL had no unstaged user modifications before removal. Base and lid geometry changed only at the connector closure; plunger geometry is unchanged. The lid is one connected printable part, not overlapping separate exports. Three PNGs (`assembly-preview.png`, `db9-preview.png`, `lid-detail.png`) regenerated headlessly under 120-second timeouts and visually reviewed. The bare-lid view exposes the closed post holes, open-bottom shell slot, forward ribs and downward guides; the assembled view shows reseated post-body references outside the panel. Simplified background references are not production component CAD.
`/usr/bin/python3 validate.py --source` passed: ## Source, expressions and mesh regression
- Actual SCAD-evaluated default stack, including roof, split, button/guide, DB9 axis/panel bottom, USB bay and plunger length. Run from this directory:
- Independent `carrier_boss_h=7` fixture moves assembly Z by 2 without changing plunger length; `module_gap=12, lid_above_carrier=28` raises modules/roof/ports and lengthens plungers without moving the carrier or button contact.
- Reference 4 mm studs cross the 1.6 mm carrier and give 2.4 mm nominal engagement. Negative fixtures reject an M3 stud that cannot reach past the carrier, an M2 stud that bottoms in the pocket, and a 0.8 mm shell wall.
- Analytical 0.4 mm nozzle checks: shell/floor/roof at least three nozzle widths; insert entry and guide webs at least two. The limiting default panel-hanger entry web is 0.8 mm, M3 daughter entry web 1.1 mm, M2 daughter entry web 1.1 mm and guide radial web 1.75 mm. These selected web checks are not slicer coverage or proof that all text/features print reliably.
- Native PCB outline vertex set, 1.6 mm thickness, U1/U2/DS1 registration and orientation.
- Actual carrier/daughterboard NPTH centres **and diameters** (carrier 3.2, RS-232 3, OLED 2), no U1 NPTH mounting holes, and button body centres. The ESP32 remains header-supported; no mounting holes were invented.
- Saved measured U1 USB mouth graphic centres/widths and mouth Y; extracted STEP shell bounds and front-lock axes (25 mm, not 24.13 mm module holes).
- Every exported triangle has nonzero area, every edge has exactly two oppositely wound uses, positive total signed volume, expected connected component counts, bottom Z≈0 and default maximum bounds (including the shortened base) to detect stale exports. These are **edge-manifold checks**, not an exhaustive mesh-intersection/vertex-manifold proof.
KiCad emitted its known enum-property assertions but completed. It did not save or regenerate the board. ```sh
/usr/bin/python3 -B validate.py --source --installation --variants
```
PNG rendering succeeded headlessly with `QT_QPA_PLATFORM=offscreen`, within a bounded 120-second command timeout. Both `assembly-preview.png` and `db9-preview.png` were visually inspected: lower base, gold supports above carrier, simplified ESP32 header supports, separated lid/base, button tips, raised labels, USB bay and externally approachable DB9 panel. No red RF overlay remains. The actual projecting antenna rectangle is retained; the enclosure no longer reserves the PCB RF region. PCB copper/reserve/layout/schematic are unchanged and no Wi-Fi performance guarantee is implied. Hardware envelopes are incomplete; stock connector-post references still intentionally overlap the panel to expose the required replacement/reseating. Visual review does not certify clearance. PASS:
- Native saved PCB outline, thickness, placements/orientations, carrier/module NPTH centres **and diameters**, no invented ESP32 mounting holes, button centres and measured USB mouth graphics.
- Extracted STEP flange plane/lateral bounds, shell size and true 25 mm lock axes. Read-only KiCad emitted its known enum-property assertions but completed.
- Actual SCAD-evaluated stack/connector expressions; independent raised-carrier and taller-module fixtures; stud engagement; obsolete panel/spacer rejection.
- Negative assertions for insufficient stud reach, bottoming studs, thin shell/panel, negative flange gap, panel entering the PCB plane, oversized lock holes consuming slot ligaments, ribs entering flange lateral bounds, insufficient sliding seam/shell clearance and inverted closure steps.
- Every STL triangle nondegenerate, every edge used twice with opposite winding, expected component counts, positive total volume, print-bed Z and expected bounds. These are **edge-manifold checks**, not exhaustive vertex-manifold or self-intersection proof.
## Continuous installation sweep
`--installation` renders CGAL intersections of the **actual lid** with rectangular hardware envelopes swept continuously through **60 mm of relative vertical travel**, from below to the final position. A remote 1 mm³ sentinel makes an empty intersection exportable; a passing result has only that cube, one component and exactly 1 mm³ volume.
Included envelopes: maximum shell root/lip, flange, STEP anchor-bracket bounds, RS-232/ESP32/OLED boards, antenna, provisional OLED glass and USB shells, full button-body/actuator bounding boxes and conservative square base-boss bounds. Flange contact is inset 0.01 mm solely to avoid a coincident CGAL face; physical tolerances are not inferred. No cable envelopes are included because **cables must be disconnected**. Plungers move with the lid and contact the switches at final seating; their return/retention remains an independent physical gate.
- **PASS default**: posts removed, zero modeled swept interference.
- **PASS variant**: `carrier_boss_h=7`, `module_gap=12`, `lid_above_carrier=28`, `panel_t=3`.
- **PASS clearance variant**: `seam_clearance=0.4`, `shell_margin=0.8`, `end_clearance=1.5`.
- Each test also intersects the actual base/lid at seating, sweeps both upstand steps separately downward 60 mm against the lid, and sweeps electronics envelopes upward 60 mm against the new base material (equivalent to vertical electronics insertion). Only the sentinel remains. Existing conservative base-boss envelopes remain included.
- An initial full-base Minkowski sweep exceeded the outer 240-second command timeout after default passed. Replacing it with a whole-upstand hull introduced false diagonal interference; the final per-step exact sweep avoids that invented material and completes within the bounded runs.
- **PASS negative control**: restoring original post-body envelopes causes detected interference, demonstrating that leaving posts installed is not an allowed assembly path.
The first thicker-panel sweep exposed a thin residual inner-wall strip where the panel front lay forward of the original wall's inner face. The recess now clears at least the full wall thickness, independently of panel depth; the corrected fixture passes. The recess starts at the lid seam; the new forward-only base upstand occupies its lower portion.
These are selected conservative nominal envelopes, not a full STEP solid-motion/real-component qualification. Unknown socket bodies, component details, wires, tolerances and actual USB/glass heights remain outside the proof.
## Parameter CGAL smoke tests ## Parameter CGAL smoke tests
Temporary STL files were rendered and checked for one closed consistently wound edge-manifold component: Rendered to temporary files (180-second limit per render), then checked with `validate.mesh_check(path, 1)` for a single closed, consistently wound edge-manifold component:
```sh ```sh
QT_QPA_PLATFORM=offscreen openscad -o /tmp/case-correction-variant-lid.stl -D 'part="lid"' -D 'module_gap=12' -D 'lid_above_carrier=28' -D 'carrier_boss_h=7' -D 'emboss=false' -D 'db_panel=false' enclosure.scad QT_QPA_PLATFORM=offscreen openscad -o /tmp/case-integral-variant-lid.stl -D 'part="lid"' -D 'carrier_boss_h=7' -D 'module_gap=12' -D 'lid_above_carrier=28' -D 'panel_t=3' -D 'emboss=false' enclosure.scad
QT_QPA_PLATFORM=offscreen openscad -o /tmp/case-correction-variant-base.stl -D 'part="base"' -D 'daughter_supports=false' -D 'side_extra=14' -D 'm3_insert_d=4.4' -D 'panel_mount_boss_d=7' enclosure.scad QT_QPA_PLATFORM=offscreen openscad -o /tmp/case-integral-access-lid.stl -D 'part="lid"' -D 'db_panel=false' -D 'emboss=false' enclosure.scad
QT_QPA_PLATFORM=offscreen openscad -o /tmp/case-integral-variant-base.stl -D 'part="base"' -D 'daughter_supports=false' -D 'side_extra=14' -D 'm3_insert_d=4.4' enclosure.scad
``` ```
Both reported `Simple: yes`. The first is a 41.4 mm roof-height configuration, lid export 3848 triangles. The second is a 110 mm-wide base, 4972 triangles. These test parameter propagation; **neither alternative is a selected or physically validated stack**. Arbitrary parameter combinations are not guaranteed valid; review every regenerated assembly. The repeatable `--variants` suite now covers these three configurations plus a raised/thicker-panel **base**. All four PASS CGAL and one-component edge-manifold checks. These exercise a thicker integral panel with raised stack, access-only lid and wider base/alternate insert pocket. They are not selected or physically validated configurations. Arbitrary parameter combinations are not guaranteed valid. `git diff --check` also passed.
## Explicit limits ## Hardware gates — unresolved, not hidden by CAD success
No physical print, slicer analysis, insert installation, board assembly, connector/thread fit, plunger return/overload test, cable-load test, RF/thermal check or firmware test was performed. No full solid-interference solver was used. In particular, 5 mm carrier-to-floor clearance must be checked against actual solder/prongs, and conductive brass shoulders/insert faces against real lands/traces. Stud lengths, upper female depths, insert usable threads, USB Z/overmoulds, display height and DB9 hardware are still provisional as detailed in the README. The DB9 retention concept remains conditional on verified compatible panel jackscrews; stock posts are not claimed to fit the panel. 1. Verify posts really unscrew and determine **actual thread/pitch**, under-shoulder stud length, female depth, required engagement, clamp torque and compatibility with any captive connector hardware. No thread is established by the STEP bore.
2. Default panel consumes **2.4 mm additional reach**: engagement = stud length − 2.4 − washers (− any flange gap). Stock studs may be too short. Verified longer-stud posts/extensions may be needed; do not bottom them or use ordinary screws in the cable-lock mouths.
3. A same-length 4.8 mm body reseated outside the panel moves its mouth from Y−2.870 to **Y−5.270**, now **1.4 mm ahead of the shell nose**. Full mating may fail despite adequate thread engagement. A compatible shorter-body/panel-mount solution must be measured; a nominal 2.4 mm body would restore the old mouth plane but is **not a qualified available part or sufficient female-thread-depth claim**.
4. Preserve full cable mating/locking and external nose protrusion (0.47 mm beyond ordinary wall, 3.4 mm beyond panel); verify actual hood and wrench access inside the **36 mm** central space between ribs. Revise geometry/hardware if necessary rather than forcing a fit.
5. Existing gates remain: insert selection, underside solder/prong and conductive shoulder clearances, header/brass stack agreement, USB envelopes, glass height, button preload/return/overload, material/print orientation and cable-load strength.
No physical print, slicer analysis, insert installation, connector/thread measurement, full cable mating, load, RF, thermal, firmware or device test was performed. The stronger integral load path is designed and modeled, **not mechanically load-qualified**.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

+108 -29
View File
@@ -1,7 +1,7 @@
// Provisional enclosure, mm. See README.md before printing or ordering hardware. // Provisional enclosure, mm. See README.md before printing or ordering hardware.
// Saved carrier coordinates: X right, Y toward USB, Z above base floor. // Saved carrier coordinates: X right, Y toward USB, Z above base floor.
// Display in conventional top view via world(): X right, cable/DB9 end up. // Display in conventional top view via world(): X right, cable/DB9 end up.
part = "assembly"; // assembly, exploded, base, lid, panel, plunger, plungers, labels, layout, check part = "assembly"; // assembly, exploded, base, lid, plunger, plungers, labels, layout, check
show_references = true; // preview only: never included in STL show_references = true; // preview only: never included in STL
emboss = true; emboss = true;
$fn = 48; $fn = 48;
@@ -60,11 +60,10 @@ usb_reference_h = 3.5; // preview estimate ONLY
/* [DB9 panel: replacement jackscrews must be verified] */ /* [DB9 panel: replacement jackscrews must be verified] */
db_panel = true; // false leaves service opening; no case-to-flange retention db_panel = true; // false leaves service opening; no case-to-flange retention
panel_t = 2; panel_t = 2.4; // axial clamp thickness; consumes this much additional stud reach
panel_flange_gap = 0; // nominal flange contact; tune after physical measurement panel_flange_gap = 0; // nominal flange contact; tune after physical measurement
external_fastener_clearance = 3.6; // NOT a thread designation external_fastener_clearance = 3.6; // NOT a thread designation
panel_mount_x = [38,76]; // separate M3 panel-to-lid screws, NOT connector locks panel_rib_w = 5; // broad forward ribs outside the flange, not insert hangers
panel_mount_boss_d = 6.4;
shell_margin = 0.6; // per edge on STEP maximum shell envelope shell_margin = 0.6; // per edge on STEP maximum shell envelope
panel_bottom_above_module = 0.5; panel_bottom_above_module = 0.5;
@@ -111,6 +110,12 @@ flange_y = 1.930;
panel_rear_y = flange_y-panel_flange_gap; panel_rear_y = flange_y-panel_flange_gap;
panel_front_y = panel_rear_y-panel_t; panel_front_y = panel_rear_y-panel_t;
panel_low_z = rs_top+panel_bottom_above_module; panel_low_z = rs_top+panel_bottom_above_module;
recess_left = 33;
recess_right = 81;
shell_slot_w = 16.878038+2*shell_margin;
upstand_top = db_axis_z-9.1/2-shell_margin;
upstand_shoulder = panel_low_z-seam_clearance;
upstand_rear = min(-0.5,panel_front_y);
button_top = carrier_top+button_h; button_top = carrier_top+button_h;
guide_low_z = button_top+guide_bottom_above_button; guide_low_z = button_top+guide_bottom_above_button;
guide_high_z = lid_z; guide_high_z = lid_z;
@@ -120,7 +125,7 @@ plunger_length = top_z+plunger_above_lid-button_top;
assert(side_extra >= case_boss_d+2, "Widen side rails for case bosses"); assert(side_extra >= case_boss_d+2, "Widen side rails for case bosses");
assert(carrier_boss_h > max(m3_insert_depth,m2_insert_depth), "Leave material under inserts"); assert(carrier_boss_h > max(m3_insert_depth,m2_insert_depth), "Leave material under inserts");
assert(min(wall,floor_t,lid_t) >= 3*nozzle_d, "Shell needs at least three nozzle widths"); assert(min(wall,floor_t,lid_t) >= 3*nozzle_d, "Shell needs at least three nozzle widths");
assert((min(carrier_boss_d,case_boss_d,daughter_m3_boss_d,panel_mount_boss_d) assert((min(carrier_boss_d,case_boss_d,daughter_m3_boss_d)
-m3_insert_d-2*insert_lead)/2 >= 2*nozzle_d, "M3 entry web too thin"); -m3_insert_d-2*insert_lead)/2 >= 2*nozzle_d, "M3 entry web too thin");
assert((daughter_m2_boss_d-m2_insert_d-2*insert_lead)/2 >= 2*nozzle_d, "M2 entry web too thin"); assert((daughter_m2_boss_d-m2_insert_d-2*insert_lead)/2 >= 2*nozzle_d, "M2 entry web too thin");
assert((guide_od-plunger_d-plunger_clearance)/2 >= 2*nozzle_d, "Guide web too thin"); assert((guide_od-plunger_d-plunger_clearance)/2 >= 2*nozzle_d, "Guide web too thin");
@@ -132,7 +137,19 @@ assert(module_gap > max(brass_m3_female_depth,brass_m2_female_depth)+1);
assert(m3_insert_d+2 < min(carrier_boss_d,case_boss_d,daughter_m3_boss_d)); assert(m3_insert_d+2 < min(carrier_boss_d,case_boss_d,daughter_m3_boss_d));
assert(m2_insert_d+2 < daughter_m2_boss_d); assert(m2_insert_d+2 < daughter_m2_boss_d);
assert(lid_z > rs_top+12.55262024+0.8, "Lid intersects DB9 flange"); assert(lid_z > rs_top+12.55262024+0.8, "Lid intersects DB9 flange");
assert(panel_low_z >= rs_top+0.5, "Panel must stay above module PCB during lowering");
assert(panel_low_z < db_axis_z-external_fastener_clearance/2-0.8); assert(panel_low_z < db_axis_z-external_fastener_clearance/2-0.8);
assert(panel_t >= 3*nozzle_d && panel_rib_w >= 3*nozzle_d);
assert(seam_clearance >= 0.2 && seam_clearance <= 0.5, "Keep printable sliding seams");
assert(shell_margin >= 0.5, "Retain shell/cable clearance");
assert(upstand_rear-ymin >= 3*nozzle_d, "Upstand wall too thin");
assert(upstand_shoulder > split_z && upstand_top > upstand_shoulder,
"Stepped closure must rise above base and below shell");
assert(upstand_rear <= -0.5, "Do not enter projecting module PCB/underside");
assert(panel_flange_gap >= 0, "Panel cannot cross flange plane");
assert(34+panel_rib_w < 41.57 && 80-panel_rib_w > 72.37, "Ribs must clear flange sides");
assert(12.5-16.878038/2-shell_margin-external_fastener_clearance/2 >= 3*nozzle_d,
"Closed lock holes need a solid web to the open shell slot");
assert(panel_front_y > -2.870, "Panel would obscure stock cable-lock mouth plane"); assert(panel_front_y > -2.870, "Panel would obscure stock cable-lock mouth plane");
assert(guide_high_z > guide_low_z+4); assert(guide_high_z > guide_low_z+4);
assert(flange_bottom_z+plunger_flange_t < guide_low_z, "Plunger flange trapped below guide"); assert(flange_bottom_z+plunger_flange_t < guide_low_z, "Plunger flange trapped below guide");
@@ -145,7 +162,13 @@ echo("DB9 lock axis Z, split, lid underside",db_axis_z,split_z,lid_z);
echo("CHECK stack",[carrier_bottom,carrier_top,module_bottom,rs_top,lid_z,top_z, echo("CHECK stack",[carrier_bottom,carrier_top,module_bottom,rs_top,lid_z,top_z,
split_z,button_top,guide_low_z,db_axis_z,panel_low_z, split_z,button_top,guide_low_z,db_axis_z,panel_low_z,
esp_top-usb_below_module_top,esp_top+usb_above_module_top,plunger_length]); esp_top-usb_below_module_top,esp_top+usb_above_module_top,plunger_length]);
echo("CHECK closure",[recess_left,recess_right,upstand_rear,upstand_shoulder,
upstand_top,shell_slot_w-2*seam_clearance,seam_clearance]);
echo("CHECK engagement",[brass_m3_stud_l-carrier_t,brass_m2_stud_l-carrier_t]); echo("CHECK engagement",[brass_m3_stud_l-carrier_t,brass_m2_stud_l-carrier_t]);
echo("CHECK connector",[panel_front_y,panel_rear_y,panel_t,panel_low_z,
db_axis_z+9.1/2+shell_margin,16.878038+2*shell_margin,
lock_x[1]-lock_x[0],external_fastener_clearance,panel_rib_w,
12.5-16.878038/2-shell_margin-external_fastener_clearance/2]);
// Reflect carrier Y for a conventional top view; text compensates separately. // Reflect carrier Y for a conventional top view; text compensates separately.
module world() { translate([0,80,0]) rotate([0,0,0]) mirror([0,1,0]) children(); } module world() { translate([0,80,0]) rotate([0,0,0]) mirror([0,1,0]) children(); }
@@ -176,13 +199,25 @@ module daughter_bosses() {
module cable_cuts() { module cable_cuts() {
// Whole DB9 service recess clears PCB overhang, flange and wrench approach. // Whole DB9 service recess clears PCB overhang, flange and wrench approach.
// Panel lives inward of this wall and ABOVE the module PCB, never through it. // Panel lives inward of this wall and ABOVE the module PCB, never through it.
box(33,ymin-1,module_bottom-0.6,48,panel_front_y-ymin+1,lid_z-module_bottom+0.6); box(33,ymin-1,split_z+seam_clearance-eps,48,
max(panel_front_y,-end_clearance)-ymin+1+eps,
lid_z-split_z-seam_clearance+eps);
// Wide joined bay admits overmoulds to the inboard USB mouths. No thin web. // Wide joined bay admits overmoulds to the inboard USB mouths. No thin web.
box(usb_centres[0]-(8.87+usb_extra_width)/2,usb_mouth_y-1, box(usb_centres[0]-(8.87+usb_extra_width)/2,usb_mouth_y-1,
esp_top-usb_below_module_top, esp_top-usb_below_module_top,
usb_centres[1]-usb_centres[0]+8.87+usb_extra_width, usb_centres[1]-usb_centres[0]+8.87+usb_extra_width,
ymax-usb_mouth_y+2,usb_below_module_top+usb_above_module_top); ymax-usb_mouth_y+2,usb_below_module_top+usb_above_module_top);
} }
module connector_upstand(travel=0) {
// Forward of the PCB edge, including its unknown underside components.
// Shoulders meet the ribs/panel with a seam; tongue closes the open shell slot.
box(recess_left+seam_clearance,ymin,split_z-eps-travel,
recess_right-recess_left-2*seam_clearance,upstand_rear-ymin,
upstand_shoulder-split_z+eps+travel);
box(module_cx-shell_slot_w/2+seam_clearance,ymin,upstand_shoulder-eps-travel,
shell_slot_w-2*seam_clearance,upstand_rear-ymin,
upstand_top-upstand_shoulder+eps+travel);
}
module base() { module base() {
difference() { difference() {
union() { union() {
@@ -205,27 +240,28 @@ module base() {
} }
cable_cuts(); cable_cuts();
} }
if(db_panel) connector_upstand();
} }
module panel() { module connector_panel() {
difference() { difference() {
// Separate externally fitted part: a closed shell hole cannot lower over DB9. // Bottom-open slot clears the shell during straight vertical lid installation.
box(34,panel_front_y,panel_low_z,46,panel_t,lid_z-panel_low_z-seam_clearance); // Solid wings and top rail meet the roof; nothing crosses the PCB plane.
box(recess_left-eps,panel_front_y,panel_low_z,
recess_right-recess_left+2*eps,panel_t,lid_z-panel_low_z+eps);
box(module_cx-16.878038/2-shell_margin,panel_front_y-eps, box(module_cx-16.878038/2-shell_margin,panel_front_y-eps,
db_axis_z-9.1/2-shell_margin,16.878038+2*shell_margin, panel_low_z-eps,16.878038+2*shell_margin,
panel_t+2*eps,9.1+2*shell_margin); panel_t+2*eps,db_axis_z+9.1/2+shell_margin-panel_low_z+eps);
for(x=lock_x) ybore(x,panel_front_y-eps,db_axis_z, for(x=lock_x) ybore(x,panel_front_y-eps,db_axis_z,
external_fastener_clearance,panel_t+2*eps); external_fastener_clearance,panel_t+2*eps);
for(x=panel_mount_x) ybore(x,panel_front_y-eps,db_axis_z,m3_clearance,panel_t+2*eps);
} }
} }
module panel_hangers() { module connector_ribs() {
for(x=panel_mount_x) difference() { // Forward of flange/anchors, outside flange X bounds; full-height roof load path.
box(x-panel_mount_boss_d/2,panel_rear_y,db_axis_z-panel_mount_boss_d/2, box(recess_left-eps,ymin,panel_low_z,34+panel_rib_w-recess_left+eps,
panel_mount_boss_d,m3_insert_depth+2,lid_z-db_axis_z+panel_mount_boss_d/2+eps); panel_rear_y-ymin,lid_z-panel_low_z+eps);
ybore(x,panel_rear_y-eps,db_axis_z,m3_insert_d,m3_insert_depth+eps); box(80-panel_rib_w,ymin,panel_low_z,recess_right-(80-panel_rib_w)+eps,
translate([x,panel_rear_y-eps,db_axis_z]) rotate([-90,0,0]) panel_rear_y-ymin,lid_z-panel_low_z+eps);
cylinder(d1=m3_insert_d+2*insert_lead,d2=m3_insert_d,h=insert_lead+eps);
}
} }
module guide(p) { module guide(p) {
difference() { difference() {
@@ -246,7 +282,7 @@ module lid_body() {
side_extra/2,4,lid_z-split_z-seam_clearance+eps); side_extra/2,4,lid_z-split_z-seam_clearance+eps);
} }
for(p=button_centres) guide(p); for(p=button_centres) guide(p);
if(db_panel) panel_hangers();
} }
// Measured glass/display boundary plus margin, not a guessed active area. // Measured glass/display boundary plus margin, not a guessed active area.
box(44.97-display_margin,40.5-display_margin,lid_z-eps, box(44.97-display_margin,40.5-display_margin,lid_z-eps,
@@ -271,7 +307,11 @@ module labels() {
label_at(module_cx,35,"DISPLAY",2.4); label_at(module_cx,35,"DISPLAY",2.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() { lid_body(); if(emboss) labels(); } module lid() {
lid_body();
if(db_panel) { connector_panel(); connector_ribs(); }
if(emboss) labels();
}
module plunger() { module plunger() {
// Local bottom at zero, prints vertically; brim recommended for slender stem. // Local bottom at zero, prints vertically; brim recommended for slender stem.
cylinder(d=plunger_d,h=plunger_length); cylinder(d=plunger_d,h=plunger_length);
@@ -324,9 +364,9 @@ module references() {
box(41.57,1.930,rs_top+0.00262024,30.8,0.4,12.55); box(41.57,1.930,rs_top+0.00262024,30.8,0.4,12.55);
box(module_cx-16.878038/2,-3.870,db_axis_z-9.1/2,16.878038,5.8,9.1); box(module_cx-16.878038/2,-3.870,db_axis_z-9.1/2,16.878038,5.8,9.1);
} }
// Stock hex envelopes conflict with panel intentionally: must be replaced/reseated. // Reseated 4.8 mm post-body reference ONLY; stud/thread and cable reach unverified.
color([0.8,0.5,0.1,0.65]) for(x=lock_x) color([0.8,0.5,0.1,0.65]) for(x=lock_x)
ybore(x,-2.870,db_axis_z,5.311622,4.8); ybore(x,(db_panel?panel_front_y:flange_y)-4.8,db_axis_z,5.311622,4.8);
color("gray") for(p=button_centres) { color("gray") for(p=button_centres) {
box(p[0]-3,p[1]-3,carrier_top,6,6,3.4); box(p[0]-3,p[1]-3,carrier_top,6,6,3.4);
zcyl(p,carrier_top+3.4,3.5,button_h-3.4); zcyl(p,carrier_top+3.4,3.5,button_h-3.4);
@@ -337,16 +377,53 @@ module references() {
} }
} }
module assembled(explode=0) { module assembled(explode=0) {
color("slategray") base(); color("slategray") render(convexity=10) base();
color([0.8,0.85,0.9,0.7]) translate([0,0,explode]) lid(); color([0.8,0.85,0.9,0.7]) translate([0,0,explode]) render(convexity=10) lid();
if(db_panel) color("slategray") translate([0,-explode/2,0]) panel();
color("orange") for(p=button_centres) translate([p[0],p[1],button_top+explode]) plunger(); color("orange") for(p=button_centres) translate([p[0],p[1],button_top+explode]) plunger();
if($preview && show_references) %references(); if($preview && show_references) %references();
} }
// Conservative rectangular envelopes swept downward in the lid frame: equivalent
// to lowering the lid vertically 60 mm onto fixed electronics, posts REMOVED.
// Contact faces inset 0.01 only to avoid a coincident flange face in CGAL.
module installation_envelopes(stock_posts=false,travel=60,upward=false) {
module swept(x,y,z,w,d,h) { box(x,y,upward?z:z-travel,w,d,h+travel); }
swept(module_cx-16.878038/2,-3.870,db_axis_z-9.1/2,16.878038,5.8,9.1);
swept(41.57,flange_y+0.01,rs_top+0.00262024,30.8,0.39,12.55);
// Connector anchor brackets: STEP solids 13/14, not invented mounting holes.
for(x=[41.97,66.97]) swept(x,5.03,module_bottom-2.23,5,6.4,12.35262024);
swept(41.095,0,module_bottom,31.75,29.337,rs232_t);
swept(6.635,21.635,module_bottom,28.09,56.07,esp_t);
swept(12.045,14.745,module_bottom,17.63,6.89,esp_t);
swept(43.97,36,module_bottom,26,26,oled_t);
swept(44.97,40.5,oled_top,24.5,16.5,2);
for(x=usb_centres) swept(x-8.87/2,usb_mouth_y-usb_reference_depth,esp_top,
8.87,usb_reference_depth,usb_reference_h);
for(p=button_centres) swept(p[0]-3,p[1]-3,carrier_top,6,6,button_h);
for(p=case_holes) swept(p[0]-case_boss_d/2,p[1]-case_boss_d/2,
floor_t,case_boss_d,case_boss_d,split_z-floor_t);
if(stock_posts) for(x=lock_x)
swept(x-5.311622/2,-2.870,db_axis_z-2.3,5.311622,4.8,4.6);
}
module installation_check(stock_posts=false) {
// Sentinel permits exporting an otherwise empty intersection; expected volume 1.
cube(1);
translate([0,0,80]) {
intersection() { lid(); installation_envelopes(stock_posts); }
intersection() { lid(); base(); }
// Sweep each step separately: a whole-part hull invents diagonal material.
if(db_panel) intersection() { lid(); connector_upstand(60); }
// New material must also clear electronics while boards lower into the base.
if(db_panel) intersection() {
connector_upstand();
installation_envelopes(false,60,true);
}
}
}
// Each printable export is on Z=0, without hardware reference geometry. // Each printable export is on Z=0, without hardware reference geometry.
if(part=="base") world() base(); if(part=="base") world() base();
else if(part=="lid") translate([0,0,-split_z-seam_clearance]) world() lid(); else if(part=="lid") translate([0,0,-split_z-seam_clearance]) world() lid();
else if(part=="panel") rotate([90,0,0]) translate([-34,-panel_front_y,-panel_low_z]) panel();
else if(part=="plunger") plunger(); else if(part=="plunger") plunger();
else if(part=="plungers") plungers(); else if(part=="plungers") plungers();
@@ -356,8 +433,10 @@ else if(part=="layout") {
translate([xmax-xmin+12,0,-split_z-seam_clearance]) world() lid(); translate([xmax-xmin+12,0,-split_z-seam_clearance]) world() lid();
translate([0,-22,0]) plungers(); translate([0,-22,0]) plungers();
translate([60,-20,0]) rotate([90,0,0]) translate([-34,-panel_front_y,-panel_low_z]) panel();
} }
else if(part=="installation-check") installation_check();
else if(part=="installation-blocked") installation_check(true);
else if(part=="check") {} // datums/assertions only, used by validate.py else if(part=="check") {} // datums/assertions only, used by validate.py
else if(part=="assembly") world() assembled(); else if(part=="assembly") world() assembled();
else if(part=="exploded") world() assembled(28); else if(part=="exploded") world() assembled(28);
+5977 -8553
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because it is too large Load Diff
+42 -42
View File
@@ -3261,20 +3261,6 @@ solid OpenSCAD_Model
vertex 1.53073 -3.69552 0.5 vertex 1.53073 -3.69552 0.5
endloop endloop
endfacet endfacet
facet normal 0.65934 -0.751845 0
outer loop
vertex 2.43505 -3.17341 0.5
vertex 2.82843 -2.82843 1.7
vertex 2.43505 -3.17341 1.7
endloop
endfacet
facet normal 0.65934 -0.751845 0
outer loop
vertex 2.82843 -2.82843 1.7
vertex 2.43505 -3.17341 0.5
vertex 2.82843 -2.82843 0.5
endloop
endfacet
facet normal -0.896871 -0.442291 0 facet normal -0.896871 -0.442291 0
outer loop outer loop
vertex -3.4641 -2 0.5 vertex -3.4641 -2 0.5
@@ -3317,6 +3303,20 @@ solid OpenSCAD_Model
vertex -1.53073 -3.69552 0.5 vertex -1.53073 -3.69552 0.5
endloop endloop
endfacet endfacet
facet normal 0.65934 -0.751845 0
outer loop
vertex 2.43505 -3.17341 0.5
vertex 2.82843 -2.82843 1.7
vertex 2.43505 -3.17341 1.7
endloop
endfacet
facet normal 0.65934 -0.751845 0
outer loop
vertex 2.82843 -2.82843 1.7
vertex 2.43505 -3.17341 0.5
vertex 2.82843 -2.82843 0.5
endloop
endfacet
facet normal -0.831471 -0.555569 0 facet normal -0.831471 -0.555569 0
outer loop outer loop
vertex -3.17341 -2.43505 0.5 vertex -3.17341 -2.43505 0.5
@@ -7265,20 +7265,6 @@ solid OpenSCAD_Model
vertex 13.5307 -3.69552 0.5 vertex 13.5307 -3.69552 0.5
endloop endloop
endfacet endfacet
facet normal 0.659321 -0.751861 0
outer loop
vertex 14.435 -3.17341 0.5
vertex 14.8284 -2.82843 1.7
vertex 14.435 -3.17341 1.7
endloop
endfacet
facet normal 0.659321 -0.751861 0
outer loop
vertex 14.8284 -2.82843 1.7
vertex 14.435 -3.17341 0.5
vertex 14.8284 -2.82843 0.5
endloop
endfacet
facet normal -0.896871 -0.442291 0 facet normal -0.896871 -0.442291 0
outer loop outer loop
vertex 8.5359 -2 0.5 vertex 8.5359 -2 0.5
@@ -7321,6 +7307,20 @@ solid OpenSCAD_Model
vertex 10.4693 -3.69552 0.5 vertex 10.4693 -3.69552 0.5
endloop endloop
endfacet endfacet
facet normal 0.659321 -0.751861 0
outer loop
vertex 14.435 -3.17341 0.5
vertex 14.8284 -2.82843 1.7
vertex 14.435 -3.17341 1.7
endloop
endfacet
facet normal 0.659321 -0.751861 0
outer loop
vertex 14.8284 -2.82843 1.7
vertex 14.435 -3.17341 0.5
vertex 14.8284 -2.82843 0.5
endloop
endfacet
facet normal -0.831471 -0.555569 0 facet normal -0.831471 -0.555569 0
outer loop outer loop
vertex 8.82659 -2.43505 0.5 vertex 8.82659 -2.43505 0.5
@@ -11269,20 +11269,6 @@ solid OpenSCAD_Model
vertex 25.5307 -3.69552 0.5 vertex 25.5307 -3.69552 0.5
endloop endloop
endfacet endfacet
facet normal 0.659321 -0.751861 0
outer loop
vertex 26.435 -3.17341 0.5
vertex 26.8284 -2.82843 1.7
vertex 26.435 -3.17341 1.7
endloop
endfacet
facet normal 0.659321 -0.751861 0
outer loop
vertex 26.8284 -2.82843 1.7
vertex 26.435 -3.17341 0.5
vertex 26.8284 -2.82843 0.5
endloop
endfacet
facet normal -0.896887 -0.44226 0 facet normal -0.896887 -0.44226 0
outer loop outer loop
vertex 20.5359 -2 0.5 vertex 20.5359 -2 0.5
@@ -11325,6 +11311,20 @@ solid OpenSCAD_Model
vertex 22.4693 -3.69552 0.5 vertex 22.4693 -3.69552 0.5
endloop endloop
endfacet endfacet
facet normal 0.659321 -0.751861 0
outer loop
vertex 26.435 -3.17341 0.5
vertex 26.8284 -2.82843 1.7
vertex 26.435 -3.17341 1.7
endloop
endfacet
facet normal 0.659321 -0.751861 0
outer loop
vertex 26.8284 -2.82843 1.7
vertex 26.435 -3.17341 0.5
vertex 26.8284 -2.82843 0.5
endloop
endfacet
facet normal -0.831462 -0.555582 0 facet normal -0.831462 -0.555582 0
outer loop outer loop
vertex 20.8266 -2.43505 0.5 vertex 20.8266 -2.43505 0.5
+82 -7
View File
@@ -78,6 +78,11 @@ def source_check():
cad = json.loads((PCB / "reference/db9-cad/measurements.json").read_text()) cad = json.loads((PCB / "reference/db9-cad/measurements.json").read_text())
near([axis["location"][0]+43-1.905 for axis in cad["front_lock_axes"]], literal("lock_x")) near([axis["location"][0]+43-1.905 for axis in cad["front_lock_axes"]], literal("lock_x"))
near(cad["shell_outer_including_root_and_lip"]["dimensions"], [16.878038, 5.8, 9.1]) near(cad["shell_outer_including_root_and_lip"]["dimensions"], [16.878038, 5.8, 9.1])
shell = cad["shell_outer_including_root_and_lip"]["bounds"]
near([shell[2], 29.337-shell[4]], [3.29762024, -3.87])
flange = cad["front_flange_exposed_faces"]["bounds"]
near([29.337-flange[4]], [literal("flange_y")])
near([flange[0]+41.095, flange[3]+41.095], [41.57, 72.37])
for axis in cad["front_lock_axes"]: for axis in cad["front_lock_axes"]:
near([axis["location"][2]-1.57], [6.27762024]) near([axis["location"][2]-1.57], [6.27762024])
print("PASS native PCB outline, thickness, placements, NPTH centres, buttons, USB mouths, STEP lock/shell data") print("PASS native PCB outline, thickness, placements, NPTH centres, buttons, USB mouths, STEP lock/shell data")
@@ -127,6 +132,8 @@ def mesh_check(path, expected_components):
def geometry_check(): def geometry_check():
"""Evaluate actual SCAD expressions, including negative stud/web fixtures.""" """Evaluate actual SCAD expressions, including negative stud/web fixtures."""
assert 'panel_hangers' not in SCAD and 'panel_mount_' not in SCAD
assert 'part=="panel"' not in SCAD and not (ROOT/"panel-default.stl").exists()
assert 'module spacers(' not in SCAD and 'part=="spacers"' not in SCAD assert 'module spacers(' not in SCAD and 'part=="spacers"' not in SCAD
assert not (ROOT/"spacers-default.stl").exists(), "Obsolete printable spacers must not ship" assert not (ROOT/"spacers-default.stl").exists(), "Obsolete printable spacers must not ship"
assert 'brass_standoff_h' not in SCAD, "No brass height below carrier" assert 'brass_standoff_h' not in SCAD, "No brass height below carrier"
@@ -149,35 +156,103 @@ def geometry_check():
match = re.search(r'ECHO: "CHECK '+label+r'", (\[[^\n]+\])', output) match = re.search(r'ECHO: "CHECK '+label+r'", (\[[^\n]+\])', output)
assert match, output assert match, output
return ast.literal_eval(match.group(1)) return ast.literal_eval(match.group(1))
return datum("stack"), datum("engagement") closure = datum("closure")
stack, engagement = run() assert closure[2] <= -0.5 and closure[3] < closure[4]
if not defines:
near(closure, [33,81,-0.5,20.82,21.6976,17.578,0.25])
return datum("stack"), datum("engagement"), datum("connector")
stack, engagement, connector = run()
near(connector, [-0.47, 1.93, 2.4, 21.07, 31.9976, 18.078, 25, 3.6, 5, 1.66098])
# Independent expected defaults, not values copied from exported meshes. # Independent expected defaults, not values copied from exported meshes.
near(stack, [7.4, 9, 19, 20.57, 35, 37.4, 15, 13.3, 15.6, near(stack, [7.4, 9, 19, 20.57, 35, 37.4, 15, 13.3, 15.6,
26.8476, 21.07, 19.6, 26.6, 25.6]) 26.8476, 21.07, 19.6, 26.6, 25.6])
near(engagement, [2.4, 2.4]) near(engagement, [2.4, 2.4])
raised, _ = run(["carrier_boss_h=7"]) raised, _, _ = run(["carrier_boss_h=7"])
near(raised, [value+(0 if i == 13 else 2) for i, value in enumerate(stack)]) near(raised, [value+(0 if i == 13 else 2) for i, value in enumerate(stack)])
taller, _ = run(["module_gap=12", "lid_above_carrier=28"]) taller, _, _ = run(["module_gap=12", "lid_above_carrier=28"])
near(taller, [value+(2 if i in [2,3,4,5,9,10,11,12,13] else 0) near(taller, [value+(2 if i in [2,3,4,5,9,10,11,12,13] else 0)
for i, value in enumerate(stack)]) for i, value in enumerate(stack)])
run(["brass_m3_stud_l=1.6"], valid=False) run(["brass_m3_stud_l=1.6"], valid=False)
run(["brass_m2_stud_l=5"], valid=False) run(["brass_m2_stud_l=5"], valid=False)
run(["wall=0.8"], valid=False) run(["wall=0.8"], valid=False)
run(["panel_t=0.8"], valid=False)
run(["panel_flange_gap=-0.1"], valid=False)
run(["panel_bottom_above_module=-0.1"], valid=False)
run(["external_fastener_clearance=5"], valid=False)
run(["panel_rib_w=8"], valid=False)
run(["seam_clearance=0"], valid=False)
run(["shell_margin=0.1"], valid=False)
run(["panel_bottom_above_module=2"], valid=False)
print("PASS actual SCAD default/raised-carrier/taller-module datums, stud engagement and nozzle-web rejection fixtures") print("PASS actual SCAD default/raised-carrier/taller-module datums, stud engagement and nozzle-web rejection fixtures")
def installation_check():
"""CGAL intersection of actual lid with continuous vertical envelope sweeps."""
with tempfile.TemporaryDirectory(prefix="case-install-") as temp:
for mode, defines in [
("installation-check", []),
("installation-check", ["carrier_boss_h=7", "module_gap=12", "lid_above_carrier=28", "panel_t=3"]),
("installation-check", ["seam_clearance=0.4", "shell_margin=0.8", "end_clearance=1.5"]),
("installation-blocked", []),
]:
path = Path(temp)/"sweep.stl"
command = ["openscad", "-o", str(path), "-D", f'part="{mode}"', "-D", "emboss=false"]
for define in defines:
command.extend(["-D", define])
result = subprocess.run(command+[str(ROOT/"enclosure.scad")],
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
if mode == "installation-check":
report = mesh_check(path, 1)
near([report["volume_mm3"]], [1])
else:
# Negative control: unremoved post envelopes MUST hit the panel.
text = path.read_text()
zs = [float(z) for z in re.findall(r"\bvertex\s+[-+\deE.]+\s+[-+\deE.]+\s+([-+\deE.]+)", text)]
assert max(zs) > 80, "Fixture failed to detect installed posts"
print("PASS continuous vertical installation sweep", mode, defines)
def variant_check():
fixtures = [
('lid', ['carrier_boss_h=7', 'module_gap=12', 'lid_above_carrier=28', 'panel_t=3']),
('base', ['carrier_boss_h=7', 'module_gap=12', 'lid_above_carrier=28', 'panel_t=3']),
('lid', ['db_panel=false']),
('base', ['daughter_supports=false', 'side_extra=14', 'm3_insert_d=4.4']),
]
with tempfile.TemporaryDirectory(prefix='case-variant-') as temp:
for part, defines in fixtures:
path = Path(temp)/'variant.stl'
command = ['openscad', '-o', str(path), '-D', f'part="{part}"', '-D', 'emboss=false']
for define in defines:
command.extend(['-D', define])
result = subprocess.run(command+[str(ROOT/'enclosure.scad')],
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
mesh_check(path, 1)
print('PASS parameter CGAL mesh', part, defines)
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")
parser.add_argument("--installation", action="store_true", help="CGAL continuous vertical sweep and blocked-post control")
parser.add_argument('--variants', action='store_true', help='CGAL parameter mesh smoke tests')
args = parser.parse_args() args = parser.parse_args()
if args.variants:
variant_check()
if args.installation:
installation_check()
geometry_check() geometry_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 [
("base-default.stl", 1), ("lid-default.stl", 1), ("panel-default.stl", 1), ("base-default.stl", 1), ("lid-default.stl", 1),
("plungers-default.stl", 3)]] ("plungers-default.stl", 3)]]
for report, high in zip(reports, [[94,83.4,15], [94,83.4,22.75], for report, high in zip(reports, [[94,83.4,21.6976], [94,83.4,22.75],
[46,0,2], [28,4,25.6]]): [28,4,25.6]]):
near(report["bounds_mm"][1], high) # catch stale exports of the old stack near(report["bounds_mm"][1], high) # catch stale exports of the old stack
print(json.dumps(reports, indent=2)) print(json.dumps(reports, indent=2))
print("PASS edge-manifold topology, winding, component counts, positive total volumes and print-bed Z") print("PASS edge-manifold topology, winding, component counts, positive total volumes and print-bed Z")