# Openscad

> Programmatic 3D CAD with OpenSCAD. Fast path for simple printable parts (Quick mode) and for editing an existing STL without reconstructing it (Modify mode), plus full design, STL-to- parametric reconstruction, and export. Triggers on: 3D model, STL, 3D print, parametric design, openscad, CAD, enclosure, bracket, spacer, adapter, holder, "modifica questo STL", or any 3D modeling task.

- Skill: `andreahaku/openscad` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add andreahaku/openscad`
- Raw SKILL.md: https://api.skillmd.com/api/skills/andreahaku/openscad/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: andreahaku (https://skillmd.com/u/andreahaku)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/andreahaku/openscad

---


# OpenSCAD Skill

Design, render, preview, and export 3D models using OpenSCAD's programmatic CAD engine.

**Speed comes from picking the right mode, not from cutting corners inside one.** Generating
code takes seconds in every mode; what costs time is asking questions that could have been
guessed, rendering four views when one answers, reconstructing a mesh that only needed a hole
moved, and printing a part whose clearances were never measured. The mode table below exists
to spend that time only where it buys something.

## Environment

- **OpenSCAD binary**: resolved at runtime by every script as `$OPENSCAD_BIN`, else
  `command -v openscad`. Linux: `sudo pacman -S openscad` (Arch) or the distro equivalent.
  macOS: `brew install openscad`. Never hardcode a path.
- **Python deps** (Reconstruct and Replicate only — Quick, Modify, Design and Export do not
  need them): `trimesh`, `numpy`, `scipy`, `shapely`, `rtree`. On Arch install them from the
  repos (`python-trimesh` and friends), not with pip: the interpreter is externally managed.
- **Working directory for designs**: `~/openscad-projects/`, one subdirectory per project.
  Quick mode is the exception and writes flat files into `~/openscad-projects/_quick/`.
- **Skill scripts**: `~/.claude/skills/openscad/scripts/`
- **Templates**: `~/.claude/skills/openscad/templates/`
- **Printer profile**: `~/.claude/skills/openscad/templates/printer-profile.scad` — the
  measured behaviour of the actual printer. Read it before emitting any part whose function
  depends on a fit, and say out loud when `profile_measured` is still false.
- **Language reference**: `~/.claude/skills/openscad/references/`

## Modes

The skill operates in eight modes, auto-detected from the user's request. The first two are
the fast ones and cover most real requests — reach for the others only when they are the job.

| Mode | Use it when | Cost |
|---|---|---|
| **Quick** | "I need a simple part, now" — a spacer, a bracket, a holder, an adapter | minutes |
| **Modify** | An STL already exists and is nearly right: a hole to move, 2mm to add, a face to flatten | minutes |
| **Design** | A new part with real requirements, several features, dimensions that must be discussed | tens of minutes |
| **Replicate** | Reproduce a physical object starting from photographs | an hour or more |
| **Reconstruct** | Turn an STL mesh back into parametric code you can own and edit | hours |
| **Refine** | Iterate on an existing .scad (change, preview, repeat) | depends |
| **Export** | Render the final STL/3MF for printing | seconds |
| **Analyze** | Review an existing design for printability | minutes |

**Choosing between Quick, Modify and Design.** Quick guesses and shows; Design asks and then
shows. If the part is simple enough that wrong guesses are cheap to correct on screen, Quick
wins because it costs one round trip instead of two. If the user hands you an existing STL,
Modify beats both — and it beats Reconstruct too, unless they actually need the geometry to
become parametric.

---

## Workflow: Quick Mode

**The default for "I need a simple part".** The point is one round trip: the user describes
the object and gets back a rendered part plus the list of everything that was assumed. They
correct the assumptions that are wrong instead of answering a questionnaire first.

### Step 1: Do not ask. Infer, and write down what you inferred.

Skip the clarification round. Derive whatever can be derived from the request, pick defensible
defaults for the rest, and **keep an explicit list of every value you invented**. That list is
what you hand back, and it is what makes guessing safe.

Standing defaults, unless the request says otherwise: wall 2mm, floor 2mm, corner radius 2mm,
`fit_clearance("close")` for anything receiving a screw or a shaft, `$fn = 64`, flat bottom on
the bed, no support.

Only stop and ask when a missing number makes the part **meaningless** rather than merely
wrong — you cannot invent the diameter of the tube a holder has to hold. One question, then
proceed.

### Step 2: Start from the library, not from an empty file

```bash
mkdir -p ~/openscad-projects/_quick
```

Write a single file `~/openscad-projects/_quick/<name>.scad`. No project scaffolding, no `src/`
and `output/` tree: a quick part that needs a directory structure is not quick.

```openscad
use <printable-lib.scad>   // resolved via OPENSCADPATH, set by the skill scripts
```

⚠️ **Never write a `~` inside `use <>` or `include <>`.** OpenSCAD does not expand the tilde,
and the failure is silent: the library is simply not found and every module call errors out.
The skill scripts export `OPENSCADPATH` pointing at `templates/`, so the bare filename works
from any directory. If you invoke `openscad` by hand outside those scripts, export it yourself:

```bash
export OPENSCADPATH=~/.claude/skills/openscad/templates
```

The library already carries counterbores, heat-set bosses, screw posts, ribs, snap tabs,
rounded boxes, shells, vents and lid lips, and those modules already read the printer profile.
Keep the Feature Tree structure from Design mode (parameters, derived, profile, body, add, cut,
assembly): it costs nothing and makes the next change trivial.

### Step 3: Deterministic gate, before you look at anything

```bash
bash ~/.claude/skills/openscad/scripts/openscad-validate.sh ~/openscad-projects/_quick/<name>.scad
```

Check what can be computed rather than seen:
- it compiles, with no warnings about unassigned variables
- the bounding box matches the dimensions the user actually gave
- no wall thinner than `min_wall`, no floor thinner than `min_floor` (printer profile)
- overhangs within `max_overhang`, or support is called out
- the part fits `bed_size`

⚠️ `openscad-validate.sh` is a **report, not a gate**: it exits 0 even when the file is broken.
Read the `Category:` line — `OK` passes, anything else (`SYNTAX_ERROR`, `WARNING`, …) stops the
part here. Do not rely on the exit code or on `set -e` to catch it.

A part that fails here never reaches the render. The eye is for shape; these are arithmetic,
and arithmetic should not cost a vision call.

### Step 4: One render, isometric

```bash
bash ~/.claude/skills/openscad/scripts/openscad-render.sh quick ~/openscad-projects/_quick/<name>.scad
```

One image, not four. Read it and check the shape is what was asked for. Four angles belong to
Design mode, where the geometry is complex enough to hide something.

### Step 5: Hand it back with the assumptions visible

Report, in this order:
1. the preview
2. **the assumptions list** — every invented number, one per line, so the wrong ones are
   obvious at a glance
3. the parametric knobs, so a change is a `-D` away and not a rewrite
4. ⚠️ if `profile_measured` is false and the part has a fit, say that the clearances are
   declared defaults and not measurements, and that the first print is the one that tells

Then stop. The user corrects an assumption or asks for the STL. Do not iterate on your own
initiative: in Quick mode a second unrequested render is wasted time.

### Step 6: Export, when asked

```bash
bash ~/.claude/skills/openscad/scripts/openscad-render.sh export ~/openscad-projects/_quick/<name>.scad
```

**When to abandon Quick mode:** the moment the part grows a third interacting feature, or a
dimension turns out to depend on a measurement nobody has, switch to Design mode and say so.
Quick mode that keeps iterating is Design mode with worse manners.

---

## Workflow: Modify Mode

**The fastest useful thing in this skill, and the most common real job.** A model already
exists — downloaded, or printed once and not quite right — and it needs one change: a hole
somewhere else, two millimetres more clearance, a boss added, a face flattened.

**Do not reconstruct it.** Reconstruction turns a mesh back into parametric code and costs
hours. Modify treats the mesh as a solid and cuts into it, which costs minutes and needs
nothing beyond `openscad` itself: no Python, no analysis pipeline.

### Step 1: Import and see it

```openscad
// modify.scad
$fn = 64;
eps = 0.01;
original = "/absolute/path/to/model.stl";

