# Assembly Positioning Using Part-Local Datums and Explicit Transforms in text-to-cad

> Learn assembly positioning with part-local datums and explicit transforms in text-to-cad. Create precise spatial relationships using build123d's joint system.

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

---

**The text-to-cad repository implements assembly positioning through a Pythonic wrapper around build123d's joint system, where parts expose named datum frames and explicit transforms create offset joints for precise spatial relationships.**

Assembly positioning in modern CAD workflows requires a clean separation between part geometry and spatial relationships. The **text-to-cad** project—`earthtojake/text-to-cad`—solves this through a purpose-built abstraction layer in [`packages/cadpy/src/cadpy/assembly.py`](https://github.com/earthtojake/text-to-cad/blob/main/packages/cadpy/src/cadpy/assembly.py) that bridges high-level Python scripting with **build123d**'s native joint mechanics. This article explains how **part-local datums** and **explicit transforms** enable reusable, parametric assembly definitions.

## Part-Local Datums: Named Frames on Every Part

Every part in a text-to-cad assembly can expose **named frames** (datums) stored as native **build123d** joints. The `AssemblyHelper.datum()` method labels a shape with a frame name, delegating actual joint creation to the underlying library:

```python
def datum(self, shape, name, *details, color=None):
    return label_shape(shape, name, *details, color=color)   # lines 132-139

```

The returned object is a `MateTarget`—a lightweight wrapper from the `target()` factory—that records both the part and the datum label for later relationship wiring. This design keeps datum definitions **declarative and reusable** across multiple assemblies.

**Key benefits of part-local datums:**

- Frames travel with the part, not the assembly
- Multiple assemblies can reference the same part-level frame
- Color and visualization metadata attach at definition time

## Explicit Transforms: Creating Offset Copies of Datums

When a fixed datum requires spatial adjustment—such as shifting a mounting hole by 10 mm—the `offset_target()` function constructs a temporary **RigidJoint** that applies explicit translation or rotation:

```python
def offset_target(fixed, offset, *, label=None):
    fixed_target = _normalize_target(fixed)
    fixed_joint_label, fixed_joint = _joint_for_target(fixed_target)
    ...
    offset_location = _offset_location(offset)
    target_location = location * offset_location
    target_label = label_text(label or fixed_joint_label, "offset")
    build123d.RigidJoint(
        label=target_label,
        to_part=fixed_target.part,
        joint_location=target_location,
    )
    return MateTarget(part=fixed_target.part, frame=target_label)   # lines 292-314

```

The function composes the original joint's location with user-supplied offset parameters, registers a new joint with a distinct label (e.g., `"frameA:offset"`), and returns a fresh `MateTarget` pointing to this transformed frame. This **explicit transform** pattern enables precise placement without mutating the original part geometry.

## Connecting Parts: From Datums to Relationships

With datums and offset datums established, `AssemblyHelper.connect()` creates a `MateRelation` that records:

1. The fixed and moving frames (as `(part_name, frame_label)` tuples or `MateTarget` objects)
2. The joint type: `rigid`, `revolute`, `prismatic`, `ball_socket`, etc.
3. Type-specific parameters (axis vectors, angle limits, linear ranges)

The method also generates a JSON-friendly payload via `assembly_mate_payload()` for downstream STEP-export tooling integrated with the text-to-cad skill CLI.

## Complete Working Example

```python
from cadpy.assembly import AssemblyHelper

# 1️⃣ Create a helper for a new assembly

asm = AssemblyHelper("my_robot")

# 2️⃣ Add parts (shapes are native build123d objects)

base = asm.add(my_base_shape, "base")
arm  = asm.add(my_arm_shape,  "arm")

# 3️⃣ Define a datum on the arm (part-local frame)

arm_joint = asm.datum(arm, "joint_center")

# 4️⃣ Create an offset datum 10 mm along the X-axis

offset_joint = asm.offset_target(arm_joint, (10, 0, 0), label="joint_center_offset")

# 5️⃣ Connect the base to the offset datum with a revolute joint

asm.revolute_frame(base, "base_hole")          # creates a datum on the base

asm.connect(
    fixed=("base", "base_hole"),
    moving=offset_joint,
    relation="revolute",
    axis=(0, 0, 1),               # Z-axis of rotation

    limit=(-180, 180)
)

# 6️⃣ Export the assembled CAD as a STEP file (via the skill CLI)

# $ text-to-cad cad step build --assembly my_robot

```

**Pattern demonstrated:** `datum` → `offset_target` → `connect`. Each stage remains pure and inspectable, with full metadata preserved for export.

## Architecture Principles

The implementation in [`packages/cadpy/src/cadpy/assembly.py`](https://github.com/earthtojake/text-to-cad/blob/main/packages/cadpy/src/cadpy/assembly.py) enforces three design constraints that distinguish text-to-cad from raw build123d scripting:

| Principle | Implementation | Benefit |
|-----------|---------------|---------|
| **Separation of concerns** | `datum`/`offset_target` vs. `connect`/`face_to_face` | Geometry and relationships evolve independently |
| **Explicit over implicit** | Offset transforms require explicit label and vector | No hidden state; assemblies remain reproducible |
| **JSON-serializable metadata** | `assembly_mate_payload()` generates export-ready structures | Seamless CLI integration and viewer compatibility |

## Summary

- **`AssemblyHelper`** in [`packages/cadpy/src/cadpy/assembly.py`](https://github.com/earthtojake/text-to-cad/blob/main/packages/cadpy/src/cadpy/assembly.py) provides the core API for assembly positioning using part-local datums and explicit transforms.
- **`datum()`** creates named, part-local frames stored as native build123d joints (lines 132-139).
- **`offset_target()`** builds temporary RigidJoints for explicit spatial transforms, composing original locations with user offsets (lines 292-314).
- **`connect()`** wires frames into typed relationships (`MateRelation`) with export-ready metadata.
- The architecture separates **declarative frame definition** from **imperative relationship wiring**, enabling reusable parts and parametric assemblies.

## Frequently Asked Questions

### How does text-to-cad differ from using build123d joints directly?

text-to-cad adds a **naming and metadata layer** that build123d lacks. While build123d joints are powerful, they are anonymous at runtime. The `AssemblyHelper` ensures every frame has a stable identifier, stores part-to-frame relationships, and generates STEP-compatible export payloads—essential for automated CAD pipelines.

### Can I reuse the same part with different datum offsets in one assembly?

Yes. Because `offset_target()` creates **new joints with unique labels** (e.g., `"joint_center:offset_1"`, `"joint_center:offset_2"`), you can instantiate multiple spatial variants of the same underlying part without duplication. Each variant receives its own `MateTarget` for independent connections.

### What joint types are supported beyond revolute?

According to the source in [`packages/cadpy/src/cadpy/assembly.py`](https://github.com/earthtojake/text-to-cad/blob/main/packages/cadpy/src/cadpy/assembly.py), the `connect()` method accepts `rigid`, `revolute`, `prismatic`, and `ball_socket` relations. The helper also provides convenience wrappers like `revolute_frame()`, `rigid_frame()`, and `face_to_face()` that infer datum creation from geometric features.

### Where does the actual STEP export happen?

The `AssemblyHelper` produces metadata only; STEP generation occurs through the **skill CLI** (`text-to-cad cad step build --assembly <name>`). The helper's `assembly_mate_payload()` method (in coordination with [`packages/cadpy/src/cadpy/metadata.py`](https://github.com/earthtojake/text-to-cad/blob/main/packages/cadpy/src/cadpy/metadata.py)) formats joint data for this downstream consumer.