Skip to content

Repository files navigation

CSG Builder

A TypeScript-based 3D mesh creation tool using a component-based architecture. Build complex 3D models programmatically and export them as STL files.

Online CSG-builder demo

Overview

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.

Features

  • 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

Examples STLs

Whole castle

Brick Wall with Window

L Profile With Holes

Connector Tower 10

Castle

Tech Stack

  • 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

Prerequisites

  • Node.js >= 22.0.0
  • npm >= 10.0.0

Installation

npm install

Development

Start the development server:

npm run dev

The application will be available at http://localhost:5173

Note on Deployment:

  • Build output goes to ./docs directory

Usage

Quick Start

  1. Create a new file in projects/[your-project]/ directory
  2. Define your 3D component using the Solid class
  3. Export a components map
  4. Register your components in the project index
  5. View and export your model in the browser

Example: Simple Box

import{Solid}from'$lib/3d/Solid';importtype{ComponentsMap}from'$stores/componentStore';exportconstsimpleBox=(): Solid=>{returnSolid.cube(10,10,10,'blue');};exportconstcomponents: ComponentsMap={SimpleBox: ()=>simpleBox()};

Example: CSG Operations

// 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);

Example: Reusable Component with Negative Solids

// 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);};

Example: Primitives & Partials

// 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});

Example: Custom Profile Prisms

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

Example: Body of Revolution (Lathe Geometry)

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

Example: Import Capabilities

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

Performance Optimization

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 computation

How 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

API Reference

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()

Project Structure

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

Scripts

  • npm run dev - Start development server
  • npm run build - Build for production
  • npm run preview - Preview production build
  • npm run export - Export components to STL files via CLI
  • npm run ts:check - Run TypeScript type checking
  • npm run lint:check - Check code quality
  • npm run lint:fix - Auto-fix linting issues
  • npm run format:check - Check code formatting
  • npm run format:fix - Auto-format code
  • npm run all - Format, lint, type-check, and build

Creating a New Project

  1. Create a new directory in projects/:

    mkdir projects/my-project
  2. Create projects/my-project/index.ts:

    import{addToComponentStore}from'$stores/componentStore';import{componentsasmyComponents}from'./myComponent';addToComponentStore({
    ...myComponents});
  3. Create component files and export them

  4. 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

Exporting Models

Web UI Export

  1. Select your component from the dropdown menu
  2. View the 3D preview in the viewport
  3. Click the "Download STL" button
  4. Use the STL file with your 3D printer or modeling software

CLI Export

Export components to STL files directly from the command line without running the web UI:

List all available components:

npm run export -- --list

Export to a file:

npm run export -- Box -o box.stl
npm run export -- "Chess Pawn" -o pawn.stl

Export to stdout (pipe to file):

npm run export --silent -- Box > box.stl
npm run export --silent -- "Brick Wall"> wall.stl

Note: 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

Tips and Best Practices

  • 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

Examples & Learning Resources

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.

Troubleshooting

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)

Browser Support

  • Chrome/Edge (recommended)
  • Firefox
  • Safari

Requires WebGL support.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Run npm run all to verify
  5. Submit a pull request

License

[Add your license here]

Acknowledgments

Support

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

About

CSG-builder: A TypeScript-based 3D mesh creation tool using a component-based architecture. Build complex 3D models programmatically and export them as STL files.

Topics

Resources

Stars

7 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages