Creating parametric designs
Build reusable 3D models with adjustable parameters that others can customize without editing code.
Creating parametric designs
This guide walks you through creating a parametric design from scratch. You will build a customizable storage box with adjustable dimensions, wall thickness, and optional features. By the end, you will have a reusable design that anyone can customize through the parameter panel.
Prerequisites
- Familiarity with Code Mode basics (primitives, transformations, boolean operations)
- Understanding of the parameter system (
defineParams, parameter types)
What you'll accomplish
You will create a storage box with these customizable features:
- Adjustable width, depth, and height
- Configurable wall thickness
- Optional lid
- Choice of corner style (sharp, rounded, or chamfered)
Step 1: Set up the project structure
Create a new Code Mode project and set up the basic parameter structure.
- Open Code Mode from the mode switcher
- Clear any existing code in the editor
- Add the imports and parameter definitions:
import { defineParams } from '@cadit-app/script-params';
import { Manifold, CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
export default defineParams({
params: {
width: { type: 'slider', default: 60, min: 20, max: 150, label: 'Width (mm)' },
depth: { type: 'slider', default: 40, min: 20, max: 150, label: 'Depth (mm)' },
height: { type: 'slider', default: 30, min: 10, max: 100, label: 'Height (mm)' },
wallThickness: { type: 'number', default: 2, min: 1, max: 5, label: 'Wall thickness' },
},
main: (params) => {
return Manifold.cube([params.width, params.depth, params.height], true);
},
});
The 3D preview shows a solid cube. The parameter panel displays sliders for dimensions and a number field for wall thickness.
Step 2: Create the hollow box
Replace the solid cube with a hollow box by subtracting an inner cavity.
Update the main function:
main: (params) => {
const { width, depth, height, wallThickness } = params;
// Create outer shell
const outer = Manifold.cube([width, depth, height], true);
// Create inner cavity (smaller by wall thickness on all sides except top)
const innerWidth = width - wallThickness * 2;
const innerDepth = depth - wallThickness * 2;
const innerHeight = height - wallThickness;
const inner = Manifold.cube([innerWidth, innerDepth, innerHeight], true)
.translate([0, 0, wallThickness / 2]);
return outer.subtract(inner);
},
The preview now shows a box with hollow interior and solid bottom. Adjust the sliders to verify the wall thickness works correctly at different sizes.
Step 3: Add a corner style option
Add a parameter to choose between sharp, rounded, or chamfered corners.
Update the params object to add the corner style choice:
params: {
width: { type: 'slider', default: 60, min: 20, max: 150, label: 'Width (mm)' },
depth: { type: 'slider', default: 40, min: 20, max: 150, label: 'Depth (mm)' },
height: { type: 'slider', default: 30, min: 10, max: 100, label: 'Height (mm)' },
wallThickness: { type: 'number', default: 2, min: 1, max: 5, label: 'Wall thickness' },
cornerStyle: {
type: 'radio',
default: 'sharp',
options: ['sharp', 'rounded', 'chamfered'],
label: 'Corner style',
},
cornerRadius: {
type: 'slider',
default: 5,
min: 2,
max: 15,
label: 'Corner radius',
},
},
Create a helper function to generate the box profile based on corner style. Add this before the main function:
function createBoxProfile(
width: number,
depth: number,
cornerStyle: string,
cornerRadius: number
): CrossSection {
if (cornerStyle === 'sharp') {
return CrossSection.square([width, depth], true);
}
// For rounded and chamfered, create a rectangle with modified corners
const halfW = width / 2;
const halfD = depth / 2;
const r = Math.min(cornerRadius, halfW - 1, halfD - 1);
if (cornerStyle === 'rounded') {
// Create rounded rectangle using offset operations
const inner = CrossSection.square([width - r * 2, depth - r * 2], true);
return inner.offset(r, 'Round');
}
// Chamfered: create octagon-like shape
const points: [number, number][] = [
[-halfW + r, -halfD],
[halfW - r, -halfD],
[halfW, -halfD + r],
[halfW, halfD - r],
[halfW - r, halfD],
[-halfW + r, halfD],
[-halfW, halfD - r],
[-halfW, -halfD + r],
];
return new CrossSection([points]);
}
Update the main function to use the profile:
main: (params) => {
const { width, depth, height, wallThickness, cornerStyle, cornerRadius } = params;
// Create outer profile and extrude
const outerProfile = createBoxProfile(width, depth, cornerStyle, cornerRadius);
const outer = outerProfile.extrude(height).translate([0, 0, -height / 2]);
// Create inner profile and extrude
const innerWidth = width - wallThickness * 2;
const innerDepth = depth - wallThickness * 2;
const innerRadius = Math.max(1, cornerRadius - wallThickness);
const innerProfile = createBoxProfile(innerWidth, innerDepth, cornerStyle, innerRadius);
const inner = innerProfile
.extrude(height - wallThickness)
.translate([0, 0, -height / 2 + wallThickness]);
return outer.subtract(inner);
},
Test each corner style option. The box should update to show sharp corners, rounded corners, or chamfered corners based on your selection.
Step 4: Add an optional lid
Add a toggle to include a matching lid with the box.
Add lid parameters:
params: {
// ... existing parameters ...
includeLid: { type: 'switch', default: false, label: 'Include lid' },
lidHeight: { type: 'slider', default: 10, min: 5, max: 30, label: 'Lid height' },
lidClearance: { type: 'number', default: 0.3, min: 0.1, max: 1, label: 'Lid clearance' },
},
Create a function to generate the lid:
function createLid(
width: number,
depth: number,
lidHeight: number,
wallThickness: number,
clearance: number,
cornerStyle: string,
cornerRadius: number
): Manifold {
// Outer lid profile matches the box
const outerProfile = createBoxProfile(width, depth, cornerStyle, cornerRadius);
const lidOuter = outerProfile.extrude(lidHeight);
// Inner lip that fits inside the box
const lipWidth = width - wallThickness * 2 - clearance * 2;
const lipDepth = depth - wallThickness * 2 - clearance * 2;
const lipRadius = Math.max(1, cornerRadius - wallThickness - clearance);
const lipProfile = createBoxProfile(lipWidth, lipDepth, cornerStyle, lipRadius);
const lipHeight = lidHeight - wallThickness;
const lip = lipProfile.extrude(lipHeight).translate([0, 0, wallThickness]);
// Hollow out the lid
const innerWidth = width - wallThickness * 2;
const innerDepth = depth - wallThickness * 2;
const innerRadius = Math.max(1, cornerRadius - wallThickness);
const innerProfile = createBoxProfile(innerWidth, innerDepth, cornerStyle, innerRadius);
const cavity = innerProfile.extrude(lipHeight).translate([0, 0, wallThickness]);
return lidOuter.add(lip).subtract(cavity);
}
Update the main function to optionally include the lid:
main: (params) => {
const {
width, depth, height, wallThickness,
cornerStyle, cornerRadius,
includeLid, lidHeight, lidClearance
} = params;
// Create the box (same as before)
const outerProfile = createBoxProfile(width, depth, cornerStyle, cornerRadius);
const outer = outerProfile.extrude(height).translate([0, 0, -height / 2]);
const innerWidth = width - wallThickness * 2;
const innerDepth = depth - wallThickness * 2;
const innerRadius = Math.max(1, cornerRadius - wallThickness);
const innerProfile = createBoxProfile(innerWidth, innerDepth, cornerStyle, innerRadius);
const inner = innerProfile
.extrude(height - wallThickness)
.translate([0, 0, -height / 2 + wallThickness]);
let result = outer.subtract(inner);
// Add lid if enabled
if (includeLid) {
const lid = createLid(
width, depth, lidHeight, wallThickness, lidClearance,
cornerStyle, cornerRadius
);
// Position lid next to the box for printing
const lidOffset = width + 10;
result = result.add(lid.translate([lidOffset, 0, -height / 2]));
}
return result;
},
Toggle the "Include lid" switch to see the lid appear next to the box. The lid is positioned separately for 3D printing.
Step 5: Organize parameters into groups
For designs with many parameters, group related options together using labels and visual hierarchy.
Reorganize the parameters with descriptive labels:
params: {
// Dimensions
width: {
type: 'slider',
default: 60,
min: 20,
max: 150,
label: 'Width (mm)',
description: 'Interior width of the box',
},
depth: {
type: 'slider',
default: 40,
min: 20,
max: 150,
label: 'Depth (mm)',
description: 'Interior depth of the box',
},
height: {
type: 'slider',
default: 30,
min: 10,
max: 100,
label: 'Height (mm)',
description: 'Interior height of the box',
},
wallThickness: {
type: 'number',
default: 2,
min: 1,
max: 5,
label: 'Wall thickness',
description: 'Thickness of walls and bottom',
},
// Style
cornerStyle: {
type: 'radio',
default: 'sharp',
options: ['sharp', 'rounded', 'chamfered'],
label: 'Corner style',
},
cornerRadius: {
type: 'slider',
default: 5,
min: 2,
max: 15,
label: 'Corner radius',
description: 'Applies to rounded and chamfered styles',
},
// Lid options
includeLid: {
type: 'switch',
default: false,
label: 'Include lid',
},
lidHeight: {
type: 'slider',
default: 10,
min: 5,
max: 30,
label: 'Lid height',
},
lidClearance: {
type: 'number',
default: 0.3,
min: 0.1,
max: 1,
label: 'Lid clearance',
description: 'Gap between lid and box for fit tolerance',
},
},
The description field adds help text that explains what each parameter does.
Result
You now have a parametric storage box with:
- Adjustable dimensions through sliders
- Configurable wall thickness
- Three corner style options
- Optional matching lid with adjustable clearance
Users can customize the design through the parameter panel without editing any code. The design updates in real-time as parameters change.
Tips for parametric designs
- Set sensible defaults that produce a valid, printable result
- Use
minandmaxvalues to prevent impossible geometry (negative dimensions, walls thicker than the box) - Add
descriptiontext to explain non-obvious parameters - Use
sliderfor parameters users will frequently adjust - Use
radioorchoicefor parameters with fixed options - Position multiple parts side by side for single-print export
- Test your design at extreme parameter values to catch edge cases
Next steps
- Code Mode parameters - Full reference for all parameter types
- Code Mode basics - Review primitives and boolean operations
- Publishing designs - Share your parametric design with others