Parametric Authoring
A parametric model is a ModelJSON document. This page is the authoring model; the runnable intro is Quickstart: parametric. The exhaustive node catalog lives in REFERENCE.md (node grammar, expression rules, operation catalog) and ENTITIES.md (entity shapes) in packages/model.
Document shape: lanes
ModelJSON is title plus named lanes — arrays of nodes with distinct roles:
const doc = {
title: 'bracket',
parameters: [/* named values + computed expressions */],
materials: [/* looks */],
layers: [/* organization */],
objects: [/* the scene — geometry is built here */],
textures: [/* procedural texture chains */],
}The full lane list: materials, layers, styles, cameras, skeletons, animations, parameters, objects, opening, views, components, textures, tables, sheets, documents. Only title is required. Runtime lanes collapse to four roles — parameters, scene (stored as objects), component, flat.
Nodes: operations with method + args
One recursive node shape. An operation names a registry method; every arg input is an expression string, even constants:
{
key: 'plate',
operations: [
{ method: 'rectangle', args: [
{ key: 'point1', input: '[-(w)/2,-(60)/2,0]' },
{ key: 'point2', input: '[(w)/2,(60)/2,0]' },
] },
{ method: 'curveExtrude', args: [{ key: 'thickness', input: '8' }] },
{ method: 'solidUnion', args: [{ key: 'solid', input: 'boss' }] }, // references another node
],
}A container swaps method for { operations: [...] } children that fold into one stream. Args may carry UI metadata (min, max, step, options) beside input.
References and expressions
- Bare key —
"boss"references that node's output stream. - Arithmetic over parameters —
"[-(w)/2,-(60)/2,0]". - Computation namespaces — the geometry namespaces are callable:
"NurbsCurve.length(centerline)","Axis.fromPointDirection([0,0,0],[0,0,1])". - Evaluation overrides —
evaluateModel(doc, { w: 800 }, { registry })overrides parameters transiently for that pass (visible inresult.paramsand the evaluated graph); the document is never written back, and the lazygeometrybake re-applies the same overrides when read, so it reflects the overridden pass too.
Chains and lane records
Two record spines recur across lanes (types from @huukhanhnguyen/types):
// Chain — the spine of skeletons / animations / textures:
type Chain = { key: string; label?: string; parameters?: (Operation | Container)[]; operations: (Operation | Container)[] }
// LaneNode — one cameras[] entry; method bound to the lane's vocabulary:
type LaneNode<M extends string = string> = {
key: string; label?: string; enabled?: string; method: M; args?: Argument[]
}Runtime doors
import { Model, createRegistry, evaluateModel, inspectModel } from '@huukhanhnguyen/model'
import { nodeRegistry } from '@huukhanhnguyen/model/nodes'
const registry = createRegistry(nodeRegistry) // once per app
const model = Model.fromJSON(doc, { registry }) // validates; throws on structural errors
const result = model.evaluate({ w: 800 }) // re-evaluate with overrides, cheap to repeat
// Diagnostics without rendering: per-group bounds (BoundingBox = { min, max }), counts
const report = inspectModel(model)
console.log(report.groups[0]?.bounds)result.geometry gives baked render items per scene key; result.nodes is the evaluated graph; result.diagnostics collects evaluation issues. Kind-sniff entities with entityTypeOf / isMesh / isShell from @huukhanhnguyen/model.
Where next
- Quickstart: parametric — the minimal working document.
- Rendering with three.js — display evaluated entities.
REFERENCE.md/ENTITIES.mdinpackages/model— the full node and entity catalog.