# Implementing AssemblyHelper with build123d Joints and Mating Datums in Text-to-CAD

> Learn to implement AssemblyHelper with build123d joints and mating datums in Text-to-CAD. Effortlessly create stateless assemblies with automatic metadata and JSON payloads.

- Repository: [earthtojake/text-to-cad](https://github.com/earthtojake/text-to-cad)
- Tags: how-to-guide
- Published: 2026-08-03

---

**The `AssemblyHelper` class in `cadpy` provides a semantic wrapper around build123d's joint system, enabling stateless assembly construction with automatic metadata generation for named frames, native joint instantiation, and JSON-compatible mate payloads.**

The **text-to-cad** repository by earthtojake implements a modular, agent-driven CAD workflow where high-level skills delegate geometry handling to reusable Python packages. At the center of this architecture sits the **AssemblyHelper**, a thin abstraction layer that transforms verbose build123d joint operations into concise, readable assembly code with built-in export metadata.

## What AssemblyHelper Provides

Located in [[`viewer/packages/cadpy/src/cadpy/assembly.py`](https://github.com/earthtojake/text-to-cad/blob/main/viewer/packages/cadpy/src/cadpy/assembly.py)](https://github.com/earthtojake/text-to-cad/blob/main/viewer/packages/cadpy/src/cadpy/assembly.py), the `AssemblyHelper` addresses three core challenges in programmatic CAD assembly:

- **Named coordinate frames** – **MateTarget** objects combine a part reference with a human-readable label from `label_text`
- **Native joint creation** – Helper methods instantiate build123d joints (`RevoluteJoint`, `LinearJoint`, `RigidJoint`) without direct API manipulation
- **Automatic relation tracking** – Every connection generates a **MateRelation** record capturing labels, joint types, fixed/moving names, and parameters

This stateless design means you add shapes, define datums, connect them, then call `build()` to receive a labeled `build123d.Compound` ready for STEP or GLB export.

## Core Components of the Assembly System

### MateTarget: Named Frames for Parts

Before connecting parts, you establish **datum frames** using semantic labels. The `MateTarget` class stores:

- A reference to the parent part
- A generated label from `label_text`
- Position and orientation data

```python
from cadpy.assembly import AssemblyHelper

asm = AssemblyHelper("mechanism")
part = asm.add(shape, "bracket")
datum = asm.revolute_frame(part, "hinge_point", axis=(0, 1, 0))

```

### Joint Helper Functions

The helper provides specialized methods for common joint types in [[`assembly.py`](https://github.com/earthtojake/text-to-cad/blob/main/assembly.py)](https://github.com/earthtojake/text-to-cad/blob/main/viewer/packages/cadpy/src/cadpy/assembly.py):

| Method | Joint Type | Parameters |
|--------|-----------|------------|
| `add_rigid_frame()` | `RigidJoint` | Fixed transformation |
| `add_axis_frame()` | `RevoluteJoint` / `LinearJoint` | Axis vector, origin |
| `add_joint_frame()` | Generic | Depends on joint subclass |
| `revolute_frame()` | Convenience wrapper | Axis, angle limits |
| `revolute()` | Creates connection | `fixed`, `moving`, `label`, `angle` |

These methods handle the underlying build123d instantiation while preserving metadata for downstream export.

### MateRelation and Payload Generation

Every successful connection produces a **MateRelation** record. When you call `assembly_mate_payload()`, these records serialize to JSON-compatible dictionaries:

```python
{
    'id': 'm1',
    'label': 'm1',
    'sourceLabel': 'shoulder',
    'type': 'revolute',
    'fixed': 'base_axis',
    'moving': 'arm_axis',
    'parameters': {'angle': 90}
}

```

The `compound()` method injects this `assembly_mates` metadata directly into the returned `build123d.Compound`, making it available to the [viewer runtime](https://github.com/earthtojake/text-to-cad/blob/main/viewer/src/client/workbench/assemblyIsolation.js).

## Practical Implementation Examples

### Two-Part Assembly with Revolute Joint

This example from the [test suite](https://github.com/earthtojake/text-to-cad/blob/main/tests/python/packages/cadpy/test_assembly_helper.py) demonstrates the complete workflow:

```python
from cadpy.assembly import AssemblyHelper

# 1️⃣ Create the helper for the assembly

asm = AssemblyHelper("my_robot")

# 2️⃣ Add parts (these could be any build123d shapes)

base = asm.add(shape_base, "base")
arm = asm.add(shape_arm, "arm")

# 3️⃣ Define datum frames on each part

base_axis = asm.revolute_frame(base, "base_axis", axis=(0, 0, 1))
arm_axis = asm.revolute_frame(arm, "arm_axis", axis=(0, 0, 1))

# 4️⃣ Connect the frames with a revolute joint

asm.revolute(fixed=base_axis, moving=arm_axis, label="shoulder", angle=90)

# 5️⃣ Build the final compound (contains joint metadata)

robot = asm.build()

```

### Offset Datum Positioning

For precise part placement, use `offset_target()` to translate a datum before connection:

```python

# Offset the datum 10 mm along Z before connecting

offset_target = asm.offset_target(fixed, offset=(0, 0, 10), label="offset_z")
asm.connect(fixed_target=offset_target, moving=arm_axis, relation="rigid")

```

This pattern appears frequently in [cadpy's assembly logic](https://github.com/earthtojake/text-to-cad/blob/main/viewer/packages/cadpy/src/cadpy/assembly.py) when parts need clearance or specific mounting positions.

### Inspecting Generated Metadata

Verify your assembly structure before export:

```python
payload = asm.compound().assembly_mates
print(payload)

# → [{'id': 'm1', 'label': 'm1', 'sourceLabel': 'shoulder',

#     'type': 'revolute', 'fixed': 'base_axis', 'moving': 'arm_axis',

#     'parameters': {'angle': 90}}]

```

This metadata drives the web viewer's joint visualization and enables accurate URDF generation in the [CAD skill layer](https://github.com/earthtojake/text-to-cad/blob/main/skills/cad/SKILL.md).

## Architecture Integration

The AssemblyHelper operates within a four-layer stack as implemented in the text-to-cad repository:

1. **build123d** – External dependency providing low-level geometry and joint primitives
2. **cadpy package** – Internal wrapper with `AssemblyHelper`, `MateTarget`, and utilities
3. **Skill layer** – Agent-written code using the helper (see [SKILL.md](https://github.com/earthtojake/text-to-cad/blob/main/skills/cad/SKILL.md))
4. **Viewer runtime** – JavaScript consumer of `assembly_mates` metadata for web rendering

Unit tests in [[`tests/python/packages/cadpy/test_assembly_helper.py`](https://github.com/earthtojake/text-to-cad/blob/main/tests/python/packages/cadpy/test_assembly_helper.py)](https://github.com/earthtojake/text-to-cad/blob/main/tests/python/packages/cadpy/test_assembly_helper.py) validate fixed-joint versus moving-joint connection semantics, ensuring predictable behavior across assembly complexity levels.

## Summary

- **AssemblyHelper** in [[`cadpy/assembly.py`](https://github.com/earthtojake/text-to-cad/blob/main/cadpy/assembly.py)](https://github.com/earthtojake/text-to-cad/blob/main/viewer/packages/cadpy/src/cadpy/assembly.py) wraps build123d joints with semantic, label-driven APIs
- **MateTarget** objects provide named coordinate frames on parts using `label_text` generation
- **MateRelation** records capture connection metadata for JSON export via `assembly_mate_payload()`
- **Stateless design** enables clean skill code: add parts, define datums, connect, then `build()`
- Generated compounds carry `assembly_mates` metadata consumed by the viewer runtime and export pipelines

## Frequently Asked Questions

### How does AssemblyHelper differ from using build123d joints directly?

**AssemblyHelper eliminates boilerplate** by managing joint instantiation, frame labeling, and metadata serialization in a single fluent interface. Direct build123d usage requires manual `Joint` object creation, explicit `Label` attachment, and separate bookkeeping for export metadata. The helper's stateless pattern also makes skill code more testable and readable.

### Can I mix custom build123d joints with AssemblyHelper?

**Yes, but with trade-offs.** The helper's `add_joint_frame()` method accepts custom joint instances, though you'll lose automatic metadata generation for those connections. For full metadata capture, extend the helper with additional connection methods following the pattern in [[`assembly.py`](https://github.com/earthtojake/text-to-cad/blob/main/assembly.py)](https://github.com/earthtojake/text-to-cad/blob/main/viewer/packages/cadpy/src/cadpy/assembly.py).

### What export formats preserve the mate metadata?

**GLB and STEP exports** consume the `assembly_mates` payload injected by `compound()`. The [viewer runtime](https://github.com/earthtojake/text-to-cad/blob/main/viewer/src/client/workbench/assemblyIsolation.js) reads this metadata for interactive joint manipulation. URDF generation in the [CAD skill](https://github.com/earthtojake/text-to-cad/blob/main/skills/cad/SKILL.md) transforms the same payload into robot description format.

### How do I validate an assembly before export?

**Run unit test patterns** from [[`test_assembly_helper.py`](https://github.com/earthtojake/text-to-cad/blob/main/test_assembly_helper.py)](https://github.com/earthtojake/text-to-cad/blob/main/tests/python/packages/cadpy/test_assembly_helper.py): inspect `asm.compound().assembly_mates` for expected connection counts, verify `fixed` and `moving` labels match your intent, and confirm parameters like `angle` or `offset` appear in the payload.