Single source of truth for AI coding agents (Claude, Codex, Copilot, etc.) working in
this repository. CLAUDE.md and .github/copilot-instructions.md are thin redirects to
this file.
If a subdirectory has stricter instructions, follow both; if they conflict, use the more restrictive rule.
OpenGeometry is a browser-native CAD kernel: geometry logic written in Rust,
compiled to WebAssembly, wrapped in TypeScript for Three.js integration.
Published to NPM as opengeometry.
Use cases: browser CAD, AEC/BIM, configurators, geometry-heavy web tools, AI-assisted CAD frontends that need deterministic geometry execution.
Not the right fit for: desktop-native CAD, non-browser runtimes without WebAssembly, visualization-only apps where raw Three.js suffices.
OpenPlans is a downstream application/toolkit built on OpenGeometry. Do not position OpenPlans as the SDK itself.
App Code (Browser/Node)
│
opengeometry-three ← TypeScript wrapper (main/opengeometry-three/)
│ Shapes: Polygon, Cuboid, Cylinder, Sphere, Wedge, Sweep, Opening, Solid
│ Primitives: Line, Arc, Curve, Polyline, Rectangle
│ Editor: parametric/freeform editing helpers
│ Markup: SpotLabel
│
wasm-bindgen glue ← Generated (main/opengeometry/pkg/, never edited directly)
│
Rust Kernel ← main/opengeometry/src/
├── brep/ B-Rep topology: Vertex, Edge, HalfEdge, Face, Loop, Wire, Shell, Brep, BrepBuilder
├── primitives/ OGPolygon, OGCuboid, OGCylinder, OGSphere, OGWedge, OGSweep, etc.
├── operations/ Triangulation (Earcut), extrude, offset, sweep, winding
├── geometry/ GeometryBuffer (vertex/normal/index serialization), Triangle
├── spatial/ Placement3D (translation, rotation, scale)
├── scenegraph.rs OGSceneManager + OGEntityRegistry: multi-entity orchestration, BRep snapshots
├── export/ projection (HLR, EdgeClass), pdf (native-only), step, stl, ifc, part21
├── booleans/ Union / Intersection / Subtraction (via boolmesh crate)
├── editor/ Parametric and freeform editing
├── freeform/ OGFreeformGeometry — non-parametric editing wrapper
└── utility/ bgeometry helpers
- App calls
await OpenGeometry.create({ wasmURL })— required before anyVector3or shape construction. - App constructs primitives/shapes; each
set_config(...)call regenerates the internal B-Rep. - App retrieves Three.js geometry via
get_geometry_serialized()→BufferGeometry. - Scene assembly + projection + export goes through
OGSceneManager(or the newOGEntityRegistry).
wasm-pack build --target webcompiles Rust → WASM + JS glue →main/opengeometry/pkg/cargo build --releaseproduces native binaries (used for PDF export CLI examples)- Rollup bundles
main/opengeometry-three/index.ts→dist/index.js(ESM, Three.js as external peer dep) scripts/prepare-dist.mjscopies WASM, rewrites import paths, buildsdist/package.json
main/opengeometry/ Rust core → WebAssembly (Cargo crate)
main/opengeometry-three/ Three.js wrapper (the published TS SDK)
main/opengeometry-{webgl,babylon,ios,export-io,export-schema}/
Scaffolds — empty placeholders, see each dir's README
docs/ Mintlify documentation source (user-facing)
knowledge/ Stable architecture / domain notes (long-lived)
dist/ Generated NPM bundle — never edit by hand
scripts/ Build orchestration (prepare-dist.mjs)
.github/ CI workflows + Copilot redirect
.claude/ Claude Code skills + settings (hooks)
No Cargo workspace. Each Rust package has its own Cargo.toml. NPM root manages
the published opengeometry package; main/opengeometry-three/ has its own
package.json for the example app.
All from repo root unless noted.
npm run build # Full pipeline: build-core → build-three → prepare-dist
npm run build-core # Rust → WASM (wasm-pack) + native release build
npm run build-three # Rollup bundle → dist/index.js
npm run prepare-dist # Copy WASM, rewrite imports, write dist/package.jsonBuild order matters. build-three depends on the generated pkg/ from build-core.
npm run build does both in order.
npm test # Cargo unit + integration tests
cargo test --manifest-path main/opengeometry/Cargo.toml # Same, direct
cargo test -q --manifest-path main/opengeometry/Cargo.toml # Quiet
cargo test --manifest-path main/opengeometry/Cargo.toml \
rectangle_generates_face_loop # Single test by namenpm run lint:check # ESLint check (TypeScript)
npm run lint # ESLint with --fix
cargo fmt --check --manifest-path main/opengeometry/Cargo.toml
cargo fmt --manifest-path main/opengeometry/Cargo.toml # Apply
cargo check --manifest-path main/opengeometry/Cargo.toml # Type/borrow checknpm --prefix main/opengeometry-three run dev-example-three # Vite dev server
npm --prefix main/opengeometry-three run build-example-three # Static build
npm --prefix main/opengeometry-three run preview-example-three # Preview buildExamples live at main/opengeometry-three/examples-vite/. Each is a standalone HTML
page importing the local SDK build.
# Run from main/opengeometry/
cargo run --example pdf_camera_projection -- ./out/pdf_camera_projection.pdf
cargo run --example pdf_camera_projection_views -- ./out/pdf_camera_projection_views
cargo run --example pdf_primitives_all -- ./out/pdf_primitives
cargo run --example scenegraph_projection -- ./out/scenegraph_projection.pdf
cargo run --example scenegraph_projection_dump_json -- ./out/projection_dump
# Inspect: jq . ./out/projection_dump_scene2d.jsonnpm test runs only cargo test. Specifically:
- ~107 Rust unit tests (
#[cfg(test)]blocks across modules) - 4 integration tests in
main/opengeometry/tests/primitives_smoke.rs - 0 doc tests
- 0 cargo
--examplestargets matched (a no-op currently — the warning is benign)
There are no TypeScript unit tests in this repository. No Vitest, Jest, or Mocha suite. Lint is the only TypeScript-side gate.
Do not claim TypeScript code is tested after running npm test. It isn't.
Validation of the TS wrapper layer is currently:
npm run lint:check- Manual inspection via the example pages (
dev-example-three) npm run buildsucceeds (catches type errors via@rollup/plugin-typescript)
If you change main/opengeometry-three/src/, run all three. If you ship behavior changes
without an example exercising them, say so explicitly in the handoff.
Before declaring a task done, run the gates that match the touched area:
| Touched | Gates |
|---|---|
main/opengeometry/src/** (Rust) |
cargo fmt --check, cargo check, cargo test, npm run build |
main/opengeometry-three/src/** (TS) |
npm run lint:check, npm run build, manual example check |
| Both | All of the above |
| Docs / instruction files only | npm run build (sanity), no other gates required |
Cargo.toml / package.json deps |
Full npm run build + npm test |
If any gate cannot run, report exactly: which command, why it could not run, and the residual risk.
.github/workflows/release.yml runs on push/PR to main. It checks for version bumps,
builds, tests, and publishes to NPM. The npm test step is now required to pass for
publish (no continue-on-error). Don't bump the version unless you've run the full
suite locally.
- Think before coding. State assumptions before writing. When a request is ambiguous, surface multiple interpretations and ask, rather than silently choosing one.
- Simplicity first. Minimum code that solves the problem. No speculative abstractions, unrequested features, or just-in-case error handling.
- Surgical changes. Touch only what the task requires. Match the existing style. Remove only code your change made obsolete — do not clean up pre-existing dead code unless explicitly asked.
- Goal-driven execution. Define verifiable success criteria up front. Use a brief multi-step plan with checkpoints.
These exist because of past incidents or non-obvious constraints. They override anything in the generic section if there's tension.
Vector3is wasm-backed. Constructing it beforeOpenGeometry.create({ wasmURL })resolves throws at runtime. Never recommend or write code that does this.- Do not edit
main/opengeometry/pkg/. It is generated bywasm-pack. Edit Rust source and rebuild. - Do not bump
earcutrpast=0.3.0. The pin (with=) is intentional — triangulation behavior is sensitive to algorithmic changes. If you need a newer version, raise the question explicitly with the user before changing. printpdfis native-only. The PDF export path inmain/opengeometry/src/export/pdf.rsis gated withcfg(not(target_arch = "wasm32")). PDF export does not work in the browser. Browser PDF requires a downstream package (planned viapdf-lib) — see the in-flight spec inknowledge/technical-drawing-pdf-export.md.- No feature flags in the Rust crate. All functionality compiles unconditionally.
Don't add
cfg(feature = "...")gates without explicit approval — bundle-size optimization is a separate, deliberate effort. - Scene snapshots are not live.
OGSceneManager.addBrepEntityToScene(...)captures the BRep at insertion time. Later changes to wrapper objects do not auto-propagate; callers must explicitly callreplaceBrepEntityInSceneorrefreshBrepEntityInScene. Document this in any new scene-related API. - One canonical local
brepper primitive;Placement3Dis separate. Applying a placement transformation updates vertex positions in the BRep + geometry buffer. Before assuming vertex updates are sufficient, confirm whether halfedges, edges, loops, faces, wires, and shells also need updating to maintain BRep invariants. See.claude/skills/brep-invariants.md. - B-Rep is canonical, triangulation is lazy. Primitives populate the BRep; the
Earcut-based triangulation runs on demand inside
get_geometry_buffer(). Do not pre-triangulate or cache triangulated meshes parallel to the BRep.
- Public TypeScript imports must use
"opengeometry"(the published package name), not@opengeometry/kernel-threeand not relative paths intomain/opengeometry/pkg/. Internal SDK files in this repo may import frompkg/directly — customer code may not. - Preferred high-level workflows (use these in examples and docs):
polygon.extrude(height)→ returnsSolidSolid.extrude(profile, height, options)→ BRep face extrusionOpening.subtractFrom(host, options?)→ host-cutting (binary)shape.subtract([operands], options?)→ shape-level subtraction (array-only)booleanUnion / booleanIntersection / booleanSubtraction→ standalone helperstoFreeform()→ wrapper-to-direct-editing handoffOGSceneManager(orOGEntityRegistryonce stabilized) → projection + export bridge
- BRep accessor precedence when serializing wrapper geometry:
getBrepSerialized()getBrepData()getBrep()Note:Polygon.getBrepData()is the exception — it returns serialized BRep JSON, not a parsed object.
- Change only what the request requires. Do not refactor unrelated modules.
- Keep each commit atomic and explainable in one sentence.
- A bug fix does not need surrounding cleanup. A one-shot operation does not need a helper. Three similar lines are better than a premature abstraction.
- Preserve public API behavior by default. The published surface is what
dist/index.jsand its.d.tsfiles re-export. - Breaking changes require explicit sign-off in the prompt or PR description, plus a migration note.
- Avoid hidden behavioral changes in existing call paths. If a function used to do X and now does Y, that is a breaking change even if the signature is unchanged.
- No hardcoded secrets, keys, tokens. Validate / sanitize external input at boundaries (user input, deserialized JSON, file imports).
- Prefer fail-safe behavior on malformed input. Return structured errors rather than panic in recoverable paths.
- Do not introduce eval-like dynamic code paths.
- No
unwrap()/expect()in code paths reachable from WASM exports unless the panic is genuinely unreachable and documented as such. - Return structured errors with actionable context (
BrepError,Result<T, E>). - Do not silently swallow failures. If a
Resultis ignored, the reason must be obvious.
- No obviously unbounded algorithms in hot paths (projection, triangulation, boolean).
- Reuse existing components; avoid duplicating data transformations between Rust and TS.
- Mind serialization cost — passing large BRep JSON across the WASM boundary is the most common perf pitfall.
- Default to no comments. Add one only when the why is non-obvious (hidden constraint, subtle invariant, workaround for a specific bug).
- Don't explain what the code does — well-named identifiers do that.
- Don't reference current task / PR / issue numbers in code comments — they belong in commit messages.
- No AI-generated handoffs, playbooks, or runbooks in the repo. The repo previously
had an
AI-DOCs/directory; it has been removed because those files went stale faster than they were read. Use git history, commit messages, and PR descriptions instead. knowledge/holds long-lived architecture and domain notes (e.g.,opengeometry-architecture.md,terminology-and-definitions.md,technical-drawing-pdf-export.md). Add to it sparingly, only when the content outlives a single task.docs/is user-facing Mintlify content. Do not put internal agent guidance there.- Do not create planning, decision, or analysis documents during a task unless the
user explicitly asks. Work from conversation context. If a long-form spec is genuinely
needed and the user agrees, place it in
knowledge/.
A targeted list of things agents have tripped on. If you've read nothing else, read this.
Vector3beforeOpenGeometry.create()will throw. It is wasm-backed.printpdf(PDF export) is native-only. Does not compile to WASM. Browser PDF is not supported in the kernel today.- Scene insertion is snapshot-based. Later wrapper edits do not propagate.
- Build order:
npm run build-coremust precedenpm run build-three. Runnpm run buildto do both correctly. pkg/is generated. Editing files there is overwritten by the nextwasm-packrun.earcutr = "=0.3.0"is intentionally pinned. Do not bump.npm testdoes not test TypeScript. Lint + manual example are the only TS gates.cargo test --examplesproduces a benign "no targets matched" warning. There is noexamples/directory in the cargo crate — the binary examples live ascargo run --example <name>targets defined inCargo.toml's[[example]]blocks.OGSceneManageris being augmented/renamed. The newOGEntityRegistrylives alongside it inscenegraph.rs. Verify current names withgit grepbefore recommending an API. Both are wasm-exported during the transition.- Five scaffold packages exist under
main/(-webgl,-babylon,-ios,-export-io,-export-schema). They are not implemented. Do not assume they are real targets when grepping or building. Three.jsis a peer dependency. Apps install it themselves; Rollup excludes it from the bundle. There is no version-mismatch detection — the published package requiresthree >= 0.168.0.- Working directory matters for cargo example commands. Run them from
main/opengeometry/, not the repo root, or use--manifest-path.
Active multi-phase initiative: technical drawing PDF export. Spec at
knowledge/technical-drawing-pdf-export.md.
Status (as of writing):
- Phase 1 (kernel-side
EdgeClass,ClassifiedSegmentinexport/projection.rs,OGEntityRegistryinscenegraph.rs) — partially landed, uncommitted in working tree. - Phases 2–6 (TS layouts package, sheet composers, PDF/SVG/DXF emitters, OpenPlans wiring) — pending, mostly in OpenPlans repo, not here.
Implications for agents working in this repo:
- Scene/projection/scenegraph APIs are mid-shift. Always
git grepfor current names before suggesting a method. Don't lock examples to symbols that are about to be renamed. - New code in
export/projection.rsshould align withEdgeClass/ClassifiedSegmentrather than emitting unclassifiedSegment2Dif the path is on the technical-drawing flow. - Browser PDF export is not part of this repo's deliverable. Don't add
pdf-libhere.
- Prefer incremental commits with focused intent. One commit per logical change.
- Commit messages: imperative subject, optional body explaining why (not what).
- Do not commit generated binaries,
pkg/regenerations, ortarget/artifacts. - Do not commit
dist/unless explicitly building a release artifact. - No noisy formatting churn (whitespace, import reordering) unrelated to the task.
- State what changed, what gates ran, and what residual risk exists, in plain text.
- If a gate could not run, name it and explain.
- Do not write a markdown handoff file. The conversation, the diff, and the commit message are the handoff.
A task is done only when all are true:
- Requested functionality is implemented end-to-end (no half-finished stubs).
- Required gates have passed (per the table in §5), or skipped gates are explicitly named with a reason.
- Documentation and examples updated when behavior or public API changed.
- No unintended files staged (
pkg/,target/,dist/,node_modules/, OS dotfiles). - Result is reviewable in one read-through without hidden assumptions.
- This file: source of truth for agents.
CLAUDE.md: redirects here..github/copilot-instructions.md: redirects here.README.md: user-facing product overview.developer.md: contributor / release process.knowledge/opengeometry-architecture.md: deeper architecture notes.knowledge/terminology-and-definitions.md: domain vocabulary.knowledge/technical-drawing-pdf-export.md: in-flight technical drawing spec..claude/skills/wasm-build-flow.md: trigger-based skill for build-pipeline issues..claude/skills/brep-invariants.md: trigger-based skill for BRep editing..claude/skills/scene-snapshot-rules.md: trigger-based skill for scene/projection/export.docs/: published Mintlify documentation source.main/opengeometry-three/examples-vite/: customer-facing examples.