642 lines
23 KiB
OpenSCAD
642 lines
23 KiB
OpenSCAD
/*
|
|
* 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_length = 12.7;
|
|
insert_flange_diameter = 9.525;
|
|
insert_flange_seat_diameter = 10.5;
|
|
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(insert_flange_seat_diameter > insert_flange_diameter,
|
|
"Insert flange relief must provide clearance around the brass flange");
|
|
assert(plate_height >= insert_length + insert_flange_depth,
|
|
"Insert length and flange relief exceed the boss 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 + perimeter_wall
|
|
< socket_mount_x - insert_boss_diameter / 2,
|
|
"Socket key wall 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 + perimeter_wall
|
|
< grid_pitch - insert_boss_diameter / 2,
|
|
"Socket key wall 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];
|
|
corner_insert_points = concat(
|
|
[for (x = [grid_xs[0], grid_xs[len(grid_xs) - 1]]) [x, rear_foot_ys]],
|
|
[for (x = [grid_xs[0], grid_xs[len(grid_xs) - 1]])
|
|
[x, grid_ys[len(grid_ys) - 1]]]
|
|
);
|
|
socket_insert_points = [
|
|
for (x = socket_hole_xs, y = socket_hole_ys) [x, y]
|
|
];
|
|
underside_insert_points = concat(corner_insert_points, socket_insert_points);
|
|
topside_insert_points = [
|
|
for (x = grid_xs, y = grid_ys)
|
|
if (!(y == grid_ys[len(grid_ys) - 1]
|
|
&& (x == grid_xs[0] || x == grid_xs[len(grid_xs) - 1])))
|
|
[x, y]
|
|
];
|
|
all_insert_points = concat(
|
|
topside_insert_points,
|
|
underside_insert_points
|
|
);
|
|
|
|
assert(len(topside_insert_points) == 62,
|
|
"The accessory grid must contain 62 top-installed inserts");
|
|
assert(len(underside_insert_points) == 8,
|
|
"Four corner and four socket inserts must install from the underside");
|
|
|
|
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, 0, 0])
|
|
square([rib_width, plate_depth]);
|
|
for (y = concat([datum_y], grid_ys))
|
|
translate([0, y - rib_width / 2, 0])
|
|
square([plate_width, rib_width]);
|
|
|
|
// A full-depth wall supports the locator independently of the
|
|
// surrounding grid ribs. The fitted slot is subtracted later.
|
|
offset(delta = perimeter_wall)
|
|
socket_key_2d(socket_key_clearance);
|
|
|
|
translate([corner_radius, rear_foot_ys - rib_width / 2, 0])
|
|
square([plate_width - 2 * corner_radius, rib_width]);
|
|
// Carry both short socket-bay ribs through to the rear wall.
|
|
for (x = socket_hole_xs)
|
|
translate([x - rib_width / 2, 0, 0])
|
|
square([rib_width, datum_y]);
|
|
|
|
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 insert_pattern_2d(diameter, points = all_insert_points) {
|
|
for (p = 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(insert_flange_seat_diameter,
|
|
topside_insert_points);
|
|
insert_pattern_2d(insert_pilot_diameter,
|
|
underside_insert_points);
|
|
socket_key_2d(socket_key_clearance);
|
|
}
|
|
}
|
|
|
|
module base_layer_2d(outline = false,
|
|
topside_hole_diameter = insert_pilot_diameter,
|
|
underside_hole_diameter = insert_pilot_diameter,
|
|
artwork = false) {
|
|
difference() {
|
|
if (outline) plate_outline_2d();
|
|
else underside_structure_2d();
|
|
insert_pattern_2d(topside_hole_diameter, topside_insert_points);
|
|
insert_pattern_2d(underside_hole_diameter, underside_insert_points);
|
|
// 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;
|
|
artwork_bottom = plate_height - inlay_depth;
|
|
topside_flange_bottom = plate_height - insert_flange_depth;
|
|
top_skin_split = (underside_top + topside_flange_bottom) / 2;
|
|
|
|
if (index == 0)
|
|
linear_extrude(height = insert_flange_depth)
|
|
base_layer_2d(false, insert_pilot_diameter,
|
|
insert_flange_seat_diameter);
|
|
else if (index == 1)
|
|
translate([0, 0, insert_flange_depth])
|
|
linear_extrude(height = underside_top - insert_flange_depth)
|
|
base_layer_2d(false);
|
|
else if (index == 2)
|
|
translate([0, 0, underside_top])
|
|
linear_extrude(height = top_skin_split - underside_top)
|
|
base_layer_2d(true);
|
|
else if (index == 3)
|
|
translate([0, 0, top_skin_split])
|
|
linear_extrude(height = topside_flange_bottom - top_skin_split)
|
|
base_layer_2d(true);
|
|
else if (index == 4)
|
|
translate([0, 0, topside_flange_bottom])
|
|
linear_extrude(height = artwork_bottom - topside_flange_bottom)
|
|
base_layer_2d(true, insert_flange_seat_diameter,
|
|
insert_pilot_diameter);
|
|
else if (index == 5)
|
|
translate([0, 0, artwork_bottom])
|
|
linear_extrude(height = inlay_depth)
|
|
base_layer_2d(true, insert_flange_seat_diameter,
|
|
insert_pilot_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_seat_diameter);
|
|
translate([x, 56, -eps])
|
|
cylinder(h = coupon_boss_height - insert_flange_depth + eps,
|
|
d = pilot);
|
|
}
|
|
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));
|