import(original, convexity = 10);
```

`convexity` matters: without it a preview of a mesh with internal cavities renders wrong.
Render once with `openscad-render.sh quick` and confirm you are looking at the right object.

### Step 2: Find the coordinates you need

The mesh arrives in its own coordinate system, which is rarely the one you want.

```bash
python3 ~/.claude/skills/openscad/scripts/openscad-profile-extract.py model.stl --json /tmp/m.json
```

If the Python stack is not available, the bounding box alone covers most edits, and OpenSCAD
gives it for free by rendering the model against a known reference cube. Do not guess
coordinates from a picture: that is Rule 2 of Reconstruct mode, and it applies here just as
hard.

### Step 3: Cut, add, or trim

```openscad
difference() {
    import(original, convexity = 10);

    // a new hole, positioned against a real reference, never a magic number
    translate([hole_x, hole_y, -eps])
        cylinder(h = part_h + 2*eps, d = 4 + fit_clearance("close"));
}
```

The three edits that cover almost everything:

| Need | Shape |
|---|---|
| New hole, or an existing one widened | `difference()` with a cylinder through it |
| Add material (boss, rib, tab, packing) | `union()` with a module from `printable-lib.scad` |
| Flatten a face, cut a part away | `intersection()` with a large cube, or `difference()` with one |

**Widening an existing hole is a subtraction, not an edit**: put a larger cylinder exactly on
the old axis. Finding that axis is the whole job, and it comes from measurement, not from the
render.

⚠️ **Booleans on an imported mesh are only as sound as the mesh.** If the STL is not manifold,
OpenSCAD will still produce something and it will be wrong. Check first:

```bash
bash ~/.claude/skills/openscad/scripts/openscad-stl-analyze.sh model.stl
```

A non-manifold input is the one case where Modify is not the answer: repair the mesh first, or
reconstruct it.

### Step 4: Verify against the original

```bash
bash ~/.claude/skills/openscad/scripts/openscad-stl-compare.sh model.stl modified.stl /tmp/cmp/
```

The boolean difference should show **exactly** the change that was asked for and nothing else.
A surprise elsewhere in the diff means a coordinate is wrong, and seeing it here is far cheaper
than seeing it after the print.

### Step 5: Export

```bash
bash ~/.claude/skills/openscad/scripts/openscad-render.sh stl ~/openscad-projects/_quick/modify.scad
```

The result is a mesh, not parametric code: the change is repeatable by editing `modify.scad`,
but the original geometry stays opaque. **That is the trade** — minutes instead of hours, at
the price of not owning the shape. When the user needs to own it, that is Reconstruct mode, and
it should be chosen deliberately rather than fallen into.

---

## Workflow: Design Mode

When the user asks to create a new 3D object:

### Step 1: Understand Requirements

Clarify with the user:
- **What** is the object? (enclosure, bracket, gear, container, etc.)
- **Dimensions** — key measurements in mm
- **Purpose** — functional print, aesthetic, mechanical fit?
- **Constraints** — printer bed size, material, wall thickness preferences
- **Parametric?** — which dimensions should be adjustable?

### Step 2: Set Up Project

```bash
bash ~/.claude/skills/openscad/scripts/openscad-project.sh init "<project-name>"
```

This creates `~/openscad-projects/<project-name>/` with subdirectories for source, output, and previews.

### Step 3: Generate the .scad File

Write the OpenSCAD code to `~/openscad-projects/<project-name>/src/main.scad`.

**Mandatory file structure (Feature Tree pattern):**
```openscad
// 1. PARAMETERS (independent variables)
width = 60;  height = 30;  wall = 2;

