Skip to content
Shapemetry

Model - Components

API reference for src/lanes/components/components, src/validate/components, src/schema/components on @huukhanhnguyen/model, reachable from ..

See the Guide for the ModelJSON / evaluate pipeline.

API reference

Signatures are generated from the live package .d.ts - not hand-written.

buildIsolatedParameterScope

function

Build an isolated-scope NodeCollection loaded with a parameters lane — a synthetic owner with NO .parent (see module doc: this is what stops the node-tree's upward owner walk immediately, so the scope can never see a host model's keys) — then apply per-call argsOverride to any parameter it names (a key not present keeps that parameter's own default expression). Shared by evaluateFlatComponent/evaluateComponent below and textureResolve.ts's textureParams (which stops here and reads the parameter values straight off the collection — no operations).

extraOwnerFields may include a definitions entry to OVERRIDE the fresh new Map() default — evaluateComponent uses this to share ONE DefinitionMap between a component's operations and opening folds (a nested block built by one is visible to the other's bake pass).

buildIsolatedParameterScope(parameters: (Operation | Container)[] | undefined, argsOverride: Record<string, string> | undefined, registry: Registry, extraOwnerFields?: Extra): { owner: IsolatedScopeOwner & Extra; collection: NodeCollection; }

componentTableWarnings

function

Soft lint: Table.* in a component body/opening expression is isolation- law violation. A component receives table data via a PARAMETER; the host evaluates Table.get(...) at placeComponent args (outer scope).

componentTableWarnings(model: ModelJSON): RegistryError[]

evaluateComponent

function

