Loading...
Loading...
Open source harness for generating 3D CAD models from text using AI coding agents with build123d/OpenCascade, exporting STEP/STL/URDF, and previewing in a local CAD Explorer viewer.
npx skill4agent add aradotso/trending-skills text-to-cad-harnessSkill by ara.so — Daily 2026 Skills collection.
User prompt → Agent edits models/*.py → Python skill regenerates artifacts → Viewer previews geometrymodels/skills/cad/@cad[...]skills/urdf/viewer/git clone https://github.com/earthtojake/text-to-cad.git
cd text-to-cadpython3.11 -m venv .venv
./.venv/bin/python -m pip install --upgrade pip
./.venv/bin/pip install -r requirements-cad.txtRequires Python 3.11+. Thepins build123d, OCP, and all geometry dependencies.requirements-cad.txt
cd viewer
npm installnpm run devtext-to-cad/
├── models/ # Your CAD source files live here
│ └── my_part/
│ ├── part.py # build123d Python source
│ ├── part.step # Generated STEP export
│ ├── part.stl # Generated STL export
│ └── part.glb # Generated GLB for viewer
├── skills/
│ ├── cad/
│ │ ├── SKILL.md # CAD skill documentation
│ │ └── ...
│ └── urdf/
│ ├── SKILL.md # URDF skill documentation
│ └── ...
├── viewer/ # React/Vite local viewer
│ ├── package.json
│ └── src/
├── requirements-cad.txt # Python dependencies
└── assets/models/# models/bracket/bracket.py
from build123d import *
with BuildPart() as bracket:
# Base plate
with BuildSketch(Plane.XY):
Rectangle(80, 60)
extrude(amount=5)
# Vertical wall
with BuildSketch(Plane.XZ.offset(30)):
Rectangle(80, 40)
extrude(amount=5)
# Fillets on all edges
fillet(bracket.edges(), radius=2)
# Mounting holes
with BuildSketch(bracket.faces().filter_by(Axis.Z).sort_by(Axis.Z)[-1]):
with Locations((-25, -15), (25, -15), (-25, 15), (25, 15)):
Circle(3.5)
extrude(amount=-5, mode=Mode.SUBTRACT)
# Export
export_step(bracket.part, "models/bracket/bracket.step")
export_stl(bracket.part, "models/bracket/bracket.stl")./.venv/bin/python models/bracket/bracket.py# models/hex_spacer/hex_spacer.py
from build123d import *
# Parameters — agent edits these values
OUTER_DIAMETER = 12.0 # mm, across-flats
HEIGHT = 10.0 # mm
HOLE_DIAMETER = 5.0 # mm (M5 clearance)
WALL_THICKNESS = 2.0 # mm
with BuildPart() as spacer:
with BuildSketch(Plane.XY):
RegularPolygon(radius=OUTER_DIAMETER / 2, side_count=6)
extrude(amount=HEIGHT)
with BuildSketch(Plane.XY):
Circle(HOLE_DIAMETER / 2)
extrude(amount=HEIGHT, mode=Mode.SUBTRACT)
fillet(spacer.edges().filter_by(Axis.Z), radius=0.5)
export_step(spacer.part, "models/hex_spacer/hex_spacer.step")
export_stl(spacer.part, "models/hex_spacer/hex_spacer.stl")# models/assembly/assembly.py
from build123d import *
# Base
with BuildPart() as base:
with BuildSketch(Plane.XY):
Rectangle(100, 80)
extrude(amount=10)
# Post
with BuildPart() as post:
with BuildSketch(Plane.XY):
Circle(8)
extrude(amount=50)
# Combine into assembly
assembly = Compound(
children=[
base.part,
post.part.move(Location((0, 0, 10))),
]
)
export_step(assembly, "models/assembly/assembly.step")
export_stl(assembly, "models/assembly/assembly.stl")models/*.pyfrom build123d import *
# STEP — full geometry, use for CAD interchange
export_step(part, "models/my_part/my_part.step")
# STL — mesh for 3D printing / simulation
export_stl(part, "models/my_part/my_part.stl")
# DXF — 2D drawing / laser cutting
section = part.section(Plane.XY)
export_dxf(section, "models/my_part/my_part.dxf")
# GLB — viewer-compatible 3D web format
export_gltf(part, "models/my_part/my_part.glb")skills/urdf/SKILL.mdmodels/my_robot/
├── robot.py # build123d geometry for each link
├── robot.urdf # Generated URDF XML
└── meshes/
├── base.stl
├── arm.stl
└── gripper.stl<!-- models/my_robot/robot.urdf (generated) -->
<?xml version="1.0"?>
<robot name="my_robot">
<link name="base_link">
<visual>
<geometry>
<mesh filename="meshes/base.stl"/>
</geometry>
</visual>
</link>
<link name="arm_link">
<visual>
<geometry>
<mesh filename="meshes/arm.stl"/>
</geometry>
</visual>
</link>
<joint name="base_to_arm" type="revolute">
<parent link="base_link"/>
<child link="arm_link"/>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
<limit lower="-1.57" upper="1.57" effort="10" velocity="1"/>
</joint>
</robot>models/cd viewer
# Start dev server
npm run dev # → http://localhost:4178
# Build for static hosting
npm run build
# Preview production build
npm run previewmodels/@cad[...]@cad[...]@cad[...]# Example agent follow-up using a reference
@cad[models/bracket/bracket.step#face:top] — add a countersunk hole at center1. User: "Create a parametric L-bracket with 4 mounting holes, 5mm thick"
2. Agent: creates models/l_bracket/l_bracket.py using build123d
3. Agent runs: ./.venv/bin/python models/l_bracket/l_bracket.py
→ generates l_bracket.step, l_bracket.stl, l_bracket.glb
4. User: opens http://localhost:4178, inspects the model
5. User copies @cad[...] reference from viewer
6. User: "Make the wall taller — @cad[models/l_bracket/l_bracket.step#face:wall]"
7. Agent edits WALL_HEIGHT parameter in l_bracket.py, reruns script
8. User commits models/l_bracket/ (source + artifacts together)with BuildPart() as panel:
with BuildSketch(Plane.XY):
Rectangle(100, 60)
extrude(amount=3)
# Horizontal slot
with BuildSketch(Plane.XY):
SlottedHole(length=30, radius=3, rotation=0, align=Align.CENTER)
extrude(amount=-3, mode=Mode.SUBTRACT)with BuildPart() as symmetric_part:
with BuildSketch(Plane.XY):
Rectangle(40, 20)
extrude(amount=10)
mirror(about=Plane.YZ)with BuildPart() as box:
Box(60, 40, 30)
# Remove top face to shell into an open container
shell(box.faces().sort_by(Axis.Z)[-1:], thickness=-2)| Problem | Fix |
|---|---|
| Run with |
| Viewer shows no models | Check that |
| Change port in |
| STEP export fails silently | Ensure the part solid is valid — check for |
| Python 3.12+ OCP errors | Pin to Python 3.11 as required by |
| Fillet fails on sharp geometry | Reduce fillet radius or apply after all cuts |
from build123d import *
with BuildPart() as my_part:
Box(50, 50, 20)
# Check validity
assert my_part.part.is_valid, "Part geometry is invalid — check for bad operations"
export_step(my_part.part, "models/my_part/my_part.step")
print(f"Exported: volume={my_part.part.volume:.2f} mm³")| Skill | Docs | Standalone Repo |
|---|---|---|
| CAD (STEP/STL/DXF/GLB) | | earthtojake/cad-skill |
| URDF (robots) | | earthtojake/urdf-skill |
| Package | Purpose |
|---|---|
| Pythonic 3D CAD modelling API |
| Geometry kernel (STEP, Boolean ops) |
| Underlying geometry utilities |
| Viewer frontend |
| In-browser geometry rendering |