Skip to content
Shapemetry

@huukhanhnguyen/model / Model

Model

Model

A compiled parametric model: value nodes (the parameters header) plus one flat body fold (the objects list / scene role) that evaluates to a scene tree. operations is the chain INSIDE one object, never the root body.

Root = Group

The root IS a Group: its top-level body nodes fold exactly like any nested operations list (producers/containers concat into the root stream, root-level transforms process it, in listed order — rootFoldSlices). It is a DEFINITION — it has no instances lane, because a model carries no transform of its own. Placement is the consumer's job: clone/ placeComponent supply the instances when this definition (once installed as a components[] entry, see share.ts's installModel) is placed inside another model.

Assets (materials / layers / views / attached files)

materials[] and layers[] are both NODE lanes (material/layer methods, expression args → resolved Material/Layer via evaluateMaterials()/evaluateLayers()). Consumed BY NAME — faceMaterial/applyLayer stamp names. Runtime they are SnapshotLaneNode-backed: evaluateMaterials/evaluateLayers dirty-gate the lane, pattern texture pulls register edges, and every graph consumer (views/sheets/placePart) goes through those resolves so a material/layer edit cascades via markDirty — same pull law as the cameras lane. views[] is already one ChainNode per key (evaluateView). Attached files (fonts/images) are url-method parameters (ordinary graph nodes).

Constructors

Constructor

new Model(name?): Model

Parameters

name?

string = "Model"

Returns

Model

_animationNodes

_animationNodes: Map<string, ChainNode<Chain, { clip: AnimationClip; tracks: Track[]; }>>

_annotationStylesNode

_annotationStylesNode: SnapshotLaneNode<(Operation | Group)[], AnnotationStyle[]> | undefined

annotationStyles[] lane — SnapshotLaneNode → AnnotationStyle.

_argOverrides

_argOverrides: Set<ExpressionNode>

Live arg overrides for gesture drag (setArgOverride). Tracked so clearArgOverrides can drop them all; evaluation reads the override via ExpressionNode.setOverride — never stored in toJSON().

_camerasNode

_camerasNode: SnapshotLaneNode<CameraNode[], CameraObject[]> | undefined

cameras[] lane — SnapshotLaneNode over authored camera nodes.

_collections

_collections: Map<string, CollectionNode>

One CollectionNode per lane — "which entries exist, in what order", as a real dependency. Separate from the ChainNode maps above on purpose: those answer "what does entry K resolve to" (its body), this answers "what entries are there" (membership + headers). Keeping the two apart is what lets a sheet place a table that lists every sheet without closing a cycle. Lazily created per lane.

_componentAnimationNodes

_componentAnimationNodes: Map<string, ChainNode<Component, { clips: AnimationClip[]; tracks: Track[]; }>>

Component-scoped animation lanes — keyed by COMPONENT, not by chain: see componentAnimationsNodeOf.

_componentNodes

_componentNodes: Map<string, ChainNode<Component, ComponentBody>>

_isAnalysisCopy

_isAnalysisCopy: boolean = false

_layersNode

_layersNode: SnapshotLaneNode<(Operation | Group)[], Layer[]> | undefined

layers[] lane — SnapshotLaneNode over authored nodes → resolved Layer.

_loggedTextureErrors

_loggedTextureErrors: Set<string>

Texture-chain keys whose failure was already logged (log-once — a broken chain resolves on every evaluate pass and would otherwise spam console.error per request). A key leaves the set on its next successful resolve, so a chain that breaks AGAIN after working logs again.

_materialRecords

_materialRecords: MaterialData[] = []

Live resolved-with-texture-data materials cache — rebuilt by _resolveProcessorTextures from the CURRENT materials[] nodes on every "change"/asset-load pass (not only inside the SnapshotLaneNode pull), so a bare collectMaterials(nodes) render sync (Renderer.ts, export menus) sees fresh pixels without forcing a full re-resolve.

_materialsNode

_materialsNode: SnapshotLaneNode<(Operation | Group)[], MaterialData[]> | undefined

