initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.stl
|
||||||
99
backplate.scad
Normal file
99
backplate.scad
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// --- Toggle which part to render ---
|
||||||
|
render_part = "full";
|
||||||
|
|
||||||
|
// --- Parameters ---
|
||||||
|
columns = 14;
|
||||||
|
rows = 5;
|
||||||
|
u_size = 19.05;
|
||||||
|
|
||||||
|
switch_outer_width = 14.2;
|
||||||
|
switch_inner_width = 13.2;
|
||||||
|
switch_total_height = 14.2;
|
||||||
|
notch_height = 4.0;
|
||||||
|
|
||||||
|
plate_thickness = 1.5;
|
||||||
|
margin = 5.0;
|
||||||
|
|
||||||
|
// --- Calculated Plate Dimensions ---
|
||||||
|
plate_width = (columns - 1) * u_size + switch_outer_width + (margin * 2);
|
||||||
|
plate_height = (rows - 1) * u_size + switch_total_height + (margin * 2);
|
||||||
|
split_point = plate_width / 2;
|
||||||
|
|
||||||
|
// --- Modules ---
|
||||||
|
module switch_cutout() {
|
||||||
|
union() {
|
||||||
|
cube([switch_inner_width, switch_total_height, plate_thickness + 2], center=true);
|
||||||
|
translate([0, (switch_total_height - (switch_total_height-notch_height)/2)/2, 0])
|
||||||
|
cube([switch_outer_width, (switch_total_height-notch_height)/2, plate_thickness + 2], center=true);
|
||||||
|
translate([0, -(switch_total_height - (switch_total_height-notch_height)/2)/2, 0])
|
||||||
|
cube([switch_outer_width, (switch_total_height-notch_height)/2, plate_thickness + 2], center=true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module stabilizer_cutout(wire_len_u) {
|
||||||
|
dist = (wire_len_u == 6.25) ? 100 : 23.85;
|
||||||
|
union() {
|
||||||
|
switch_cutout();
|
||||||
|
for(m = [-1, 1]) {
|
||||||
|
translate([m * dist/2, 0, 0]) {
|
||||||
|
cube([3.3, 14.0, plate_thickness + 2], center=true);
|
||||||
|
translate([m * 0.5, 0, 0])
|
||||||
|
cube([4.3, 8.0, plate_thickness + 2], center=true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module whole_plate() {
|
||||||
|
difference() {
|
||||||
|
cube([plate_width, plate_height, plate_thickness]);
|
||||||
|
|
||||||
|
translate([margin + switch_outer_width/2, margin + switch_total_height/2, plate_thickness/2]) {
|
||||||
|
for (r = [0 : rows - 1]) {
|
||||||
|
for (c = [0 : columns - 1]) {
|
||||||
|
translate([c * u_size, r * u_size, 0]) {
|
||||||
|
|
||||||
|
// --- ROW 0: BOTTOM ROW ---
|
||||||
|
if (r == 0) {
|
||||||
|
if (c < 3) {
|
||||||
|
switch_cutout();
|
||||||
|
}
|
||||||
|
else if (c == 3) {
|
||||||
|
translate([u_size * 3, 0, 0]) stabilizer_cutout(6.25);
|
||||||
|
}
|
||||||
|
else if (c >= 10) {
|
||||||
|
switch_cutout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ROW 2: THIRD ROW (ENTER) ---
|
||||||
|
else if (r == 2) {
|
||||||
|
if (c < 12) {
|
||||||
|
switch_cutout();
|
||||||
|
} else if (c == 12) {
|
||||||
|
translate([u_size * 0.5, 0, 0]) stabilizer_cutout(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ALL OTHER ROWS ---
|
||||||
|
else {
|
||||||
|
switch_cutout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Render ---
|
||||||
|
if (render_part == "left") {
|
||||||
|
intersection() { whole_plate(); cube([split_point, plate_height, plate_thickness + 2]); }
|
||||||
|
} else if (render_part == "right") {
|
||||||
|
translate([-split_point, 0, 0]) intersection() {
|
||||||
|
whole_plate();
|
||||||
|
translate([split_point, 0, -1]) cube([split_point, plate_height, plate_thickness + 2]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
whole_plate();
|
||||||
|
}
|
||||||
82
backplate_numpad.scad
Normal file
82
backplate_numpad.scad
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// --- Parameters ---
|
||||||
|
columns = 4;
|
||||||
|
rows = 6;
|
||||||
|
u_size = 19.05;
|
||||||
|
|
||||||
|
switch_outer_width = 14.2;
|
||||||
|
switch_inner_width = 13.2;
|
||||||
|
switch_total_height = 14.2;
|
||||||
|
notch_height = 4.0;
|
||||||
|
|
||||||
|
plate_thickness = 1.5;
|
||||||
|
margin = 5.0;
|
||||||
|
|
||||||
|
// --- Calculated Plate Dimensions ---
|
||||||
|
plate_width = (columns - 1) * u_size + switch_outer_width + (margin * 2);
|
||||||
|
plate_height = (rows - 1) * u_size + switch_total_height + (margin * 2);
|
||||||
|
|
||||||
|
// --- Modules ---
|
||||||
|
module switch_cutout() {
|
||||||
|
union() {
|
||||||
|
cube([switch_inner_width, switch_total_height, plate_thickness + 2], center=true);
|
||||||
|
translate([0, (switch_total_height - (switch_total_height-notch_height)/2)/2, 0])
|
||||||
|
cube([switch_outer_width, (switch_total_height-notch_height)/2, plate_thickness + 2], center=true);
|
||||||
|
translate([0, -(switch_total_height - (switch_total_height-notch_height)/2)/2, 0])
|
||||||
|
cube([switch_outer_width, (switch_total_height-notch_height)/2, plate_thickness + 2], center=true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module stabilizer_cutout(is_vertical=false) {
|
||||||
|
dist = 23.85; // Standard 2u stabilizer distance
|
||||||
|
union() {
|
||||||
|
switch_cutout();
|
||||||
|
rotate([0, 0, is_vertical ? 90 : 0]) {
|
||||||
|
for(m = [-1, 1]) {
|
||||||
|
translate([m * dist/2, 0, 0]) {
|
||||||
|
cube([3.3, 14.0, plate_thickness + 2], center=true);
|
||||||
|
translate([m * 0.5, 0, 0])
|
||||||
|
cube([4.3, 8.0, plate_thickness + 2], center=true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module whole_plate() {
|
||||||
|
difference() {
|
||||||
|
cube([plate_width, plate_height, plate_thickness]);
|
||||||
|
|
||||||
|
translate([margin + switch_outer_width/2, margin + switch_total_height/2, plate_thickness/2]) {
|
||||||
|
for (r = [0 : rows - 1]) {
|
||||||
|
for (c = [0 : columns - 1]) {
|
||||||
|
translate([c * u_size, r * u_size, 0]) {
|
||||||
|
|
||||||
|
// 1. COLUMN 3: (Enter, Plus, and 2 1U Keys)
|
||||||
|
if (c == 3) {
|
||||||
|
if (r == 0 || r == 2) {
|
||||||
|
// Bottom 2 2U Vertical keys
|
||||||
|
translate([0, u_size/2, 0]) stabilizer_cutout(true);
|
||||||
|
} else if (r == 4 || r == 5) {
|
||||||
|
// Top 2 1U keys
|
||||||
|
switch_cutout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. BOTTOM ROW: 2U '0' Key (Cols 0 and 1)
|
||||||
|
else if (r == 0 && (c == 0 || c == 1)) {
|
||||||
|
if (c == 0) translate([u_size/2, 0, 0]) stabilizer_cutout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. OTHER 1U KEYS
|
||||||
|
else {
|
||||||
|
switch_cutout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- full render ---
|
||||||
|
whole_plate();
|
||||||
188
case.scad
Normal file
188
case.scad
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
// --- Parameters ---
|
||||||
|
columns = 14;
|
||||||
|
rows = 5;
|
||||||
|
switch_spacing = 19.05;
|
||||||
|
switch_outer_width = 14.2;
|
||||||
|
switch_total_height = 14.2;
|
||||||
|
margin = 5.0;
|
||||||
|
|
||||||
|
plate_w = (columns - 1) * switch_spacing + switch_outer_width + (margin * 2);
|
||||||
|
plate_h = (rows - 1) * switch_spacing + switch_total_height + (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;
|
||||||
|
|
||||||
|
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 = 6.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 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, 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);
|
||||||
|
|
||||||
|
intersection() {
|
||||||
|
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));
|
||||||
|
|
||||||
|
translate([x_pos, y_pos, 0])
|
||||||
|
cylinder(d = pillar_dia, h = back_height + 10, $fn = 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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. The Plate Recess (Hardcode removed, now using plate_slack)
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
149
case_numpad.scad
Normal file
149
case_numpad.scad
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
// --- Parameters ---
|
||||||
|
columns = 4; // Numpad Width
|
||||||
|
rows = 6; // Numpad Height
|
||||||
|
switch_spacing = 19.05;
|
||||||
|
switch_outer_width = 14.2;
|
||||||
|
switch_total_height = 14.2;
|
||||||
|
margin = 5.0;
|
||||||
|
|
||||||
|
plate_w = (columns - 1) * switch_spacing + switch_outer_width + (margin * 2);
|
||||||
|
plate_h = (rows - 1) * switch_spacing + switch_total_height + (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;
|
||||||
|
|
||||||
|
case_w = plate_w + (wall_thickness * 2) + (clearance * 2) - 2;
|
||||||
|
case_h = (plate_h * cos(case_angle)) + (wall_thickness * 2) + (clearance * 2) - 2 * cos(case_angle);
|
||||||
|
|
||||||
|
back_height = front_height + (tan(case_angle) * case_h);
|
||||||
|
|
||||||
|
// --- USB-C and Controller Holder ---
|
||||||
|
usb_w = 12.0;
|
||||||
|
usb_h = 6.5;
|
||||||
|
usb_x_offset = (case_w / 2) - (usb_w / 2);
|
||||||
|
usb_z_pos = floor_thickness;
|
||||||
|
|
||||||
|
holder_x = 20.0;
|
||||||
|
holder_y = 30.0;
|
||||||
|
holder_height = 5.0;
|
||||||
|
holder_wall = 2.0;
|
||||||
|
|
||||||
|
// --- Clips ---
|
||||||
|
clip_width = 10.0;
|
||||||
|
clip_depth = 1.5;
|
||||||
|
clip_height = 1.5;
|
||||||
|
|
||||||
|
module controller_holder() {
|
||||||
|
x_pos = (case_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() {
|
||||||
|
col_gaps = [1, 3];
|
||||||
|
row_gaps = [1, 3, 5];
|
||||||
|
|
||||||
|
plate_start_x = (case_w - plate_w)/2 - 2;
|
||||||
|
plate_start_y = (case_h - (plate_h * cos(case_angle)))/2 - 2 * cos(case_angle);
|
||||||
|
|
||||||
|
intersection() {
|
||||||
|
union() {
|
||||||
|
for (c = col_gaps) {
|
||||||
|
for (r = row_gaps) {
|
||||||
|
x_pos = plate_start_x + margin + (c * switch_spacing);
|
||||||
|
y_pos_flat = margin + (r * switch_spacing);
|
||||||
|
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;
|
||||||
|
recess_w = plate_w + (plate_slack * 2);
|
||||||
|
recess_h = plate_h + (plate_slack * 2);
|
||||||
|
|
||||||
|
union() {
|
||||||
|
difference() {
|
||||||
|
// 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]]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Internal Tub
|
||||||
|
translate([wall_thickness, wall_thickness, floor_thickness])
|
||||||
|
cube([case_w - (wall_thickness * 2), case_h - (wall_thickness * 2), back_height + 20]);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// Clips (Reduced number for smaller case)
|
||||||
|
for (i = [0:2]) {
|
||||||
|
z_front = (front_height - recess_depth) + clip_z_offset;
|
||||||
|
translate([wall_thickness + (case_w - wall_thickness*2)/4 * (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)/4 * (i+1) - clip_width/2, y_back_wall - clip_depth * 0.5, z_back_clip])
|
||||||
|
angled_clip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Final Render (Single Part) ---
|
||||||
|
difference() {
|
||||||
|
case_full_geometry();
|
||||||
|
usb_cutout();
|
||||||
|
}
|
||||||
|
controller_holder();
|
||||||
46
keycaps_numpad.sh
Executable file
46
keycaps_numpad.sh
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
labels=(
|
||||||
|
"1" "2" "3" "4" "5" "6" "7" "8" "9" "Num" "/" "*" "-" "." "Esc" "F1" "F2" "F3"
|
||||||
|
)
|
||||||
|
labels2=(
|
||||||
|
"0"
|
||||||
|
)
|
||||||
|
labels3=(
|
||||||
|
"+" "Ent"
|
||||||
|
)
|
||||||
|
|
||||||
|
mkdir -p keycaps_numpad_stl
|
||||||
|
|
||||||
|
echo "Starting STL generation..."
|
||||||
|
|
||||||
|
for label in "${labels[@]}"; do
|
||||||
|
echo "Generating: $label.stl"
|
||||||
|
openscad \
|
||||||
|
-o "keycaps_numpad_stl/$label.stl" \
|
||||||
|
-D "legend_char=\"$label\"" \
|
||||||
|
-D "legend_size=5" \
|
||||||
|
./xda_keycap.scad
|
||||||
|
done
|
||||||
|
|
||||||
|
for label in "${labels2[@]}"; do
|
||||||
|
echo "Generating: $label.stl"
|
||||||
|
openscad \
|
||||||
|
-o "keycaps_numpad_stl/$label.stl" \
|
||||||
|
-D "legend_char=\"$label\"" \
|
||||||
|
-D "legend_size=5" \
|
||||||
|
-D "u_count_x=2" \
|
||||||
|
./xda_keycap.scad
|
||||||
|
done
|
||||||
|
|
||||||
|
for label in "${labels3[@]}"; do
|
||||||
|
echo "Generating: $label.stl"
|
||||||
|
openscad \
|
||||||
|
-o "keycaps_numpad_stl/$label.stl" \
|
||||||
|
-D "legend_char=\"$label\"" \
|
||||||
|
-D "legend_size=5" \
|
||||||
|
-D "u_count_y=2" \
|
||||||
|
./xda_keycap.scad
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Done! Check the 'keycaps_numpad_stl' folder."
|
||||||
159
xda_keycap.scad
Normal file
159
xda_keycap.scad
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
// --- Parameters ---
|
||||||
|
$fn = 512;
|
||||||
|
|
||||||
|
// Key Dimensions
|
||||||
|
u_count_x = 1;
|
||||||
|
u_count_y = 1;
|
||||||
|
u_pitch = 19.05;
|
||||||
|
|
||||||
|
width_base_1u = 18.2;
|
||||||
|
width_top_1u = 14.5;
|
||||||
|
height = 9.0;
|
||||||
|
corner_radius = 2.5;
|
||||||
|
|
||||||
|
// Engraving Settings
|
||||||
|
legend_char = "A";
|
||||||
|
legend_depth = 0.5;
|
||||||
|
legend_size = 5;
|
||||||
|
|
||||||
|
// Calculated Dimensions
|
||||||
|
is_2u = (u_count_x >= 2 || u_count_y >= 2);
|
||||||
|
w_base = width_base_1u + (u_count_x - 1) * u_pitch;
|
||||||
|
h_base = width_base_1u + (u_count_y - 1) * u_pitch;
|
||||||
|
w_top = width_top_1u + (u_count_x - 1) * u_pitch;
|
||||||
|
h_top = width_top_1u + (u_count_y - 1) * u_pitch;
|
||||||
|
|
||||||
|
// Dish (Only for 1u)
|
||||||
|
dish_depth = 1;
|
||||||
|
dish_radius = 45;
|
||||||
|
|
||||||
|
// Stem & Stabilizer Dimensions
|
||||||
|
stem_h = 5.0;
|
||||||
|
stem_outer_d = 5.5;
|
||||||
|
cross_l = 4.1;
|
||||||
|
cross_w1 = 1.25;
|
||||||
|
cross_w2 = 1.15;
|
||||||
|
stab_dist = 23.85;
|
||||||
|
|
||||||
|
// Supports
|
||||||
|
support_depth = 5;
|
||||||
|
|
||||||
|
// --- Modules ---
|
||||||
|
|
||||||
|
module rounded_rectangle(w, h, z, r) {
|
||||||
|
hull() {
|
||||||
|
translate([r, r, 0]) cylinder(r=r, h=z);
|
||||||
|
translate([w - r, r, 0]) cylinder(r=r, h=z);
|
||||||
|
translate([r, h - r, 0]) cylinder(r=r, h=z);
|
||||||
|
translate([w - r, h - r, 0]) cylinder(r=r, h=z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module xda_body() {
|
||||||
|
difference() {
|
||||||
|
hull() {
|
||||||
|
rounded_rectangle(w_base, h_base, 0.1, corner_radius);
|
||||||
|
translate([(w_base - w_top)/2, (h_base - h_top)/2, height - 0.1])
|
||||||
|
rounded_rectangle(w_top, h_top, 0.1, corner_radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_2u) {
|
||||||
|
translate([w_base/2, h_base/2, height + dish_radius - dish_depth])
|
||||||
|
sphere(r=dish_radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
translate([0,0,-0.5])
|
||||||
|
hull() {
|
||||||
|
translate([1.5, 1.5, 0])
|
||||||
|
rounded_rectangle(w_base - 3, h_base - 3, 0.1, corner_radius/2);
|
||||||
|
translate([(w_base - w_top)/2 + 1.5, (h_base - h_top)/2 + 1.5, height - 2.5])
|
||||||
|
rounded_rectangle(w_top - 3, h_top - 3, 0.1, corner_radius/2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module single_stem(pos_x, pos_y) {
|
||||||
|
translate([pos_x, pos_y, 0]) {
|
||||||
|
intersection() {
|
||||||
|
cylinder(d=stem_outer_d, h=height - (is_2u ? 1.0 : dish_depth) - 0.5);
|
||||||
|
difference() {
|
||||||
|
cylinder(d=stem_outer_d, h=height);
|
||||||
|
translate([0, 0, -0.1]) {
|
||||||
|
cube([cross_l, cross_w1, stem_h * 2], center=true);
|
||||||
|
cube([cross_w2, cross_l, stem_h * 2], center=true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module all_stems() {
|
||||||
|
center_x = w_base / 2;
|
||||||
|
center_y = h_base / 2;
|
||||||
|
single_stem(center_x, center_y);
|
||||||
|
if (u_count_x >= 2) {
|
||||||
|
single_stem(center_x - stab_dist/2, center_y);
|
||||||
|
single_stem(center_x + stab_dist/2, center_y);
|
||||||
|
}
|
||||||
|
if (u_count_y >= 2) {
|
||||||
|
single_stem(center_x, center_y - stab_dist/2);
|
||||||
|
single_stem(center_x, center_y + stab_dist/2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module side_engraving() {
|
||||||
|
// Calculate angle based on 1u profile so text sits flush on the slope
|
||||||
|
side_angle = atan((width_base_1u - width_top_1u) / (2 * height));
|
||||||
|
// Offset slightly from the bottom edge
|
||||||
|
y_offset = (width_base_1u - width_top_1u) / 4;
|
||||||
|
|
||||||
|
translate([w_base/2, y_offset, height/2])
|
||||||
|
rotate([90 - side_angle, 0, 0])
|
||||||
|
translate([0, 0, -legend_depth + 0.1])
|
||||||
|
linear_extrude(height = legend_depth + 0.5) {
|
||||||
|
text(legend_char, size = legend_size,
|
||||||
|
halign = "center", valign = "center",
|
||||||
|
font = "Overpass:style=Bold");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module support_rib() {
|
||||||
|
rib_thickness = 1.2;
|
||||||
|
wall_offset = 1.5;
|
||||||
|
stem_radius = stem_outer_d / 2;
|
||||||
|
// Inner wall starts at (width_base_1u/2 - wall_offset)
|
||||||
|
rib_length = (width_base_1u / 2) - wall_offset - stem_radius - 0.1;
|
||||||
|
translate([width_base_1u/2, width_base_1u/2, 0]) {
|
||||||
|
translate([stem_radius- 0.1, -rib_thickness/2, support_depth])
|
||||||
|
cube([rib_length, rib_thickness, height - dish_depth - support_depth]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module supports() {
|
||||||
|
union() {
|
||||||
|
support_rib();
|
||||||
|
rotate([0, 0, 180])
|
||||||
|
translate([-width_base_1u, -width_base_1u, 0])
|
||||||
|
support_rib();
|
||||||
|
rotate([0, 0, 90])
|
||||||
|
translate([0, -width_base_1u, 0])
|
||||||
|
support_rib();
|
||||||
|
rotate([0, 0, 270])
|
||||||
|
translate([-width_base_1u, 0, 0])
|
||||||
|
support_rib();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final Assembly
|
||||||
|
difference() {
|
||||||
|
union() {
|
||||||
|
xda_body();
|
||||||
|
all_stems();
|
||||||
|
// Ribs only for 1u
|
||||||
|
if (!is_2u) {
|
||||||
|
supports();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
side_engraving();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user