How CAD Skills Handles Assemblies and Joints in Text-to-CAD

The CAD Skills subsystem represents mechanical structures as hierarchical assemblies defined by AssemblySpec dataclasses, flattens the hierarchy for export, and instantiates build123d Joint objects—including RevoluteJoint, RigidJoint, and LinearJoint—to model mechanical relationships between parts.

The earthtojake/text-to-cad repository implements a complete pipeline for converting text descriptions into manufacturable CAD artifacts. Understanding how CAD Skills handles assemblies and joints requires examining the specification layer, the flattening logic, and the joint instantiation code that bridges abstract descriptions with physical OCCT scenes.

Assembly Specification and Hierarchy

At the core of the system lies a strict data model that separates instance definitions from hierarchical structure.

Defining AssemblyInstanceSpec and AssemblyNodeSpec

The specification layer in assembly_spec.py defines two primary dataclasses that govern how assemblies are structured:

  • AssemblyInstanceSpec describes concrete STEP parts, encapsulating the instance ID, source file path, local path, 4×4 transform matrix, and color usage flags.
  • AssemblyNodeSpec describes nodes in the hierarchy, tracking instance IDs, names, local transforms, and optional child nodes to build parent-child relationships.

These dataclasses enforce type safety across the generation pipeline and provide the schema for the AssemblySpec container object.

Validating Payloads with assembly_spec_from_payload

When users supply Python payloads—either as a list of children or a dictionary containing an instances list—the helper assembly_spec_from_payload() performs strict validation:

from cadpy.assembly_spec import assembly_spec_from_payload
from pathlib import Path

assembly_payload = {
    "instances": [
        {
            "instance_id": "base",
            "source_path": Path("models/base.step"),
            "path": "base.step",
            "name": "BasePlate",
            "transform": (1,0,0, 0,1,0, 0,0,1, 0,0,0, 1),
            "use_source_colors": True,
        },
        {
            "instance_id": "motor",
            "source_path": Path("models/motor.step"),
            "path": "motor.step",
            "name": "Motor",
            "transform": (1,0,0, 0,1,0, 0,0,1, 0,0,20, 1),
            "use_source_colors": True,
        },
    ]
}

spec = assembly_spec_from_payload(Path("my_assembly.py"), assembly_payload)

This function forbids duplicate sibling names within the same parent node and normalizes the hierarchy into a validated AssemblySpec object ready for geometry processing.

Building the Assembly Pipeline

Once validated, the AssemblySpec traverses a three-stage pipeline to produce renderable geometry.

Flattening Hierarchies with assembly_flatten.py

The assembly_flatten.py module walks the hierarchical specification tree and produces a flat list of leaf instances. During traversal, it multiplies cumulative transforms using multiply_transforms to resolve each component's final world-space position. This flattening step is essential for preparing instances for both STEP export and GLB serialization.

Composition Strategies in assembly_composition.py

The assembly_composition.py module determines how to construct the actual OCCT scene. It selects between:

  • Native composition: Direct OCCT-based assembly building for maximum control over geometry and metadata.
  • Linked composition: Integration with the build123d library for simplified scene construction.

The choice of composition strategy affects how joints are attached and how the final Compound object is structured before export.

Joint Creation and Management

Joints represent mechanical relationships—degrees of freedom, axes of rotation, and attachment points—between assembly components.

Supported Joint Types

According to the source code in assembly.py, CAD Skills supports five joint primitives from the build123d library:

  1. RigidJoint – Fixes two components together with no degrees of freedom.
  2. RevoluteJoint – Allows rotation around a single axis.
  3. LinearJoint – Permits translation along an axis.
  4. CylindricalJoint – Combines rotation and translation along the same axis.
  5. BallJoint – Provides spherical rotation with multiple degrees of freedom.

Creating Joints in assembly.py

The assembly.py file contains the core joint instantiation logic. For each leaf instance in the flattened hierarchy, the code creates the appropriate build123d Joint object and attaches it to a labeled Compound representing the complete assembly.

The helper function add_axis_frame configures the joint's origin, axis vector, motion limits, and visualization options:

def _add_revolute_joint(part, name, axis, **joint_options):
    # Used by the assembly builder to create a RevoluteJoint

    return add_axis_frame(
        part,
        name,
        axis,
        joint_type="RevoluteJoint",
        **joint_options,
    )

Joint parameters include coordinate frames, angular limits for revolute joints, and linear limits for prismatic motion, all serialized into the final CAD artifact.

Exporting Assemblies with Joints

The generation pipeline culminates in export modules that preserve joint metadata. The generation.py module orchestrates the workflow:

from cadpy.generation import (
    build_native_assembly_composition,
    export_assembly_step_scene,
)

# Build the OCCT scene (includes Build123d joints)

scene = build_native_assembly_composition(spec)

# Export the whole assembly as a STEP file

export_assembly_step_scene(spec, scene, output_path=Path("out/assembly.step"))

The AssemblySpec.kind value—either "part" or "assembly"—drives exporter selection in step_export.py and glb.py, determining whether the output contains joint annotations and hierarchical metadata or represents a single solid body.

Summary

  • CAD Skills uses AssemblySpec, AssemblyInstanceSpec, and AssemblyNodeSpec dataclasses in assembly_spec.py to define hierarchical mechanical structures.
  • The assembly_spec_from_payload() function validates user input and forbids duplicate sibling names during hierarchy normalization.
  • assembly_flatten.py resolves world-space transforms by multiplying cumulative transformation matrices down the hierarchy.
  • Joint instantiation occurs in assembly.py, creating build123d Joint objects (Revolute, Rigid, Linear, Cylindrical, and Ball) attached to a labeled Compound.
  • The AssemblySpec.kind field controls export behavior in step_export.py and glb.py, determining whether outputs include assembly metadata and joint definitions.

Frequently Asked Questions

What joint types does CAD Skills support?

CAD Skills supports five joint types through the build123d library: RigidJoint for fixed connections, RevoluteJoint for rotational motion, LinearJoint for sliding motion, CylindricalJoint for combined rotation and translation, and BallJoint for spherical articulation. These are instantiated in assembly.py using the add_axis_frame helper function.

How does CAD Skills validate assembly structures?

Validation occurs in assembly_spec.py via the assembly_spec_from_payload() function, which checks for duplicate sibling names within parent nodes, validates path references, and normalizes transform matrices into standard AssemblyInstanceSpec objects before pipeline processing begins.

What is the difference between native and linked composition?

Native composition (handled in assembly_composition.py) constructs the OCCT scene directly using internal geometry kernels, while linked composition delegates assembly construction to the build123d library. Native composition provides finer control over STEP metadata and joint serialization, whereas linked composition offers simplified API integration for rapid prototyping.

How are transforms handled in hierarchical assemblies?

The assembly_flatten.py module recursively traverses the AssemblyNodeSpec hierarchy and multiplies local transforms using multiply_transforms to calculate each component's final world-space position. This flattening process produces a list of leaf instances with absolute coordinates ready for export to STEP or GLB formats.

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 →