Types - Document
API reference for ./model/modelDocument on @huukhanhnguyen/types.
See the Guide for the package's role.
API reference
Signatures are generated from the live package .d.ts - not hand-written.
AreaUnit
type
type AreaUnit
// = "mm2" | "cm2" | "m2" | "in2" | "ft2"Argument
type
type Argument
// = {
key: string;
/** ALWAYS an expression string — an object-shaped VALUE (e.g.
* placeComponent's `args` override) is authored as expression-string
* SYNTAX like `"{ width: 500 }"`, never a raw JS object. Empty is allowed
* (file parameter with no file selected); validity is enforced by
* validateExpressions(). */
input: string;
label?: string;
/** Constraint fields — parameter args (numeric methods) only; ignored elsewhere. */
min?: string;
max?: string;
step?: string;
options?: Option[];
}Chain
type
The ONE record spine of the chain lanes: key + optional label/parameters + required operations. The skeleton / animation / texture lanes are all exactly this shape, so they all carry this type — there are no per-lane aliases (a second name for the same shape is debt). TableChain (columns instead of parameters) and model's FlatComponentSource (no label) diverge deliberately and stay explicit. The per-lane zod schemas in model differ (Skeleton/Animation default operations to [], Texture requires min(1)) — zod stays the validation SSOT.
type Chain
// = {
key: string;
label?: string;
parameters?: (Operation | Container)[];
operations: (Operation | Container)[];
}Component
type
A reusable DEFINITION — 2D or 3D. key is unique within the components[] lane (per-lane uniqueness via uniqueKeyArray — not a model-wide key space shared with parameters/operations). Placed via placeComponent in an ordinary 3D chain (root scene / another component's body): a real instanced GROUP placement + the opening cut. NOT on paper — a sheet places a VIEW (a views[] entry with a viewDetail head), never a definition (2026-07-26).
Flatness contract: DECLARED (flat: true on this record — the ONLY flat declaration there is, since a step cannot declare itself flat) WINS over INFERRED isFlat (bbox z-extent at current params). A declared-flat component enforces the flat contract over its whole body (FLAT_LANE_METHODS, point-arg z=0, force-flatten created entities) so a slider cannot silently un-flatten it.
type Component
// = {
key: string;
/** Editor-chrome display name (not printed on the artifact — see
* model's title-vs-label law). */
label?: string;
/** Chain-level flat declaration — with `ModelJSON.flat` and the "flat"
* LANE, one of the only three ways 2D context is established. When true,
* the whole `operations` body runs under the flat contract (vocabulary
* gate + z=0 point args + force-flatten). Empty `operations` is valid.
* `opening` under flat is meaningless (validate warning). */
flat?: boolean;
/** This component's own input arguments — a function signature. Evaluated
* in an ISOLATED scope (never sees the host model's `parameters[]`); a
* placement's `args` overrides these by key, so the SAME component
* placed twice can render differently. Value methods only
* (length/number/…). */
parameters?: (Operation | Container)[];
/** The component's own body — an entity-streaming chain. Lane "component"
* when undeclared; when `flat: true`, the whole body is flat-enforced.
* Undeclared content falls back to INFERRED isFlat (bbox z-extent). */
operations: (Entities | Container)[];
/**
* Baked static geometry (import/AI) — plain Entity[], never an
* expression. Spliced by placeComponent alongside evaluated `operations`.
* A component may have baked content, authored content, or both; only
* `entities` (empty operations) is a valid imported asset.
*/
entities?: unknown[];
/** Optional cut volumes — evaluated in the SAME isolated scope + `args`,
* with the SAME placement transform (`at`/`rotate`/`scale`) as the body.
* THE OPENING LAW (cut-at-placement): in an ordinary 3D chain, each
* `placeComponent` step SUBTRACTS these (closed solid) volumes from the
* stream entities PRESENT BEFORE that step in that SAME chain (a wall
* placed earlier gets a doorway cut through it; a wall placed AFTER is
* unaffected — "what is behind does not know"). The opening volumes themselves
* never render and never flow downstream — they exist only to cut.
* Ignored entirely in a FLAT placement context (no volume to cut there).
* Meaningless when `flat: true` (validate warning). */
opening?: (Entities | Container)[];
/** Rig chains scoped to this component (joint trees) — materialize per
* placement. One skeleton chain: nested `joint` nodes fold to
* flat Skeleton (DFS pre-order); key = sole identity
* (skin.skeleton / track.skeleton). See ModelJSON.skeletons. */
skeletons?: Chain[];
/** Animation chains scoped to this component — track nodes fold to
* AnimationClip + Track exactly like ModelJSON.animations, and
* materialize per placement like `skeletons` just above.
*
* This lane USED to be a second animation vocabulary: a baked
* interchange record whose track declared its own `target`+`property`,
* whose clip stored `duration`/`loop`, whose `values` were flat, and
* which admitted CUBICSPLINE — every one of those removed from the
* graph model on purpose (a Track carries no target: the CONSUMER holds
* the reference; a clip's length is `groupDuration` over its members and
* looping belongs to a USE). `skeletons` was unified with the model lane
* already; animations now match, so one clip shape exists, not two.
* An importer authors track NODES here (the same way it authors joint
* nodes for `skeletons`), not a baked record. */
animations?: Chain[];
}Container
type
A keyed grouping of operations. ONE shape, in two positions.
- At the root (
ModelJSON.objects[]) it is a scene OBJECT: the root is a SET of these, not one chain, and every entry folds its ownoperationsfrom an EMPTY stream — so no object can ever see another object's entities. That independence is what makes the object the unit of change (root CLAUDE.md, "The object is the unit of change"). - Nested inside a chain it is display grouping, and stays anonymous.
key is optional HERE because a nested container needs none. At the root it is required — the root addresses its objects by key for selection, render binding and the per-object channels — and that is enforced by VALIDATION, not by a second type. There used to be a SceneObject = Container & { key:
string }; it was deleted 2026-08-15 because it named no second concept, and both shapes always became the same ContainerNode anyway.
type Container
// = {
key?: string;
label?: string;
enabled?: string;
operations: (Entities | Container)[];
}Entities
type
ONE step in an entity-streaming chain: it folds an entity stream to an entity stream, and that is the whole of it.
Which is why it carries neither authoring markers nor a child list. There is no flat (2D context comes from the LANE — ModelLane === "flat" — or from the chain-level owner declaration Component.flat / ModelJSON.flat; a step cannot declare itself into another dimension), no locked (immunity is a property of the ENTITY — EntityBase.locked, stamped by applyLocked — because it is entities the later steps skip, not nodes), and no children (a chain is a flat pipeline; grouping is a Container, whose own operations is the nested list).
type Entities
// = {
key?: string;
label?: string;
/** Optional enable expression — falsy = this step is bypassed / emits nothing. */
enabled?: string;
/** Method VALIDITY is registry-dependent (which node registries are
* loaded) — the schema only checks the string shape; model's
* validateModelJSON(json, registry) rejects unknown methods. */
method: string;
args?: Argument[];
}LaneNode
type
One cameras[] entry (a lane node — not the viewport's live camera state). args is the full Argument the lane zod schema accepts (it reuses NodeSchema); nothing narrows it.
type LaneNode
// = {
key: string;
label?: string;
enabled?: string;
method: M;
args?: Argument[];
}LengthUnit
type
type LengthUnit
// = "mm" | "cm" | "m" | "in" | "ft"MassUnit
type
type MassUnit
// = "mg" | "g" | "kg" | "lb" | "oz"ModelJSON
type
type ModelJSON
// = {
id?: string;
/** Schema shape this document was written against — model's
* SCHEMA_VERSION at save time. Optional and inert on load (absent =
* pre-stamp vintage, sniffed by shape). */
version?: number;
/** Content title printed on the artifact (title-vs-label law). */
title: string;
/** Chain-level flat declaration — with `Component.flat` and the "flat"
* LANE, one of the only three ways 2D context is established (a single
* step cannot declare itself flat). The whole `operations` body runs
* under the flat contract: FLAT_LANE_METHODS vocabulary gate, point-arg
* z=0, created entities force-flattened. Same field as `Component.flat`
* — model's `installModel` copies it onto the installed component.
* Empty body + flat is trivially valid. */
flat?: boolean;
/** Model-level settings — see ModelSettings's doc. Display units live
* at `settings.units` (not a top-level field). Same family as `title`:
* data the body reads, not a graph node. */
settings?: ModelSettings;
/** Material nodes (method `"material"`, expression args for every
* Material field). **`name` = the record's identity**, referenced by
* name from applyMaterial; **`key` = optional node graph id** (editor
* use only, never a lookup key). Resolved: evaluateMaterials() →
* Material. */
materials?: (Operation | Container)[];
/** Layer nodes (method `"layer"`, expression args name/color/lineWidth/
* lineStyle). **`name` = by-name identity** (applyLayer); **`key` =
* optional graph id only**. Resolved: evaluateLayers() → Layer. */
layers?: (Operation | Container)[];
/** Style nodes (method `"style"`, expression args name/background/
* shading/exposure). **`name` = by-name identity**
* (viewStyle); **`key` = optional graph id only**. Resolved:
* evaluateStyles() → Style. */
styles?: (Operation | Container)[];
/** Text style nodes (method `"textStyle"`, expression args name/font/
* height/widthFactor/oblique). **`name` = by-name identity** (a `text`
* node's `style` arg, and every annotation style's `textStyle`).
* Resolved: collectTextStyles() → TextStyle. */
textStyles?: (Operation | Container)[];
/** Annotation style nodes (method `"annotationStyle"`, expression args
* for arrow/extension geometry and measurement formatting). **`name` =
* by-name identity** (a dimension or leader node's `style` arg).
* Resolved: collectAnnotationStyles() → AnnotationStyle. A SEPARATE lane
* from `textStyles` on purpose — DXF's STYLE vs DIMSTYLE split, so one
* text style serves dimensions, notes and leaders alike. */
annotationStyles?: (Operation | Container)[];
/** Named camera poses (camera method nodes). A view3d head may reference by key.
* NOT product: never in the entity stream. A LUMINAIRE is not a lane node
* either — it is an ordinary object in `objects[]` carrying
* `EntityBase.emission` (the `lights[]` lane was deleted 2026-08-16, and
* the `helpers[]` datum lane the same day — see RETIRED_ROOT_MESSAGES). */
cameras?: LaneNode<CameraM>[];
/** Skeleton chains — nested joint nodes fold to flat Skeleton
* (DFS pre-order). Key = sole identity (skin.skeleton references it). */
skeletons?: Chain[];
/** Animation chains — track nodes fold to AnimationClip
* (expression args). Baked import clips stay on Component.animations. */
animations?: Chain[];
parameters?: (Operation | Container)[];
/** Root body — the SET of scene objects (ModelLane role "scene"). Each
* entry folds its OWN `operations` from an empty stream; the root is not
* itself a chain (SCHEMA 46, see Container / migrateRootObjects). */
objects?: Container[];
/** Texture lane: math->image chains in the operations grammar, but a
* fully separate stream (image buffers, never entities) with its own
* method vocabulary. A material whose `texture` names a texture key
* receives the rasterized pixels at evaluate time. Sampling (wrap/
* filter/uvSet) rides the chain's builtin params. */
textures?: Chain[];
/** Reporting lane: named table chains (Revit-Schedule-like pipelines,
* see TableChain's doc). Referenced from any expression via
* `Table.get('key')` — never part of the entity graph. */
tables?: TableChain[];
/** Named views: CHAIN lane — key + operations of typed heads
* (viewPlan/viewSection/viewElevation/view3d) + viewFilter/viewStyle
* steps. Family is the head method (VIEW_FAMILY_OF map). Consumed by
* resolveSheet/drawingSetToSvg / evaluateView. */
views?: View[];
/** Named reusable definitions — 2D or 3D, whichever they evaluate to; see
* Component's doc for the full contract (isolated scope, emergent
* flatness, placement via `placeComponent`, the `opening` cut-at-
* placement law). Placed into root `operations` / another component's
* own `operations` (ordinary 3D chain — instanced group placement).
* Reaches PAPER only through a views[] entry (a viewDetail head frames
* it), never a sheet-level `placeComponent`. */
components?: Component[];
/** Sheet set: an ordered construction-drawing set (A-101 GROUND FLOOR
* PLAN, A-201 SECTION A-A …). Each sheet's `operations` places its own
* views/tables/images by key (see Sheet's own doc) plus
* any sheet-local ad hoc flat content; model's
* drawingSetToSvg renders one SVG per sheet plus a drawing index. */
sheets?: Sheet[];
}ModelLane
type
Which section of the model a node was declared in (drives lane validation and serialization). parameters = value bindings; scene = the root fold ROLE (storage field is operations); component = a Component's own operations/opening (full 3D grammar — an ISOLATED scope, not a method restriction); flat = a sheet's ad hoc content — entity methods only, restricted to FLAT_LANE_METHODS (no solid/3D).
type ModelLane
// = "parameters" | "scene" | "component" | "flat"ModelSettings
type
Model-level settings: ONE document-config block (USD/FBX/DXF industry pattern). Two natures of field:
- content semantics:
units(display/config units; internal length is ALWAYS mm — never a runtime scale conversion) - presentation state:
activeView,cameraTourPermanent CONVENTIONS (never config, never fields here): Z-up world frame and internal mm — glTF-style fixed convention, zero upAxis/scale knob.
Display chord deflection is NOT a document setting — callers pass it as the optional argument to render() / tessellateFaces (engine default when omitted). Nesting and DXF export own separate legitimate knobs.
units: optional display unit config; absent ={length:"mm"}. Read only via Model.unit (the ONE accessor — never N call-site fallbacks).activeView: whichviews[]entry the 3D viewport restores on load.cameraTour: ordered camera legs for viewer present-mode playback (host-owned; engine never plays it).environment: the light that has no place in space — the site and the moment the sun is DERIVED from, plus the sky fill and any HDRI. A document FACT like units (Revit project location / SketchUp geo-locate), and ONE per document on purpose: a building has one sky, so this cannot be a per-view field. SeeEnvironment. (It replaced the baresitefield and the retired per-stylesunon 2026-08-16.)
Retired (v41 migrateMeshNgon deletes when present): curveTolerance.
type ModelSettings
// = {
units?: UnitConfig;
activeView?: string;
cameraTour?: {
legs: {
view: string;
duration: number;
}[];
};
environment?: Environment;
}Operation
type
type Operation
// = {
key?: string;
label?: string;
/** Optional enable expression — falsy = this node is bypassed / emits nothing. */
enabled?: string;
/** Method VALIDITY is registry-dependent (which node registries are
* loaded) — the schema only checks the string shape; model's
* validateModelJSON(json, registry) rejects unknown methods. */
method: string;
args?: Argument[];
/** Child nodes for CONTAINER methods. Recursive — the same Operation | Container
* shape as every other chain. The entity-fold engine never reads this
* (a scene[] operation with children silently drops them). */
children?: (Operation | Container)[];
/** Numeric parameters only — this parameter's USE of a track: which
* `formulaTrack`/`numberTrack` in animations[] drives it (a numeric
* parameter is one number over time, so only the two 1-number track
* methods may), and on
* what terms. Keys are plain, not expressions: the same "just the id, no
* evaluation" convention `BlockEntity.definition` / skin's `skeleton`
* already use. The terms live here rather than on the track because two
* parameters may share one curve and disagree about them — see
* {@link AnimationBinding}. */
animation?: AnimationBinding;
}Option
type
type Option
// = {
label: string;
/** An expression string (model's exprSchema). */
input: string;
}RawModel
type
Loose (pre-validation) shape of a model document — Partialunit is the only extra: pre-migration top-level unit still recognized by offline tooling (settings.units is the live field).
type RawModel
// = Partial<ModelJSON<CameraM>> & { title: string; /** Pre-migration top-level unit — offline tooling only; live docs use settings.units. */ unit?: UnitConfig; }Sheet
type
A drawing sheet — an OBJECT whose space is BOUNDED. Its content is an entity chain like any other; what makes it a sheet is that the chain composes into a finite rectangle of paper instead of unbounded world space. Resolving one produces SheetValue.
Record spine is Chain with title instead of label — a deliberate divergence, kept explicit (see Chain, and TableChain which diverges the same way with columns). The reason is the title-vs-label law: a sheet's name is PRINTED, in the ISO 7200 title block's "Title" cell, so it is content, not editor chrome, and it is an expression. A second editor-chrome label beside it would be a second name for the same thing, so there is none.
type Sheet
// = {
/** Sheet identifier / drawing number, e.g. "A-101". Free-form (NOT
* generatorKeySchema) — drawing numbers contain hyphens and must stay
* author-facing strings. Unique within the set; also the index row key. */
key: string;
/** Sheet title block name, e.g. "GROUND FLOOR PLAN" — CONTENT printed on
* the artifact (title-vs-label law). An EXPRESSION (titleValue law):
* `"'PLAN LEVEL ' + level"` renders evaluated, a plain name stays
* literal. */
title?: string;
/** Builtin paper/scale params (paperWidth/paperHeight/scale) + free params. */
parameters?: (Operation | Container)[];
/** This sheet's content — lane "flat" (see model's FLAT_LANE_METHODS). A
* top-level `viewport` node with no `at` arg auto-tiles into a scaled
* row (resolveSheet extracts these before running the rest); every
* other node (a positioned `viewport`, `tableEntity`, `imageEntity`,
* or ordinary curve/point/annotation content) is
* authored in the SAME y-up bottom-left paper-space convention every
* other flat context uses, flipped once into sheet space at resolve
* time.
*
* Required, like every other chain's body. A sheet with no operations is
* a blank page — frame, zone grid and title block and nothing else —
* which is a legal (if empty) drawing, and it is spelled `[]`;
* `undefined` was a second spelling of the same nothing (the zod schema
* defaults it, so a document that omits the field still loads). */
operations: (Operation | Container)[];
}TableChain
type
A reporting stream — header columns (signature) + body operations (row producers + transforms), fold law same as the root body. Referenced via Table.get('key'). Chain-level provenance is banned (the imported CSV stays discoverable as its url-method parameter). Record spine is Chain with columns instead of parameters — a deliberate divergence, kept explicit (see Chain).
type TableChain
// = {
key: string;
label?: string;
/** HEADER — one `column` node per column (key on the node). */
columns?: (Operation | Container)[];
/** BODY — `row` producers + verb transforms (tableFilter/Group/Sort/…). */
operations: (Operation | Container)[];
}UnitConfig
type
Runtime unit config — SSOT shape for settings.units.
type UnitConfig
// = {
length: LengthUnit;
area?: AreaUnit;
volume?: VolumeUnit;
mass?: MassUnit;
}View
type
A view is a CHAIN: optional typed HEAD + stackable filter/style steps. Family is the head method (VIEW_FAMILY_OF map in model's views.ts) — never inferred from camera fields. Config-fold evaluation turns operations into a projection definition consumed by resolveView's HLR math.
Heads (must be first when present; at most one): viewPlan / viewSection / viewElevation / view3d Steps (any count, any order among themselves after the head): viewFilter (rules json) / viewStyle (name) Headless (filter/style only, or empty ops) = pure filter/style preset, family "3d", never placeable on paper.
type View
// = {
key: string;
label?: string;
operations: (Operation | Container)[];
}VolumeUnit
type
type VolumeUnit
// = "mm3" | "cm3" | "m3" | "in3" | "ft3" | "L"