Created
January 30, 2026 05:58
-
-
Save brentvatne/d27007e6b4bd4af59f3352b0de56a79c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ============================================ | |
| // DIGITAKT 2 BATTCAVE - Wedge Stand Design | |
| // ============================================ | |
| /* [Device Dimensions] */ | |
| device_width = 215; | |
| device_depth = 176; | |
| device_height = 63; | |
| /* [Stand Design] */ | |
| tilt_angle = 12; | |
| wall = 5; // Outer wall thickness | |
| base_floor = 8; // Solid bottom thickness | |
| ledge_width = 10; // How far the ledge extends under the device | |
| ledge_thickness = 5; // Ledge thickness | |
| lip_height = 10; // Lip height above ledge (front and sides) | |
| front_height = 55; // Height at front of stand | |
| /* [Power Bank - Anker 165W] */ | |
| pb_length = 157; | |
| pb_width = 54; | |
| pb_height = 49; | |
| /* [W+ Link Transmitter] */ | |
| wl_size = 65; | |
| wl_height = 28; | |
| /* [Aesthetics] */ | |
| corner_r = 8; | |
| /* [Hidden] */ | |
| $fn = $preview ? 32 : 64; | |
| epsilon = 0.01; | |
| // ============================================ | |
| // CALCULATED | |
| // ============================================ | |
| clearance = 0.5; | |
| // Device pocket (where device body sits) | |
| pocket_w = device_width + clearance * 2; | |
| pocket_d = device_depth + clearance * 2; | |
| // Stand outer dimensions (wall + ledge on each side) | |
| stand_width = pocket_w + wall * 2; | |
| stand_depth = pocket_d + wall * 2; | |
| // Wedge heights | |
| tilt_rise = stand_depth * tan(tilt_angle); | |
| back_height = front_height + tilt_rise; | |
| // Where the tilted ledge surface sits (Z at front edge) | |
| ledge_z = front_height - ledge_thickness - lip_height; | |
| // Opening through the shelf (for battery access when device removed) | |
| // All 4 sides have shelf for support | |
| shelf_opening_w = pocket_w - ledge_width * 2; | |
| shelf_opening_d = pocket_d - ledge_width * 2; | |
| echo(str("=== Digitakt 2 BattCave ===")); | |
| echo(str("Stand: ", stand_width, " x ", stand_depth, " mm")); | |
| echo(str("Pocket: ", pocket_w, " x ", pocket_d, " mm")); | |
| echo(str("Ledge opening: ", shelf_opening_w, " x ", shelf_opening_d, " mm")); | |
| // Debug: Key Z heights | |
| echo(str("--- Geometry Debug ---")); | |
| echo(str("ledge_z (shelf bottom at front): ", ledge_z)); | |
| echo(str("ledge_z + ledge_thickness (shelf top at front): ", ledge_z + ledge_thickness)); | |
| echo(str("Shelf top at back: ", ledge_z + ledge_thickness + pocket_d * tan(tilt_angle))); | |
| echo(str("lip_height: ", lip_height)); | |
| echo(str("Expected lip top at back: ", ledge_z + ledge_thickness + pocket_d * tan(tilt_angle) + lip_height)); | |
| echo(str("back_height (wedge top at back): ", back_height)); | |
| echo(str("Wrap width: 10mm, Wrap should be ", lip_height, "mm tall")); | |
| // ============================================ | |
| // MODULES | |
| // ============================================ | |
| module rounded_rect_2d(w, d, r) { | |
| offset(r) offset(-r) square([w, d]); | |
| } | |
| // Wedge with rounded corners | |
| module wedge_shape(w, d, h_front, h_back, r) { | |
| intersection() { | |
| linear_extrude(h_back + 10) | |
| rounded_rect_2d(w, d, r); | |
| hull() { | |
| cube([w, epsilon, h_front]); | |
| translate([0, d - epsilon, 0]) | |
| cube([w, epsilon, h_back]); | |
| } | |
| } | |
| } | |
| module battcave() { | |
| difference() { | |
| // === OUTER WEDGE SHELL === | |
| wedge_shape(stand_width, stand_depth, front_height, back_height, corner_r); | |
| // === HOLLOW OUT BATTERY COMPARTMENT === | |
| // Only up to BELOW the ledge | |
| translate([wall, wall, base_floor]) | |
| cube([stand_width - wall*2, stand_depth - wall*2, ledge_z - base_floor]); | |
| // === CUT THE DEVICE POCKET (above the ledge) === | |
| // This is where the device body sits - full pocket size | |
| translate([wall, wall, ledge_z + ledge_thickness]) | |
| rotate([tilt_angle, 0, 0]) | |
| cube([pocket_w, pocket_d + 50, back_height]); | |
| // === CUT OPENING THROUGH THE LEDGE === | |
| // Smaller than device - device edges REST on the remaining ledge | |
| translate([wall + ledge_width, wall + ledge_width, ledge_z - 10]) | |
| rotate([tilt_angle, 0, 0]) | |
| cube([shelf_opening_w, shelf_opening_d, ledge_thickness + 20]); | |
| // === TRIM TILTED GEOMETRY === | |
| translate([-10, -50, 0]) | |
| cube([stand_width + 20, 50, back_height + 50]); | |
| translate([-10, stand_depth, 0]) | |
| cube([stand_width + 20, 50, back_height + 50]); | |
| translate([-50, -10, 0]) | |
| cube([50, stand_depth + 20, back_height + 50]); | |
| translate([stand_width, -10, 0]) | |
| cube([50, stand_depth + 20, back_height + 50]); | |
| // === REAR CABLE HOLE (rounded rectangle) === | |
| cable_hole_w = 50; | |
| cable_hole_h = 25; | |
| cable_hole_r = 8; // Corner radius | |
| translate([stand_width/2 - cable_hole_w/2, stand_depth + epsilon, base_floor + 15]) | |
| rotate([90, 0, 0]) | |
| linear_extrude(wall + epsilon*2) | |
| offset(cable_hole_r) offset(-cable_hole_r) | |
| square([cable_hole_w, cable_hole_h]); | |
| // === REMOVE BACK LIP (except 10mm wrap at right corner) === | |
| // Cut must start at shelf surface level at the back, NOT at front shelf level | |
| // The shelf tilts upward from front to back, so we calculate Z at the cut position | |
| back_lip_wrap = 10; | |
| cut_w = pocket_w - back_lip_wrap; | |
| cut_d = wall + ledge_width + 50; | |
| cut_h = back_height * 2; | |
| // Calculate the Y position where cut starts (inner back edge of shelf) | |
| back_lip_cut_y = stand_depth - wall - ledge_width; | |
| // Calculate shelf top Z at that Y position (accounting for tilt from front inner edge) | |
| back_lip_cut_z = ledge_z + ledge_thickness + (back_lip_cut_y - wall) * tan(tilt_angle); | |
| translate([wall, back_lip_cut_y, back_lip_cut_z]) | |
| rotate([tilt_angle, 0, 0]) // Follow the tilt to match lip geometry | |
| hull() { | |
| // Main cut body (shortened to leave room for rounded corner) | |
| cube([cut_w - corner_r, cut_d, cut_h]); | |
| // Rounded corner (cylinder creates the 8mm radius) | |
| translate([cut_w - corner_r, corner_r, 0]) | |
| cylinder(r=corner_r, h=cut_h, $fn=32); | |
| } | |
| } | |
| } | |
| // ============================================ | |
| // RENDER | |
| // ============================================ | |
| color("DarkSlateGray") battcave(); | |
| // Ghost Digitakt 2 - sitting ON the tilted ledge | |
| %translate([wall + clearance, wall + clearance, ledge_z + ledge_thickness + 0.5]) | |
| rotate([tilt_angle, 0, 0]) | |
| cube([device_width, device_depth, device_height]); | |
| // Ghost power bank (in battery compartment) | |
| %translate([wall + 10, stand_depth/2 - pb_width/2, base_floor + 1]) | |
| color("DimGray", 0.5) | |
| cube([pb_length, pb_width, pb_height]); | |
| // Ghost W+ Link | |
| %translate([stand_width - wall - wl_size - 10, wall + 10, base_floor + 1]) | |
| color("SteelBlue", 0.5) | |
| cube([wl_size, wl_size, wl_height]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment