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.

  1. Open Code Mode from the mode switcher
  2. Clear any existing code in the editor
  3. 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 min and max values to prevent impossible geometry (negative dimensions, walls thicker than the box)
  • Add description text to explain non-obvious parameters
  • Use slider for parameters users will frequently adjust
  • Use radio or choice for 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