/* * Parametric copy-stand base for a Bambu Lab X2D. * Service coordinates: X left-to-right, Y rear-to-front, Z bottom-to-top. * The grid origin is at plate Y=datum_y. The socket flange's straight front * edge is 0.5 inch forward of that origin, while the rail front face is at 0. */ /* [Output] */ part = "assembly"; // [assembly,base,base_black,base_segment,base_black_segment,base_accent,socket,coupon,coupon_black,coupon_key,coupon_accent] segment_index = 0; // [0:1:5] show_references = true; /* [Base] */ plate_width = 203.2; plate_depth = 248; plate_height = 15; corner_radius = 12.7; datum_y = 26; top_skin = 3.2; perimeter_wall = 3.2; rib_width = 2.4; /* [Grid inlay] */ grid_pitch = 25.4; grid_columns = 8; grid_rows = 8; grid_line_width = 0.8; inlay_depth = 0.6; inlay_clearance = 0.08; /* [McMaster 97171A240 inserts] */ insert_pilot_diameter = 8.1; insert_body_length = 9.525; insert_flange_diameter = 12.9; insert_flange_depth = 1.35; screw_clearance_diameter = 6.8; insert_boss_diameter = 20; /* [Feet] */ foot_pad_diameter = 19; foot_pad_height = 8.5; foot_stud_length = 17.8; foot_nut_diameter = 24.4; foot_nut_height = 6; /* [Removable 2020 socket] */ rail_size = 20; rail_clearance = 0.05; socket_capture = 50; socket_front_y = grid_pitch / 2; socket_rear_y = -datum_y; socket_flange_size = [110, socket_front_y - socket_rear_y, 10]; socket_rail_front_y = 0; socket_key_axis_y = -0.05; // Fixed to the existing base locator slot. socket_key_bottom_clearance = 0.2; socket_key_size = [52, 22, plate_height - socket_key_bottom_clearance]; socket_key_clearance = 0.25; socket_side_wall = 6; socket_end_wall = 2.7; socket_mount_x = 45; socket_mount_ys = [-16, 3]; socket_mount_tool_diameter = 19; socket_gusset_outer_x = 35; socket_gusset_y_span = [-23, 10]; socket_gusset_height = 32; rail_screw_diameter = 5.5; rail_screw_head_diameter = 9.5; rail_screw_head_depth = 3; rail_tool_diameter = 14; rail_screw_heights = [15, 40]; /* [Calibration] */ coupon_insert_diameters = [7.70, 7.85, 8.00, 8.15, 8.30]; coupon_rail_gaps = [20.0, 20.1, 20.2, 20.3]; $fn = $preview ? 36 : 72; eps = 0.02; assert(plate_width <= 256 && plate_depth <= 256, "Base exceeds the X2D main-nozzle build area"); assert(plate_height >= 13.462, "Insert bosses violate McMaster's 0.530-inch minimum material thickness"); assert(top_skin > inlay_depth, "Top skin must be thicker than the inlay"); assert(grid_columns == 8 && grid_rows == 8, "This release's shared front-foot geometry expects an 8 x 8 grid"); assert(socket_key_size[0] / 2 + socket_key_clearance < socket_mount_x - insert_boss_diameter / 2, "Socket key slot overlaps a full-depth mounting-insert boss"); assert(socket_gusset_outer_x < socket_mount_x - socket_mount_tool_diameter / 2, "Socket gusset enters a flange-screw tool-access envelope"); assert(socket_front_y == grid_pitch / 2, "Socket flange reference face must remain at the half-inch grid coordinate"); assert(socket_rail_axis_y() + (rail_size + 2 * rail_clearance) / 2 == socket_rail_front_y, "Rail front face must remain at the grid origin"); assert(socket_rear_y == -datum_y, "Socket flange must reach the rear edge of the base"); assert(socket_mount_x + socket_mount_tool_diameter / 2 <= socket_flange_size[0] / 2, "Socket washer envelope exceeds the flange width"); assert(socket_mount_ys[0] - socket_mount_tool_diameter / 2 >= socket_rear_y && socket_mount_ys[len(socket_mount_ys) - 1] + socket_mount_tool_diameter / 2 <= socket_front_y, "Socket washer envelope exceeds the flange depth"); assert(socket_key_axis_y + socket_key_size[1] / 2 + socket_key_clearance < grid_pitch - insert_boss_diameter / 2, "Socket key slot overlaps the first grid-row insert bosses"); assert(socket_mount_ys[len(socket_mount_ys) - 1] + insert_boss_diameter / 2 < grid_pitch - insert_boss_diameter / 2, "Socket mounting bosses overlap the first grid-row bosses"); assert(socket_key_size[2] == plate_height - socket_key_bottom_clearance, "Socket tongue must leave 0.2 mm clearance above the base underside"); grid_xs = [for (i = [0 : grid_columns - 1]) plate_width / 2 + (i - (grid_columns - 1) / 2) * grid_pitch]; grid_ys = [for (i = [1 : grid_rows]) datum_y + i * grid_pitch]; rear_foot_ys = corner_radius; socket_hole_xs = [plate_width / 2 - socket_mount_x, plate_width / 2 + socket_mount_x]; socket_hole_ys = [for (y = socket_mount_ys) datum_y + y]; all_insert_points = concat( [for (x = grid_xs, y = grid_ys) [x, y]], [for (x = [grid_xs[0], grid_xs[len(grid_xs) - 1]]) [x, rear_foot_ys]], [for (x = socket_hole_xs, y = socket_hole_ys) [x, y]] ); module rounded_rect_2d(size, radius) { offset(r = radius) square([size[0] - 2 * radius, size[1] - 2 * radius], center = true); } module rounded_prism(size, radius) { linear_extrude(height = size[2]) rounded_rect_2d([size[0], size[1]], radius); } module plate_outline_2d(inset = 0) { translate([plate_width / 2, plate_depth / 2]) offset(delta = -inset) rounded_rect_2d([plate_width, plate_depth], corner_radius); } module insert_boss_at(x, y) { translate([x, y, 0]) cylinder(h = plate_height, d = insert_boss_diameter); } module insert_positive_bosses() { for (p = all_insert_points) insert_boss_at(p[0], p[1]); } module underside_structure_2d() { intersection() { plate_outline_2d(); union() { difference() { plate_outline_2d(); plate_outline_2d(perimeter_wall); } for (x = grid_xs) translate([x - rib_width / 2, corner_radius, 0]) square([rib_width, plate_depth - 2 * corner_radius]); for (y = concat([datum_y], grid_ys)) translate([corner_radius, y - rib_width / 2, 0]) square([plate_width - 2 * corner_radius, rib_width]); translate([corner_radius, rear_foot_ys - rib_width / 2, 0]) square([plate_width - 2 * corner_radius, rib_width]); for (x = socket_hole_xs) translate([x - rib_width / 2, corner_radius, 0]) square([rib_width, datum_y - corner_radius]); for (p = all_insert_points) translate(p) circle(d = insert_boss_diameter); } } } module base_structure_positive() { union() { linear_extrude(height = plate_height - top_skin) underside_structure_2d(); translate([0, 0, plate_height - top_skin]) linear_extrude(height = top_skin) plate_outline_2d(); } } module one_insert_cutout(x, y, pilot = insert_pilot_diameter) { translate([x, y, -eps]) cylinder(h = insert_flange_depth + eps, d = insert_flange_diameter); translate([x, y, insert_flange_depth - eps]) cylinder(h = insert_body_length + 2 * eps, d = pilot); translate([x, y, insert_flange_depth + insert_body_length - eps]) cylinder( h = plate_height - insert_flange_depth - insert_body_length + 2 * eps, d = screw_clearance_diameter ); } module all_insert_cutouts() { union() { for (p = all_insert_points) translate([p[0], p[1], -eps]) cylinder(h = insert_flange_depth + eps, d = insert_flange_diameter); } union() { for (p = all_insert_points) translate([p[0], p[1], insert_flange_depth - eps]) cylinder(h = insert_body_length + 2 * eps, d = insert_pilot_diameter); } union() { for (p = all_insert_points) translate([p[0], p[1], insert_flange_depth + insert_body_length - eps]) cylinder( h = plate_height - insert_flange_depth - insert_body_length + 2 * eps, d = screw_clearance_diameter ); } } module insert_pattern_2d(diameter) { for (p = all_insert_points) translate(p) circle(d = diameter); } module socket_key_2d(clearance = 0) { translate([plate_width / 2, datum_y + socket_key_axis_y]) rounded_rect_2d( [socket_key_size[0] + 2 * clearance, socket_key_size[1] + 2 * clearance], 2 + clearance ); } module grid_artwork_2d(clearance = 0) { intersection() { plate_outline_2d(); offset(delta = clearance) union() { for (x = grid_xs) translate([x - grid_line_width / 2, datum_y]) square([grid_line_width, plate_depth - datum_y]); for (y = concat([datum_y], grid_ys)) translate([0, y - grid_line_width / 2]) square([plate_width, grid_line_width]); } } } module accent_service() { translate([0, 0, plate_height - inlay_depth]) linear_extrude(height = inlay_depth) difference() { grid_artwork_2d(); insert_pattern_2d(screw_clearance_diameter); socket_key_2d(socket_key_clearance); } } module base_layer_2d(outline = false, hole_diameter, artwork = false) { difference() { if (outline) plate_outline_2d(); else underside_structure_2d(); insert_pattern_2d(hole_diameter); // The locator is a through-slot so no unsupported bridge is printed // against the textured build plate. socket_key_2d(socket_key_clearance); if (artwork) grid_artwork_2d(inlay_clearance); } } module base_segment_service(index, with_artwork = false) { underside_top = plate_height - top_skin; body_top = insert_flange_depth + insert_body_length; artwork_bottom = plate_height - inlay_depth; top_split = (underside_top + artwork_bottom) / 2; if (index == 0) linear_extrude(height = insert_flange_depth) base_layer_2d(false, insert_flange_diameter); else if (index == 1) translate([0, 0, insert_flange_depth]) linear_extrude(height = insert_body_length) base_layer_2d(false, insert_pilot_diameter); else if (index == 2) translate([0, 0, body_top]) linear_extrude(height = underside_top - body_top) base_layer_2d(false, screw_clearance_diameter); else if (index == 3) translate([0, 0, underside_top]) linear_extrude(height = top_split - underside_top) base_layer_2d(true, screw_clearance_diameter); else if (index == 4) translate([0, 0, top_split]) linear_extrude(height = artwork_bottom - top_split) base_layer_2d(true, screw_clearance_diameter); else if (index == 5) translate([0, 0, artwork_bottom]) linear_extrude(height = inlay_depth) base_layer_2d(true, screw_clearance_diameter, with_artwork); else assert(false, str("Unknown base segment: ", index)); } module base_layered_service(with_artwork = false) { for (i = [0 : 5]) base_segment_service(i, with_artwork); } module base_black_service() { base_layered_service(true); } module base_complete_service() { base_layered_service(false); } module print_orient_base() { translate([0, plate_depth, plate_height]) rotate([180, 0, 0]) children(); } module socket_flange_2d() { translate([0, (socket_front_y + socket_rear_y) / 2]) rounded_rect_2d([socket_flange_size[0], socket_flange_size[1]], 3); } function socket_outer_y() = rail_size + 2 * rail_clearance + 2 * socket_end_wall; function socket_rail_axis_y() = socket_rail_front_y - (rail_size + 2 * rail_clearance) / 2; module socket_key_local_2d() { translate([0, socket_key_axis_y]) rounded_rect_2d([socket_key_size[0], socket_key_size[1]], 2); } module socket_gusset(side = 1) { cavity = rail_size + 2 * rail_clearance; collar_half_x = (cavity + 2 * socket_side_wall) / 2; collar_y_min = socket_rail_axis_y() - socket_outer_y() / 2; collar_y_depth = socket_outer_y(); near_x = side > 0 ? collar_half_x : -collar_half_x - 2; outer_x = side > 0 ? socket_gusset_outer_x - 2 : -socket_gusset_outer_x; gusset_depth = socket_gusset_y_span[1] - socket_gusset_y_span[0]; // The full-height attachment follows the collar's front and rear edges, // while the low outer footprint retains the proven flange support area. // Tool-access bores below remove material around both fastener families. hull() { translate([near_x, collar_y_min, socket_flange_size[2]]) cube([2, collar_y_depth, socket_gusset_height]); translate([outer_x, socket_gusset_y_span[0], socket_flange_size[2]]) cube([2, gusset_depth, 2]); } } module socket_positive() { cavity = rail_size + 2 * rail_clearance; outer_x = cavity + 2 * socket_side_wall; outer_y = socket_outer_y(); translate([0, 0, -socket_key_size[2]]) linear_extrude(height = socket_key_size[2] + eps) socket_key_local_2d(); linear_extrude(height = socket_flange_size[2]) socket_flange_2d(); translate([-outer_x / 2, socket_rail_axis_y() - outer_y / 2, socket_flange_size[2]]) cube([outer_x, outer_y, socket_capture]); socket_gusset(-1); socket_gusset(1); } module socket_cutouts() { cavity = rail_size + 2 * rail_clearance; collar_half_x = (cavity + 2 * socket_side_wall) / 2; translate([-cavity / 2, socket_rail_axis_y() - cavity / 2, socket_flange_size[2]]) cube([cavity, cavity, socket_capture + eps]); // Four base mounting holes with clear vertical driver access. for (x = [-socket_mount_x, socket_mount_x], y = socket_mount_ys) { translate([x, y, -socket_key_size[2] - eps]) cylinder(h = socket_flange_size[2] + socket_key_size[2] + 2 * eps, d = screw_clearance_diameter); translate([x, y, socket_flange_size[2]]) cylinder(h = socket_gusset_height + eps, d = socket_mount_tool_diameter); } // Two screw levels through both side walls create four M5 clamp points. for (h = rail_screw_heights) { translate([-socket_flange_size[0] / 2, socket_rail_axis_y(), socket_flange_size[2] + h]) rotate([0, 90, 0]) cylinder(h = socket_flange_size[0], d = rail_screw_diameter); translate([-collar_half_x - eps, socket_rail_axis_y(), socket_flange_size[2] + h]) rotate([0, 90, 0]) cylinder(h = rail_screw_head_depth + eps, d = rail_screw_head_diameter); translate([collar_half_x - rail_screw_head_depth, socket_rail_axis_y(), socket_flange_size[2] + h]) rotate([0, 90, 0]) cylinder(h = rail_screw_head_depth + eps, d = rail_screw_head_diameter); // Driver tunnels exist only outside the collar, preserving the side // wall while allowing a socket driver through each buttress. translate([-socket_gusset_outer_x - eps, socket_rail_axis_y(), socket_flange_size[2] + h]) rotate([0, 90, 0]) cylinder(h = socket_gusset_outer_x - collar_half_x + 2 * eps, d = rail_tool_diameter); translate([collar_half_x - eps, socket_rail_axis_y(), socket_flange_size[2] + h]) rotate([0, 90, 0]) cylinder(h = socket_gusset_outer_x - collar_half_x + 2 * eps, d = rail_tool_diameter); } } module socket_service() { difference() { socket_positive(); socket_cutouts(); } } module socket_print() { translate([socket_flange_size[0] / 2, -socket_rear_y, socket_key_size[2]]) socket_service(); } module reference_rail() { color([0.08, 0.08, 0.09, 0.8]) translate([plate_width / 2 - rail_size / 2, datum_y + socket_rail_axis_y() - rail_size / 2, plate_height + socket_flange_size[2]]) cube([rail_size, rail_size, 400]); } module reference_feet() { foot_points = concat( [[grid_xs[0], rear_foot_ys], [grid_xs[len(grid_xs)-1], rear_foot_ys]], [[grid_xs[0], grid_ys[len(grid_ys)-1]], [grid_xs[len(grid_xs)-1], grid_ys[len(grid_ys)-1]]] ); for (p = foot_points) { color([0.05, 0.05, 0.05, 0.8]) { translate([p[0], p[1], -foot_nut_height]) cylinder(h = foot_nut_height, d = foot_nut_diameter); translate([p[0], p[1], -foot_nut_height - foot_pad_height]) cylinder(h = foot_pad_height, d = foot_pad_diameter); } } } // Two-piece calibration coupon, modeled directly in print orientation. coupon_size = [165, 110, 3.2]; coupon_boss_height = 15; coupon_rail_xs = [20, 60, 100, 140]; coupon_rail_y = 23; coupon_fit_center = [82.5, 90]; coupon_fit_outer = [64, 34]; coupon_fit_height = 6; coupon_key_depth = 6; coupon_key_center = [200, 90]; coupon_key_cap = [56, 26, 2]; coupon_key_handle = [40, 6, 8]; assert(coupon_fit_height == coupon_key_depth, "Coupon socket and tongue gauges must have matching depths"); assert(len(coupon_rail_gaps) == len(coupon_rail_xs), "Each rail gauge must have one layout position"); assert(len([for (gap = coupon_rail_gaps) if (gap == rail_size + 2 * rail_clearance) gap]) == 1, "Production rail cavity must appear once in the coupon gauges"); assert(coupon_rail_xs[0] - (coupon_rail_gaps[len(coupon_rail_gaps) - 1] + 2 * socket_side_wall) / 2 >= 0 && coupon_rail_xs[len(coupon_rail_xs) - 1] + (coupon_rail_gaps[len(coupon_rail_gaps) - 1] + 2 * socket_side_wall) / 2 <= coupon_size[0], "Rail gauges exceed the main coupon width"); assert(coupon_fit_center[0] - coupon_fit_outer[0] / 2 >= 0 && coupon_fit_center[0] + coupon_fit_outer[0] / 2 <= coupon_size[0] && coupon_fit_center[1] - coupon_fit_outer[1] / 2 >= 0 && coupon_fit_center[1] + coupon_fit_outer[1] / 2 <= coupon_size[1], "Coupon fit socket exceeds the main coupon outline"); assert(coupon_key_center[0] - coupon_key_cap[0] / 2 > coupon_size[0], "Coupon tongue must remain a physically separate print"); assert(coupon_key_center[0] + coupon_key_cap[0] / 2 <= 256 && coupon_key_center[1] + coupon_key_cap[1] / 2 <= 256, "Coupon and separate tongue exceed the X2D build area"); module coupon_outline_2d() { translate([coupon_size[0] / 2, coupon_size[1] / 2]) rounded_rect_2d([coupon_size[0], coupon_size[1]], 4); } module coupon_accent_2d(clearance = 0) { offset(delta = clearance) translate([8, 6]) square([75, grid_line_width]); } module coupon_positive() { linear_extrude(height = coupon_size[2]) coupon_outline_2d(); for (i = [0 : len(coupon_insert_diameters) - 1]) translate([14 + i * 24, 56, coupon_size[2]]) cylinder(h = coupon_boss_height - coupon_size[2], d = insert_boss_diameter); // Four production-style closed collars, left-to-right 20.0-20.3 mm. for (i = [0 : len(coupon_rail_gaps) - 1]) { gap = coupon_rail_gaps[i]; outer_x = gap + 2 * socket_side_wall; outer_y = gap + 2 * socket_end_wall; translate([coupon_rail_xs[i], coupon_rail_y, coupon_size[2] - eps]) difference() { translate([-outer_x / 2, -outer_y / 2, 0]) cube([outer_x, outer_y, socket_capture + eps]); translate([-gap / 2, -gap / 2, -eps]) cube([gap, gap, socket_capture + 3 * eps]); } } // Full-depth test socket surrounding the production-size through-slot. translate([coupon_fit_center[0], coupon_fit_center[1], coupon_size[2] - eps]) rounded_prism( [coupon_fit_outer[0], coupon_fit_outer[1], coupon_fit_height - coupon_size[2] + eps], 3 ); } module coupon_common_cutouts() { for (i = [0 : len(coupon_insert_diameters) - 1]) { x = 14 + i * 24; pilot = coupon_insert_diameters[i]; translate([x, 56, coupon_boss_height - insert_flange_depth]) cylinder(h = insert_flange_depth + eps, d = insert_flange_diameter); translate([x, 56, coupon_boss_height - insert_flange_depth - insert_body_length]) cylinder(h = insert_body_length + eps, d = pilot); translate([x, 56, -eps]) cylinder(h = coupon_boss_height - insert_flange_depth - insert_body_length + 2 * eps, d = screw_clearance_diameter); } translate([coupon_fit_center[0], coupon_fit_center[1], -eps]) rounded_prism( [socket_key_size[0] + 2 * socket_key_clearance, socket_key_size[1] + 2 * socket_key_clearance, coupon_fit_height + 2 * eps], 2 + socket_key_clearance ); } module coupon_key() { // Separate full-size tongue. A 45-degree stop shoulder remains // support-free, and the vertical rib provides a grip for removal. translate([coupon_key_center[0], coupon_key_center[1], 0]) { rounded_prism( [socket_key_size[0], socket_key_size[1], coupon_key_depth], 2); hull() { translate([0, 0, coupon_key_depth - eps]) rounded_prism( [socket_key_size[0], socket_key_size[1], eps], 2); translate([0, 0, coupon_key_depth + coupon_key_cap[2] - eps]) rounded_prism( [coupon_key_cap[0], coupon_key_cap[1], eps], 3); } translate([0, 0, coupon_key_depth + coupon_key_cap[2] - eps]) rounded_prism( [coupon_key_handle[0], coupon_key_handle[1], coupon_key_handle[2] + eps], 2 ); } } module coupon_black() { difference() { coupon_positive(); coupon_common_cutouts(); translate([0, 0, -eps]) linear_extrude(height = inlay_depth + 2 * eps) coupon_accent_2d(inlay_clearance); } } module coupon_accent() { linear_extrude(height = inlay_depth) coupon_accent_2d(); } module coupon_complete() { union() { difference() { coupon_positive(); coupon_common_cutouts(); } coupon_key(); } } module assembly() { color([0.035, 0.035, 0.04]) base_black_service(); color([0.05, 0.25, 0.8]) accent_service(); color([0.035, 0.035, 0.04]) translate([plate_width / 2, datum_y, plate_height]) socket_service(); if (show_references) { reference_rail(); reference_feet(); } } if (part == "assembly") assembly(); else if (part == "base") print_orient_base() base_complete_service(); else if (part == "base_black") print_orient_base() base_black_service(); else if (part == "base_segment") print_orient_base() base_segment_service(segment_index, false); else if (part == "base_black_segment") print_orient_base() base_segment_service(segment_index, true); else if (part == "base_accent") print_orient_base() accent_service(); else if (part == "socket") socket_print(); else if (part == "coupon") coupon_complete(); else if (part == "coupon_black") coupon_black(); else if (part == "coupon_key") coupon_key(); else if (part == "coupon_accent") coupon_accent(); else assert(false, str("Unknown part: ", part));