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

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), 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
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/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:

{
    '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.

Practical Implementation Examples

Two-Part Assembly with Revolute Joint

This example from the test suite demonstrates the complete workflow:

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:


# 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 when parts need clearance or specific mounting positions.

Inspecting Generated Metadata

Verify your assembly structure before export:

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.

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)
  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) 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/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/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 reads this metadata for interactive joint manipulation. URDF generation in the CAD skill 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/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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →