Project instructions for Claude Code when working with this codebase.
CSG Builder: TypeScript-based 3D mesh creation using component architecture. Users write TypeScript to define 3D components (React-like) from primitives (cubes, cylinders, spheres, cones, prisms). Uses CSG operations for boolean operations, exports binary STL.
Progressive examples with comprehensive inline comments:
Foundational (A-G): Primitives, operations, alignment, partials, composition, custom profiles, revolution Advanced (H-N): Scaling, transforms, 3D grids, patterns, optimization, production composition, import capabilities
Start with A-G for basics, then H-N for advanced patterns.
Complete multi-file project showing: modular structure, hierarchical composition, shared config, performance optimization, cross-file dependencies, advanced CSG.
npm run dev # Dev server (http://localhost:5173)
npm run build # Production build to ./docs
npm run export # CLI STL export tool
npm run all # format, lint, typecheck, build-
Primitive Layer (
Solidclass insrc/lib/3d/Solid.ts)- Wraps Three.js
Brushfromthree-bvh-csg - Factory methods:
cube(),cylinder(),sphere(),cone(),prism(),trianglePrism() - Static CSG:
SUBTRACT(),UNION(),INTERSECT(),MERGE()- all immutable - Static Grids:
GRID_X(),GRID_XY(),GRID_XYZ() - Fluent API: transformations return
this
- Wraps Three.js
-
Component Registry (dual-store)
- Base:
componentStore.ts- Plain array for CLI/Node - Svelte:
componentStore.svelte.ts- Reactive wrapper for UI - Auto-register via
addToComponentStore() - Type-safe
ComponentsMapenforces functions returningSolid
- Base:
Web: projects/.ts → addToComponentStore() → reactive store → UI dropdown → rendering → STL export CLI: projects/.ts → addToComponentStore() → base store → bin/csg-export.ts → STL file
$lib/*→src/lib/*$stores/*→src/stores/*$types/*,$components/*,$projects
projects/
[project-name]/
index.ts # Calls addToComponentStore()
component.ts # Exports ComponentsMap
Critical: Each project must be re-exported in projects/index.ts
Always import: import { addToComponentStore } from '$stores/componentStore' (NOT .svelte)
- All static and immutable - return new Solid instances
Solid.evaluatorperforms all operations- MERGE([base, pos, neg, pos]): Processes in array order, respects negative flags
- First solid cannot be negative (throws error)
- Negative solids:
setNegative()marks for subtraction inMERGE() - Partial geometries (angle < 360°): Uses CSG subtraction with wedge cutter
- Three.js right-handed: X-right, Y-up, Z-toward camera
- Absolute:
at(x, y, z)- all params required, sets position - Relative:
move({ x?, y?, z? })- optional properties, accumulates - Rotation: degrees, converts internally
- Scaling:
scale({ all?, x?, y?, z? })- multiplicative, cumulative
Auto-normalized: cylinder(), sphere(), cone(), prism(). Forces CSG rebuild via union with empty cube, prevents artifacts.
solid.getBounds(); // { width, height, depth, min, max, center }
solid.center(); // All axes at origin
solid.center({ x: true, y: true }); // Specific axes
solid.align('bottom'); // Edge to coordinate plane
// Directions: 'bottom', 'top', 'left', 'right', 'front', 'back'Binary STL: 80-byte header, 4-byte triangle count, per-triangle data. Coordinate swap: (X, Y, Z) → (X, -Z, Y).
npm run export -- --list
npm run export -- Box -o box.stl
npm run export --silent -- Box > box.stlSolid.cube(width, height, depth, { color? })
Solid.roundedBox(width, height, depth, { color?, radius?, segments? })
Solid.cylinder(radius, height, { color?, angle? }) // angle in degrees
Solid.sphere(radius, { color?, angle? })
Solid.cone(radius, height, { color?, angle? })
Solid.prism(sides, radius, height, { color?, angle? })
Solid.trianglePrism(radius, height, { color? })
Solid.text(text, { color?, size?, height?, curveSegments?, bevelEnabled? })// Shape API
Solid.profilePrism(height, (shape) => { shape.moveTo/lineTo/arc... }, { color? })
// Points array
Solid.profilePrismFromPoints(height, [[x,y], ...], { color? })
// Path segments
import { straight, curve } from '$lib/3d/Solid';
Solid.profilePrismFromPath(height, [straight(10), curve(5, 90), ...], { color? })
// straight(length), curve(radius, angle) - positive=right, negative=left, 0=sharp// Shape API
Solid.revolutionSolid((shape) => { ... }, { angle?, color? })
// Points (x=radius, y=height)
Solid.revolutionSolidFromPoints([[x,y], ...], { angle?, color? })
// Path segments
Solid.revolutionSolidFromPath([straight(5), curve(2, 90), ...], { angle?, color? })Profile coordinates: X=radius from center, Y=height, rotates around Y-axis
Create 3D text shapes with customizable size and extrusion depth:
// Simple text
const label = Solid.text('Hello', { size: 10, height: 2, color: 'blue' });
// Text with bevel (rounded edges)
const logo = Solid.text('LOGO', { size: 15, height: 3, bevelEnabled: true, color: 'gold' });
// Text as cutter for engraving
const plate = Solid.cube(50, 20, 5, { color: 'gray' });
const text = Solid.text('ENGRAVED', { size: 4, height: 10, color: 'gray' });
const engraved = Solid.SUBTRACT(plate, text);
// Embossed text (raised text on surface)
const base = Solid.cube(40, 30, 3, { color: 'gray' });
const embossText = Solid.text('LOGO', { size: 6, height: 2, color: 'gold' }).move({ y: 3 }); // Position on top of base
const embossed = Solid.UNION(base, embossText);Text Parameters:
text: The string to render (required, cannot be empty)size: Font size (default: 10)height: Extrusion depth along Y-axis (default: 2)curveSegments: Smoothness of curves (default: 12, lower=faster but blockier)bevelEnabled: Add rounded edges to text (default: false)color: Color of the text geometry
Important Notes:
- Uses Helvetiker Regular font (built-in)
- Text is automatically centered on XZ plane and aligned to Y=0
- For engraving/embossing, make text height > target depth to ensure complete cut
- Performance warning: Long text (>10 characters) or high curveSegments can slow CSG operations
- For better performance with long text, reduce
curveSegmentsto 8
Create boxes with filleted/rounded edges:
// Basic rounded box with auto-calculated radius (10% of smallest dimension)
const box = Solid.roundedBox(10, 10, 10, { color: 'teal' });
// Custom radius
const customBox = Solid.roundedBox(20, 15, 10, { color: 'blue', radius: 2 });
// High quality rounding (more segments)
const smoothBox = Solid.roundedBox(10, 10, 10, { color: 'red', radius: 1.5, segments: 4 });Rounded Box Parameters:
width,height,depth: Box dimensions (required, must be positive)radius: Corner radius (default: 10% of smallest dimension)- Must be ≤ half of smallest dimension
- Larger radius = more rounded corners
segments: Rounding quality (default: 2)- Higher values = smoother but more geometry
- 2 is sufficient for most cases
Use Cases:
- Mechanical parts with filleted edges
- Product design and prototyping
- Realistic objects (boxes are rarely sharp-edged in real life)
- Ergonomic designs
Load external STL files (binary or ASCII format) as Solid components:
// Import STL file using Vite's import syntax
import stlData from './model.stl?raw'; // ASCII STL
// For binary STL, use ?url and fetch as ArrayBuffer
// Create Solid from STL data
const imported = Solid.fromSTL(stlData, { color: 'blue' });
// Use in boolean operations
const cube = Solid.cube(20, 20, 20, { color: 'red' });
const result = Solid.SUBTRACT(cube, imported);
// Transform imported geometry
const transformed = Solid.fromSTL(stlData, { color: 'green' })
.scale({ all: 0.5 })
.rotate({ y: 45 })
.move({ y: 10 });Key points:
- Supports both binary and ASCII STL formats
- Automatically normalized for CSG operations
- Works with all standard Solid methods (transforms, CSG, grids)
- Uses STLLoader from Three.js (no additional dependencies)
Import SVG path data and extrude into 3D profiles:
// Simple SVG rectangle path
const rectPath = 'M 0 0 L 20 0 L 20 10 L 0 10 Z';
const rect = Solid.profilePrismFromSVG(rectPath, 5, { color: 'blue' });
// Complex path with curves (Q = quadratic bezier) - creates wavy pattern
const curvedPath = 'M 0 5 Q 5 0, 10 5 Q 15 10, 20 5 L 20 10 L 0 10 Z';
const curved = Solid.profilePrismFromSVG(curvedPath, 8, { color: 'pink' });
// Star shape
const starPath = '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';
const star = Solid.profilePrismFromSVG(starPath, 3, { color: 'gold' });
// Use in boolean operations
const plate = Solid.cube(30, 20, 5, { color: 'gray' });
const result = Solid.SUBTRACT(plate, star);SVG Path Commands Supported:
- M/m - Move to
- L/l - Line to
- H/h - Horizontal line
- V/v - Vertical line
- C/c - Cubic bezier curve
- Q/q - Quadratic bezier curve
- A/a - Arc
- Z/z - Close path
Key points:
- Extrudes SVG path along Y-axis
- Handles SVG coordinate system (Y-down → Y-up conversion)
- Works with all CSG operations
- Perfect for logos, custom profiles, 2D designs
- Uses SVGLoader from Three.js (no additional dependencies)
All imported geometries work seamlessly with CSG operations:
// STL + Primitive
const stl = Solid.fromSTL(stlData, { color: 'red' });
const cube = Solid.cube(20, 20, 20, { color: 'red' });
const combined = Solid.UNION(stl, cube);
// SVG + STL
const svg = Solid.profilePrismFromSVG(path, 5, { color: 'blue' });
const stl = Solid.fromSTL(data, { color: 'blue' });
const result = Solid.SUBTRACT(stl, svg);
// Grid from imports
const imported = Solid.fromSTL(data, { color: 'purple' });
const grid = Solid.GRID_XY(imported, { cols: 3, rows: 3, spacing: [5, 5] });import { Solid } from '$lib/3d/Solid';
import type { ComponentsMap } from '$stores/componentStore';
export const myPart = (w: number, h: number): Solid => {
const base = Solid.cube(w, h, 1, { color: 'blue' });
const hole = Solid.cylinder(2, 5, { color: 'blue' }).rotate({ x: 90 });
return Solid.SUBTRACT(base, hole);
};
export const components: ComponentsMap = {
MyPart: () => myPart(10, 10)
};export const window = (w: number, h: number): Solid => {
const frame = Solid.cube(w, h, 3, { color: 'brown' });
const opening = Solid.cube(w - 4, h - 4, 10, { color: 'gray' }).setNegative();
const bar = Solid.cube(1, h, 2, { color: 'brown' });
return Solid.MERGE([frame, opening, bar]);
};
const wall = Solid.cube(20, 20, 1, { color: 'gray' });
const win = window(5, 8).move({ x: 10, y: 5 });
return Solid.UNION(wall, win); // Window's hole cuts throughconst brick = Solid.cube(3, 1, 1, { color: 'red' });
Solid.GRID_X(brick, { cols: 10, spacing: 1 }); // 1D
Solid.GRID_XY(brick, { cols: 10, rows: 5, spacing: [1, 0.5] }); // 2D
Solid.GRID_XYZ(brick, { cols: 5, rows: 5, levels: 3, spacing: [1, 1, 2] }); // 3DArrange solids in circular/radial patterns (polar arrays):
// Gear teeth - elements face outward by default
// IMPORTANT: Element must extend in +X direction to face outward when rotated
// Width (X) > depth (Z) makes tooth point radially
const disk = Solid.cylinder(15, 8, { color: 'gray' }).align('bottom');
const tooth = Solid.cube(3, 10, 2, { color: 'gray' }) // 3 wide (X), 2 deep (Z)
.center({ x: true, z: true })
.align('bottom')
.move({ y: 8 }); // Position on top of disk
const teeth = Solid.ARRAY_CIRCULAR(tooth, { count: 24, radius: 17 });
const gear = Solid.UNION(disk, teeth);
// Bolt holes - no rotation needed for holes
const hole = Solid.cylinder(2, 10, { color: 'blue' }).setNegative();
const pattern = Solid.ARRAY_CIRCULAR(hole, { count: 8, radius: 20, rotateElements: false });
// Half circle (amphitheater seating)
const seat = Solid.cube(2, 1, 2, { color: 'red' });
const seating = Solid.ARRAY_CIRCULAR(seat, { count: 15, radius: 25, startAngle: 0, endAngle: 180 });
// Wheel spokes from center (radius = 0, just rotation)
const spoke = Solid.cube(1, 20, 2, { color: 'silver' }).center({ x: true, z: true });
const spokes = Solid.ARRAY_CIRCULAR(spoke, { count: 12, radius: 0 });
// Decorative rosette with multiple layers
const orb = Solid.sphere(2, { color: 'gold' });
const innerRing = Solid.ARRAY_CIRCULAR(orb, { count: 8, radius: 8, rotateElements: false });
const outerRing = Solid.ARRAY_CIRCULAR(orb, { count: 16, radius: 15, rotateElements: false });
const rosette = Solid.UNION(innerRing, outerRing);Parameters:
count: Number of copies (required, must be >= 1)radius: Radius of circular arrangement (required, must be > 0)startAngle: Starting angle in degrees (default: 0)endAngle: Ending angle in degrees (default: 360)rotateElements: Rotate elements to face outward (default: true)
Key Behaviors:
- Elements distributed evenly in XZ plane (horizontal circle around Y-axis)
- By default, elements rotate to face outward (like gear teeth)
- Set
rotateElements: falsefor spheres, holes, or decorative elements - Partial circles: use
startAngle/endAnglefor arcs (e.g., 0° to 180° = half circle) - Elements evenly spaced within angular range (startAngle included, endAngle excluded for full circles)
- Y position preserved: Elements maintain their vertical (Y) position, allowing layered circular patterns
- Element orientation: For radial elements, create them so they extend in the +X direction. When rotated, they'll face outward correctly. Example: for a tooth pointing outward, use
cube(3, 10, 2)notcube(2, 10, 3)- width > depth - IMPORTANT: Center elements with
.center({ x: true, z: true })before passing to ARRAY_CIRCULAR. Theradiusparameter handles X/Z positioning, but you can use.move({ y })for vertical placement
Common Use Cases:
- Mechanical: Gear teeth, splines, bolt holes, heat sink fins
- Architectural: Columns, amphitheater seating, circular windows
- Decorative: Medallions, rosettes, clock markers, mandalas
- Functional: Turbine blades, fan blades, ventilation slots, spokes
Create reflections and symmetric objects by mirroring across axis planes:
// Simple mirror - creates reflected copy
const shape = Solid.cube(10, 5, 3, { color: 'blue' }).move({ x: 8 });
const mirrored = Solid.MIRROR(shape, 'X');
// Bilateral symmetry - combine original + mirror
const half = Solid.cube(10, 20, 5, { color: 'brown' }).move({ x: 10 });
const symmetric = Solid.UNION(half, Solid.MIRROR(half, 'X'));
// Full 3D symmetry - chain multiple mirrors
const quarter = Solid.cube(5, 5, 5, { color: 'red' }).move({ x: 10, z: 10 });
const halfX = Solid.UNION(quarter, Solid.MIRROR(quarter, 'X'));
const full = Solid.UNION(halfX, Solid.MIRROR(halfX, 'Z'));
// Works with negative solids
const hole = Solid.cylinder(2, 10, { color: 'blue' }).move({ x: 5 }).setNegative();
const holes = Solid.MERGE([
Solid.cube(30, 30, 5, { color: 'blue' }),
hole,
Solid.MIRROR(hole, 'X')
]);
// Archway with symmetric pillars
const pillar = Solid.cube(5, 30, 8, { color: 'brown' }).move({ x: 15 }).align('bottom');
const arch = Solid.UNION(pillar, Solid.MIRROR(pillar, 'X'));Axis Parameter:
'X': Mirrors across YZ plane (flips X coordinates) - for left/right symmetry'Y': Mirrors across XZ plane (flips Y coordinates) - for top/bottom symmetry'Z': Mirrors across XY plane (flips Z coordinates) - for front/back symmetry
Key Behaviors:
- Returns only the mirrored copy (not combined with original)
- Use
UNION(original, MIRROR(original, axis))for bilateral symmetry - Bakes all transformations before mirroring (position, rotation, scale)
- Preserves negative flag - mirrored negative solids remain negative
- Works with all primitives, CSG results, and complex shapes
- Can chain mirrors for multi-axis symmetry
- Immutable - does not modify original solid
Common Use Cases:
- Mechanical: Symmetric gears, propellers, mirror-image parts
- Architectural: Arches, symmetric facades, bilateral structures
- Organic: Butterflies, leaves, faces (bilateral symmetry)
- Decorative: Symmetric patterns, medallions, ornaments
- Functional: Mirrored mounting holes, symmetric assemblies
import { cacheFunction, cacheInlineFunction } from '$lib/cacheFunction';
// Named function
const cachedWall = cacheFunction((length: number) => {
/* ... */
});
// Inline function
export const Wall = cacheInlineFunction('Wall', (length: number): Solid => {
/* expensive CSG ops */
return wall.align('bottom');
});
// Usage: same params = cached result
const w1 = Wall(20); // Computes and caches
const w2 = Wall(20); // Returns cached (instant)
const w3 = Wall(30); // Different params, new computationCache key: ${functionName}:${JSON.stringify(args)}
Use for: Reusable parametric components, expensive CSG ops, grid base shapes
Don't use for: One-time components, simple ops, non-serializable params
cube(w,h,d,opts), roundedBox(w,h,d,opts), cylinder(r,h,opts), sphere(r,opts), cone(r,h,opts), prism(sides,r,h,opts), trianglePrism(r,h,opts), text(text,opts)
fromSTL(data,opts), profilePrismFromSVG(svgPathData,height,opts)
profilePrism(h,builder,opts), profilePrismFromPoints(h,pts,opts), profilePrismFromPath(h,segs,opts)
revolutionSolid(builder,opts), revolutionSolidFromPoints(pts,opts), revolutionSolidFromPath(segs,opts)
straight(length), curve(radius, angle)
at(x,y,z), move({x?,y?,z?}), rotate({x?,y?,z?}), scale({all?,x?,y?,z?})
SUBTRACT(src,...others), UNION(src,...others), INTERSECT(a,b), MERGE(solids[])
GRID_X(solid,{cols,spacing?}), GRID_XY(solid,{cols,rows,spacing?}), GRID_XYZ(solid,{cols,rows,levels,spacing?}), ARRAY_CIRCULAR(solid,{count,radius,startAngle?,endAngle?,rotateElements?}), MIRROR(solid,axis)
center(axes?), align(dir), getBounds()
setNegative(bool?), setColor(color), clone(), getVertices()
- "First solid in MERGE cannot be negative" - First element in
MERGE([...])array must be positive - Position confusion -
at()is absolute (all 3 params),move()is relative (optional) - Chaining
.at()- Only last takes effect, don't chain - CSG immutability - Static methods return new Solid, originals unchanged
- Component not appearing - Check
addToComponentStore()call andprojects/index.tsexport - Import paths - Always use aliases (
$lib/,$stores/), never relative paths - Component store import - Always from
componentStore, NOTcomponentStore.svelte
- Keep components pure (return new instances)
- Use descriptive names (appear in UI)
- Chain transformations (fluent API)
- Use static CSG methods (immutable)
- Use parameters for flexibility
- Absolute:
at(x,y,z)(don't chain), Relative:move({x?,y?,z?})(accumulates) - Profile/revolution imports:
import { Solid, straight, curve } from '$lib/3d/Solid' - Cache expensive reusable components with
cacheInlineFunction()
- Import Three.js geometry in
Solid.ts - Update
geometryToBrush()type signature - Add factory method with adaptive segments:
MathMinMax(value * factor, min, max) - Optionally call
normalize()after creation
csg-builder/
├── bin/csg-export.ts # CLI export tool
├── src/
│ ├── lib/3d/
│ │ ├── Solid.ts # Core primitives, CSG, grids
│ │ └── stl.ts # STL export
│ ├── stores/
│ │ ├── componentStore.ts # Base (CLI)
│ │ └── componentStore.svelte.ts # Reactive (UI)
│ └── App*.svelte # UI components
├── projects/
│ ├── index.ts # Registry
│ └── [project]/
│ ├── index.ts # Registration
│ └── *.ts # Components
└── tsconfig.json/cli.json