Six-Piece Burr Puzzle
The classic interlocking burr: six 2 x 2 x 6 unit sticks with notches cut from their middles, which lock together into a 60 x 60 x 60 mm cross and only come apart in the right order. One piece is a solid key; the other five are described as voxel patterns, a short list of the unit cubes each one loses, and the geometry is generated from those lists. The design is checked in the model itself: the pieces share no volume when assembled, exactly one piece is solid, and the disassembly is proven move by move (the key slides out, then two pieces together, then one, then one, each along a straight path). It is a level-1 burr, so it is a good first puzzle and a nice fidget piece. Set part to print for all six pieces laid flat with their cuts facing up, so there are no overhangs and no supports; print with several perimeters or 100% infill. Parameters: unit size (default 10 mm, so a piece is 20 x 20 x 60 mm), clearance per side (default 0.2 mm, applied to every face and notch; raise it for a looser fit), and which piece or layout to output.
# Six-Piece Burr Puzzle - the classic interlocking burr, six notched 2 x 2 x 6 sticks
#
# Each piece is a 2 x 2 x 6 unit stick with unit cubes cut out of its middle
# four units. A piece is written as a voxel pattern: a list of the cells it
# loses, as three-digit codes xyz (x 0-5 along the stick, y 0-1 across,
# z 0-1 up; the cut cells all lie in x 1-4). The cutter is generated from that
# list by a comprehension, so a piece is one line of data and the geometry
# follows. One piece is the solid key; the other five are notched.
#
# Assembled, the six sticks lie in three orthogonal pairs: the two X sticks
# side by side, the two Y sticks one above the other, the two Z sticks side
# by side, each pair centred on the 6 x 6 x 6 unit cube. Their middles meet
# in a 4 x 4 x 4 core, where every cell of a stick is either kept by that
# stick or given up to a crossing stick. The assertions prove the design:
# the six pieces share no volume (the sum of their volumes equals the volume
# of the assembly), the assembly spans the full 6 x 6 x 6 units, exactly one
# piece is solid, and the puzzle really comes apart: the key slides out along
# its axis, then two pieces together, then one, then one, each along a free
# straight path (the whole swept path is checked against the pieces left).
#
# Solving: push the solid key out, then slide the rest apart as above.
# To assemble, play the sequence backwards: it is a level-1 burr, every move
# is a straight slide.
#
# Print orientation: part = "print" lays all six pieces flat, cut faces up,
# so there are no overhangs and no supports. Print at 100% or thick walls
# with a few perimeters; the fit is tuned with the clearance parameter.
@param choices:["assembled", "print", "piece1", "piece2", "piece3", "piece4", "piece5", "piece6"] desc:"Output: the assembled puzzle, the print layout, or one piece"
part = "assembled"
@param 8..20 step:1 desc:"Unit size: one voxel edge (mm); a piece is 2 x 2 x 6 units"
u = 10
@param 0..0.5 step:0.05 desc:"Clearance per side (mm): each piece is shrunk by this on its sides and in every notch"
clearance = 0.2
# --- Voxel patterns: the cells each piece loses (xyz codes) ---
# Piece 1 is the solid key (no cuts). Pieces 2 and 3 are the same shape (the
# middle cut to half thickness), piece 3 turned over. Pieces 4 and 6 are the
# same shape (one side of the middle removed, plus two cells of the other
# side's top), piece 6 turned a quarter turn; piece 5 is their mirror image.
cut2 = [100, 110, 200, 210, 300, 310, 400, 410]
cut3 = [101, 111, 201, 211, 301, 311, 401, 411]
cut4 = [110, 111, 201, 210, 211, 301, 311, 410, 411]
cut5 = [101, 111, 201, 210, 211, 310, 311, 401, 411]
cut6 = [100, 110, 200, 210, 211, 310, 311, 400, 410]
# --- One stick, in its own frame: x 0..6u along it, y and z 0..2u ---
c = clearance
bar = box (6 * u) (2 * u - 2 * c) (2 * u - 2 * c) at:(3 * u, u, u)
cell = u + 2 * c # a cut cell, grown by the clearance on every side
def cutter(cuts) = union [box cell cell cell at:((k // 100 + 0.5) * u, ((k // 10) % 10 + 0.5) * u, (k % 10 + 0.5) * u) for k in cuts]
def stick(cuts) = bar - cutter(cuts)
p1 = bar
p2 = stick(cut2)
p3 = stick(cut3)
p4 = stick(cut4)
p5 = stick(cut5)
p6 = stick(cut6)
assert p1.solids == 1 and p2.solids == 1 and p3.solids == 1
assert p4.solids == 1 and p5.solids == 1 and p6.solids == 1
assert p4.bbox.x ~ (6 * u) and p4.bbox.y ~ (2 * u - 2 * c) and p4.bbox.z ~ (2 * u - 2 * c)
cell_vol = u ** 3
kept = 6 * 24 - (8 + 8 + 9 + 9 + 9) # cells kept by the six pieces (the lengths of the cut lists)
print "piece volumes (mm3)" p1.volume p2.volume p3.volume p4.volume p5.volume p6.volume
# --- Assembly: X pair (pieces 1, 4), Y pair (2, 3), Z pair (5, 6), cube centred on the origin ---
a1 = p1 | translate (-3 * u) 0 (-u) # X stick, y 0..2u, z -u..u
a4 = p4 | translate (-3 * u) (-2 * u) (-u) # X stick, y -2u..0
a2 = p2 | rotate 0 0 90 | translate u (-3 * u) 0 # Y stick, x -u..u, z 0..2u
a3 = p3 | rotate 0 0 90 | translate u (-3 * u) (-2 * u) # Y stick, z -2u..0
a5 = p5 | rotate 0 -90 0 | translate (2 * u) (-u) (-3 * u) # Z stick, x 0..2u, y -u..u
a6 = p6 | rotate 0 -90 0 | translate 0 (-u) (-3 * u) # Z stick, x -2u..0
asm = union [a1, a2, a3, a4, a5, a6]
sum = p1.volume + p2.volume + p3.volume + p4.volume + p5.volume + p6.volume
# (a) no two pieces share volume: the assembly's volume is the sum of the pieces'
assert asm.volume ~ sum tol:0.01%
# (b) the assembly spans the full 6 x 6 x 6 unit cube
assert asm.bbox.x ~ (6 * u) and asm.bbox.y ~ (6 * u) and asm.bbox.z ~ (6 * u)
# (c) exactly one piece is solid
def solid(p) = if p.volume ~ bar.volume then 1 else 0
assert solid(p1) + solid(p2) + solid(p3) + solid(p4) + solid(p5) + solid(p6) == 1
# (d) the puzzle comes apart: each move is a straight slide of 6 units, and the
# whole swept path is clear of the pieces still in place
def slide(p, dx, dy, dz) = union [p | translate (k * u * dx) (k * u * dy) (k * u * dz) for k in range(7)]
assert clear(slide(a1, 1, 0, 0), union [a2, a3, a4, a5, a6]) # 1. the key slides out along +X
assert clear(slide(a4 + a3, 0, -1, 0), union [a2, a5, a6]) # 2. pieces 4 and 3 slide out together along -Y
assert clear(slide(a5, 1, 0, 0), union [a2, a6]) # 3. piece 5 slides out along +X
assert clear(slide(a2, 1, 0, 0), a6) # 4. piece 2 slides out along +X; piece 6 is free
print "assembled" asm.bbox "volume" asm.volume "- the sticks cover 104 cells, the pieces keep" kept "so" (104 - kept) "single cells inside are empty"
# --- Print layout: six sticks side by side, cut faces up ---
pitch = 2 * u + 10
def flat(p, row) = p | floor | translate 0 (row * pitch) 0
f2 = p2 | rotate 180 0 0 | translate 0 (2 * u) 0 # pieces 2 and 6 are turned over: their cuts face down in the stick frame
f6 = p6 | rotate 180 0 0 | translate 0 (2 * u) 0
$print = union [flat(p1, 0), flat(f2, 1), flat(p3, 2), flat(p4, 3), flat(p5, 4), flat(f6, 5)]
if part == "assembled" then asm
else if part == "print" then $print
else if part == "piece1" then p1
else if part == "piece2" then p2
else if part == "piece3" then p3
else if part == "piece4" then p4
else if part == "piece5" then p5
else p6