Evaluate one Component in its isolated, UNRESTRICTED (full 3D grammar) scope — lane "component" (see schema/model.ts's ModelLane/Component doc). argsOverride supplies this PLACEMENT's argument values (by parameter key); resolveComponent looks up another components[] entry by key (for placeComponent nesting, 3D mode — see resolvePlacedComponentBody on FlatLaneExtras); resolving guards against a reference cycle.

operations and opening evaluate as TWO SEPARATE folds (each starting from an empty stream) but share ONE DefinitionMap, so a nested block built by one is visible to the other's bake pass (isFlat's own bake, and — for a real placement — EntitiesNode's opening-subtract, which reads opening directly as already-resolved geometry).

evaluateComponent(component: Component, argsOverride: Record<string, string> | undefined, registry: Registry, resolveComponent: (key: string) => Component | undefined, resolving?: Set<string>, extra?: FlatLaneExtras): ResolvedComponentValue

evaluateFlatComponent

function

Evaluate one ALWAYS-FLAT source (a sheet's ad hoc operations via evaluateFlatOperations below, or any flat-lane component content) in its isolated "flat" scope — lane "flat" (FLAT_LANE_METHODS-restricted, force-flattened to z=0 at every step). argsOverride supplies this call's argument values (by parameter key) — a key not present falls back to the parameter's own default expression. resolveComponent looks up a components[] entry by key (for placeComponent nesting, FLAT mode); resolving guards against a reference cycle (A places B places A).

evaluateFlatComponent(source: FlatComponentSource, argsOverride: Record<string, string> | undefined, registry: Registry, _resolveComponent: (key: string) => Component | undefined, resolving?: Set<string>, extra?: FlatLaneExtras): Entity[]

evaluateFlatOperations

function

Evaluate a Sheet's own ad hoc operations (sheet-local flat content, authored directly in sheet-mm coordinates, so no placement transform is applied by the caller). Reuses evaluateFlatComponent's isolated scope + flat-lane machinery via a throwaway key.

parameters is the sheet's OWN signature (2026-07-26): the same Sheet.parameters lane resolveSheetParams reads paper size and scale from, wired here so a free parameter is READABLE by bare key from any ad hoc op's expression instead of being inert data nothing could reach. It is a true isolated signature — the host model's parameters stay invisible, so a sheet is a function of its own declarations exactly like a component is.

evaluateFlatOperations(operations: (Operation | Container)[], registry: Registry, resolveComponent: (key: string) => Component | undefined, extra?: FlatLaneExtras, parameters?: (Operation | Container)[]): Entity[]

flatContractWarnings

function

flatContractWarnings(model: ModelJSON): RegistryError[]

FlatLaneExtras

type

Extra scope hooks an isolated evaluation may need beyond resolveComponent (the by-key lookup passed as a plain function argument) — viewEntity's resolvePlacedView, tableEntity's resolvePlacedTable, imageEntity's imageUrlOf, Table.get()'s resolveTable, Material.get()'s resolveMaterial (settings-functions §B.2 — same wire as Table.get), and the TWO placeComponent hooks: resolvePlacedComponent (FLAT-mode — splices a gated, flattened result; wired for a truly-flat scope: sheets/rasterize) and resolvePlacedComponentBody (3D-mode — raw untransformed body+opening, no flatness gate; wired for the ROOT scope and a component's own "component"-lane scope). All Model-owned — absent when the caller has none (e.g. a standalone test), in which case each generator fails soft to empty/passthrough, and a bare Table.get(...) / Material.get(...) reference simply isn't defined (the same ReferenceError it always was before this hook existed).

type FlatLaneExtras
// = {
    resolvePlacedView?: (key: string) => Entity[];
    resolvePlacedTable?: (key: string) => Entity[];
    /** 3D-mode placeComponent hook (2026-07-17) — raw (untransformed) body +
     *  opening volumes, NO flatness gate: valid to place a flat component in
     *  a 3D chain. Wired at the ROOT scope (Model's constructor) AND a
     *  component's own isolated "component"-lane scope (nested 3D
     *  placement) — never at a truly-flat scope, where placeComponent is a
     *  validate error outright (2026-07-26: paper places a VIEW) and the
     *  flat-mode resolver refuses (see EntitiesNode's dispatch,
     *  which checks `owner.lane === "flat"` OR the chain-level flat
     *  declaration). `flat` (the component's own `Component.flat`) rides
     *  along so a flat-declared CHAIN reaching this hook from a 3D lane can
     *  still splice the declared 2D representation
     *  (see EntitiesNode._resolvePlacedComponentFlat). */
    resolvePlacedComponentBody?: (key: string, argsOverride?: Record<string, string>) => {
        entities: Entity[];
        opening: Entity[];
        flat: boolean;
    };
    imageUrlOf?: (name: string) => string;
    resolveTable?: (key: string) => unknown;
    /** Material.get(name) — resolved materials[] record by name (fail-soft
     *  null). Wired like resolveTable so component/flat isolated scopes
     *  register a materials-lane dependency edge (settings-functions §B.2). */
    resolveMaterial?: (name: string) => unknown;
    /** `nesting`'s hook — a whole-scene-scan-capable flat-lane producer.
     *  Deliberately still NEVER wired onto the root scope, nor a component's
     *  own scope (a component's placed output can itself join the scene this
     *  would scan — same self-reference risk as at root) — UNLIKE
     *  resolvePlacedView/Table (see their own docs, and Model.ts's
     *  sceneFrame fold-depth guard), `nesting`'s whole-scene scan has no per-key
     *  identity to name a cycle by (it reads the WHOLE scene unconditionally,
     *  never one keyed entry), so a blanket lane-placement ban stays the
     *  simplest correct rule here — not yet worth the same detection
     *  machinery. Post VIEW-REFACTOR the `parts` keys name components[]
     *  entries (their view:"pattern" content is what gets nested — see
     *  Model._partPatternOf). Args already coerced by the calling
     *  EntitiesNode (`parts` array of {key,quantity}, `stock` object,
     *  `spacing`/`seed` numbers). */
    resolvePlacedNesting?: (parts: {
        key: string;
        quantity: number;
    }[], stock: {
        width: number;
        height: number;
    }, spacing: number, seed: number, stockLength: number, kerfWidth: number) => Entity[];
    /** Called once per step whose fold FAILED, after the lane has run.
     *  `EntitiesNode._stepFailed` is fail-soft by design — it records the
     *  error on the node, logs it, and passes the input through — so without
     *  this hook the only trace outside the node is a console line, and a
     *  caller (a sheet) cannot tell a placement that produced nothing from one
     *  that was never there. Reporting only; it must not change the fold. */
    onStepError?: (failure: {
        step: string;
        method: string;
        reason: string;
    }) => void;
}

openingCutWarnings

function

Soft lint for the opening law's GROUP BEHAVIOR (2026-07-26): the subtract does NOT reach inside groups, and that is deliberate — geometry ops pass groups through, and a void is a geometry op like any other. The failure mode is silent: the doorway simply is not there, and the author has no error to read.

So: a placeComponent whose component declares a non-empty opening, sitting in a scope where EVERYTHING is grouped, cuts nothing. Reported here rather than at eval time because it is not wrong — a scene may legitimately be all blocks — and because the JSON says enough to see it.

Position stopped mattering when the cut became a block BEHAVIOR run over the settled chain (behaviors.ts): a wall authored after the door is cut just the same, so only the group boundary is left to warn about.

Needs a registry to tell a producer from a transform; without one the lint is skipped rather than guessed.

openingCutWarnings(model: ModelJSON, registry?: Registry): RegistryError[]
Last updated: 📖 4 min readEdit on GitHub