tweak the fit of the parts
This commit is contained in:
+366
-120
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
/* [Output] */
|
||||
part = "assembly"; // [assembly,body,lid,buttons,insert_coupon,switch_coupon,fit_coupon]
|
||||
part = "assembly"; // [assembly,body,lid,lid_base,lid_accent,buttons,insert_coupon,switch_coupon,fit_coupon]
|
||||
exploded_view = 8; // [0:1:20]
|
||||
show_references = true;
|
||||
|
||||
@@ -47,24 +47,24 @@ battery_length = 69;
|
||||
battery_diameter = 18;
|
||||
battery_radial_clearance = 0.6;
|
||||
battery_end_clearance = 1;
|
||||
switch_hole_diameter = 12;
|
||||
switch_hole_compensation = 0.3;
|
||||
switch_cap_diameter = 8;
|
||||
switch_flange_diameter = 16.2;
|
||||
switch_body_length = 24;
|
||||
switch_body_diameter = 12;
|
||||
switch_wire_allowance = 3;
|
||||
battery_partition_thickness = 1.8;
|
||||
rocker_cutout_size = [12.9, 19.4];
|
||||
rocker_corner_radius = 0.8;
|
||||
rocker_body_size = [15, 21, 21];
|
||||
rocker_artwork_gap = 2;
|
||||
|
||||
/* [Tripod mount] */
|
||||
tripod_insert_enabled = false;
|
||||
tripod_insert_length = 6.4;
|
||||
tripod_insert_hole_diameter = 8.8;
|
||||
tripod_insert_length = 5;
|
||||
tripod_insert_hole_diameter = 7.2;
|
||||
tripod_insert_outer_diameter = 8;
|
||||
tripod_thread_clearance_diameter = 6.8;
|
||||
tripod_boss_diameter = 14;
|
||||
|
||||
/* [M2.5 inserts] */
|
||||
m25_insert_length = 4;
|
||||
m25_insert_hole_diameter = 3.7;
|
||||
m25_insert_hole_diameter = 3.1;
|
||||
m25_insert_outer_diameter = 3.5;
|
||||
m25_screw_clearance = 2.8;
|
||||
m25_boss_diameter = 7;
|
||||
insert_entry_chamfer = 0.6;
|
||||
@@ -78,15 +78,32 @@ corner_radius = 4;
|
||||
board_bottom_clearance = 5.4;
|
||||
board_side_clearance = 7;
|
||||
board_rear_clearance = 4;
|
||||
usb_board_wall_gap = 1.2;
|
||||
usb_board_wall_gap = 2;
|
||||
lid_lip_depth = 2.4;
|
||||
lid_lip_thickness = 1.2;
|
||||
fit_clearance = 0.35;
|
||||
cutout_min_web = 1.2;
|
||||
hole_compensation = 0.2;
|
||||
lid_screw_head_diameter = 5.2;
|
||||
lid_screw_head_height = 2.5;
|
||||
|
||||
/* [Two-color lid artwork] */
|
||||
lid_label = "";
|
||||
lid_logo_enabled = false;
|
||||
lid_art_orientation = "usb_left"; // [usb_left,usb_front,usb_right,usb_rear]
|
||||
lid_inlay_depth = 0.6; // [0.4:0.2:1]
|
||||
lid_inlay_fit_clearance = 0.08;
|
||||
lid_label_size = 5;
|
||||
lid_label_logo_gap = 2;
|
||||
lid_art_margin = 8.5;
|
||||
logo_file = "assets/NME-fixed.svg";
|
||||
|
||||
/* [USB-C opening] */
|
||||
usb_opening_enabled = true;
|
||||
usb_guide_enabled = true;
|
||||
usb_connector_overhang = 2;
|
||||
usb_guide_wall_thickness = 1.2;
|
||||
usb_guide_end_gap = 1;
|
||||
usb_opening_width = 12.5;
|
||||
usb_opening_height = 7;
|
||||
usb_opening_radius = 1.5;
|
||||
@@ -100,6 +117,8 @@ extra_cutouts = [];
|
||||
|
||||
/* [Calibration] */
|
||||
coupon_steps = [-0.3, -0.15, 0, 0.15, 0.3];
|
||||
m25_coupon_steps = [-0.15, -0.075, 0, 0.075, 0.15];
|
||||
tripod_coupon_steps = [-0.3, -0.15, 0, 0.15, 0.3];
|
||||
|
||||
$fn = $preview ? 36 : 72;
|
||||
eps = 0.02;
|
||||
@@ -111,6 +130,9 @@ board_thickness = 1.6;
|
||||
mount_x = [2.54, 48.26];
|
||||
mount_y = [2.54, 20.32];
|
||||
usb_center_y = 11.43;
|
||||
jst_center_xy = [10.9855, 19.304];
|
||||
jst_body_size = [8, 7.5, 4.5];
|
||||
jst_relief_clearance = 0.5;
|
||||
|
||||
// Official 128x64 OLED FeatherWing geometry in Feather-board coordinates.
|
||||
oled_visible_min = [11.42, 3.78];
|
||||
@@ -127,11 +149,34 @@ effective_board_clearance = max(
|
||||
board_bottom_clearance,
|
||||
m25_insert_length + insert_melt_relief + 0.6
|
||||
);
|
||||
lid_boss_r = m25_boss_diameter / 2;
|
||||
lid_boss_wall_overlap = 0.6;
|
||||
front_lid_boss_center = wall_thickness + lid_boss_r
|
||||
- lid_boss_wall_overlap;
|
||||
|
||||
// Combined battery/tripod builds need room around the centered insert entry.
|
||||
combined_tripod_board_gutter = battery_enabled && tripod_insert_enabled ? 2.4 : 0;
|
||||
combined_tripod_x_shift = battery_enabled && tripod_insert_enabled ? 5 : 0;
|
||||
|
||||
board_origin_x = wall_thickness + usb_board_wall_gap;
|
||||
board_origin_y = wall_thickness + board_side_clearance;
|
||||
board_origin_y = wall_thickness + board_side_clearance
|
||||
+ combined_tripod_board_gutter;
|
||||
board_bottom_z = effective_floor + effective_board_clearance;
|
||||
board_top_z = board_bottom_z + board_thickness;
|
||||
usb_center_u = board_origin_y + usb_center_y;
|
||||
usb_center_z = board_top_z + 1.6 + usb_vertical_offset;
|
||||
usb_inner_width = usb_opening_width + hole_compensation;
|
||||
usb_inner_height = usb_opening_height + hole_compensation;
|
||||
usb_guide_length = max(
|
||||
0,
|
||||
usb_board_wall_gap - usb_connector_overhang - usb_guide_end_gap
|
||||
);
|
||||
usb_guide_active = usb_opening_enabled && usb_guide_enabled
|
||||
&& usb_guide_length > 0;
|
||||
usb_reserved_width = usb_inner_width
|
||||
+ (usb_guide_active ? 2 * usb_guide_wall_thickness : 0);
|
||||
usb_reserved_height = usb_inner_height
|
||||
+ (usb_guide_active ? 2 * usb_guide_wall_thickness : 0);
|
||||
|
||||
computed_top_pcb_z = board_top_z + wing_count * wing_pitch;
|
||||
top_pcb_z = stack_height_override > 0
|
||||
@@ -147,19 +192,16 @@ profile_top_gap =
|
||||
generic_top_gap;
|
||||
|
||||
partition_y = board_origin_y + feather_width + 1;
|
||||
battery_center_y = partition_y + wall_thickness + battery_radial_clearance
|
||||
+ battery_diameter / 2;
|
||||
battery_start_x = board_origin_x + battery_end_clearance;
|
||||
battery_center_y = partition_y + battery_partition_thickness
|
||||
+ battery_radial_clearance + battery_diameter / 2;
|
||||
battery_start_x = front_lid_boss_center + lid_boss_r
|
||||
+ battery_end_clearance;
|
||||
battery_end_x = battery_start_x + battery_length;
|
||||
switch_center_x = battery_end_x + max(
|
||||
switch_body_diameter / 2 + 0.8,
|
||||
switch_flange_diameter / 2 + 0.5
|
||||
);
|
||||
|
||||
case_length_plain = board_origin_x + feather_length
|
||||
+ board_rear_clearance + wall_thickness;
|
||||
case_length_battery = switch_center_x + switch_flange_diameter / 2
|
||||
+ 1.2 + wall_thickness;
|
||||
case_length_battery = battery_end_x + battery_end_clearance
|
||||
+ wall_thickness + m25_boss_diameter - lid_boss_wall_overlap;
|
||||
case_length = battery_enabled ? max(case_length_plain, case_length_battery)
|
||||
: case_length_plain;
|
||||
|
||||
@@ -170,23 +212,70 @@ case_width_battery = battery_center_y + battery_diameter / 2
|
||||
case_width = battery_enabled ? max(case_width_plain, case_width_battery)
|
||||
: case_width_plain;
|
||||
|
||||
rocker_center_x = case_length - wall_thickness - fit_clearance
|
||||
- lid_lip_thickness - fit_clearance - rocker_body_size[0] / 2;
|
||||
rocker_center_y = front_lid_boss_center + lid_boss_r + fit_clearance
|
||||
+ rocker_body_size[1] / 2;
|
||||
|
||||
lid_artwork_enabled = lid_label != "" || lid_logo_enabled;
|
||||
lid_art_rotation =
|
||||
lid_art_orientation == "usb_left" ? 0 :
|
||||
lid_art_orientation == "usb_front" ? 90 :
|
||||
lid_art_orientation == "usb_right" ? 180 : 270;
|
||||
lid_art_safe_max_x = battery_enabled
|
||||
? rocker_center_x - rocker_cutout_size[0] / 2 - rocker_artwork_gap
|
||||
: case_length - lid_art_margin;
|
||||
lid_art_physical_size = [
|
||||
lid_art_safe_max_x - lid_art_margin,
|
||||
case_width - 2 * lid_art_margin
|
||||
];
|
||||
lid_art_center = [
|
||||
lid_art_margin + lid_art_physical_size[0] / 2,
|
||||
case_width / 2
|
||||
];
|
||||
lid_art_canvas_size = lid_art_rotation == 0 || lid_art_rotation == 180
|
||||
? lid_art_physical_size
|
||||
: [lid_art_physical_size[1], lid_art_physical_size[0]];
|
||||
lid_art_available_size = lid_art_canvas_size;
|
||||
lid_label_height = lid_label == "" ? 0 : lid_label_size;
|
||||
lid_art_gap = lid_label != "" && lid_logo_enabled ? lid_label_logo_gap : 0;
|
||||
logo_native_size = [30, 25.75];
|
||||
logo_max_height = lid_art_available_size[1] - lid_label_height - lid_art_gap;
|
||||
logo_scale = lid_logo_enabled ? min(
|
||||
lid_art_available_size[0] / logo_native_size[0],
|
||||
logo_max_height / logo_native_size[1]
|
||||
) : 0;
|
||||
logo_size = [logo_native_size[0] * logo_scale,
|
||||
logo_native_size[1] * logo_scale];
|
||||
lid_art_total_height = logo_size[1] + lid_art_gap + lid_label_height;
|
||||
lid_label_center_y = -lid_art_total_height / 2 + lid_label_height / 2;
|
||||
lid_logo_center_y = -lid_art_total_height / 2 + lid_label_height
|
||||
+ lid_art_gap + logo_size[1] / 2;
|
||||
|
||||
tripod_center_x = case_length / 2 - combined_tripod_x_shift;
|
||||
tripod_center_y = case_width / 2;
|
||||
tripod_entry_radius = tripod_insert_outer_diameter / 2;
|
||||
tripod_standoff_clearance = min([
|
||||
for (mx = mount_x, my = mount_y)
|
||||
sqrt(
|
||||
pow(board_origin_x + mx - tripod_center_x, 2)
|
||||
+ pow(board_origin_y + my - tripod_center_y, 2)
|
||||
) - tripod_entry_radius - m25_boss_diameter / 2
|
||||
]);
|
||||
tripod_partition_clearance = partition_y
|
||||
- (tripod_center_y + tripod_entry_radius);
|
||||
|
||||
battery_center_z = effective_floor + battery_radial_clearance
|
||||
+ battery_diameter / 2;
|
||||
switch_center_z = max(
|
||||
effective_floor + switch_flange_diameter / 2 + 1,
|
||||
battery_center_z
|
||||
);
|
||||
electronics_ceiling_z = top_pcb_z + profile_component_height + profile_top_gap;
|
||||
battery_ceiling_z = battery_enabled
|
||||
? max(
|
||||
battery_center_z + battery_diameter / 2 + battery_radial_clearance,
|
||||
switch_center_z + switch_flange_diameter / 2 + 1
|
||||
)
|
||||
? battery_center_z + battery_diameter / 2 + battery_radial_clearance
|
||||
: 0;
|
||||
body_height = max(electronics_ceiling_z, battery_ceiling_z);
|
||||
rocker_ceiling_z = battery_enabled
|
||||
? effective_floor + rocker_body_size[2] + fit_clearance
|
||||
: 0;
|
||||
body_height = max(electronics_ceiling_z, battery_ceiling_z, rocker_ceiling_z);
|
||||
|
||||
lid_boss_r = m25_boss_diameter / 2;
|
||||
lid_boss_wall_overlap = 0.6;
|
||||
lid_boss_xy = [
|
||||
[
|
||||
wall_thickness + lid_boss_r - lid_boss_wall_overlap,
|
||||
@@ -210,14 +299,86 @@ assert(wing_count >= 1, "wing_count must be at least one");
|
||||
assert(top_profile == "generic" || top_profile == "gps"
|
||||
|| top_profile == "oled128x64", "Unsupported top_profile");
|
||||
assert(wall_thickness >= 1.2, "wall_thickness is too small for 0.6 mm FDM");
|
||||
assert(battery_partition_thickness >= 1.2,
|
||||
"battery_partition_thickness is too small for 0.6 mm FDM");
|
||||
assert(lid_lip_thickness >= 1.2, "lid lip needs at least two 0.6 mm lines");
|
||||
assert(lid_art_orientation == "usb_left"
|
||||
|| lid_art_orientation == "usb_front"
|
||||
|| lid_art_orientation == "usb_right"
|
||||
|| lid_art_orientation == "usb_rear",
|
||||
"Unsupported lid_art_orientation");
|
||||
assert(!lid_artwork_enabled || top_profile != "oled128x64",
|
||||
"Lid artwork is not supported with the OLED profile");
|
||||
assert(!lid_artwork_enabled || lid_inlay_depth < lid_thickness,
|
||||
"Lid inlay must be shallower than the lid");
|
||||
assert(!lid_artwork_enabled
|
||||
|| (lid_art_available_size[0] > 0 && lid_art_available_size[1] > 0),
|
||||
"Lid artwork margin leaves no printable area");
|
||||
assert(!lid_logo_enabled || logo_scale > 0,
|
||||
"Lid logo has no printable area");
|
||||
assert(m25_boss_diameter > m25_insert_hole_diameter + 2,
|
||||
"M2.5 boss does not have enough radial material");
|
||||
assert(m25_insert_outer_diameter >= m25_insert_hole_diameter,
|
||||
"M2.5 insert entry must not be smaller than its pilot");
|
||||
assert(!tripod_insert_enabled
|
||||
|| tripod_boss_diameter > tripod_insert_hole_diameter + 3,
|
||||
"Tripod insert boss does not have enough radial material");
|
||||
assert(usb_opening_enabled || !usb_guide_enabled,
|
||||
"USB guide requires the USB opening");
|
||||
assert(!tripod_insert_enabled
|
||||
|| tripod_insert_outer_diameter >= tripod_insert_hole_diameter,
|
||||
"Tripod insert entry must not be smaller than its pilot");
|
||||
assert(!tripod_insert_enabled || tripod_standoff_clearance >= 1.2,
|
||||
"Tripod insert entry is too close to a Feather standoff");
|
||||
assert(!battery_enabled || !tripod_insert_enabled
|
||||
|| tripod_partition_clearance >= 1,
|
||||
"Tripod insert entry is too close to the battery partition");
|
||||
assert(!battery_enabled
|
||||
|| rocker_center_x - rocker_body_size[0] / 2
|
||||
>= board_origin_x + feather_length + fit_clearance,
|
||||
"Rocker body overlaps the Feather PCB");
|
||||
assert(!battery_enabled
|
||||
|| rocker_center_y + rocker_body_size[1] / 2
|
||||
<= partition_y - fit_clearance,
|
||||
"Rocker body overlaps the battery partition");
|
||||
assert(!battery_enabled
|
||||
|| body_height - rocker_body_size[2] >= effective_floor,
|
||||
"Rocker body intersects the case floor");
|
||||
|
||||
function clamp(v, lo, hi) = min(max(v, lo), hi);
|
||||
function cutout_height(entry) = len(entry) > 5 ? entry[5] : entry[4];
|
||||
function cutouts_separated(first, second, web) =
|
||||
abs(first[2] - second[2])
|
||||
>= (first[4] + second[4]) / 2 + web
|
||||
|| abs(first[3] - second[3])
|
||||
>= (cutout_height(first) + cutout_height(second)) / 2 + web;
|
||||
function cutout_clear_of_usb(entry, web) =
|
||||
abs(entry[2] - usb_center_u)
|
||||
>= (entry[4] + usb_reserved_width) / 2 + web
|
||||
|| abs(entry[3] - usb_center_z)
|
||||
>= (cutout_height(entry) + usb_reserved_height) / 2 + web;
|
||||
|
||||
module validate_cutout_spacing() {
|
||||
if (len(extra_cutouts) > 1)
|
||||
for (first = [0 : len(extra_cutouts) - 2],
|
||||
second = [first + 1 : len(extra_cutouts) - 1])
|
||||
if (extra_cutouts[first][0] == extra_cutouts[second][0])
|
||||
assert(
|
||||
cutouts_separated(
|
||||
extra_cutouts[first],
|
||||
extra_cutouts[second],
|
||||
cutout_min_web
|
||||
),
|
||||
"Custom cutouts are too close on the same wall"
|
||||
);
|
||||
if (usb_opening_enabled)
|
||||
for (entry = extra_cutouts)
|
||||
if (entry[0] == "front")
|
||||
assert(
|
||||
cutout_clear_of_usb(entry, cutout_min_web),
|
||||
"Front cutout is too close to the USB opening or guide"
|
||||
);
|
||||
}
|
||||
|
||||
module rounded_rect_2d(size, radius) {
|
||||
r = clamp(radius, 0.01, min(size[0], size[1]) / 2 - 0.01);
|
||||
@@ -242,8 +403,8 @@ module rounded_box(size, radius) {
|
||||
rounded_rect_2d([size[0], size[1]], radius);
|
||||
}
|
||||
|
||||
module insert_pocket(diameter, depth, entry_z, downward = true) {
|
||||
bore_d = diameter + hole_compensation;
|
||||
module insert_pocket(diameter, entry_diameter, depth, entry_z, downward = true) {
|
||||
bore_d = diameter;
|
||||
if (downward) {
|
||||
translate([0, 0, entry_z - depth])
|
||||
cylinder(h = depth + eps, d = bore_d);
|
||||
@@ -251,7 +412,7 @@ module insert_pocket(diameter, depth, entry_z, downward = true) {
|
||||
cylinder(
|
||||
h = insert_entry_chamfer + eps,
|
||||
d1 = bore_d,
|
||||
d2 = bore_d + 2 * insert_entry_chamfer
|
||||
d2 = entry_diameter
|
||||
);
|
||||
} else {
|
||||
translate([0, 0, entry_z - eps])
|
||||
@@ -259,7 +420,7 @@ module insert_pocket(diameter, depth, entry_z, downward = true) {
|
||||
translate([0, 0, entry_z - eps])
|
||||
cylinder(
|
||||
h = insert_entry_chamfer + eps,
|
||||
d1 = bore_d + 2 * insert_entry_chamfer,
|
||||
d1 = entry_diameter,
|
||||
d2 = bore_d
|
||||
);
|
||||
}
|
||||
@@ -301,6 +462,40 @@ module face_circle_cutout(face, u, z, diameter) {
|
||||
rotate([-90, 0, 0]) cylinder(h = depth, d = diameter);
|
||||
}
|
||||
|
||||
module x_rounded_prism(x, length, u, z, width, height, radius) {
|
||||
r = clamp(radius, 0.01, min(width, height) / 2 - 0.01);
|
||||
hull()
|
||||
for (yy = [u - width / 2 + r, u + width / 2 - r],
|
||||
zz = [z - height / 2 + r, z + height / 2 - r])
|
||||
translate([x, yy, zz])
|
||||
rotate([0, 90, 0]) cylinder(h = length, r = r);
|
||||
}
|
||||
|
||||
module usb_guide_shell() {
|
||||
outer_width = usb_inner_width + 2 * usb_guide_wall_thickness;
|
||||
outer_height = usb_inner_height + 2 * usb_guide_wall_thickness;
|
||||
difference() {
|
||||
x_rounded_prism(
|
||||
wall_thickness - eps,
|
||||
usb_guide_length + 2 * eps,
|
||||
usb_center_u,
|
||||
usb_center_z,
|
||||
outer_width,
|
||||
outer_height,
|
||||
usb_opening_radius + usb_guide_wall_thickness
|
||||
);
|
||||
x_rounded_prism(
|
||||
wall_thickness - 2 * eps,
|
||||
usb_guide_length + 4 * eps,
|
||||
usb_center_u,
|
||||
usb_center_z,
|
||||
usb_inner_width,
|
||||
usb_inner_height,
|
||||
usb_opening_radius
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module custom_wall_cutout(entry) {
|
||||
face = entry[0];
|
||||
shape = entry[1];
|
||||
@@ -368,20 +563,27 @@ module battery_cradle() {
|
||||
battery_end_x - 4
|
||||
];
|
||||
|
||||
// Board/battery partition, with a lead-routing notch near the JST end.
|
||||
// One open-topped front passage provides JST plug access and enough room
|
||||
// to route the battery lead beneath the Feather PCB beside the lid boss.
|
||||
difference() {
|
||||
translate([wall_thickness, partition_y, effective_floor - eps])
|
||||
cube([
|
||||
battery_end_x - wall_thickness + battery_end_clearance,
|
||||
wall_thickness,
|
||||
battery_partition_thickness,
|
||||
partition_height - effective_floor + eps
|
||||
]);
|
||||
translate([
|
||||
board_origin_x + 6.5,
|
||||
wall_thickness - eps,
|
||||
partition_y - eps,
|
||||
effective_floor + 1.5
|
||||
])
|
||||
cube([9, wall_thickness + 2 * eps, 6]);
|
||||
cube([
|
||||
board_origin_x + jst_center_xy[0]
|
||||
+ (jst_body_size[0] + 2 * jst_relief_clearance) / 2
|
||||
- wall_thickness + 2 * eps,
|
||||
battery_partition_thickness + 2 * eps,
|
||||
partition_height - effective_floor - 1.5 + eps
|
||||
]);
|
||||
}
|
||||
|
||||
// Three low curved ribs cradle the protected cylindrical pack.
|
||||
@@ -412,29 +614,16 @@ module battery_cradle() {
|
||||
battery_end_x + battery_end_clearance
|
||||
])
|
||||
translate([
|
||||
x,
|
||||
x - eps,
|
||||
battery_center_y - battery_diameter / 2 - battery_radial_clearance,
|
||||
effective_floor - eps
|
||||
])
|
||||
cube([
|
||||
wall_thickness,
|
||||
wall_thickness + 2 * eps,
|
||||
battery_diameter + 2 * battery_radial_clearance,
|
||||
battery_diameter / 2 + 1 + eps
|
||||
]);
|
||||
|
||||
// Rear cross-strip separates the side-mounted switch body from the cell.
|
||||
translate([
|
||||
battery_end_x + battery_end_clearance,
|
||||
partition_y,
|
||||
effective_floor - eps
|
||||
])
|
||||
cube([
|
||||
case_length - wall_thickness - battery_end_x - battery_end_clearance,
|
||||
case_width - wall_thickness - partition_y,
|
||||
min(body_height - effective_floor, switch_center_z
|
||||
+ switch_body_diameter / 2 + switch_wire_allowance
|
||||
- effective_floor) + eps
|
||||
]);
|
||||
}
|
||||
|
||||
module body_positive() {
|
||||
@@ -453,8 +642,10 @@ module body_positive() {
|
||||
if (battery_enabled)
|
||||
battery_cradle();
|
||||
if (tripod_insert_enabled)
|
||||
translate([case_length / 2, case_width / 2, 0])
|
||||
translate([tripod_center_x, tripod_center_y, 0])
|
||||
cylinder(h = effective_floor, d = tripod_boss_diameter);
|
||||
if (usb_guide_active)
|
||||
usb_guide_shell();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,6 +655,7 @@ module body_cutouts() {
|
||||
translate([board_origin_x + mx, board_origin_y + my, 0])
|
||||
insert_pocket(
|
||||
m25_insert_hole_diameter,
|
||||
m25_insert_outer_diameter,
|
||||
m25_insert_length + insert_melt_relief,
|
||||
board_bottom_z
|
||||
);
|
||||
@@ -473,44 +665,24 @@ module body_cutouts() {
|
||||
translate([p[0], p[1], 0])
|
||||
insert_pocket(
|
||||
m25_insert_hole_diameter,
|
||||
m25_insert_outer_diameter,
|
||||
m25_insert_length + insert_melt_relief,
|
||||
body_height
|
||||
);
|
||||
|
||||
// USB-C cable opening. Connector center is from the official ESP32 V2 PCB.
|
||||
usb_center_z = board_top_z + 1.6 + usb_vertical_offset;
|
||||
face_rounded_cutout(
|
||||
"front",
|
||||
board_origin_y + usb_center_y,
|
||||
usb_center_z,
|
||||
usb_opening_width + hole_compensation,
|
||||
usb_opening_height + hole_compensation,
|
||||
usb_opening_radius
|
||||
);
|
||||
|
||||
if (battery_enabled) {
|
||||
face_circle_cutout(
|
||||
"right",
|
||||
switch_center_x,
|
||||
switch_center_z,
|
||||
switch_hole_diameter + switch_hole_compensation
|
||||
if (usb_opening_enabled)
|
||||
face_rounded_cutout(
|
||||
"front",
|
||||
usb_center_u,
|
||||
usb_center_z,
|
||||
usb_inner_width,
|
||||
usb_inner_height,
|
||||
usb_opening_radius
|
||||
);
|
||||
// Clear the switch body and its prewired terminals through the rear bay.
|
||||
translate([
|
||||
switch_center_x,
|
||||
case_width - wall_thickness + eps,
|
||||
switch_center_z
|
||||
])
|
||||
rotate([90, 0, 0])
|
||||
cylinder(
|
||||
h = switch_body_length + switch_wire_allowance
|
||||
+ wall_thickness + eps,
|
||||
d = switch_body_diameter + 2 * fit_clearance
|
||||
);
|
||||
}
|
||||
|
||||
if (tripod_insert_enabled) {
|
||||
translate([case_length / 2, case_width / 2, 0]) {
|
||||
translate([tripod_center_x, tripod_center_y, 0]) {
|
||||
translate([0, 0, -eps])
|
||||
cylinder(
|
||||
h = effective_floor + 2 * eps,
|
||||
@@ -518,6 +690,7 @@ module body_cutouts() {
|
||||
);
|
||||
insert_pocket(
|
||||
tripod_insert_hole_diameter,
|
||||
tripod_insert_outer_diameter,
|
||||
tripod_insert_length + insert_melt_relief,
|
||||
effective_floor
|
||||
);
|
||||
@@ -611,6 +784,13 @@ module oled_button_lid_cutout(xy) {
|
||||
module lid_cutouts() {
|
||||
// Through holes plus shallow counterbores for flush M2.5 socket heads.
|
||||
for (p = lid_boss_xy) {
|
||||
// Scallop only the locating lip so it can pass over the full-height
|
||||
// insert boss while leaving the lid plate supported by the boss top.
|
||||
translate([p[0], p[1], lid_thickness - eps])
|
||||
cylinder(
|
||||
h = lid_lip_depth + 2 * eps,
|
||||
d = m25_boss_diameter + 2 * fit_clearance
|
||||
);
|
||||
translate([p[0], p[1], -eps])
|
||||
cylinder(
|
||||
h = lid_thickness + lid_lip_depth + 2 * eps,
|
||||
@@ -623,6 +803,14 @@ module lid_cutouts() {
|
||||
);
|
||||
}
|
||||
|
||||
if (battery_enabled)
|
||||
translate([rocker_center_x, rocker_center_y, -eps])
|
||||
centered_rounded_prism(
|
||||
rocker_cutout_size,
|
||||
lid_thickness + lid_lip_depth + 2 * eps,
|
||||
rocker_corner_radius
|
||||
);
|
||||
|
||||
if (top_profile == "oled128x64") {
|
||||
beveled_oled_window();
|
||||
if (button_a) oled_button_lid_cutout(oled_button_a_xy);
|
||||
@@ -632,13 +820,55 @@ module lid_cutouts() {
|
||||
}
|
||||
}
|
||||
|
||||
module lid() {
|
||||
module lid_artwork_nominal_2d() {
|
||||
translate(lid_art_center)
|
||||
rotate(lid_art_rotation)
|
||||
// The lid is modeled exterior-face-down; mirror the artwork so it
|
||||
// reads normally after the lid is turned into service position.
|
||||
mirror([0, 1, 0]) {
|
||||
if (lid_logo_enabled)
|
||||
translate([0, lid_logo_center_y])
|
||||
resize(logo_size)
|
||||
import(logo_file, center = true);
|
||||
if (lid_label != "")
|
||||
translate([0, lid_label_center_y])
|
||||
text(
|
||||
lid_label,
|
||||
size = lid_label_size,
|
||||
halign = "center",
|
||||
valign = "center",
|
||||
font = "DejaVu Sans:style=Bold"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module lid_artwork_2d(inset = 0) {
|
||||
offset(delta = inset)
|
||||
lid_artwork_nominal_2d();
|
||||
}
|
||||
|
||||
module lid_base() {
|
||||
difference() {
|
||||
lid_plate_positive();
|
||||
lid_cutouts();
|
||||
if (lid_artwork_enabled)
|
||||
translate([0, 0, -eps])
|
||||
linear_extrude(height = lid_inlay_depth + eps)
|
||||
lid_artwork_2d(lid_inlay_fit_clearance);
|
||||
}
|
||||
}
|
||||
|
||||
module lid_accent() {
|
||||
if (lid_artwork_enabled)
|
||||
linear_extrude(height = lid_inlay_depth)
|
||||
lid_artwork_2d();
|
||||
}
|
||||
|
||||
module lid() {
|
||||
color([0.92, 0.92, 0.95]) lid_base();
|
||||
color([0.95, 0.42, 0.08]) lid_accent();
|
||||
}
|
||||
|
||||
module one_oled_button() {
|
||||
recess_depth = button_flange_thickness + button_travel;
|
||||
stem_height = lid_thickness - recess_depth + button_projection;
|
||||
@@ -678,6 +908,13 @@ module reference_board() {
|
||||
color([0.05, 0.25, 0.55, 0.75])
|
||||
translate([board_origin_x, board_origin_y, board_bottom_z])
|
||||
rounded_box([feather_length, feather_width, board_thickness], 2.54);
|
||||
color([0.92, 0.92, 0.86, 0.9])
|
||||
translate([
|
||||
board_origin_x + jst_center_xy[0] - jst_body_size[0] / 2,
|
||||
board_origin_y + jst_center_xy[1] - 4.5,
|
||||
board_top_z
|
||||
])
|
||||
cube(jst_body_size);
|
||||
color([0.12, 0.45, 0.75, 0.45])
|
||||
for (i = [1 : wing_count])
|
||||
translate([
|
||||
@@ -744,19 +981,20 @@ module reference_battery_and_switch() {
|
||||
translate([battery_start_x, battery_center_y, battery_center_z])
|
||||
rotate([0, 90, 0])
|
||||
cylinder(h = battery_length, d = battery_diameter);
|
||||
color([0.8, 0.15, 0.1, 0.65])
|
||||
color([0.08, 0.08, 0.08, 0.85])
|
||||
translate([
|
||||
switch_center_x,
|
||||
case_width - wall_thickness,
|
||||
switch_center_z
|
||||
rocker_center_x - rocker_body_size[0] / 2,
|
||||
rocker_center_y - rocker_body_size[1] / 2,
|
||||
body_height + exploded_view - rocker_body_size[2]
|
||||
])
|
||||
rotate([90, 0, 0]) {
|
||||
cylinder(h = switch_body_length, d = switch_body_diameter);
|
||||
translate([0, 0, -1.5])
|
||||
cylinder(h = 1.5, d = switch_flange_diameter);
|
||||
translate([0, 0, -3])
|
||||
cylinder(h = 3, d = switch_cap_diameter);
|
||||
}
|
||||
cube(rocker_body_size);
|
||||
color([0.12, 0.12, 0.12, 0.95])
|
||||
translate([
|
||||
rocker_center_x - rocker_cutout_size[0] / 2,
|
||||
rocker_center_y - rocker_cutout_size[1] / 2,
|
||||
body_height + exploded_view + lid_thickness
|
||||
])
|
||||
cube([rocker_cutout_size[0], rocker_cutout_size[1], 2.5]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,13 +1012,9 @@ module assembly() {
|
||||
lid();
|
||||
}
|
||||
|
||||
module coupon_hole(x, y, diameter, depth) {
|
||||
translate([x, y, -eps]) cylinder(h = depth + 2 * eps, d = diameter);
|
||||
}
|
||||
|
||||
module insert_coupon() {
|
||||
spacing = 15;
|
||||
coupon_length = spacing * len(coupon_steps) + 8;
|
||||
coupon_length = spacing * len(tripod_coupon_steps) + 8;
|
||||
coupon_width = 32;
|
||||
coupon_height = max(
|
||||
m25_insert_length + insert_melt_relief + 1.2,
|
||||
@@ -788,18 +1022,22 @@ module insert_coupon() {
|
||||
);
|
||||
difference() {
|
||||
rounded_box([coupon_length, coupon_width, coupon_height], 3);
|
||||
for (i = [0 : len(coupon_steps) - 1]) {
|
||||
for (i = [0 : len(tripod_coupon_steps) - 1]) {
|
||||
x = 7 + i * spacing;
|
||||
coupon_hole(
|
||||
x, 8,
|
||||
m25_insert_hole_diameter + coupon_steps[i],
|
||||
m25_insert_length + insert_melt_relief
|
||||
);
|
||||
coupon_hole(
|
||||
x, 23,
|
||||
tripod_insert_hole_diameter + coupon_steps[i],
|
||||
tripod_insert_length + insert_melt_relief
|
||||
);
|
||||
translate([x, 8, 0])
|
||||
insert_pocket(
|
||||
m25_insert_hole_diameter + m25_coupon_steps[i],
|
||||
m25_insert_outer_diameter,
|
||||
m25_insert_length + insert_melt_relief,
|
||||
coupon_height
|
||||
);
|
||||
translate([x, 23, 0])
|
||||
insert_pocket(
|
||||
tripod_insert_hole_diameter + tripod_coupon_steps[i],
|
||||
tripod_insert_outer_diameter,
|
||||
tripod_insert_length + insert_melt_relief,
|
||||
coupon_height
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -807,15 +1045,17 @@ module insert_coupon() {
|
||||
module switch_coupon() {
|
||||
spacing = 18;
|
||||
coupon_length = spacing * len(coupon_steps) + 6;
|
||||
coupon_width = 24;
|
||||
coupon_height = wall_thickness;
|
||||
coupon_width = rocker_cutout_size[1] + 6;
|
||||
coupon_height = lid_thickness;
|
||||
difference() {
|
||||
rounded_box([coupon_length, coupon_width, coupon_height], 3);
|
||||
for (i = [0 : len(coupon_steps) - 1])
|
||||
translate([6 + i * spacing, coupon_width / 2, -eps])
|
||||
cylinder(
|
||||
h = coupon_height + 2 * eps,
|
||||
d = switch_hole_diameter + coupon_steps[i]
|
||||
centered_rounded_prism(
|
||||
rocker_cutout_size
|
||||
+ [coupon_steps[i], coupon_steps[i]],
|
||||
coupon_height + 2 * eps,
|
||||
rocker_corner_radius
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -854,12 +1094,18 @@ module fit_coupon() {
|
||||
}
|
||||
}
|
||||
|
||||
validate_cutout_spacing();
|
||||
|
||||
if (part == "assembly")
|
||||
assembly();
|
||||
else if (part == "body")
|
||||
body();
|
||||
else if (part == "lid")
|
||||
lid();
|
||||
else if (part == "lid_base")
|
||||
lid_base();
|
||||
else if (part == "lid_accent")
|
||||
lid_accent();
|
||||
else if (part == "buttons")
|
||||
buttons();
|
||||
else if (part == "insert_coupon")
|
||||
|
||||
Reference in New Issue
Block a user