Skip to content
Shapemetry

@huukhanhnguyen/types / objects/base

objects/base

BaseInstance

BaseInstance = { attributes?: Record<string, unknown>; layer?: string; locked?: boolean; name?: string; transformation: Transformation; visible?: boolean; }

Placement fields every instance carries, whatever the object's kind.

Kind-specific extras (material, behaviors, …) live on that kind's *Instance, not here. behaviors in particular is a block instruction and must not appear on a mesh or a curve placement.

Its index is STABLE — instances mirror an authored list one-for-one, so an expression may address instances[2] and keep meaning the same placement across re-evaluation.

Coordinates are LOCAL to the object; whatever contains the object composes its own transform on top. An instance therefore never names its parent.

attributes?

optional attributes?: Record<string, unknown>

Free-form classification read by a consumer (schedule, IFC export, plugin), never by the engine. Per PLACEMENT, so one door out of forty can carry a fire rating the other thirty-nine do not.

layer?

optional layer?: string

Layer key.

locked?

optional locked?: boolean

Authoring-safety marker lifted from the chain at assembly. After assembly it shields the placement (draw3d refuses gesture edits). Absent === unlocked.

name?

optional name?: string

What THIS placement is in the world, distinct from BaseObject.label — a different TIER, not a synonym.

label describes the DEFINITION ("this is a Door") and is shared by every placement of it. name identifies the real, concrete thing this one placement IS — "Door-01" vs "Door-02" for two placements of one Door object — the way a schedule/BOM row needs a unique tag per physical unit, not per definition. A name is a fact about the placement, not about the formula that produced it, which is exactly why it sits beside transformation and not on the object.

Absent means the reader falls back to the definition's label. Not attributes: that bag is opaque and a reader must know a key to interpret it, whereas an identity is named on sight.

transformation

transformation: Transformation

The placement, LOCAL to the object, as one composed matrix.

A matrix HERE and translation/rotation/scale on the authoring node — the two tiers want opposite things and this is where they part:

  • The node stores them apart because a human authors "move it up 100" and an animation track binds to a single CHANNEL. A matrix has no channels. (Blender: loc/rot/scale, DNA_object_types.h; three.js: position/rotation/scale, Object3D.js.)
  • This value stores them composed because nobody ever asks it whether POSITION changed — the diff asks whether the placement changed at all, and a renderer wants the matrix anyway. (Rhino stores exactly this for a block reference: ON_InstanceRef::m_xform, one 4x4, no TRS.)

Required, not optional: this is COMPUTED, so the producer always knows it, and an absent-means-identity convention would put a branch in every consumer to buy back sixteen numbers.

visible?

optional visible?: boolean

Absent === visible.


BaseObject

BaseObject = { attributes?: Record<string, unknown>; key: string; label?: string; }

The fields every scene object carries, whatever its data.

key is REQUIRED and is the diff's pairing handle. instances is declared on each kind (CurveInstance[], MeshInstance[], …) — an ordinary single object is instances of length one, so no consumer needs a branch for "not yet duplicated".

attributes?

optional attributes?: Record<string, unknown>

Free-form classification for the object as a whole. Per placement data goes on that kind's *Instance instead.

key

key: string

label?

optional label?: string

Last updated: 📖 3 min readEdit on GitHub