@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
_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
_sheetNodes
_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
readonlyeditHistory: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
readonlyscope: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
Set Signature
set cameras(
value):void
Parameters
value
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
Set Signature
set components(
value):void
Parameters
value
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
Set Signature
set tables(
value):void
Parameters
value
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
Set Signature
set unit(
value):void
Parameters
value
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
Returns
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
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
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
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
evaluateCameras()
evaluateCameras():
Resolved<CameraObject[]>
Top-level cameras[] pull (no placement materialization).
Returns
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()
layers[] pull — dirty-gated SnapshotLaneNode. Resolves each layer node's expression args into Layer records (by evaluated name).
Returns
evaluateMaterials()
evaluateMaterials():
Resolved<MaterialData[]>
materials[] pull — dirty-gated SnapshotLaneNode. Texture edges + consumers (views/sheets/placePart) cascade from here.
Returns
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
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()
patterns[] pull — dirty-gated SnapshotLaneNode. Resolves each pattern node's expression args into Pattern records (by evaluated name).
Returns
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
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
evaluateStyles()
styles[] pull — dirty-gated SnapshotLaneNode. Resolves each style node's expression args into Style records (by evaluated name).
Returns
evaluateTable()
Parameters
key
string
operationCount?
number
Returns
evaluateTextStyles()
textStyles[] pull — dirty-gated SnapshotLaneNode.
Returns
evaluateTexture()
evaluateTexture(
key,argsOverride?,operationCount?):Resolved<ImageBuffer>
Parameters
key
string
argsOverride?
Record<string, string>
operationCount?
number
Returns
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
Returns
fromJSON()
staticfromJSON(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
opts
assets?
registry
Returns
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
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
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
sceneObjects()
sceneObjects():
SceneObject[]
The scene as typed objects — one per objects[] entry.
Returns
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
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
toJSON()
toJSON():
ModelJSON
Serialize the model back to a ModelJSON document: parameters[] / objects[] from the runtime collection; asset / camera lanes from the scope.
Returns
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