Skip to content

CADSmith

Peter Pak edited this page Apr 15, 2026 · 6 revisions

CADSmith

The cadsmith module handles parametric CAD generation, geometry extraction, asset generation, and assembly layout. All CAD scripts use build123d and execute in an isolated uv environment.

MCP Tools

Tool Description
cadsmith_run_script Execute a build123d Python script in an isolated uv env with pre- and post-execution validation
cadsmith_extract_part Extract volume, surface area, bounding box, centre of mass from a STEP or BREP file; writes gui/parts/<name>.json to the project directory. Optional out_path overrides the output location.
cadsmith_generate_assets Generate visualization assets for a STEP file: isometric PNG thumbnail, 360° rotating isometric GIF, and/or 360-frame ASCII loading animation; writes to gui/assets/ in the project directory. Optional out_dir overrides the output directory.
cadsmith_assembly Generate or read assembly.json -- the spatial layout of parts for the 3D viewer; writes to the project directory. Optional out_path overrides the output location.
cadsmith_bd_warehouse_info Instantiate a bd_warehouse parametric part and return dimensional attributes; exports STEP and Part JSON to the project directory. Optional out_path overrides the STEP output location.

Part Model

The Part model (cadsmith/models.py) uses pintdantic for unit-aware geometry fields:

class Part(QuantityModel):
    name: str
    stl_path: str | None
    step_path: str | None
    brep_path: str | None
    bounding_box: UnitVector | None      # (x, y, z) extents in mm
    color: str = "#cccccc"
    cost: float | None
    description: str | None
    id: str | None
    volume: QuantityField | None         # mm^3
    surface_area: QuantityField | None   # mm^2
    center_of_mass: UnitVector | None    # (x, y, z) in mm
    mass: QuantityField | None           # g or kg

Part also has an optional display_name field for human-readable names (e.g. "Nose Cone").

AssemblyPart references a part JSON file instead of duplicating part data:

class AssemblyPart(QuantityModel):
    part_file: str          # e.g. "gui/parts/nose_cone.json"
    position: UnitVector
    rotation: UnitVector
    color: str = "#cccccc"
    invert_z: bool = False  # flip 180° around X (e.g. nose cones)
    joint_offset: QuantityField | None  # shoulder overlap in mm

An Assembly holds a list of AssemblyPart references plus total_length. The assembly generator computes positions accounting for shoulder overlaps (joint_offset) so parts interlock correctly.

UnitVector

UnitVector is a shared 3D vector model with unit-aware components (default: mm):

class UnitVector(QuantityModel):
    x: QuantityField = (0.0, "mm")
    y: QuantityField = (0.0, "mm")
    z: QuantityField = (0.0, "mm")

Two class methods:

  • UnitVector.from_vector(v, precision=3) -- create from a build123d Vector or BoundBox.size, rounding to the given precision.
  • UnitVector.deg(x=0, y=0, z=0) -- create a vector in degrees (used for rotation fields in assembly parts).

Script Validation

validate_script.py performs AST-level checks before executing any build123d script:

  1. Export checks -- the script must call export_step(). STL files are generated separately by cadsmith_generate_assets.
  2. Import allowlist -- only build123d, bd_warehouse, pathlib, math, and typing are permitted. Any other import is rejected.

After execution, cadsmith_run_script also performs post-run validation: it verifies that non-empty .step files were actually written to the output directory.

Pre-run:  AST parse -> check export calls -> check imports
Execute:  uv run --isolated --with build123d <script.py>
Post-run: verify .step files exist and are non-empty

Asset Pipeline

cadsmith_generate_assets produces up to three output types for a given STEP file. All three read directly from the STEP and run in parallel with STL generation via a ThreadPoolExecutor. The outputs parameter controls which types are generated; it defaults to all three.

Output Format Path Description
thumbnail PNG gui/assets/png/<part_name>.png Single isometric view
gif GIF gui/assets/gif/<part_name>.gif 360° rotating isometric animation (36 frames, 10°/frame)
ascii TXT gui/assets/txt/<part_name>.txt 360-frame text animation (1°/frame), used as a loading icon

Progress Tracking

A gui/progress/<part_name>.json file tracks the status of each output type in real time so the GUI can display a progress bar:

{
  "part_name": "nose_cone",
  "outputs": {
    "thumbnail": {"status": "done", "path": "gui/assets/png/nose_cone.png"},
    "gif": {"status": "in_progress", "path": null},
    "ascii": {"status": "pending", "path": null}
  }
}

Status values: pending, in_progress, done, failed.

The PreviewProgress class manages this file, flushing to disk on every update() call.

Part Extraction

cadsmith_extract_part extracts geometric properties from a STEP file and writes a part JSON:

  • Input: STEP file path, optional material density, optional display name, optional out_path
  • Output: Part object with volume, surface area, bounding box, center of mass, and mass
  • Always writes gui/parts/<name>.json to the project directory; out_path overrides the destination

The part JSON serves as the single source of truth for part metadata, referenced by assembly.json and displayed in the GUI's part detail pages.

Assembly Generation

cadsmith_assembly supports two actions:

generate

Reads the component tree and STEP files to compute a spatial layout:

  1. Extracts bounding boxes from each STEP file via extract_part() and writes part JSONs to gui/parts/.
  2. Stacks parts along the Z axis, accounting for shoulder overlaps (joint_offset) so parts interlock correctly.
  3. Sets invert_z=true on nose cones (built shoulder-down for printing, displayed tip-forward).
  4. Each AssemblyPart references a part_file (e.g. gui/parts/nose_cone.json) instead of duplicating geometry data.
  5. Writes gui/assembly.json.

read

Loads and returns an existing gui/assembly.json.

The resulting Assembly model contains schema_version, project_root, generated_at, parts (list of AssemblyPart), and total_length.

Clone this wiki locally