@ifc-lite/export 2.9.4 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/csv-cell.d.ts +101 -0
- package/dist/csv-cell.d.ts.map +1 -0
- package/dist/csv-cell.js +90 -0
- package/dist/csv-cell.js.map +1 -0
- package/dist/declared-property-type.d.ts +19 -8
- package/dist/declared-property-type.d.ts.map +1 -1
- package/dist/declared-property-type.js +31 -9
- package/dist/declared-property-type.js.map +1 -1
- package/dist/demesh-session.d.ts.map +1 -1
- package/dist/demesh-session.js +2 -1
- package/dist/demesh-session.js.map +1 -1
- package/dist/effective-index.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/lod1-generator.d.ts +0 -2
- package/dist/lod1-generator.d.ts.map +1 -1
- package/dist/lod1-generator.js +1 -1
- package/dist/lod1-generator.js.map +1 -1
- package/dist/merged-exporter.d.ts +19 -0
- package/dist/merged-exporter.d.ts.map +1 -1
- package/dist/merged-exporter.js +23 -41
- package/dist/merged-exporter.js.map +1 -1
- package/dist/parquet-exporter.d.ts +1 -3
- package/dist/parquet-exporter.d.ts.map +1 -1
- package/dist/parquet-exporter.js +1 -0
- package/dist/parquet-exporter.js.map +1 -1
- package/dist/property-value-serialization.d.ts +54 -0
- package/dist/property-value-serialization.d.ts.map +1 -0
- package/dist/property-value-serialization.js +134 -0
- package/dist/property-value-serialization.js.map +1 -0
- package/dist/reference-collector.d.ts +20 -0
- package/dist/reference-collector.d.ts.map +1 -1
- package/dist/reference-collector.js +46 -113
- package/dist/reference-collector.js.map +1 -1
- package/dist/schema-converter.d.ts.map +1 -1
- package/dist/schema-converter.js +46 -96
- package/dist/schema-converter.js.map +1 -1
- package/dist/source-ref-bounds.d.ts +3 -3
- package/dist/source-ref-bounds.js +3 -3
- package/dist/step-attribute-mutations.d.ts +49 -0
- package/dist/step-attribute-mutations.d.ts.map +1 -0
- package/dist/step-attribute-mutations.js +172 -0
- package/dist/step-attribute-mutations.js.map +1 -0
- package/dist/step-attribute-serializers.d.ts +41 -0
- package/dist/step-attribute-serializers.d.ts.map +1 -0
- package/dist/step-attribute-serializers.js +74 -0
- package/dist/step-attribute-serializers.js.map +1 -0
- package/dist/step-collection.d.ts +55 -0
- package/dist/step-collection.d.ts.map +1 -0
- package/dist/step-collection.js +126 -0
- package/dist/step-collection.js.map +1 -0
- package/dist/step-export-contexts.d.ts +61 -0
- package/dist/step-export-contexts.d.ts.map +1 -0
- package/dist/step-export-contexts.js +50 -0
- package/dist/step-export-contexts.js.map +1 -0
- package/dist/step-export-types.d.ts +262 -0
- package/dist/step-export-types.d.ts.map +1 -0
- package/dist/step-export-types.js +22 -0
- package/dist/step-export-types.js.map +1 -0
- package/dist/step-exporter.d.ts +23 -354
- package/dist/step-exporter.d.ts.map +1 -1
- package/dist/step-exporter.js +76 -1260
- package/dist/step-exporter.js.map +1 -1
- package/dist/step-geometry-types.d.ts +3 -0
- package/dist/step-geometry-types.d.ts.map +1 -0
- package/dist/step-geometry-types.js +73 -0
- package/dist/step-geometry-types.js.map +1 -0
- package/dist/step-georeferencing.d.ts +0 -10
- package/dist/step-georeferencing.d.ts.map +1 -1
- package/dist/step-georeferencing.js +46 -94
- package/dist/step-georeferencing.js.map +1 -1
- package/dist/step-header.d.ts +53 -0
- package/dist/step-header.d.ts.map +1 -0
- package/dist/step-header.js +63 -0
- package/dist/step-header.js.map +1 -0
- package/dist/step-map-unit.d.ts +90 -0
- package/dist/step-map-unit.d.ts.map +1 -0
- package/dist/step-map-unit.js +277 -0
- package/dist/step-map-unit.js.map +1 -0
- package/dist/step-omission-predicates.d.ts +51 -0
- package/dist/step-omission-predicates.d.ts.map +1 -0
- package/dist/step-omission-predicates.js +268 -0
- package/dist/step-omission-predicates.js.map +1 -0
- package/dist/step-overlay-attribute-overrides.d.ts +32 -0
- package/dist/step-overlay-attribute-overrides.d.ts.map +1 -0
- package/dist/step-overlay-attribute-overrides.js +83 -0
- package/dist/step-overlay-attribute-overrides.js.map +1 -0
- package/dist/step-overlay-entities.d.ts +64 -0
- package/dist/step-overlay-entities.d.ts.map +1 -0
- package/dist/step-overlay-entities.js +119 -0
- package/dist/step-overlay-entities.js.map +1 -0
- package/dist/step-pass-builder.d.ts +45 -0
- package/dist/step-pass-builder.d.ts.map +1 -0
- package/dist/step-pass-builder.js +249 -0
- package/dist/step-pass-builder.js.map +1 -0
- package/dist/step-property-set-collection.d.ts +24 -0
- package/dist/step-property-set-collection.d.ts.map +1 -0
- package/dist/step-property-set-collection.js +286 -0
- package/dist/step-property-set-collection.js.map +1 -0
- package/dist/step-property-set-generators.d.ts +20 -0
- package/dist/step-property-set-generators.d.ts.map +1 -0
- package/dist/step-property-set-generators.js +98 -0
- package/dist/step-property-set-generators.js.map +1 -0
- package/dist/step-property-set-index.d.ts +25 -0
- package/dist/step-property-set-index.d.ts.map +1 -0
- package/dist/step-property-set-index.js +69 -0
- package/dist/step-property-set-index.js.map +1 -0
- package/dist/step-property-set-readers.d.ts +158 -0
- package/dist/step-property-set-readers.d.ts.map +1 -0
- package/dist/step-property-set-readers.js +251 -0
- package/dist/step-property-set-readers.js.map +1 -0
- package/dist/step-property-sets.d.ts +2 -184
- package/dist/step-property-sets.d.ts.map +1 -1
- package/dist/step-property-sets.js +22 -674
- package/dist/step-property-sets.js.map +1 -1
- package/dist/step-serialization.d.ts +33 -52
- package/dist/step-serialization.d.ts.map +1 -1
- package/dist/step-serialization.js +69 -131
- package/dist/step-serialization.js.map +1 -1
- package/dist/step-source-iteration.d.ts +97 -0
- package/dist/step-source-iteration.d.ts.map +1 -0
- package/dist/step-source-iteration.js +178 -0
- package/dist/step-source-iteration.js.map +1 -0
- package/package.json +7 -7
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the `ExportPass` one `export()` call threads through its phases.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `step-exporter.ts` for #2475. The literal moved verbatim; the
|
|
5
|
+
* only edits are that the three things it used to read off the exporter
|
|
6
|
+
* (`dataStore`, `mutationView`, `isGeometryEntity`) now arrive as fields of
|
|
7
|
+
* {@link PassBuildInput}.
|
|
8
|
+
*
|
|
9
|
+
* ONE RULE GOVERNS THIS FILE, and it is easy to break by tidying. The
|
|
10
|
+
* predicates below are closures over `pass` ITSELF, and `pass.allowedEntityIds`
|
|
11
|
+
* / `pass.hiddenProductIds` are still `null` when this function returns --
|
|
12
|
+
* `collectModifications` assigns them afterwards, on this very object. So this
|
|
13
|
+
* must `return pass` and nothing else. A spread, a `structuredClone`, an
|
|
14
|
+
* `Object.freeze`, or a caller that stores a copy detaches every predicate from
|
|
15
|
+
* the object that later gets written, and each one silently answers from a
|
|
16
|
+
* snapshot that never fills in. That is #2637, and the failure mode is a
|
|
17
|
+
* `visibleOnly` export shipping a structurally wrong file rather than throwing.
|
|
18
|
+
*/
|
|
19
|
+
import type { IfcDataStore, IfcSourceHeader } from '@ifc-lite/parser';
|
|
20
|
+
import type { MutablePropertyView } from '@ifc-lite/mutations';
|
|
21
|
+
import type { IfcSchemaVersion } from './schema-converter.js';
|
|
22
|
+
import type { ExportPass, StepExportOptions } from './step-export-types.js';
|
|
23
|
+
/**
|
|
24
|
+
* Everything the pass literal reads that is not its own field.
|
|
25
|
+
*
|
|
26
|
+
* `dataStore` and `mutationView` are the exporter's, injected rather than
|
|
27
|
+
* reached for. `isGeometryEntity` arrives as a parameter for a different
|
|
28
|
+
* reason: it is the free function in `step-geometry-types.ts`, so taking it
|
|
29
|
+
* here keeps this builder independent of the exporter AND of that module.
|
|
30
|
+
*/
|
|
31
|
+
export interface PassBuildInput {
|
|
32
|
+
readonly dataStore: IfcDataStore;
|
|
33
|
+
readonly mutationView: MutablePropertyView | null;
|
|
34
|
+
readonly isGeometryEntity: (type: string) => boolean;
|
|
35
|
+
readonly options: StepExportOptions;
|
|
36
|
+
readonly schema: IfcSchemaVersion;
|
|
37
|
+
readonly sourceSchema: IfcSchemaVersion;
|
|
38
|
+
readonly converting: boolean;
|
|
39
|
+
readonly applyMutations: boolean;
|
|
40
|
+
readonly excludeGeometry: boolean;
|
|
41
|
+
readonly sourceHeader: IfcSourceHeader | undefined;
|
|
42
|
+
readonly schemaToken: string;
|
|
43
|
+
}
|
|
44
|
+
export declare function buildExportPass(input: PassBuildInput): ExportPass;
|
|
45
|
+
//# sourceMappingURL=step-pass-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-pass-builder.d.ts","sourceRoot":"","sources":["../src/step-pass-builder.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAqB,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAM5E;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAClD,QAAQ,CAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACrD,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,eAAe,GAAG,SAAS,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,GAAG,UAAU,CA2QjE"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { getEffectiveEntityIndex } from './effective-index.js';
|
|
5
|
+
import { createModificationLedger } from './delta-modification-ledger.js';
|
|
6
|
+
import { createSourceRefReader } from './source-ref-bounds.js';
|
|
7
|
+
import { buildStepHeader } from './step-header.js';
|
|
8
|
+
export function buildExportPass(input) {
|
|
9
|
+
const { dataStore, mutationView, isGeometryEntity, options, schema, sourceSchema, converting, applyMutations, excludeGeometry, sourceHeader, schemaToken, } = input;
|
|
10
|
+
const pass = {
|
|
11
|
+
entities: [],
|
|
12
|
+
newEntityCount: 0,
|
|
13
|
+
schema,
|
|
14
|
+
sourceSchema,
|
|
15
|
+
converting,
|
|
16
|
+
sourceHeader,
|
|
17
|
+
schemaToken,
|
|
18
|
+
overlayActive: !!mutationView && applyMutations,
|
|
19
|
+
// Built once entity counts are known, so the provenance item can report the
|
|
20
|
+
// actual modification count. See the two call sites (empty delta + final).
|
|
21
|
+
// Body lives in `step-header.ts` (#2475 header/assembly tail): the
|
|
22
|
+
// closure still has to be built here, because it closes over this
|
|
23
|
+
// call's own `options`/`sourceHeader`/`schemaToken`, and both call
|
|
24
|
+
// sites still read it as `pass.buildHeader`.
|
|
25
|
+
buildHeader: (modifications) => buildStepHeader(options, sourceHeader, schemaToken, modifications),
|
|
26
|
+
// The one authority for exists / class / deleted, overlay first and source
|
|
27
|
+
// buffer second. Every pass below asks this instead of `dataStore`,
|
|
28
|
+
// which answers only for the file as parsed (#2012).
|
|
29
|
+
effective: getEffectiveEntityIndex(dataStore, mutationView, applyMutations),
|
|
30
|
+
// Does this id belong to an entity the OVERLAY created (`createEntity` /
|
|
31
|
+
// `store.addEntity`) rather than to a record in the source buffer? Such an
|
|
32
|
+
// entity has no source bytes, so the source-iteration pass below never sees
|
|
33
|
+
// it and the new-entities pass at the end owns its line entirely (#2006).
|
|
34
|
+
isOverlayCreated: (entityId) => pass.effective.isOverlayCreated(entityId),
|
|
35
|
+
// Does this record describe a line this export can actually READ out of the
|
|
36
|
+
// source? One predicate for every byte-range gate below, so they cannot
|
|
37
|
+
// disagree — see `source-ref-bounds.ts` for the corrupt file the weaker
|
|
38
|
+
// "is there a source / does the ref claim bytes" pair let through (#2491).
|
|
39
|
+
isReadableSourceRef: createSourceRefReader(dataStore.source),
|
|
40
|
+
// Build visible-only closure if requested. Classification, the closure walk
|
|
41
|
+
// and the style pass all run over the EFFECTIVE index: an overlay-created
|
|
42
|
+
// product becomes a root by the same type rules as a parsed one, the walk
|
|
43
|
+
// follows its authored references into the geometry it alone owns, and a
|
|
44
|
+
// tombstoned entity is simply not there. Run over the source buffer, a
|
|
45
|
+
// created wall could never be a root and nothing referenced it, so
|
|
46
|
+
// `visibleOnly` wrote a file without it and said nothing (#2012).
|
|
47
|
+
//
|
|
48
|
+
// Computed here, ahead of the modification-count passes below, because
|
|
49
|
+
// `hasEmittableHostBytes` needs it: a source-backed host EXCLUDED by
|
|
50
|
+
// `visibleOnly` never gets its line written by the source-iteration pass
|
|
51
|
+
// either, so counting it as "modified" would make the header claim a
|
|
52
|
+
// change the DATA section does not contain (CodeRabbit finding on #2414).
|
|
53
|
+
allowedEntityIds: null,
|
|
54
|
+
// Populated alongside `allowedEntityIds` below. `getVisibleEntityIds`
|
|
55
|
+
// excludes a hidden PRODUCT's own line from the closure, but `IFCREL*` is
|
|
56
|
+
// an unconditional root a few lines down and its bytes are copied verbatim
|
|
57
|
+
// by the source-iteration pass — nothing there filters a `#N` the closure
|
|
58
|
+
// just excluded out of the relationship's own attribute list. Kept because
|
|
59
|
+
// the closure walk's `isRefExcludedDuringClosureWalk` needs a notion of
|
|
60
|
+
// "hidden" that does not read `allowedEntityIds` — the set that walk is
|
|
61
|
+
// producing (#2398). The two OUTPUT passes no longer read this directly:
|
|
62
|
+
// they filter on `isOmittedFromOutput`, which subsumes it via
|
|
63
|
+
// `allowedEntityIds`.
|
|
64
|
+
hiddenProductIds: null,
|
|
65
|
+
// A relationship can name an excluded entity two ways that have nothing
|
|
66
|
+
// to do with each other: a `visibleOnly` hidden PRODUCT (`hiddenProductIds`,
|
|
67
|
+
// below), and a TOMBSTONED one — `editor.removeEntity` on a related object
|
|
68
|
+
// named by a relationship the deletion sweep below does not reach (that
|
|
69
|
+
// sweep only withholds an `IfcRelDefinesByProperties` when EVERY related
|
|
70
|
+
// object is gone, and only for that one relationship class). Left alone, a
|
|
71
|
+
// relationship still naming a deleted entity ships the identical `#N` with
|
|
72
|
+
// no `#N=` line, on a path with no `visibleOnly` involved at all (#2398).
|
|
73
|
+
// `effective.isDeleted` answers for every id, not just a precomputed set,
|
|
74
|
+
// so this predicate covers both sources without a second exclusion set.
|
|
75
|
+
//
|
|
76
|
+
// Declared here, ahead of the closure walk below, and passed into
|
|
77
|
+
// `collectReferencedEntityIds` as its `isRefExcluded` — rather than the
|
|
78
|
+
// walk inventing its own `!entityIndex.has` proxy for "deleted" that could
|
|
79
|
+
// disagree on an id that never existed in the file at all
|
|
80
|
+
// (maintainer-found regression on #2637: such an id blocked the bridge but
|
|
81
|
+
// did not stop the relationship's own line from shipping, dropping a
|
|
82
|
+
// VISIBLE sibling's pset while adding a fresh dangling ref). A closure over
|
|
83
|
+
// `pass.hiddenProductIds`, not a value snapshot — correct because nothing
|
|
84
|
+
// reads it before the closure walk assigns it just below.
|
|
85
|
+
//
|
|
86
|
+
// ## Why this is NOT the predicate the OUTPUT-line filter uses
|
|
87
|
+
//
|
|
88
|
+
// The name says walk, and only walk. The two passes that write a
|
|
89
|
+
// relationship's line ask `isOmittedFromOutput` (further below, derived
|
|
90
|
+
// from `pass.willBeEmitted`), which is strictly stronger — it also answers
|
|
91
|
+
// for the closure, for an unreadable source ref and for a geometry
|
|
92
|
+
// exclusion.
|
|
93
|
+
//
|
|
94
|
+
// This one CANNOT be `willBeEmitted`, and the difference is structural
|
|
95
|
+
// rather than stylistic: `willBeEmitted`'s first act is to consult
|
|
96
|
+
// `allowedEntityIds`, and `allowedEntityIds` is precisely what the call
|
|
97
|
+
// below is computing. Wiring it in here is circular: it would answer "not
|
|
98
|
+
// in the closure" as `false` while the closure is still being built and
|
|
99
|
+
// `true` for the same id afterwards.
|
|
100
|
+
//
|
|
101
|
+
// That is a genuine departure from the contract #2637 was closed on —
|
|
102
|
+
// `reference-collector.ts` still documents the bridge as taking the
|
|
103
|
+
// caller's OWN output predicate, "not two expressions that happened to
|
|
104
|
+
// agree". It has an OBSERVABLE consequence, not just a naming one: for an
|
|
105
|
+
// unreadable source ref this admits, the walk bridges through a
|
|
106
|
+
// relationship the output then withholds, leaving the relationship's other
|
|
107
|
+
// target in the closure with nothing naming it — an orphan, pinned by
|
|
108
|
+
// `unreadable-ref-dangling.test.ts` ("walk and output predicates diverge").
|
|
109
|
+
// The reverse direction is closed: every id this excludes,
|
|
110
|
+
// `isOmittedFromOutput` excludes too, so the #2548 leak cannot return.
|
|
111
|
+
isRefExcludedDuringClosureWalk: (id) => (pass.hiddenProductIds !== null && pass.hiddenProductIds.has(id))
|
|
112
|
+
|| pass.effective.isDeleted(id),
|
|
113
|
+
// Will THIS entity's own line ever land in the file? The same byte-range
|
|
114
|
+
// test `willBeEmitted` uses (defined further below) and the source-
|
|
115
|
+
// iteration pass's own skip at `entityRef.byteLength === 0` — a source
|
|
116
|
+
// entity with no bytes (a point-cloud / GLB "entity" from
|
|
117
|
+
// `createSyntheticDataStore`, not an overlay-created one) never gets a
|
|
118
|
+
// defining line written, source-iteration or otherwise, so a pset/attribute
|
|
119
|
+
// edit against it must not count as a modification either: the header
|
|
120
|
+
// would describe a change the file does not contain (out-of-scope finding
|
|
121
|
+
// in #2398). Also excludes a source-backed host the visible-only closure
|
|
122
|
+
// above drops — same reasoning, different reason the line never lands.
|
|
123
|
+
//
|
|
124
|
+
// And, like `willBeEmitted` below, excludes a geometry-classified SOURCE
|
|
125
|
+
// host under `includeGeometry: false`: the source-iteration pass's own
|
|
126
|
+
// `isGeometryEntity` skip (further below) drops that line too, so this
|
|
127
|
+
// predicate must agree or a geometry entity's attribute edit inflates the
|
|
128
|
+
// count over an omitted line (CodeRabbit finding on #2414). Guarded by
|
|
129
|
+
// `!deltaOnly` for the same reason `willBeEmitted` is: under `deltaOnly`
|
|
130
|
+
// the source-iteration pass — and its geometry skip — never runs at all,
|
|
131
|
+
// so a source entity's line is assumed to already exist in the file being
|
|
132
|
+
// patched, geometry or not.
|
|
133
|
+
isGeometryExcluded: (entityId, recordType) => excludeGeometry
|
|
134
|
+
&& isGeometryEntity(pass.effective.effectiveType(entityId, recordType)),
|
|
135
|
+
hasEmittableHostBytes: (entityId) => {
|
|
136
|
+
if (pass.allowedEntityIds !== null && !pass.allowedEntityIds.has(entityId))
|
|
137
|
+
return false;
|
|
138
|
+
const ref = pass.effective.get(entityId);
|
|
139
|
+
// The ref must be READABLE, not merely non-empty: a range this source
|
|
140
|
+
// cannot address decodes to the empty string, which used to be pushed
|
|
141
|
+
// into the file as a blank line while everything generated FOR the host
|
|
142
|
+
// still named it (#2491).
|
|
143
|
+
if (!ref || !pass.isReadableSourceRef(ref))
|
|
144
|
+
return false;
|
|
145
|
+
if (options.deltaOnly !== true && pass.isGeometryExcluded(entityId, ref.type))
|
|
146
|
+
return false;
|
|
147
|
+
return true;
|
|
148
|
+
},
|
|
149
|
+
/**
|
|
150
|
+
* Will this id have a defining STEP line in the output at all?
|
|
151
|
+
*
|
|
152
|
+
* The predicate is #2030's, and it is the right one: the pset, quantity and
|
|
153
|
+
* type-owned passes below are built from unfiltered mutation history, and
|
|
154
|
+
* what each of them needs to know before emitting an
|
|
155
|
+
* `IFCRELDEFINESBYPROPERTIES` is not "was this deleted" or "is this hidden"
|
|
156
|
+
* but the general question those are two answers to. A relation naming an
|
|
157
|
+
* expressId that never gets written is a dangling reference and an invalid
|
|
158
|
+
* file, whichever route dropped the line.
|
|
159
|
+
*
|
|
160
|
+
* #2030 had to reach for four things to answer it — a tombstone probe, a
|
|
161
|
+
* visibility set, a byte-range test on `completeIndex`, and a `getNewEntity`
|
|
162
|
+
* fallback whose stated purpose was that `deleteEntity` FORGOT an
|
|
163
|
+
* overlay-created entity instead of tombstoning it, so `isDeleted` could not
|
|
164
|
+
* answer for one. That fallback was documented on main as a workaround for
|
|
165
|
+
* exactly the model-level defect this branch fixes: `deleteEntity` now
|
|
166
|
+
* tombstones as well as forgets, so the effective index answers existence
|
|
167
|
+
* for source and overlay ids alike and the workaround collapses into it.
|
|
168
|
+
*
|
|
169
|
+
* The overlay branch does NOT disappear with it, and the distinction matters:
|
|
170
|
+
* `isOverlayCreated` is still load-bearing here, because a live
|
|
171
|
+
* overlay-created entity has no source bytes and would fail the byte-range
|
|
172
|
+
* test that a source record passes. What the tombstone fix removed is the
|
|
173
|
+
* need for that branch to double as a deletion detector.
|
|
174
|
+
*
|
|
175
|
+
* Deliberately unchanged from #2030 for source records under `deltaOnly` /
|
|
176
|
+
* `exportPropertiesOnly`: the source-iteration pass is skipped wholesale in
|
|
177
|
+
* those modes, yet a source entity still answers true here. A delta is a
|
|
178
|
+
* patch against a file that already has the line, not a standalone model.
|
|
179
|
+
*/
|
|
180
|
+
willBeEmitted: (entityId) => {
|
|
181
|
+
if (pass.allowedEntityIds !== null && !pass.allowedEntityIds.has(entityId))
|
|
182
|
+
return false;
|
|
183
|
+
// Undefined for a tombstoned id and for one neither the file nor the
|
|
184
|
+
// session ever had — a stale mutation must not conjure a relation either.
|
|
185
|
+
const ref = pass.effective.get(entityId);
|
|
186
|
+
if (!ref)
|
|
187
|
+
return false;
|
|
188
|
+
// An overlay-created record carries the placeholder byte range and is
|
|
189
|
+
// written by the new-entities pass; a source record needs real bytes.
|
|
190
|
+
if (pass.effective.isOverlayCreated(entityId)) {
|
|
191
|
+
// The overlay new-entities pass applies its OWN `isGeometryEntity`
|
|
192
|
+
// filter unconditionally — deltaOnly or not (see the comment at that
|
|
193
|
+
// loop, further below) — so this branch mirrors it without the
|
|
194
|
+
// deltaOnly carve-out the source branch gets.
|
|
195
|
+
return !pass.isGeometryExcluded(entityId, ref.type);
|
|
196
|
+
}
|
|
197
|
+
// Same readability test as `hasEmittableHostBytes`, and for the reason
|
|
198
|
+
// that predicate names: a ref this source cannot address is not a line
|
|
199
|
+
// this export can write, so nothing may be generated naming it (#2491).
|
|
200
|
+
if (!pass.isReadableSourceRef(ref))
|
|
201
|
+
return false;
|
|
202
|
+
// Mirrors `hasEmittableHostBytes`: under `deltaOnly` the source-
|
|
203
|
+
// iteration pass — and its geometry skip — never runs, so a source
|
|
204
|
+
// entity's line is assumed to already exist in the file being patched.
|
|
205
|
+
if (options.deltaOnly === true)
|
|
206
|
+
return true;
|
|
207
|
+
return !pass.isGeometryExcluded(entityId, ref.type);
|
|
208
|
+
},
|
|
209
|
+
// Under `deltaOnly` a nomination only becomes a count once some pass has
|
|
210
|
+
// actually written content that delivers THAT KIND of edit for the host —
|
|
211
|
+
// see `delta-modification-ledger.ts` for why the two are not the same event
|
|
212
|
+
// in that mode, and why the pair is (entity, kind) rather than the entity
|
|
213
|
+
// (#2462).
|
|
214
|
+
modifications: createModificationLedger(options.deltaOnly === true),
|
|
215
|
+
/**
|
|
216
|
+
* Hosts whose in-place named-attribute edits a FULL export may count, per
|
|
217
|
+
* kind. Filled by the collection passes below and read by the two passes
|
|
218
|
+
* that write a rewritten source line — see `in-place-nomination.ts` for why
|
|
219
|
+
* the nomination waits for the rewrite in this mode and not under
|
|
220
|
+
* `deltaOnly` (#2483).
|
|
221
|
+
*/
|
|
222
|
+
inPlaceNominees: {
|
|
223
|
+
attribute: new Set(),
|
|
224
|
+
georeferencing: new Set(),
|
|
225
|
+
},
|
|
226
|
+
// Collect entities that need to be modified or created
|
|
227
|
+
modifiedEntities: new Set(),
|
|
228
|
+
modifiedAttributes: new Map(),
|
|
229
|
+
newPropertySets: [],
|
|
230
|
+
newQuantitySets: [],
|
|
231
|
+
typeOwnedPsetNamesByEntity: new Map(),
|
|
232
|
+
typeOwnedPsetIdsByEntity: new Map(),
|
|
233
|
+
rewrittenEntityIds: new Set(),
|
|
234
|
+
rewrittenEntityLines: new Map(),
|
|
235
|
+
/** HasPropertySets slot value for an OVERLAY-CREATED type object, applied
|
|
236
|
+
* by the new-entities pass (there is no source line to rewrite). */
|
|
237
|
+
overlayTypeOwnedPsets: new Map(),
|
|
238
|
+
// Track property set IDs and relationship IDs to skip
|
|
239
|
+
skipPropertySetIds: new Set(),
|
|
240
|
+
skipRelationshipIds: new Set(),
|
|
241
|
+
// Written by the georeferencing pass and read again by the final
|
|
242
|
+
// assembly, which is why they are pass state and not phase locals.
|
|
243
|
+
newGeorefLines: [],
|
|
244
|
+
warnings: [],
|
|
245
|
+
};
|
|
246
|
+
// The same object, deliberately. See the file header.
|
|
247
|
+
return pass;
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=step-pass-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-pass-builder.js","sourceRoot":"","sources":["../src/step-pass-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type PropertySetContext } from './step-property-set-readers.js';
|
|
2
|
+
import type { ExportPass, StepExportOptions } from './step-exporter.js';
|
|
3
|
+
/** The mutation groupings `export()` builds before the collection phase runs. */
|
|
4
|
+
export interface PropertyMutationGroups {
|
|
5
|
+
readonly entityPropMutations: Map<number, Set<string>>;
|
|
6
|
+
readonly entityQuantMutations: Map<number, Set<string>>;
|
|
7
|
+
readonly relDefinesByEntity: Map<number, Array<{
|
|
8
|
+
relId: number;
|
|
9
|
+
psetId: number;
|
|
10
|
+
}>>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Collect what the overlay's property-set and quantity-set edits mean for this
|
|
14
|
+
* export: which sets to regenerate (`pass.newPropertySets` /
|
|
15
|
+
* `pass.newQuantitySets`), which source records to withhold
|
|
16
|
+
* (`pass.skipPropertySetIds` / `pass.skipRelationshipIds`), and which type
|
|
17
|
+
* objects need their `HasPropertySets` resolved later
|
|
18
|
+
* (`pass.typeOwnedPsetNamesByEntity`, `…IdsByEntity`, `pass.rewrittenEntityIds`).
|
|
19
|
+
*
|
|
20
|
+
* The caller owns the `mutationView && options.applyMutations !== false` gate;
|
|
21
|
+
* reaching here means overlay edits were both present and enabled.
|
|
22
|
+
*/
|
|
23
|
+
export declare function collectPropertyAndQuantitySetMutations(pass: ExportPass, options: StepExportOptions, groups: PropertyMutationGroups, ctx: PropertySetContext): void;
|
|
24
|
+
//# sourceMappingURL=step-property-set-collection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-property-set-collection.d.ts","sourceRoot":"","sources":["../src/step-property-set-collection.ts"],"names":[],"mappings":"AAkBA,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAExE,iFAAiF;AACjF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACpF;AAkBD;;;;;;;;;;GAUG;AACH,wBAAgB,sCAAsC,CACpD,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,sBAAsB,EAC9B,GAAG,EAAE,kBAAkB,GACtB,IAAI,CAqQN"}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { extractQuantitiesOnDemand } from '@ifc-lite/parser';
|
|
5
|
+
import { isTypeClass } from './type-owned-psets.js';
|
|
6
|
+
import { getPropertySetName, getPropertyIdsInSet, getTypeOwnedHasPropertySetIds, getElementQuantityName, } from './step-property-set-readers.js';
|
|
7
|
+
/**
|
|
8
|
+
* The store the extractor THIS class installed on a view currently reads
|
|
9
|
+
* (#2487). The extractor is installed once per view and closes over this box
|
|
10
|
+
* rather than over a store directly, so a later export of the same view against
|
|
11
|
+
* a different store re-points it instead of answering from the first file.
|
|
12
|
+
*
|
|
13
|
+
* A box, and not a `WeakSet` of views, because ownership has to reflect the
|
|
14
|
+
* CURRENT state and not the historical fact that an export once installed
|
|
15
|
+
* something. `setQuantityExtractor` is public: a caller may install its own
|
|
16
|
+
* afterwards, and a marker saying "the exporter owns this view" would then keep
|
|
17
|
+
* overwriting a caller-supplied base forever. With a box, the second export
|
|
18
|
+
* writes to a box nothing reads any more and never calls the setter again, so
|
|
19
|
+
* the caller's extractor stands. Weak, so it never keeps a session alive.
|
|
20
|
+
*/
|
|
21
|
+
const exporterQuantityBase = new WeakMap();
|
|
22
|
+
/**
|
|
23
|
+
* Collect what the overlay's property-set and quantity-set edits mean for this
|
|
24
|
+
* export: which sets to regenerate (`pass.newPropertySets` /
|
|
25
|
+
* `pass.newQuantitySets`), which source records to withhold
|
|
26
|
+
* (`pass.skipPropertySetIds` / `pass.skipRelationshipIds`), and which type
|
|
27
|
+
* objects need their `HasPropertySets` resolved later
|
|
28
|
+
* (`pass.typeOwnedPsetNamesByEntity`, `…IdsByEntity`, `pass.rewrittenEntityIds`).
|
|
29
|
+
*
|
|
30
|
+
* The caller owns the `mutationView && options.applyMutations !== false` gate;
|
|
31
|
+
* reaching here means overlay edits were both present and enabled.
|
|
32
|
+
*/
|
|
33
|
+
export function collectPropertyAndQuantitySetMutations(pass, options, groups, ctx) {
|
|
34
|
+
const { entityPropMutations, entityQuantMutations, relDefinesByEntity } = groups;
|
|
35
|
+
// `export()` narrowed this through the enclosing `if`, and the caller still
|
|
36
|
+
// owns that gate: reaching here means the view exists and mutations are
|
|
37
|
+
// enabled. Named once here rather than asserted at each of the six reads.
|
|
38
|
+
const mutationView = ctx.mutationView;
|
|
39
|
+
// Collect modified property sets and find original psets to skip
|
|
40
|
+
for (const [entityId, psetNames] of entityPropMutations) {
|
|
41
|
+
// A deleted entity must not cause the exporter to REMOVE anything.
|
|
42
|
+
//
|
|
43
|
+
// This is the other half of the dangling-reference class, and the half
|
|
44
|
+
// `willBeEmitted` cannot reach: that predicate guards what gets ADDED,
|
|
45
|
+
// and this loop's real work is deciding what gets SKIPPED. An edited
|
|
46
|
+
// pset is replaced wholesale, so its original id goes into
|
|
47
|
+
// `skipPropertySetIds` — but IFC exporters share one IfcPropertySet
|
|
48
|
+
// between entities, and once the host is deleted there is no
|
|
49
|
+
// replacement to take its place. The surviving entity's relation then
|
|
50
|
+
// points at a container nobody wrote. Verified against main at
|
|
51
|
+
// e6516991 (#2030's own merge): edit `Pset_WallCommon` on one of two
|
|
52
|
+
// walls sharing it, delete that wall, and the export drops #11 while
|
|
53
|
+
// #12 still names it. `retainSharedAtoms` rescues a shared ATOM one
|
|
54
|
+
// level down; nothing rescues the shared container.
|
|
55
|
+
//
|
|
56
|
+
// Leaving the pset alone makes it an orphan when nothing else
|
|
57
|
+
// references it, which is valid IFC. Its relation is dropped by the
|
|
58
|
+
// sweep above, which handles a plain delete too — no pset edit needed.
|
|
59
|
+
if (pass.effective.isDeleted(entityId))
|
|
60
|
+
continue;
|
|
61
|
+
pass.modifiedEntities.add(entityId);
|
|
62
|
+
// Same rule as the attribute loop below: an overlay-CREATED entity is
|
|
63
|
+
// emitted once, by the new-entities pass, and already counted in
|
|
64
|
+
// `newEntityCount` — as are the pset entities this loop goes on to
|
|
65
|
+
// generate. Only the COUNT is guarded; the entity still records its
|
|
66
|
+
// pset edits and still emits them.
|
|
67
|
+
//
|
|
68
|
+
// A NOMINATION, in both modes, never a count on its own: this site sees
|
|
69
|
+
// a pset NAME the session touched, not whether that name resolves to
|
|
70
|
+
// anything. `deletePropertySet(id, 'AName')` on a host that owns no such
|
|
71
|
+
// set reaches here and changes nothing at all, and used to put "1
|
|
72
|
+
// modification" in the header of a byte-identical file (#2474). What
|
|
73
|
+
// settles it is the generator's `recordEmitted` and the skip branches'
|
|
74
|
+
// `recordWithheld` below.
|
|
75
|
+
if (!pass.isOverlayCreated(entityId) && pass.hasEmittableHostBytes(entityId)) {
|
|
76
|
+
pass.modifications.nominate(entityId, 'property-set');
|
|
77
|
+
}
|
|
78
|
+
// Get the FULL mutated property sets for this entity (merged base + mutations)
|
|
79
|
+
const allPsets = mutationView.getForEntity(entityId);
|
|
80
|
+
const relevantPsets = allPsets.filter((pset) => psetNames.has(pset.name));
|
|
81
|
+
const relDefinedPsetNames = new Set();
|
|
82
|
+
if (relevantPsets.length > 0) {
|
|
83
|
+
pass.newPropertySets.push({ entityId, psets: relevantPsets });
|
|
84
|
+
}
|
|
85
|
+
// Find original property set IDs and relationship IDs to skip — look
|
|
86
|
+
// up only the IfcRelDefinesByProperties rels that reference this entity.
|
|
87
|
+
const rels = relDefinesByEntity.get(entityId);
|
|
88
|
+
if (rels) {
|
|
89
|
+
for (const { relId, psetId: relatedPsetId } of rels) {
|
|
90
|
+
// Check if this pset is one we're modifying
|
|
91
|
+
const psetName = getPropertySetName(ctx, relatedPsetId);
|
|
92
|
+
if (psetName) {
|
|
93
|
+
relDefinedPsetNames.add(psetName);
|
|
94
|
+
}
|
|
95
|
+
if (psetName && psetNames.has(psetName)) {
|
|
96
|
+
pass.skipRelationshipIds.add(relId);
|
|
97
|
+
pass.skipPropertySetIds.add(relatedPsetId);
|
|
98
|
+
// Also skip the individual properties in this pset
|
|
99
|
+
const propIds = getPropertyIdsInSet(ctx, relatedPsetId);
|
|
100
|
+
for (const propId of propIds) {
|
|
101
|
+
pass.skipPropertySetIds.add(propId);
|
|
102
|
+
}
|
|
103
|
+
// The other half of "did this edit change the file": a full export
|
|
104
|
+
// applies a set DELETION by leaving these lines out, and produces
|
|
105
|
+
// no replacement content to record an emission for. Without this
|
|
106
|
+
// the count would settle from the generator alone and a real
|
|
107
|
+
// deletion would stop counting along with the no-op one (#2474).
|
|
108
|
+
pass.modifications.recordWithheld(entityId, 'property-set');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (isTypeClass(pass.effective.typeOf(entityId))) {
|
|
113
|
+
const typeOwnedPsetIds = getTypeOwnedHasPropertySetIds(ctx, entityId, pass.effective);
|
|
114
|
+
const typeOwnedAffected = new Set();
|
|
115
|
+
for (const psetId of typeOwnedPsetIds) {
|
|
116
|
+
const psetName = getPropertySetName(ctx, psetId);
|
|
117
|
+
if (!psetName || !psetNames.has(psetName))
|
|
118
|
+
continue;
|
|
119
|
+
typeOwnedAffected.add(psetName);
|
|
120
|
+
pass.skipPropertySetIds.add(psetId);
|
|
121
|
+
const propIds = getPropertyIdsInSet(ctx, psetId);
|
|
122
|
+
for (const propId of propIds) {
|
|
123
|
+
pass.skipPropertySetIds.add(propId);
|
|
124
|
+
}
|
|
125
|
+
// No `recordWithheld` twin of the rel-defined branch above, and
|
|
126
|
+
// deliberately: a name that matches an OWNED pset is either dropped
|
|
127
|
+
// from the resolved list or swapped for the replacement this export
|
|
128
|
+
// generated, so slot 5 always comes back different and the repoint
|
|
129
|
+
// below records the emission for it. A second record here would be
|
|
130
|
+
// one no mutation can kill.
|
|
131
|
+
}
|
|
132
|
+
for (const psetName of psetNames) {
|
|
133
|
+
if (!relDefinedPsetNames.has(psetName)) {
|
|
134
|
+
typeOwnedAffected.add(psetName);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (typeOwnedAffected.size > 0) {
|
|
138
|
+
pass.typeOwnedPsetNamesByEntity.set(entityId, typeOwnedAffected);
|
|
139
|
+
pass.typeOwnedPsetIdsByEntity.set(entityId, typeOwnedPsetIds);
|
|
140
|
+
pass.rewrittenEntityIds.add(entityId);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Collect modified quantity sets (only if quantities are included)
|
|
145
|
+
if (options.includeQuantities === false)
|
|
146
|
+
entityQuantMutations.clear();
|
|
147
|
+
// A quantity overlay with nothing under it regenerates a source quantity
|
|
148
|
+
// set from the edited quantity ALONE, and the skip loop below then
|
|
149
|
+
// withholds the source lines that held its siblings (#2487). Unlike
|
|
150
|
+
// properties — whose base falls back to the `baseTable` the view was
|
|
151
|
+
// constructed with — quantities have only the opt-in
|
|
152
|
+
// `setQuantityExtractor`, so the default really is an empty base, and
|
|
153
|
+
// four in-tree callers plus every external embedder never set it.
|
|
154
|
+
//
|
|
155
|
+
// The exporter is the one place that always holds the missing half: it
|
|
156
|
+
// was handed the very store the view is an overlay ON. Supplying it here
|
|
157
|
+
// makes the loss impossible for every caller rather than for the callers
|
|
158
|
+
// we happened to find, and a view that resolves its own quantities (the
|
|
159
|
+
// viewer, MCP, the CLI headless backend) is never overwritten.
|
|
160
|
+
//
|
|
161
|
+
// The extractor closes over ONE store, and the view outlives this export.
|
|
162
|
+
// So it closes over a BOX this class owns instead: a second export of the
|
|
163
|
+
// same view against a DIFFERENT store re-points that box rather than
|
|
164
|
+
// reading the first store's quantities, which is the one way "install only
|
|
165
|
+
// when absent" could have answered from the wrong file. The setter is
|
|
166
|
+
// called at most once per view, so a caller that installs its own
|
|
167
|
+
// extractor at any point — before the first export or after it — keeps it.
|
|
168
|
+
//
|
|
169
|
+
// `hasQuantityBase` and `setQuantityExtractor` are probed, like every other
|
|
170
|
+
// optional view capability this class reaches for (`peekNextExpressId`,
|
|
171
|
+
// `getNewEntities`, `getEntityTypeMutation`): `MutablePropertyView` is
|
|
172
|
+
// published API arriving from a separately versioned package, and callers
|
|
173
|
+
// pass partial and duck-typed views. `hasQuantityBase` is newer than
|
|
174
|
+
// `setQuantityExtractor`, and without it there is no way to tell an empty
|
|
175
|
+
// base from a caller-supplied one — so an older view falls back to the
|
|
176
|
+
// pre-#2487 behaviour (no base supplied) rather than risk overwriting one.
|
|
177
|
+
const quantityView = mutationView;
|
|
178
|
+
if (entityQuantMutations.size > 0 &&
|
|
179
|
+
typeof quantityView.setQuantityExtractor === 'function' &&
|
|
180
|
+
typeof quantityView.hasQuantityBase === 'function') {
|
|
181
|
+
const installed = exporterQuantityBase.get(quantityView);
|
|
182
|
+
if (installed) {
|
|
183
|
+
// Ours, or a caller's that replaced ours: re-pointing the box is a
|
|
184
|
+
// no-op in the second case, and calling the setter again is what
|
|
185
|
+
// would not be.
|
|
186
|
+
installed.store = ctx.dataStore;
|
|
187
|
+
}
|
|
188
|
+
else if (!quantityView.hasQuantityBase()) {
|
|
189
|
+
const box = { store: ctx.dataStore };
|
|
190
|
+
exporterQuantityBase.set(quantityView, box);
|
|
191
|
+
quantityView.setQuantityExtractor((id) => extractQuantitiesOnDemand(box.store, id));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
for (const [entityId, qsetNames] of entityQuantMutations) {
|
|
195
|
+
// Same rule as the property loop above: a deleted entity removes nothing.
|
|
196
|
+
if (pass.effective.isDeleted(entityId))
|
|
197
|
+
continue;
|
|
198
|
+
pass.modifiedEntities.add(entityId);
|
|
199
|
+
// See the property loop above — an overlay-created entity is counted as
|
|
200
|
+
// new, not modified. The pset loop's own nomination no longer has to be
|
|
201
|
+
// excluded to avoid a double count: the ledger settles per ENTITY, so a
|
|
202
|
+
// host with both a pset and a qset edit counts once whatever is
|
|
203
|
+
// nominated. Nominating both buys the opposite — an accurate warning
|
|
204
|
+
// when the qset half is the half a delta cannot carry.
|
|
205
|
+
//
|
|
206
|
+
// Settled from effect like its property-set twin (#2474). The reachable
|
|
207
|
+
// no-op here is an UNDONE quantity-set creation whose name matches NO
|
|
208
|
+
// source set: `getMutations()` is append-only, so the `CREATE_QUANTITY`
|
|
209
|
+
// record still names the qset after `removeQuantityMutation` has taken
|
|
210
|
+
// it out of the overlay, and the generator below then finds nothing to
|
|
211
|
+
// write. The same undo against a COLLIDING name is not a no-op — it
|
|
212
|
+
// withholds the source set's lines — which is what the skip loop's
|
|
213
|
+
// `recordWithheld` below settles.
|
|
214
|
+
if (!pass.isOverlayCreated(entityId) && pass.hasEmittableHostBytes(entityId)) {
|
|
215
|
+
pass.modifications.nominate(entityId, 'quantity-set');
|
|
216
|
+
}
|
|
217
|
+
const allQsets = mutationView.getQuantitiesForEntity(entityId);
|
|
218
|
+
const relevantQsets = allQsets.filter((qset) => qsetNames.has(qset.name));
|
|
219
|
+
if (relevantQsets.length > 0) {
|
|
220
|
+
pass.newQuantitySets.push({ entityId, qsets: relevantQsets });
|
|
221
|
+
}
|
|
222
|
+
// The names this export is actually WRITING a replacement for. The
|
|
223
|
+
// affected-name set is not the same thing: it comes from the session's
|
|
224
|
+
// append-only mutation history, which keeps naming a quantity set after
|
|
225
|
+
// an undo has taken it back out of the overlay, so a Ctrl+Z used to
|
|
226
|
+
// withhold a source `IfcElementQuantity` that nothing regenerated.
|
|
227
|
+
//
|
|
228
|
+
// A quantity-set REMOVAL is the one case where withholding WITHOUT a
|
|
229
|
+
// replacement is the intent rather than the bug. It had no public
|
|
230
|
+
// populator when #2487 wrote that rule, so the rule read "always the
|
|
231
|
+
// bug"; `MutablePropertyView.deleteQuantitySet` (#2508) gives it one,
|
|
232
|
+
// and the deleted set is now asked for by name below. Without that, the
|
|
233
|
+
// panel hid a base quantity set the exported file still carried.
|
|
234
|
+
const regeneratedQsetNames = new Set(relevantQsets.map((qset) => qset.name));
|
|
235
|
+
// Skip original quantity set entities (IfcElementQuantity).
|
|
236
|
+
// Same per-entity index lookup as the property branch above.
|
|
237
|
+
const rels = relDefinesByEntity.get(entityId);
|
|
238
|
+
if (rels) {
|
|
239
|
+
for (const { relId, psetId: relatedPsetId } of rels) {
|
|
240
|
+
const qsetName = getElementQuantityName(ctx, relatedPsetId);
|
|
241
|
+
const deleted = qsetName !== null
|
|
242
|
+
&& mutationView.isQuantitySetDeleted?.(entityId, qsetName) === true;
|
|
243
|
+
if (qsetName && (regeneratedQsetNames.has(qsetName) || deleted)) {
|
|
244
|
+
pass.skipRelationshipIds.add(relId);
|
|
245
|
+
pass.skipPropertySetIds.add(relatedPsetId);
|
|
246
|
+
const quantIds = getPropertyIdsInSet(ctx, relatedPsetId);
|
|
247
|
+
for (const quantId of quantIds) {
|
|
248
|
+
pass.skipPropertySetIds.add(quantId);
|
|
249
|
+
}
|
|
250
|
+
// The withheld half, exactly as the rel-defined property branch
|
|
251
|
+
// above. This loop has just decided that #`relatedPsetId`, its
|
|
252
|
+
// quantity atoms and the relationship that attached them do NOT
|
|
253
|
+
// go into the file; whether anything is generated to take their
|
|
254
|
+
// place is decided elsewhere, and is not this branch's to assume.
|
|
255
|
+
//
|
|
256
|
+
// It IS assumable for the pset side and not here, and the
|
|
257
|
+
// difference is where the two read their base from.
|
|
258
|
+
// `getForEntity` merges the overlay over the base pset walk, so a
|
|
259
|
+
// name the session touched but did not change still resolves to
|
|
260
|
+
// source content and is regenerated.
|
|
261
|
+
// `getQuantitiesForEntity` merges the overlay over
|
|
262
|
+
// `quantityExtractor`, which is OPT-IN: it defaults to null, and
|
|
263
|
+
// several in-tree callers wire the property extractor beside it
|
|
264
|
+
// and not it (`cli/commands/mutate.ts`, `gym.ts`,
|
|
265
|
+
// `generate-spaces.ts`, `export/demesh-session.ts`), as does any
|
|
266
|
+
// external embedder of these two published packages. With no
|
|
267
|
+
// extractor the base is empty and the overlay is the only source,
|
|
268
|
+
// so a qset the overlay no longer holds resolves to nothing.
|
|
269
|
+
//
|
|
270
|
+
// Which makes this reachable through an UNDONE quantity-set
|
|
271
|
+
// creation whose name COLLIDES with a source set:
|
|
272
|
+
// `setQuantity(id, 'Qto_WallBaseQuantities', ...)` followed by the
|
|
273
|
+
// `removeQuantityMutation` that mutationSlice runs on Ctrl+Z. The
|
|
274
|
+
// append-only history still names the qset, so this branch
|
|
275
|
+
// withholds the source lines; the overlay is empty again, so
|
|
276
|
+
// nothing is regenerated. The export drops the source quantity set
|
|
277
|
+
// — a real change to the file, and a data-loss bug of its own
|
|
278
|
+
// (#2487) — and this call is what stops the count from calling it
|
|
279
|
+
// nothing.
|
|
280
|
+
pass.modifications.recordWithheld(entityId, 'quantity-set');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=step-property-set-collection.js.map
|