diff --git a/case.scad b/case.scad new file mode 100644 index 0000000..ff652f0 --- /dev/null +++ b/case.scad @@ -0,0 +1,201 @@ +// --- Parameters --- +columns = 15; +rows = 5; +u_size = 19.05; +margin = 5.0; + +plate_w = (15 * u_size) + (margin * 2); +plate_h = (5 * u_size) + (margin * 2); + +// --- Plate --- +plate_thickness = 1.5; +plate_slack = 0.15; +recess_depth = 2.5; + +// --- Case Dimensions --- +case_angle = 5; +wall_thickness = 4.0; +front_height = 18.0; +clearance = 0.25; +floor_thickness = 4.0; +rim_extra_height = 3.0; + +pillar_dia = 3; + +back_hole_dia = 15.5; +hole_to_holder_gap = 10.0; + +case_w = plate_w + (wall_thickness * 2) + (clearance * 2) - 2; +case_h = ((plate_h - 2) * cos(case_angle)) + (wall_thickness * 2) + (clearance * 2); + +back_height = front_height + (tan(case_angle) * case_h); + +// --- USB-C and Controller Holder --- +usb_w = 12.0; +usb_h = 8.5; +usb_x_offset = 60.0; +usb_z_pos = floor_thickness; + +holder_x = 20.0; +holder_y = 35.0; +holder_height = 5.0; +holder_wall = 2.0; + +// --- Splitting --- +joint_clearance = 0.05; +tab_size = 10; + +// --- Clips --- +clip_width = 10.0; +clip_depth = 1.5; +clip_height = 1.5; + +module back_wall_hole() { + // 1. Locate the left edge of the controller holder assembly + holder_left_edge = usb_x_offset + (usb_w/2) - (holder_x/2) - holder_wall; + + // 2. Calculate X center: (Left Edge - Gap - Radius) + x_pos = holder_left_edge - hole_to_holder_gap - (back_hole_dia / 2); + + // 3. Calculate Z center (Bottom of hole touching the floor) + z_pos = floor_thickness + (back_hole_dia / 2); + + // 4. Create the cutout + translate([x_pos, case_h - wall_thickness - 1, z_pos]) + rotate([-90, 0, 0]) + cylinder(d = back_hole_dia, h = wall_thickness + 2, $fn = 64); +} + +module controller_holder() { + x_pos = usb_x_offset + (usb_w/2) - (holder_x/2) - holder_wall; + y_inner_wall = case_h - wall_thickness; + y_pos = y_inner_wall - holder_y - holder_wall; + + translate([x_pos, y_pos, floor_thickness]) { + difference() { + cube([holder_x + (holder_wall * 2), holder_y + holder_wall, holder_height]); + translate([holder_wall, holder_wall, -0.1]) + cube([holder_x, holder_y + 0.1, holder_height + 0.2]); + } + } +} + +module usb_cutout() { + translate([usb_x_offset, case_h - wall_thickness - 1, usb_z_pos]) + cube([usb_w, wall_thickness + 2, usb_h]); +} + +module angled_clip() { + color("red") + rotate([case_angle, 0, 0]) + cube([clip_width, clip_depth, clip_height]); +} + +module support_pillars() { + // Gap indices: x means the gap between column/row x and x+1 + col_gaps = [3, 6, 9, 12]; + row_gaps = [1, 2, 3]; + + plate_start_x = (case_w - plate_w)/2 - 2; + plate_start_y = (case_h - (plate_h * cos(case_angle)))/2; + + intersection() { + union() { + for (c = col_gaps) { + for (r = row_gaps) { + x_pos = plate_start_x + margin + (c * u_size); + + y_pos_flat = margin + (r * u_size); + y_pos = plate_start_y + (y_pos_flat * cos(case_angle)); + + translate([x_pos, y_pos, 0]) + cylinder(d = pillar_dia, h = back_height + 10, $fn = 32); + } + } + } + + translate([0, plate_start_y, front_height - recess_depth]) + rotate([case_angle, 0, 0]) + translate([-50, 0, -500]) + cube([case_w + 100, plate_h, 500]); + } +} + +module case_full_geometry() { + clip_z_offset = plate_thickness + plate_slack; + + // Exact dimensions for the recess + recess_w = plate_w + (plate_slack * 2); + recess_h = plate_h + (plate_slack * 2); + + union() { + difference() { + // 1. Shell + polyhedron( + points=[ + [0,0,0], [case_w,0,0], [case_w,case_h,0], [0,case_h,0], + [0,0,front_height + rim_extra_height], [case_w,0,front_height + rim_extra_height], + [0,case_h,back_height + rim_extra_height], [case_w,case_h,back_height + rim_extra_height] + ], + faces=[[0,1,2,3], [4,5,1,0], [7,6,3,2], [5,7,2,1], [6,4,0,3], [4,6,7,5]] + ); + + // 2. Internal Tub + translate([wall_thickness, wall_thickness, floor_thickness]) + cube([case_w - (wall_thickness * 2), case_h - (wall_thickness * 2), back_height + 20]); + + // 3. Plate Recess + translate([(case_w - recess_w)/2, (case_h - (recess_h * cos(case_angle)))/2, front_height - recess_depth]) + rotate([case_angle, 0, 0]) + cube([recess_w, recess_h, 20]); + } + + support_pillars(); + + // 4. Clips + for (i = [0:5]) { + z_front = (front_height - recess_depth) + clip_z_offset; + translate([wall_thickness + (case_w - wall_thickness*2)/7 * (i+1) - clip_width/2, wall_thickness - clip_depth, z_front]) + angled_clip(); + + y_back_wall = case_h - wall_thickness; + z_back_shelf = (front_height - recess_depth) + (tan(case_angle) * (y_back_wall - wall_thickness)); + z_back_clip = z_back_shelf + clip_z_offset; + translate([wall_thickness + (case_w - wall_thickness*2)/7 * (i+1) - clip_width/2, y_back_wall - clip_depth * 0.5, z_back_clip]) + angled_clip(); + } + } +} + +module jigsaw_cutter(gap) { + union() { + translate([case_w/2 + gap, -1, -1]) + cube([case_w, case_h + 2, back_height + 40]); + translate([case_w/2 - tab_size + gap, case_h/2 - 15, -1]) + linear_extrude(back_height + 40) + polygon(points=[[gap,0], [tab_size, -5], [tab_size, 35], [gap, 30]]); + } +} + +// --- Render --- +render_part = "both"; + +if (render_part == "left" || render_part == "both") { + union() { + difference() { + case_full_geometry(); + jigsaw_cutter(-joint_clearance); + usb_cutout(); + back_wall_hole(); + } + controller_holder(); + } +} + +if (render_part == "right" || render_part == "both") { + translate([render_part == "both" ? 10 : 0, 0, 0]) + intersection() { + case_full_geometry(); + jigsaw_cutter(joint_clearance); + } +} \ No newline at end of file diff --git a/case_ortholinear.scad b/case_ortholinear.scad index 3d23da4..e1f6859 100644 --- a/case_ortholinear.scad +++ b/case_ortholinear.scad @@ -98,7 +98,6 @@ module support_pillars() { col_gaps = [3, 6, 8, 11]; row_gaps = [1, 2, 3]; - // Plate start positions in case coordinates plate_start_x = (case_w - plate_w)/2 - 2; plate_start_y = (case_h - (plate_h * cos(case_angle)))/2 - 2 * cos(case_angle); @@ -106,10 +105,9 @@ module support_pillars() { union() { for (c = col_gaps) { for (r = row_gaps) { - // X is simple: plate offset + margin + (gap index * spacing) + x_pos = plate_start_x + margin + (c * switch_spacing); - // Y must account for the tilt contraction (cos) y_pos_flat = margin + (r * switch_spacing); y_pos = plate_start_y + (y_pos_flat * cos(case_angle)); @@ -119,11 +117,9 @@ module support_pillars() { } } - // Clipping volume: Vertical space from floor up to the underside of the tilted plate - // This ensures the pillars are vertical but have an angled top surface translate([0, plate_start_y, front_height - recess_depth]) rotate([case_angle, 0, 0]) - translate([-50, 0, -500]) // Big volume below the plate + translate([-50, 0, -500]) cube([case_w + 100, plate_h, 500]); } } @@ -151,7 +147,7 @@ module case_full_geometry() { translate([wall_thickness, wall_thickness, floor_thickness]) cube([case_w - (wall_thickness * 2), case_h - (wall_thickness * 2), back_height + 20]); - // 3. The Plate Recess (Hardcode removed, now using plate_slack) + // 3. Plate Recess translate([(case_w - recess_w)/2, (case_h - (recess_h * cos(case_angle)))/2, front_height - recess_depth]) rotate([case_angle, 0, 0]) cube([recess_w, recess_h, 20]);