// 2. DERIVED DIMENSIONS (calculated from parameters)
inner_width = width - 2 * wall;

// 3. BASE PROFILE (2D sketch — the core shape)
module sketch_base() {
    offset(r = corner_r)
        square([width - 2*corner_r, depth - 2*corner_r], center=true);
}

// 4. PRIMARY BODY (extrude the sketch)
module body() { linear_extrude(height = height) sketch_base(); }

// 5. ADDITIVE FEATURES (bosses, ribs, tabs)
module features_add() { ... }

// 6. SUBTRACTIVE FEATURES (holes, slots, pockets — ALWAYS LAST)
module features_cut() { ... }

// 7. ASSEMBLY (the Feature Tree)
difference() {
    union() { body(); features_add(); }
    features_cut();
}
```

**Profile-first design rules:**
- Prefer `polygon()` + `linear_extrude()` over `hull()` of 3D primitives
- Use `offset(r=radius)` for corner rounding instead of `hull()` with cylinders
- Use `rotate_extrude()` for axially symmetric parts (never stack cylinders)
- Define dimensions relative to edges/features, not absolute coordinates: `hole_x = total_length - edge_margin` (not magic numbers)
- Cascade tolerances from a single `fit_clearance` parameter

**Critical rules for generating OpenSCAD code:**
- Read `~/.claude/skills/openscad/references/language-reference.md` if unsure about syntax
- Always define parametric dimensions as variables at the top of the file
- Use `$fn = 64;` for smooth curves (or higher for final renders)
- Add comments explaining each section
- Use modules for reusable parts
- Keep wall thickness >= 1.2mm for FDM printing
- Design with the print orientation in mind (flat bottom, minimal overhangs)

### Step 4: Preview

Render a multi-angle PNG preview:

```bash
bash ~/.claude/skills/openscad/scripts/openscad-render.sh preview ~/openscad-projects/<project-name>/src/main.scad
```

This generates 4 preview images (front, side, top, isometric) in the project's `previews/` directory.

### Step 5: Analyze Preview

Read each preview PNG using the Read tool to see the rendered object. Evaluate:
- Does the shape match the user's description?
- Are proportions correct?
- Are there visible artifacts or unintended geometry?
- Would this print well? (overhangs, bridging, thin walls)

Report findings to the user with the preview images.

### Step 6: Iterate

If changes are needed, edit the .scad file and re-render. Repeat Steps 4-5 until the user is satisfied. Each iteration should be targeted — change one aspect at a time.

### Step 7: Export

When the design is approved:

```bash
bash ~/.claude/skills/openscad/scripts/openscad-render.sh export ~/openscad-projects/<project-name>/src/main.scad
```

This produces:
- `output/model.stl` — for slicing and printing
- `output/model.3mf` — alternative format (better metadata)
- `previews/final-preview.png` — high-res final render

---

## Workflow: Replicate Mode

When the user provides reference images of a physical object to reproduce in OpenSCAD:

### Step 1: Analyze Reference Images

Read ALL provided reference images using the Read tool. For each image, extract:
- **Overall shape**: What geometric primitives compose this object?
- **Proportions**: Relative dimensions (height-to-width ratio, etc.)
- **Features**: Holes, fillets, chamfers, textures, slots, lips, threads
- **Symmetry**: Is it symmetric along any axis?
- **Construction**: How would you decompose it into boolean operations?

If dimensions are provided, note them. If not, estimate proportions from the images and ask the user for at least one known measurement to establish scale.

### Step 2: Create Decomposition Plan

Before writing any code, describe the object as a series of OpenSCAD operations:

```
Object: Phone stand
Decomposition:
1. Base: flat rectangle with rounded corners (80x60x5mm)
2. Back support: angled plate (60x3mm, tilted 70 degrees)
3. Front lip: small ridge to hold phone (60x3x8mm)
4. Fillet: smooth transition between base and back support
5. Cable channel: cylinder subtracted from base center
```

Present this plan to the user for confirmation before coding.

### Step 3: Generate Initial .scad File

Write the OpenSCAD code based on the decomposition. Set up the project:

```bash
bash ~/.claude/skills/openscad/scripts/openscad-project.sh init "<object-name>"
```

Write the .scad to the project's `src/main.scad`.

### Step 4: Render and Compare

Generate a preview from the **same angle** as the reference image:

```bash
bash ~/.claude/skills/openscad/scripts/openscad-render.sh quick ~/openscad-projects/<name>/src/main.scad
```

Read both the reference image and the rendered preview. Compare them side by side mentally:
- Does the overall silhouette match?
- Are proportions correct?
- Are features (holes, edges, curves) in the right places?
- What's the biggest discrepancy?

### Step 5: Iterative Refinement Loop

For each discrepancy found:
1. Identify which part of the .scad code controls the mismatched feature
2. Make a **single targeted edit** to improve the match
3. Re-render from the same angle
4. Re-compare with the reference

**Refinement priorities** (fix in this order):
1. Overall shape and proportions
2. Major features (holes, cutouts, protrusions)
3. Angles and curves
4. Fillets, chamfers, and surface details
5. Fine details

### Step 6: Multi-Angle Validation

Once the primary angle looks good, render from all angles that have reference images:

```bash
bash ~/.claude/skills/openscad/scripts/openscad-render.sh preview ~/openscad-projects/<name>/src/main.scad
```

Compare each rendered view against its corresponding reference image. Fix any angle-specific discrepancies.

### Step 7: Dimensional Verification

If the user provided measurements, add `echo()` statements to verify:

```openscad
echo("Total width:", width);
echo("Total height:", height);
echo("Wall thickness:", wall);
```

Render with echo capture to verify dimensions match specifications.

### Step 8: Export

When the user confirms the replication is satisfactory, export for printing.

### Tips for Accurate Replication

- **Start simple**: Begin with bounding-box primitives, then refine
- **Use reference dimensions**: If user says "it's about 10cm tall", anchor ALL proportions to that
- **Match camera angle**: Use `--camera` to match the reference photo's perspective
- **Organic shapes**: Approximate with hull(), minkowski(), or rotate_extrude() of a profile
- **Iterate small**: Change one thing per render cycle
- **Ask when unsure**: If a feature is ambiguous from the images, ask the user rather than guessing

---

## Workflow: Reconstruct Mode (STL-to-SCAD)

When the user provides an STL file and wants it converted to parametric OpenSCAD code:

### Overview

STL files are triangle meshes with no semantic information about the original primitives or operations that created them. Reconstruction is the process of analyzing the mesh geometry and re-expressing it as clean, parametric OpenSCAD code. This is valuable because:
- Parametric code can be modified (change dimensions, add features)
- OpenSCAD code is human-readable and version-controllable
- The resulting model can be adapted to different use cases

### Critical Rules for Reconstruction

**Read `references/reconstruction-guide.md` before starting any reconstruction.** It contains the complete best practices guide learned from real reconstructions.

**Rule 1: SCULPTOR APPROACH (mandatory).** Start from a full solid block, subtract ALL features. Never build up from pieces — CSG ordering bugs cause added material to cover previously-cut holes. Structure: `difference() { solid_body(); channels(); tapers(); ALL_holes_LAST(); }`

**Rule 2: NEVER add features based on assumptions.** Always verify with SVG contour data AND reference images. If a feature doesn't appear as a separate contour in the SVG slices, IT DOES NOT EXIST. Known hallucinations to avoid:
- Pyramids/cones from render shadows
- Cylinders from curved wall edges
- Top holes from through-hole exit points

**Rule 3: Bounding box match ≠ correct model.** 0.000mm bbox delta can mean only 70% geometric accuracy. Always use mesh comparison (`openscad-stl-compare.sh`) with boolean diff images.

**Rule 4: ANALYZE FIRST, DECOMPOSE, THEN CHOOSE per-component approach.**
Do NOT jump to code. The analysis phase must answer these questions:
1. **What are the dominant features?** (diagonal arm, clips, channels, holes)
2. **What is the thinnest axis?** That's the likely extrusion direction — NOT necessarily the stability score winner
3. **Can the object be decomposed into simpler sub-objects?** Model each with its best technique
4. **Where are internal channels/gaps?** Slice along Z to find multi-body cross-sections

**Rule 5: Choose the extrusion axis by geometry, not just stability score.**

The profile extractor's stability score finds the axis with the most uniform cross-section. But this is misleading for models with diagonal features — slicing along Z for a diagonal bracket produces staircase artifacts. Instead:

| Model Type | Best Extrusion Axis | Why |
|-----------|-------------------|-----|
| Flat bracket/plate | Thinnest axis (smallest extent) | Profile in the wide plane captures all detail |
| Diagonal/angled arm | Thinnest axis | Diagonals live in the plane of the two longest axes |
| Clean extrusion (stability < 0.1) | Stability-score axis | Profiles are identical → stability is reliable |
| Cylindrical (stability > 0.3) | Object's rotational axis | Use rotate_extrude or parametric primitives |
| Truly complex (no good axis) | Dense multi-axis slabbing | 2mm slabs along thinnest axis |

```bash
# Always run ALL analysis tools before writing any code:
bash ~/.claude/skills/openscad/scripts/openscad-stl-reconstruct.sh model.stl analysis/
python3 ~/.claude/skills/openscad/scripts/openscad-profile-extract.py model.stl --json analysis/profile.json
python3 ~/.claude/skills/openscad/scripts/openscad-adaptive-slice.py model.stl analysis/
```

After analysis, compare: **thinnest axis extent** vs **stability-score axis**. If they differ, the thinnest axis is usually better for models with angled features.

**Rule 6: Choose the right technique for each component:**

| Geometry | Best Approach | Expected Accuracy |
|----------|--------------|-------------------|
| Flat/angular (brackets, plates) | Profile extraction + linear_extrude | 90-96% |
| Diagonal features (angled arms, tapers) | Profile along thinnest axis + linear_extrude | 85-92% |
| Simple known shapes (stadium, box) | Parametric primitives + SDF optimizer | 90-96% |
| Cylindrical features (puzzle tabs, bosses) | Parametric circle() + square() | 85-95% |
| Smooth transitions (convex shapes only) | hull() between boundary profiles | 85-90% |
| Multi-width models (width varies along axis) | Dense X-slab (2mm profiles along thinnest axis) | ~92% |
| Mixed (curves + flats) | Polygon profile (hi-res, tol=0.02) | 70-80% |
| Complex organic shapes | import() original STL + parametric modifications | N/A |

**Rule 7: hull() ONLY for convex profiles.** Hull between two profiles creates the convex hull — it fills in ALL concavities (channels, clips, U-forks, hooks). Only use hull for simple solid zones with 1 contour and no holes. For concave profiles, use linear_extrude of a representative profile instead.

**Rule 8: Dense X-slab approach for complex models.**
When no single extrusion works, slice every 2mm along the thinnest axis:
1. At each X position, extract the full Y-Z cross-section (ALL bodies, not just the largest)
2. Extrude each slab for 2mm width
3. Union all slabs — gaps between bodies are naturally preserved
4. Note: this approach has a ~6% volume overestimate floor from polygon extraction artifacts. Below 6% requires hand-modeled parametric geometry.

**Use the automated reconstruction analysis FIRST — before writing any code:**
```bash
bash ~/.claude/skills/openscad/scripts/openscad-stl-reconstruct.sh model.stl output_dir/
```

This runs the full pipeline: mesh stats (trimesh), 2D profile slices (OpenSCAD projection), primitive detection (RANSAC/normal analysis), and generates SVG profiles at multiple Z levels. The SVG profile analysis is the MOST IMPORTANT output — it reveals the complete cross-section structure at each height level.

**The SVG Profile Method** (preferred over vertex analysis):
1. `projection(cut=true)` slices the STL at a Z height → exports 2D SVG
2. Parse the SVG to count contours: BODY (large area) vs HOLES (small area)
3. Compare contours at different Z levels to understand how the shape changes with height
4. This reveals: channels, slots, holes, wall thickness, taper angles — all from 2D data

**After analysis, verify with mesh comparison:**
```bash
bash ~/.claude/skills/openscad/scripts/openscad-stl-compare.sh original.stl reconstruction.stl output_dir/
```
Target: >95% geometric accuracy. Use diff images to identify remaining discrepancies.

### Step 1: Automated Analysis (run ALL tools)

```bash
# Tool 1: SVG profiling + primitive detection
bash ~/.claude/skills/openscad/scripts/openscad-stl-reconstruct.sh model.stl analysis/

# Tool 2: Profile extraction + extrusion axis detection
python3 ~/.claude/skills/openscad/scripts/openscad-profile-extract.py model.stl \
    --output analysis/profile.scad --json analysis/profile.json

# Tool 3: Adaptive multi-axis feature map
python3 ~/.claude/skills/openscad/scripts/openscad-adaptive-slice.py model.stl analysis/
```

**Key outputs to examine:**
- `analysis/slices/*.svg` — 2D profiles at 5 Z levels
- `analysis/profile.json` — extrusion axis, stability score, profile points, hole count
- `analysis/adaptive-slicing.json` — feature zones on all 3 axes, transition locations
- `analysis/primitives.json` — detected cylinders/planes
- `analysis/mesh-info.json` — volume, dimensions, symmetry

### Step 1b: Understand the Object (BEFORE writing code)

After running analysis tools, render multi-angle previews and answer:

1. **What are the main components?** (e.g. "bottom clip + diagonal arm + top clip")
2. **Which axis is thinnest?** Compare extents — the thinnest is likely the extrusion direction
3. **Are there diagonal/angled features?** If yes, the stability-score axis is probably WRONG
4. **Where do cross-sections change?** Check the adaptive slicer's transition zones
5. **Are there multi-body zones?** (channels, rails, gaps between parts)

**Decision tree — axis selection:**
1. If `stability_score < 0.1` AND thinnest axis matches stability axis → clean extrusion, use `profile.scad`
2. If model has **diagonal features** → use the **thinnest axis** regardless of stability score
3. If `stability_score < 0.3` AND no diagonals → stability axis + feature variations
4. If `stability_score > 0.3` → complex shape. Try dense X-slab along thinnest axis, or decompose into sub-objects

**Decision tree — technique per component:**
- Simple extruded body → profile + linear_extrude along extrusion axis
- Diagonal arm/strut → profile along thinnest axis captures it naturally
- Clips, hooks, U-channels → profile extraction (NOT hull — hull fills concavities)
- Cylindrical features → parametric circle() + square(), NOT polygon profiles
- Smooth convex transitions → hull() between boundary profiles (ONLY if convex)
- Complex multi-width → dense 2mm slabs along thinnest axis

**For models with cylindrical features** (stability > 0.3 or SVG shows circular contours):
- Do NOT rely on polygon profiles — they approximate curves poorly
- Identify circle centers and radii from the SVG contour data
- Model with `circle()` + `square()` in 2D, then extrude

Then render multi-angle previews:

```openscad
// Temporary viewer file
import("path/to/model.stl");
```

```bash
bash ~/.claude/skills/openscad/scripts/openscad-render.sh preview /tmp/stl-viewer.scad
```

Read all preview images to understand the 3D shape from multiple angles.

### Step 1b: Auto-Reconstruction (NEW — recommended for most models)

After running the adaptive slicer, use the auto-reconstructor to generate parametric .scad directly:

```bash
# Option A: With pre-computed analysis
python3 ~/.claude/skills/openscad/scripts/openscad-auto-reconstruct.py model.stl \
    --analysis analysis/ --output project/src/main.scad

# Option B: Run analysis + reconstruction in one step
python3 ~/.claude/skills/openscad/scripts/openscad-auto-reconstruct.py model.stl \
    --output project/src/main.scad --run-analysis

# Option C: Tighter circle fitting for precision parts
python3 ~/.claude/skills/openscad/scripts/openscad-auto-reconstruct.py model.stl \
    --analysis analysis/ --output project/src/main.scad --circle-threshold 0.3
```

This automatically:
1. Parses the feature map JSON into zones
2. Extracts profiles at zone boundaries
3. Fits circles/arcs to replace polygon approximations (Feature 3)
4. Generates hull() blends for transition zones (Feature 2)
5. Emits one OpenSCAD module per zone with sculptor assembly (Feature 1)

The output is a good starting point — review and refine the generated .scad, then verify with mesh comparison. For models with cylindrical features, this typically achieves >85% accuracy automatically (vs 75% with polygon-only).

### Step 2: Detailed Structure Mapping

**For simple models** (5 SVG slices are enough):
Parse the SVG contours to count bodies vs holes at each Z level.

**For complex models** (brackets, enclosures with multiple features):
Use the adaptive multi-axis slicer for efficient feature detection:
```bash
python3 ~/.claude/skills/openscad/scripts/openscad-adaptive-slice.py model.stl analysis/
```
This automatically: scans all 3 axes with coarse pass (5mm) → detects transitions → fine pass (0.5mm) around transitions. Produces a feature map classifying each zone as `solid`, `shell_or_channel`, `multi_body`, or `complex`.

For manual fine-grained slicing at specific heights:
```bash
for z in $(seq 0.5 1 <max_z>); do
    echo "projection(cut=true) translate([0,0,-$z]) import(\"model.stl\");" > /tmp/s.scad
    openscad -o "slices/z${z}.svg" /tmp/s.scad
done
```

Parse each SVG to build a structural map:
```
Z=0-5:   1 body (full width) + 8 holes     → Solid base with screw holes
Z=5-10:  2 bodies + 8 holes                → Channel appeared, walls split
Z=10-20: 2 bodies narrowing                → Taper zone (measure rate)
Z=20-33: 2 bodies constant width           → Top section
Z=25-27: Bodies interrupted                → Counterbore pockets at this depth
```

**Hole positions from SVG centroids** — for each hole contour at a given Z, compute the centroid. This gives exact X,Y positions far more reliably than vertex analysis.

**Feature verification rule:** If a feature doesn't appear as a distinct contour in the SVG data, IT DOES NOT EXIST in the model. Never add features based on visual interpretation of 3D renders alone.

### Step 3: Choose Approach and Decompose

Based on the analysis data, choose the reconstruction approach:

**Approach A — Profile Extrusion** (for extruded parts, stability < 0.3):
```bash
# The profile extractor already generated the .scad — use it as a starting point
cat analysis/profile.scad
# Adjust: add cavity with offset(delta=-wall), add floor, add features
```

**Approach B — Parametric Primitives** (for known shapes or cylindrical features):
Create a decomposition plan using measured dimensions from SVG data:
```
Decomposition:
1. Base: square([80, 80]) + circle tabs — from SVG outer contour at Z=mid
2. Cavity: offset(delta=-wall) of base — from SVG inner contour
3. Floor: solid at Z=0 to floor_h — from SVG at Z=0 (1 contour = solid)
4. Holes: cylinder(d=3) at SVG hole centroids
5. Counterbores: cylinder(d=8, h=2) at same positions
```

**Approach C — Hybrid** (for complex shapes with both flat and curved features):
1. Extract polygon profile for the overall outline
2. Identify which curves in the profile are circles (regular spacing, arc-like)
3. Replace those polygon sections with parametric `circle(r)` operations
4. Assemble: `square() + circle()` union for tabs, `difference()` for slots

**Counterbore vs Countersink** — always verify from reference images:
- **Counterbore**: flat cylindrical pocket (`cylinder(d=cb_d, h=cb_depth)`)
- **Countersink**: conical taper (`cylinder(d1=cs_d, d2=hole_d, h=cs_depth)`)
- Most 3D-printed parts use counterbores, not countersinks

### Step 4: Write Parametric .scad Code

Create a new project and write the reconstructed code:

```bash
bash ~/.claude/skills/openscad/scripts/openscad-project.sh init "<name>-reconstructed"
```

**Key principles for reconstruction:**
- Extract ALL dimensions as named variables at the top
- Use meaningful variable names that describe the physical feature
- Add comments linking each section to the original STL features
- Include `echo()` statements for bounding box verification
- Add `assert()` for parameter ranges

### Step 5: Visual Comparison Loop

Render the reconstructed .scad and compare side-by-side with the original STL renders:

1. Render the reconstruction from the same camera angles as Step 1
2. Read both sets of images
3. Compare silhouettes, proportions, and feature placement
4. Identify the biggest discrepancy
5. Fix it and re-render
6. Repeat until the reconstruction matches the original

### Step 6: Overlay Verification

For precise verification, create an overlay .scad file:

```openscad
// Overlay: original STL (transparent) vs reconstruction
%import("path/to/original.stl");  // % = transparent background
color("red", 0.6) reconstructed_model();
```

Render this overlay — any RED areas visible through the transparent original indicate reconstruction errors. Any grey areas not covered by red indicate missing geometry.

### Step 7: Mesh-to-Mesh Comparison

**This is the most important verification step.** Export the reconstruction as STL and compare it against the original using boolean difference:

```bash
# Export reconstruction
bash ~/.claude/skills/openscad/scripts/openscad-render.sh stl ~/openscad-projects/<name>/src/main.scad

# Run mesh comparison
bash ~/.claude/skills/openscad/scripts/openscad-stl-compare.sh \
    path/to/original.stl \
    ~/openscad-projects/<name>/output/main.stl \
    ~/openscad-projects/<name>/previews/comparison
```

This produces:
- **diff-A-minus-B.png** — geometry in original but MISSING from reconstruction (what you need to add)
- **diff-B-minus-A.png** — EXTRA geometry in reconstruction not in original (what you need to remove)
- **overlay.png** — both models overlaid for visual check
- **Geometric accuracy %** — based on volume of boolean differences vs original volume

**Target: >95% geometric accuracy.** If below 95%, examine the diff images to identify which features are wrong, fix them, re-export, and re-compare. Iterate until accuracy is satisfactory.

**Important:** Bounding box delta can be 0.000mm while geometric accuracy is only 78% — internal features matter more than outer dimensions.

### Step 8: Dimensional Verification

Compare echo output from the reconstruction with the STL bounding box:

```openscad
echo(str("Reconstructed BBOX: ", width, " x ", depth, " x ", height));
```

### Step 9: SDF Parameter Optimization (Advanced)

If the SVG profile method doesn't achieve >95% accuracy, use the SDF optimizer for automatic parameter tuning:

```bash
python3 ~/.claude/skills/openscad/scripts/openscad-sdf-optimize.py \
    path/to/original.stl \
    stadium-slot \
    --verbose \
    --output analysis/sdf-result.json
```

This works by:
1. Sampling 30,000 random points in the bounding box
2. Computing target occupancy (inside/outside original mesh) via trimesh
3. Defining the reconstruction as a parametric SDF (Signed Distance Field)
4. Using `scipy.optimize.minimize(method="Powell")` to maximize IoU (Intersection over Union)
5. Generating OpenSCAD code with optimized parameters

**When to use**: When you know the correct model topology (e.g., "stadium body with cylindrical slot") but can't find the exact parameters. The optimizer finds them automatically.

**Supported model types**: `stadium-slot`, `box-holes`. Add new types by defining an SDF function in the script.

**Workflow**: Run `openscad-stl-reconstruct.sh` first (to identify the model topology), then `openscad-sdf-optimize.py` (to find exact parameters), then `openscad-stl-compare.sh` (to verify).

**Prerequisites**: `pip3 install trimesh numpy scipy rtree`

### Common Pitfalls

- **Bounding box match ≠ correct model.** A model with completely wrong internal geometry can still have a 0.000mm bounding box delta. Always verify visually from multiple angles.
- **Don't assume features from renders alone.** What looks like a cylinder in a top-down view might just be a curved wall edge. Always verify with vertex analysis.
- **Coincident faces cause Z-fighting.** If a feature touches the body boundary exactly, use `intersection()` to clip it cleanly rather than making it the exact same size.
- **Don't flip between adding and removing features.** If unsure whether a feature exists, run cross-section analysis before deciding. Oscillating between "add bar" and "remove bar" wastes iterations.
- **Offset features are common.** Cylinders, holes, and channels are often NOT centered. Always calculate the actual center from vertex data rather than assuming symmetry.

### Limitations

- **Organic shapes** (sculpted, freeform surfaces) cannot be fully reconstructed as primitives. For these, keep the STL import and wrap it in a module.
- **Very complex models** (1000+ features) should be reconstructed incrementally, starting with the major body and adding features one group at a time.
- **Thread geometry** in STL is extremely difficult to reconstruct. Use `threads.scad` library instead of trying to match individual thread faces.
- **Text/engravings** embedded in STL meshes are very hard to extract. It's better to re-add text using OpenSCAD's `text()` module.

### Hybrid Approach

For complex models, use a hybrid strategy:
```openscad
// Import the complex organic base from STL
module original_base() {
    import("base-section.stl");
}

// Reconstruct and parameterize the mechanical features
module mounting_bracket(width=30, hole_d=5) {
    difference() {
        original_base();
        // Add parametric mounting holes
        for (pos = hole_positions)
            translate(pos) cylinder(d=hole_d, h=50, center=true);
    }
}
```

This lets the user modify the parametric parts while keeping the complex geometry intact.

---

## Workflow: Refine Mode

When the user wants to modify an existing design:

### Step 1: Read the Existing File

```bash
# Find .scad files in the project
```
Read the .scad source to understand the current design.

### Step 2: Render Current State

```bash
bash ~/.claude/skills/openscad/scripts/openscad-render.sh preview /path/to/file.scad
```

Read the preview images to see what currently exists.

### Step 3: Apply Changes

Edit the .scad file with the requested modifications. Use the Edit tool for surgical changes.

### Step 4: Re-render and Compare

Generate new previews and visually compare with the previous version. Report what changed.

### Step 5: Repeat or Export

Continue iterating or export when satisfied.

---

## Workflow: Export Mode

Quick export of an existing .scad file:

```bash
# Single format
bash ~/.claude/skills/openscad/scripts/openscad-render.sh stl /path/to/file.scad

# Multiple formats
bash ~/.claude/skills/openscad/scripts/openscad-render.sh export /path/to/file.scad

# With parameter overrides
bash ~/.claude/skills/openscad/scripts/openscad-render.sh stl /path/to/file.scad -D 'width=50' -D 'height=30'
```

---

## Workflow: Analyze Mode

Review a design for printability:

```bash
bash ~/.claude/skills/openscad/scripts/openscad-render.sh analyze /path/to/file.scad
```

This renders cross-section views and reports:
- Object bounding box dimensions
- Whether the mesh is manifold (watertight)
- Estimated print time indicators (volume, surface area from STL)
- Visual check of overhangs via bottom-up view

---

## Script Reference

All scripts live in `~/.claude/skills/openscad/scripts/`:

| Script | Purpose |
|--------|---------|
| `openscad-render.sh` | Core render/export/preview engine |
| `open

…(truncated)
