SceneModel ​

Contract 2.0.0 · package sha256 a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e · generated from configurator-runtime.d.ts by services/onsite-scripter/scripts/build-contract-docs.mjs; edit the source, not this file.

Interface · import type { SceneModel } from '@holostep-configurators/runtime-client/contract'; · stable · since 2.0.0 · section 21. Stored data (vendored; replaces @holo/interfaces)

What the runtime renders: the IConfigurator fields it reads. IConfigurator stays assignable to it. The runtime's copy (getSceneModel()) carries no key material: never save it as is. The package has no tree helpers: the runtime never prices, and in an app with its own store the store owns the model (one scene-model writer). The example's helpers are the app-side ones a page writes once; run withNode on the store's document, then sync.

Members ​

Member Kind Summary
root property The node tree.
defaultNodeId? property As IConfigurator.defaultNodeId.
connections? property Input only: getSceneModel(), state().model and the events never carry it (read assembly.connections()).
connectionsBasis? property Input only, as connections.
defaultView? property Stored data the app converts with cameraViewFromStored, then passes to camera.set; a scene write never moves the camera (IDefaultView).
presets? property
attributes? property
parameters? property
toneMapping? property Carried for a spread IConfigurator; never read (IConfigurator.toneMapping).
backgroundColor? property As IConfigurator.backgroundColor: a non-null value is the scene model's background layer (BackgroundValue).
hdrUrl? property Carried for a spread IConfigurator; never read (IConfigurator.hdrUrl).
name? property
_id? property

Declaration ​

ts
export interface SceneModel {
  root: ConfiguratorNode[];
  defaultNodeId?: string;
  connections?: Connection[];
  connectionsBasis?: string;
  defaultView?: IDefaultView;
  presets?: IPreset[];
  attributes?: Record<string, unknown>;
  parameters?: Record<string, unknown>;
  toneMapping?: string;
  backgroundColor?: BackgroundValue;
  hdrUrl?: string;
  name?: string;
  _id?: string;
}

Example ​

Imports: values (has, validate, the Holo*Error classes, the pure helpers) from @holostep-configurators/runtime-client, types from @holostep-configurators/runtime-client/contract, /math and /testing names from those entries; boot from the runtime module the root's loadRuntimeAtFloor resolves to. A type your page defines is your app's: declare it there, never import it from the package.

Compiled under the contract's strict settings (examples/declaration/21-SceneModel.ts):

ts
import type { ConfiguratorNode, DeepReadonly, SceneModel, Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
// A node anywhere in the tree, by nodeId.
export function findNode(
  nodes: readonly DeepReadonly<ConfiguratorNode>[],
  nodeId: string,
): DeepReadonly<ConfiguratorNode> | null {
  for (const node of nodes) {
    if (node.nodeId === nodeId) return node;
    const found = findNode(node.children ?? [], nodeId);
    if (found) return found;
  }
  return null;
}
// A copy with `node` added under `parentId` (at the root without one), the rest untouched.
export function withNode(
  model: DeepReadonly<SceneModel>,
  node: ConfiguratorNode,
  parentId?: string,
): SceneModel {
  const copy = structuredClone(model) as SceneModel;
  const addUnder = (nodes: ConfiguratorNode[]): boolean =>
    nodes.some((n) => {
      if (n.nodeId !== parentId) return addUnder(n.children ?? []);
      n.children = [...(n.children ?? []), node];
      return true;
    });
  if (parentId === undefined) copy.root.push(node);
  else if (!addUnder(copy.root)) throw new Error(`no node ${parentId}`);
  return copy;
}
// A global-material group's selected material: `select(group, material)` records it as the
// group's `defaultNodeId`. Prices are your app's: read them from your catalogue by this id.
export function selectedMaterial(model: DeepReadonly<SceneModel>, group: string): string | null {
  const node = findNode(model.root, group);
  return node?.type === 'GroupNode' ? (node.defaultNodeId ?? null) : null;
}

Generic-API test (spec 3.1) ​

Rule Result
1. Neutral name pass: SceneModel names no product
2. A precedent not recorded: no ledger row (coverage.json) names this member or its owner
3. Two apps, or labs pass: 20 apps (fleet cross-check: app-04c2774145, app-0c773a4ee3, app-37356bf09f, app-4762caacdc, app-4fd23bc869, app-822bb9d6aa, app-8675b3225c, app-b362a81c85 and 12 more)
4. The cheaper fix first not recorded per member (Appendix B decides it per need)