diff --git a/model/common.scad b/model/common.scad index abfb1d0..c43dfcf 100644 --- a/model/common.scad +++ b/model/common.scad @@ -61,9 +61,10 @@ module main_display(pin_header = false) { left(sd_inset) cuboid([sd_width, sd_length, sd_height], anchor=TOP + RIGHT + FRONT); - position(BOTTOM + LEFT) - xrot(180) - pin_header(14, spin=90, anchor=BOTTOM + BACK); + if (pin_header) + position(BOTTOM + LEFT) + xrot(180) + pin_header(14, spin=90, anchor=BOTTOM + BACK); } xcopies(length - (2 * hole_inset) - hole_diameter) { diff --git a/model/left-half.scad b/model/left-half.scad index f3c9203..ff13c4d 100644 --- a/model/left-half.scad +++ b/model/left-half.scad @@ -1,12 +1,225 @@ +include include -module left_half_components() { - cube([1,1,1]); +module left_half_orig() { + translate(BASE_MODEL_TRANSLATE) scale(BASE_MODEL_SCALE) import(file="left-half.3mf"); +} + +BASE_MODEL_SCALE = 1.1; +// Bounds: ~ [128.62, 185.46, 35.2] +LEFT_HALF_BOUNDING_BOX = BASE_MODEL_SCALE * [116.93, 168.6, 32]; + +w = LEFT_HALF_BOUNDING_BOX.x; +l = LEFT_HALF_BOUNDING_BOX.y; +max_h = LEFT_HALF_BOUNDING_BOX.z; + +// Proportional heights based on a 4.5 unit total scale +base_h = max_h * (2.0 / 4.5); +border_h = max_h * (2.5 / 4.5); +top_h = max_h * (3.5 / 4.5); +sphere_r = max_h - top_h; + +recess_h = 3; + +bump_height = 10; +faceplate_thickness = 2; +wall = 5; + +top_bump_width_big = 55; +top_bump_width_small = 35; + +hinge_thickness = 22; + +/// Builds the top bump. +module top_bump(height, extra_width = 0, inverted = false, wall = 2, anchor = CENTER, spin = 0, orient = UP) { + width_big = top_bump_width_big; + width_small = top_bump_width_small; + + // Assuming 'w' is your base width defined in the outer scope. + // Lock the left side to the base width, add extra_width only to the right. + left_x = -w / 2; + right_x = (w / 2) + extra_width; + + top_path = back( + // center the path around the origin + width_big / 2, [ + [right_x, 0], // Index 0, top-right + [left_x, 0], // Index 1, top-left + [left_x, -width_big], // Index 2, bottom-left + [left_x / 2 + 20, -width_big], // Index 3, bottom-center-left + [0, -width_small], // Index 4, middle-center + [right_x, -width_small], // Index 5, bottom-right + ] + ); + + radii_list = [5, 5, 5, 15, 5, 5]; + + top_poly = round_corners(top_path, radius=radii_list); + inner_poly = offset(top_poly, delta=-wall); + + // The new total width + total_width = w + extra_width; + part_size = [total_width, width_big, height]; + + // The geometric center of the asymmetric shape is shifted to the right + part_offset = [extra_width / 2, 0, 0]; + + // Removed the `right(extra_width)` translation here. + // Added `offset` so BOSL2 knows the bounding box isn't centered at [0,0,0], + // keeping the CENTER anchor exactly on your original origin point. + attachable(anchor, spin, orient, size=part_size, offset=part_offset) { + if (inverted) { + down(wall / 2) + linear_sweep(inner_poly, height=height - wall + EPS, center=true); + } else { + diff("empty") { + linear_sweep(top_poly, height=height, center=true) { + attach(TOP) + tag("empty") + down(wall) + left(wall / 4) // (Leaving your original shift here) + linear_sweep(inner_poly, height=height - wall + EPS, anchor=TOP); + } + } + } + + children(); + } +} + +module top_bump_skirt(height = 5) { + intersection() { + diff("heel_holes") { + tag("heel_holes") + down(EPS / 2) + top_bump(height=height * 2 + EPS, wall=wall, inverted=true, anchor=FRONT + BOTTOM); + top_bump(height=height, wall=wall, anchor=FRONT + BOTTOM); + } + down(EPS / 2) + cuboid([w, wall + (top_bump_width_big - top_bump_width_small) + EPS, height * 2 + EPS], anchor=FRONT + BOTTOM); + } +} + +module top_bump_side_extension(width, height = 5) { + intersection() { + diff("heel_holes") { + tag("heel_holes") + down(EPS / 2) + top_bump(height=height * 2 + EPS, wall=wall, inverted=true, anchor=RIGHT + TOP + BACK); + top_bump(height=height, wall=wall, anchor=RIGHT + TOP + BACK); + } + back(EPS / 2) + up(EPS / 2) + cuboid([width, top_bump_width_small + EPS, height * 2 + EPS], anchor=RIGHT + TOP + BACK); + } +} + +module top_bump_big_sphere(diameter, ring_extra_diameter, ring_height) { + zcyl(d=diameter + ring_extra_diameter, h=ring_height, anchor=BOTTOM) + position(TOP) + top_half() sphere(diameter / 2); +} + +module top_bump_small_spheres(diameter, spacing) { + top_half() { + left(spacing) sphere(r=diameter / 2, anchor=CENTER); + sphere(r=diameter / 2, anchor=CENTER); + right(spacing) sphere(r=diameter / 2, anchor=CENTER); + } +} + +/// Hinges to attach the right to the left (=this) half. +/// Origin is at the bottom left front corner. +module hinges(joint_axis_length = 80, hinge_thickness = 22) { + hinge_inset = 10; + hinge_inset_diameter = 6; + + back(joint_axis_length) + yflip_copy(offset=joint_axis_length) { + right(border_h / 2 + 2) + // cannot get it to align with edge otherwise + ycyl(r=border_h / 2 + 2, l=hinge_thickness, anchor=BOTTOM + BACK) + position(FRONT) tag("holes") fwd(EPS) ycyl(d=hinge_inset_diameter, l=hinge_inset, anchor=FRONT); + cuboid([border_h / 2 + 2 + EPS, hinge_thickness, base_h], anchor=LEFT + BOTTOM + BACK); + } } module left_half() { - translate(BASE_MODEL_TRANSLATE) scale(BASE_MODEL_SCALE) import(file = "left-half.3mf"); + diff("holes", "forcekeep") + // Outer walls + color_this("green") + rect_tube(h=base_h, size=[w, l], wall=wall, rounding=[0, 5, 5, 0]) { + + // Gap for extension of top bump + position(RIGHT + BACK + BOTTOM) + fwd(wall) + move([EPS / 2, 0, -EPS / 2]) + tag("holes") + cuboid([wall + EPS, top_bump_width_small - wall + EPS, EPS + bump_height + base_h], anchor=RIGHT + BOTTOM + BACK); + + // Recessed Faceplate + attach(TOP) + recolor("blue") + down(recess_h) { + cuboid([w - EPS, l - EPS, faceplate_thickness], rounding=5, edges="Z", anchor=TOP) { + position(BACK) + tag("holes") top_bump(height=bump_height + faceplate_thickness + recess_h, wall=wall, inverted=true, anchor=BACK); + } + } + + // Top Bump + tag("forcekeep") + recolor("Gold") + position(TOP + BACK) { + down(recess_h + faceplate_thickness) + top_bump(height=bump_height + faceplate_thickness + recess_h, extra_width=hinge_thickness, wall=wall, anchor=BOTTOM + BACK) { + position(TOP + BACK) + fwd(20) { + position(LEFT) + right(w / 5) + top_bump_big_sphere(diameter=23, ring_extra_diameter=4, ring_height=2); + position(RIGHT) + left(w / 5) + top_bump_small_spheres(diameter=7, spacing=15); + } + position(FRONT + BOTTOM) + fwd(wall) + top_bump_skirt(recess_h + faceplate_thickness); + position(RIGHT + BACK + TOP) + down(EPS) + back(EPS) + recolor("red") + top_bump_side_extension(width=hinge_thickness + wall, height=bump_height + base_h); + } + } + + // TODO: do this properly! + // 4. Cutouts/Details in the Recessed Area + *up(base_h / 2 - max_h / 2) { + // Main Portrait Display Cutout + translate([0, 10, 0]) + tag("holes") cuboid([w - 40, l - 90, base_h + 2], rounding=2, edges="Z"); + + // Small Landscape Display Cutout (Bottom Left) + translate([-w / 2 + 35, -l / 2 + 35, 0]) + tag("holes") cuboid([28, 12, base_h + 2], rounding=1, edges="Z"); + + // D-Pad Cutout (Bottom Right) + translate([w / 2 - 35, -l / 2 + 35, 0]) + tag("holes") union() { + cuboid([22, 8, base_h + 2], rounding=1, edges="Z"); + cuboid([8, 22, base_h + 2], rounding=1, edges="Z"); + } + } + + // 5. Right Side Hinges + recolor("Red") + position(BOTTOM + RIGHT + FRONT) { + hinges(); + } + } } left_half(); -#left_half_components(); \ No newline at end of file + +left(300) left_half_orig();