@ifc-lite/export 3.0.0 → 3.1.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 (137) hide show
  1. package/README.md +34 -0
  2. package/dist/anonymize-export.d.ts +60 -0
  3. package/dist/anonymize-export.d.ts.map +1 -0
  4. package/dist/anonymize-export.js +117 -0
  5. package/dist/anonymize-export.js.map +1 -0
  6. package/dist/anonymize-placement.d.ts +71 -0
  7. package/dist/anonymize-placement.d.ts.map +1 -0
  8. package/dist/anonymize-placement.js +266 -0
  9. package/dist/anonymize-placement.js.map +1 -0
  10. package/dist/anonymize-scrub.d.ts +64 -0
  11. package/dist/anonymize-scrub.d.ts.map +1 -0
  12. package/dist/anonymize-scrub.js +312 -0
  13. package/dist/anonymize-scrub.js.map +1 -0
  14. package/dist/anonymize-types.d.ts +212 -0
  15. package/dist/anonymize-types.d.ts.map +1 -0
  16. package/dist/anonymize-types.js +5 -0
  17. package/dist/anonymize-types.js.map +1 -0
  18. package/dist/declared-property-type.d.ts +19 -8
  19. package/dist/declared-property-type.d.ts.map +1 -1
  20. package/dist/declared-property-type.js +31 -9
  21. package/dist/declared-property-type.js.map +1 -1
  22. package/dist/entity-type-sets.d.ts +47 -0
  23. package/dist/entity-type-sets.d.ts.map +1 -0
  24. package/dist/entity-type-sets.js +91 -0
  25. package/dist/entity-type-sets.js.map +1 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +5 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/merged-exporter.d.ts +19 -0
  31. package/dist/merged-exporter.d.ts.map +1 -1
  32. package/dist/merged-exporter.js +1 -1
  33. package/dist/merged-exporter.js.map +1 -1
  34. package/dist/parquet-exporter.d.ts +1 -3
  35. package/dist/parquet-exporter.d.ts.map +1 -1
  36. package/dist/parquet-exporter.js +11 -10
  37. package/dist/parquet-exporter.js.map +1 -1
  38. package/dist/property-value-serialization.d.ts +54 -0
  39. package/dist/property-value-serialization.d.ts.map +1 -0
  40. package/dist/property-value-serialization.js +134 -0
  41. package/dist/property-value-serialization.js.map +1 -0
  42. package/dist/reference-collector.d.ts +47 -0
  43. package/dist/reference-collector.d.ts.map +1 -1
  44. package/dist/reference-collector.js +88 -156
  45. package/dist/reference-collector.js.map +1 -1
  46. package/dist/related-entities.d.ts +10 -0
  47. package/dist/related-entities.d.ts.map +1 -0
  48. package/dist/related-entities.js +259 -0
  49. package/dist/related-entities.js.map +1 -0
  50. package/dist/schema-converter.d.ts.map +1 -1
  51. package/dist/schema-converter.js +46 -96
  52. package/dist/schema-converter.js.map +1 -1
  53. package/dist/source-ref-bounds.d.ts +3 -3
  54. package/dist/source-ref-bounds.js +3 -3
  55. package/dist/step-attribute-mutations.d.ts +0 -42
  56. package/dist/step-attribute-mutations.d.ts.map +1 -1
  57. package/dist/step-attribute-mutations.js +17 -147
  58. package/dist/step-attribute-mutations.js.map +1 -1
  59. package/dist/step-attribute-serializers.d.ts +41 -0
  60. package/dist/step-attribute-serializers.d.ts.map +1 -0
  61. package/dist/step-attribute-serializers.js +74 -0
  62. package/dist/step-attribute-serializers.js.map +1 -0
  63. package/dist/step-collection.d.ts +2 -2
  64. package/dist/step-collection.d.ts.map +1 -1
  65. package/dist/step-collection.js +38 -8
  66. package/dist/step-collection.js.map +1 -1
  67. package/dist/step-export-contexts.d.ts +5 -7
  68. package/dist/step-export-contexts.d.ts.map +1 -1
  69. package/dist/step-export-contexts.js +6 -7
  70. package/dist/step-export-contexts.js.map +1 -1
  71. package/dist/step-export-types.d.ts +27 -0
  72. package/dist/step-export-types.d.ts.map +1 -1
  73. package/dist/step-export-types.js.map +1 -1
  74. package/dist/step-exporter.d.ts +3 -3
  75. package/dist/step-exporter.js +4 -4
  76. package/dist/step-exporter.js.map +1 -1
  77. package/dist/step-geometry-types.d.ts +1 -17
  78. package/dist/step-geometry-types.d.ts.map +1 -1
  79. package/dist/step-geometry-types.js +51 -35
  80. package/dist/step-geometry-types.js.map +1 -1
  81. package/dist/step-georeferencing.d.ts +0 -10
  82. package/dist/step-georeferencing.d.ts.map +1 -1
  83. package/dist/step-georeferencing.js +46 -94
  84. package/dist/step-georeferencing.js.map +1 -1
  85. package/dist/step-header.d.ts +11 -1
  86. package/dist/step-header.d.ts.map +1 -1
  87. package/dist/step-header.js +2 -9
  88. package/dist/step-header.js.map +1 -1
  89. package/dist/step-map-unit.d.ts +90 -0
  90. package/dist/step-map-unit.d.ts.map +1 -0
  91. package/dist/step-map-unit.js +277 -0
  92. package/dist/step-map-unit.js.map +1 -0
  93. package/dist/step-overlay-attribute-overrides.d.ts +32 -0
  94. package/dist/step-overlay-attribute-overrides.d.ts.map +1 -0
  95. package/dist/step-overlay-attribute-overrides.js +83 -0
  96. package/dist/step-overlay-attribute-overrides.js.map +1 -0
  97. package/dist/step-overlay-entities.d.ts +3 -3
  98. package/dist/step-pass-builder.d.ts +4 -3
  99. package/dist/step-pass-builder.d.ts.map +1 -1
  100. package/dist/step-pass-builder.js.map +1 -1
  101. package/dist/step-property-set-collection.d.ts +24 -0
  102. package/dist/step-property-set-collection.d.ts.map +1 -0
  103. package/dist/step-property-set-collection.js +286 -0
  104. package/dist/step-property-set-collection.js.map +1 -0
  105. package/dist/step-property-set-generators.d.ts +20 -0
  106. package/dist/step-property-set-generators.d.ts.map +1 -0
  107. package/dist/step-property-set-generators.js +98 -0
  108. package/dist/step-property-set-generators.js.map +1 -0
  109. package/dist/step-property-set-index.d.ts +25 -0
  110. package/dist/step-property-set-index.d.ts.map +1 -0
  111. package/dist/step-property-set-index.js +69 -0
  112. package/dist/step-property-set-index.js.map +1 -0
  113. package/dist/step-property-set-readers.d.ts +158 -0
  114. package/dist/step-property-set-readers.d.ts.map +1 -0
  115. package/dist/step-property-set-readers.js +251 -0
  116. package/dist/step-property-set-readers.js.map +1 -0
  117. package/dist/step-property-sets.d.ts +2 -186
  118. package/dist/step-property-sets.d.ts.map +1 -1
  119. package/dist/step-property-sets.js +22 -674
  120. package/dist/step-property-sets.js.map +1 -1
  121. package/dist/step-serialization.d.ts +17 -51
  122. package/dist/step-serialization.d.ts.map +1 -1
  123. package/dist/step-serialization.js +19 -128
  124. package/dist/step-serialization.js.map +1 -1
  125. package/dist/step-source-iteration.d.ts +7 -5
  126. package/dist/step-source-iteration.d.ts.map +1 -1
  127. package/dist/step-source-iteration.js +3 -3
  128. package/dist/step-source-iteration.js.map +1 -1
  129. package/dist/subset-entity-reader.d.ts +103 -0
  130. package/dist/subset-entity-reader.d.ts.map +1 -0
  131. package/dist/subset-entity-reader.js +102 -0
  132. package/dist/subset-entity-reader.js.map +1 -0
  133. package/dist/subset-roots.d.ts +58 -0
  134. package/dist/subset-roots.d.ts.map +1 -0
  135. package/dist/subset-roots.js +103 -0
  136. package/dist/subset-roots.js.map +1 -0
  137. package/package.json +7 -7
