Use this skill whenever the user wants to create, modify, or iterate on 3D printable models. Triggers include: any mention of '3D print', 'STL', 'parametric model', 'enclosure', 'bracket', 'mount', 'case', 'housing', 'CadQuery', 'OpenSCAD', 'CAD', or requests to design physical objects. Also use when the user mentions their 3D printer (e.g. Bambu Lab, Prusa, Ender), asks about print-friendly design, snap-fits, tolerances, or wants to create functional parts like Arduino enclosures, cable organizers, wall mounts, adapters, or mechanical components. Use this skill even if the user just describes a physical object they want to make without explicitly mentioning 3D printing. Do NOT use for 3D rendering, game assets, or non-manufacturing 3D work.
This skill generates parametric 3D models using CadQuery (Python) and exports them as STL files ready for slicing. CadQuery is preferred because it installs via pip, has a Pythonic API, and handles complex geometry (fillets, chamfers, booleans, assemblies) better than alternatives.
# CadQuery requires Python 3.10-3.12 (OCC kernel lacks 3.13+ wheels)
python3.12 -m venv .venv && source .venv/bin/activate
# Install CadQuery and preview dependencies
pip install cadquery trimesh pyrender Pillow
CadQuery uses the OpenCASCADE kernel under the hood. trimesh, pyrender, and Pillow are used for the preview-analyze-iterate loop. No display server is needed — everything renders headlessly via pyrender's offscreen backend.
If CadQuery fails to install (OCC kernel build errors), try:
# Option 1: Use conda (CadQuery's officially recommended method)
conda install -c cadquery -c conda-forge cadquery
# Option 2: Use the pre-built wheels
pip install cadquery --find-links https://github.com/CadQuery/CadQuery/releases
When designing objects that interface with real products (phones, chargers, PCBs, connectors, etc.), use web search to find accurate dimensions before writing any geometry code. Don't guess or use approximate values — even 1-2mm off can make a part unusable.
What to research:
How to use it:
# MagSafe puck dimensions (source: Apple spec + iFixit teardown)
puck_diameter = 56.0 # mm
puck_thickness = 5.6 # mm
This is especially important for: phone cases/stands, charger mounts, PCB enclosures, cable management, adapter fittings, and anything that clips onto or wraps around an existing product.
This is a collaborative, show-as-you-go process. Do NOT disappear and come back with a finished model. Show the user your progress at each phase and incorporate their feedback before moving on.
Before writing any code, walk through these topics with the user conversationally. Don't dump all questions at once — ask the most important ones first, then follow up based on answers. Use reasonable defaults when the user doesn't specify.
What is it? Object type, purpose, what it holds/protects/attaches to. Get a clear mental model of the object before anything else.
Critical dimensions Must-fit measurements — PCB size, phone width, screw spacing, diameter of the thing it wraps around, etc. These are non-negotiable and drive everything else.
Mounting & attachment How does it connect to things? Screws (what size?), snap-fit, adhesive tape, magnets, freestanding on a desk? This affects wall thickness, boss placement, and overall structure.
Printer & material What printer do they have? (Bambu, Prusa, Ender, etc.) Nozzle size? Material — PLA, PETG, TPU? This directly affects tolerances, minimum feature sizes, and design constraints. Defaults: 0.4mm nozzle, PLA, 0.2mm layer height.
Functional needs Ventilation/airflow, water resistance, cable routing, access panels, visibility windows, stacking, weight limits. Ask only what's relevant to the object.
Aesthetic preferences Rounded vs sharp edges, minimal vs industrial look, color considerations (affects visibility of layer lines). Ask briefly — most users care more about function than form.
Start with the first two (what + dimensions), then ask about mounting and material if relevant. Only ask about aesthetics if the user seems to care or if it affects structural choices.
Build the model in phases. At each phase, export an STL, render a preview, self-review it, then show it to the user and ask for feedback before proceeding. This catches problems early and keeps the user involved.
python3 preview.py model.stl preview.png --views multi
Then view the preview image, self-review it against the checklist in design-review.md, and fix any issues you spot before showing it to the user.
Build the basic outer form: overall dimensions, shell/walls, bottom plate. No cutouts, no fillets, no details yet.
Add functional details: holes, cutouts, mounting bosses, cable slots, ventilation, snap-fits, internal structures.
Apply finishing touches: fillets, chamfers, edge cleanup. Do a full printability review.
design-review.md — visual inspection, dimensional verification, printability analysisImportant: Do NOT skip phases or combine them unless the model is very simple (e.g., a flat bracket with two holes). For anything with enclosed geometry, multiple features, or tight tolerances, follow all three phases.
Read design-review.md for the full visual inspection checklist, dimensional verification code, and printability analysis helpers.
ALWAYS structure scripts like this:
import cadquery as cq
# ============================================================
# PARAMETERS - Edit these to customize the model
# ============================================================
# Overall dimensions
width = 60.0 # mm - outer width
depth = 40.0 # mm - outer depth
height = 25.0 # mm - outer height
# Wall and structural
wall = 2.0 # mm - wall thickness (min 1.2 for FDM)
corner_r = 2.0 # mm - corner fillet radius
# Tolerances
fit_clearance = 0.3 # mm - clearance for press-fit (adjust per printer)
# ============================================================
# MODEL
# ============================================================
result = (
cq.Workplane("XY")
.box(width, depth, height, centered=(True, True, False))
# ... build geometry using parameters above (bottom at Z=0)
)
# ============================================================
# EXPORT
# ============================================================
cq.exporters.export(result, "output.stl")
print(f"Exported: {width}x{depth}x{height}mm")
screw_hole_d, not d1Key FDM design defaults:
| Property | Minimum | Recommended |
|---|---|---|
| Wall thickness | 1.2mm | 2.0mm |
| Layer height | 0.08mm | 0.2mm |
| Hole clearance | 0.2mm | 0.3mm |
| Press-fit interference | 0.1mm | 0.15mm |
| Min feature size | 0.4mm (nozzle) | 0.8mm |
| Fillet radius (bottom) | 0.5mm | 1.0mm |
| Bridge span | - | < 20mm unsupported |
| Overhang angle | - | < 45 degrees from vertical |
Material-specific adjustments: TPU needs larger clearances (~0.5mm) due to flex. PETG is stickier, so add +0.1mm to fit clearances. ABS shrinks ~0.5-0.7%, so scale critical dimensions up slightly. When in doubt, print a small test piece first.
Common patterns to know:
Hollow enclosure:
result = (
cq.Workplane("XY")
.box(width, depth, height, centered=(True, True, False))
.edges("|Z").fillet(corner_r)
.shell(-wall) # negative = shell inward
)
Screw boss:
.pushPoints([(x, y)])
.circle(boss_od / 2).extrude(boss_h)
.pushPoints([(x, y)])
.hole(screw_d + fit_clearance)
Snap-fit clip:
# Cantilever beam with overhang hook
.workplane(offset=wall)
.moveTo(x, y).rect(clip_w, clip_l).extrude(clip_h)
# Add hook at tip with a small overhang (< 45 deg)
Ventilation grid:
.pushPoints(vent_positions)
.slot2D(slot_l, slot_w).cutThruAll()
Other patterns: mounting brackets, cable routing channels, text/labels (.text()), multi-part assemblies with alignment pins.
.shell() first (hollow out), then .fillet() (round edges), then boolean cuts (holes, slots, pockets). Shelling a filleted body often fails, and filleting edges left by cuts is fragile.centered=(True, True, False) on .box() to place bottom at Z=0..hole() cuts through the entire part by default. Use .cboreHole() or .cskHole() for counterbore/countersink..extrude(taper=angle), a positive taper angle narrows the shape (draft inward), negative flares it outward. This is opposite to what many people expect..shell() frequently fails on tapered bodies, lofted shapes, or geometry with many fillets. The reliable alternative is boolean subtraction — create the outer solid, create a slightly smaller inner solid, then use outer.cut(inner) to hollow it out. This gives you full control over wall thickness at every point..loft() fails on many cross-section combinations. Prefer .extrude(taper=angle) when transitioning between a shape and a scaled version of itself. Only use .loft() when you need to transition between genuinely different profiles (e.g., circle to rectangle).# STL (for slicing)
cq.exporters.export(result, "model.stl")
# STEP (for further CAD editing)
cq.exporters.export(result, "model.step")
# Both formats
for fmt in ["stl", "step"]:
cq.exporters.export(result, f"model.{fmt}")
Always export STL for printing. Optionally export STEP if the user might want to edit in Fusion 360 or similar.
For models with multiple parts (e.g., enclosure + lid):
# Export each part separately
cq.exporters.export(body, "enclosure_body.stl")
cq.exporters.export(lid, "enclosure_lid.stl")
Name files descriptively so the user knows which part is which.
After delivering the final model, always present the key parameters as a summary table and offer to tweak them. This lets the user fine-tune without re-explaining the whole design.
Example:
Here's your final model! Current parameters:
| Parameter | Value |
|----------------|--------|
| Width | 90 mm |
| Depth | 65 mm |
| Height | 30 mm |
| Wall thickness | 4 mm |
| Cable slot | 18 mm |
| Corner radius | 3 mm |
| Fit clearance | 0.3 mm |
Want me to adjust anything? Just say e.g. "make it 5mm taller" or "wider cable slot."
Only include parameters the user would plausibly want to change — skip internal constants like eps or nozzle_d. Group them logically (dimensions first, then structural, then tolerances).
Before delivering a model, verify: