Parametric design guide
Parametric CAD lets one model become many parts — change a dimension, the whole thing regenerates. To get there, your variables, modules, and naming need a small amount of structure. This guide collects the conventions that make models survive 50 turns of edits with an AI agent (or with another human).
Why parametric?
The same model serves more use cases:
- A bracket with
leg_lengthbecomes both a 30 mm corner bracket and a 60 mm shelf bracket. - A cable clip with
cable_dbecomes a USB-C clip and a Cat-6 clip without rewriting anything. - A gear with
teethandmodulebecomes an entire gear family.
And — because every variable is named — you (and the AI agent) can change one thing without breaking the rest.
Variables: top of the file, declared once
Put every dimension into a named variable at the top of the file, in one block, with a comment explaining the unit and intent:
// === Parameters =====================================
leg_length = 30; // [10:1:100] — bracket leg in mm
leg_width = 20;
thickness = 4; // wall thickness (min 1.6 for FDM)
hole_d = 3.2; // M3 clearance
hole_inset = 5;
$fn = 48; // global facet count
// [min : step : max] comment is special.
OpenSCAD's Customizer (and the PrintPal Build mode) reads it to render a slider for that variable. The PrintPal AI CAD agent adds these automatically when it can infer reasonable bounds.
Naming conventions
- Dimensions in mm:
plate_w,plate_t,hole_d. - Counts:
n_holes,n_teeth,cells. - Booleans:
has_lid,chamfer_top,countersink. - Vectors: 2- and 3-vectors when things group naturally —
bolt_pattern = [25, 25]beatsbolt_x = 25; bolt_y = 25;. - Hardware specs: prefix with the standard —
m3_clear_d = 3.2,m3_csk_d = 6.
Match the built-ins: d not diam, r not radius, h not height when the meaning is obvious from context.
Design intent: derive, don't duplicate
If two values move together, write the dependent one as an expression of the independent one:
// Don't do this — they drift on edits:
plate_w = 40;
hole_x = 20; // should be plate_w/2
// Do this — design intent is explicit:
plate_w = 40;
hole_x = plate_w / 2; // always centered, even after edit
Modules: one per feature, with a tag
Wrap every feature in a named module, even features used only once. Then prefix it with a // @feature: name tag so it can be referenced from chat across turns:
// @feature: corner_holes
module corner_holes() {
for (x = [hole_inset, leg_length-hole_inset])
translate([x, leg_width/2, -1])
cylinder(h=thickness+2, d=hole_d, $fn=$fn);
}
// @feature: leg_a
module leg_a() {
difference() {
cube([leg_length, leg_width, thickness]);
corner_holes();
}
}
This buys you three things:
- Re-use:
leg_a()+mirrorfor a symmetric bracket. - Targeted edits: the AI agent can rewrite one module without touching the others.
- Click-to-highlight: chips above the chat composer let you click
@feature[corner_holes]to outline the geometry in 3D.
Layered defaults & overrides
For libraries, give every module a sensible default and let the caller override. Pattern:
module m3_hole(depth = 10, d = 3.2) {
translate([0, 0, -1])
cylinder(h=depth+2, d=d, $fn=32);
}
m3_hole(); // defaults
m3_hole(depth=20); // override one
m3_hole(d=3.5, depth=12); // override both
Assertions catch impossible configurations
Use assert to enforce constraints. The error surfaces in the agent's tool card and the next turn knows about it:
assert(thickness >= 1.6, "Wall too thin for FDM — min 1.6 mm.");
assert(leg_length > 2 * hole_inset,
"leg_length must clear two hole insets.");
Echo your design summary
End the file with an echo dumping the final dimensions, weight estimate, or BOM. The agent reads this from the OpenSCAD console:
echo("Bracket envelope:", [leg_length, leg_width, thickness]);
echo("Holes:", 4, "× M3 clearance");
A file structure that scales
// ===========================================================
// Bracket — parametric M3 corner mount
// ===========================================================
//
// Parameters above; geometry below. Every named feature group
// is tagged with @feature: name so it can be referenced from
// chat across regenerations.
// ===========================================================
// === Parameters ====================================
leg_length = 30; // [10:1:100]
leg_width = 20;
thickness = 4;
hole_d = 3.2;
hole_inset = 5;
$fn = 48;
// === Assertions ====================================
assert(thickness >= 1.6, "Wall too thin for FDM");
// === Modules =======================================
// @feature: corner_holes
module corner_holes() { /* ... */ }
// @feature: leg_a
module leg_a() { /* ... */ }
// @feature: leg_b
module leg_b() { /* ... */ }
// @feature: bracket
module bracket() { union() { leg_a(); leg_b(); } }
// === Entrypoint ====================================
bracket();
echo("Envelope:", [leg_length, leg_width, thickness]);
Anti-patterns
| Avoid | Because |
|---|---|
| Magic numbers in geometry | Can't refactor a dimension you can't search for. Always name it. |
Single 400-line top-level difference() | Impossible to reuse, hard to read, breaks the @feature workflow. |
Module names like part1(), thing() | Future-you and the agent both want descriptive names. |
| Inline expressions deep in transforms | Hard to debug. Pull the value into a named variable. |
| Renaming features mid-conversation | Breaks all your @feature[...] references. Add a new feature instead. |
Designing for AI handoff
If you intend the agent to keep editing the file, follow these extra rules:
- Don't delete
@featuretags by hand — the agent uses them as the contract between turns. - Don't combine multiple features into one module — the agent can't selectively edit half a module.
- Use vertical whitespace between feature blocks — the agent uses it as a parse hint.
- Comment intent, not mechanics. "Stiffening rib — must clear the M3 boss above" is far more useful than "Adds a 3 mm × 10 mm cube here".
Let the agent set up the structure for you
The AI CAD agent emits this structure by default — named parameters, @feature tags, sensible assertions, an echo summary at the bottom. Just describe the part.