Use when the user provides a natural language description of a 3D object or mechanical part and wants to generate a CAD model. Converts the description into CadQuery Python code, automatically detects or sets up the CadQuery environment, executes the script, and produces STL and STEP output files.
This skill converts a natural language description of a 3D object into a fully functional CadQuery Python script, executes it, and delivers STL + STEP files. The workflow is designed to handle everything from simple primitives ("a cube with rounded edges") to complex mechanical assemblies ("a flanged bearing housing with bolt holes").
Before generating any model, automatically detect a working CadQuery environment. Follow this sequence -- stop at the first success:
Check if cadquery is already importable:
python -c "import cadquery; print(cadquery.__version__)"
If this succeeds, use python directly as the interpreter.
Search for conda/mamba environments that have cadquery:
conda env list
For each environment found, test:
conda run -n <env_name> python -c "import cadquery; print(cadquery.__version__)"
If one succeeds, use conda run -n <env_name> python as the interpreter.
Search for virtual environments in the working directory or common locations (.venv, venv, env):
# Linux/macOS
.venv/bin/python -c "import cadquery; print(cadquery.__version__)"
# Windows
.venv/Scripts/python -c "import cadquery; print(cadquery.__version__)"
If no environment found, install cadquery:
pip install cadquery (in current Python)conda install -c conda-forge cadquery (if conda is available)Cache the result: Once a working interpreter command is found, reuse it for all subsequent executions in this session. Store it as CADQUERY_PYTHON (e.g., python, conda run -n myenv python, .venv/bin/python).
If all attempts fail, inform the user and provide manual installation instructions:
pip install cadquery
# or
conda install -c conda-forge cadquery
When the user provides a natural language description:
Parse the description to extract:
Fill in missing details intelligently:
Confirm understanding (brief, 2-3 sentences):
Generate a complete, self-contained CadQuery Python script following these mandatory rules:
"""
CadQuery Model: {model_name}
Description: {user_description}
Generated dimensions: {key_dimensions}
Units: millimeters (mm)
"""
import cadquery as cq
import os
# ============================================================
# Parameters (easy to modify)
# ============================================================
# Group all dimensional parameters at the top for easy tweaking
PARAM_NAME = value # description, unit
# ============================================================
# Output Configuration
# ============================================================
# Output to an "output" folder relative to this script's location.
# The user can override OUTPUT_DIR if they prefer a different path.
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "output")
MODEL_NAME = "{model_name}"
os.makedirs(OUTPUT_DIR, exist_ok=True)
# ============================================================
# Model Construction
# ============================================================
# Build the model step by step with comments explaining each operation
result = (
cq.Workplane("XY")
.box(...)
# ... operations ...
)
# ============================================================
# Export
# ============================================================
step_path = os.path.join(OUTPUT_DIR, f"{MODEL_NAME}.step")
stl_path = os.path.join(OUTPUT_DIR, f"{MODEL_NAME}.stl")
cq.exporters.export(result, step_path)
cq.exporters.export(result, stl_path)
print(f"Model '{MODEL_NAME}' generated successfully!")
print(f" STEP: {step_path}")
print(f" STL: {stl_path}")
# Print bounding box for verification
bb = result.val().BoundingBox()
print(f" Bounding Box: {bb.xlen:.2f} x {bb.ylen:.2f} x {bb.zlen:.2f} mm")
Primitives & Basic Shapes:
cq.Workplane("XY").box(length, width, height) -- centered boxcq.Workplane("XY").cylinder(height, radius) -- centered cylindercq.Workplane("XY").sphere(radius) -- spherecq.Workplane("XY").wedge(dx, dy, dz, xmin, zmin, xmax, zmax) -- wedge/prism2D Sketch -> 3D Extrusion (most versatile pattern):
result = (
cq.Workplane("XY")
.moveTo(x, y).lineTo(...).lineTo(...).close() # sketch profile
.extrude(height) # or .revolve(angleDegrees, axisStart, axisEnd)
)
Feature Operations:
.fillet(radius) -- round all edges (use with .edges("|Z") etc. for selective).chamfer(distance) -- chamfer edges.hole(diameter, depth=None) -- through or blind hole at center.cboreHole(diameter, cboreDiameter, cboreDepth) -- counterbore hole.cskHole(diameter, cskDiameter, cskAngle) -- countersink hole.shell(thickness) -- hollow out (negative = inward)Face/Edge Selection (critical for targeted operations):
.faces(">Z") -- topmost face in Z.faces("<Z") -- bottommost face in Z.edges("|Z") -- edges parallel to Z.edges(">Z") -- topmost edges in Z.edges("%Circle") -- circular edges.faces("+Z") -- faces with normal pointing in +Z directionBoolean Operations:
.cut(other_shape) -- subtract.union(other_shape) -- add.intersect(other_shape) -- intersectionPatterns & Arrays:
.pushPoints([(x1,y1), (x2,y2), ...]) -- place features at points.rarray(xSpacing, ySpacing, xCount, yCount) -- rectangular array.polarArray(radius, startAngle, angle, count) -- circular arrayAdvanced:
.sweep(path) -- sweep a profile along a path.loft() -- loft between profiles.twistExtrude(height, angleDegrees) -- helical extrusion.text("text", fontsize, distance) -- embossed/engraved text.mirror("XY") -- mirror about a plane.translate((x, y, z)) -- move.rotate((0,0,0), (0,0,1), angleDeg) -- rotateMulti-body / Assembly Pattern:
part_a = cq.Workplane("XY").box(10, 10, 10)
part_b = cq.Workplane("XY").transformed(offset=(20, 0, 0)).cylinder(10, 5)
result = part_a.union(part_b)
flange_diameter, not d1.fillet() which often fails.fillet() with radius >= smallest edge length -> crash. Always use conservative radii..shell() on complex geometry with thin walls -> often fails. Keep wall thickness reasonable..clean() -> geometry corruption. Add .clean() after complex booleans..box() and .cylinder() are centered by default..faces(">Z").fillet() when there are multiple faces at the same Z height -> ambiguous selection..fillet() before .cut() -- fillet edges may be destroyed by the cut.{working_dir}/{model_name}.py.{CADQUERY_PYTHON} {working_dir}/{model_name}.py
Where {CADQUERY_PYTHON} is the cached interpreter command from environment detection.If execution fails, follow this diagnostic protocol:
| Error Type | Diagnosis | Fix Strategy |
|---|---|---|
Standard_ConstructionError | Fillet/chamfer radius too large | Reduce radius to 50% of smallest adjacent edge |
BRep_API: not done | Boolean operation failed | Add .clean() before boolean; simplify geometry |
StdFail_NotDone | Impossible geometric operation | Re-order operations; split into sub-steps |
ValueError: No wire found | Unclosed sketch profile | Ensure .close() is called; check .lineTo() endpoints |
Selector found no objects | Face/edge selector matched nothing | Use simpler selectors; print available faces/edges for debugging |
ModuleNotFoundError | Missing package | Install via pip install {package} using the detected interpreter's environment, then retry. If cadquery itself is missing, re-run Phase 0 |
MemoryError or timeout | Model too complex | Reduce polygon count; simplify fillets |
Debug approach:
print(result.faces().vals()) to inspect geometry stateAfter successful execution:
Verify output files exist and have non-zero size
Report to user:
Offer follow-up options:
If the user requests changes:
Bolt/Screw:
head = cq.Workplane("XY").cylinder(head_height, head_radius)
shaft = cq.Workplane("XY").workplane(offset=-head_height).cylinder(shaft_length, shaft_radius)
result = head.union(shaft)
Gear (simplified profile):
result = (
cq.Workplane("XY")
.circle(outer_radius)
.extrude(thickness)
.faces(">Z")
.workplane()
.hole(bore_diameter)
.faces(">Z")
.workplane()
.polarArray(pitch_radius, 0, 360, num_teeth)
.rect(tooth_width, tooth_height)
.cutThruAll()
)
Enclosure/Box with lid:
body = cq.Workplane("XY").box(L, W, H).edges("|Z").fillet(corner_r).shell(-wall)
lid = cq.Workplane("XY").workplane(offset=H/2).box(L, W, lid_h).edges("|Z").fillet(corner_r)
Pipe/Tube:
result = (
cq.Workplane("XY")
.circle(outer_radius)
.circle(inner_radius) # concentric circle creates annular profile
.extrude(length)
)
Flange:
result = (
cq.Workplane("XY")
.circle(flange_radius).extrude(flange_thickness)
.faces(">Z").workplane()
.circle(pipe_radius).extrude(pipe_length)
.faces("<Z").workplane()
.pushPoints(bolt_hole_positions)
.hole(bolt_hole_diameter)
.faces("<Z").workplane()
.hole(bore_diameter)
)
Always respond in the same language as the user's message. If the user writes in Chinese, respond in Chinese. If in English, respond in English.