@ifc-lite/mcp 0.9.2 → 0.10.0

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.
Files changed (57) hide show
  1. package/README.md +32 -1
  2. package/dist/backend-query.d.ts +41 -0
  3. package/dist/backend-query.d.ts.map +1 -0
  4. package/dist/backend-query.js +454 -0
  5. package/dist/backend-query.js.map +1 -0
  6. package/dist/headless-backend.d.ts +4 -3
  7. package/dist/headless-backend.d.ts.map +1 -1
  8. package/dist/headless-backend.js +26 -212
  9. package/dist/headless-backend.js.map +1 -1
  10. package/dist/overlay.d.ts +171 -0
  11. package/dist/overlay.d.ts.map +1 -0
  12. package/dist/overlay.js +300 -0
  13. package/dist/overlay.js.map +1 -0
  14. package/dist/resources/providers.d.ts.map +1 -1
  15. package/dist/resources/providers.js +28 -27
  16. package/dist/resources/providers.js.map +1 -1
  17. package/dist/schema-tables.d.ts +42 -0
  18. package/dist/schema-tables.d.ts.map +1 -0
  19. package/dist/schema-tables.js +75 -0
  20. package/dist/schema-tables.js.map +1 -0
  21. package/dist/spatial-tree.d.ts +40 -0
  22. package/dist/spatial-tree.d.ts.map +1 -0
  23. package/dist/spatial-tree.js +70 -0
  24. package/dist/spatial-tree.js.map +1 -0
  25. package/dist/tools/bsdd.d.ts.map +1 -1
  26. package/dist/tools/bsdd.js +5 -16
  27. package/dist/tools/bsdd.js.map +1 -1
  28. package/dist/tools/clash.d.ts.map +1 -1
  29. package/dist/tools/clash.js +18 -13
  30. package/dist/tools/clash.js.map +1 -1
  31. package/dist/tools/diff-fingerprints.d.ts +110 -0
  32. package/dist/tools/diff-fingerprints.d.ts.map +1 -0
  33. package/dist/tools/diff-fingerprints.js +410 -0
  34. package/dist/tools/diff-fingerprints.js.map +1 -0
  35. package/dist/tools/diff.d.ts.map +1 -1
  36. package/dist/tools/diff.js +199 -26
  37. package/dist/tools/diff.js.map +1 -1
  38. package/dist/tools/discovery.d.ts.map +1 -1
  39. package/dist/tools/discovery.js +124 -36
  40. package/dist/tools/discovery.js.map +1 -1
  41. package/dist/tools/export.d.ts.map +1 -1
  42. package/dist/tools/export.js +31 -6
  43. package/dist/tools/export.js.map +1 -1
  44. package/dist/tools/mutate.d.ts.map +1 -1
  45. package/dist/tools/mutate.js +6 -9
  46. package/dist/tools/mutate.js.map +1 -1
  47. package/dist/tools/query.d.ts.map +1 -1
  48. package/dist/tools/query.js +129 -69
  49. package/dist/tools/query.js.map +1 -1
  50. package/dist/tools/util.d.ts +36 -7
  51. package/dist/tools/util.d.ts.map +1 -1
  52. package/dist/tools/util.js +90 -0
  53. package/dist/tools/util.js.map +1 -1
  54. package/dist/tools/validation.d.ts.map +1 -1
  55. package/dist/tools/validation.js +82 -26
  56. package/dist/tools/validation.js.map +1 -1
  57. package/package.json +14 -13