package/README.md CHANGED
@@ -61,6 +61,39 @@ const blob = new Blob([stepText], { type: 'application/x-step' });
61
61
 
62
62
  `exportToStep` is the round-trip path for property edits. Edit via `@ifc-lite/mutations`, export with `applyMutations: true`, ship the resulting `.ifc` to whatever consumes IFC.
63
63
 
64
+ ## Anonymized isolated export — for bug reports
65
+
66
+ `collectRelatedEntities` + `exportAnonymizedSubset` pick a seed selection,
67
+ expand it by relationship context (host, openings/fillers, type, materials,
68
+ spatial containment), and export exactly that subset with every
69
+ project-identifying signal removed — while local placement rotations and
70
+ non-orthogonal cuts survive, so a parsing/geometry bug reproduction still
71
+ reproduces:
72
+
73
+ ```typescript
74
+ import { collectRelatedEntities, exportAnonymizedSubset } from '@ifc-lite/export';
75
+
76
+ const related = collectRelatedEntities(store, new Set([312])); // seed expressId(s)
77
+ const result = exportAnonymizedSubset(store, related.all);
78
+ await saveFile('anonymized.ifc', result.content);
79
+ ```
80
+
81
+ Every `AnonymizeOptions` field defaults to the maximally-scrubbed direction
82
+ (names pseudonymized, `GlobalId`s regenerated, property sets dropped, owner
83
+ history and georeferencing scrubbed) — `ObjectType` and material/style names
84
+ are pseudonymized right along with everything else under the default
85
+ `pseudonymizeAllNames`, and the STEP header's `originating_system` is
86
+ blanked under the default `scrubOwnerHistory`. What stays in the file by
87
+ decision: geometry and local placement rotations/non-orthogonal cuts (only
88
+ the root placement's translation is zeroed), structural entity types,
89
+ material *assignments* (which material, just not its original name), and
90
+ the header's `preprocessor_version` (from `IfcApplication`, defaults to
91
+ `'ifc-lite'`) — that is what makes the file useful for a bug repro. See the
92
+ [Exporting
93
+ Guide](https://ifclite.dev/docs/guide/exporting/#anonymized-isolated-export)
94
+ for the full option list, including how to opt back into authored names via
95
+ `--keep-other-names`.
96
+
64
97
  ## Apache Parquet — for analytics
65
98
 
66
99
  ```typescript
@@ -134,6 +167,7 @@ const lod1 = await generateLod1(bytes, { quality: 'medium' });
134
167
  ## Also exported
135
168
 
136
169
  - `MergedExporter` - merge several parsed models into one STEP export
170
+ - `collectRelatedEntities` / `exportAnonymizedSubset` - anonymized isolated export (see above)
137
171
  - `convertEntityType` / `convertStepLine` / `needsConversion` - IFC2X3 / IFC4 / IFC4X3 schema conversion helpers
138
172
  - `parseGLB` / `parseGLBToMeshData` / `extractGlbMapping` - GLB round-trip readers
139
173
 
@@ -0,0 +1,60 @@
1
+ /**
2
+ * `exportAnonymizedSubset` (#2934, the "anonymized isolated export" feature,
3
+ * plan A2 "Orchestrator" row): given a caller-picked `includedIds` selection
4
+ * (typically `RelatedEntities.all` from `related-entities.ts`), produces a
5
+ * STEP file that contains ONLY that subset, with every project-identifying
6
+ * signal removed while the geometry-relevant local transformations
7
+ * (rotations, non-orthogonal cuts) survive — so a parsing bug reproduction
8
+ * keeps reproducing.
9
+ *
10
+ * Per the plan's "Key mechanism choice": anonymization is expressed as
11
+ * mutations on a PRIVATE `MutablePropertyView` + `StoreEditor` — never a text
12
+ * post-pass — fed to the same `StepExporter` every other export mode uses.
13
+ * The overlay is scoped to one call: nothing here writes into `store`, and
14
+ * the view is discarded once `export()` returns, so two calls against the
15
+ * same `store` never interact.
16
+ *
17
+ * Call order matters and is fixed:
18
+ * 1. `pruneUnresolvableRelationships` — ahead of everything else, so a
19
+ * relationship this export cannot legally emit (its own mandatory,
20
+ * single-valued reference — or every member of a SET/LIST reference —
21
+ * names an entity outside `includedIds`) is removed from the ROOT set
22
+ * BEFORE `StepExporter` ever sees it, rather than discovered mid-closure
23
+ * and withheld with the generic `relationshipWithheldWarning`
24
+ * (`step-export-types.ts`). Reported distinctly, as
25
+ * `stats.prunedRelationshipIds`, for exactly this reason: a caller
26
+ * should be able to tell "this association is gone because the anonymizer
27
+ * pruned it" from "this association is gone for some other export-time
28
+ * reason" without grepping warning text.
29
+ * 2. `applyPlacementAnonymization` (`anonymize-placement.ts`) — root
30
+ * placement zeroing and georeferencing/address blanking.
31
+ * 3. `applyScrub` (`anonymize-scrub.ts`) — pseudonyms, GlobalId
32
+ * regeneration, owner-history scrub, `HasPropertySets` clearing.
33
+ * 4. `StepExporter.export` with `subsetEntityIds` set to the PRUNED id set
34
+ * and the header scrubbed (`author`/`organization`/`authorization`
35
+ * blanked, per the decision doc — `originating_system` and
36
+ * `preprocessor_version` are deliberately left alone).
37
+ *
38
+ * 2 and 3 both read `store`/`index` and queue their edits onto the SAME
39
+ * `view`/`editor` pair, so `StepExporter`'s closure walk (which reads the
40
+ * overlay through `effective-index.ts`) sees every edit from both passes at
41
+ * once — never two overlays that could disagree about the same entity.
42
+ */
43
+ import type { IfcDataStore } from '@ifc-lite/parser';
44
+ import type { AnonymizeOptions, AnonymizeResult } from './anonymize-types.js';
45
+ /**
46
+ * Export `includedIds` (plus infrastructure and their forward closure — see
47
+ * `subset-roots.ts`) as an anonymized STEP file. Every `AnonymizeOptions`
48
+ * field defaults to the maximally-scrubbed direction (see that interface's
49
+ * own doc), so calling this with no options at all is the intended common
50
+ * case, not a partial anonymization a caller must opt further into.
51
+ *
52
+ * Throws when `store` has no source bytes to read entity records from — a
53
+ * server-parsed or synthetic store with `source.byteLength === 0` has
54
+ * nothing for `readEntityArgs`/`StepExporter`'s source-iteration pass to
55
+ * read, and every module this orchestrator calls assumes source-backed
56
+ * records exist for at least the infrastructure/relationship entities it
57
+ * touches.
58
+ */
59
+ export declare function exportAnonymizedSubset(store: IfcDataStore, includedIds: ReadonlySet<number>, options?: AnonymizeOptions): AnonymizeResult;
60
+ //# sourceMappingURL=anonymize-export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymize-export.d.ts","sourceRoot":"","sources":["../src/anonymize-export.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,kBAAkB,CAAC;AAUrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAmD9E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,YAAY,EACnB,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAChC,OAAO,GAAE,gBAAqB,GAC7B,eAAe,CA0DjB"}
@@ -0,0 +1,117 @@
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 { MutablePropertyView, StoreEditor } from '@ifc-lite/mutations';
5
+ import { getEffectiveEntityIndex } from './effective-index.js';
6
+ import { getSubsetEntityIds, IFC_ROOT_TYPES } from './subset-roots.js';
7
+ import { readEntityArgs } from './subset-entity-reader.js';
8
+ import { refGroupFromArg, relationshipRefsSurviveExclusion } from './reference-collector.js';
9
+ import { applyPlacementAnonymization } from './anonymize-placement.js';
10
+ import { applyScrub } from './anonymize-scrub.js';
11
+ import { StepExporter } from './step-exporter.js';
12
+ /**
13
+ * Every id in `includedIds` whose EXPRESS type is an `IfcRel*` (an
14
+ * `IFC_ROOT_TYPES` member starting `IFCREL`), and whose own STEP record
15
+ * `relationshipRefsSurviveExclusion` would refuse to bridge or emit — i.e.
16
+ * one that names, in a single-valued attribute, an id NOT in `includedIds`
17
+ * (and not infrastructure), or whose every member of a SET/LIST attribute is
18
+ * likewise excluded. Everything ELSE in `includedIds` is left exactly as the
19
+ * caller passed it: this only ever REMOVES relationship ids, never adds or
20
+ * removes anything else.
21
+ *
22
+ * "Excluded" here is computed the same way the export's own closure would
23
+ * (`getSubsetEntityIds`'s `excludedIds` — every `IFC_ROOT_TYPES` /
24
+ * `IDENTIFYING_TYPES` id not in `includedIds`), so a relationship this
25
+ * function prunes is exactly one `StepExporter` would otherwise have had to
26
+ * withhold on its own.
27
+ */
28
+ function pruneUnresolvableRelationships(store, index, includedIds) {
29
+ const { excludedIds } = getSubsetEntityIds(index, includedIds);
30
+ const isExcluded = (id) => excludedIds.has(id);
31
+ const relationshipIds = [...includedIds]
32
+ .filter((id) => (index.typeOf(id) ?? '').startsWith('IFCREL'))
33
+ .sort((a, b) => a - b);
34
+ const prunedRelationshipIds = [];
35
+ const finalIncludedIds = new Set(includedIds);
36
+ for (const id of relationshipIds) {
37
+ const record = readEntityArgs(store, index, id);
38
+ // Unreadable (no source bytes, or an unparseable line): leave it for
39
+ // `StepExporter`'s own gates to decide, rather than guessing here.
40
+ if (!record)
41
+ continue;
42
+ const groups = record.args
43
+ .map(refGroupFromArg)
44
+ .filter((g) => g !== undefined);
45
+ if (!relationshipRefsSurviveExclusion(groups, isExcluded)) {
46
+ prunedRelationshipIds.push(id);
47
+ finalIncludedIds.delete(id);
48
+ }
49
+ }
50
+ return { prunedRelationshipIds, finalIncludedIds };
51
+ }
52
+ /**
53
+ * Export `includedIds` (plus infrastructure and their forward closure — see
54
+ * `subset-roots.ts`) as an anonymized STEP file. Every `AnonymizeOptions`
55
+ * field defaults to the maximally-scrubbed direction (see that interface's
56
+ * own doc), so calling this with no options at all is the intended common
57
+ * case, not a partial anonymization a caller must opt further into.
58
+ *
59
+ * Throws when `store` has no source bytes to read entity records from — a
60
+ * server-parsed or synthetic store with `source.byteLength === 0` has
61
+ * nothing for `readEntityArgs`/`StepExporter`'s source-iteration pass to
62
+ * read, and every module this orchestrator calls assumes source-backed
63
+ * records exist for at least the infrastructure/relationship entities it
64
+ * touches.
65
+ */
66
+ export function exportAnonymizedSubset(store, includedIds, options = {}) {
67
+ if (!store.source || store.source.byteLength <= 0) {
68
+ throw new Error('exportAnonymizedSubset: the store has no source bytes to export from '
69
+ + '(a server-parsed or synthetic store with source.byteLength === 0 '
70
+ + 'cannot be anonymized — this feature reads and rewrites the original '
71
+ + 'STEP records).');
72
+ }
73
+ // One private overlay for this call only — never the caller's own
74
+ // mutation view, and never written into `store`. See the module doc.
75
+ const view = new MutablePropertyView(null, 'anonymize');
76
+ const editor = new StoreEditor(store, view);
77
+ const index = getEffectiveEntityIndex(store, view, true);
78
+ const { prunedRelationshipIds, finalIncludedIds } = pruneUnresolvableRelationships(store, index, includedIds);
79
+ const placementResult = applyPlacementAnonymization(store, index, finalIncludedIds, editor, view, options);
80
+ const scrubResult = applyScrub(store, index, finalIncludedIds, view, options);
81
+ const exportResult = new StepExporter(store, view).export({
82
+ schema: store.schemaVersion,
83
+ subsetEntityIds: finalIncludedIds,
84
+ filename: options.filename ?? 'anonymized.ifc',
85
+ // Header scrub per the decision doc: author/organization/authorization
86
+ // blanked outright (never inherited from the source header — an empty
87
+ // string is a deliberate override, not "no override", per
88
+ // `step-header.ts`'s `??` wiring). `originating_system` is blanked under
89
+ // `scrubOwnerHistory` (a vendor build string such as `26.0.0 NOR FULL`
90
+ // encodes the licence region); `preprocessor_version` comes from
91
+ // `application`, left unset here (defaults to 'ifc-lite').
92
+ author: '',
93
+ organization: '',
94
+ authorization: '',
95
+ ...((options.scrubOwnerHistory ?? true) ? { originatingSystem: '' } : {}),
96
+ timeStamp: options.timeStamp,
97
+ guidRandom: options.guidRandom,
98
+ });
99
+ const includedRootEntityCount = [...finalIncludedIds]
100
+ .filter((id) => IFC_ROOT_TYPES.has(index.typeOf(id) ?? '')).length;
101
+ return {
102
+ content: exportResult.content,
103
+ guidMap: scrubResult.guidMap,
104
+ stats: {
105
+ entityCount: exportResult.stats.entityCount,
106
+ includedRootEntityCount,
107
+ prunedRelationshipIds,
108
+ zeroedPlacements: placementResult.zeroedPlacements,
109
+ warnings: [
110
+ ...placementResult.warnings,
111
+ ...scrubResult.warnings,
112
+ ...exportResult.stats.warnings,
113
+ ],
114
+ },
115
+ };
116
+ }
117
+ //# sourceMappingURL=anonymize-export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymize-export.js","sourceRoot":"","sources":["../src/anonymize-export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Root-placement zeroing and georeferencing/address blanking for the
3
+ * "anonymized isolated export" feature (#2934, plan section A4).
4
+ *
5
+ * Coordinates are the strongest identifying signal a reproduction carries —
6
+ * an absolute site position pins the model to a real address — but the
7
+ * ROTATION in the placement chain and any non-orthogonal cuts are exactly
8
+ * what a parsing bug reproduction needs to keep. So this module zeroes only
9
+ * the TRANSLATION of each root `IfcLocalPlacement` (`PlacementRelTo = $`),
10
+ * leaving `Axis`/`RefDirection` and every child placement untouched.
11
+ *
12
+ * **Clone, never rewrite.** `IfcCartesianPoint` and `IfcAxis2Placement3D` /
13
+ * `2D` records are routinely SHARED — two storeys placed at the same origin,
14
+ * or a template point reused across a model — so rewriting one in place would
15
+ * silently move every other placement that happens to reference the same
16
+ * point. Each zeroed root gets its own freshly-cloned point and axis via
17
+ * `StoreEditor.addEntity`, and only the root's `RelativePlacement` slot is
18
+ * repointed (`setPositionalAttribute`) to the clone. Nothing else in the
19
+ * model ever sees the edit.
20
+ *
21
+ * The same clone-and-repoint mechanism handles
22
+ * `IfcGeometricRepresentationContext.WorldCoordinateSystem` (a second, less
23
+ * obvious place a survey offset can live — #2934's plan A4 step 4) and is
24
+ * shared between both call sites as {@link zeroAxisPlacementSlot}.
25
+ *
26
+ * `IfcGridPlacement` / `IfcLinearPlacement` roots have no `RelativePlacement`
27
+ * → `IfcCartesianPoint` chain to clone against, so they are left untouched
28
+ * and reported as a warning rather than silently ignored or corrupted.
29
+ *
30
+ * Reads go through {@link readEntityArgs} / {@link attrIndex}
31
+ * (`subset-entity-reader.ts`) so a positional slot is always resolved BY
32
+ * EXPRESS NAME against the schema registry, never a hand-counted index that
33
+ * would silently drift the day an entity's attribute layout changes.
34
+ */
35
+ import type { IfcSourceBytes } from '@ifc-lite/parser';
36
+ import type { MutablePropertyView, StoreEditor } from '@ifc-lite/mutations';
37
+ import type { EffectiveEntityIndex } from './effective-index.js';
38
+ import type { AnonymizeOptions } from './anonymize-types.js';
39
+ /** The slice of {@link AnonymizeOptions} this module reads. */
40
+ export type PlacementAnonymizationOptions = Pick<AnonymizeOptions, 'zeroRootPlacement' | 'removeGeoreferencing'>;
41
+ /** What {@link applyPlacementAnonymization} did — folded into
42
+ * `AnonymizeResult.stats` by the orchestrator (`anonymize-export.ts`). */
43
+ export interface PlacementAnonymizationResult {
44
+ /** Each root `IfcLocalPlacement` this call zeroed, and the ORIGINAL
45
+ * (pre-zero) translation it removed. */
46
+ zeroedPlacements: {
47
+ expressId: number;
48
+ translation: number[];
49
+ }[];
50
+ /** Non-fatal notices: an unsupported root placement kind, an unreadable
51
+ * chain, multiple roots collapsing onto one origin, or a chain this walk
52
+ * bounded before it reached one (256-hop cap, see "Bounding walks over
53
+ * file-supplied references", AGENTS.md). */
54
+ warnings: string[];
55
+ }
56
+ /**
57
+ * Zero every root `IfcLocalPlacement` translation reachable from
58
+ * `includedIds`, zero `WorldCoordinateSystem` on every
59
+ * `IfcGeometricRepresentationContext` (always exported — it is an
60
+ * `INFRASTRUCTURE_TYPES` member, `reference-collector.ts`), and blank
61
+ * `IfcSite` / `IfcBuilding` georeferencing and address attributes.
62
+ *
63
+ * `store`/`index` are read from (never mutated directly) — every change goes
64
+ * through `editor` (new entities, positional overrides) or `view` (named
65
+ * attribute overrides), so the private `MutablePropertyView` overlay the
66
+ * orchestrator built stays the ONE place this export's edits live.
67
+ */
68
+ export declare function applyPlacementAnonymization(store: {
69
+ readonly source: IfcSourceBytes;
70
+ }, index: EffectiveEntityIndex, includedIds: ReadonlySet<number>, editor: StoreEditor, view: MutablePropertyView, opts?: PlacementAnonymizationOptions): PlacementAnonymizationResult;
71
+ //# sourceMappingURL=anonymize-placement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymize-placement.d.ts","sourceRoot":"","sources":["../src/anonymize-placement.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAE5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAK7D,+DAA+D;AAC/D,MAAM,MAAM,6BAA6B,GAAG,IAAI,CAC9C,gBAAgB,EAChB,mBAAmB,GAAG,sBAAsB,CAC7C,CAAC;AAEF;2EAC2E;AAC3E,MAAM,WAAW,4BAA4B;IAC3C;6CACyC;IACzC,gBAAgB,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IACjE;;;iDAG6C;IAC7C,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;CAAE,EAC1C,KAAK,EAAE,oBAAoB,EAC3B,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAChC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,mBAAmB,EACzB,IAAI,CAAC,EAAE,6BAA6B,GACnC,4BAA4B,CAc9B"}
@@ -0,0 +1,266 @@
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 { attrIndex, readEntityArgs } from './subset-entity-reader.js';
5
+ import { splitTopLevelArgs } from './step-argument-parser.js';
6
+ import { entityRef, stepReal } from './step-serialization.js';
7
+ const IFC_SITE_ADDRESS_ATTRIBUTES = [
8
+ 'RefLatitude',
9
+ 'RefLongitude',
10
+ 'RefElevation',
11
+ 'LandTitleNumber',
12
+ 'SiteAddress',
13
+ ];
14
+ /**
15
+ * Zero every root `IfcLocalPlacement` translation reachable from
16
+ * `includedIds`, zero `WorldCoordinateSystem` on every
17
+ * `IfcGeometricRepresentationContext` (always exported — it is an
18
+ * `INFRASTRUCTURE_TYPES` member, `reference-collector.ts`), and blank
19
+ * `IfcSite` / `IfcBuilding` georeferencing and address attributes.
20
+ *
21
+ * `store`/`index` are read from (never mutated directly) — every change goes
22
+ * through `editor` (new entities, positional overrides) or `view` (named
23
+ * attribute overrides), so the private `MutablePropertyView` overlay the
24
+ * orchestrator built stays the ONE place this export's edits live.
25
+ */
26
+ export function applyPlacementAnonymization(store, index, includedIds, editor, view, opts) {
27
+ const warnings = [];
28
+ const zeroedPlacements = [];
29
+ if (opts?.zeroRootPlacement !== false) {
30
+ zeroRootPlacements(store, index, includedIds, editor, warnings, zeroedPlacements);
31
+ zeroWorldCoordinateSystems(store, index, editor, warnings);
32
+ }
33
+ if (opts?.removeGeoreferencing !== false) {
34
+ blankSiteAndBuildingAddresses(index, includedIds, view);
35
+ }
36
+ return { zeroedPlacements, warnings };
37
+ }
38
+ function zeroRootPlacements(store, index, includedIds, editor, warnings, zeroedPlacements) {
39
+ // Memoised by placementId: the root a placement chain terminates at is a
40
+ // pure function of that id ("Bounding walks", AGENTS.md — global memoising
41
+ // is the right scope here, not path-scoped, since the answer never
42
+ // accumulates across callers), and many products in a subset commonly
43
+ // share one storey/building placement chain.
44
+ const rootCache = new Map();
45
+ const localPlacementRoots = new Set();
46
+ for (const id of includedIds) {
47
+ const rec = readEntityArgs(store, index, id);
48
+ if (!rec)
49
+ continue;
50
+ const objectPlacementIdx = attrIndex(rec.type, 'ObjectPlacement'); // declared on IfcProduct itself — slot order checked identical across ENTITIES_IFC2X3/_IFC4/_IFC4X3, no `schema` arg needed (#3309)
51
+ if (objectPlacementIdx < 0 || objectPlacementIdx >= rec.args.length)
52
+ continue;
53
+ const placementId = parseRef(rec.args[objectPlacementIdx]);
54
+ if (placementId === null)
55
+ continue;
56
+ let root = rootCache.get(placementId);
57
+ if (root === undefined) {
58
+ root = findRootPlacement(store, index, placementId, warnings);
59
+ rootCache.set(placementId, root);
60
+ }
61
+ if (!root)
62
+ continue;
63
+ if (root.rootType !== 'IFCLOCALPLACEMENT') {
64
+ warnings.push(`#${root.rootId} (${root.rootType}) is a root placement this export cannot zero — only ` +
65
+ `IfcLocalPlacement roots are supported; left untouched, so its absolute position is still in the file.`);
66
+ continue;
67
+ }
68
+ localPlacementRoots.add(root.rootId);
69
+ }
70
+ for (const rootId of localPlacementRoots) {
71
+ const translation = zeroAxisPlacementSlot(store, index, editor, rootId, 'IFCLOCALPLACEMENT', 'RelativePlacement', warnings);
72
+ if (translation)
73
+ zeroedPlacements.push({ expressId: rootId, translation });
74
+ }
75
+ if (zeroedPlacements.length > 1) {
76
+ warnings.push(`${zeroedPlacements.length} independent root placements were each zeroed to the origin; ` +
77
+ `their original relative offset from one another is not preserved.`);
78
+ }
79
+ }
80
+ /**
81
+ * Follow `PlacementRelTo` from `startPlacementId` while the current record is
82
+ * an `IfcLocalPlacement`, and return the terminal placement — either an
83
+ * `IfcLocalPlacement` with `PlacementRelTo = $` (a zeroable root) or a
84
+ * non-`IfcLocalPlacement` record the chain cannot continue past (an
85
+ * `IfcGridPlacement` / `IfcLinearPlacement`, reported by the caller). `null`
86
+ * when the chain could not be safely walked at all — a cycle, a hop past the
87
+ * 256-hop cap, or a link that does not resolve to a readable record — each
88
+ * case appending its own warning before returning.
89
+ */
90
+ function findRootPlacement(store, index, startPlacementId, warnings) {
91
+ const visited = new Set();
92
+ let currentId = startPlacementId;
93
+ let hops = 0;
94
+ for (;;) {
95
+ if (visited.has(currentId)) {
96
+ warnings.push(`Placement chain from #${startPlacementId} revisits #${currentId} (PlacementRelTo cycle); left untouched.`);
97
+ return null;
98
+ }
99
+ visited.add(currentId);
100
+ hops++;
101
+ if (hops > 256) {
102
+ warnings.push(`Placement chain from #${startPlacementId} exceeds 256 hops; left untouched rather than followed indefinitely.`);
103
+ return null;
104
+ }
105
+ const rec = readEntityArgs(store, index, currentId);
106
+ if (!rec) {
107
+ warnings.push(`Placement #${currentId} in the chain from #${startPlacementId} could not be read; left untouched.`);
108
+ return null;
109
+ }
110
+ if (rec.type !== 'IFCLOCALPLACEMENT') {
111
+ return { rootId: currentId, rootType: rec.type };
112
+ }
113
+ const relToIdx = attrIndex(rec.type, 'PlacementRelTo'); // `rec.type` is always IFCLOCALPLACEMENT here — order verified identical across all 3 schemas (#3309)
114
+ const parentId = relToIdx >= 0 && relToIdx < rec.args.length ? parseRef(rec.args[relToIdx]) : null;
115
+ if (parentId === null) {
116
+ return { rootId: currentId, rootType: rec.type };
117
+ }
118
+ currentId = parentId;
119
+ }
120
+ }
121
+ // ---------------------------------------------------------------------------
122
+ // IfcGeometricRepresentationContext.WorldCoordinateSystem
123
+ // ---------------------------------------------------------------------------
124
+ /**
125
+ * Every `IfcGeometricRepresentationContext` in the model — not just
126
+ * `includedIds` — because it is an `INFRASTRUCTURE_TYPES` member
127
+ * (`reference-collector.ts`) and is therefore always carried into the
128
+ * export regardless of what the caller selected. A plain, non-recursive
129
+ * pass over `index`: bounded by the model's own (small) count of
130
+ * representation contexts, not a reference walk that needs its own guard.
131
+ */
132
+ function zeroWorldCoordinateSystems(store, index, editor, warnings) {
133
+ for (const [id, ref] of index) {
134
+ if (index.effectiveType(id, ref.type) !== 'IFCGEOMETRICREPRESENTATIONCONTEXT')
135
+ continue;
136
+ // `TrueNorth` is deliberately left alone — only the origin offset is
137
+ // identifying; the survey rotation is exactly the kind of
138
+ // geometry-relevant signal this feature is written to keep (#2934).
139
+ zeroAxisPlacementSlot(store, index, editor, id, 'IFCGEOMETRICREPRESENTATIONCONTEXT', 'WorldCoordinateSystem', warnings);
140
+ }
141
+ }
142
+ // ---------------------------------------------------------------------------
143
+ // Shared clone-and-repoint primitive
144
+ // ---------------------------------------------------------------------------
145
+ /**
146
+ * Zero the `IfcCartesianPoint.Location` reachable through
147
+ * `entityId.<slotName>` → `IfcAxis2Placement2D|3D` → `Location`, by CLONING
148
+ * the point and the axis placement (never rewriting either — see the module
149
+ * doc) and repointing `entityId.<slotName>` at the clone.
150
+ *
151
+ * Returns the original (pre-zero) coordinates when a zero-clone was
152
+ * performed, or `null` when there was nothing to do: the slot is `$`, it
153
+ * does not resolve to an `IfcAxis2Placement2D/3D` → `IfcCartesianPoint`
154
+ * chain (warned), or the point is already at the origin.
155
+ */
156
+ function zeroAxisPlacementSlot(store, index, editor, entityId, entityType, slotName, warnings) {
157
+ const slotIdx = attrIndex(entityType, slotName); // entityType/slotName always fixed literals, order verified identical across all 3 schemas (#3309)
158
+ if (slotIdx < 0)
159
+ return null;
160
+ const rec = readEntityArgs(store, index, entityId);
161
+ if (!rec || slotIdx >= rec.args.length)
162
+ return null;
163
+ const axisId = parseRef(rec.args[slotIdx]);
164
+ if (axisId === null)
165
+ return null; // `$` — nothing placed here to zero.
166
+ const axisRec = readEntityArgs(store, index, axisId);
167
+ if (!axisRec) {
168
+ warnings.push(`#${entityId} ${slotName} references #${axisId}, which could not be read; left untouched.`);
169
+ return null;
170
+ }
171
+ const is3D = axisRec.type === 'IFCAXIS2PLACEMENT3D';
172
+ const is2D = axisRec.type === 'IFCAXIS2PLACEMENT2D';
173
+ if (!is3D && !is2D) {
174
+ warnings.push(`#${entityId} ${slotName} resolves to #${axisId} (${axisRec.type}), not an ` +
175
+ `IfcAxis2Placement2D/3D; left untouched.`);
176
+ return null;
177
+ }
178
+ const locationIdx = attrIndex(axisRec.type, 'Location'); // IfcAxis2Placement2D/3D — same verified-stable guarantee as slotIdx above (#3309)
179
+ const pointId = locationIdx >= 0 ? parseRef(axisRec.args[locationIdx]) : null;
180
+ if (pointId === null)
181
+ return null;
182
+ const pointRec = readEntityArgs(store, index, pointId);
183
+ if (!pointRec || pointRec.type !== 'IFCCARTESIANPOINT') {
184
+ warnings.push(`#${entityId} ${slotName} → #${axisId} → Location #${pointId} is not an IfcCartesianPoint; left untouched.`);
185
+ return null;
186
+ }
187
+ const coords = parseCoordinateList(pointRec.args[0]);
188
+ if (!coords || coords.every((c) => c === 0))
189
+ return null; // absent, or already at the origin.
190
+ // Clone the point at the origin, same dimensionality.
191
+ const newPoint = editor.addEntity('IfcCartesianPoint', [coords.map(() => stepReal(0))]);
192
+ // Clone the axis placement, carrying `Axis`/`RefDirection` over VERBATIM —
193
+ // the rotation is exactly what a reproduction needs to keep.
194
+ const newAxisAttrs = [entityRef(newPoint.expressId)];
195
+ for (const name of is3D ? ['Axis', 'RefDirection'] : ['RefDirection']) {
196
+ const idx = attrIndex(axisRec.type, name); // same guarantee as locationIdx above (#3309)
197
+ newAxisAttrs.push(idx >= 0 ? verbatimAttr(axisRec.args[idx]) : null);
198
+ }
199
+ const newAxis = editor.addEntity(is3D ? 'IfcAxis2Placement3D' : 'IfcAxis2Placement2D', newAxisAttrs);
200
+ editor.setPositionalAttribute(entityId, slotIdx, entityRef(newAxis.expressId));
201
+ return coords;
202
+ }
203
+ // ---------------------------------------------------------------------------
204
+ // IfcSite / IfcBuilding georeferencing + address blanking
205
+ // ---------------------------------------------------------------------------
206
+ /**
207
+ * `IfcMapConversion(Scaled)` / `IfcProjectedCRS` need no handling here: they
208
+ * are `IDENTIFYING_TYPES` (`subset-roots.ts`), excluded from the export
209
+ * outright, and only inverse-referenced from these fields — so they are
210
+ * simply absent from the output once nothing still points at them.
211
+ *
212
+ * All six blanked slots are OPTIONAL but not all STRING-typed — a LIST OF
213
+ * INTEGER, a REAL, and two entity refs among them — so `$` (via
214
+ * `setPositionalAttribute` + `null`) is the only blank valid for all of them;
215
+ * `setAttribute` only emits a quoted STEP string and would corrupt the rest.
216
+ */
217
+ function blankSiteAndBuildingAddresses(index, includedIds, view) {
218
+ for (const id of includedIds) {
219
+ const type = index.typeOf(id);
220
+ if (type === 'IFCSITE') {
221
+ for (const attr of IFC_SITE_ADDRESS_ATTRIBUTES) {
222
+ const idx = attrIndex(type, attr); // IfcSite's address slots verified stable across all 3 schemas (#3309)
223
+ if (idx >= 0)
224
+ view.setPositionalAttribute(id, idx, null);
225
+ }
226
+ }
227
+ else if (type === 'IFCBUILDING') {
228
+ const idx = attrIndex(type, 'BuildingAddress'); // verified stable across all 3 schemas (#3309)
229
+ if (idx >= 0)
230
+ view.setPositionalAttribute(id, idx, null);
231
+ }
232
+ }
233
+ }
234
+ // ---------------------------------------------------------------------------
235
+ // Token helpers
236
+ // ---------------------------------------------------------------------------
237
+ /** `"#42"` → `42`; anything else (`$`, a literal, missing) → `null`. */
238
+ function parseRef(token) {
239
+ if (!token)
240
+ return null;
241
+ const match = /^#(\d+)$/.exec(token.trim());
242
+ return match ? Number(match[1]) : null;
243
+ }
244
+ /** Carry a verbatim STEP token (`$` or `#N`) into `StoreEditor.addEntity`'s
245
+ * attribute conventions: `$` becomes `null`, a reference token is passed
246
+ * through as the `"#42"` string `addEntity` recognizes. */
247
+ function verbatimAttr(token) {
248
+ if (!token)
249
+ return null;
250
+ const trimmed = token.trim();
251
+ return trimmed === '$' || trimmed === '' ? null : trimmed;
252
+ }
253
+ /** Parse an `IfcCartesianPoint.Coordinates` token — a parenthesized,
254
+ * top-level-comma-separated REAL list (`(1000.,2000.,30.)`) — into numbers.
255
+ * `null` when the token isn't a list, or any member doesn't parse as a
256
+ * finite number. */
257
+ function parseCoordinateList(token) {
258
+ if (!token)
259
+ return null;
260
+ const trimmed = token.trim();
261
+ if (!trimmed.startsWith('(') || !trimmed.endsWith(')'))
262
+ return null;
263
+ const numbers = splitTopLevelArgs(trimmed.slice(1, -1)).map((part) => Number(part.trim()));
264
+ return numbers.every((n) => Number.isFinite(n)) ? numbers : null;
265
+ }
266
+ //# sourceMappingURL=anonymize-placement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anonymize-placement.js","sourceRoot":"","sources":["../src/anonymize-placement.ts"],"names":[],"mappings":"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"}