Scene output scripts
Create scripts that output native CADit shapes like rectangles, circles, stars, and polygons with full 3D features including twist, sculpt, and revolve.
Scene output scripts
Scripts in Code Mode normally produce a single 3D mesh (a Manifold or GLTFNode). Scene output scripts instead produce native CADit shapes — rectangles, circles, stars, polygons, and more. Each shape appears as a separate object in the scene that you can select, move, and edit individually.
This is useful when your script needs to:
- Generate multiple independent objects at once
- Produce shapes that use CADit-specific features like twist, sculpt, and revolve
- Take existing 2D shapes as input and output modified versions
- Create layouts, patterns, or arrangements of standard shapes
Basic structure
A scene output script uses createSceneOutput to return an array of shapes:
import { defineParams, createSceneOutput, rect } from '@cadit-app/script-params';
export default defineParams({
params: {
width: { type: 'number', default: 30 },
height: { type: 'number', default: 10 },
},
main: (p) => createSceneOutput([
rect(p.width, 20, { height: p.height }),
]),
});
The key difference from a normal Code Mode script: instead of returning a Manifold object, you return createSceneOutput([...shapes]).
Shape helper functions
Import these from @cadit-app/script-params to create shapes:
| Function | Arguments | Description |
|---|---|---|
rect(width, height, options?) | width, height in mm | Rectangle |
circle(radius, options?) | radius in mm | Circle |
triangle(width, height, options?) | base width, height in mm | Triangle |
star(outerRadius, innerRadius, rays, options?) | outer/inner radius, number of rays | Star |
regularPolygon(numPoints, radius, options?) | number of sides, radius | Regular polygon (hexagon, pentagon, etc.) |
heart(width, height, options?) | width, height in mm | Heart shape |
polygon(points, options?) | array of {x, y} points | Custom polygon |
Every helper accepts an optional options object with the shape properties described below.
Note: a text() helper exists in the API but text rendering in scene outputs is not yet implemented. Use the visual Text tool in the drawing toolbar instead.
Shape properties
All shapes support these optional properties:
Position and rotation
rect(20, 20, {
position: { x: 30, y: -10 }, // Center position in mm
rotation: Math.PI / 4, // Rotation in radians
})
Color
Color can be specified in three formats:
circle(10, { color: 0xff6b35 }) // Hex number
circle(10, { color: '#32cd32' }) // CSS hex string
circle(10, { color: [255, 107, 53] }) // RGB array [0-255]
If omitted, the default color is used.
3D extrusion height
rect(20, 20, { height: 15 }) // Extrude 15mm tall
rect(20, 20, { z: 5 }) // Start 5mm above the ground
Twist
Twist rotates the shape along its extrusion height:
rect(20, 20, {
height: 40,
twist: 0.3, // Twist in radians
})
Sculpt
Sculpt varies the cross-section scale along the extrusion height. Each point has a pos (0 = bottom, 1 = top) and a scale factor:
circle(15, {
height: 30,
sculpt: [
{ pos: 0, scale: 0.5 }, // Narrow at bottom
{ pos: 0.5, scale: 1.2 }, // Wide in the middle
{ pos: 1, scale: 0.5 }, // Narrow at top
],
})
Revolve
Revolve spins a 2D profile around an axis to create shapes of revolution like vases, donuts, and bowls:
circle(8, {
extrusionMode: 'revolve',
revolveAngle: 360, // Degrees (1-360)
revolveAxisOffset: 10, // Distance from profile to axis
revolveSegments: 32, // Smoothness
})
| Property | Default | Description |
|---|---|---|
extrusionMode | 'linear' | Set to 'revolve' for revolution |
revolveAngle | 360 | Angle of revolution in degrees |
revolveAxisOffset | 0 | Distance from the shape's left edge to the axis |
revolveSegments | 32 | Number of segments (higher = smoother) |
Fill
rect(20, 20, { fill: false }) // Outline only (no fill)
Custom polygons with bezier curves
For organic shapes, use polygon() with bezier control points:
import { polygon } from '@cadit-app/script-params';
import type { BezierPoint2D } from '@cadit-app/script-params';
const points: BezierPoint2D[] = [
{ x: 0, y: 20, handleIn: { x: -8, y: -4 }, handleOut: { x: 8, y: -4 } },
{ x: 15, y: 0, handleIn: { x: 0, y: 8 }, handleOut: { x: 0, y: -8 } },
{ x: 0, y: -20, handleIn: { x: 8, y: 4 }, handleOut: { x: -8, y: 4 } },
{ x: -15, y: 0, handleIn: { x: 0, y: -8 }, handleOut: { x: 0, y: 8 } },
];
polygon(points, { height: 10 })
Each point has x and y coordinates plus optional handleIn and handleOut offsets (relative to the point) that define bezier curves between points.
Polygons also support holes:
polygon(outerPoints, {
holes: [holePoints], // Array of point arrays
height: 10,
})
Taking shapes as input
Scripts can accept existing 2D shapes from the canvas using the shape2d parameter type. The selected shape's path data becomes available for processing.
import { defineParams, createSceneOutput, polygon } from '@cadit-app/script-params';
import type { Shape2dValue, PathPoint2D } from '@cadit-app/script-params';
export default defineParams({
params: {
inputShape: { type: 'shape2d', label: 'Select a shape' },
height: { type: 'number', default: 5 },
},
main: (p) => {
const shape = p.inputShape as Shape2dValue | null;
if (!shape?.shapeData?.doodlePaths?.length) {
return createSceneOutput([]); // No shape selected
}
// Process each path from the input shape
const outputs = shape.shapeData.doodlePaths.map(path => {
const points: PathPoint2D[] = path.points.map(pt => {
if ('point' in pt) {
// PathSegment format with handles
const seg = pt as { point: { x: number; y: number }; handleIn?: { x: number; y: number }; handleOut?: { x: number; y: number } };
return { x: seg.point.x, y: seg.point.y, handleIn: seg.handleIn, handleOut: seg.handleOut };
}
return { x: (pt as any).x, y: (pt as any).y };
});
return polygon(points, { height: p.height });
});
return createSceneOutput(outputs);
},
});
The shape data includes:
shapeData.doodlePaths— array of paths, each withpointsand optionalholes- Each point has
x,ycoordinates and optional bezierhandleIn/handleOut
You can also use the samplePathTo2D utility from @cadit-app/script-params to convert bezier paths into sampled [x, y] arrays, which is useful when passing paths to Manifold's CrossSection for operations like offset.
Session behavior
Scene output scripts follow a session lifecycle:
- Running — Each run replaces the previous output shapes
- Editing parameters — Adjusting parameter values re-runs the script, replacing outputs
- Closing — When you close Code Mode or Maker Mode, the session finalizes and the output shapes become regular scene objects
After finalization, the shapes are independent objects. The script is saved to storage and can be reopened later.
Example scripts
The following complete scripts demonstrate different scene output features. You can paste any of these into Code Mode to try them.
Mixed shapes with colors
Creates a rectangle, circle, and triangle side by side with different colors.
import { defineParams, createSceneOutput, rect, circle, triangle } from '@cadit-app/script-params';
import type { ShapeDefinition } from '@cadit-app/script-params';
export default defineParams({
params: {
spread: { type: 'float', default: 40, min: 20, max: 100, caption: 'Spread Distance' },
height: { type: 'float', default: 10, min: 1, max: 30, caption: '3D Height' },
},
main: (p) => {
const objects: ShapeDefinition[] = [
// Rectangle with hex number color
rect(25, 20, {
height: p.height,
position: { x: -p.spread, y: 0 },
color: 0xff6b35,
}),
// Circle with CSS hex string color
circle(12, {
height: p.height,
position: { x: 0, y: 0 },
color: '#32cd32',
}),
// Triangle with no color (uses default)
triangle(26, 25, {
height: p.height,
position: { x: p.spread, y: 0 },
}),
];
return createSceneOutput(objects);
},
});
Parametric grid
Generates a grid of rectangles with configurable rows, columns, and spacing.
import { defineParams, createSceneOutput, rect } from '@cadit-app/script-params';
import type { RectInput } from '@cadit-app/script-params';
export default defineParams({
params: {
rows: { type: 'int', default: 3, min: 1, max: 10, caption: 'Rows' },
cols: { type: 'int', default: 3, min: 1, max: 10, caption: 'Columns' },
size: { type: 'float', default: 15, min: 5, max: 50, caption: 'Rectangle Size' },
spacing: { type: 'float', default: 25, min: 10, max: 100, caption: 'Spacing' },
height: { type: 'float', default: 10, min: 1, max: 50, caption: '3D Height' },
},
main: (p) => {
const objects: RectInput[] = [];
const totalWidth = (p.cols - 1) * p.spacing;
const totalHeight = (p.rows - 1) * p.spacing;
const offsetX = -totalWidth / 2;
const offsetY = -totalHeight / 2;
for (let row = 0; row < p.rows; row++) {
for (let col = 0; col < p.cols; col++) {
objects.push(
rect(p.size, p.size, {
height: p.height,
position: {
x: offsetX + col * p.spacing,
y: offsetY + row * p.spacing,
},
})
);
}
}
return createSceneOutput(objects);
},
});
Twisted columns
Demonstrates the twist property on different shape types.
import { defineParams, createSceneOutput, rect, star, regularPolygon } from '@cadit-app/script-params';
import type { ShapeDefinition } from '@cadit-app/script-params';
export default defineParams({
params: {
height: { type: 'float', default: 40, min: 10, max: 80, caption: 'Height' },
twistAmount: { type: 'float', default: 0.3, min: -0.5, max: 0.5, caption: 'Twist' },
spread: { type: 'float', default: 50, min: 30, max: 100, caption: 'Spacing' },
},
main: (p) => {
const objects: ShapeDefinition[] = [
// Twisted square column
rect(20, 20, {
height: p.height,
twist: p.twistAmount,
position: { x: -p.spread, y: 0 },
color: 0xe74c3c,
}),
// Twisted star
star(18, 8, 5, {
height: p.height,
twist: p.twistAmount,
position: { x: 0, y: 0 },
color: 0xf39c12,
}),
// Twisted hexagon
regularPolygon(6, 15, {
height: p.height,
twist: p.twistAmount,
position: { x: p.spread, y: 0 },
color: 0x3498db,
}),
];
return createSceneOutput(objects);
},
});
Sculpt profiles
Uses sculpt to create organic shapes: a bulging column, a tapered cone, and a vase form.
import { defineParams, createSceneOutput, rect, circle, star } from '@cadit-app/script-params';
import type { ShapeDefinition, SculptPoint } from '@cadit-app/script-params';
export default defineParams({
params: {
height: { type: 'float', default: 30, min: 10, max: 80, caption: '3D Height' },
twist: { type: 'float', default: 1, min: 0, max: 4, caption: 'Twist (rotations)' },
taperAmount: { type: 'float', default: 0.3, min: 0, max: 1, caption: 'Taper Amount' },
bulgePos: { type: 'float', default: 0.5, min: 0.1, max: 0.9, caption: 'Bulge Position' },
bulgeScale: { type: 'float', default: 1.5, min: 1, max: 2.5, caption: 'Bulge Scale' },
},
main: (p) => {
const sculptBulge: SculptPoint[] = [
{ pos: 0, scale: 1 - p.taperAmount },
{ pos: p.bulgePos, scale: p.bulgeScale },
{ pos: 1, scale: 1 },
];
const sculptTaper: SculptPoint[] = [
{ pos: 0, scale: 0.3 },
{ pos: 1, scale: 1 },
];
const sculptVase: SculptPoint[] = [
{ pos: 0, scale: 0.5 },
{ pos: 0.3, scale: 1 },
{ pos: 0.7, scale: 1 },
{ pos: 1, scale: 0.5 },
];
const objects: ShapeDefinition[] = [
// Rectangle with twist and bulge
rect(20, 20, {
height: p.height,
twist: p.twist * Math.PI * 2,
sculpt: sculptBulge,
position: { x: -50, y: 0 },
}),
// Circle with taper (cone-like)
circle(15, {
height: p.height,
sculpt: sculptTaper,
position: { x: 0, y: 0 },
}),
// Star with vase shape and twist
star(18, 8, 5, {
height: p.height,
twist: p.twist * Math.PI,
sculpt: sculptVase,
position: { x: 50, y: 0 },
}),
];
return createSceneOutput(objects);
},
});
Revolved shapes
Creates shapes of revolution: a torus, an arch, and an ornamental vase.
import { defineParams, createSceneOutput, rect, circle, star } from '@cadit-app/script-params';
import type { ShapeDefinition, SculptPoint } from '@cadit-app/script-params';
export default defineParams({
params: {
angle: { type: 'float', default: 360, min: 15, max: 360, caption: 'Revolve Angle' },
segments: { type: 'int', default: 32, min: 8, max: 128, caption: 'Segments' },
axisOffset: { type: 'float', default: 5, caption: 'Axis Offset' },
},
main: (p) => {
const vaseSculpt: SculptPoint[] = [
{ pos: 0, scale: 0.6 },
{ pos: 0.35, scale: 1.2 },
{ pos: 0.7, scale: 0.9 },
{ pos: 1, scale: 0.5 },
];
const objects: ShapeDefinition[] = [
// Torus from a revolved circle
circle(6, {
position: { x: -60, y: 0 },
height: 2,
extrusionMode: 'revolve',
revolveAngle: p.angle,
revolveSegments: p.segments,
revolveAxisOffset: p.axisOffset,
}),
// Arch from a partial revolve of a rectangle
rect(8, 20, {
position: { x: 0, y: 0 },
height: 2,
extrusionMode: 'revolve',
revolveAngle: 180,
revolveSegments: p.segments,
}),
// Ornamental vase from a revolved star with sculpt
star(12, 6, 5, {
position: { x: 60, y: 0 },
height: 2,
extrusionMode: 'revolve',
revolveAngle: p.angle,
revolveSegments: p.segments,
revolveAxisOffset: p.axisOffset,
sculpt: vaseSculpt,
}),
];
return createSceneOutput(objects);
},
});
Bezier curve shapes
Creates smooth organic shapes using bezier control points.
import { defineParams, createSceneOutput, polygon } from '@cadit-app/script-params';
import type { ShapeDefinition, BezierPoint2D } from '@cadit-app/script-params';
export default defineParams({
params: {
size: { type: 'float', default: 40, min: 20, max: 80, caption: 'Size' },
height: { type: 'float', default: 15, min: 5, max: 40, caption: '3D Height' },
smoothness: { type: 'float', default: 0.5, min: 0, max: 1, caption: 'Smoothness' },
},
main: (p) => {
const s = p.size;
const h = p.smoothness * s * 0.4;
// Heart shape using bezier curves
const heartPoints: BezierPoint2D[] = [
{ x: 0, y: s * 0.6, handleIn: { x: -h, y: -h * 0.5 }, handleOut: { x: h, y: -h * 0.5 } },
{ x: s * 0.5, y: 0, handleIn: { x: 0, y: h }, handleOut: { x: 0, y: -h } },
{ x: s * 0.25, y: -s * 0.3, handleIn: { x: h * 0.5, y: 0 }, handleOut: { x: -h * 0.5, y: 0 } },
{ x: 0, y: -s * 0.15, handleIn: { x: h * 0.3, y: -h * 0.3 }, handleOut: { x: -h * 0.3, y: -h * 0.3 } },
{ x: -s * 0.25, y: -s * 0.3, handleIn: { x: h * 0.5, y: 0 }, handleOut: { x: -h * 0.5, y: 0 } },
{ x: -s * 0.5, y: 0, handleIn: { x: 0, y: -h }, handleOut: { x: 0, y: h } },
];
// Leaf shape using bezier curves
const leafPoints: BezierPoint2D[] = [
{ x: 0, y: s * 0.5, handleIn: { x: -h * 0.3, y: -h * 0.5 }, handleOut: { x: h * 0.3, y: -h * 0.5 } },
{ x: s * 0.3, y: 0, handleIn: { x: 0, y: h * 0.8 }, handleOut: { x: 0, y: -h * 0.8 } },
{ x: 0, y: -s * 0.5, handleIn: { x: h * 0.3, y: h * 0.5 }, handleOut: { x: -h * 0.3, y: h * 0.5 } },
{ x: -s * 0.3, y: 0, handleIn: { x: 0, y: -h * 0.8 }, handleOut: { x: 0, y: h * 0.8 } },
];
const objects: ShapeDefinition[] = [
polygon(heartPoints, {
height: p.height,
position: { x: -s, y: 0 },
color: [220, 60, 80],
}),
polygon(leafPoints, {
height: p.height,
position: { x: s, y: 0 },
color: [60, 180, 80],
}),
];
return createSceneOutput(objects);
},
});
Shape input: grid duplicator
Takes a 2D shape from the canvas and creates a grid of copies as native scene objects.
import { defineParams, createSceneOutput, polygon } from '@cadit-app/script-params';
import type { Shape2dValue, PathPoint, Point2D, PolygonInput, PathPoint2D, BezierPoint2D } from '@cadit-app/script-params';
function convertPathPoint(p: PathPoint): PathPoint2D {
if ('point' in p && typeof p.point === 'object') {
const segment = p as { point: Point2D; handleIn?: Point2D; handleOut?: Point2D };
const result: BezierPoint2D = { x: segment.point.x, y: segment.point.y };
if (segment.handleIn) result.handleIn = { x: segment.handleIn.x, y: segment.handleIn.y };
if (segment.handleOut) result.handleOut = { x: segment.handleOut.x, y: segment.handleOut.y };
return result;
}
return { x: (p as Point2D).x, y: (p as Point2D).y };
}
function translatePoints(points: PathPoint2D[], dx: number, dy: number): PathPoint2D[] {
return points.map(p => {
const result: PathPoint2D = { x: p.x + dx, y: p.y + dy };
if ('handleIn' in p && p.handleIn) (result as BezierPoint2D).handleIn = p.handleIn;
if ('handleOut' in p && p.handleOut) (result as BezierPoint2D).handleOut = p.handleOut;
return result;
});
}
export default defineParams({
params: {
inputShape: { type: 'shape2d' as const, label: 'Input Shape', default: null },
rows: { type: 'int', default: 3, min: 1, max: 10, caption: 'Rows' },
cols: { type: 'int', default: 3, min: 1, max: 10, caption: 'Columns' },
spacingX: { type: 'float', default: 30, min: 5, max: 200, caption: 'X Spacing' },
spacingY: { type: 'float', default: 30, min: 5, max: 200, caption: 'Y Spacing' },
height: { type: 'float', default: 5, min: 1, max: 50, caption: '3D Height' },
},
main: (p) => {
const shapeInput = p.inputShape as Shape2dValue | null;
if (!shapeInput?.shapeData?.doodlePaths?.length) {
return createSceneOutput([]);
}
const shapes: PolygonInput[] = [];
const baseOffsetX = -((p.cols - 1) * p.spacingX) / 2;
const baseOffsetY = -((p.rows - 1) * p.spacingY) / 2;
for (let row = 0; row < p.rows; row++) {
for (let col = 0; col < p.cols; col++) {
const dx = baseOffsetX + col * p.spacingX;
const dy = baseOffsetY + row * p.spacingY;
for (const doodlePath of shapeInput.shapeData.doodlePaths) {
const convertedPoints = doodlePath.points.map(convertPathPoint);
const translatedPoints = translatePoints(convertedPoints, dx, dy);
let translatedHoles: PathPoint2D[][] | undefined;
if (doodlePath.holes?.length) {
translatedHoles = doodlePath.holes.map(hole =>
translatePoints(hole.map(convertPathPoint), dx, dy)
);
}
shapes.push(polygon(translatedPoints, {
height: p.height,
holes: translatedHoles,
}));
}
}
}
return createSceneOutput(shapes);
},
});
Shape input: offset tool
Takes a 2D shape and creates an expanded or contracted version using Manifold's CrossSection offset.
import { CrossSection } from 'manifold-3d/manifoldCAD';
import { defineParams, createSceneOutput, polygon, samplePathTo2D } from '@cadit-app/script-params';
import type { Shape2dValue, PathPoint, PolygonInput, PathPoint2D } from '@cadit-app/script-params';
type JoinType = 'Round' | 'Square' | 'Miter';
function pathToCrossSectionPoints(points: PathPoint[]): [number, number][] {
const sampled = samplePathTo2D(points);
return sampled.map(([x, y]) => [x, -y]); // Flip Y for CrossSection
}
function isClockwise(points: [number, number][]): boolean {
let area = 0;
for (let i = 0; i < points.length; i++) {
const j = (i + 1) % points.length;
area += points[i][0] * points[j][1] - points[j][0] * points[i][1];
}
return area < 0;
}
function ensureCCW(points: [number, number][]): [number, number][] {
return isClockwise(points) ? [...points].reverse() : points;
}
export default defineParams({
params: {
inputShape: { type: 'shape2d' as const, label: 'Input Shape', default: null },
offset: { type: 'float', default: 5, min: -50, max: 50, caption: 'Offset Amount', step: 0.5 },
joinType: { type: 'choice' as const, default: 'Round', options: ['Round', 'Square', 'Miter'], caption: 'Corner Type' },
height: { type: 'float', default: 5, min: 1, max: 50, caption: '3D Height' },
keepOriginal: { type: 'switch', default: false, caption: 'Show Original Shape' },
},
main: (p) => {
const shapeInput = p.inputShape as Shape2dValue | null;
if (!shapeInput?.shapeData?.doodlePaths?.length) {
return createSceneOutput([]);
}
const shapes: PolygonInput[] = [];
for (const doodlePath of shapeInput.shapeData.doodlePaths) {
const points = pathToCrossSectionPoints(doodlePath.points);
if (points.length < 3) continue;
const oriented = ensureCCW(points);
let crossSection;
if (doodlePath.holes?.length) {
const contours: [number, number][][] = [oriented];
for (const hole of doodlePath.holes) {
const hp = pathToCrossSectionPoints(hole);
contours.push(isClockwise(hp) ? hp : [...hp].reverse());
}
crossSection = new CrossSection(contours);
} else {
crossSection = new CrossSection([oriented]);
}
const result = crossSection.offset(p.offset, p.joinType as JoinType, 2, 16);
const polys = result.toPolygons();
for (const poly of polys) {
const pathPoints: PathPoint2D[] = (poly as any[]).map((pt: any) =>
Array.isArray(pt) ? { x: pt[0], y: pt[1] } : { x: pt.x, y: pt.y }
);
shapes.push(polygon(pathPoints, { height: p.height, color: [100, 150, 255] }));
}
if (p.keepOriginal) {
const original = samplePathTo2D(doodlePath.points);
shapes.push(polygon(
original.map(([x, y]) => ({ x, y })),
{ height: p.height * 0.5, color: [255, 200, 100] }
));
}
}
return createSceneOutput(shapes);
},
});
Radial pattern generator
Arranges shapes in a circular pattern with configurable count and radius.
import { defineParams, createSceneOutput, rect, circle, star, triangle, heart } from '@cadit-app/script-params';
import type { ShapeDefinition } from '@cadit-app/script-params';
export default defineParams({
params: {
shapeType: {
type: 'choice',
default: 'star',
options: ['rect', 'circle', 'star', 'triangle', 'heart'],
label: 'Shape type',
},
count: { type: 'int', default: 8, min: 2, max: 24, label: 'Number of copies' },
radius: { type: 'float', default: 40, min: 10, max: 100, label: 'Pattern radius' },
shapeSize: { type: 'float', default: 10, min: 3, max: 30, label: 'Shape size' },
height: { type: 'float', default: 8, min: 1, max: 40, label: '3D Height' },
rotateWithPattern: { type: 'switch', default: true, label: 'Rotate shapes to follow ring' },
addCenter: { type: 'switch', default: true, label: 'Add center shape' },
},
main: (p) => {
const objects: ShapeDefinition[] = [];
function makeShape(x: number, y: number, rotation: number): ShapeDefinition {
const opts = { height: p.height, position: { x, y }, rotation };
switch (p.shapeType) {
case 'rect': return rect(p.shapeSize, p.shapeSize, opts);
case 'circle': return circle(p.shapeSize / 2, opts);
case 'star': return star(p.shapeSize / 2, p.shapeSize / 4, 5, opts);
case 'triangle': return triangle(p.shapeSize, p.shapeSize, opts);
case 'heart': return heart(p.shapeSize, p.shapeSize, opts);
default: return rect(p.shapeSize, p.shapeSize, opts);
}
}
// Place shapes in a circle
for (let i = 0; i < p.count; i++) {
const angle = (i / p.count) * Math.PI * 2;
const x = Math.cos(angle) * p.radius;
const y = Math.sin(angle) * p.radius;
const rotation = p.rotateWithPattern ? angle : 0;
objects.push(makeShape(x, y, rotation));
}
// Optionally add a center shape
if (p.addCenter) {
objects.push(makeShape(0, 0, 0));
}
return createSceneOutput(objects);
},
});
Honeycomb grid
Generates a hexagonal honeycomb pattern from circles with configurable size and gap.
import { defineParams, createSceneOutput, regularPolygon } from '@cadit-app/script-params';
import type { ShapeDefinition } from '@cadit-app/script-params';
export default defineParams({
params: {
rings: { type: 'int', default: 3, min: 1, max: 6, label: 'Number of rings' },
cellRadius: { type: 'float', default: 8, min: 3, max: 20, label: 'Cell radius' },
gap: { type: 'float', default: 2, min: 0.5, max: 8, label: 'Gap between cells' },
height: { type: 'float', default: 5, min: 1, max: 20, label: '3D Height' },
},
main: (p) => {
const objects: ShapeDefinition[] = [];
const spacing = (p.cellRadius + p.gap / 2) * Math.sqrt(3);
// Hex grid directions for axial coordinates
const directions = [
[1, 0], [0, 1], [-1, 1], [-1, 0], [0, -1], [1, -1],
];
// Center cell
objects.push(regularPolygon(6, p.cellRadius, {
height: p.height,
position: { x: 0, y: 0 },
color: 0xf0c040,
}));
// Surrounding rings
for (let ring = 1; ring <= p.rings; ring++) {
let q = ring;
let r = 0;
for (let side = 0; side < 6; side++) {
for (let step = 0; step < ring; step++) {
const x = spacing * (q + r * 0.5);
const y = spacing * (r * Math.sqrt(3) / 2);
const shade = 0.5 + 0.5 * (1 - ring / (p.rings + 1));
objects.push(regularPolygon(6, p.cellRadius, {
height: p.height,
position: { x, y },
color: [
Math.round(240 * shade),
Math.round(192 * shade),
Math.round(64 * shade),
],
}));
q += directions[side][0];
r += directions[side][1];
}
}
}
return createSceneOutput(objects);
},
});
Staircase generator
Generates a spiral or straight staircase from rectangles with configurable step dimensions.
import { defineParams, createSceneOutput, rect } from '@cadit-app/script-params';
import type { ShapeDefinition } from '@cadit-app/script-params';
export default defineParams({
params: {
steps: { type: 'int', default: 10, min: 2, max: 30, label: 'Number of steps' },
stepWidth: { type: 'float', default: 20, min: 5, max: 50, label: 'Step width' },
stepDepth: { type: 'float', default: 12, min: 5, max: 30, label: 'Step depth' },
stepHeight: { type: 'float', default: 3, min: 1, max: 10, label: 'Step height' },
spiral: { type: 'switch', default: false, label: 'Spiral staircase' },
spiralRadius: { type: 'float', default: 15, min: 10, max: 80, label: 'Spiral radius' },
},
main: (p) => {
const objects: ShapeDefinition[] = [];
for (let i = 0; i < p.steps; i++) {
let x: number, y: number, rotation: number;
if (p.spiral) {
const angle = (i / p.steps) * Math.PI * 2;
x = Math.cos(angle) * p.spiralRadius;
y = Math.sin(angle) * p.spiralRadius;
rotation = angle;
} else {
x = 0;
y = i * p.stepDepth;
rotation = 0;
}
objects.push(rect(p.stepWidth, p.stepDepth, {
height: p.stepHeight,
z: i * p.stepHeight,
position: { x, y },
rotation,
color: i % 2 === 0 ? 0xcccccc : 0x999999,
}));
}
return createSceneOutput(objects);
},
});
Tips
- Return
createSceneOutput([])to produce no output (useful as a fallback when no shape is selected) - Each shape in the array becomes an independent scene object after the session finalizes
- Combine scene outputs with standard Manifold scripts by using two separate code objects in the same design
- Use
console.log()for debugging — output appears in the error panel - The
ShapeDefinitiontype is the union of all shape input types, useful for arrays of mixed shapes
Next steps
- Code Mode basics — Fundamentals of creating geometry with code
- Code Mode parameters — Full reference for all parameter types
- Creating objects of revolution — Use the visual Revolve tool for shapes of revolution