summaryrefslogtreecommitdiff
path: root/triumphboard/triumph_board.scad
diff options
context:
space:
mode:
authorDustin Walker <dwalker@dwalker.xyz>2026-09-07 21:55:22 -0400
committerDustin Walker <dwalker@dwalker.xyz>2026-09-07 21:55:22 -0400
commit1e232ebc071e198590aa9003fb273c61462969a5 (patch)
tree90a489291d4520ac788d1b55dd96c26e585a108b /triumphboard/triumph_board.scad
initial commit
Diffstat (limited to 'triumphboard/triumph_board.scad')
-rw-r--r--triumphboard/triumph_board.scad142
1 files changed, 142 insertions, 0 deletions
diff --git a/triumphboard/triumph_board.scad b/triumphboard/triumph_board.scad
new file mode 100644
index 0000000..11f5c5a
--- /dev/null
+++ b/triumphboard/triumph_board.scad
@@ -0,0 +1,142 @@
+/*
+ Double-sided magnetic board for TRIUMPH! (15 mm figures)
+
+ All dimensions are millimetres. The default exposed playing area is
+ 960 x 640 mm: 24 x 16 standard 40 mm TRIUMPH! base widths.
+
+ Construction represented here:
+ - a 24 gauge cold-rolled carbon-steel core;
+ - 30 mm wide wooden frames on both faces;
+ - 30 mm total finished board depth, with the steel centred;
+ - 10 mm square blind corner holes in the top frame only;
+ - full-length short-end pieces, with long-edge pieces between them.
+
+ The steel extends below the whole frame. This preserves a regulation-size
+ exposed playing area and gives the blind holes a metal stop.
+*/
+
+// ---------- User parameters ----------
+
+playing_length = 960; // long dimension of exposed gaming area
+playing_width = 640; // short dimension of exposed gaming area
+frame_width = 30; // wooden border width in plan
+steel_thickness = 0.607; // nominal 24 ga carbon steel (0.0239 in)
+finished_depth = 60 + steel_thickness; // total board thickness, including steel
+
+peg_hole_size = 10; // square blind hole width
+peg_hole_inset = 10; // from each outside edge to the hole edge
+
+// "assembled", "exploded", "steel", "top_frame", "bottom_frame",
+// "short_end_piece", or "long_edge_piece"
+display = "assembled";
+exploded_gap = 25;
+
+// Rendering colors only
+wood_color = [0.58, 0.34, 0.16];
+steel_color = [0.50, 0.54, 0.58];
+
+// ---------- Derived dimensions ----------
+
+board_length = playing_length + 2 * frame_width;
+board_width = playing_width + 2 * frame_width;
+wood_face_depth = (finished_depth - steel_thickness) / 2;
+
+steel_bottom = -steel_thickness / 2;
+steel_top = steel_thickness / 2;
+bottom_wood_bottom = -finished_depth / 2;
+top_wood_bottom = steel_top;
+
+epsilon = 0.02;
+
+assert(playing_length > 0 && playing_width > 0,
+ "The playing-area dimensions must be positive.");
+assert(frame_width > 0 && finished_depth > steel_thickness,
+ "The frame must be wider than zero and deeper than the steel.");
+assert(peg_hole_size > 0 && 2 * peg_hole_inset + peg_hole_size <= frame_width,
+ "Corner holes must fit completely inside the frame width.");
+
+// ---------- Components ----------
+
+module steel_core(z = steel_bottom) {
+ color(steel_color)
+ translate([0, 0, z])
+ cube([board_length, board_width, steel_thickness]);
+}
+
+// Full-side pieces at the two short ends of the rectangular board.
+module short_end_rails(height) {
+ cube([frame_width, board_width, height]);
+ translate([board_length - frame_width, 0, 0])
+ cube([frame_width, board_width, height]);
+}
+
+// Long-edge pieces occupy the remaining distance between the end pieces.
+module long_edge_rails(height) {
+ translate([frame_width, 0, 0])
+ cube([playing_length, frame_width, height]);
+ translate([frame_width, board_width - frame_width, 0])
+ cube([playing_length, frame_width, height]);
+}
+
+module wood_frame_blank(height) {
+ union() {
+ short_end_rails(height);
+ long_edge_rails(height);
+ }
+}
+
+module corner_holes(height) {
+ hole_xy = [
+ [peg_hole_inset, peg_hole_inset],
+ [board_length - peg_hole_inset - peg_hole_size, peg_hole_inset],
+ [peg_hole_inset, board_width - peg_hole_inset - peg_hole_size],
+ [board_length - peg_hole_inset - peg_hole_size,
+ board_width - peg_hole_inset - peg_hole_size]
+ ];
+
+ for (p = hole_xy)
+ translate([p[0], p[1], -epsilon])
+ cube([peg_hole_size, peg_hole_size, height + 2 * epsilon]);
+}
+
+module wood_frame(height, drilled = false) {
+ color(wood_color)
+ difference() {
+ wood_frame_blank(height);
+ if (drilled)
+ corner_holes(height);
+ }
+}
+
+module assembled_board(explode = 0) {
+ // Sheet remains centred; the two frames move apart in exploded mode.
+ steel_core();
+
+ translate([0, 0, top_wood_bottom + explode])
+ wood_frame(wood_face_depth, drilled = true);
+
+ translate([0, 0, bottom_wood_bottom - explode])
+ wood_frame(wood_face_depth, drilled = false);
+}
+
+// ---------- Display / export selector ----------
+
+if (display == "assembled") {
+ assembled_board();
+} else if (display == "exploded") {
+ assembled_board(exploded_gap);
+} else if (display == "steel") {
+ translate([0, 0, -steel_bottom]) steel_core();
+} else if (display == "top_frame") {
+ wood_frame(wood_face_depth, drilled = true);
+} else if (display == "bottom_frame") {
+ wood_frame(wood_face_depth, drilled = false);
+} else if (display == "short_end_piece") {
+ // One representative full-side rail, placed at the origin for export.
+ color(wood_color) cube([frame_width, board_width, wood_face_depth]);
+} else if (display == "long_edge_piece") {
+ // One representative infill rail, placed at the origin for export.
+ color(wood_color) cube([playing_length, frame_width, wood_face_depth]);
+} else {
+ assert(false, str("Unknown display mode: ", display));
+}