Parametron

Store is the engine's session-global asset registry: raw model JSONs by id, parsed fonts by name, and platform-native materials by name. It is JSON-level only — it never instantiates a Model and it never fetches. A host (browser app, SketchUp bridge, server function) does the actual network/file I/O and hands the result to Store; the engine only ever does synchronous Map lookups against it during evaluation. This is the core doctrine: the engine never fetches — a missing asset at eval time is a lookup error, not a network call.

Import

import { store } from "@parametron/parametric"

store is a ready-to-use singleton exported from the package root — most hosts use it directly rather than constructing their own. The class is also exported (Store) if you need an isolated instance (tests, multi-session servers).

Loading a bundle

The host fetches a model plus its transitive dependencies (already resolved server-side, flat) and hands it to store.load(). This is the one async phase in the whole pipeline; everything downstream is synchronous:

const bundle = await db.fetchWithDeps("modelA")   // host fetches
await store.load(bundle)                          // register + loadFonts
store.registerMaterials(sketchup.materials)        // platform bridge
const model = Model.fromJSON(store.getModel("modelA") as ModelJSON, { registry, assets: store })

load(bundle, opts?):

  • Registers every JSON in bundle.deps (id → JSON) that isn't already known, scanning each for assets.
  • Registers bundle.model itself, under opts.rootId if given.
  • Awaits loadFonts() so every font referenced by the bundle is parsed before you evaluate.
type ModelBundle = {
    model: unknown
    deps?: Record<string, unknown>
}

Registering fonts

registerFromModel(json) walks a raw model JSON's static fonts[] lane (v22) and queues each { name, url } for async load — it also recurses into embedded loadModel docs (a nested sub-model can carry its own fonts). This runs automatically inside load()/registerModel(); call it directly if you're registering JSON you didn't fetch through a bundle.

store.registerFont("Oak Sans", "https://cdn.example.com/oak-sans.ttf")
await store.loadFonts()   // parses all pending fonts in parallel, non-fatal on failure

loadFonts() fetches each pending URL, parses it with the font domain's parse(), and stores the result under its name. A font that fails to load is silently dropped — the font-typed arg coercer falls back to the fallback font rather than throwing (see packages/parametric/src/domains/font.ts).

Materials and layers need no such pre-load step: the document's own materials[]/layers[] lanes are static records, read straight off the model scope during evaluation. registerMaterial(s) exists only for platform-native materials (see below).

Registering models and materials directly

store.registerModel(id, json)              // JSON by id, without a bundle fetch
store.registerMaterial({ name: "Walnut", baseColor: "#5c4326" })
store.registerMaterials([...])             // batch form

registerMaterial is lowest-priority: it only inserts if the name isn't already registered, so a document's own materials[] lane record always wins over a platform default. This is how a host bridge (e.g. a SketchUp palette) offers fallback materials without shadowing anything the model author declared explicitly:

export type MaterialDefJSON = {
    name: string
    baseColor?: string   // hex color: "#rrggbb" or "#rrggbbaa"
    texture?: string     // https:// or data: URI
    opacity?: number
    metallic?: number
    roughness?: number
}

MaterialDefJSON is a compatible subset of the document's static materials[] lane record shape (MaterialJSON adds tileWidth/tileHeight/colorFactor), so a lookup result is interchangeable wherever those extra fields aren't needed — see Schema & validation for the document-side MaterialJSON type.

Lookup

store.getModel(id)      // unknown — raw JSON, or undefined
store.getFont(key)       // parsed font data, or null
store.getMaterial(name)  // MaterialDefJSON, or null

These three are the only reads the rest of the system performs, and all are synchronous. getFont/getMaterial are what a font-/material-typed arg resolves through during coercion — see the arg-coercion pass in Registry & contracts. getModel is host-facing (e.g. a catalog opening a model by id); the evaluation graph itself does not call it — see below.

Wiring into evaluation

Pass the Store as assets when building or evaluating a model:

import { evaluateModel } from "@parametron/parametric"

const result = evaluateModel(json, params, { registry, assets: store })

or via Model.fromJSON(json, { registry, assets: store }). The Store then flows onto the model's scope (scope.assets) and every nested sub-scope, and reaches ComputeContext.store for node compute/create hooks and the arg coercer. A font-typed or material-typed string arg resolves through assets.getFont/assets.getMaterial at coercion time; everything else ignores the Store entirely. Evaluation with no assets at all is valid — fonts fall back to the built-in fallback font, and font/material args that resolve to nothing just come back null.

Model composition does not use the Store

A loadModel parameter embeds the full sub-model JSON as its input — there is no Store id/URL indirection for model composition. store.getModel/registerModel exist for host-facing lookup (a catalog or file browser opening a model by id), not for the engine's own graph traversal: placeModel resolves its source by key against the already-embedded sub-model handle, never through the Store. If you need to prove this to yourself, the test suite does exactly that — a Store subclass whose getModel throws still evaluates a two-level nested model composition without ever tripping it (packages/parametric/src/__tests__/bundle.test.ts).

Reset

store.reset()

Clears models, materials, fonts, and any still-pending font loads. Useful between tests or when starting a fresh session against the shared singleton.

See also

  • Model & pull graph — where scope.assets lives and how it's inherited by nested sub-scopes.
  • Registry & contractsComputeContext.store, arg type coercion (font/material string → object).
  • Render contract — how resolved materials/fonts end up in EvaluateResult/SceneJSON output.
  • Schema & validation — the document's own static materials[]/layers[]/fonts[] lanes.
📖 4 min read