materials[] lane — SnapshotLaneNode over authored nodes → resolved MaterialData (texture chains + hasChanged).

_objectAnnouncedEpochs

_objectAnnouncedEpochs: Map<string, number>

The node _epoch each key was last announced at — the flush gate. Epoch (not isDirty) survives an evaluateObject between edit and notification, so an eager reader cannot swallow an announcement.

_objectListeners

_objectListeners: Map<string, Set<ObjectChannelListener>>

Per-object change listeners, and the signature each key was last emitted at (the documentKey cutoff — see _flushObjectChannels).

_objectNodes

_objectNodes: Map<string, ObjectNode>

One ObjectNode per objects[] entry — the per-OBJECT grain (SCHEMA 46) on top of the per-node grain. See evaluateObject/subscribeObject.

_objectSignatures

_objectSignatures: Map<string, ObjectSignature>

_patternsNode

_patternsNode: SnapshotLaneNode<(Operation | Group)[], Pattern[]> | undefined

patterns[] lane — SnapshotLaneNode over authored nodes → Pattern.

_sceneNode

_sceneNode: SceneNode<ScenePull>

_sheetNodes

_sheetNodes: Map<string, ChainNode<Sheet, Resolved<Page>>>

_sheetWarnings

_sheetWarnings: Map<string, StepFailure[]>

Per-sheet step failures from the LAST resolve of that sheet's node. Rewritten on every resolve and read by evaluateSheet, so it stays in step with the node's own cache — a cached (unchanged) sheet keeps the warnings it was composed with rather than reporting clean.

_stylesNode

_stylesNode: SnapshotLaneNode<(Operation | Group)[], Style[]> | undefined

styles[] lane — SnapshotLaneNode over authored nodes → resolved Style.

_tableNodes

_tableNodes: Map<string, ChainNode<TableChain, Table>>

_textStylesNode

_textStylesNode: SnapshotLaneNode<(Operation | Group)[], TextStyle[]> | undefined

textStyles[] lane — SnapshotLaneNode over authored nodes → TextStyle.

_textureDataCache

_textureDataCache: Map<string, { buffer: ImageBuffer; data: TextureData; }>

Texture-chain keys whose ImageBuffer→TextureData wrap is memoized.

_textureNodes

_textureNodes: Map<string, ChainNode<Chain, ImageBuffer>>

_viewNodes

_viewNodes: Map<string, ChainNode<View, Resolved<{ data: ViewSpaceData; extent: BoundingBox2d; frame: Transformation; }>>>

editHistory

readonly editHistory: EditHistory

Inverse-based undo/redo stack for lane-door edits (see editHistory.ts). Never serialized — history is a live session concern. Mutations always go through applyLaneEdit.

id

id: string = ""

Unique model ID (empty string for unsaved models).

name

name: string

Display name of the model.

nodes

nodes: NodeCollection<any>

Flat ordered collection of every node in the model (header value nodes first, then the body fold list — see modelSections).

scope

readonly scope: ModelScope

Expression evaluation scope shared across all nodes in this model.

views

views: View[] = []

Named drawing views (projection/section/layers) — carried verbatim.

Accessors

animations

Get Signature

get animations(): Chain[]

Animation chains (track nodes) — expression clips; evaluateAnimations.

Returns

Chain[]

Set Signature

set animations(value): void

Parameters
value

Chain[]

Returns

void

cameras

Get Signature

get cameras(): CameraNode[]

Named camera poses (camera method nodes) — NOT product.

Returns

CameraNode[]

Set Signature

set cameras(value): void

Parameters
value

CameraNode[]

Returns

void

components

Get Signature

get components(): Component[]

