ProtectedattributeProtectedattributeProtecteddeletedProtecteddeletedProtectedentityProtectedforgottenIds createEntity allocated and deleteEntity then forgot (removed from
newEntities, per that method's "existing entities are tombstoned; new
entities are simply forgotten" contract). Tracked separately so
getEffectiveChanges() / collectEffectiveChanges can tell "overlay-created
then forgotten" apart from "an ordinary source-buffer entity" — both are
otherwise indistinguishable, being simply absent from newEntities.
restoreNewEntity (the undo-of-delete counterpart) clears the id back out.
ProtectedforgottenSnapshot of a forgotten-created entity's overlay rows, stashed by
deleteEntity and restored by restoreNewEntity.
deleteEntity on an overlay-created entity does more than drop it from
newEntities — it also PURGES every other overlay entry the entity left
behind (property/quantity/attribute/positional/type mutations, its
newPsets/newQsets entries, and its own mutationHistory records).
Without that purge, an entity that was created, edited, then deleted
before export left a dangling reference: StepExporter derives its
property/quantity work list from getMutations() (the append-only
history) and reads getForEntity() / getQuantitiesForEntity() straight
off newPsets / newQsets — neither of which the review-side
forgottenCreatedEntities filter in effective-changes.ts touches. The
review dialog looked clean while the exported file still contained an
IFCPROPERTYSET + IFCRELDEFINESBYPROPERTIES pointing at an expressId
that was never actually created (maintainer finding on #1967).
The purged data is captured here, not discarded, because restoreNewEntity
(undo of the delete) must bring it all back — rows AND count AND what the
exporter would see — not just re-add the bare NewEntity record.
ProtectedmutationProtectednewProtectednewProtectednewProtectednextProtectedpositionalProtectedpropertySecondary indices: entityId → mutation keys for that entity.
getForEntity previously iterated the entire propertyMutations /
quantityMutations map per pset to find newly-added properties — O(M·P)
per call. These indices keep that step O(M_entity) instead.
ProtectedpropertyProtectedquantityProtectedquantityProtectedtombstonesProtectedtypeApply a batch of mutations (e.g., from imported change set). The
dispatcher itself lives in applyMutationsBatch (./apply-mutations.js)
— this method just supplies the two bits of private state it needs
without exposing them publicly.
ProtectedcaptureCapture every overlay row one set owns on one entity (#5965). Deep-copied, so later edits to the live maps cannot reach into a recorded snapshot.
Clear all mutations (reset to base state)
ProtectedcopyCreate a new entity in the overlay. Returns the freshly-allocated
expressId. Callers must ensure setExpressIdWatermark has been seeded
from the underlying store before calling this for the first time.
Create a new property set
Create a new quantity set
ProtecteddeleteMark an entity for deletion. Returns false if the id is unknown to this view, or was already tombstoned.
An overlay-created entity is dropped from newEntities — so it is emitted
nowhere, which is the right answer for something created and deleted in one
session — AND tombstoned, so isDeleted tells the truth about it.
It used to be only forgotten, and that made isDeleted lie: every guard
that asks "was this deleted" got false for an entity that no longer
exists, so the export still emitted the IFCRELDEFINESBYPROPERTIES for a
pset queued on it, dangling at a record nothing wrote (#2012). Forgetting
without tombstoning cannot be made safe one guard at a time, because the
question the guards ask has no true answer to find.
Consumers that count entities must therefore intersect tombstones with the
source store rather than subtracting tombstones.size wholesale — a
created-then-deleted id is absent from BOTH the store and getNewEntities,
so counting it as a deletion would subtract it twice.
Delete a property
OptionalskipHistory: boolean
If true, don't add to mutation history (used for undo/redo)
ProtecteddeleteProtecteddeleteDelete an entire quantity set - the inverse of createQuantitySet, and the
exact mirror of deletePropertySet one level up.
It was missing until #2508's zone write-back needed it, which is why
deletedQsets existed but was only ever populated by the restore path.
Without it, a writer that REPLACES an entity's quantity set can shrink it
but never empty it: re-running with no quantities to write leaves the
previous run's numbers in place, so the file states volumes beside a
property saying the volume could not be computed.
Export mutations as JSON. Includes every record in mutationHistory,
including CREATE_ENTITY — but see importMutations for why replaying
that record on another view does not reconstruct the entity.
Every attribute override currently in the overlay, keyed by entity then attribute name.
This is the current overlay state, not the append-only mutation history:
an undone edit has had its overlay entry reset to the pre-edit value (or
removed outright), so it does not appear here, whereas its superseded
UPDATE_ATTRIBUTE record lives on in getMutations forever. Export
must read this — replaying the history resurrects undone edits (#1957).
Get mutated attributes for an entity. Returns only attributes that have been added/modified via mutations.
Snapshot candidate ids for effective attribute reads. Includes named, positional and type overrides even when written without journal records; callers apply their normal live-entity and attribute-layout checks.
Enumerate every change the overlay currently carries, as it stands right
now — never from mutationHistory (see getModifiedEntityCount).
This is what the export-review UI (issue #1915) and any snapshot test
should read: previousValue is derived from the base data (property
table / on-demand extractor / attribute extractor), so an undo→redo
cycle reports the true original, not a stale history entry.
Whole-pset/qset deletes and creates are reported as a single
pset-added / pset-deleted / qset-added / qset-deleted row rather
than one row per property/quantity inside them (deletePropertySet /
createPropertySet also populate individual property/quantity mutations
internally — those are intentionally not double-reported here).
Deterministic ordering: entityId, then kind, then name, then setName.
Read the alias for a given overlay id, or null if none.
Get the retype intent for an entity, or null if it hasn't been retyped.
Get all property sets with mutations applied. An explicit base provider lets an exporter supply its store without changing this live view.
OptionalbaseProvider: (baseId: number) => PropertySet[]Get count of modified entities.
Reads the live overlay, NOT mutationHistory (issue #1915): undo does
not pop history, so a history-based count could over-report — e.g. after
setAttribute + removeAttributeMutation (exactly what undoing a
freshly-created attribute mutation does), the overlay is empty again but
history still holds the one entry. This must agree with
hasPendingChanges: zero here iff that is false.
Must also agree with getEffectiveChanges — an entity contributing
zero effective rows (a create -> edit -> delete deleteEntity forgot, or
an edit fully undone back to its base value) must not be counted here
either. collectModifiedEntityIds is deliberately DERIVED FROM
getEffectiveChanges() rather than hand-walking the overlay maps a
second time, so the two structurally cannot diverge again (issue: the
#1915 forgotten-created blind spot, and the #1967 no-op-edit blind spot
that a second hand-rolled walk reintroduced).
O(1) live-overlay invalidation token, independent of Undo history. Writes, removals and state publications advance it, including skipHistory. Conservative increments may invalidate unchanged geometry; it is never serialized or rewound by Undo/Redo. Detached failed drafts do not affect it.
Get journal mutations from an optional append-only cursor (default: all)
Optionalsince: numberReturns all overlay-created entities in insertion order.
Created entities authored as type, in creation order, from an index (#5413).
Get all positional argument overrides for an entity, keyed by index.
The live overlay's CURRENT property mutation for one entity's specific
pset+prop, or undefined when that exact key carries no override right
now.
Reads propertyMutations directly (the same map getPropertyValue and
hasChanges consult) — never mutationHistory (see getMutationsForEntity
above), which is append-only and does not shrink on undo. Undo re-applies
the inverse mutation with skipHistory=true (mutationSlice.ts, "to
avoid polluting mutation history"): that inverse call still writes
through setProperty/deleteProperty, so propertyMutations — and thus
this method — reflects the reverted (or, after redo, re-applied) value
immediately, while getMutationsForEntity keeps returning the stale
pre-undo entry.
Unlike getPropertyValue (which collapses "no override", "override
value is null", and "override is a DELETE marker" all down to a bare
null), this returns the raw PropertyMutation so a caller projecting
the overlay onto an EXTERNAL base it doesn't otherwise share with this
view (e.g. the IDS bridge's PropertyOverlayResolver, #3929) can tell
"nothing to apply here" apart from "apply a DELETE" apart from "apply a
SET to null". Unlike getEffectiveChanges(), this does not require the
view's own getBasePropertiesForEntity to already know the pset —
setProperty always writes propertyMutations regardless of whether the
pset is new-in-session or pre-existing (see its "Always store in
propertyMutations for tracking" comment), so this stays correct for a
view with no base wired at all (a MutablePropertyView overlay used
purely as a delta against someone else's separate base).
Get a specific property value with mutations applied
Get all quantity sets with mutations applied. The optional provider reads an external base store without changing this live view. Native exporters may opt into empty instances to retain a deleted last member as an ownership removal; ordinary quantity displays omit them.
OptionalbaseProvider: (baseId: number) => QuantitySet[]OptionalincludeEmptyInstances: booleanRead the current quantity override, including explicit unit removal. Unlike the append-only journal, this also reflects skipHistory and Undo.
Every express id this session deleted — source-buffer entities AND ones it
created and then deleted. The two are not distinguishable from this set
alone; a caller that needs to tell them apart intersects it with the store's
own index (see deleteEntity).
All retype intents, keyed by expressId. Returns a defensive copy.
Check if an entity currently carries an overlay change.
Reads the live overlay (same footprint as hasPendingChanges),
NOT the append-only mutationHistory — undo does not pop history (see
getMutations()), so a history-based check could report true for an
entity whose edit was fully undone. Called with no entityId, this is
exactly hasPendingChanges.
Unlike getModifiedEntityCount (derived from
getEffectiveChanges so it can't diverge), this is a direct
per-entity map lookup kept O(1)-ish for callers that probe many entities
(e.g. a per-row "has changes" indicator) — re-deriving effective changes
per call would be O(overlay size) each time. That means it can still
report true for an entity whose only overlay entry is a no-op edit
(undo landed it back at the base value, so previousValue === newValue
— see getEffectiveChanges's doc). Over-reporting here is the same
safe direction hasPendingChanges already documents; nothing in
this repo reads this per-entity form in production as of #1967.
OptionalentityId: numberTrue when the overlay currently carries anything the STEP exporter would bake (property/quantity overrides, attribute / positional / type edits, pset/qset creates or deletes, or overlay-created/tombstoned entities).
Unlike getMutations / hasChanges, this reflects the current
overlay footprint — the same set clear resets and the exporter
reads — rather than the append-only mutation history, which never shrinks.
It is deliberately a conservative over-approximation: undoing an edit resets
the overlay entry's value (or leaves a no-op DELETE marker) instead of
removing it, so a fully-reverted model can still report true. That is the
safe direction for gating an export bake — over-reporting only costs a
redundant (identical-output) re-bake, whereas under-reporting would silently
drop edits.
Whether this view has anything UNDER its quantity overlay.
Properties always do — getBasePropertiesForEntity falls back to the
baseTable the constructor takes — but quantities have only
setQuantityExtractor, which is opt-in and defaults to null. A view
without one answers getQuantitiesForEntity from the overlay ALONE, so a
session that edits one quantity of a source quantity set sees that one
quantity and none of its siblings.
Exposed so a consumer holding the base data can tell "this entity has no
quantities" apart from "this view cannot see them" and supply the missing
half rather than write the overlay out as if it were the whole set — which
is how a full STEP export deleted a source IfcElementQuantity
(github.com/LTplus-AG/ifc-lite/issues/2487).
Import mutations from JSON produced by exportMutations.
Not a full inverse of exportMutations. A CREATE_ENTITY record
carries only the expressId in the history — not the entity's type and
attributes — so importMutations cannot rebuild the entity from the
record alone: it logs a console.warn and skips the record, and drops
every other mutation recorded against that same entity id in the same
batch too (so the round trip is lossy — entity and edits both dropped —
rather than leaving an orphaned property/attribute/quantity keyed to an
id that was never created on the receiving view).
To carry an overlay-created entity across, call restoreNewEntity()
with its NewEntity payload (from getNewEntity/getNewEntities on
the source view) before calling importMutations. Once the id is
live in newEntities, its dependent mutations replay normally — only
the console.warn for the (now redundant) CREATE_ENTITY record still
fires.
Whether a base property set is masked by a whole-set deletion.
Whether a base quantity set is masked by a whole-set deletion.
ProtectedmarkProtectedmatchesProtectedoverlayBorrowed only for synchronous comparison; never returned to callers.
The next expressId that createEntity would allocate.
Prepare an overlay publication without changing live IFC state (#4243).
Use for commands that must validate other synchronous resources first.
validate and commit reject intervening edits, including skip-history
edits. Commit is idempotent; the prepared draft is never published by reference.
Remove an attribute mutation (used by undo for newly set attributes)
Drop a single positional override. Used by undo to roll a
setPositionalAttribute back to "no override" when there was no prior
value. Mirrors removeAttributeMutation for symmetric naming.
Remove a quantity mutation (used by undo for newly created quantities)
OptionalquantName: stringDrop a retype intent, reverting the entity to its original class. Because
setEntityType never mutates NewEntity.type in place, this is a complete
revert for both source-buffer and overlay-created entities — nothing else
to roll back.
Resolve to the base id used for property/quantity reads. Returns the input id when no alias is set. Aliases follow at most one hop — chained duplicates resolve to their immediate source, not the original.
Reverse deleteEntity for an existing-entity tombstone. Returns true if
a tombstone was removed; false if the id was not tombstoned. Used by
undo of a DELETE_ENTITY mutation on a source-buffer entity. Overlay-only
entities are restored via a separate path (restoreNewEntity).
Re-add an overlay-only entity to newEntities. Pairs with deleteEntity
to support undo of a freshly-created-and-then-deleted entity. The caller
is responsible for stashing the NewEntity record between delete and
restore (the slice's undo stack does this).
ProtectedrestorePut one set's overlay rows back exactly as snapshot captured them,
dropping any row the set has gained since. Undo restores a whole-set
mutation's setOverlay.before, redo its after (#5965).
Stage synchronous overlay edits and publish them together (#4243). Throws leave the original overlay, history and allocator untouched. Source tables/extractors are shared read-only; mutable state is detached both before editing and on commit, so an escaped draft cannot edit this view. External effects (files, renderer or network) belong outside this callback.
Set an entity attribute value (Name, Description, ObjectType, Tag, etc.)
OptionaloldValue: stringOptionalskipHistory: booleanSet the base entity-attribute extractor (Name, Description, ObjectType,
Tag, ...), used only to resolve previousValue in getEffectiveChanges().
Without one, attribute previousValue falls back to whatever oldValue
the overlay entry itself carries — which undo can leave stale/absent (see
getEffectiveChanges() doc).
ProtectedsetAlias an overlay-only entity to a source entity for property / quantity reads. Used by the duplicate flow so a fresh duplicate inherits its source's psets / qsets in the property panel without eagerly cloning them. Edits on the duplicate stay scoped to the duplicate's own id (override slots are keyed by entity id, not by base id).
Pass null as the source to clear an existing alias.
Change an entity's IFC class in place ("retype" / reassign class).
The entity keeps its expressId, so its geometry, placement, representation
and every IfcRel* reference (all keyed by #id) carry over unchanged.
At export the exporter re-lays-out the entity's attributes BY NAME against
the target class's declared attribute list — attributes the target class
doesn't have are dropped, missing ones become $. This mirrors
IfcOpenShell's ifcopenshell.util.schema.reassign_class.
Intended for compatible reassignments — e.g. the building-element subtypes
(IfcBuildingElementProxy → IfcColumn / IfcBeam / IfcMember /
IfcPlate / IfcWall) that share the IfcElement attribute layout. For
such retypes only the class keyword changes (and an optional PredefinedType).
Target IFC class (canonical PascalCase, e.g. "IfcColumn").
OptionalpredefinedType: string | null
Optional PredefinedType for the target class. Unknown values fall back to USERDEFINED + ObjectType at export.
OptionaloldType: stringOptionalskipHistory: booleanSeed the express-ID allocator. Should be called once after parsing with
the highest existing expressId in the store; subsequent createEntity
calls allocate IDs strictly above this watermark.
Set an on-demand property extractor function This is used when properties are extracted lazily from the source buffer
Set a positional STEP argument on an entity by zero-based index.
This is the only path for editing non-IfcRoot entities (e.g. profile
dimensions on IfcRectangleProfileDef) where attributes have no symbolic
names. Values follow the same conventions as NewEntity.attributes:
numbers become #expressId references when paired with a reference slot,
otherwise REAL/INTEGER literals; strings become quoted STEP strings;
null becomes $.
OptionalskipHistory: booleanSet a property value If the property set doesn't exist, creates it automatically
OptionalvalueType: PropertyValueTypeOptionalunit: stringOptionalskipHistory: boolean
If true, don't add to mutation history (used for undo/redo)
OptionaldataType: string
IFC measure dataType this value was scaled against at
write time (e.g. an IDS correction, #3929/#3943), stored on the
PropertyMutation for a read-side overlay that needs to convert it
between unit frames. New, additive, optional — every existing caller
is unaffected.
ProtectedsetSet a single quantity value (add to existing or new quantity set)
OptionalqType: QuantityTypeOptionalunit: string | nullOptionalskipHistory: booleanProtectedsetProtectedstampRecord the set's overlay state on either side of a whole-set edit on the
mutation it produced, for undo / redo (#5965). before is taken first.
Overlay-entity → source-entity aliases for property/quantity reads.
When the viewer duplicates an existing entity, the new entity has no row in the parsed property table —
getBasePropertiesForEntitywould return[]and the property panel would show "No property sets". Aliasing redirects the BASE read to the source entity so the duplicate inherits its psets / qsets visually, while overlay mutations (overrides, creates, deletes) stay scoped to the overlay-entity's own id — so editing a property on the duplicate doesn't bleed into the source.Aliases follow at most one hop (no chains). They never affect STEP export — the export overlay emits the duplicate exactly as the StoreEditor recorded it, with whatever new IfcRel*ByProperties the caller chose to add.