# CAD Khana: The Assembly That Tests Itself

*Interactive version: [View on web](/articles/cad-khana.html)*


[sorted-studs](https://sorted-studs.cyberchitta.cc/) is a LEGO sorter. Bricks pass one at a time through a scanner, and a diverter turns to drop each into a basket. AI models write its parts in [build123d](https://build123d.readthedocs.io/), a Python CAD library, and @restlessronin directs. [CAD Khana](https://github.com/cyberchitta/cad-khana), a Python library built on build123d alongside the sorter, lets the models assemble those parts, declare how they move, and state claims about them.

## Redesign Was the Easy Part

In May @claude-opus-4.7 [threw out the scanner's dump cam and kicker lever](https://github.com/cyberchitta/sorted-studs/commit/c4263bc) for a servo. One commit deleted 1,382 lines and wrote 653. These models work fast on code, so a redesign was a refactor, and a refactor needs tests to show it still works.

But a part file has nothing to test: it gives only a part's shape. It says nothing about what the part must clear, reach or let past. Those questions get answered two ways. Claims such as "no part of this bolt is inside the motor" work like pytest tests, checked on every run. Quick experiments, written as separate Python scripts, settle a question once, and the ones worth repeating become CAD Khana features. Every defect below was found in code; nothing has been built yet.

## What a Bolt Head Touches

@claude-opus-4.7 [added the scanner drive's foot bolts](https://github.com/cyberchitta/sorted-studs/commit/51a8e33) in May, for renders only. The assembly the checks run on leaves fasteners out, so no check ever saw the bolts.

In September @claude-opus-5 [wrote the first check that puts them in](https://github.com/cyberchitta/sorted-studs/commit/c1506f7) and asserted each bolt against the motor:

```python
a = a.assert_no_interference(bolt, "static.drive_motor")
```

The check failed: one M5 head sat 1 mm inside the motor body. The same day @claude-fable-5.1 [moved the bolts outboard of the bracket wall](https://github.com/cyberchitta/sorted-studs/commit/60cef28). The check passed.

## What a Camera Sees

In May the scanner had one camera and three mirrors. At @restlessronin's request, @claude-opus-4.7 [rendered the view from the camera's lens](https://github.com/cyberchitta/sorted-studs/commit/019d5ec). The brick showed directly and in the upper mirror, but the platform's frames hid it from both lower ones. The design allowed for a smaller brick in the reflections, not for none. No simple change kept the mirrors, and that single image ended the design.

![The scanner from its one camera: a red brick on a clear platform, and the same brick, small, in a mirror at the top of the frame.](/assets/images/articles/mirrors-one-camera-261013-light.webp)

*The camera's view. / The lower mirrors sit behind the platform frames.*


@claude-opus-4.7 then [rendered the views from four cameras](https://github.com/cyberchitta/sorted-studs/commit/2f40705): one where the camera stood, and one at each mirror's position. All four showed the brick clearly. The same evening it [put the cameras into the design](https://github.com/cyberchitta/sorted-studs/commit/26e9729) in place of the mirrors.

A sightline claim could have caught the blocked mirrors too. @restlessronin thought of a render first, and one look was enough.

## What Happens Over a Whole Turn

In the diverter, a turning stack aims each floor's ramp at a basket, and each ramp swings past the six inner columns.

[3D model: A hex tower of extrusion columns, with a turning stack of ramps through its middle.](https://sorted-studs.cyberchitta.cc/assets/exhibits/diverter-260920-light.glb)

*The diverter, September 20.*


In May @claude-opus-4.7 [argued in a comment](https://github.com/cyberchitta/sorted-studs/commit/fe1185d) that the ramp's tip cleared the inner columns at any angle. Four months on, @claude-fable-5.1 [gave the ramp side walls](https://github.com/cyberchitta/sorted-studs/commit/2cb37bf) without revisiting the comment.

In September @claude-opus-5 probed the ramp in 2° steps. At rest the ramp had 30 mm to spare; from 14° to 20°, a wall corner sat inside a column. The six column claims beside that comment stayed green: they were only ever checked at rest.

On September 20 @claude-fable-5.1 [made CAD Khana check every claim at each step of a declared motion](https://github.com/cyberchitta/cad-khana/commit/6aa7ec3). Five minutes later @claude-opus-5 [declared the stack's turn as a motion](https://github.com/cyberchitta/sorted-studs/commit/499b86b), and the six claims went red. CAD Khana now saw what the probe had found, on every run.

Before: ![A grey ramp's side wall running into a square column.](/assets/images/articles/ramp-turn-before-261013-light.webp)

After: ![The same ramp with its corner cut back, just short of the column.](/assets/images/articles/ramp-turn-after-261013-light.webp)

*At 16°: the ramp wall inside a column. / After the cut: 5 mm clear.*


Then @claude-opus-5 [cut the ramp](https://github.com/cyberchitta/sorted-studs/commit/0fb500b) to clear the columns at every angle.

## Whether a Basket Comes Out

Back in May, @gpt-5.5-codex [cut channels in the baskets around the tower's inner and outer columns](https://github.com/cyberchitta/sorted-studs/commit/dfae3b9) "so baskets can be installed and removed horizontally". The docstring said so for four months. Every claim about a basket was about a basket at rest.

In September @claude-opus-5 wrote a short script to try what the docstring promised. Drawn straight out, each channeled basket hit an outer column after 5 to 15 mm of travel.

Before: ![A basket's channeled wall overlapping a square column.](/assets/images/articles/basket-draw-before-261013-light.webp)

After: ![The same basket with its wall angled past the column, a gap between them.](/assets/images/articles/basket-draw-after-261013-light.webp)

*Drawn 15 mm: its wall inside the outer column. / Through the swept channel.*


The same day @claude-opus-5 [swept the channel around the outer column along the draw-out path](https://github.com/cyberchitta/sorted-studs/commit/0a6228e). It added a check that steps every basket out, 4 mm at a time.

CAD Khana got faster as well as more capable. That check took 17 minutes a run. In October @claude-opus-5.5 [gave CAD Khana a keep-out](https://github.com/cyberchitta/cad-khana/commit/eae94fd): the exact volume a body sweeps on a straight path, which other parts must stay out of. With [one keep-out per basket](https://github.com/cyberchitta/sorted-studs/commit/3299d22) in place of the stepping, the check takes 20 seconds, short enough to run on every change.

---

Fast iteration needs thorough checks.

## Credits

Written by @claude-opus-5.5 from sorted-studs' commit log and session history, and CAD Khana's adoption notes.

Every figure was made with CAD Khana: the stills rendered by @claude-opus-5.5, the 3D model exported by @claude-opus-5.

Showrunner: @restlessronin.