@@ -0,0 +1,410 @@
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
+ /**
5
+ * The MCP server's adapter onto the real `@ifc-lite/diff` engine (issue #1891).
6
+ *
7
+ * `model_diff` on its own answers "what changed at the type and identity
8
+ * level" — counts per type, GlobalIds added and removed. That answer is wrong
9
+ * in the most damaging way the moment the two models came from a from-scratch
10
+ * re-export: every GlobalId is new, so the whole model reads as
11
+ * deleted-and-added. `by_content: true` routes the same two loaded models
12
+ * through the engine's content-keyed matching pass instead.
13
+ *
14
+ * **Data scope only, on purpose.** Node has no geometry pipeline here: no
15
+ * meshes, so no world geometry hash and no bounding box. Rather than pretend
16
+ * otherwise the handler passes `scope: 'data'`, which is the honest description
17
+ * of what it can see — the engine then classifies every unambiguous 1:1 content
18
+ * match as `renamed` and reports every genuinely ambiguous group as a group,
19
+ * exactly as it does for a viewer session whose geometry hashing was
20
+ * unavailable.
21
+ *
22
+ * **Scope of the comparison: every `IfcObjectDefinition` in the model.** IFC's
23
+ * three `IfcRoot` branches are not equally comparable. An `IfcObjectDefinition`
24
+ * (every `IfcObject` — product, task, actor, control, resource, group — plus
25
+ * `IfcTypeObject` and `IfcContext`) is an independently identifiable thing. The
26
+ * other two branches are dependent and stay out:
27
+ *
28
+ * - `IfcRelationship`: its identity is its endpoints. Re-GUIDing an
29
+ * `IfcRelAggregates` while both ends are untouched is not a change anyone
30
+ * wants reported.
31
+ * - `IfcPropertyDefinition`: a property set's content is already folded into
32
+ * its owner's `dataHash`, so comparing it again would double-report every
33
+ * edited property — once on the element, once on the pset.
34
+ *
35
+ * Membership is decided from the inheritance chain of every bundled schema
36
+ * (IFC2X3 + IFC4 + IFC4X3), not from whether the columnar parser happened to
37
+ * put the entity in its `EntityTable`, and not from the IFC4 codegen pin alone.
38
+ * That distinction is the whole fix for a class of silent drop-outs: the table
39
+ * only holds the categories the viewer renders, so `IfcTask`, `IfcActor`,
40
+ * `IfcWorkPlan` and every other non-product `IfcObject` reported an empty
41
+ * GlobalId and vanished from the comparison entirely, even though their STEP
42
+ * records carry one. Those are read straight from the source record instead.
43
+ * (`by_entity` on the same tool still asks the store and so still misses them;
44
+ * that is pre-existing behaviour of a different flag, untouched here.)
45
+ *
46
+ * The same distinction closes the drop-out's mirror image. The parser fills the
47
+ * table's GlobalId column positionally, and for a resource entity slot 0 is not
48
+ * a GlobalId: an `IfcMaterial`, `IfcSurfaceStyle`, `IfcClassification` or
49
+ * `IfcProjectedCRS` was being compared under its *Name*, which put colliding
50
+ * keys into the comparison — a material and a surface style of the same name
51
+ * arriving as one entity. None of them is an `IfcRoot`, so the chain check
52
+ * leaves them out and the key set is unique again.
53
+ *
54
+ * ## Why this is a second copy, and what stops it drifting
55
+ *
56
+ * `packages/cli/src/commands/diff-engine.ts` and its `diff-scope.ts` are the
57
+ * same adapter, and the two must stay behaviourally identical — a fingerprint
58
+ * means nothing unless both producers compute it the same way. It is duplicated
59
+ * rather than shared because there is no honest home for it yet:
60
+ *
61
+ * - `@ifc-lite/cli` already depends on `@ifc-lite/mcp`, so this package cannot
62
+ * import from there without a cycle.
63
+ * - `@ifc-lite/diff` is deliberately store-agnostic and dependency-free; giving
64
+ * it a runtime dependency on `@ifc-lite/parser` to host an adapter would undo
65
+ * the property that lets the viewer, the CLI and this server each supply
66
+ * their own.
67
+ * - `@ifc-lite/parser` must not learn about diffing. The *scope* walk is close
68
+ * to parser-domain — it exists to compensate for the columnar parser's own
69
+ * `EntityTable` gaps — but which `IfcRoot` branches a comparison may speak
70
+ * for is diff policy, and exporting that from the most-depended-on package
71
+ * under a neutral name would only rename the problem.
72
+ *
73
+ * That leaves a fourth package both could depend on, which is a published
74
+ * artefact and a release decision rather than a review fix.
75
+ *
76
+ * Until then the agreement is **asserted, not assumed**. The copies previously
77
+ * relied on parallel suites (`diff.test.ts` here, `diff-content.test.ts` in the
78
+ * CLI) checking the same behaviour separately, which cannot detect a drift:
79
+ * fixing one copy and not the other passes both. It took hours to find out —
80
+ * #2001 moved the CLI's membership check to the cross-schema inheritance lookup
81
+ * and this copy stayed on the IFC4 codegen pin, silently dropping every IFC2X3
82
+ * and IFC4X3 object class outside that pin. `diff-fingerprints.test.ts` now runs
83
+ * *both* copies over the CLI's own fixtures and requires the same entities under
84
+ * the same type names, so the next divergence fails a build instead of shipping.
85
+ *
86
+ * The one thing this copy has and the CLI's does not is the optional
87
+ * `overlay` argument. It is additive, not a divergence: a `model_id` on this
88
+ * server names a session that may carry queued mutations, and the CLI diffs two
89
+ * files that cannot. Called without it, the two copies compute byte-identical
90
+ * fingerprints, which is what the paired tests check.
91
+ */
92
+ import { buildComponentFingerprints, buildDataFingerprint, } from '@ifc-lite/diff';
93
+ import { RelationshipType } from '@ifc-lite/data';
94
+ import { EntityExtractor, extractAllEntityAttributes, extractPropertiesOnDemand, extractQuantitiesOnDemand, extractRootAttributesFromEntity, getAttributeNamesAcrossSchemas, getInheritanceChainAcrossSchemas, } from '@ifc-lite/parser';
95
+ /**
96
+ * Classify one STEP type against the three `IfcRoot` branches.
97
+ *
98
+ * The chain has to come from **every bundled schema**, not from the parser's
99
+ * IFC4 codegen pin. `getInheritanceChainForEntity` answers an empty chain for
100
+ * any class the pin does not carry, and that is not a rare corner: IFC2X3 alone
101
+ * puts 23 `IfcObjectDefinition` classes there (`IfcMove`, `IfcOrderAction`,
102
+ * `IfcScheduleTimeControl`, `IfcSpaceProgram`, `IfcServiceLife`, …) and IFC4X3
103
+ * another 77. Judging those as `unknown` dropped the ones the `EntityTable`
104
+ * does not hold — real objects with real GlobalIds — and let through the
105
+ * IFC2X3-only *resource* classes it does hold under a `…STYLE` name, keyed on
106
+ * the Name in slot 0. Both are wrong answers about an IFC2X3 file, which is
107
+ * still most of what is in the wild.
108
+ *
109
+ * `unknown` is still not `dependent`: a vendor extension no schema declares has
110
+ * no chain to judge, so it keeps exactly the reach the `EntityTable` already
111
+ * gave it — which for a `…TYPE` class is a genuine GlobalId, since the parser's
112
+ * type-object branch is name-based and takes those in. Guessing that an
113
+ * unrecognised class is an `IfcObject` and reading its source record instead
114
+ * would cost one STEP extraction per row of every unrecognised bucket in the
115
+ * model, on every call, to reach entities almost no file has. The price of that
116
+ * choice is that a vendor `IfcRoot` subtype whose name does not end in `TYPE`
117
+ * stays uncompared.
118
+ *
119
+ * The one name the chain is not needed for is `IFCREL…`: that prefix is the
120
+ * parser's own rule for taking an unrecognised relationship into the table, and
121
+ * a relationship is excluded here whether any schema can confirm it or not.
122
+ * Without this, an unrecognised `IfcRelXxx` would be the single class of entity
123
+ * that got in through the relationship branch the comparison deliberately shuts.
124
+ */
125
+ function classifyType(typeKey) {
126
+ const upper = typeKey.toUpperCase();
127
+ const chain = getInheritanceChainAcrossSchemas(upper);
128
+ if (chain.length === 0) {
129
+ return {
130
+ role: upper.startsWith('IFCREL') ? 'dependent' : 'unknown',
131
+ name: typeKey,
132
+ typeObject: false,
133
+ };
134
+ }
135
+ // The chain holds the class itself plus its supertypes; which end the leaf
136
+ // sits at is the schema source's business — the union walk answers leaf→root
137
+ // and the IFC4 pin it falls back to answers root→leaf — so find it by name.
138
+ const name = chain.find((ancestor) => ancestor.toUpperCase() === upper) ?? typeKey;
139
+ const typeObject = chain.includes('IfcTypeObject');
140
+ if (!chain.includes('IfcRoot'))
141
+ return { role: 'dependent', name, typeObject };
142
+ return {
143
+ role: chain.includes('IfcObjectDefinition') ? 'independent' : 'dependent',
144
+ name,
145
+ typeObject,
146
+ };
147
+ }
148
+ /**
149
+ * Build one {@link EntityFingerprint} per `IfcObjectDefinition` in a store.
150
+ *
151
+ * `components` is populated as well as `dataHash`: the content pass's only
152
+ * defence against a `dataHash` collision retiring an unrelated add/delete pair
153
+ * is agreement on `ifcType` and on every component sub-hash, and the second
154
+ * check is inert unless both sides supply them (see the "Hash collisions"
155
+ * section of `docs/guide/model-diff.md`).
156
+ *
157
+ * Pass `overlay` to fingerprint the model *as the session has it* rather than
158
+ * as the file was parsed — tombstoned entities drop out, created ones join, and
159
+ * edited names, descriptions and property values are hashed at their new
160
+ * values. Omitting it (the CLI twin has no session to overlay) is the original
161
+ * store-only behaviour exactly.
162
+ */
163
+ export function buildModelFingerprints(store, overlay) {
164
+ const fingerprints = [];
165
+ const seen = new Set();
166
+ // One extractor for the whole model: it holds a buffer reference, and the
167
+ // source read below only fires for the (small) set of object types the
168
+ // EntityTable declines to hold.
169
+ const extractor = new EntityExtractor(store.source);
170
+ for (const [typeKey, ids] of store.entityIndex.byType) {
171
+ // Classified once per type rather than once per entity — the geometry
172
+ // buckets (IfcCartesianPoint, IfcPolyLoop, …) are the bulk of a real model
173
+ // and are dismissed here without touching a single row.
174
+ const type = classifyType(typeKey);
175
+ if (type.role === 'dependent')
176
+ continue;
177
+ for (const expressId of ids) {
178
+ if (seen.has(expressId))
179
+ continue;
180
+ seen.add(expressId);
181
+ // Queued for deletion: the session no longer has this entity, so the
182
+ // comparison must not keep reporting it as present and unchanged.
183
+ if (overlay?.deleted.has(expressId))
184
+ continue;
185
+ let globalId = store.entities.getGlobalId(expressId);
186
+ let source;
187
+ if (!globalId && type.role === 'independent') {
188
+ // In the model but not in the table: a schedule task, an actor, a work
189
+ // plan. Its GlobalId is in the STEP record, so read it there.
190
+ source = readRootAttributes(extractor, store, expressId);
191
+ globalId = source?.globalId ?? '';
192
+ }
193
+ // Still nothing: the entity is not an IfcRoot at all (a placement, a
194
+ // profile, a representation item), so it has no cross-model identity.
195
+ if (!globalId)
196
+ continue;
197
+ const tableType = store.entities.getTypeName(expressId);
198
+ // `getTypeName` answers 'Unknown' for a row the table never took in. The
199
+ // registry's own spelling is the honest answer, and it has to be a real
200
+ // type name: `ifcType` is hashed into the fingerprint and cross-checked
201
+ // on every content match, so 'Unknown' would pair a task with an actor.
202
+ const ifcType = source && (!tableType || tableType === 'Unknown') ? type.name : tableType;
203
+ const input = buildDataInput(store, expressId, ifcType, source, type.typeObject, overlay);
204
+ fingerprints.push({
205
+ key: globalId,
206
+ ifcType,
207
+ dataHash: buildDataFingerprint(input),
208
+ components: buildComponentFingerprints(input),
209
+ ref: expressId,
210
+ });
211
+ }
212
+ }
213
+ for (const entity of overlay?.created ?? []) {
214
+ const fingerprint = createdFingerprint(entity, overlay);
215
+ if (fingerprint)
216
+ fingerprints.push(fingerprint);
217
+ }
218
+ return fingerprints;
219
+ }
220
+ /**
221
+ * Fingerprint one overlay-created entity (`entity_create`).
222
+ *
223
+ * It goes through the same `classifyType` gate as a stored entity — a created
224
+ * `IfcRelAggregates` stays out of the comparison exactly as a parsed one does,
225
+ * and an unrecognised class keeps the same reach the store path gives it — and
226
+ * its `ifcType` is the registry's spelling. The caller may have authored the
227
+ * STEP-uppercase form, and `ifcType` is hashed and cross-checked on every
228
+ * content match, so `IFCWALL` would never pair with `IfcWall`.
229
+ *
230
+ * Its attributes come from the same place a stored entity's do: the creation
231
+ * payload is only the *base*, which `entity_set_attribute` overrides exactly as
232
+ * it overrides a parsed record (see {@link buildDataInput}). Create-then-rename
233
+ * is an ordinary two-step, and reading the frozen payload made this the one
234
+ * fingerprint in the model describing a value the session no longer holds — a
235
+ * rename went unseen, a clear was hashed as the name it cleared. All four
236
+ * hashed IfcRoot attributes: `Name` and `Description` have a payload behind them
237
+ * (STEP slots 0/2/3 are fixed across `IfcRoot` subtypes), `ObjectType` has none
238
+ * (slot 4 is `ApplicableOccurrence` on an `IfcTypeObject`) and so exists only as
239
+ * an override, and `Tag` is the same — an override only, and hashed only when
240
+ * the created class is an `IfcTypeObject` (issue #2021), matching the stored
241
+ * path exactly. Property sets and quantities never had the asymmetry — both
242
+ * were already read through the overlay.
243
+ *
244
+ * `predefinedType` and `typeAssignments` are necessarily absent: both are read
245
+ * through the store, which has no row for an entity that exists only in the
246
+ * overlay, and neither is reachable through `entity_set_attribute`. A created
247
+ * entity that would have matched on those alone therefore reports as added
248
+ * rather than matched, which is the safe direction.
249
+ */
250
+ function createdFingerprint(entity, overlay) {
251
+ const type = classifyType(entity.ifcType);
252
+ if (type.role === 'dependent')
253
+ return null;
254
+ const edited = overlay.attributes(entity.expressId);
255
+ const input = {
256
+ ifcType: type.name,
257
+ name: override(edited.get('Name'), entity.name),
258
+ description: override(edited.get('Description'), entity.description),
259
+ objectType: override(edited.get('ObjectType'), undefined),
260
+ tag: type.typeObject ? override(edited.get('Tag'), undefined) : undefined,
261
+ propertySets: overlay.propertySets(entity.expressId).map((set) => ({
262
+ name: set.name,
263
+ properties: set.properties.map((property) => ({ name: property.name, value: property.value })),
264
+ })),
265
+ quantitySets: overlay.quantitySets(entity.expressId).map((set) => ({
266
+ name: set.name,
267
+ quantities: set.quantities.map((quantity) => ({
268
+ name: quantity.name,
269
+ value: roundQuantity(quantity.value),
270
+ })),
271
+ })),
272
+ typeAssignments: [],
273
+ };
274
+ return {
275
+ key: entity.globalId,
276
+ ifcType: type.name,
277
+ dataHash: buildDataFingerprint(input),
278
+ components: buildComponentFingerprints(input),
279
+ ref: entity.expressId,
280
+ };
281
+ }
282
+ /** IfcRoot attributes straight from the entity's STEP record, for the rows the
283
+ * columnar `EntityTable` never took in. */
284
+ function readRootAttributes(extractor, store, expressId) {
285
+ const ref = store.entityIndex.byId.get(expressId);
286
+ if (!ref)
287
+ return undefined;
288
+ const entity = extractor.extractEntity(ref);
289
+ return entity ? extractRootAttributesFromEntity(entity) : undefined;
290
+ }
291
+ /**
292
+ * Assemble the canonical {@link DataFingerprintInput} for one entity.
293
+ *
294
+ * Mirrors the viewer adapter (`apps/viewer/src/lib/compare/buildFingerprints.ts`)
295
+ * minus its geometry-data filtering: that filter exists to keep placement data
296
+ * out of the *data* hash so a pure move reads as a geometry-only change, and
297
+ * this path has no geometry hash for such a change to land in. Dropping the
298
+ * filter here would make a moved element look unchanged.
299
+ */
300
+ function buildDataInput(store, expressId, ifcType,
301
+ /** Set only when the entity is absent from the columnar `EntityTable`, whose
302
+ * accessors then answer '' for every display attribute. */
303
+ source,
304
+ /** `IfcTypeObject` subtype? Gates `Tag` into the fingerprint (issue #2021). */
305
+ isTypeObject,
306
+ /** Set when the session has queued edits; its reads are base-merged, so it
307
+ * replaces the store read rather than being layered on top of it. */
308
+ overlay) {
309
+ const predefinedType = extractAllEntityAttributes(store, expressId).find((attribute) => attribute.name === 'PredefinedType')?.value;
310
+ // `Tag` for a TYPE OBJECT only. On an occurrence it is the authoring tool's
311
+ // element id, which changes across producers while the design does not, and
312
+ // `dataHash` is the content bucket key — hashing it there would stop the
313
+ // re-export matching this whole path exists for. On a type object it is the
314
+ // only thing separating same-named types with no geometry hash to fall back
315
+ // on (issue #2021, and `DataFingerprintInput.tag` for the full argument).
316
+ const storedTag = isTypeObject
317
+ ? attributeAcrossSchemas(store, expressId, ifcType, 'Tag')
318
+ : undefined;
319
+ const edited = overlay?.attributes(expressId);
320
+ const propertySets = (overlay
321
+ ? overlay.propertySets(expressId)
322
+ : extractPropertiesOnDemand(store, expressId)).map((set) => ({
323
+ name: set.name,
324
+ properties: set.properties.map((property) => ({ name: property.name, value: property.value })),
325
+ }));
326
+ const quantitySets = (overlay
327
+ ? overlay.quantitySets(expressId)
328
+ : extractQuantitiesOnDemand(store, expressId)).map((set) => ({
329
+ name: set.name,
330
+ quantities: set.quantities.map((quantity) => ({
331
+ name: quantity.name,
332
+ // Rounded to 4 dp, matching the viewer: re-exporting a model with
333
+ // sub-tolerance float jitter must not flip the data hash on an otherwise
334
+ // identical element, which on this path would cost the pair its match.
335
+ value: roundQuantity(quantity.value),
336
+ })),
337
+ }));
338
+ const typeAssignments = store.relationships
339
+ .getRelated(expressId, RelationshipType.DefinesByType, 'inverse')
340
+ .map((typeId) => ({
341
+ globalId: store.entities.getGlobalId(typeId) || undefined,
342
+ name: store.entities.getName(typeId) || undefined,
343
+ type: store.entities.getTypeName(typeId) || undefined,
344
+ }));
345
+ return {
346
+ ifcType,
347
+ // The hash sees all four attributes `entity_set_attribute` accepts, but
348
+ // `Tag` only on a type object (issue #2021) — on an occurrence it stays out
349
+ // of the hash, so an edit to it is deliberately invisible here. The overlay
350
+ // carries all four, so the selection happens at this consumer, which has a
351
+ // reason for it, rather than in the projection where it silently starved
352
+ // the readback (#2014).
353
+ name: override(edited?.get('Name'), store.entities.getName(expressId) || source?.name),
354
+ description: override(edited?.get('Description'), store.entities.getDescription(expressId) || source?.description),
355
+ objectType: override(edited?.get('ObjectType'), store.entities.getObjectType(expressId) || source?.objectType),
356
+ predefinedType: predefinedType != null ? String(predefinedType) : undefined,
357
+ tag: isTypeObject
358
+ ? override(edited?.get('Tag'), storedTag != null ? String(storedTag) : undefined)
359
+ : undefined,
360
+ propertySets,
361
+ quantitySets,
362
+ typeAssignments,
363
+ };
364
+ }
365
+ function roundQuantity(value) {
366
+ return Number.isFinite(value) ? Math.round(value * 1e4) / 1e4 : value;
367
+ }
368
+ /**
369
+ * One named attribute, read positionally through the **cross-schema** attribute
370
+ * list (issue #2021). The CLI's twin, and it must stay one.
371
+ *
372
+ * `extractAllEntityAttributes` names attributes through the parser's IFC4
373
+ * codegen pin, which answers an EMPTY list for a class the pin does not carry —
374
+ * so a `.find(name === 'Tag')` over it silently finds nothing on every
375
+ * IFC4X3-only type object (`IfcRailType`, `IfcTrackElementType`,
376
+ * `IfcSignalType`, …) while working perfectly on IFC2X3 and IFC4. That is a
377
+ * no-op nobody would notice: the entity is in scope, its class name is right,
378
+ * `isTypeObject` is right, and only the evidence is missing.
379
+ *
380
+ * This is the same pinned-registry family as the membership defect `#2001`
381
+ * fixed, and it has to be answered from the same place: the inheritance chain
382
+ * decides *whether* to read a `Tag`, so the attribute list that decides *where*
383
+ * it sits must span the same schemas. `getAttributeNamesAcrossSchemas` returns
384
+ * the pinned result unchanged for every class the pin does know, so this is
385
+ * additive — no IFC2X3 or IFC4 entity's hash moves because of it.
386
+ *
387
+ * Reads the raw STEP slot rather than reusing `extractAllEntityAttributes`'
388
+ * display normalization: this value is hashed, not shown, so `$` (absent) is
389
+ * the only case that needs interpreting and it arrives as null.
390
+ */
391
+ function attributeAcrossSchemas(store, expressId, ifcType, attributeName) {
392
+ const index = getAttributeNamesAcrossSchemas(ifcType).indexOf(attributeName);
393
+ if (index < 0)
394
+ return undefined;
395
+ const ref = store.entityIndex.byId.get(expressId);
396
+ if (!ref)
397
+ return undefined;
398
+ const raw = new EntityExtractor(store.source).extractEntity(ref)?.attributes?.[index];
399
+ return typeof raw === 'string' || typeof raw === 'number' ? String(raw) : undefined;
400
+ }
401
+ /**
402
+ * An overlay override wins whenever one exists, including when it is empty —
403
+ * `entity_set_attribute` with `''` clears the attribute, and falling back to
404
+ * the stored value there would hash the edit away.
405
+ */
406
+ function override(edited, stored) {
407
+ const value = edited !== undefined ? edited : stored;
408
+ return value ? value : undefined;
409
+ }
410
+ //# sourceMappingURL=diff-fingerprints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-fingerprints.js","sourceRoot":"","sources":["../../src/tools/diff-fingerprints.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/tools/diff.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AA+IvC,eAAO,MAAM,SAAS,EAAE,IAAI,EAA8B,CAAC"}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/tools/diff.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AA+VvC,eAAO,MAAM,SAAS,EAAE,IAAI,EAA8B,CAAC"}