This is the full developer documentation for CAD. # Start of CAD documentation # Roadmap ## v0.1 — Direct Modeling Foundation - [x] WebGPU-based 3D viewer with truck B-Rep kernel (WASM) - [x] 4 primitives: cube, sphere, cylinder, torus - [x] Boolean operations: union, subtract, intersect (cubes/cylinders only) - [x] Translate transform - [x] Save/load scenes as JSON (full B-Rep, no tessellation loss) - [x] Responsive UI: desktop sidebar, mobile bottom sheet + dock - [x] Auto-offset primitives — new objects partially overlap, ready for booleans - [x] Docs: auto-generated screenshots + lesson videos (R2-hosted) - [x] Deployed to Cloudflare Workers + custom domain ## v0.2 — Undo/Redo + Gizmo - [x] **UUID identity** — every object has a stable UUID v4, persisted through transforms and export/import - [x] **Undo/redo** — snapshot-based with Ctrl+Z / Ctrl+Shift+Z keyboard shortcuts - [x] **Operation grouping** — related operations (add + offset) grouped into single undo steps - [x] **Timeline UI** — visual strip showing recent operations as clickable chips - [x] **Click-to-select** — ray-cast picking via bounding sphere intersection - [x] **Translate gizmo** — 3-axis colored arrows (X=red, Y=green, Z=blue), drag to move - [x] **Gizmo cancel** — Escape reverses drag, restores original position - [x] **Automerge integration** — CRDT-based op log for collaborative editing - [x] **Cross-tab sync** — BroadcastChannel adapter for same-browser collaboration - [x] **Document management** — new doc, share URL, example scenes - [x] **Keyboard shortcuts** — Ctrl+Z undo, Ctrl+Shift+Z redo, Escape cancel, Delete remove - [x] **24 E2E tests** — Playwright tests covering all operations including gizmo ## v0.3 — Parametric Modeling (Current) - [x] **ezpz constraint solver** — integrated KittyCAD/ezpz for 2D sketch constraints (11 constraint types) - [x] **Sketch mode** — 2D sketch on XY/XZ/YZ planes with points and edges - [x] **Sketch constraints** — fixed, horizontal, vertical, distance, H/V-distance, coincident, parallel, perpendicular, equal length, midpoint - [x] **Constraint solver** — Newton-Raphson solver via ezpz, live preview of solved positions - [x] **Extrude** — 2D profile → 3D solid via truck `tsweep` (closed loop detection) - [x] **Quick rectangle** — one-click constrained rectangle with auto-constraints - [x] **Sketch export/import** — JSON serialization for Automerge replay - [x] **Automerge sketch ops** — `sketch_extrude` in op log, collaborative replay - [x] **31 tests** — 9 Rust unit (sketch), 11 golden resource, 11 Playwright E2E sketch tests - [ ] **Rotate gizmo** — circular handles for rotate-around-axis - [ ] **Scale gizmo** — square handles for uniform/non-uniform scale ## Medium Term - [ ] **Boolean ops for all shapes** — fix sphere/torus booleans in truck-shapeops - [ ] **STEP import/export** — leverage truck-stepio for industry-standard CAD interchange - [ ] **kkrpc integration** — bidirectional RPC for server-side modeling operations - [ ] **Mesh raycasting** — upgrade from bounding sphere to triangle-level picking - [ ] **Snap-to-grid** — constrain drag/sketch to grid increments - [ ] **Multi-select** — Shift+click to select multiple objects - [ ] **RDK (robotics) integration** — CAD modeling with robot kinematics/planning ## Long Term - [ ] **Assembly mode** — multi-part assemblies with mates/joints - [ ] **Server-side rendering** — headless WebGPU for thumbnails and CI screenshots (Tier 3) - [ ] **Revolve** — 2D profile → 3D solid via truck `rsweep` - [ ] **Plugin system** — extend with custom operations via WASM modules - [x] **Hugo docs site** — full documentation site on Cloudflare Pages - [ ] **Mobile-first touch gestures** — multi-touch transform gizmos ## Known Issues - **Sphere/Torus booleans fail** — truck-shapeops NURBS surface intersection crashes. Cubes/cylinders work. - **Object size changes after translate** — bounding-box normalization rescales rendered objects. B-Rep geometry is correct. - **Large translations go off screen** — camera doesn't follow objects. Keep values small. - **Bounding sphere picking** — imprecise for elongated/flat objects. Mesh raycasting planned. # Gizmo — WYSIWYG Direct Manipulation Fusion 360-style direct manipulation: click objects to select, drag gizmo handles to transform. ## Interaction State Machine (Rust) ``` IDLE ──click object──> SELECTED ──drag arrow──> DRAGGING ^ │ ^ │ └──click empty───────────┘ └──mouseup / Escape─────┘ ``` Three modes in `InteractionMode` enum: - **Idle** — no selection, normal camera orbit - **Selected** — object highlighted, gizmo arrows visible, camera still works - **Dragging** — constrained axis movement, camera orbit disabled, live preview ## Picking - **Bounding sphere** per object, computed from solid AABB - `Camera::ray(ndc)` casts ray from screen coordinates - Ray-sphere intersection finds closest hit - Gizmo arrow picking via ray-to-segment distance ## Gizmo Geometry - 3 colored `WireFrameInstance` arrows (X=red, Y=green, Z=blue) at selected object center - Arrows scaled proportionally to camera distance (constant screen size) - During drag: active axis highlighted, others dimmed ## Drag Mechanics - Screen-to-world: project mouse NDC delta onto constrained axis via ray-axis closest-point math - Live preview: `translate_object()` called incrementally during drag - Cumulative delta tracked for final commit - Escape reverses cumulative translation (cancel) ## JS ↔ WASM API Public methods called from JS: - `select_object_at(ndc_x, ndc_y) → JsValue` — pick and select - `begin_gizmo_drag(ndc_x, ndc_y) → JsValue` — start drag if clicking gizmo arrow - `update_gizmo_drag(ndc_x, ndc_y, prev_ndc_x, prev_ndc_y)` — live preview - `end_gizmo_drag() → JsValue` — finish drag, return { objectId, dx, dy, dz } - `cancel_gizmo_drag() → bool` — reverse translation - `set_on_select(f)` / `set_on_drag_complete(f)` — JS callbacks ## Automerge Integration On drag complete, JS commits a single `translate` operation to the Automerge op log. No ops created during drag (live preview only). Undo reverses the committed op. ## Future - **Rotation gizmo** — circular handles for rotate-around-axis - **Scale gizmo** — square handles for uniform/non-uniform scale - **Mesh raycasting** — upgrade from bounding sphere to triangle-level picking - **Snap-to-grid** — constrain drag to grid increments - **Multi-select** — Shift+click to select multiple objects # Direct vs. Parametric Modeling ## Vision We provide both modeling paradigms: - **Direct modeling** (like SketchUp) — for architects, quick concept work - **Parametric modeling** (like Fusion 360) — for mechanical engineers, precise constraint-driven design ## Direct Modeling (v0.1–v0.2) Implemented features: - Primitive creation (cube, sphere, cylinder, torus) - Boolean operations (union, subtract, intersect) - Gizmo-based translate transform (click-drag) - Undo/redo with Automerge CRDT op log - Scene save/load as JSON B-Rep - Collaborative editing via Automerge + BroadcastChannel ## Parametric Modeling (v0.3 — Current) ### Constraint Solver: ezpz We use [KittyCAD/ezpz](https://github.com/KittyCAD/ezpz) as the 2D geometric constraint solver. - **Language**: Pure Rust, WASM-compatible - **What it does**: Takes a set of geometric constraints and solves positions/dimensions - **Constraints implemented**: fixed, horizontal, vertical, distance, H/V-distance, coincident, parallel, perpendicular, equal length, midpoint - **Solver**: Newton-Raphson (Gauss-Newton with Tikhonov regularization) - **Local path**: `.src/ezpz/kcl-ezpz` (Cargo path dependency) - **Integration**: Wrapper types in `sketch.rs` bridge serde-serializable sketch types to ezpz's non-serde internal types ### Parametric Workflow ``` Sketch Plane → Points + Edges → Constraints → Solver → Extrude → 3D B-Rep Solid ``` 1. **Sketch plane**: User selects XY, XZ, or YZ plane 2. **2D profile**: Add points with (x, y) coordinates, connect with edges 3. **Constraints**: Add geometric constraints (fixed, distance, horizontal, etc.) 4. **Solver**: ezpz solves the constraint system, returns solved positions 5. **Closed loop**: Edge graph is walked to find a polygon boundary 6. **Extrude**: truck's `tsweep` sweeps the 2D face along the plane normal 7. **Automerge**: `sketch_extrude` op stores full sketch JSON for collaborative replay ### Quick Rectangle One-click helper creates a fully constrained rectangle: - 4 points at (0,0), (w,0), (w,h), (0,h) - 4 edges forming a closed loop - 7 constraints: fixed origin, 2 horizontal edges, 2 vertical edges, 2 distance constraints ### Integration Points | Component | Role | Status | |---|---|---| | ezpz (`kcl-ezpz`) | 2D constraint solving | Integrated | | truck-modeling | vertex → line → wire → face builder | Integrated | | truck-modeling `tsweep` | 2D face → 3D solid extrusion | Integrated | | Automerge | Op log with `sketch_extrude` operation | Integrated | | WASM | All runs in browser via WebAssembly | Integrated | ### What's Next - **Arc/circle entities** — curved sketch geometry - **Revolve** — `rsweep` for rotational sweeps - **Face-based sketch planes** — sketch on existing solid faces - **Feature tree UI** — visual history with re-evaluation # Architecture Overview CAD/spatial platform. truck B-Rep kernel + Automerge CRDT + isomorphic API. Runs everywhere. ## Three Layers | Layer | What | Tech | |---|---|---| | HTTP API | Isomorphic across all targets | Hono + Zod | | GUI push | Server → client updates | Datastar + SSE (no websockets) | | WASM boundary | JS/TS ↔ Go/Rust WASM | kkrpc | ## Four Target Classes | Target | Rendering | WASM Transport | Build Tooling | |---|---|---|---| | Browser (web) | WebGPU (Tier 1) | SharedWorker | standard web | | Native webviews | WebGPU (Tier 1) | Web Worker | goup-util | | CF Workers | none | direct call | wrangler | | Bare metal | none (or headless) | stdio | bun/node/deno | Native webviews: WKWebView (macOS/iOS), WebView2 (Windows), WebKitGTK (Linux), Chromium WebView (Android). ## Rendering Tiers - **Tier 1 (browser-native)**: truck B-Rep kernel + wgpu compiled to WASM, renders locally via WebGPU. Zero server cost. This is the product. - **Tier 3 (server-rendered video)**: same Rust binary running natively on a GPU server, streaming H.264 video via WebRTC (LiveKit). Works on any device. Demo/fallback only. - No Tier 2. Binary decision: can the browser handle WebGPU? Yes → Tier 1. No → Tier 3. ## State and Sync - **Automerge**: CRDT op log for collaborative editing. Runs as WASM on browser + CF Workers + bare metal. - **R2 + D1**: Persistent storage on Cloudflare. - **NATS JetStream**: Real-time sync of Automerge ops between participants. ## What Runs Where | Module | Browser | CF Workers | Bare Metal | GPU Server (Tier 3) | |---|---|---|---|---| | Automerge (Rust WASM) | Yes — CRDT state | Yes — sync/merge/R2 | Yes — sync | native Rust | | truck (Rust WASM) | Yes — B-Rep + WebGPU | NO | maybe — headless | native Rust | | Go business logic | Yes — validation | Yes — API logic | Yes | native Go | truck does NOT ship to CF Workers. No rendering on CF. ## WASM Compilation Two builds, not three. Browser + CF share `wasm32-unknown-unknown`. Bare metal gets `wasm32-wasip1`. - Rust: cargo + wasm-bindgen + wasm-opt - Go: TinyGo only (standard Go too large for CF/browser) - Automerge: start with npm package, link Rust crates later if needed ## Key Decisions - No websockets. Datastar + SSE for data push. WebRTC (LiveKit) only for Tier 3 video. - kkrpc for all JS ↔ WASM communication. Same typed API, transport swapped per target. - SharedWorker in browser (one WASM shared across tabs). Web Worker in native webviews. - Design to CF Workers limits (3 MB compressed, 128 MB memory, 1s startup). - Thin JS ↔ WASM boundary. Coarse operations. Typed arrays for bulk data. Lazy init. ## Related Docs - [kkrpc](kkrpc.md) — WASM boundary layer, transports, compilation strategy - [automerge](automerge.md) — CRDT sync, storage, state management - [undo-redo](undo-redo.md) — Undo/redo and operation log - [webgpu](webgpu.md) — GPU rendering architecture - [gizmo](gizmo.md) — Direct manipulation (click-select, drag-transform) - [direct-vs-parametric](direct-vs-parametric.md) — Parametric modeling with ezpz constraint solver # MCP and API Stack ## Stack | Layer | Technology | Purpose | |---|---|---| | Server | Hono + Zod | Isomorphic HTTP API with validation | | Auth | Better-Auth | TypeScript authentication | | Docs | @hono/zod-openapi | Auto-generated OpenAPI documentation | | AI | @hono/mcp | Model Context Protocol integration | ## Cloudflare Worker The CAD API runs as a Cloudflare Worker (`systems/truck/worker/`): - Hono router for API endpoints - Static asset serving for the web GUI - R2 bucket bindings for document storage - Deployed to `truck-cad.gedw99.workers.dev` and `cad.ubuntusoftware.net` # Sketch and Extrude Pipeline Technical documentation for the parametric modeling system: 2D constrained sketch → solve → extrude to 3D solid. ## Architecture ``` ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ ┌──────────┐ │ sketch-ui.js│────▶│ SceneController│────▶│ sketch.rs │────▶│ truck │ │ (JS UI) │ │ (WASM API) │ │ (solve+loop) │ │ (B-Rep) │ └─────────────┘ └──────────────┘ └───────────────┘ └──────────┘ │ │ │ ▼ │ ┌──────────────┐ │ │ kcl-ezpz │ │ │ (constraint │ │ │ solver) │ └──── Automerge op log ──────────▶└──────────────┘ ``` ## Rust Layer ### Types (`crates/truck-webgpu-gui/src/sketch.rs`) | Type | Purpose | |---|---| | `SketchPlane` | XY, XZ, YZ — determines 2D→3D mapping and extrude direction | | `SketchPoint` | UUID + (x, y) initial position | | `SketchEdge` | UUID + two point UUIDs | | `SketchConstraintKind` | Enum with 11 variants (Fixed, Horizontal, Vertical, Distance, etc.) | | `SketchConstraint` | UUID + kind | | `Sketch` | Full sketch: plane, points, edges, constraints | | `SolvedSketch` | Result of constraint solving: `Vec<(Uuid, f64, f64)>` positions | All types derive `Serialize` and `Deserialize` for JSON round-trip (Automerge storage). ### Constraint Solver Integration ezpz types (`DatumPoint`, `DatumLineSegment`, `Constraint`) do **not** implement serde. The `SolveContext` struct bridges this gap: 1. Creates fresh ezpz datums from sketch points/edges at solve time 2. Maps sketch `SketchConstraintKind` variants to ezpz `Constraint` values 3. Builds guess vectors from initial positions 4. Calls `kcl_ezpz::solve()` with Newton-Raphson solver 5. Extracts solved positions via `outcome.final_value_point()` Key detail: `Fixed { point_id, x, y }` expands to **two** ezpz constraints — `Constraint::Fixed(dp.x_id, x)` and `Constraint::Fixed(dp.y_id, y)`. ### Extrude Pipeline `sketch_to_solid(sketch, height)` follows truck's builder pattern: 1. **Solve** — run constraint solver to get final 2D positions 2. **Closed loop** — `find_closed_loop()` walks the edge graph to order points into a polygon boundary 3. **3D vertices** — map 2D solved positions to 3D via `SketchPlane::to_3d(x, y)` 4. **Wire** — `builder::vertex()` → `builder::line()` → `Wire::from(edges)` 5. **Face** — `builder::try_attach_plane(&[wire])` creates a planar face 6. **Extrude** — `builder::tsweep(&face, normal * height)` sweeps along plane normal This is the same pattern as `make_cube()` in lib.rs — vertex → line → wire → face → tsweep. ## WASM API (`crates/truck-webgpu-gui/src/wasm_app.rs`) 12 methods on `SceneController`: | Method | Returns | Description | |---|---|---| | `begin_sketch(plane)` | sketch UUID | Start sketch on XY/XZ/YZ | | `sketch_add_point(x, y)` | point UUID | Add a point | | `sketch_add_edge(p0_id, p1_id)` | edge UUID | Connect two points | | `sketch_add_constraint(type, params_json)` | constraint UUID | Add constraint (11 types) | | `sketch_solve()` | JSON positions | Solve and return `[{id, x, y}]` | | `sketch_extrude(height)` | object UUID | Extrude → solid, add to scene | | `sketch_cancel()` | — | Discard active sketch | | `sketch_export()` | JSON string | Serialize sketch for Automerge | | `sketch_import(json)` | bool | Restore sketch from JSON | | `has_active_sketch()` | bool | Check if sketch is active | The active sketch is stored in `SharedState.active_sketch: Option`. Extrude consumes it (takes ownership via `.take()`). On extrude failure, the sketch is put back so the user can fix it. ## JavaScript Layer ### `web/gui/sketch-ui.js` IIFE that manages sketch state client-side: - Tracks `sketchPoints`, `sketchEdges`, `sketchConstraints` arrays - Populates point/edge dropdowns for constraint UI - Shows/hides constraint fields based on selected type - Quick rectangle helper: 4 points + 4 edges + 7 constraints in one click - Exposes `window.sketchUI = { isActive, cancel() }` for keyboard shortcuts ### `web/gui/cad-document.js` Automerge integration: - `sketch_extrude` operation type stores `{ sketchJson, height }` in op log - On replay: `ctrl.sketch_import(sketchJson)` then `ctrl.sketch_extrude(height)` - Enables collaborative sketch → extrude across peers ## Constraint Types | Kind | ezpz mapping | Parameters | |---|---|---| | `Fixed` | 2x `Constraint::Fixed` (x, y separately) | point_id, x, y | | `Horizontal` | `Constraint::Fixed` on y0==y1 | edge_id | | `Vertical` | `Constraint::Fixed` on x0==x1 | edge_id | | `Distance` | `Constraint::EuclideanDistance` | p0_id, p1_id, value | | `HorizontalDistance` | `Constraint::HorizontalDistance` | p0_id, p1_id, value | | `VerticalDistance` | `Constraint::VerticalDistance` | p0_id, p1_id, value | | `Coincident` | `Constraint::Coincident` | p0_id, p1_id | | `Parallel` | `Constraint::Parallel` | edge0_id, edge1_id | | `Perpendicular` | `Constraint::Perpendicular` | edge0_id, edge1_id | | `EqualLength` | `Constraint::EqualLength` | edge0_id, edge1_id | | `Midpoint` | `Constraint::InternalMidpoint` | edge_id, point_id | ## Tests ### Rust Unit Tests (9) In `crates/truck-webgpu-gui/src/sketch.rs`: - `test_empty_sketch` — empty sketch solves to empty result - `test_unconstrained_sketch` — points keep initial positions - `test_fixed_point` — fixed constraint pins a point - `test_solve_rectangle` — 4 points + constraints → correct solved positions - `test_solve_triangle_with_distances` — triangle with distance constraints - `test_sketch_serialization_roundtrip` — JSON round-trip preserves sketch - `test_extrude_rectangle` — rectangle → box with 6 faces - `test_extrude_triangle` — triangle → prism with 5 faces - `test_sketch_plane_to_3d` — XY/XZ/YZ coordinate mapping ### Playwright E2E Tests (11) In `tests/e2e/sketch.spec.ts`: - WASM API tests: begin_sketch, add_point, add_edge, add_constraint, solve, extrude (rectangle + triangle) - Round-trip: export/import preserves sketch - Edge cases: < 3 edges fails gracefully, has_active_sketch tracks state - Cancel: sketch_cancel clears active sketch - Multi-plane: XZ plane extrude produces solid ### Running Tests ```sh task truck:test:crate # Rust unit tests (sketch + golden) task truck:test:sketch # Playwright E2E sketch tests (needs gui:serve) task truck:ci # Full CI: check + test + WASM build ``` # WebGPU Rendering GPU rendering architecture for the CAD platform. ## Tier 1: Browser-Native WebGPU The primary rendering path. truck B-Rep kernel + wgpu compiled to WASM, renders locally via WebGPU. - Zero server cost - Full B-Rep precision - Real-time interaction (gizmo, camera orbit) - Requires Chrome 113+ or equivalent WebGPU support ### Stack | Component | Role | |---|---| | truck-platform | Scene, Camera, Lights, event loop (winit) | | truck-rendimpl | PBR shaders, InstanceCreator, WireFrameInstance | | wgpu | WebGPU abstraction layer | | wasm-bindgen | Rust ↔ JS bridge | ### Build ```sh task truck:wasm:build-browser-renderer # Outputs to web/gui/pkg-browser-renderer/ ``` ## Tier 3: Server-Rendered Video (Future) For devices without WebGPU support. Same Rust binary running natively on a GPU server, streaming H.264 video via WebRTC (LiveKit). See `docs/adr/webgpu_server.md` for the full Tier 3 specification. ## Canvas Setup The browser renderer uses a single `` element. The WASM module: 1. Initializes wgpu with the canvas 2. Creates a Scene with camera + lighting 3. Runs the winit event loop for input handling 4. Renders at display refresh rate ## Object Rendering Each solid is rendered as: - **PolygonInstance** — filled PBR surface (tessellated from B-Rep) - **WireFrameInstance** — edge wireframe overlay - **Gizmo arrows** — colored WireFrameInstance lines for selected object # Automerge — Collaborative Editing CRDT-based state sync for collaborative CAD editing across browser, Cloudflare Workers, and bare metal. ## Why Automerge We have state sync issues across three deployment contexts (browser, CF Workers, bare metal). The end data lives on D1 and R2, but operations happen in all 3 contexts. Automerge provides: - **Conflict-free merging** — concurrent edits from multiple users merge automatically - **Offline-first** — works without network, syncs when reconnected - **Operation log** — every change is recorded, enabling undo/redo and audit trails ## How It's Used ### Operation Log All CAD operations are recorded as Automerge operations: | Operation | Data | |---|---| | `add` | `{ type, id, params }` | | `translate` | `{ objectId, dx, dy, dz }` | | `boolean` | `{ op, idA, idB, resultId }` | | `delete` | `{ objectId }` | | `clear` | `{}` | ### Document Structure Each scene is an Automerge document containing: - Operation log (ordered list of ops) - Scene state (derived from replaying ops) ### Sync - **Cross-tab**: BroadcastChannel adapter (same browser) - **Cross-device**: WebSocket adapter via sync server - **Storage**: IndexedDB in browser, R2/D1 on Cloudflare ## References - https://automerge.org - https://automerge.org/docs/guides/using-automerge-with-llms/ - LLM reference: `docs/llms/automerge-llms-full.txt` (166KB) # kkrpc — WASM Boundary Layer Typed bidirectional RPC for JS/TS ↔ WASM communication. ## Role kkrpc is the typed boundary layer between JavaScript and WASM modules. Same API, transport swapped per target: | Target | Transport | |---|---| | Browser | SharedWorker (one WASM shared across tabs) | | Native webviews | Web Worker | | CF Workers | Direct call (same isolate) | | Bare metal | stdio (Node/Bun/Deno) | ## WASM Compilation Strategy Two builds, not three: | Build | Target | Used By | |---|---|---| | `wasm32-unknown-unknown` | Browser + CF Workers | wasm-bindgen | | `wasm32-wasip1` | Bare metal | WASI runtime | ## Key Principles - **Thin boundary**: Coarse operations, not fine-grained calls - **Typed arrays**: For bulk data transfer (mesh vertices, etc.) - **Lazy init**: WASM modules initialized on first use - **Zero-copy**: Where possible, share memory instead of copying ## CF Workers Limits Design to these constraints: - 3 MB compressed WASM - 128 MB memory - 1s startup time ## References - https://docs.kkrpc.kunkun.sh - LLM reference: `docs/llms/kkrpc-llms-full.txt` (221KB) # Undo/Redo System Two-layer architecture combining fast local snapshots with Automerge CRDT operation log. ## Architecture ### Layer 1: Snapshot-based Local Undo (fast) - Before each operation, capture a JSON snapshot of the entire scene - Undo = restore the previous snapshot (< 5ms) - Redo = restore the next snapshot - Stack-based: undo stack + redo stack ### Layer 2: Automerge Operation Log (collaborative) - Every operation is also recorded in the Automerge document - Enables collaborative undo (each user undoes their own ops) - Persistent across sessions via IndexedDB/R2 ## Operation Types | Op | Recorded Data | Undo Behavior | |---|---|---| | `add` | type, id, params | Remove the added object | | `translate` | objectId, dx, dy, dz | Reverse the translation | | `boolean` | op, idA, idB, resultId | Restore both input objects | | `delete` | objectId, snapshot | Restore deleted object | | `clear` | snapshot | Restore entire scene | ## Grouping Related operations are grouped into single undo steps: - Adding a primitive + auto-offset = one undo step - This prevents partial undos (e.g., object added but not positioned) ## Timeline UI The timeline strip shows recent operations as clickable chips. Each chip represents one undo step. ## UUID Stability All objects have stable UUIDs (v4). UUIDs persist through: - Translations - Scene export/import - Undo/redo cycles - Cross-tab sync ## Performance - Snapshot capture: < 5ms - Snapshot restore: < 5ms - Scene replay (10 ops): ~200ms # truck — Rust CAD Kernel Pure Rust B-Rep CAD kernel with WebGPU rendering. ## What It Is truck is a Rust library for B-Rep (Boundary Representation) solid modeling. It provides: - **truck-modeling**: Geometric primitives, sweeps, boolean operations - **truck-platform**: Scene management, camera, lighting, event loop - **truck-rendimpl**: PBR rendering, instance creation, wireframes - **truck-meshalgo**: Mesh algorithms and tessellation - **truck-shapeops**: Boolean operations (union, subtract, intersect) ## How We Use It truck is cloned to `.src/truck/` as a path dependency. Our `crates/truck-webgpu-gui/` crate builds on top of it to create the CAD application. ### Key Operations | Operation | truck API | |---|---| | Create cube | `builder::cube(vertex, edge)` | | Create sphere | `builder::sphere(center, radius, ...)` | | Create cylinder | `builder::cylinder(bottom, top, radius)` | | Translate | Modify vertex positions in topology | | Boolean union | `truck_shapeops::and(solid_a, solid_b)` | | Boolean subtract | `truck_shapeops::or(solid_a, solid_b)` (inverted) | | Extrude profile | `builder::tsweep(face, vector)` | | Revolve profile | `builder::rsweep(face, origin, axis, angle)` | ### Serialization Solids serialize to JSON via serde. This preserves full B-Rep precision (no tessellation loss). ## Source - Upstream: https://github.com/ricosjp/truck - Fork: https://github.com/joeblew999/truck - Local: `.src/truck/` ## Build ```sh task truck:deps:clone # Clone to .src/truck/ task truck:build # Build library task truck:test # Run tests task truck:wasm:build-browser-renderer # Build WASM for browser ``` # Sketch and Extrude Create 3D solids from 2D constrained sketches. This is the parametric modeling workflow: draw a 2D profile, add geometric constraints, solve, then extrude into a 3D solid. ## Quick Start: Rectangle Extrude The fastest way to create a parametric solid: 1. Switch to the **Sketch** tab (click dock button or press **S**) 2. Set **W** and **H** for your rectangle dimensions 3. Click **Rect** — creates a fully constrained rectangle 4. Set **extrude height** and click **Extrude** A new 3D box appears in the scene, created from the constrained 2D sketch. ## Full Sketch Workflow ### 1. Begin Sketch - Select a sketch plane: **XY**, **XZ**, or **YZ** - Click **Begin Sketch** to enter sketch mode ### 2. Add Points - Enter **x** and **y** coordinates - Click **+Pt** to add each point - Points appear in the dropdowns for edges and constraints ### 3. Add Edges - Select two points from the **P0** and **P1** dropdowns - Click **+Edge** to connect them - Edges appear in the constraint edge dropdowns ### 4. Add Constraints Select a constraint type from the dropdown. The UI shows/hides relevant fields based on the type: | Constraint | What it does | Required fields | |---|---|---| | **Fixed** | Pin a point to exact (x, y) | Point, x value, y value | | **Horizontal** | Force an edge to be horizontal | Edge | | **Vertical** | Force an edge to be vertical | Edge | | **Distance** | Set distance between two points | Two points, value | | **H-Distance** | Set horizontal distance between points | Two points, value | | **V-Distance** | Set vertical distance between points | Two points, value | | **Coincident** | Make two points overlap | Two points | | **Parallel** | Make two edges parallel | Two edges | | **Perpendicular** | Make two edges perpendicular | Two edges | | **Equal Length** | Make two edges the same length | Two edges | | **Midpoint** | Place a point at the midpoint of an edge | Edge, point | ### 5. Solve (Preview) Click **Solve** to run the constraint solver and preview the solved point positions. The status bar shows the solved coordinates. ### 6. Extrude - Set the **extrude height** (distance along the plane normal) - Click **Extrude** to create the 3D solid - The sketch is consumed and a new solid appears in the scene - Requires at least 3 edges forming a closed loop ### 7. Cancel Click **Cancel Sketch** or press **Escape** to discard the active sketch without creating a solid. ## Keyboard Shortcuts | Key | Action | |---|---| | S | Switch to Sketch tab | | Escape | Cancel active sketch | ## Tips - **Closed loop required**: Edges must form a closed polygon (each point connected to exactly 2 edges) for extrude to work. - **Over-constraining**: Adding too many constraints may cause the solver to produce unexpected results. Start with fixed + horizontal/vertical + distances. - **Quick Rect**: The rectangle helper auto-creates 4 points, 4 edges, and 7 constraints (fixed origin, H/V edges, distances). It's the easiest way to start. - **Multiple sketches**: Each extrude creates an independent solid. You can add primitives and sketch-extruded solids in the same scene, then combine them with boolean operations. ## Automerge Collaboration Sketch extrude operations are stored in the Automerge op log as `sketch_extrude` operations. When a collaborator extrudes a sketch, the full sketch JSON is replayed on your side to produce the same solid. This means sketches are fully collaborative — the constraint solving and extrusion happen independently on each peer. # Getting Started A parametric 3D modeler running entirely in your browser using WebGPU. ## First Load When the app loads you see a default cube in a 3D viewport. The cube is automatically selected (highlighted with a gizmo). ![Initial scene with default cube](/screenshots/01-initial-scene.png) ## Camera Controls | Input | Action | |---|---| | Left drag | Rotate camera | | Scroll / pinch | Zoom in/out | | Right-click drag | Move light source | | Touch (1 finger) | Rotate camera | | Touch (2 finger pinch) | Zoom in/out | ## Gizmo Controls | Input | Action | |---|---| | Click object | Select it (shows translate gizmo) | | Drag gizmo arrow | Move object along that axis | | Escape | Cancel drag (reverts to original position) | | Delete | Delete selected object | | Click empty space | Deselect | ## Keyboard Shortcuts | Key | Action | |---|---| | Ctrl+Z | Undo | | Ctrl+Shift+Z | Redo | | Escape | Cancel drag, deselect, or cancel active sketch | | Delete | Delete selected object | | S | Switch to Sketch tab | ## UI Overview ![Full UI with multiple objects](/screenshots/09-ui-overview.png) ## Requirements - Chrome 113+ or any browser with WebGPU support - Desktop or tablet (mobile works with touch controls) # Known Issues ## Current Limitations - **Sphere/Torus booleans fail** — Boolean operations only work reliably with cubes and cylinders. Sphere and torus booleans crash due to NURBS surface intersection limitations in the upstream truck-shapeops library. - **Object size changes after translate** — Moving an object may slightly change its rendered size due to bounding box renormalization. This is a rendering artifact; the B-Rep geometry is unchanged. - **Large translations go off screen** — The camera doesn't follow translated objects. Keep dx/dy/dz values small (0.1 to 5.0) to stay in view. - **No rotate/scale gizmo** — Only translate gizmo is currently supported. Rotate and scale transforms are planned. - **Bounding sphere picking** — Object selection uses bounding sphere approximation, which may be imprecise for elongated or flat objects. Mesh-level raycasting is planned. ## Sketch Limitations - **No arc/circle sketch entities** — Only straight edges (line segments) are supported. Arcs and circles are planned. - **No sketch overlay** — Sketch geometry is not visually rendered on the canvas. Use the Solve button to preview solved positions in the status bar. - **Closed loop required** — Extrude requires edges to form a single closed polygon. Branching or open edge graphs will fail. - **No face-based sketch planes** — Sketch planes are limited to XY, XZ, YZ. Sketching on a face of an existing solid is planned. ## Fixed in v0.2 These issues from v0.1 have been resolved: - ~~No click-to-select~~ — Objects can now be selected by clicking in the viewport - ~~No undo~~ — Full undo/redo with Ctrl+Z / Ctrl+Shift+Z - ~~Objects selected by index only~~ — Gizmo-based direct manipulation now available ## Fixed in v0.3 - ~~No parametric modeling~~ — Full sketch → constrain → extrude workflow now available - ~~No constraint solver~~ — ezpz integrated with 11 constraint types # Boolean Operations Combine overlapping objects using CSG (Constructive Solid Geometry). ## Operations | Operation | Description | |---|---| | **Union** | Merge A and B into one solid | | **Subtract** | Cut B out of A | | **Intersect** | Keep only the overlapping region | ![Two objects set up for a boolean](/screenshots/06-boolean-setup.png) ## How to Use 1. Click an object to select it as **A**, then Shift+click a second object for **B** 2. Click the operation button (Union, Subtract, or Intersect) 3. The two input objects are replaced with the result The result gets a new UUID. Both input objects are removed from the scene. ![Boolean subtract result — cylinder cut from cube](/screenshots/07-boolean-subtract.png) ## Requirements - Objects **must overlap** for boolean operations to work - Objects must be valid B-Rep solids ## Known Limitations - Boolean operations work reliably with cubes and cylinders - Sphere and torus booleans may fail due to NURBS surface intersection limitations in the upstream truck-shapeops library - All boolean operations are recorded in the undo history ## Undo Boolean operations can be undone with **Ctrl+Z**. Undo restores both original objects to their pre-operation state. # Save and Load ## Save JSON Click **Save JSON** to download the current scene as a `.json` file. The file contains full B-Rep data for every object, including: - Object UUIDs - Complete solid geometry (no tessellation loss) - Exact mathematical surface definitions ## Load JSON Click **Load JSON** to restore a previously saved scene. This replaces the current scene with the loaded one. ![Save and load UI](/screenshots/08-save-load.png) ## File Format The JSON format stores an array of objects, each with: ```json [ { "id": "550e8400-e29b-41d4-a716-446655440000", "solid": { ... } } ] ``` The `solid` field contains the truck B-Rep serialization — vertices, edges, faces, and surface definitions. ## Example Scenes The **Load example** dropdown provides pre-built scenes: - Default cube - Two overlapping cubes - Multi-primitive scene - Boolean subtract result - Complex assembly ## Automerge Documents Beyond JSON save/load, scenes are also persisted via Automerge documents: - **New Doc** — creates a fresh Automerge document - **Share** — copies the document URL for collaborative editing - Documents sync across tabs via BroadcastChannel - Each document has a unique `automerge:` URL # Scene Management ## Object List The Scene section shows all objects in the current scene. Each object is displayed as `[index:uuid-prefix]`. The selected object is marked with `*`. ![Scene panel with multiple objects](/screenshots/09-ui-overview.png) ## Selection - **Click an object** in the viewport to select it - **Click empty space** to deselect - Selected objects show a translate gizmo (3 colored arrows) ## Delete - **Delete Sel.** — removes the currently selected object - **Delete key** — keyboard shortcut for the same action - Deletion can be undone with Ctrl+Z ## Clear All Removes all objects from the scene. This creates a blank scene. ## Undo / Redo | Action | Shortcut | |---|---| | Undo | Ctrl+Z | | Redo | Ctrl+Shift+Z | The undo system uses snapshots for fast restoration. Related operations (like adding a primitive and offsetting it) are grouped into single undo steps. ### Timeline The timeline strip shows recent operations as chips. Each chip represents an undoable action (add, translate, boolean, delete, clear). ## Collaborative Editing When multiple browser tabs are open, changes sync automatically via BroadcastChannel. Each tab sees the same scene state. # Creating Shapes ## Primitives Set the **Size** parameter, then click a shape button. Each new shape is automatically offset so objects partially overlap — ready for boolean operations. | Shape | Description | |---|---| | **Cube** | Box with the given edge length | | **Sphere** | Sphere with the given radius | | **Cylinder** | Radius = size/2, height = size | | **Torus** | Major radius = size, tube radius = size x 0.3 | ![Adding a sphere to the scene](/screenshots/02-add-sphere.png) ![Multiple primitives in the scene](/screenshots/04-multiple-primitives.png) ## How It Works Each primitive is created as a full B-Rep (Boundary Representation) solid using the truck CAD kernel compiled to WASM. This means: - Exact mathematical surfaces (not mesh approximations) - Boolean operations work correctly - Export preserves full precision - Each object gets a UUID for stable identity ## Size Parameter The size input (default: 1.0, minimum: 0.1) controls the scale of new primitives. Adjust it before clicking a shape button. ## Auto-Offset New shapes are automatically placed with a small offset from existing objects. This ensures they overlap, which is required for boolean operations to work. # Moving Objects ## Gizmo Drag (Direct Manipulation) Click an object to select it. Three colored arrows appear at its center: - **Red arrow** (X axis) — drag to move left/right - **Green arrow** (Y axis) — drag to move up/down - **Blue arrow** (Z axis) — drag to move forward/back Drag an arrow to move the object along that axis. The movement is constrained to a single axis for precision. ![Object translated along an axis](/screenshots/05-translate.png) ### Cancel a Drag Press **Escape** while dragging to cancel. The object snaps back to its position before the drag started. ### Commit When you release the mouse button, the translation is committed to the undo history. You can undo it with **Ctrl+Z**. ## Panel Transform You can also enter exact translation values in the Transform section of the tool panel: 1. Enter dx, dy, dz values 2. Click **Move Selected** The object moves relative to its current position. ## Camera During Drag Camera rotation is automatically disabled while dragging a gizmo arrow, so you can drag precisely without accidentally rotating the view.