Named reusable 2D/3D definitions — carried verbatim, resolved on demand via evaluateComponent() (never part of the live pull graph — a component is an isolated function, see components.ts's module doc). Lives on the scope (like textures) so UI panels reach it through the owner chain — the material install-copy pushes rasterize-referenced components there.

Returns

Component[]

Set Signature

set components(value): void

Parameters
value

Component[]

Returns

void

settings

Get Signature

get settings(): ModelSettings | undefined

Model-level settings (units/activeView/cameraTour — see ModelSettings). Lives on the scope so free functions reach it via node.owner?.settings.

Returns

ModelSettings | undefined

Set Signature

set settings(value): void

Parameters
value

ModelSettings | undefined

Returns

void

sheets

Get Signature

get sheets(): Sheet[]

Construction sheet set — carried verbatim, never re-derived. Lives on the scope (like patterns/tables/components) so UI panels reach it through the owner chain; this accessor keeps the Model-level API.

Returns

Sheet[]

Set Signature

set sheets(value): void

Parameters
value

Sheet[]

Returns

void

tables

Get Signature

get tables(): TableChain[]

Table lane: reporting streams resolved on demand via Table.get() in any expression. Lives on the scope (like textures) so UI panels reach it through the owner chain; this accessor keeps the Model-level API.

Returns

TableChain[]

Set Signature

set tables(value): void

Parameters
value

TableChain[]

Returns

void

templates

Get Signature

get templates(): Template[]

Returns

Template[]

Set Signature

set templates(value): void

Parameters
value

Template[]

Returns

void

textures

Get Signature

get textures(): Chain[]

Texture lane: math->image chains resolved into material textureData. Lives on the scope (like materials/layers/fonts) so UI panels reach it through the owner chain; this accessor keeps the Model-level API.

Returns

Chain[]

Set Signature

set textures(value): void

Parameters
value

Chain[]

Returns

void

trackPool

Get Signature

get trackPool(): Map<string, Track>

All tracks resolved anywhere in the model, keyed by Track.key — what BoneEntity.*Track / ParameterNode.animation resolve against (AnimationPlayer's PlayerModel.tracks). Recomputed on read, same demand-driven convention as evaluateAnimations() itself.

Returns

Map<string, Track>

unit

Get Signature

get unit(): UnitConfig

Display/config units — ONE model-side accessor (settings-functions §A). Materialises settings.units (default {length:"mm"}) so callers that mutate the returned object (convertModelUnit) and BaseNode.symbol lookups all share the same record. Never read settings.units at sites.

Returns

UnitConfig

Set Signature

set unit(value): void

Parameters
value

UnitConfig

Returns

void

Methods

applyDocument()

applyDocument(json): ApplyReport

Apply a whole document onto THIS live model instead of rebuilding it.

fromJSON builds a fresh Model, which marks everything dirty and re-evaluates all geometry. When most of the incoming document is identical to what is loaded — an AI rewrite, a re-import, a large undo — that is nearly all wasted work. This compares per ENTRY, replaces only what differs, and leaves untouched entries holding their cached values.

Entry-level, never deeper: see the root CLAUDE.md, "The object is the unit of change". Derive json from toJSON() for the best hit rate. Returns what it replaced, plus the inverses so a host can undo the whole apply as one step.

Parameters

json

ModelJSON

Returns

ApplyReport

bakeValue()

bakeValue(value): ObjectData[]

Bake a node's value (ONE ObjectData, possibly a nested blockData placement) into flat leaf data. Children live on each placement. A host or test that reads node.value directly uses this to get concrete geometry. Delegates to the scope so a node-tree consumer reaching the same scope via its own .model getter (e.g. a hover preview) shares the identical bake logic.

Parameters

value

ObjectData | undefined

Returns

ObjectData[]

clearArgOverrides()

clearArgOverrides(): void

Drop every live arg override; evaluation returns to authored inputs.

Returns

void

collection()

collection(lane): CollectionNode

The CollectionNode for one lane (membership + per-entry headers as real dependency edges). Lazily created, one per lane.

Parameters

lane

string

Returns

CollectionNode

convertUnit()

convertUnit(unitType, newUnit): void

Convert all unit-carrying literals in the model from currentUnit to newUnit.

Applies to:

  • Parameter inputs with declared unit types (length/area/volume/mass)
  • Operation arg inputs with any of the above types, including composite arg types that contain length components (point/axis/plane)

Uses dimensional inference to only convert literals that carry physical units (not dimensionless counts or ratios). Cross-param references resolve their own inputs independently, so expressions like "width - 18" correctly convert only the literal 18.

Parameters

unitType

string

newUnit

string

Returns

void

evaluate()

evaluate(params?): EvaluateResult

Evaluate the model with optional parameter overrides; returns the full render result. Every asset (fonts/images) is pre-loaded in the Store, so the graph itself never does async I/O — this call always returns synchronously. A node's OWN registry-supplied compute() may still be maybe-async (see registry/contract.ts): ParaShape's bundled node registry stays 100% synchronous by policy, so this stays true in practice, but the return value can reflect a still-pending node if some OTHER registry's compute hasn't settled yet (it settles later via that node's own notify cascade).

params overrides are TRANSIENT: each overridden parameter's input is snapshotted before the pass and restored before this call returns (even on throw), so a later evaluate() without params recomputes from the document's own inputs and toJSON() never persists an override. The lazy result.geometry bake re-applies the same overrides when first read, so it reflects the overridden pass (not the restored inputs).

Parameters

params?

Record<string, unknown> = {}

Returns

EvaluateResult

evaluateAnimations()

evaluateAnimations(): Resolved<{ clips: AnimationClip[]; tracks: Track[]; }>

All model-level animations[] resolved to AnimationClip + the flat track pool every clip/joint/parameter reference resolves against (2026-08-14: a Track carries no target of its own — see evaluateAnimationChain's own header).

Returns

Resolved<{ clips: AnimationClip[]; tracks: Track[]; }>

evaluateAnnotationStyles()

evaluateAnnotationStyles(): Resolved<AnnotationStyle[]>

annotationStyles[] pull — dirty-gated SnapshotLaneNode.

Returns

Resolved<AnnotationStyle[]>

evaluateCameras()

evaluateCameras(): Resolved<CameraObject[]>

Top-level cameras[] pull (no placement materialization).

Returns

Resolved<CameraObject[]>

evaluateComponent()

evaluateComponent(key, argsOverride?): Resolved<ObjectData | undefined>

Flat-context public entry — envelope over the filtered data stream.

Parameters

key

string

argsOverride?

Record<string, string>

Returns

Resolved<ObjectData | undefined>

evaluateComponentAnimations()

evaluateComponentAnimations(componentKey, args?): { clips: AnimationClip[]; tracks: Track[]; }

Animations on one components[] entry (isolated param scope).

Parameters

componentKey

string

args?

Record<string, string>

Returns

{ clips: AnimationClip[]; tracks: Track[]; }

clips

clips: AnimationClip[]

tracks

tracks: Track[]

evaluateLayers()

evaluateLayers(): Resolved<Layer[]>

layers[] pull — dirty-gated SnapshotLaneNode. Resolves each layer node's expression args into Layer records (by evaluated name).

Returns

Resolved<Layer[]>

evaluateMaterials()

evaluateMaterials(): Resolved<MaterialData[]>

materials[] pull — dirty-gated SnapshotLaneNode. Texture edges + consumers (views/sheets/placePart) cascade from here.

Returns

Resolved<MaterialData[]>

evaluateObject()

evaluateObject(key): SceneObject

Resolve ONE objects[] entry into its local content + lifted instance placement (nodes/ObjectNode.ts). Per-key and dirty-gated: pulling one object never evaluates a sibling, which is what lets a host mount objects one at a time and move one by writing a matrix.

Parameters

key

string

Returns

SceneObject

evaluatePart()

evaluatePart(key, argsOverride?): Resolved<ObjectData | undefined>

Resolve + evaluate one components[] entry's FLAT PATTERN by key (see _partPatternOf) — the 2D preview/nesting entry point. For the FOLDED 3D result, see the placePart producer (_resolvePlacedPart below), which calls this internally.

Parameters

key

string

argsOverride?

Record<string, string>

Returns

Resolved<ObjectData | undefined>

evaluatePatterns()

evaluatePatterns(): Resolved<Pattern[]>

patterns[] pull — dirty-gated SnapshotLaneNode. Resolves each pattern node's expression args into Pattern records (by evaluated name).

Returns

Resolved<Pattern[]>

evaluateSheet()

evaluateSheet(sheetNumber): SheetEvaluation

Compose one sheets[] entry into flat sheet-space data (paper mm) — every content source wired: the views lane (an auto-tiled top-level viewport node), the sheet's operations (positioned viewport/ tableEntity/imageData/placeComponent placements plus ordinary ad hoc content), border + title block. Pulls a persistent, dirty-gated SheetNode (nodes/LaneNode.ts) — every lane it reads (view/component/ table) registers an EXPLICIT dependency edge back onto it (see _sheetNodeOf), so editing any of them invalidates precisely this sheet, not every sheet in the model.

Parameters

sheetNumber

string

Returns

SheetEvaluation

evaluateSheetOperations()

evaluateSheetOperations(sheetKey): Resolved<ObjectData[]>

Evaluate one sheet's own ad hoc operations (sheet-local flat content, see Sheet's doc) by sheet key. Empty when the sheet has none. A one-off entry (see evaluateDrawingSet's doc on the same no-consumer reasoning) — evaluateSheet (below) is the cached path for a sheet's FULL composed content, including this same ad hoc lane.

Parameters

sheetKey

string

Returns

Resolved<ObjectData[]>

evaluateStyles()

evaluateStyles(): Resolved<Style[]>

styles[] pull — dirty-gated SnapshotLaneNode. Resolves each style node's expression args into Style records (by evaluated name).

Returns

Resolved<Style[]>

evaluateTable()

evaluateTable(key, operationCount?): Resolved<Table>

Parameters

key

string

operationCount?

number

Returns

Resolved<Table>

evaluateTextStyles()

evaluateTextStyles(): Resolved<TextStyle[]>

textStyles[] pull — dirty-gated SnapshotLaneNode.

Returns

Resolved<TextStyle[]>

evaluateTexture()

evaluateTexture(key, argsOverride?, operationCount?): Resolved<ImageBuffer>

Parameters

key

string

argsOverride?

Record<string, string>

operationCount?

number

Returns

Resolved<ImageBuffer>

evaluateView()

evaluateView(key): Resolved<{ data: ViewSpaceData; extent: BoundingBox2d; frame: Transformation; }>

Resolve one views[] entry (a saved query over the 3D scene — see views.ts) into flat drawing data. Pulls a persistent, dirty-gated ViewNode (nodes/LaneNode.ts).

Parameters

key

string

Returns

Resolved<{ data: ViewSpaceData; extent: BoundingBox2d; frame: Transformation; }>

foldView()

foldView(view): FoldedView

Config-fold one views[] entry against THIS model — the door every consumer that only needs the DEFINITION (camera pose, style name, family) must use. A bare foldViewChain(view) has no cameras lane and no parameter scope, so a view3d head naming a cameras[] key folded to the hard-coded default pose (1000,1000,1000 → origin) and the 3D viewport framed empty space.

Parameters

view

View

Returns

FoldedView

fromJSON()

static fromJSON(json, opts): Model

Deserialize a ModelJSON document into a Model instance; validates schema and loads all nodes. registry = the merged node registry (createRegistry(nodeRegistry) — the engine's own CORE_NODES are always merged in first) the model runs on.

Parameters

json

ModelJSON

opts
assets?

Store

registry

Registry

Returns

Model

graphSnapshot()

graphSnapshot(): GraphSnapshot

Live pull-graph snapshot: nodes (id/key/lane/method/type/dirty/lastEvalMs)

  • typed edges from _dependents. For closedness asserts / Mermaid·DOT serializers — no UI panel.

Returns

GraphSnapshot

laneCarrier()

laneCarrier(): LaneCarrier

Every lane array of THIS model, keyed by lane name — the live-model equivalent of a stored ModelJSON, so laneChains (and any other descriptor-driven walk) reads a document and a Model the same way. Which object owns each array comes from the descriptor.

Returns

LaneCarrier

objectKeys()

objectKeys(): string[]

Keys of the objects[] entries, in declaration order — the model's per-object grain, and EMPTY when it has none.

A loose DataNode at the root (an invalid doc, or a host that added a bare step) turns the whole root back into ONE sequential fold: its containers no longer fold from an empty stream on their own, so no entry is an addressable object and a consumer must take the whole-frame path (collectRenderFrame) instead.

Returns

string[]

refreshTextures()

refreshTextures(): void

Re-resolve material textureData from the current chains and notify the render sync — the builder panel calls this after editing a chain so the 3D scene picks the new pixels up without a full reload. The notify("change", …) below IS both the invalidation AND the re-resolve (see the constructor's "change" listener, which runs _invalidateLaneNodes() then _resolveProcessorTextures() in that order) — no separate pre-notify resolve step needed here.

Returns

void

sceneFrame()

sceneFrame(reader?): RenderFrameSlice[]

Whole-scene render frame — THE aggregate pull (node-model law: explicit scene() / sceneFrame, not a hidden fan-out). reader names the consumer for cycle errors; omit for host-side pulls. Every root body edit dirties this cache; structural root changes dirty it directly.

Parameters

reader?

string

Returns

RenderFrameSlice[]

sceneObjects()

sceneObjects(): SceneObject[]

The scene as typed objects — one per objects[] entry.

Returns

SceneObject[]

setArgOverride()

setArgOverride(nodeKey, argKey, expression): void

Apply a live evaluation override for one arg on a keyed operation (or a parameter's primary value input when argKey is "value"). Marks the arg dirty like a real edit so evaluation/render sync; toJSON() and getInput() still expose the authored document expression.

Parameters

nodeKey

string

argKey

string

expression

string

Returns

void

subscribeObject()

subscribeObject(key, listener): () => void

Subscribe to one object's changes. The callback fires with the channel that actually moved — "content" (the local geometry), "transform" (only the lifted matrix), "props" (only the lifted material/layer/ name) — and is SKIPPED entirely when a re-resolve produces the same object (an edit that changes no output emits nothing).

The engine holds the callback and nothing else: no renderer type ever crosses this boundary.

Parameters

key

string

listener

ObjectChannelListener

Returns

() => void

tessellateScene()

tessellateScene(params?): ObjectData[]

Produce tessellated ObjectData[] for a host renderer (Three.js / SketchUp). Runs evaluate first, then tessellates from the SceneNode cache (one fold — same pull sceneFrame / views / sheets use). brep→MeshData (triangles as 3-gons), curve→polyline; BlockData placements are KEPT unexpanded.

Placements keep their children inline (BlockData.objects); N placements share that array by reference. A writer that walks children sees the placed geometry — there is no definitions map to pass.

Parameters

params?

Record<string, unknown> = {}

Returns

ObjectData[]

toJSON()

toJSON(): ModelJSON

Serialize the model back to a ModelJSON document: parameters[] / objects[] from the runtime collection; asset / camera lanes from the scope.

Returns

ModelJSON


collectFonts

Re-exports collectFonts


collectGeometryFromSlices

Re-exports collectGeometryFromSlices


collectMaterials

Re-exports collectMaterials


collectParams

Re-exports collectParams


collectRenderFrame

Re-exports collectRenderFrame


convertLaneUnits

Re-exports convertLaneUnits


convertModelUnit

Re-exports convertModelUnit


frameFromSlices

Re-exports frameFromSlices


frameLeaves

Re-exports frameLeaves


materialsResolverOf

Re-exports materialsResolverOf


modelCombinationFromSlices

Re-exports modelCombinationFromSlices


modelSections

Re-exports modelSections


nodeGeometry

Re-exports nodeGeometry


rootBodyNodes

Re-exports rootBodyNodes


rootFoldSlices

Re-exports rootFoldSlices


sceneNodeOf

Re-exports sceneNodeOf


seedNewModel

Re-exports seedNewModel


sliceLeaves

Re-exports sliceLeaves

Last updated: 📖 20 min readEdit on GitHub