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
+108 -29
View File
@@ -1,7 +1,7 @@
// Provisional enclosure, mm. See README.md before printing or ordering hardware.
// 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.
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
emboss = true;
$fn = 48;
@@ -60,11 +60,10 @@ usb_reference_h = 3.5; // preview estimate ONLY
/* [DB9 panel: replacement jackscrews must be verified] */
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
external_fastener_clearance = 3.6; // NOT a thread designation
panel_mount_x = [38,76]; // separate M3 panel-to-lid screws, NOT connector locks
panel_mount_boss_d = 6.4;
panel_rib_w = 5; // broad forward ribs outside the flange, not insert hangers
shell_margin = 0.6; // per edge on STEP maximum shell envelope
panel_bottom_above_module = 0.5;
@@ -111,6 +110,12 @@ flange_y = 1.930;
panel_rear_y = flange_y-panel_flange_gap;
panel_front_y = panel_rear_y-panel_t;
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;
guide_low_z = button_top+guide_bottom_above_button;
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(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(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");
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");
@@ -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(m2_insert_d+2 < daughter_m2_boss_d);
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_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(guide_high_z > guide_low_z+4);
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,
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]);
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 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.
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() {
// 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.
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.
box(usb_centres[0]-(8.87+usb_extra_width)/2,usb_mouth_y-1,
esp_top-usb_below_module_top,
usb_centres[1]-usb_centres[0]+8.87+usb_extra_width,
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() {
difference() {
union() {
@@ -205,27 +240,28 @@ module base() {
}
cable_cuts();
}
if(db_panel) connector_upstand();
}
module panel() {
module connector_panel() {
difference() {
// Separate externally fitted part: a closed shell hole cannot lower over DB9.
box(34,panel_front_y,panel_low_z,46,panel_t,lid_z-panel_low_z-seam_clearance);
// Bottom-open slot clears the shell during straight vertical lid installation.
// 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,
db_axis_z-9.1/2-shell_margin,16.878038+2*shell_margin,
panel_t+2*eps,9.1+2*shell_margin);
panel_low_z-eps,16.878038+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,
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() {
for(x=panel_mount_x) difference() {
box(x-panel_mount_boss_d/2,panel_rear_y,db_axis_z-panel_mount_boss_d/2,
panel_mount_boss_d,m3_insert_depth+2,lid_z-db_axis_z+panel_mount_boss_d/2+eps);
ybore(x,panel_rear_y-eps,db_axis_z,m3_insert_d,m3_insert_depth+eps);
translate([x,panel_rear_y-eps,db_axis_z]) rotate([-90,0,0])
cylinder(d1=m3_insert_d+2*insert_lead,d2=m3_insert_d,h=insert_lead+eps);
}
module connector_ribs() {
// Forward of flange/anchors, outside flange X bounds; full-height roof load path.
box(recess_left-eps,ymin,panel_low_z,34+panel_rib_w-recess_left+eps,
panel_rear_y-ymin,lid_z-panel_low_z+eps);
box(80-panel_rib_w,ymin,panel_low_z,recess_right-(80-panel_rib_w)+eps,
panel_rear_y-ymin,lid_z-panel_low_z+eps);
}
module guide(p) {
difference() {
@@ -246,7 +282,7 @@ module lid_body() {
side_extra/2,4,lid_z-split_z-seam_clearance+eps);
}
for(p=button_centres) guide(p);
if(db_panel) panel_hangers();
}
// Measured glass/display boundary plus margin, not a guessed active area.
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);
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() {
// Local bottom at zero, prints vertically; brim recommended for slender stem.
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(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)
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) {
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);
@@ -337,16 +377,53 @@ module references() {
}
}
module assembled(explode=0) {
color("slategray") base();
color([0.8,0.85,0.9,0.7]) translate([0,0,explode]) lid();
if(db_panel) color("slategray") translate([0,-explode/2,0]) panel();
color("slategray") render(convexity=10) base();
color([0.8,0.85,0.9,0.7]) translate([0,0,explode]) render(convexity=10) lid();
color("orange") for(p=button_centres) translate([p[0],p[1],button_top+explode]) plunger();
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.
if(part=="base") world() base();
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=="plungers") plungers();
@@ -356,8 +433,10 @@ else if(part=="layout") {
translate([xmax-xmin+12,0,-split_z-seam_clearance]) world() lid();
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=="assembly") world() assembled();
else if(part=="exploded") world() assembled(28);