Skip to content
Shapemetry

Architecture

Shapemetry splits into three package tiers with one-way dependency arrows. Domain logic lives in one layer; hosts only orchestrate.

geometry = COMPUTE   (geometry kernel — Rust/wasm, numbers in/out)
io       = FORMATS   (file readers/writers — Rust/wasm, lazy-loaded)
model    = RUNTIME   (parametric kernel: ModelJSON, evaluate → scene data)
types    = shared type contracts (declarations only — sits below all packages)
Packagenpm nameRole
packages/geometry@huukhanhnguyen/geometryCOMPUTE
packages/io@huukhanhnguyen/ioFORMATS
packages/model@huukhanhnguyen/modelRUNTIME
packages/types@huukhanhnguyen/typesshared cross-package type contracts (declarations only)
model ──► io ──► geometry

Product hosts (e.g. ParaShape) consume geometry + io + model. Model may depend on io + geometry; never on product packages. @huukhanhnguyen/io loads its wasm lazily — a viewer that never imports the package never fetches it.

Geometry public surface

@huukhanhnguyen/geometry is the TypeScript face of crates/geometry:

  1. Namespace objectsPoint, Vector, Mesh, NurbsCurve, Surface, Shell, …
    Grouped over flat snake_case wasm exports by scripts/generate.mjs.
    The generated Geometry pages are the live inventory of which namespace owns which function; GEOMETRY_CATALOG.md (repo root) is a historical port-plan document, kept for reference only.
  2. B-Rep documents — JSON strings. Operations return a fresh document string; there is no free registry. Solid-ness is a property of the root, never a type or namespace name.

There is no deep OOP class tree (new Point(...), Face/Edge entity graph as the default API). That was the deleted legacy TS kernel. Callers use namespaces only.

Examples of the live style

import { Brep, Point, Mesh } from '@huukhanhnguyen/geometry'

const box = Brep.box(2, 3, 4)
Brep.volume(box, Brep.measurementDefaultEps())
Point.distanceTo(0, 0, 0, 1, 0, 0)
// Mesh.* takes/returns arena handles — free when done
const m = Mesh.box(1, 1, 1)
// Catmull-Clark in place (each n-gon → n quads per level)
Mesh.subdivide(m, JSON.stringify({ levels: 2 }))
Mesh.free(m)

Io and model

Live inventories (generated from package .d.ts / registries — not hand-maintained member lists). Top nav is package-scoped; each package has its own API sidebar:

  • Geometry — COMPUTE namespaces (Shell, Point, Mesh, …) over Rust/wasm.
  • Io — file readers/writers (STEP, IGES, USD, glTF, STL, …) under @huukhanhnguyen/io. Wire format between layers is JSON (Shell, triangle meshes), not kernel class instances. Evaluated document vocabulary (Entity, materials, views, sheets) lives on Model.
  • Model — parametric runtime: parse ModelJSON, build graph, evaluate() → scene data. Headless, deterministic, no UI deps. Registry tables: package REFERENCE.md / ENTITIES.md (AUTOGEN via pnpm gen:docs).
  • Types — shared cross-package type contracts (Point, Curve, Surface, …). Declarations only; imports nothing, so every package may depend on it.

Import contract

  • Primary compute entry: @huukhanhnguyen/geometry
  • File formats: @huukhanhnguyen/io; evaluated document vocabulary: @huukhanhnguyen/model
  • Parametric host logic: @huukhanhnguyen/model (+ optional ./nodes, ./compute)
  • Shared cross-package types: @huukhanhnguyen/types (import type only — no runtime)
  • No deep imports under dist/ or package internals
  • Coordinates: Z-up
  • Renderer-agnostic — tessellate/export, then render with Three.js or anything else
Last updated: 📖 2 min readEdit on GitHub