A TypeScript-based 3D mesh creation tool using a component-based architecture. Build complex 3D models programmatically and export them as STL files.
CSG Builder allows you to create 3D meshes using TypeScript code with a React-like component pattern. It leverages Constructive Solid Geometry (CSG) operations to combine, subtract, and transform primitive shapes (cubes, cylinders, spheres, cones, and polygon prisms) into complex geometries.
📖 New to CSG Builder? Check out the Examples & Learning Resources section below. All example projects are fully documented with comprehensive inline comments to help you learn the syntax and patterns.
- Component-Based Architecture - Define reusable 3D components as TypeScript functions
- Primitive Shapes - Cubes, cylinders, spheres, cones, and polygon prisms with customizable dimensions
- Custom Profile Prisms - Extrude 2D profiles into 3D shapes using points, paths, or Shape API
- Body of Revolution - Create rotationally symmetric objects (chess pieces, vases, bottles) by rotating profiles
- Partial Geometries - Create pie slices, hemispheres, and wedges with CSG-based angle cutting
- Static CSG Operations - Immutable SUBTRACT, UNION, INTERSECT, and MERGE operations for complex geometries
- Grid Arrays - Create 1D, 2D, and 3D arrays with static GRID_X, GRID_XY, GRID_XYZ methods
- Transformations - Translate, rotate, and scale objects with chainable methods
- Real-Time Preview - Interactive 3D viewport with orbit controls
- STL Export - Export models in binary STL format for 3D printing
- Hot Reload - Instant updates during development with simple browser refresh (Ctrl+R)
- Comprehensive Documentation - Fully-commented example projects teaching syntax from basics to advanced patterns
- TypeScript - Type-safe component definitions
- Three.js - 3D rendering engine
- three-bvh-csg - High-performance CSG operations
- Svelte 5 - Reactive UI framework
- Threlte - Svelte wrapper for Three.js
- Vite - Fast build tool and dev server
- Tailwind CSS - Utility-first styling
- Node.js >= 22.0.0
- npm >= 10.0.0
npm installStart the development server:
npm run devThe application will be available at http://localhost:5173
Note on Deployment:
- Build output goes to
./docsdirectory
- Create a new file in
projects/[your-project]/directory - Define your 3D component using the Solid class
- Export a components map
- Register your components in the project index
- View and export your model in the browser
import { Solid } from '$lib/3d/Solid';
import type { ComponentsMap } from '$stores/componentStore';
export const simpleBox = (): Solid => {
return Solid.cube(10, 10, 10, 'blue');
};
export const components: ComponentsMap = {
SimpleBox: () => simpleBox()
};// Hollow box - outer minus inner
const outer = Solid.cube(20, 20, 20, { color: 'red' });
const inner = Solid.cube(16, 16, 16, { color: 'red' });
const hollowBox = Solid.SUBTRACT(outer, inner);
// Box with holes - subtract multiple cylinders
const box = Solid.cube(20, 20, 20, { color: 'blue' });
const holeX = Solid.cylinder(3, 25, { color: 'blue' }).rotate({ z: 90 });
const holeY = Solid.cylinder(3, 25, { color: 'blue' });
const holeZ = Solid.cylinder(3, 25, { color: 'blue' }).rotate({ x: 90 });
const boxWithHoles = Solid.SUBTRACT(box, holeX, holeY, holeZ);// Window that can be placed in any wall and will cut through it
export const window = (width: number, height: number, depth: number): Solid => {
const frame = Solid.cube(width, height, depth, 'brown');
// Mark opening as negative - it will be subtracted during MERGE
const opening = Solid.cube(width - 4, height - 4, depth * 4, 'gray').setNegative();
const bar = Solid.cube(2, height, depth - 1, 'brown').move({ z: -0.5 });
// MERGE respects negative flags: frame + opening (subtracted) + bar
return Solid.MERGE([frame, opening, bar]);
};
// Usage in wall
export const wallWithWindow = (): Solid => {
const wall = Solid.cube(20, 20, 1, 'gray');
const win = window(5, 8, 3).move({ x: 10, y: 5 }); // Use move for relative positioning
// Window's negative opening is already processed in MERGE
return Solid.UNION(wall, win);
};// Sphere for rounded features
const roundedCorner = Solid.SUBTRACT(
Solid.cube(20, 20, 20, { color: 'red' }),
Solid.sphere(3, { color: 'red' }).move({ x: 10, y: 10, z: 10 })
);
// Prism - N-sided shapes
const hexNut = Solid.SUBTRACT(
Solid.prism(6, 10, 5, { color: 'gray' }),
Solid.cylinder(4, 6, { color: 'gray' })
).center();
// Partial geometries with angle parameter (in degrees)
const pieSlice = Solid.cylinder(10, 2, { color: 'red', angle: 90 });
const hemisphere = Solid.sphere(8, { color: 'cyan', angle: 180 });Extrude 2D profiles into 3D shapes - three methods:
import { Solid, straight, curve } from '$lib/3d/Solid';
// 1. Shape Builder API
const lBracket = Solid.profilePrism(
10,
(shape) => {
shape.moveTo(0, 0);
shape.lineTo(20, 0);
shape.lineTo(20, 5);
shape.lineTo(5, 5);
shape.lineTo(5, 20);
shape.lineTo(0, 20);
},
{ color: 'blue' }
);
// 2. Point Array
const trapezoid = Solid.profilePrismFromPoints(
8,
[
[0, 0],
[10, 0],
[8, 5],
[2, 5]
],
{ color: 'red' }
);
// 3. Path Segments - smooth curves & controlled turns
const roundedRect = Solid.profilePrismFromPath(
5,
[
straight(20),
curve(5, 90),
straight(10),
curve(5, 90),
straight(20),
curve(5, 90),
straight(10),
curve(5, 90)
],
{ color: 'blue' }
);
// Sharp corners with zero-radius curves
const triangle = Solid.profilePrismFromPath(
4,
[straight(15), curve(0, 120), straight(15), curve(0, 120), straight(15), curve(0, 120)],
{ color: 'orange' }
);Path segments: straight(length), curve(radius, angle) - positive=right, negative=left, 0=sharp
Create rotationally symmetric objects like chess pieces, vases, and bottles by rotating a 2D profile around the Y-axis:
import { Solid, straight, curve } from '$lib/3d/Solid';
// 1. SHAPE BUILDER API - Full control with Three.js Shape methods
export const vase = (): Solid => {
return Solid.revolutionSolid(
(shape) => {
shape.moveTo(5, 0); // Bottom radius (x = radius, y = height)
shape.lineTo(3, 5); // Narrow middle
shape.lineTo(6, 10); // Wide top
shape.lineTo(5, 15); // Rim
shape.lineTo(0, 15); // Back to center
},
{ color: 'blue' }
);
};
// 2. POINT ARRAY - Simple coordinate-based profiles
export const chessPawn = (): Solid => {
return Solid.revolutionSolidFromPoints(
[
[0, 0], // Bottom center (x = radius, y = height)
[3, 0], // Bottom edge
[2, 2], // Narrow stem
[4, 8], // Body
[2, 10], // Neck
[3, 12], // Head
[0, 12] // Top center
],
{ color: 'white' }
);
};
// 3. PATH SEGMENTS - Smooth curves and controlled turns
export const bottle = (): Solid => {
return Solid.revolutionSolidFromPath(
[
straight(5), // Bottom radius
curve(2, 90), // Rounded corner up
straight(8), // Body height
curve(3, -90), // Curve inward for neck
straight(5), // Neck height
curve(1, -90), // Top curve
straight(2) // Rim width
],
{ color: 'green' }
);
};
// Partial revolution (90° cut-away)
const quarterVase = Solid.revolutionSolidFromPoints(
[
[0, 0],
[4, 0],
[3, 2],
[5, 6],
[4, 10],
[0, 10]
],
{ angle: 90, color: 'purple' }
);Profile coords: X=radius, Y=height. Rotates around Y-axis. Uses: Chess pieces, vases, bottles, tableware, architectural elements
Cache expensive component computations that are called repeatedly:
import { cacheFunction, cacheInlineFunction } from '$lib/cacheFunction';
// Named functions
const cachedWall = cacheFunction((length: number, height: number): Solid => {
return Solid.SUBTRACT(
Solid.cube(length, height, 2, { color: 'gray' }),
Solid.cube(5, 8, 3, { color: 'gray' })
);
});
// Inline/arrow functions
export const Wall = cacheInlineFunction('Wall', (length: number): Solid => {
/* expensive CSG ops */
return wall.align('bottom');
});
// Usage
const w1 = Wall(20); // Computes and caches
const w2 = Wall(20); // Returns cached (instant)
const w3 = Wall(30); // Different params, new computationHow it works: Cache key = ${functionName}:${JSON.stringify(args)}, results cloned for immutability
Use for: Reusable parametric components, expensive CSG ops, grid base shapes
Don't use for: One-time components, simple ops, non-serializable params
Primitives: cube(w,h,d,opts), cylinder(r,h,opts), sphere(r,opts), cone(r,h,opts), prism(sides,r,h,opts), trianglePrism(r,h,opts)
Profiles: profilePrism(h,builder,opts), profilePrismFromPoints(h,points,opts), profilePrismFromPath(h,segments,opts)
Revolution: revolutionSolid(builder,opts), revolutionSolidFromPoints(points,opts), revolutionSolidFromPath(segments,opts)
Path Factories: straight(length), curve(radius,angle) - positive=right, negative=left, 0=sharp
Transforms (chainable): at(x,y,z) absolute, move({x?,y?,z?}) relative, rotate({x?,y?,z?}) degrees, scale({all?,x?,y?,z?}) multiplicative
CSG (static, immutable): SUBTRACT(src,...cut), UNION(src,...add), INTERSECT(a,b), MERGE(solids[]) - respects setNegative()
Grids (static, immutable): GRID_X(solid,{cols,spacing?}), GRID_XY(solid,{cols,rows,spacing?}), GRID_XYZ(solid,{cols,rows,levels,spacing?})
Spacing: GRID_X=number, GRID_XY=[x,y], GRID_XYZ=[x,y,z]
Alignment (chainable): center({x?,y?,z?}?), align('bottom'|'top'|'left'|'right'|'front'|'back'), getBounds()
Other: setNegative(bool?), setColor(color), clone(), getVertices()
csg-builder/
├── bin/
│ └── csg-export.ts # CLI tool for exporting STL files
├── src/
│ ├── lib/
│ │ ├── 3d/
│ │ │ ├── Solid.ts # Core 3D class with primitives, CSG ops, and grids
│ │ │ └── stl.ts # STL export functionality
│ │ ├── Math.ts # Math utilities
│ │ ├── buffer.ts # Buffer utilities
│ │ └── download.ts # File download utilities
│ ├── stores/
│ │ ├── componentStore.ts # Component registry (non-Svelte, for CLI)
│ │ └── componentStore.svelte.ts # Component registry (Svelte, for web UI)
│ ├── types/ # TypeScript types
│ ├── App.svelte # Main application
│ ├── App3DScene.svelte # 3D viewport
│ └── AppNavigation.svelte # UI controls
├── projects/
│ ├── index.ts # Project registry
│ └── [project-name]/ # Your project folders
│ ├── index.ts # Component registration
│ └── *.ts # Component definitions
├── package.json
├── tsconfig.json
├── tsconfig.cli.json # TypeScript config for CLI
├── vite.config.ts
└── README.md
npm run dev- Start development servernpm run build- Build for productionnpm run preview- Preview production buildnpm run export- Export components to STL files via CLInpm run ts:check- Run TypeScript type checkingnpm run lint:check- Check code qualitynpm run lint:fix- Auto-fix linting issuesnpm run format:check- Check code formattingnpm run format:fix- Auto-format codenpm run all- Format, lint, type-check, and build
-
Create a new directory in
projects/:mkdir projects/my-project
-
Create
projects/my-project/index.ts:import { addToComponentStore } from '$stores/componentStore'; import { components as myComponents } from './myComponent'; addToComponentStore({ ...myComponents });
-
Create component files and export them
-
Add to
projects/index.ts:export * from './my-project';
Note on Component Store:
- Always import from
$stores/componentStore(not.svelte) - This works for both web UI and CLI contexts
- The system uses a dual-store architecture:
componentStore.ts- Base store (plain TypeScript array)componentStore.svelte.ts- Reactive wrapper for Svelte UI- Both share the same underlying data automatically
- Select your component from the dropdown menu
- View the 3D preview in the viewport
- Click the "Download STL" button
- Use the STL file with your 3D printer or modeling software
Export components to STL files directly from the command line without running the web UI:
List all available components:
npm run export -- --listExport to a file:
npm run export -- Box -o box.stl
npm run export -- "Chess Pawn" -o pawn.stlExport to stdout (pipe to file):
npm run export --silent -- Box > box.stl
npm run export --silent -- "Brick Wall" > wall.stlNote: Use --silent flag when piping to stdout to suppress npm output.
Benefits of CLI export:
- Automate STL generation in build scripts
- Batch export multiple components
- Integrate with CI/CD pipelines
- Export without starting the dev server
- Keep components pure (return new instances)
- Chain transformations (fluent API)
- Use static CSG methods (immutable)
- Positioning:
at(x,y,z)= absolute (don't chain),move({x?,y?,z?})= relative (accumulates) - MERGE rule: First solid cannot be negative
- Imports: Use path aliases (
$lib/,$stores/), not relative paths - Path factories:
import { Solid, straight, curve } from '$lib/3d/Solid' - Caching: Use
cacheInlineFunction()for expensive reusable components
projects/examples/ - Progressive tutorial (A-M) with inline docs:
- A-G: Primitives, operations, alignment, partials, composition, profiles, revolution
- H-M: Scaling, transforms, 3D grids, patterns, optimization, production composition
projects/castle/ - Production multi-file architecture: modular structure, caching, cross-file dependencies, advanced CSG
projects/sample/ - Working examples: box, brick wall, window, shapes showcase, chess pieces
Start with examples A-G, then explore castle project for production patterns.
Component not in dropdown: Check addToComponentStore() in projects/[project]/index.ts, export in projects/index.ts, restart dev server
Black/invisible solid: Valid CSS color, returns Solid type, non-zero volume
"First solid in MERGE cannot be negative": First element must be positive - MERGE([box, hole.setNegative()]) ✅
Slow CSG: Reduce segments, simplify geometry, chain efficiently
Positioning issues: at(x,y,z) = absolute (don't chain), move({x?,y?,z?}) = relative (accumulates)
- Chrome/Edge (recommended)
- Firefox
- Safari
Requires WebGL support.
- Fork the repository
- Create a feature branch
- Make your changes
- Run
npm run allto verify - Submit a pull request
[Add your license here]
- Three.js - 3D rendering
- three-bvh-csg - CSG operations
- Threlte - Svelte Three.js integration
- Svelte - Reactive framework
For issues and questions:
- Create an issue on GitHub
- Check existing examples in
projects/sample/ - Review documentation in
CLAUDE.md
Built with TypeScript, Three.js, and Svelte
