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
- Import Capabilities - Import STL files and SVG paths, use them in CSG operations like any primitive
- 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
- Mirror Operations - Create reflections and symmetric objects across axis planes (X, Y, Z)
- Grid Arrays - Create 1D, 2D, and 3D arrays with static GRID_X, GRID_XY, GRID_XYZ methods
- Circular Arrays - Create polar patterns and radial arrangements with ARRAY_CIRCULAR
- 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';importtype{ComponentsMap}from'$stores/componentStore';exportconstsimpleBox=(): Solid=>{returnSolid.cube(10,10,10,'blue');};exportconstcomponents: ComponentsMap={SimpleBox: ()=>simpleBox()};// Hollow box - outer minus innerconstouter=Solid.cube(20,20,20,{color: 'red'});constinner=Solid.cube(16,16,16,{color: 'red'});consthollowBox=Solid.SUBTRACT(outer,inner);// Box with holes - subtract multiple cylindersconstbox=Solid.cube(20,20,20,{color: 'blue'});constholeX=Solid.cylinder(3,25,{color: 'blue'}).rotate({z: 90});constholeY=Solid.cylinder(3,25,{color: 'blue'});constholeZ=Solid.cylinder(3,25,{color: 'blue'}).rotate({x: 90});constboxWithHoles=Solid.SUBTRACT(box,holeX,holeY,holeZ);// Window that can be placed in any wall and will cut through itexportconstwindow=(width: number,height: number,depth: number): Solid=>{constframe=Solid.cube(width,height,depth,'brown');// Mark opening as negative - it will be subtracted during MERGEconstopening=Solid.cube(width-4,height-4,depth*4,'gray').setNegative();constbar=Solid.cube(2,height,depth-1,'brown').move({z: -0.5});// MERGE respects negative flags: frame + opening (subtracted) + barreturnSolid.MERGE([frame,opening,bar]);};// Usage in wallexportconstwallWithWindow=(): Solid=>{constwall=Solid.cube(20,20,1,'gray');constwin=window(5,8,3).move({x: 10,y: 5});// Use move for relative positioning// Window's negative opening is already processed in MERGEreturnSolid.UNION(wall,win);};// Sphere for rounded featuresconstroundedCorner=Solid.SUBTRACT(Solid.cube(20,20,20,{color: 'red'}),Solid.sphere(3,{color: 'red'}).move({x: 10,y: 10,z: 10}));// Prism - N-sided shapesconsthexNut=Solid.SUBTRACT(Solid.prism(6,10,5,{color: 'gray'}),Solid.cylinder(4,6,{color: 'gray'})).center();// Partial geometries with angle parameter (in degrees)constpieSlice=Solid.cylinder(10,2,{color: 'red',angle: 90});consthemisphere=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 APIconstlBracket=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 Arrayconsttrapezoid=Solid.profilePrismFromPoints(8,[[0,0],[10,0],[8,5],[2,5]],{color: 'red'});// 3. Path Segments - smooth curves & controlled turnsconstroundedRect=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 curvesconsttriangle=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 methodsexportconstvase=(): Solid=>{returnSolid.revolutionSolid((shape)=>{shape.moveTo(5,0);// Bottom radius (x = radius, y = height)shape.lineTo(3,5);// Narrow middleshape.lineTo(6,10);// Wide topshape.lineTo(5,15);// Rimshape.lineTo(0,15);// Back to center},{color: 'blue'});};// 2. POINT ARRAY - Simple coordinate-based profilesexportconstchessPawn=(): Solid=>{returnSolid.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 turnsexportconstbottle=(): Solid=>{returnSolid.revolutionSolidFromPath([straight(5),// Bottom radiuscurve(2,90),// Rounded corner upstraight(8),// Body heightcurve(3,-90),// Curve inward for neckstraight(5),// Neck heightcurve(1,-90),// Top curvestraight(2)// Rim width],{color: 'green'});};// Partial revolution (90° cut-away)constquarterVase=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
Load external STL files and SVG paths to use in your designs:
import{Solid}from'$lib/3d/Solid';// Import STL file using Vite's import syntaximportstlDatafrom'./model.stl?raw';// ASCII STL// For binary STL, use ?url and fetch as ArrayBuffer// 1. STL IMPORT - Load external 3D modelsconstimported=Solid.fromSTL(stlData,{color: 'blue'});// Use imported geometry in CSG operationsconstcube=Solid.cube(20,20,20,{color: 'red'});constresult=Solid.SUBTRACT(cube,imported);// Transform imported modelsconsttransformed=Solid.fromSTL(stlData,{color: 'green'}).scale({all: 0.5}).rotate({y: 45}).move({y: 10});// 2. SVG PATH IMPORT - Extrude SVG paths into 3DconststarPath='M 10 0 L 12 8 L 20 8 L 14 13 L 16 21 L 10 16 L 4 21 L 6 13 L 0 8 L 8 8 Z';conststar=Solid.profilePrismFromSVG(starPath,3,{color: 'gold'});// Curved SVG paths with quadratic bezier curves (Q)constcurvedPath='M 0 5 Q 5 0, 10 5 Q 15 10, 20 5 L 20 10 L 0 10 Z';constcurved=Solid.profilePrismFromSVG(curvedPath,8,{color: 'pink'});// Use SVG as cutterconstplate=Solid.cube(30,20,5,{color: 'gray'});constcutout=Solid.SUBTRACT(plate,star);// 3. COMBINE IMPORTS - Mix imported and primitive shapesconstbase=Solid.fromSTL(stlData,{color: 'brown'}).align('bottom');constdecoration=Solid.profilePrismFromSVG('M 0 0 L 5 0 L 5 5 L 0 5 Z',2,{color: 'brown'});constcombined=Solid.UNION(base,decoration);Supported formats:
- STL: Binary and ASCII formats (via Three.js STLLoader)
- SVG: Path data with M, L, C, Q, A commands (via Three.js SVGLoader)
- Works with all CSG operations, transformations, and grids
- No additional dependencies required
Cache expensive component computations that are called repeatedly:
import{cacheFunction,cacheInlineFunction}from'$lib/cacheFunction';// Named functionsconstcachedWall=cacheFunction((length: number,height: number): Solid=>{returnSolid.SUBTRACT(Solid.cube(length,height,2,{color: 'gray'}),Solid.cube(5,8,3,{color: 'gray'}));});// Inline/arrow functionsexportconstWall=cacheInlineFunction('Wall',(length: number): Solid=>{/* expensive CSG ops */returnwall.align('bottom');});// Usageconstw1=Wall(20);// Computes and cachesconstw2=Wall(20);// Returns cached (instant)constw3=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)Import:fromSTL(data,opts), profilePrismFromSVG(svgPathData,height,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()
Arrays (static, immutable):
- Grids:
GRID_X(solid,{cols,spacing?}),GRID_XY(solid,{cols,rows,spacing?}),GRID_XYZ(solid,{cols,rows,levels,spacing?}) - Circular:
ARRAY_CIRCULAR(solid,{count,radius,startAngle?,endAngle?,rotateElements?}) - Mirror:
MIRROR(solid,axis)- axis: 'X'|'Y'|'Z'
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{componentsasmyComponents}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-P) with inline docs:
- A-G: Primitives, operations, alignment, partials, composition, profiles, revolution
- H-P: Scaling, transforms, 3D grids, patterns, optimization, production composition, import capabilities, circular arrays, mirror operations
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 H-P for advanced features including STL/SVG imports, circular arrays, and mirror operations.
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
