littletechbird/breaker-test-pins
Primary injection test pins (tulips) for molded case circuit breakers: hex lug end, tulip neck, all rounded-square body, seven sizes.
Dirac belt (belt trick): a cube tied to anchor pads by ribbons, posed at any moment of a 720 degree untangle.
| 1 | """Dirac belt (the belt trick, or plate trick): a cube tied to fixed anchor pads by elastic ribbons, posed at any |
| 2 | moment of a full 720 degree untangle, with the cube's ribbons never passing through each other or the cube. |
| 3 | |
| 4 | Every point p in space is turned rigidly about the centre by a rotation that depends only on its distance |p| |
| 5 | from the centre: points inside the ball of radius R0 around the cube turn with the cube, points outside R1 stay |
| 6 | still, and each sphere in between turns as one piece. That map is smooth, one-to-one and volume-preserving at |
| 7 | every moment, so ribbons that start apart stay apart and keep their volume. The motion follows Andrew Hanson's |
| 8 | 360 and 720 degree quaternion paths (Visualizing Quaternions, 2006), then a six-ribbon finale. |
| 9 | |
| 10 | Ribbons are B-spline solids fitted through the exact mapped points (top and bottom surfaces, ruled edges and |
| 11 | ends). The cube sits at the origin; ribbon +z points up. Its +x face is marked red so its turn is visible. |
| 12 | """ |
| 13 | import math |
| 14 | |
| 15 | import cadquery as cq |
| 16 | from steve import Model |
| 17 | |
| 18 | time = 24.0 # s Moment in the motion |
| 19 | ribbons = "as in the video" # Ribbons |
| 20 | cube_size = 40 # mm Cube size |
| 21 | ribbon_width = 18 # mm Ribbon width |
| 22 | ribbon_thickness = 0.8 # mm Ribbon thickness |
| 23 | ribbon_length = 108 # mm Ribbon length |
| 24 | |
| 25 | # ---- geometry, in the cube's half-size `a` (the video's units: a = 0.5) ---- |
| 26 | a = cube_size / 2 |
| 27 | W = ribbon_width |
| 28 | TH = ribbon_thickness |
| 29 | L = a + ribbon_length # anchor face distance from the centre |
| 30 | R0 = 1.76 * a # rigid ball: turns with the cube (cube corner is 1.732 a) |
| 31 | R1 = L - 0.3 * a # outside this nothing moves |
| 32 | DELTA = 0.15 # ramp width of the log-radius profile |
| 33 | PAD_HALF = 0.9 * a |
| 34 | PAD_T = 0.2 * a |
| 35 | |
| 36 | # Proportions the fitted ribbon surfaces are checked for (the video: width 0.45, thickness 0.02, length 2.7 x cube) |
| 37 | if not W <= 0.5 * cube_size: |
| 38 | raise ValueError("ribbon_width must be at most 0.5 x cube_size.") |
| 39 | if not TH <= 0.02 * cube_size: |
| 40 | raise ValueError("ribbon_thickness must be at most 0.02 x cube_size.") |
| 41 | if not TH <= W / 4: |
| 42 | raise ValueError("ribbon_thickness must be at most a quarter of ribbon_width.") |
| 43 | if not ribbon_length >= 2.5 * cube_size: |
| 44 | raise ValueError("ribbon_length must be at least 2.5 x cube_size.") |
| 45 | |
| 46 | # Timeline in seconds, as in the 37.5 s video: rest, 360 degree turn (A1), Hanson 360 loop, second turn (A2), |
| 47 | # Hanson 720 untangle, four side ribbons grow, six-ribbon finale loop. |
| 48 | PHASES = [("rest", 0.0, 3.0), ("A1", 3.0, 9.0), ("H360", 9.0, 15.0), ("A2", 15.0, 21.0), |
| 49 | ("H720", 21.0, 28.0), ("grow", 28.0, 29.5), ("coda", 29.5, 37.5)] |
| 50 | |
| 51 | # (name, axis e, width direction u); the ribbon's front (gold) face looks along e x u |
| 52 | RIBBONS = [("pz", (0, 0, 1), (0, -1, 0)), ("nz", (0, 0, -1), (0, 1, 0)), |
| 53 | ("px", (1, 0, 0), (0, 1, 0)), ("nx", (-1, 0, 0), (0, -1, 0)), |
| 54 | ("py", (0, 1, 0), (-1, 0, 0)), ("ny", (0, -1, 0), (1, 0, 0))] |
| 55 | SHOWN = {"as in the video": None, "1": 1, "2": 2, "6": 6}[ribbons] |
| 56 | |
| 57 | |
| 58 | def ease(x): |
| 59 | x = min(max(x, 0.0), 1.0) |
| 60 | return x * x * x * (10 - 15 * x + 6 * x * x) |
| 61 | |
| 62 | |
| 63 | def _E(x): |
| 64 | return 2.5 * x ** 4 - 3 * x ** 5 + x ** 6 # integral of smootherstep |
| 65 | |
| 66 | |
| 67 | def h(r): |
| 68 | """distance from the centre -> path parameter s: 1 inside R0, 0 outside R1, smooth (C3) and monotone""" |
| 69 | rho = min(max(math.log(max(r, 1e-12) / R0) / math.log(R1 / R0), 0.0), 1.0) |
| 70 | d = DELTA |
| 71 | if rho < d: |
| 72 | w = d * _E(rho / d) |
| 73 | elif rho > 1 - d: |
| 74 | w = d / 2 + (1 - 2 * d) + d * (0.5 - _E((1 - rho) / d)) |
| 75 | else: |
| 76 | w = d / 2 + (rho - d) |
| 77 | return 1.0 - w / (1.0 - d) |
| 78 | |
| 79 | |
| 80 | T = min(max(float(time), 0.0), 37.5) |
| 81 | PHASE, U = "coda", 1.0 |
| 82 | for _name, _t0, _t1 in PHASES: |
| 83 | if T <= _t1 + 1e-12: |
| 84 | PHASE, U = _name, ease((T - _t0) / (_t1 - _t0)) |
| 85 | break |
| 86 | |
| 87 | |
| 88 | def qmul(p, q): |
| 89 | aw, ax, ay, az = p |
| 90 | bw, bx, by, bz = q |
| 91 | return (aw * bw - ax * bx - ay * by - az * bz, aw * bx + ax * bw + ay * bz - az * by, |
| 92 | aw * by - ax * bz + ay * bw + az * bx, aw * bz + ax * by - ay * bx + az * bw) |
| 93 | |
| 94 | |
| 95 | def Q(s): |
| 96 | """unit quaternion (w, x, y, z) for path parameter s (0 = anchors, 1 = cube) at this moment""" |
| 97 | if PHASE in ("rest", "grow"): |
| 98 | return (1.0, 0.0, 0.0, 0.0) |
| 99 | if PHASE in ("A1", "A2"): |
| 100 | tau = 0.5 * U if PHASE == "A1" else 0.5 + 0.5 * U |
| 101 | ang = 2 * math.pi * s * tau |
| 102 | return (math.cos(ang), 0.0, 0.0, math.sin(ang)) |
| 103 | if PHASE == "H360": |
| 104 | al = 2 * math.pi * U |
| 105 | c, sn = math.cos(math.pi * s), math.sin(math.pi * s) |
| 106 | return (c, 0.0, sn * math.sin(al), sn * math.cos(al)) |
| 107 | if PHASE == "H720": |
| 108 | b = 0.5 * math.pi * U |
| 109 | c2 = math.cos(2 * math.pi * s) |
| 110 | return (math.sin(b) ** 2 + math.cos(b) ** 2 * c2, 0.0, |
| 111 | math.cos(b) * math.sin(b) * (1 - c2), math.cos(b) * math.sin(2 * math.pi * s)) |
| 112 | hs = 0.5 * math.pi * s # coda: loop of the six-ribbon finale |
| 113 | al = 2 * math.pi * U |
| 114 | q1 = (math.cos(hs), math.sin(hs) * math.cos(al), math.sin(hs) * math.sin(al), 0.0) |
| 115 | q0c = (math.cos(hs), -math.sin(hs), 0.0, 0.0) |
| 116 | return qmul(q1, q0c) |
| 117 | |
| 118 | |
| 119 | def rotate(q, p): |
| 120 | w, vx, vy, vz = q |
| 121 | px, py, pz = p |
| 122 | tx, ty, tz = 2 * (vy * pz - vz * py), 2 * (vz * px - vx * pz), 2 * (vx * py - vy * px) |
| 123 | return (px + w * tx + (vy * tz - vz * ty), py + w * ty + (vz * tx - vx * tz), pz + w * tz + (vx * ty - vy * tx)) |
| 124 | |
| 125 | |
| 126 | def phi(p): |
| 127 | return rotate(Q(h(math.sqrt(p[0] ** 2 + p[1] ** 2 + p[2] ** 2))), p) |
| 128 | |
| 129 | |
| 130 | def grown(index): |
| 131 | """(pad scale, ribbon length fraction) of ribbon `index` at this moment""" |
| 132 | if SHOWN is not None: |
| 133 | return (1.0, 1.0) if index < SHOWN else (0.0, 0.0) |
| 134 | if index < 2: |
| 135 | return 1.0, 1.0 |
| 136 | if T < 28.0: |
| 137 | return 0.0, 0.0 |
| 138 | g = (T - 28.0) / 1.5 |
| 139 | return ease(g / 0.5), ease((g - 0.3) / 0.7) |
| 140 | |
| 141 | |
| 142 | def box_at(centre, e, u, size_e, size_u, size_n): |
| 143 | """box of the given sizes along e, u and n = e x u, centred on `centre`""" |
| 144 | n = (e[1] * u[2] - e[2] * u[1], e[2] * u[0] - e[0] * u[2], e[0] * u[1] - e[1] * u[0]) |
| 145 | o = [centre[i] - (e[i] * size_e + u[i] * size_u + n[i] * size_n) / 2 for i in range(3)] |
| 146 | return cq.Solid.makeBox(size_e, size_u, size_n).moved(cq.Location(cq.Plane(o, e, n))) |
| 147 | |
| 148 | |
| 149 | def ribbon_solid(e, u, frac, nl=240, nw=12): |
| 150 | n = (e[1] * u[2] - e[2] * u[1], e[2] * u[0] - e[0] * u[2], e[0] * u[1] - e[1] * u[0]) |
| 151 | tip = a + frac * (L - a) |
| 152 | |
| 153 | def pt(l, w, k): |
| 154 | return tuple(l * e[i] + w * u[i] + k * n[i] for i in range(3)) |
| 155 | |
| 156 | if frac < 1.0: # growing: only in the grow phase, where nothing has turned yet, so it is a plain slab |
| 157 | return box_at(pt((a + tip) / 2, 0, 0), e, u, tip - a, W, TH) |
| 158 | # length samples are denser in the moving shell R0..R1 (where ribbons stretch) than in the still parts |
| 159 | ls = [] |
| 160 | for i in range(nl + 1): |
| 161 | x = i / nl |
| 162 | ls.append(a + (L - a) * (x - 0.10 * math.sin(2 * math.pi * x) / (2 * math.pi))) |
| 163 | ws = [-W / 2 + W * j / nw for j in range(nw + 1)] |
| 164 | layers = [] |
| 165 | for k in (TH / 2, -TH / 2): |
| 166 | layers.append(cq.Face.makeSplineApprox( |
| 167 | [[cq.Vector(*phi(pt(l, w, k))) for w in ws] for l in ls], tol=5e-5 * a, minDeg=3, maxDeg=5)) |
| 168 | top, bot = layers |
| 169 | faces = [top, bot] |
| 170 | for et in top.Edges(): # sides and ends: ruled between matching top and bottom edges |
| 171 | mt = et.positionAt(0.5) |
| 172 | eb = min(bot.Edges(), key=lambda x: (x.positionAt(0.5) - mt).Length) |
| 173 | if (et.startPoint() - eb.startPoint()).Length > (et.startPoint() - eb.endPoint()).Length: |
| 174 | eb = cq.Edge(eb.wrapped.Reversed()) |
| 175 | faces.append(cq.Face.makeRuledSurface(et, eb)) |
| 176 | return cq.Solid.makeSolid(cq.Shell.makeShell(faces)).fix() |
| 177 | |
| 178 | |
| 179 | # cube: turns rigidly with Q(1); edges rounded; +x face marked |
| 180 | qc = Q(1.0) |
| 181 | mark = box_at((a - 0.02 * a, 0, 0), (1, 0, 0), (0, 1, 0), 0.04 * a, 1.6 * a, 1.6 * a) # inlaid, flush |
| 182 | cube = cq.Workplane("XY").box(2 * a, 2 * a, 2 * a).edges().fillet(0.07 * a).val().cut(mark) |
| 183 | ang = 2 * math.degrees(math.acos(min(1.0, abs(qc[0])))) |
| 184 | if ang > 1e-9: |
| 185 | axis = (qc[1], qc[2], qc[3]) if qc[0] >= 0 else (-qc[1], -qc[2], -qc[3]) |
| 186 | cube = cube.rotate(cq.Vector(0, 0, 0), cq.Vector(*axis), ang) |
| 187 | mark = mark.rotate(cq.Vector(0, 0, 0), cq.Vector(*axis), ang) |
| 188 | |
| 189 | model = Model() |
| 190 | model.add("cube", cube, op="new", name="Cube") |
| 191 | model.add("mark", mark, op="new", name="Marked face") |
| 192 | pads, ribs = [], [] |
| 193 | for index, (key, e, u) in enumerate(RIBBONS): |
| 194 | gp, gr = grown(index) |
| 195 | if gp > 1e-3: |
| 196 | c = tuple((L + PAD_T / 2) * x for x in e) |
| 197 | model.add(f"pad_{key}", box_at(c, e, u, gp * PAD_T, gp * 2 * PAD_HALF, gp * 2 * PAD_HALF), op="new", |
| 198 | name=f"Anchor pad {key}") |
| 199 | pads.append(f"pad_{key}") |
| 200 | if gr > 1e-3: |
| 201 | model.add(f"ribbon_{key}", ribbon_solid(e, u, gr), op="new", name=f"Ribbon {key}") |
| 202 | ribs.append(f"ribbon_{key}") |
| 203 | |
| 204 | model.material("aluminum-6061-t6", bodies=["cube"], finish="brushed", color="#cfd1d4") |
| 205 | model.material("pa6", bodies=["mark"], color="#d91f0a") # red inlay on the +x face |
| 206 | if pads: |
| 207 | model.material("aluminum-6061-t6", bodies=pads, finish="anodized", color="#2a2c31") |
| 208 | if ribs: |
| 209 | model.material("tpu-60a", bodies=ribs, finish="satin", color="#cc7a1a") # elastic: ribbons stretch |
| 210 | result = model.result() |
| 211 |
| 1 | name = "littletechbird/dirac-belt" |
| 2 | version = "1.0.0" |
| 3 | runtime = "steve" |
| 4 | license = "MIT" |
| 5 | summary = "Dirac belt (belt trick): a cube tied to anchor pads by ribbons, posed at any moment of a 720 degree untangle." |
| 6 | description = """Dirac's belt trick as a solid model. A cube is tied by two (or six) elastic ribbons to anchor pads that \ |
| 7 | never move. Turning the cube 360 degrees leaves the ribbons twisted in a way no sliding can undo; after 720 degrees \ |
| 8 | they can be untangled without turning the cube or the pads. It is the topology behind spin-1/2 particles, which \ |
| 9 | need two full turns to come back to themselves. |
| 10 | |
| 11 | `time` picks the moment in a 37.5 s sequence: rest, a 360 degree turn, Hanson's 360 loop, a second turn, the 720 \ |
| 12 | untangle, four more ribbons grow out, and a six-ribbon finale in which the cube turns another 720 degrees. Each sphere around the cube turns as one rigid \ |
| 13 | piece by an angle set only by its distance from the centre, so the ribbons never pass through each other or the \ |
| 14 | cube at any moment, and each keeps its exact volume. Ribbons are B-spline solids fitted through the exact mapped \ |
| 15 | points. Cube at the origin, ribbon +z up, the cube's +x face marked red. Units mm.""" |
| 16 | tags = ["physics", "topology", "dirac-belt", "belt-trick", "spinor", "quaternion", "education", "animation"] |
| 17 | |
| 18 | [parameters.time] |
| 19 | type = "number" |
| 20 | default = 24.0 |
| 21 | min = 0 |
| 22 | max = 37.5 |
| 23 | step = 0.01 |
| 24 | unit = "s" |
| 25 | label = "Moment in the motion" |
| 26 | description = "0-3 rest, 3-9 first 360 degree turn, 9-15 Hanson 360 loop, 15-21 second turn, 21-28 the 720 untangle, 28-29.5 side ribbons grow, 29.5-37.5 six-ribbon finale. Same timeline as the video, frame = time x 60." |
| 27 | |
| 28 | [parameters.ribbons] |
| 29 | type = "choice" |
| 30 | default = "as in the video" |
| 31 | choices = ["as in the video", "1", "2", "6"] |
| 32 | label = "Ribbons" |
| 33 | description = "As in the video: the two vertical ribbons, then all six from 28 s. Or a fixed count: 1 (+z), 2 (+z, -z) or 6 (all faces) at any moment." |
| 34 | |
| 35 | [parameters.cube_size] |
| 36 | type = "number" |
| 37 | default = 40 |
| 38 | min = 5 |
| 39 | max = 1000 |
| 40 | unit = "mm" |
| 41 | label = "Cube size" |
| 42 | |
| 43 | [parameters.ribbon_width] |
| 44 | type = "number" |
| 45 | default = 18 |
| 46 | min = 0.5 |
| 47 | max = 600 |
| 48 | unit = "mm" |
| 49 | label = "Ribbon width" |
| 50 | description = "At most 0.5 x cube size." |
| 51 | |
| 52 | [parameters.ribbon_thickness] |
| 53 | type = "number" |
| 54 | default = 0.8 |
| 55 | min = 0.01 |
| 56 | max = 50 |
| 57 | unit = "mm" |
| 58 | label = "Ribbon thickness" |
| 59 | description = "At most 0.02 x cube size (as in the video), and at most a quarter of the ribbon width." |
| 60 | |
| 61 | [parameters.ribbon_length] |
| 62 | type = "number" |
| 63 | default = 108 |
| 64 | min = 12.5 |
| 65 | max = 10000 |
| 66 | unit = "mm" |
| 67 | label = "Ribbon length" |
| 68 | description = "From the cube face to the anchor pad; at least 2.5 x cube size." |
| 69 |
A cube tied to six fixed anchor pads by elastic ribbons (two in the first half), posed at any moment of a full 720 degree untangle. Turn the cube once (360 degrees) and the ribbons end up twisted in a way that no moving of the ribbons can undo while the cube and pads stay put. Turn it twice (720 degrees) and they can be untangled completely. This is the topology behind spin-1/2 particles: the rotation group SO(3) is double-covered by the unit quaternions SU(2), and only a 720 degree turn is a loop that shrinks to nothing.
Every point p is turned rigidly about the centre by the unit quaternion Q(h(|p|), t):
h(r) is 1 inside the ball of radius R0 = 1.76 a around the cube (a = half the cube size; the cube's corner is
at 1.732 a), 0 outside R1 = anchor distance - 0.3 a, and smooth (C3) and monotone in between. Its slope is
constant in log r over the middle 70 %, which keeps the ribbons' stretch as low as this construction allows.Q(s, t) follows the timeline below. The two untangling loops are Andrew Hanson's 360 and 720 degree quaternion
paths (A. J. Hanson, Visualizing Quaternions, Morgan Kaufmann 2006).Because each sphere |p| = r turns as one rigid piece, the map is smooth, one-to-one and volume-preserving at every moment. Ribbons that are apart at rest therefore never touch each other, themselves, the cube or the pads, and each ribbon keeps exactly its rest volume. The cube (inside R0) turns rigidly with Q(1, t), and the pads (outside R1) never move.
Each ribbon is a closed solid: its top and bottom surfaces are B-splines fitted (to 0.001 mm at the default size) through the exact mapped points, 241 along the length by 13 across, and its sides and ends are ruled between them.
time, seconds; the same as the video, frame = time x 60)| Time | What happens |
|---|---|
| 0-3 | Rest |
| 3-9 | The cube turns 360 degrees about z; the ribbons twist |
| 9-15 | Hanson's 360 loop: the twist moves around but cannot be removed |
| 15-21 | The cube turns a second 360 degrees (720 in total) |
| 21-28 | Hanson's 720 untangle: back to rest, with the cube never turning back |
| 28-29.5 | Four more ribbons and pads grow out (nothing turns) |
| 29.5-37.5 | Six-ribbon finale: with all six ribbons attached the cube turns another 720 degrees, and the ribbons come back untangled |
Every phase starts and ends at rest or at a whole-turn pose, so the last moment matches the first exactly.
| Parameter | Meaning |
|---|---|
time |
Moment in the motion, 0 to 37.5 s |
ribbons |
as in the video (2, then 6 from 28 s), or a fixed 1, 2 or 6. The map moves all of space, so any count is valid at any moment |
cube_size |
Edge of the cube, mm |
ribbon_width |
mm, at most 0.5 x cube size |
ribbon_thickness |
mm, at most 0.02 x cube size (as in the video) and a quarter of the width |
ribbon_length |
From cube face to anchor pad, mm, at least 2.5 x cube size |
The defaults reproduce the video's proportions exactly (cube 40 mm, so one video unit is 80 mm).
The cube is centred on the origin, ribbon pz points up (+z), and the cube's +x face carries a red inlay so its
turn is visible. Bodies: cube, mark (the inlay, flush with the face), pad_<axis> and ribbon_<axis> for each
ribbon present (pz, nz, px, nx, py, ny). Each ribbon end is glued to its cube face and to its pad face
(they touch; they don't overlap).
| 1 | # Dirac belt (belt trick) |
| 2 | |
| 3 | A cube tied to six fixed anchor pads by elastic ribbons (two in the first half), posed at any moment of a full |
| 4 | 720 degree untangle. Turn the cube once (360 degrees) and the ribbons end up twisted in a way that no moving |
| 5 | of the ribbons can undo while the cube and pads stay put. Turn it twice (720 degrees) and they can be untangled |
| 6 | completely. This is the topology behind spin-1/2 particles: the rotation group SO(3) is double-covered by the unit |
| 7 | quaternions SU(2), and only a 720 degree turn is a loop that shrinks to nothing. |
| 8 | |
| 9 | ## How the shape is made |
| 10 | |
| 11 | Every point p is turned rigidly about the centre by the unit quaternion Q(h(|p|), t): |
| 12 | |
| 13 | - `h(r)` is 1 inside the ball of radius R0 = 1.76 a around the cube (a = half the cube size; the cube's corner is |
| 14 | at 1.732 a), 0 outside R1 = anchor distance - 0.3 a, and smooth (C3) and monotone in between. Its slope is |
| 15 | constant in log r over the middle 70 %, which keeps the ribbons' stretch as low as this construction allows. |
| 16 | - `Q(s, t)` follows the timeline below. The two untangling loops are Andrew Hanson's 360 and 720 degree quaternion |
| 17 | paths (A. J. Hanson, *Visualizing Quaternions*, Morgan Kaufmann 2006). |
| 18 | |
| 19 | Because each sphere |p| = r turns as one rigid piece, the map is smooth, one-to-one and volume-preserving at every |
| 20 | moment. Ribbons that are apart at rest therefore never touch each other, themselves, the cube or the pads, and |
| 21 | each ribbon keeps exactly its rest volume. The cube (inside R0) turns rigidly with Q(1, t), and the pads (outside |
| 22 | R1) never move. |
| 23 | |
| 24 | Each ribbon is a closed solid: its top and bottom surfaces are B-splines fitted (to 0.001 mm at the default size) |
| 25 | through the exact mapped points, 241 along the length by 13 across, and its sides and ends are ruled between them. |
| 26 | |
| 27 | ## Timeline (`time`, seconds; the same as the video, frame = time x 60) |
| 28 | |
| 29 | | Time | What happens | |
| 30 | |---|---| |
| 31 | | 0-3 | Rest | |
| 32 | | 3-9 | The cube turns 360 degrees about z; the ribbons twist | |
| 33 | | 9-15 | Hanson's 360 loop: the twist moves around but cannot be removed | |
| 34 | | 15-21 | The cube turns a second 360 degrees (720 in total) | |
| 35 | | 21-28 | Hanson's 720 untangle: back to rest, with the cube never turning back | |
| 36 | | 28-29.5 | Four more ribbons and pads grow out (nothing turns) | |
| 37 | | 29.5-37.5 | Six-ribbon finale: with all six ribbons attached the cube turns another 720 degrees, and the ribbons come back untangled | |
| 38 | |
| 39 | Every phase starts and ends at rest or at a whole-turn pose, so the last moment matches the first exactly. |
| 40 | |
| 41 | ## Parameters |
| 42 | |
| 43 | | Parameter | Meaning | |
| 44 | |---|---| |
| 45 | | `time` | Moment in the motion, 0 to 37.5 s | |
| 46 | | `ribbons` | `as in the video` (2, then 6 from 28 s), or a fixed `1`, `2` or `6`. The map moves all of space, so any count is valid at any moment | |
| 47 | | `cube_size` | Edge of the cube, mm | |
| 48 | | `ribbon_width` | mm, at most 0.5 x cube size | |
| 49 | | `ribbon_thickness` | mm, at most 0.02 x cube size (as in the video) and a quarter of the width | |
| 50 | | `ribbon_length` | From cube face to anchor pad, mm, at least 2.5 x cube size | |
| 51 | |
| 52 | The defaults reproduce the video's proportions exactly (cube 40 mm, so one video unit is 80 mm). |
| 53 | |
| 54 | ## Layout and bodies |
| 55 | |
| 56 | The cube is centred on the origin, ribbon `pz` points up (+z), and the cube's +x face carries a red inlay so its |
| 57 | turn is visible. Bodies: `cube`, `mark` (the inlay, flush with the face), `pad_<axis>` and `ribbon_<axis>` for each |
| 58 | ribbon present (`pz`, `nz`, `px`, `nx`, `py`, `ny`). Each ribbon end is glued to its cube face and to its pad face |
| 59 | (they touch; they don't overlap). |
| 60 | |
| 61 | ## Notes |
| 62 | |
| 63 | - The ribbons stretch by up to about 9 times along their length in the 720 untangle, like elastic bands, which is |
| 64 | why they are TPU 60A. Real belts bend rather than stretch; the topology is the same. |
| 65 | - Checked with CadQuery 2.8 / OCCT at the defaults every 0.5 s of the timeline (76 moments), plus six ribbons at |
| 66 | 8 moments through the turns and the untangle, plus the smallest allowed proportions (cube 20, width 10, |
| 67 | thickness 0.4, length 50 mm) with six ribbons at 5 moments: every body a valid closed solid, no overlap between any |
| 68 | two bodies (beyond the glued contacts), no ribbon self-intersection, ribbon volume equal to the rest volume within |
| 69 | 0.03 %, and fitted surfaces within 0.03 mm of the exact mapped points. The closest two ribbons come at the |
| 70 | defaults is 4.2 mm (six ribbons, 22.5 s). |
| 71 | - STEVE's quick mass and volume figures for the ribbons can read about 3 % high: its default volume integration is |
| 72 | coarse on these thin B-spline solids. The exact ribbon volume is width x thickness x length at every moment. |
| 73 |
Dirac's belt trick as a solid model. A cube is tied by two (or six) elastic ribbons to anchor pads that never move. Turning the cube 360 degrees leaves the ribbons twisted in a way no sliding can undo; after 720 degrees they can be untangled without turning the cube or the pads. It is the topology behind spin-1/2 particles, which need two full turns to come back to themselves. `time` picks the moment in a 37.5 s sequence: rest, a 360 degree turn, Hanson's 360 loop, a second turn, the 720 untangle, four more ribbons grow out, and a six-ribbon finale in which the cube turns another 720 degrees. Each sphere around the cube turns as one rigid piece by an angle set only by its distance from the centre, so the ribbons never pass through each other or the cube at any moment, and each keeps its exact volume. Ribbons are B-spline solids fitted through the exact mapped points. Cube at the origin, ribbon +z up, the cube's +x face marked red. Units mm.
A cube tied to six fixed anchor pads by elastic ribbons (two in the first half), posed at any moment of a full 720 degree untangle. Turn the cube once (360 degrees) and the ribbons end up twisted in a way that no moving of the ribbons can undo while the cube and pads stay put. Turn it twice (720 degrees) and they can be untangled completely. This is the topology behind spin-1/2 particles: the rotation group SO(3) is double-covered by the unit quaternions SU(2), and only a 720 degree turn is a loop that shrinks to nothing.
Every point p is turned rigidly about the centre by the unit quaternion Q(h(|p|), t):
h(r) is 1 inside the ball of radius R0 = 1.76 a around the cube (a = half the cube size; the cube's corner is
at 1.732 a), 0 outside R1 = anchor distance - 0.3 a, and smooth (C3) and monotone in between. Its slope is
constant in log r over the middle 70 %, which keeps the ribbons' stretch as low as this construction allows.Q(s, t) follows the timeline below. The two untangling loops are Andrew Hanson's 360 and 720 degree quaternion
paths (A. J. Hanson, Visualizing Quaternions, Morgan Kaufmann 2006).Because each sphere |p| = r turns as one rigid piece, the map is smooth, one-to-one and volume-preserving at every moment. Ribbons that are apart at rest therefore never touch each other, themselves, the cube or the pads, and each ribbon keeps exactly its rest volume. The cube (inside R0) turns rigidly with Q(1, t), and the pads (outside R1) never move.
Each ribbon is a closed solid: its top and bottom surfaces are B-splines fitted (to 0.001 mm at the default size) through the exact mapped points, 241 along the length by 13 across, and its sides and ends are ruled between them.
time, seconds; the same as the video, frame = time x 60)| Time | What happens |
|---|---|
| 0-3 | Rest |
| 3-9 | The cube turns 360 degrees about z; the ribbons twist |
| 9-15 | Hanson's 360 loop: the twist moves around but cannot be removed |
| 15-21 | The cube turns a second 360 degrees (720 in total) |
| 21-28 | Hanson's 720 untangle: back to rest, with the cube never turning back |
| 28-29.5 | Four more ribbons and pads grow out (nothing turns) |
| 29.5-37.5 | Six-ribbon finale: with all six ribbons attached the cube turns another 720 degrees, and the ribbons come back untangled |
Every phase starts and ends at rest or at a whole-turn pose, so the last moment matches the first exactly.
| Parameter | Meaning |
|---|---|
time |
Moment in the motion, 0 to 37.5 s |
ribbons |
as in the video (2, then 6 from 28 s), or a fixed 1, 2 or 6. The map moves all of space, so any count is valid at any moment |
cube_size |
Edge of the cube, mm |
ribbon_width |
mm, at most 0.5 x cube size |
ribbon_thickness |
mm, at most 0.02 x cube size (as in the video) and a quarter of the width |
ribbon_length |
From cube face to anchor pad, mm, at least 2.5 x cube size |
The defaults reproduce the video's proportions exactly (cube 40 mm, so one video unit is 80 mm).
The cube is centred on the origin, ribbon pz points up (+z), and the cube's +x face carries a red inlay so its
turn is visible. Bodies: cube, mark (the inlay, flush with the face), pad_<axis> and ribbon_<axis> for each
ribbon present (pz, nz, px, nx, py, ny). Each ribbon end is glued to its cube face and to its pad face
(they touch; they don't overlap).
Primary injection test pins (tulips) for molded case circuit breakers: hex lug end, tulip neck, all rounded-square body, seven sizes.