Complete reference for using moicad with JavaScript/TypeScript for parametric CAD modeling.
- Introduction
- Getting Started
- API Styles
- Core Concepts
- API Reference
- Examples
- Best Practices
- TypeScript Support
The moicad JavaScript API provides a modern, type-safe interface for creating parametric 3D models. It offers two complementary API styles:
- Fluent API (Primary): Object-oriented, chainable methods
- Functional API (Alternative): Pure functions, composable operations
Both APIs provide full access to manifold-3d's robust CSG engine, ensuring all output is manifold-valid (no topology errors).
- Modern Language: ES6+ features, async/await, classes
- Type Safety: Full TypeScript support with IntelliSense
- Performance: 10-20x faster than OpenSCAD parsing
- Reusability: npm packages, modules, classes
- Familiarity: Web developers already know JavaScript
- Tooling: ESLint, Prettier, VS Code support
import { Shape } from 'moicad';
// Create a simple cube
const cube = Shape.cube(10);
// Must export a Shape as default
export default cube;- Open moicad frontend (http://localhost:3002)
- Select "JavaScript" from the language selector
- Write your code
- Press "Render (Alt+R)" to visualize
import { evaluateJavaScript } from './backend/javascript/runtime';
const code = `
import { Shape } from 'moicad';
export default Shape.cube(10);
`;
const result = await evaluateJavaScript(code);
console.log('Geometry:', result.geometry);Object-oriented style with chainable methods:
import { Shape } from 'moicad';
const model = Shape.cube([20, 20, 10])
.subtract(
Shape.sphere(8, { $fn: 32 })
.translate([10, 10, 0])
)
.color('blue');
export default model;Pros:
- Intuitive method chaining
- Discoverable via IDE autocomplete
- Clear object relationships
- Easier for beginners
Functional programming style with pure functions:
import { cube, sphere, translate, subtract, color } from 'moicad';
const model = color(
'blue',
subtract(
cube([20, 20, 10]),
translate([10, 10, 0], sphere(8))
)
);
export default model;Pros:
- Point-free style
- Function composition
- Familiar to FP enthusiasts
- Easy to test
Both styles are fully supported and can be mixed.
All operations return new Shape instances. Original shapes are never modified:
const cube1 = Shape.cube(10);
const cube2 = cube1.translate([5, 0, 0]); // cube1 is unchanged
console.log(cube1.getBounds()); // { min: [0,0,0], max: [10,10,10] }
console.log(cube2.getBounds()); // { min: [5,0,0], max: [15,10,10] }All output from moicad is guaranteed to be manifold (closed, non-self-intersecting). This is enforced by the underlying manifold-3d engine.
- Origin: Bottom-left-back corner (same as OpenSCAD)
- Units: Millimeters (by convention)
- Right-handed: X=right, Y=back, Z=up
Every JavaScript file must export a Shape as default:
// ✅ Correct
export default myShape;
// ❌ Wrong - will cause evaluation error
myShape;Create a cube or rectangular box.
Parameters:
size:number | [number, number, number]- Size or [width, depth, height]center:boolean(optional) - If true, cube is centered at origin
Examples:
Shape.cube(10); // 10x10x10 cube at origin
Shape.cube([20, 10, 5]); // Box: 20mm wide, 10mm deep, 5mm tall
Shape.cube(10, true); // Centered cubeCreate a sphere.
Parameters:
radius:number- Sphere radiusoptions:PrimitiveOptions(optional)$fn: number of fragments (default: auto)center: always centered (ignored)
Examples:
Shape.sphere(5); // Sphere with default detail
Shape.sphere(10, { $fn: 64 }); // High-detail sphereCreate a cylinder or tapered cylinder (cone).
Parameters:
height:number- Cylinder heightradius:number | [number, number]- Radius or [radiusBottom, radiusTop]options:PrimitiveOptions(optional)$fn: number of sidescenter: if true, centered on Z-axis
Examples:
Shape.cylinder(20, 5); // Cylinder: h=20, r=5
Shape.cylinder(20, [10, 5]); // Tapered cylinder
Shape.cylinder(20, 5, { center: true }); // Centered
Shape.cylinder(20, 5, { $fn: 6 }); // Hexagonal cylinderCreate a cone or truncated cone.
Parameters:
height:number- Cone heightradiusBottom:number- Base radiusradiusTop:number(optional, default: 0) - Top radiusoptions:PrimitiveOptions(optional)
Examples:
Shape.cone(20, 10); // Pointed cone
Shape.cone(20, 10, 5); // Truncated cone
Shape.cone(20, 10, 0, { $fn: 32 }); // Smooth coneCreate a custom polyhedron from vertices and faces.
Parameters:
points:number[][]- Array of [x, y, z] verticesfaces:number[][]- Array of face indices
Example:
const points = [
[0, 0, 0], [10, 0, 0],
[5, 10, 0], [5, 5, 10]
];
const faces = [
[0, 1, 2], // Bottom
[0, 2, 3], // Side
[0, 3, 1], // Side
[1, 3, 2] // Side
];
Shape.polyhedron(points, faces); // TetrahedronCreate a 2D circle (for extrusion).
Parameters:
radius:number- Circle radiusoptions:PrimitiveOptions(optional)$fn: number of segments
Example:
Shape.circle(10); // Circle for extrusion
Shape.circle(10, { $fn: 64 }); // Smooth circleCreate a 2D square or rectangle.
Parameters:
size:number | [number, number]- Size or [width, height]center:boolean(optional) - Center at origin
Example:
Shape.square(10); // 10x10 square
Shape.square([20, 10], true); // Centered rectangleCreate a 2D polygon from points.
Parameters:
points:[number, number][]- Array of [x, y] points
Example:
Shape.polygon([
[0, 0], [10, 0], [5, 10]
]); // Triangle
Shape.polygon([
[0, 0], [10, 0],
[10, 10], [0, 10]
]); // SquareAll transformations return a new Shape (immutable).
Move the shape in 3D space.
Parameters:
offset:[number, number, number]- [x, y, z] translation
Example:
cube.translate([10, 0, 0]); // Move 10mm on X
cube.translate([5, 5, 10]); // Move in 3DRotate the shape around X, Y, Z axes (in that order).
Parameters:
angles:[number, number, number]- [rx, ry, rz] in degrees
Example:
cube.rotate([45, 0, 0]); // Rotate 45° around X
cube.rotate([0, 90, 0]); // Rotate 90° around Y
cube.rotate([45, 45, 0]); // Rotate around X and YScale the shape uniformly or non-uniformly.
Parameters:
factors:number | [number, number, number]- Scale factor(s)
Example:
cube.scale(2); // Uniform 2x scaling
cube.scale([2, 1, 0.5]); // Non-uniform scalingMirror the shape across a plane defined by its normal vector.
Parameters:
normal:[number, number, number]- Plane normal vector
Example:
shape.mirror([1, 0, 0]); // Mirror across YZ plane (X=0)
shape.mirror([0, 1, 0]); // Mirror across XZ plane (Y=0)
shape.mirror([0, 0, 1]); // Mirror across XY plane (Z=0)Apply color to the shape (visual only, doesn't affect geometry).
Parameters:
color:string | [number, number, number] | [number, number, number, number]- CSS color name:
'red','blue' - Hex string:
'#ff0000' - RGB:
[1, 0, 0](0-1 range) - RGBA:
[1, 0, 0, 0.5](with transparency)
- CSS color name:
Example:
cube.color('red');
cube.color('#00ff00');
cube.color([1, 0, 0]); // Red (RGB)
cube.color([1, 0, 0, 0.5]); // Semi-transparent redCombine this shape with one or more other shapes.
Parameters:
shapes:Shape[]- Shapes to unite with this one
Example:
cube.union(sphere);
cube.union(sphere1, sphere2, sphere3);
// Static method
Shape.union(cube, sphere1, sphere2);Subtract one or more shapes from this shape (difference operation).
Parameters:
shapes:Shape[]- Shapes to subtract
Example:
cube.subtract(sphere); // Sphere-shaped hole
box.subtract(hole1, hole2); // Multiple holes
// Alias
cube.difference(sphere);Find the intersection of this shape with one or more shapes.
Parameters:
shapes:Shape[]- Shapes to intersect with
Example:
cube.intersect(sphere); // Only overlapping volume
// Static method
Shape.intersection(cube, sphere);Compute convex hull of this shape and other shapes.
Returns the smallest convex shape that contains all input shapes.
Parameters:
shapes:Shape[](optional) - Additional shapes
Example:
sphere1.hull(sphere2, sphere3); // Hull of 3 spheres
// Static method
Shape.hull(sphere1, sphere2, sphere3);
// Create organic shape
const organic = Shape.hull(
Shape.sphere(5).translate([0, 0, 0]),
Shape.sphere(4).translate([10, 5, 5]),
Shape.sphere(6).translate([5, 10, 10])
);Compute Minkowski sum with another shape.
The Minkowski sum places a copy of the second shape at every point of the first shape. Useful for rounding edges.
Parameters:
shape:Shape- Shape to perform Minkowski sum with
Example:
// Round the edges of a cube
const roundedCube = cube.minkowski(
Shape.sphere(2, { $fn: 16 })
);
// Static method
Shape.minkowski(cube, sphere);Extrude a 2D shape linearly along the Z-axis.
Parameters:
height:number- Extrusion heightoptions:LinearExtrudeOptions(optional)twist: number (degrees) - Twist along extrusionscale: number or [x, y] - Scale at topslices: number - Number of slices (for twist)center: boolean - Center vertically
Examples:
// Simple extrusion
Shape.circle(10).linearExtrude(20);
// Twisted tower
Shape.square(10).linearExtrude(50, {
twist: 180,
scale: 0.5
});
// Tapered column
Shape.circle(10).linearExtrude(30, {
scale: [0.5, 0.5]
});Revolve a 2D shape around the Z-axis (lathe operation).
Parameters:
options:RotateExtrudeOptions(optional)angle: number (degrees, default: 360) - Revolution angle$fn: number - Number of fragments
Examples:
// Full revolution (vase)
const profile = Shape.polygon([
[10, 0], [12, 10], [10, 20]
]);
profile.rotateExtrude({ $fn: 64 });
// Partial revolution (180°)
profile.rotateExtrude({ angle: 180, $fn: 32 });Offset a 2D shape (expand or contract).
Parameters:
delta:number- Offset distance (positive=expand, negative=contract)options:OffsetOptions(optional)chamfer: boolean - Use chamfer instead of round
Example:
Shape.square(20).offset(5); // Expand by 5mm
Shape.circle(10).offset(-2); // Contract by 2mmProject a 3D shape to 2D.
Parameters:
options:ProjectionOptions(optional)cut: boolean - Cut at Z=0 instead of shadow projection
Example:
Shape.sphere(10).projection(); // Shadow projection
Shape.cube(20).projection({ cut: true }); // Cut at Z=0These methods extract information from shapes (non-chainable).
Extract the final Geometry object for rendering/export.
Returns: Geometry
vertices: number[] - Vertex positions [x,y,z, x,y,z, ...]indices: number[] - Triangle indicesnormals: number[] - Normal vectorsbounds: { min: [x,y,z], max: [x,y,z] }stats: { vertexCount, faceCount, volume, surfaceArea }
Example:
const geometry = cube.getGeometry();
console.log('Vertices:', geometry.stats.vertexCount);
console.log('Volume:', geometry.stats.volume);Get the axis-aligned bounding box.
Returns: BoundingBox
min: [number, number, number]max: [number, number, number]
Example:
const bounds = shape.getBounds();
console.log('Min:', bounds.min); // [0, 0, 0]
console.log('Max:', bounds.max); // [10, 10, 10]
const width = bounds.max[0] - bounds.min[0];Get the volume of the shape.
Returns: number - Volume in cubic units
Example:
const volume = sphere.getVolume();
console.log('Volume:', volume, 'mm³');Get the surface area of the shape.
Returns: number - Surface area in square units
Example:
const area = cube.getSurfaceArea();
console.log('Surface area:', area, 'mm²');Check if the shape is a valid manifold.
Returns: boolean - True if manifold is valid
Example:
if (shape.isManifold()) {
console.log('Shape is watertight and printable');
}The runtime module provides JavaScript code evaluation capabilities, allowing you to execute user-provided JavaScript code safely.
import { evaluateJavaScript, JavaScriptRuntime } from '@moicad/sdk/runtime';Evaluates JavaScript code and returns geometry or errors.
Parameters:
code: string- JavaScript code to executeoptions?: RuntimeOptions- Runtime configuration options
Returns: Promise<EvaluateResult> - Evaluation result with geometry or errors
Example:
const result = await evaluateJavaScript(`
import { Shape } from '@moicad/sdk';
export default Shape.cube(10).union(Shape.sphere(5));
`);
if (result.success) {
console.log('Geometry created:', result.geometry.vertices.length);
} else {
console.error('Evaluation failed:', result.errors);
}Creates a runtime instance with custom configuration.
Constructor:
const runtime = new JavaScriptRuntime({
timeout: 30000, // Execution timeout
memoryLimit: 1024**3, // 1GB memory limit
allowedModules: ['@moicad/sdk', 'moicad'] // Allowed imports
});Methods:
evaluate(code, options?)- Evaluate JavaScript codevalidateCode(code)- Check if code is safe to execute
Example:
const runtime = new JavaScriptRuntime({ timeout: 5000 });
// Validate code first
const validation = runtime.validateCode(`
import { Shape } from '@moicad/sdk';
export default Shape.cube(10);
`);
if (validation.isValid) {
const result = await runtime.evaluate(code);
console.log('Success:', result.success);
}The runtime module includes security features:
- Restricted imports: Only
@moicad/sdkmodules allowed - Timeout protection: Prevents infinite loops
- Code validation: Detects dangerous patterns
- Memory limits: Prevents excessive memory usage
// Named imports
import { cube, sphere } from '@moicad/sdk';
// Namespace imports
import * as moicad from '@moicad/sdk';
// Default imports
import Shape from '@moicad/sdk';
// Mixed imports
import Shape, { cube } from '@moicad/sdk';Use Cases:
- Web-based CAD editors (like OpenSCAD.org)
- Plugin systems with dynamic model generation
- Educational platforms
- Online CAD playgrounds
See the examples/javascript/ directory for comprehensive examples:
- 01-basic-shapes.js - Primitives and positioning
- 02-parametric-design.js - Classes and parameters
- 03-functional-api.js - Functional programming style
- 04-extrusion.js - 2D to 3D conversion
- 05-advanced-techniques.js - Hull, Minkowski, patterns
- 06-real-world-enclosure.js - Complete project
// Good
const mountingPost = Shape.cylinder(10, 2);
const ventilationSlot = Shape.cube([20, 1, 3]);
// Avoid
const c1 = Shape.cylinder(10, 2);
const c2 = Shape.cube([20, 1, 3]);const WALL_THICKNESS = 2;
const MOUNTING_POST_DIA = 4;
const PCB_CLEARANCE = 0.5;
const box = createBox(width, depth, height, WALL_THICKNESS);function createMountingPost(height, diameter) {
return Shape.cylinder(height, diameter / 2)
.union(
Shape.sphere(diameter / 2, { $fn: 16 })
.translate([0, 0, height])
);
}class ParametricEnclosure {
constructor(width, depth, height, options = {}) {
this.width = width;
this.depth = depth;
this.height = height;
this.options = { wallThickness: 2, ...options };
}
build() {
// Implementation
}
}// Create hexagonal bolt head (6 sides) for M6 thread
const boltHead = Shape.cylinder(5, 9, { $fn: 6 });
// Add 0.2mm clearance tolerance for snap-fit assembly
const TOLERANCE = 0.2;
const innerDiameter = outerDiameter - TOLERANCE;Full TypeScript support with type definitions:
import { Shape, type PrimitiveOptions, type Vector3 } from 'moicad';
interface BoltParams {
length: number;
diameter: number;
headHeight?: number;
}
class Bolt {
constructor(private params: BoltParams) {}
build(): Shape {
const { length, diameter, headHeight = diameter * 0.7 } = this.params;
const shaft = Shape.cylinder(length, diameter / 2);
const head = Shape.cylinder(headHeight, diameter * 0.9, { $fn: 6 })
.translate([0, 0, length]);
return shaft.union(head);
}
}
export default new Bolt({ length: 20, diameter: 6 }).build();Import types from shared/javascript-types.ts:
Language- 'openscad' | 'javascript'PrimitiveOptions- Options for primitivesTextOptions- Text rendering optionsLinearExtrudeOptions- Extrusion optionsRotateExtrudeOptions- Revolution optionsColor- Color typeVector3- [number, number, number]Vector2- [number, number]BoundingBox- Bounding box type
// Preview (fast)
Shape.sphere(10, { $fn: 16 });
// Default (balanced)
Shape.sphere(10, { $fn: 32 });
// Final render (slow, high quality)
Shape.sphere(10, { $fn: 128 });// Good: Calculate once
const complexPart = createComplexShape();
const pattern = Array.from({ length: 10 }, (_, i) =>
complexPart.translate([i * 10, 0, 0])
);
// Avoid: Recalculate each time
const pattern = Array.from({ length: 10 }, (_, i) =>
createComplexShape().translate([i * 10, 0, 0])
);// Good: Single operation
Shape.union(part1, part2, part3, part4);
// Avoid: Nested operations
part1.union(part2).union(part3).union(part4);"Shape is not defined"
// Fix: Import Shape
import { Shape } from 'moicad';"export default is missing"
// Fix: Export your shape
const myShape = Shape.cube(10);
export default myShape;"Geometry is null"
- Check for invalid parameters (negative sizes, etc.)
- Verify all shapes are properly constructed
- Check browser console for detailed errors
"Render is slow"
- Reduce $fn values
- Simplify complex boolean operations
- Split large designs into parts
| OpenSCAD | JavaScript (Fluent) | JavaScript (Functional) |
|---|---|---|
cube(10); |
Shape.cube(10) |
cube(10) |
translate([5,0,0]) cube(10); |
Shape.cube(10).translate([5,0,0]) |
translate([5,0,0], cube(10)) |
union() { cube(10); sphere(5); } |
Shape.cube(10).union(Shape.sphere(5)) |
union(cube(10), sphere(5)) |
difference() { cube(10); sphere(5); } |
Shape.cube(10).subtract(Shape.sphere(5)) |
difference(cube(10), sphere(5)) |
- No semicolons inside operations: JavaScript uses method chaining
- Export required: Must
export defaultyour final shape - Imports needed: Must import Shape or functions
- Degrees: Rotation angles are in degrees (same as OpenSCAD)
- Arrays: Use JavaScript array syntax
[1, 2, 3]
- Examples:
examples/javascript/directory - Type Definitions:
shared/javascript-types.ts - API Implementation:
backend/javascript/shape.ts - Tests:
test-javascript-api*.tsfiles
For questions or contributions, visit the moicad GitHub repository.