@ifc-lite/viewer 1.17.6 → 1.19.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 (156) hide show
  1. package/.turbo/turbo-build.log +20 -15
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +949 -0
  4. package/dist/assets/{basketViewActivator-86rgogji.js → basketViewActivator-RZy5c3Td.js} +1 -1
  5. package/dist/assets/decode-worker-Collf_X_.js +1320 -0
  6. package/dist/assets/{exporters-CcPS9MK5.js → exporters-BraHBeoi.js} +4194 -3025
  7. package/dist/assets/{geometry.worker-BFUYA08u.js → geometry.worker-DQEZB2rB.js} +1 -1
  8. package/dist/assets/ifc-lite_bg-4yUkDRD8.wasm +0 -0
  9. package/dist/assets/index-0XpVr_S5.css +1 -0
  10. package/dist/assets/{index-Bfms9I4A.js → index-BOi3BuUI.js} +46423 -31181
  11. package/dist/assets/index-XwKzDuw6.js +22 -0
  12. package/dist/assets/{native-bridge-DUyLCMZS.js → native-bridge-CpBeOPQa.js} +1 -1
  13. package/dist/assets/sandbox-Baez7n-t.js +9682 -0
  14. package/dist/assets/{server-client-BuZK7OST.js → server-client-BB6cMAXE.js} +1 -1
  15. package/dist/assets/{wasm-bridge-JsqEGDV8.js → wasm-bridge-CAYCUHbE.js} +1 -1
  16. package/dist/index.html +6 -6
  17. package/package.json +11 -10
  18. package/src/apache-arrow.d.ts +30 -0
  19. package/src/components/viewer/AddElementPanel.tsx +758 -0
  20. package/src/components/viewer/BulkPropertyEditor.tsx +7 -0
  21. package/src/components/viewer/ChatPanel.tsx +64 -2
  22. package/src/components/viewer/CommandPalette.tsx +56 -7
  23. package/src/components/viewer/EntityContextMenu.tsx +168 -4
  24. package/src/components/viewer/ExportChangesButton.tsx +25 -5
  25. package/src/components/viewer/ExportDialog.tsx +19 -1
  26. package/src/components/viewer/MainToolbar.tsx +73 -12
  27. package/src/components/viewer/PointCloudPanel.tsx +174 -0
  28. package/src/components/viewer/PropertiesPanel.tsx +222 -22
  29. package/src/components/viewer/SearchInline.tsx +669 -0
  30. package/src/components/viewer/SearchModal.filter.builder.tsx +766 -0
  31. package/src/components/viewer/SearchModal.filter.tsx +514 -0
  32. package/src/components/viewer/SearchModal.text.tsx +388 -0
  33. package/src/components/viewer/SearchModal.tsx +235 -0
  34. package/src/components/viewer/ToolOverlays.tsx +5 -0
  35. package/src/components/viewer/ViewerLayout.tsx +24 -4
  36. package/src/components/viewer/Viewport.tsx +29 -2
  37. package/src/components/viewer/ViewportContainer.tsx +45 -5
  38. package/src/components/viewer/ViewportOverlays.tsx +13 -2
  39. package/src/components/viewer/annotations/AnnotationDropInput.tsx +203 -0
  40. package/src/components/viewer/annotations/AnnotationLayer.tsx +287 -0
  41. package/src/components/viewer/annotations/AnnotationPin.tsx +90 -0
  42. package/src/components/viewer/annotations/AnnotationPopover.tsx +296 -0
  43. package/src/components/viewer/bcf/BCFTopicDetail.tsx +1 -1
  44. package/src/components/viewer/lists/ListPanel.tsx +14 -21
  45. package/src/components/viewer/properties/RawStepCard.tsx +332 -0
  46. package/src/components/viewer/properties/RawStepRow.tsx +261 -0
  47. package/src/components/viewer/properties/ScheduleCard.tsx +224 -0
  48. package/src/components/viewer/properties/TaskEditCard.tsx +510 -0
  49. package/src/components/viewer/properties/raw-step-format.ts +193 -0
  50. package/src/components/viewer/schedule/AnimationSettingsPopover.tsx +542 -0
  51. package/src/components/viewer/schedule/GanttDependencyArrows.tsx +89 -0
  52. package/src/components/viewer/schedule/GanttDragTooltip.tsx +48 -0
  53. package/src/components/viewer/schedule/GanttEmptyState.tsx +97 -0
  54. package/src/components/viewer/schedule/GanttPanel.tsx +295 -0
  55. package/src/components/viewer/schedule/GanttTaskBar.tsx +199 -0
  56. package/src/components/viewer/schedule/GanttTaskTree.tsx +250 -0
  57. package/src/components/viewer/schedule/GanttTimeline.tsx +305 -0
  58. package/src/components/viewer/schedule/GanttToolbar.tsx +406 -0
  59. package/src/components/viewer/schedule/GenerateAdvancedPanel.tsx +147 -0
  60. package/src/components/viewer/schedule/GenerateScheduleDialog.tsx +392 -0
  61. package/src/components/viewer/schedule/HeightStrategyPanel.tsx +120 -0
  62. package/src/components/viewer/schedule/generate-schedule.test.ts +439 -0
  63. package/src/components/viewer/schedule/generate-schedule.ts +648 -0
  64. package/src/components/viewer/schedule/schedule-animator.test.ts +452 -0
  65. package/src/components/viewer/schedule/schedule-animator.ts +488 -0
  66. package/src/components/viewer/schedule/schedule-selection.test.ts +148 -0
  67. package/src/components/viewer/schedule/schedule-selection.ts +163 -0
  68. package/src/components/viewer/schedule/schedule-utils.ts +223 -0
  69. package/src/components/viewer/schedule/useConstructionSequence.ts +156 -0
  70. package/src/components/viewer/schedule/useGanttBarDrag.test.ts +90 -0
  71. package/src/components/viewer/schedule/useGanttBarDrag.ts +305 -0
  72. package/src/components/viewer/schedule/useGanttSelection3DHighlight.ts +152 -0
  73. package/src/components/viewer/schedule/useOverlayCompositor.ts +108 -0
  74. package/src/components/viewer/selectionHandlers.ts +446 -0
  75. package/src/components/viewer/tools/AddElementOverlay.tsx +581 -0
  76. package/src/components/viewer/useDuplicateShortcut.ts +77 -0
  77. package/src/components/viewer/useMouseControls.ts +9 -1
  78. package/src/components/viewer/usePointCloudLifecycle.ts +64 -0
  79. package/src/components/viewer/usePointCloudSync.ts +98 -0
  80. package/src/hooks/ingest/pointCloudIngest.ts +391 -0
  81. package/src/hooks/ingest/viewerModelIngest.ts +32 -3
  82. package/src/hooks/useIfcFederation.ts +72 -3
  83. package/src/hooks/useIfcLoader.ts +89 -13
  84. package/src/hooks/useKeyboardShortcuts.ts +25 -0
  85. package/src/hooks/useSandbox.ts +1 -1
  86. package/src/hooks/useSearchIndex.ts +125 -0
  87. package/src/index.css +66 -0
  88. package/src/lib/llm/system-prompt.test.ts +14 -0
  89. package/src/lib/llm/system-prompt.ts +102 -1
  90. package/src/lib/llm/types.ts +6 -0
  91. package/src/lib/recent-files.ts +38 -4
  92. package/src/lib/scripts/templates/bim-globals.d.ts +136 -114
  93. package/src/lib/scripts/templates/construction-schedule.ts +223 -0
  94. package/src/lib/scripts/templates.ts +7 -0
  95. package/src/lib/search/common-ifc-types.ts +36 -0
  96. package/src/lib/search/filter-evaluate.test.ts +537 -0
  97. package/src/lib/search/filter-evaluate.ts +610 -0
  98. package/src/lib/search/filter-rules.test.ts +119 -0
  99. package/src/lib/search/filter-rules.ts +198 -0
  100. package/src/lib/search/filter-schema.test.ts +233 -0
  101. package/src/lib/search/filter-schema.ts +146 -0
  102. package/src/lib/search/recent-searches.test.ts +116 -0
  103. package/src/lib/search/recent-searches.ts +93 -0
  104. package/src/lib/search/result-export.test.ts +101 -0
  105. package/src/lib/search/result-export.ts +104 -0
  106. package/src/lib/search/saved-filters.test.ts +118 -0
  107. package/src/lib/search/saved-filters.ts +154 -0
  108. package/src/lib/search/tier0-scan.test.ts +196 -0
  109. package/src/lib/search/tier0-scan.ts +237 -0
  110. package/src/lib/search/tier1-index.test.ts +242 -0
  111. package/src/lib/search/tier1-index.ts +448 -0
  112. package/src/sdk/adapters/export-adapter.test.ts +434 -1
  113. package/src/sdk/adapters/export-adapter.ts +404 -1
  114. package/src/sdk/adapters/export-schedule-splice.test.ts +127 -0
  115. package/src/sdk/adapters/export-schedule-splice.ts +87 -0
  116. package/src/sdk/adapters/model-compat.ts +8 -2
  117. package/src/sdk/adapters/schedule-adapter.ts +73 -0
  118. package/src/sdk/adapters/store-adapter.ts +201 -0
  119. package/src/sdk/adapters/visibility-adapter.ts +3 -0
  120. package/src/sdk/local-backend.ts +16 -8
  121. package/src/services/desktop-export.ts +3 -1
  122. package/src/services/desktop-native-metadata.ts +41 -18
  123. package/src/services/file-dialog.ts +8 -3
  124. package/src/services/tauri-modules.d.ts +25 -0
  125. package/src/store/basketVisibleSet.ts +3 -0
  126. package/src/store/globalId.ts +4 -1
  127. package/src/store/index.ts +79 -1
  128. package/src/store/slices/addElementMeshes.ts +365 -0
  129. package/src/store/slices/addElementSlice.ts +275 -0
  130. package/src/store/slices/annotationsSlice.test.ts +133 -0
  131. package/src/store/slices/annotationsSlice.ts +251 -0
  132. package/src/store/slices/dataSlice.test.ts +23 -4
  133. package/src/store/slices/dataSlice.ts +1 -1
  134. package/src/store/slices/modelSlice.test.ts +67 -9
  135. package/src/store/slices/modelSlice.ts +39 -7
  136. package/src/store/slices/mutationSlice.ts +964 -3
  137. package/src/store/slices/overlayCompositor.test.ts +164 -0
  138. package/src/store/slices/overlaySlice.test.ts +93 -0
  139. package/src/store/slices/overlaySlice.ts +151 -0
  140. package/src/store/slices/pinboardSlice.test.ts +6 -1
  141. package/src/store/slices/playbackSlice.ts +128 -0
  142. package/src/store/slices/pointCloudSlice.ts +102 -0
  143. package/src/store/slices/schedule-edit-helpers.test.ts +97 -0
  144. package/src/store/slices/schedule-edit-helpers.ts +179 -0
  145. package/src/store/slices/scheduleSlice.test.ts +694 -0
  146. package/src/store/slices/scheduleSlice.ts +1330 -0
  147. package/src/store/slices/searchSlice.test.ts +342 -0
  148. package/src/store/slices/searchSlice.ts +341 -0
  149. package/src/store/slices/selectionSlice.test.ts +46 -0
  150. package/src/store/slices/selectionSlice.ts +20 -0
  151. package/src/store/types.ts +7 -0
  152. package/src/store.ts +14 -0
  153. package/vite.config.ts +1 -0
  154. package/dist/assets/ifc-lite_bg-BINvzoCP.wasm +0 -0
  155. package/dist/assets/index-_bfZsDCC.css +0 -1
  156. package/dist/assets/sandbox-C8575tul.js +0 -5951
package/CHANGELOG.md CHANGED
@@ -1,5 +1,954 @@
1
1
  # @ifc-lite/viewer
2
2
 
3
+ ## 1.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - E57 reader (subset) + clear errors when users drop unsupported formats.
8
+
9
+ **E57 (ASTM E2807-11) reader.**
10
+
11
+ - 48-byte FileHeader parser (`ASTM-E57` magic + xmlPhysicalOffset/Length
12
+ - pageSize).
13
+ - Page-CRC stripping: every 1024-byte physical page ends with 4 bytes
14
+ of CRC32-C; we strip them to get the logical view that XML offsets
15
+ reference. CRCs aren't validated (faster + still correct on
16
+ well-formed files).
17
+ - XML parser via `DOMParser` walks `e57Root → data3D → vectorChild` and
18
+ extracts each scan's record count, binary fileOffset, and prototype
19
+ fields.
20
+ - Binary section decoder walks DataPackets, reads bytestream length
21
+ table, decodes uncompressed Float32 / Float64 cartesianX/Y/Z plus
22
+ optional Float colors and Integer u8 colorRed/Green/Blue.
23
+ - ScaledIntegerNode encoding throws a clear error so the host can guide
24
+ the user to a Float-encoded export.
25
+
26
+ **Drop UX.** Dropping a file we can't load (Recap `.rwp/.rwi/.rwcx/.dmt`,
27
+ `.skp`, `.zip`, Faro `.fls`, ASCII `.pts/.xyz`) now shows an
28
+ explanatory toast describing what the format is and what to do
29
+ (typically: "export to E57 / LAS / PLY"). Previously the drop was
30
+ silently rejected.
31
+
32
+ **File picker** accepts `.e57` in browser drop, the native dialog, and
33
+ the recent-files command palette.
34
+
35
+ 7 new pointcloud unit tests cover the FileHeader parser, page-CRC
36
+ stripping (full pages and partial trailing page), the binary packet
37
+ walker on a hand-built single-packet scan with Float64 cartesianX/Y/Z
38
+
39
+ - uint8 RGB, and the ScaledInteger error path.
40
+
41
+ Tests: 48 pointcloud unit tests pass, full repo typecheck (24/24),
42
+ test suite green (22 runs), viewer Vite build emits decode-worker
43
+ chunk correctly.
44
+
45
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - Fix LAZ loading + add PLY / PCD as standalone formats; sliders feel
46
+ responsive on first contact.
47
+
48
+ **LAZ silently failed to load.** `laz-perf` is shipped as CommonJS,
49
+ which Vite/webpack wrap under `.default` differently across builds.
50
+ The previous probe only checked `lazPerf.createLazPerf` and
51
+ `lazPerf.default` (as a function), so all real-world LAZ loads threw
52
+ "could not find createLazPerf factory". The probe now walks four
53
+ candidate shapes (named export, `default.createLazPerf`, `default` as
54
+ function, namespace-as-function) and reports the visible keys when
55
+ none match.
56
+
57
+ **PLY + PCD now load directly.** Two new streaming sources backed by
58
+ the existing format decoders:
59
+
60
+ - `PlyStreamingSource` — ASCII + binary little/big-endian, optional
61
+ RGB (uchar) + intensity. Header probe (64 KB) + whole-file decode.
62
+ - `PcdStreamingSource` — wraps `decodePcd` (already supported PCD
63
+ ASCII / binary / binary_compressed via inline LZF).
64
+
65
+ Both use stride downsampling for the host's 25M-point cap.
66
+
67
+ **Format detection** sniffs `.ply` (magic "ply"), `.pcd` (`# .P` or
68
+ `.PCD` token), and the existing `.las/.laz` paths.
69
+
70
+ **File picker** accepts `.ply` and `.pcd` in browser drop, the native
71
+ dialog, and the recent-files command palette.
72
+
73
+ **Slider UX.** Default size mode is now `fixed-px` (was `attenuated`).
74
+ The previous default felt inert because the slider in `attenuated` mode
75
+ is the upper _cap_ on adaptive sizing — at typical wide views the
76
+ projected world-radius sat well below the cap, so dragging the slider
77
+ 1↔20 px never engaged. `fixed-px` always uses the slider value, and
78
+ "Auto" is one click away when users want adaptive behaviour.
79
+
80
+ **Worker URL fix.** `worker-client.ts` now imports
81
+ `./decode-worker.ts` (matching geometry's pattern) so Vite's worker
82
+ plugin resolves through the source-alias path. The package's build
83
+ script post-rewrites that to `.js` for dist consumers.
84
+
85
+ Tests: 41 pointcloud unit tests pass (7 new for PLY ascii/binary +
86
+ header probe + truncation), full repo typecheck (24/24), full test
87
+ suite (22 runs green), viewer Vite build emits the decode-worker
88
+ chunk correctly.
89
+
90
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - Phases 1–4 of point cloud loading.
91
+
92
+ - **LAS streaming** (`.las` files) — header parser + per-point record decoder
93
+ for ASPRS Point Data Formats 0–10, with auto-detection of "8-bit RGB
94
+ in u16 channels" producers and on-the-fly rescaling.
95
+ - **LAZ streaming** (`.laz` files) — wraps `laz-perf` (Apache-2.0) as a
96
+ runtime dep, decoded inside a Web Worker so the main thread stays
97
+ responsive.
98
+ - **Streaming pipeline** — Blob-backed byte source, decode worker with a
99
+ postMessage protocol that ships chunks back as transferable typed-array
100
+ buffers, host-side controller that paces decode, applies a 25M-point
101
+ memory cap with stride downsampling, and reports progress / completion.
102
+ - **Renderer streaming API** — `Renderer.beginPointCloudStream`,
103
+ `appendPointCloudChunk`, `endPointCloudStream`, `removePointCloudAsset`,
104
+ `setPointCloudOptions`. Streamed assets coexist with IFCx-derived
105
+ assets in separate ownership buckets so `setPointClouds` doesn't clobber
106
+ active streams.
107
+ - **Color modes** — `rgb` / `classification` (ASPRS palette) / `intensity` /
108
+ `height` (cool-warm ramp) / `fixed`. Per-point classification + intensity
109
+ travel through the GPU vertex layout and the WGSL shader picks the
110
+ channel based on the active mode uniform.
111
+ - **Viewer integration** — file picker accepts `.las,.laz` (browser drop +
112
+ native dialog), a small bottom-left panel exposes the color modes when
113
+ point clouds are loaded, and the federation registry's `modelIndex`
114
+ flows through streaming ingest for multi-model picking parity.
115
+
116
+ GPU-based point picking is deferred to a follow-up; clicks on points
117
+ return null and don't crash existing mesh selection.
118
+
119
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - Point cloud rendering quality: splat pipeline + Eye-Dome Lighting.
120
+
121
+ The 1-pixel `point-list` rendering looked great from far away but turned
122
+ into a halftone screen as you zoomed in — `point-list` topology has no
123
+ `gl_PointSize` equivalent in WebGPU, so density was fixed in screen space.
124
+
125
+ This swaps the pipeline for instanced 6-vertex quad splats and adds a
126
+ post-pass EDL for depth perception.
127
+
128
+ **Splat pipeline**
129
+
130
+ - `topology: 'triangle-list'`, vertex buffer `stepMode: 'instance'`,
131
+ 6 verts emitted per source point. Vertex shader picks a corner from
132
+ `vertex_index` and inflates clip-space position by the active size.
133
+ - Three size modes:
134
+ - `fixed-px` — every splat is N pixels (1..20)
135
+ - `adaptive-world` — splat covers a world-space radius, projected each
136
+ frame; closer = bigger
137
+ - `attenuated` (default) — adaptive but clamped to [1, N] px so splats
138
+ stay visible at far plane and don't blow up to half the screen up close
139
+ - Round shape: fragment discards corners outside the unit disc, so splats
140
+ render as discs not squares.
141
+
142
+ **Eye-Dome Lighting**
143
+
144
+ - New `EdlPass` runs after the existing PostProcessor. Samples 4 (low) or
145
+ 8 (high) neighbouring depths at radius R px, computes mean log-depth-
146
+ diff, darkens by `1 - exp(-300 * meanLog * strength)`. ~9 texture taps
147
+ per pixel. Only active when point clouds are loaded.
148
+ - Reverse-Z aware (`max(0, log(centre) - log(neighbour))`), early-out at
149
+ the far plane.
150
+
151
+ **UI**
152
+
153
+ - `PointCloudPanel` gains size-mode buttons, a 1–20 px slider, a 1–100 mm
154
+ world-radius slider (visible in adaptive/attenuated modes), and an EDL
155
+ toggle with a 0–3 strength slider.
156
+ - New `pointCloudSlice` fields: `pointCloudSizeMode`, `pointCloudPointSize`,
157
+ `pointCloudWorldRadius`, `pointCloudRoundShape`, `pointCloudEdlEnabled`,
158
+ `pointCloudEdlStrength`. Slice clamps numeric ranges.
159
+
160
+ Renderer API additions: `setEdlOptions({enabled, strength, radiusPx,
161
+ highQuality})`. `setPointCloudOptions` now also accepts `sizeMode`,
162
+ `worldRadius`, `roundShape`.
163
+
164
+ ### Patch Changes
165
+
166
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - Three Codex review fixes on the streaming ingest path.
167
+
168
+ **Streamed point cloud assets leaked across model removal.** The
169
+ renderer handle returned from `beginPointCloudStream` was discarded,
170
+ and streamed nodes are intentionally outside the IFCx
171
+ `setPointClouds` bucket, so removing a model left the GPU buffers
172
+ allocated for the rest of the session. `FederatedModel` now carries
173
+ an optional `pointCloudHandleId`; both ingest sites populate it; a
174
+ new `usePointCloudLifecycle` hook diffs the model map on every
175
+ change and frees handles for models that disappear.
176
+
177
+ **Double cleanup on ingest failure.** The outer `try/catch` in both
178
+ ingest sites called `removePointCloudAsset` + `incCount(-1)`, but
179
+ `ingestPointCloud`'s `onError` already does the same before
180
+ rethrowing. The duplicate cleanup pushed the asset counter negative
181
+ and caused a "remove twice" warning. The outer `catch` now only
182
+ handles store / UI state.
183
+
184
+ **PCD header probe.** The streaming source used the file's reported
185
+ size as the upper bound for the header probe; on truncated files
186
+ that walked off the end with a confusing error. Capped the probe at
187
+ 4 KiB so malformed PCD headers fail with a clear "header > 4 KiB"
188
+ message.
189
+
190
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - Fix two regressions that prevented point clouds from rendering in the viewer:
191
+
192
+ 1. **IFCx samples extracted zero points.** The entity extractor required
193
+ `bsi::ifc::class` on every node before assigning an `expressId`, but the
194
+ buildingSMART Point*Cloud*\*.ifcx fixtures place `pcd::base64` /
195
+ `points::array` / `points::base64` on nodes that carry only USD
196
+ `xformop`. Those nodes now also become first-class entities (synthetic
197
+ `IfcGeographicElement` type) so the point cloud extractor can emit
198
+ them. Added regression assertions in `verify-dist-hello-wall.mjs`.
199
+
200
+ 2. **`.las` / `.laz` files were silently ignored on single-file load.**
201
+ The drop / picker single-file path goes through `useIfcLoader.loadFile`,
202
+ which only branched on `ifcx` / `glb` / `ifc`. Added the LAS/LAZ branch
203
+ there and wired it into the streaming ingest. Camera fit-to-view now
204
+ triggers from `usePointCloudSync` for points-only scenes (the geometry
205
+ streaming hook bails out early when there are no meshes).
206
+
207
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - Fix `TypeError: entities.getTypeName is not a function` when picking a
208
+ point on a streamed point cloud (LAS / LAZ / PLY / PCD / E57).
209
+
210
+ The synthetic `IfcDataStore` that `pointCloudIngest.ts` builds for
211
+ point-cloud-only models stubbed `entities` with only a handful of
212
+ methods (`getId`, `getType`, `getName`, `getGlobalId`) and used method
213
+ names that don't match the real `EntityTable` interface. Picking
214
+ selects the synthetic expressId, which routes through the regular
215
+ property / hover / properties-panel pipeline — that pipeline calls
216
+ `entities.getTypeName`, `entities.getTypeEnum`,
217
+ `properties.getForEntity`, etc., and crashed on the missing
218
+ `getTypeName`.
219
+
220
+ `emptyDataStore()` now produces a stub that matches the real shape:
221
+
222
+ - `entities`: `count=1`, `expressId=Uint32Array([id])`, `typeEnum`,
223
+ plus `getTypeName` → `'IfcGeographicElement'`, `getName` → file
224
+ name, `getGlobalId` → `pointcloud-<id>`, and `getTypeEnum`,
225
+ `getByType`, `hasGeometry`, `getExpressIdByGlobalId`,
226
+ `getGlobalIdMap` covered.
227
+ - `properties`: real `PropertyTable` shape — `entityIndex`,
228
+ `psetIndex`, `propIndex`, `getForEntity`, `getPropertyValue`,
229
+ `findByProperty` (all empty / no-op).
230
+ - `quantities` / `relationships`: matching empty stubs.
231
+ - `entityIndex.byType` includes `IFCGEOGRAPHICELEMENT → [id]` so type
232
+ filters resolve.
233
+
234
+ `emptyDataStore` now takes the synthetic `expressId` and `fileName` so
235
+ the stub round-trips real data instead of `undefined`.
236
+
237
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - Round 3 of point cloud fixes — correctness gaps that block multi-model
238
+ sessions and silent rendering stalls.
239
+
240
+ **Federation relabel for streamed point clouds.**
241
+ `ingestPointCloud` now emits a synthetic entry on
242
+ `geometryResult.pointClouds`. Without this, `useIfcFederation`'s
243
+ `idOffset` fold + `relabelPointCloudAsset` call never fired for
244
+ LAS/LAZ/PLY/PCD/E57 streams, so picked `expressId`s for streamed
245
+ assets collided across federated models.
246
+
247
+ **Sync-throw cleanup.** Wrap `streamPointCloud()` in `try/catch`
248
+ inside `ingestPointCloud`. The renderer asset and asset-count
249
+ increment happen before the worker spins up, so a sync throw during
250
+ validation/worker setup used to leak both. We now `removePointCloudAsset`
251
+
252
+ - `onCountChange(-1)` before re-throwing.
253
+
254
+ **`setPointClouds()` shrinks bounds correctly.** The replace path
255
+ called `expandModelBoundsForPointClouds` (grow-only). Reloading IFCx
256
+ with a smaller scan kept stale extents until `clear`. Switched to
257
+ `recomputeModelBounds()` so bounds re-baseline from current state.
258
+
259
+ **`requestRender()` after every mutation.** `appendPointCloudChunk`,
260
+ `setPointCloudOptions`, `setEdlOptions`, `setPointClouds`,
261
+ `addPointClouds`, `clearPointClouds`, `removePointCloudAsset`,
262
+ `endPointCloudStream` now schedule a frame. Previously streamed
263
+ chunks could sit invisible until an unrelated camera move triggered
264
+ the next render.
265
+
266
+ **Worker cancel race.** `worker-client.next()` now re-checks
267
+ `signal.aborted` after `await session.send()`. A chunk that won the
268
+ race against `cancel()` would otherwise still call `onChunk` after
269
+ the host returned to the caller.
270
+
271
+ **Multi-scan E57 rejection.** `parseE57Xml` now records `hasPose` per
272
+ Data3D entry. `decodeE57` rejects multi-scan files where any entry
273
+ carries a `<pose>` element, with a clear "registered multi-scan;
274
+ re-export as merged" error. Previously such files silently
275
+ concatenated in scan-local space and rendered misaligned.
276
+
277
+ Verified: 62 pointcloud unit tests (1 new for pose flag), full repo
278
+ typecheck (24/24), viewer Vite build green.
279
+
280
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - Address CodeRabbit + Codex review feedback on PR #608.
281
+
282
+ Critical visual / correctness fixes:
283
+
284
+ - Point splats rendered ~2× too large because the shader treated the
285
+ user-facing `pointSizePx` (diameter) as the splat radius. Fixed in
286
+ both the live splat shader and the picker shader so click targets
287
+ match the rendered disc.
288
+ - Routed every detected point-cloud format (`ply`, `pcd`, `e57`) through
289
+ the streaming ingest in both `useIfcLoader` (single-file drop) and
290
+ `useIfcFederation` (multi-file). Previously only `las/laz` got the
291
+ pointcloud branch; `ply/pcd/e57` fell through into the IFC STEP path.
292
+ - Federation: applied `idOffset` to `geometryResult.pointClouds` too so
293
+ multi-pointcloud-model loads don't collide on local `expressId`.
294
+ - `expressId` defaulted to `1` on every ingest, so multiple inline LAS
295
+ loads collided. Now uses a process-local synthetic counter.
296
+ - E57 integer color channels are commonly u16 (0..65535); reader was
297
+ forcing u8 reads, distorting RGB. Now picks element width from the
298
+ declared min/max range.
299
+ - PCD `applyStride` preserved positions + colors but dropped intensity
300
+ and classification, so those color modes silently broke on files
301
+ past the 25M-point downsample cap.
302
+ - Inline `uploadAssetToGpu` forwards `intensities` + `classifications`
303
+ (added to `PointCloudAsset.chunk` shape).
304
+ - Model bounds recomputed after `removePointCloudAsset` /
305
+ `clearPointClouds` — previously stayed oversized, breaking
306
+ fit-to-view and section sliders.
307
+ - `usePointCloudLifecycle` disposes a model's GPU asset when the model
308
+ stays in the store but its `pointCloudHandleId` changes (re-stream of
309
+ the same file used to leak the old handle).
310
+ - `resetViewerState` now clears the point-cloud slice runtime fields so
311
+ loading a new file doesn't inherit the previous file's color mode /
312
+ size / EDL state.
313
+
314
+ Correctness / robustness:
315
+
316
+ - `streamPointCloud`'s host now closes the source on probe + onOpen
317
+ failures (single try/finally wrapping the whole open-and-decode
318
+ flow), so worker-backed sources don't leak the decoder on parse
319
+ errors or aborts.
320
+ - `worker-client.close()` clears cached `info`; subsequent `open()`
321
+ actually re-opens instead of returning stale info next to a null
322
+ `sourceId`.
323
+ - `LasStreamingSource.open()` and `LazStreamingSource.open()` are
324
+ atomic on failure: state is committed only after every step
325
+ succeeds, so a retry rerruns the probe + RGB-scale detection
326
+ cleanly. LAZ also frees malloc'd wasm pointers in the catch path.
327
+ - PLY decoder rejects files where `vertex` isn't the first element
328
+ (decoder reads from `header.bodyOffset`; non-leading vertex would
329
+ silently produce garbage).
330
+ - `decodePointsArray` validates each `colors[i]` is a `[r,g,b]` triple
331
+ before indexing, so malformed schemas fail with a clear message.
332
+ - `useIfcLoader` LAS/LAZ/PLY/PCD/E57 branch is guarded by
333
+ `loadSessionRef` on both error and success paths so a newer load can
334
+ replace an in-flight one without overwriting the newer model state;
335
+ stale renderer handle is freed.
336
+
337
+ Critical webhook fixes:
338
+
339
+ - `ViewportOverlays.tsx` had three imports between executable code;
340
+ hoisted them above the `const isDesktop = isTauri()` declaration.
341
+ - `edl-pass.ts` used `0u` for `texture_depth_multisampled_2d`'s
342
+ `sample_index`; WGSL spec requires `i32`.
343
+ - `pcd.test.ts` switched from `__dirname` to
344
+ `fileURLToPath(import.meta.url)` so it works outside vitest's
345
+ CommonJS-compat shim.
346
+
347
+ UX polish:
348
+
349
+ - `PointCloudPanel` toggle buttons expose `aria-pressed` so screen
350
+ readers announce the active option.
351
+ - `pointCloudSlice` setters reject `NaN`/`Infinity` (Math.min/max
352
+ passes them through unchanged).
353
+ - `BlobByteSource.read` clamps a negative `start` to `0`.
354
+ - File-dialog filters split GLB out of the IFC bucket into a "Mesh
355
+ Files" group.
356
+
357
+ The flattenMatrix transpose flagged in the review is actually correct
358
+ for USD's row-major-with-translation-in-row-3 convention (verified by
359
+ inspecting the Point_Cloud_S1 sample's transform; the rendered scan is
360
+ at the right world position). Added a clarifying comment so future
361
+ reviewers don't reach for the wrong fix.
362
+
363
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - Round 2 of CodeRabbit review fixes — correctness + robustness.
364
+
365
+ P1 (real correctness):
366
+
367
+ - Federation: streamed point clouds now get the post-`idOffset` global
368
+ expressId in picking output. New `Renderer.relabelPointCloudAsset()`
369
+ updates a per-asset uniform (`flags.x`) the shader prefers over the
370
+ per-vertex attribute, so federation is just a metadata write — no
371
+ GPU buffer rewrite. `useIfcFederation.addModel` calls it after the
372
+ pointClouds offset is applied.
373
+ - Section-plane range now folds in `pointCloudRenderer.getBounds()`, so
374
+ pure point-cloud scenes don't fall through to `[-100, 100]` and mixed
375
+ scenes don't clip points outside a smaller mesh-only range.
376
+ - `recomputeModelBounds()` now recomputes from scratch (mesh baseline +
377
+ current pc bounds) instead of growing-only. Previously, removing one
378
+ of several point clouds left stale oversized extents until every
379
+ point cloud was gone.
380
+ - `streamPointCloud` validates `chunkSize > 0` upfront; `LasStreamingSource`
381
+ and `LazStreamingSource` reject `maxPoints <= 0`. Prevents
382
+ zero-progress decode loops from accidental misuse.
383
+ - E57 merge uses `some()` instead of `every()`; mixed-attribute files
384
+ no longer drop colour/intensity for the whole merged cloud just
385
+ because one scan lacks the channel.
386
+ - E57 intensity is now allocated for `Integer`-encoded prototypes too
387
+ (was silently dropped); `ScaledInteger` throws a clear error.
388
+
389
+ P2 (robustness):
390
+
391
+ - `xml-mini` rejects truncated input — unclosed elements throw instead
392
+ of silently returning a partial tree.
393
+ - `worker-client.next()` now sends a `kind: 'abort'` to the worker when
394
+ the signal fires mid-flight. Previously cancel returned to the caller
395
+ while the worker kept decoding.
396
+ - `decodePointsArray` rejects empty arrays (was producing ±Infinity
397
+ bbox); `decodePointsBase64` rejects empty strings (no silent
398
+ downgrade to uncoloured cloud).
399
+ - `transformPositionsZUpToYUp` guards against zero / non-finite
400
+ homogeneous `w` (malformed `usd::xformop` matrices).
401
+
402
+ P3 (polish):
403
+
404
+ - `POINT_CLOUD_DEFAULTS` is now an exported constant shared by the
405
+ slice initializer and `resetViewerState`, so the two paths can't
406
+ drift.
407
+ - Replaced `as any` cast around `AbortSignal.any` with a typed
408
+ intersection.
409
+ - Doc comment on `pointCloudSizeMode` now matches the actual default
410
+ (`fixed-px`).
411
+
412
+ Verified: 61 pointcloud unit tests pass, full repo typecheck (24/24),
413
+ test suite green (22 runs), viewer Vite build emits decode-worker
414
+ chunk correctly.
415
+
416
+ - [#608](https://github.com/louistrue/ifc-lite/pull/608) [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1) Thanks [@louistrue](https://github.com/louistrue)! - Streaming point clouds (LAS / LAZ / PLY / PCD / E57) now arrive in
417
+ the renderer's Y-up convention, matching the IFCx ingest path.
418
+
419
+ Without this, scans rendered rotated 90° onto their side because the
420
+ renderer is Y-up internally and LIDAR / surveying formats store data
421
+ Z-up by convention. The IFCx path applied the swap inside
422
+ `pointcloud-extractor.ts`; the streaming path went straight from the
423
+ worker's decoded chunk into `appendPointCloudChunk`, skipping the
424
+ swap.
425
+
426
+ `ingestPointCloud` now wraps `onChunk` to re-orient positions and
427
+ bbox before forwarding to the renderer:
428
+ Z-up: X=right, Y=forward, Z=up
429
+ Y-up: X=right, Y=up, Z=back (negate Y to keep right-hand rule)
430
+
431
+ Mirrors the geometry / pointcloud extractors' existing handling.
432
+
433
+ - Updated dependencies [[`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1), [`0b8c860`](https://github.com/louistrue/ifc-lite/commit/0b8c860d3e13c8b498c515854db74e0850ce59f1)]:
434
+ - @ifc-lite/pointcloud@0.2.0
435
+ - @ifc-lite/renderer@1.18.0
436
+ - @ifc-lite/geometry@1.17.0
437
+ - @ifc-lite/parser@2.3.0
438
+
439
+ ## 1.18.0
440
+
441
+ ### Minor Changes
442
+
443
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Add Element tool — instant 3D appearance, off-surface placement, 3D ghost preview.
444
+
445
+ Three UX-blocker fixes that turn the Add Element tool into a real
446
+ authoring surface (previously every drop emitted STEP into the overlay
447
+ but the user saw nothing in the 3D scene until export+reparse).
448
+
449
+ - **Instant 3D appearance.** Every `add*` action now also builds a
450
+ renderer-frame mesh for the new element and injects it via the
451
+ same `appendGeometryBatch` action `duplicateEntity` uses. Walls,
452
+ beams, and members are oriented thickness-extruded boxes;
453
+ columns, doors, and windows are axis-aligned boxes;
454
+ slabs / roofs / plates / spaces are polygon extrusions (with fan
455
+ triangulation good enough for typical room shapes). Storey
456
+ elevation is read from the spatial hierarchy so multi-storey
457
+ placements drop on the right floor. The new mesh is tagged with
458
+ the federation-aware globalId so picking + selection work
459
+ immediately and the property panel opens on the new entity.
460
+ - **Off-surface placement.** A new
461
+ `raycastStoreyFloor()` helper unprojects the cursor to a ray and
462
+ intersects the storey floor plane (renderer Y =
463
+ `storeyElevation`). The hover preview and click handler both
464
+ fall back to it when the scene raycast misses, so columns can
465
+ drop onto empty floor outside the existing geometry. Snap-to-
466
+ surface still wins whenever there is a mesh under the cursor.
467
+ - **3D ghost preview.** The SVG overlay now projects the about-to-
468
+ commit element's 8 corners (or polygon ring) to screen and
469
+ renders the silhouette via a convex-hull outline. Single-click
470
+ types (column / door / window) show the ghost on hover before
471
+ any clicks; two-click types (wall / beam / member) show it once
472
+ the start point is placed. The ghost reads live per-type form
473
+ params, so adjusting Width / Height / Thickness updates it in
474
+ real time.
475
+
476
+ Also includes a panel polish: when the active type is `space` an
477
+ **Auto Spaces** section appears with snap tolerance, min area,
478
+ height, naming pattern, and IfcSpaceTypeEnum settings + Preview /
479
+ Generate buttons that drive the wall-graph face finder.
480
+
481
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Annotate-in-3D — drop pins on the scene with notes.
482
+
483
+ Press `P` (or pick the new `MapPin` button on the main toolbar),
484
+ click anywhere in the 3D scene, type a note. A pin lands at the
485
+ world point you clicked on, persists to localStorage, and re-anchors
486
+ itself as you orbit / pan. Pins are 14px amber dots with a
487
+ 1-character glyph (numbered ≤ 9, dot beyond), drop shadow, idle-pulse
488
+ on first paint (respects `prefers-reduced-motion`), emerald selection
489
+ ring matching the existing constructive accent.
490
+
491
+ Flow:
492
+
493
+ - `P` toggles the Annotate tool. Toolbar gains a `MapPin` button
494
+ with an amber active-tone, distinct from the primary blue used
495
+ for Select / Walk / Measure / Section.
496
+ - Cursor switches to crosshair while annotating.
497
+ - Click → raycast into the scene → on hit, an inline note input
498
+ drops at the click site with a guiding "What's worth noting?"
499
+ label and the entity context inline (e.g. `· IfcSlab #2036`).
500
+ Misses are silent — annotations are anchored to surface points
501
+ by design, not floating in space.
502
+ - `Enter` saves, `⇧Enter` newline, `Esc` cancels. Outside-click
503
+ saves a non-empty draft and silently cancels an empty one.
504
+ - Click an existing pin → popover with note + relative time +
505
+ pen / trash icons. Edit mode mirrors the drop-input treatment.
506
+ - Tool stays active across drops so you can drop several pins
507
+ in sequence.
508
+
509
+ Architecture:
510
+
511
+ - New `annotationsSlice` — Map-keyed store (`begin/commit/cancel
512
+ Draft`, `update`, `remove`, `select`, `clearAll`). Notes are
513
+ clamped at 2000 chars, soft-warned at 200. Persists to
514
+ `ifc-lite:annotations:v1` in localStorage and survives a fresh
515
+ slice instantiation. Covered by 9 unit tests.
516
+ - New DOM-billboard overlay (`AnnotationLayer`) sitting on top of
517
+ the WebGPU canvas. A single rAF loop re-projects every pin's
518
+ world position to screen via `cameraCallbacks.projectToScreen`,
519
+ skipping `setState` when nothing changed (so the loop is cheap
520
+ when the camera is still). Pointer-events: none on the wrapper
521
+ so empty space passes through to canvas controls; pins +
522
+ popover opt back into pointer events explicitly.
523
+ - `AnnotationPin`, `AnnotationPopover`, `AnnotationDropInput` —
524
+ composable components, all amber-accented, edge-clamped,
525
+ backdrop-blurred where it matters.
526
+
527
+ Pins are NOT IFC entities — they live alongside the model as an
528
+ authoring overlay. Future PRs will wire BCF round-trip and
529
+ IfcAnnotation export, plus an annotations-list panel and category
530
+ tags.
531
+
532
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Auto Spaces — diagnostics, broader wall coverage, and a sweep of
533
+ review feedback.
534
+
535
+ **Auto Spaces detection.** The "no enclosed regions detected"
536
+ failure mode now surfaces actionable counts — both in devtools
537
+ and in the panel itself.
538
+
539
+ - `extract-walls.ts` now tries the standard `Axis` representation
540
+ (`IfcShapeRepresentation` with `RepresentationIdentifier='Axis'`,
541
+ `IfcPolyline` items) **before** falling back to the
542
+ `addWallToStore` rectangle-profile convention. That covers
543
+ walls authored by Revit / ArchiCAD / IfcOpenShell — the previous
544
+ extractor only handled walls placed via the Add Element tool.
545
+ The placement chain is read once and the polyline endpoints are
546
+ transformed through it, so rotated walls work.
547
+ - Every wall that gets dropped is recorded with a typed reason
548
+ (`no-axis-or-rect-profile`, `placement-not-resolvable`,
549
+ `zero-length-axis`, …) — the panel summarises them as
550
+ `"3× no-axis-or-rect-profile, 1× zero-length-axis"`.
551
+ - `detectEnclosedAreas` exposes a
552
+ `detectEnclosedAreasWithStats(...)` companion that returns
553
+ per-stage counts (vertices, edges-after-split, faces total,
554
+ outer / below-min-area drops, largest area). The intersection
555
+ splitter's iteration cap now scales with input size
556
+ (`max(100, segments * 10)`) so dense floor plans don't bail
557
+ out early.
558
+ - `generateSpacesFromWalls` always logs a `console.info`
559
+ one-liner and threads a new `debug?: boolean` flag down to the
560
+ extractor + detector for verbose tracing. The viewer's Auto
561
+ Spaces panel exposes a "Verbose console logging" checkbox.
562
+ - The Auto Spaces diagnostic block now shows the graph stats
563
+ (`123v / 456e / 78f`), the drop counts, and per-reason wall
564
+ skips. Two amber hints fire automatically when walls were
565
+ extracted but no faces formed (likely snap tolerance), or
566
+ when nothing extracted (likely an unsupported geometry shape).
567
+
568
+ **Review-feedback sweep (PR #598).**
569
+
570
+ - `addElementMeshes.linearBox()` and the SVG `linearBoxCorners`
571
+ helper honour each endpoint's Y so a sloped beam previews as
572
+ a sloped prism instead of being flattened to the start.
573
+ - `bridge-store.requireStoreyId` rejects `0` (EXPRESS ids are
574
+ 1-based, `#0` is never valid).
575
+ - `addWindow` / `addDoor` `tsParamTypes` include
576
+ `UserDefinedPartitioningType` / `UserDefinedOperationType`
577
+ so typed sandbox callers can hit the IFC4 round-trip without
578
+ casts.
579
+ - `AnnotationLayer.resolveEntityType` no longer falls back to
580
+ `ifcDataStore` when the annotation's `modelId` is missing
581
+ from a federated `models` map (would resolve the wrong
582
+ entity in multi-model sessions). Single-model sessions keep
583
+ the fallback.
584
+ - `addDoorToStore` / `addWindowToStore` validate
585
+ `OperationType` / `PartitioningType` against the IFC4 enum
586
+ and re-route unknown values through
587
+ `.USERDEFINED.` + `User-defined…Type` so custom labels
588
+ round-trip cleanly.
589
+ - `addWallToStore` defaults `PredefinedType` to `.NOTDEFINED.`
590
+ (was `.STANDARD.`) to match the rest of the in-store
591
+ builders.
592
+ - `duplicateInStore` / `resolveDuplicateSource` allow
593
+ `OwnerHistory` to be `null` (IFC4 made it optional). The
594
+ duplicate emits a bare `$` token instead of `#null` for the
595
+ omitted case.
596
+ - `StoreEditor.addEntity` accepts an injected schema-aware
597
+ normalizer (`setEntityTypeNormalizer`); `@ifc-lite/sdk`
598
+ registers `normalizeIfcTypeName` + `isKnownType` at load
599
+ time so direct callers — CLI scripts, sandbox bridge,
600
+ unit tests — see registry-grade rejection of typos like
601
+ `IfcWal`, plus canonical PascalCase on `EntityRef.type`.
602
+
603
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Auto Spaces — generate IfcSpace volumes from a storey's walls.
604
+
605
+ Pick the **Space** type in the Add Element panel and the new **Auto
606
+ Spaces** section appears underneath the dimensions. Hit **Preview** to
607
+ see every enclosed region the wall graph forms (live SVG overlay,
608
+ labelled with area), then **Generate** to commit one IfcSpace per
609
+ region. Settings: snap tolerance (collapse sloppy wall ends), min area
610
+ (drop closets and slivers), height (extrusion), name pattern, and
611
+ IfcSpaceTypeEnum.
612
+
613
+ **`@ifc-lite/create`** — three new modules, all parser-pure:
614
+
615
+ - `auto-space-detect.ts` — planar-graph face finder. Snap →
616
+ resolve crossings → DCEL half-edge graph → leftmost-turn cycle
617
+ walk → drop unbounded faces → filter by min area. Handles
618
+ multi-component layouts (two non-touching rooms find both),
619
+ T-junctions, and snap-induced corner merges. 8 fixture tests.
620
+ - `extract-walls.ts` — pulls every wall axis on a target storey
621
+ from a parsed `IfcDataStore`. Walks
622
+ IfcRelContainedInSpatialStructure → IfcWall → placement chain →
623
+ IfcRectangleProfileDef.XDim. Optional overlay reader includes
624
+ walls created via the Add Element tool without a re-parse.
625
+ - `generate-spaces.ts` — orchestration: extract → detect → emit
626
+ via `addSpaceToStore` polygon mode. `dryRun` runs detection only.
627
+
628
+ **`@ifc-lite/viewer`** — `mutationSlice.generateSpacesFromWalls`
629
+ returns the detection result. `AddElementPanel` gains the Auto Spaces
630
+ section; `AddElementOverlay` projects detected outlines back to screen
631
+ using the storey's elevation so the preview tracks the camera in
632
+ real time.
633
+
634
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Add the `bim.store.*` namespace — high-level editing of an already-parsed
635
+ `IfcDataStore` via the existing mutation overlay. Closes the merge-roundtrip
636
+ gap from #592 (you can edit `IfcRectangleProfileDef.XDim` or drop a fresh
637
+ `IfcColumn` into a model without round-tripping through a script + re-parse).
638
+
639
+ **`@ifc-lite/mutations`** — new `StoreEditor` facade plus four
640
+ `MutablePropertyView` extensions: positional-attribute mutations, overlay
641
+ entity creation/deletion (with watermark seeding), and three helpers used by
642
+ the viewer's undo/redo (`removePositionalMutation`, `restoreFromTombstone`,
643
+ `restoreNewEntity`).
644
+
645
+ **`@ifc-lite/create`** — new `in-store/` module: `addColumnToStore` builds a
646
+ 12-entity IfcColumn sub-graph (placement, profile, extruded solid,
647
+ representation, product shape, rel-contained-in-spatial-structure) anchored
648
+ to a target `IfcBuildingStorey`. `resolveSpatialAnchor` walks the parsed
649
+ store to find the IfcOwnerHistory, the 'Body' representation context, and
650
+ the storey's local placement.
651
+
652
+ **`@ifc-lite/sdk`** — new `StoreNamespace` exposed as `bim.store` on
653
+ `BimContext`. Methods: `addEntity`, `removeEntity`, `setPositionalAttribute`,
654
+ `addColumn`. Backed by `StoreBackendMethods` on `BimBackend`; the
655
+ `RemoteBackend` proxy round-trips them through the transport.
656
+
657
+ **`@ifc-lite/sandbox`** — `bim.store.*` is bridged into the QuickJS sandbox
658
+ with full TypeScript types via `bim-globals.d.ts` and an LLM cheat sheet in
659
+ the system prompt. Gated on a new `store: true` permission (default
660
+ `false`, mirrors the existing `mutate` permission pattern).
661
+
662
+ **`@ifc-lite/cli`** — `HeadlessBackend.store` is now functional (was a
663
+ no-op before). Scripts run via the CLI can edit a parsed model and export it
664
+ with mutations applied.
665
+
666
+ **`@ifc-lite/viewer`** — three new UI surfaces:
667
+
668
+ - Raw STEP tab in `PropertiesPanel` — lists every positional STEP argument
669
+ with an inline pen-icon editor for scalar values (numbers, refs, enums,
670
+ null). Mutated rows show a purple dot and tinted background.
671
+ - `EntityContextMenu` gains "Delete entity" (red, calls `removeEntity`
672
+ with toast + undo support) and "Add column here…" (emerald, only enabled
673
+ when the right-clicked entity is an `IfcBuildingStorey`).
674
+ - `AddColumnDialog` modal — storey picker sorted by elevation, position
675
+ (storey-local metres), cross-section, height, name, optional collapsible
676
+ for Description/ObjectType/Tag. Anchor-resolution failures surface
677
+ inline, not as thrown exceptions.
678
+
679
+ Plus four new actions on `mutationSlice` (`setPositionalAttribute`,
680
+ `removeEntity`, `addColumn`, dialog open/close) backed by per-model
681
+ `StoreEditor` caches, with undo/redo wired for `UPDATE_POSITIONAL_ATTRIBUTE`,
682
+ `CREATE_ENTITY`, and `DELETE_ENTITY`.
683
+
684
+ **`@ifc-lite/parser`** — `package.json` `exports` re-ordered to put `types`
685
+ before `import` so downstream consumers using TS5 `nodenext` resolution
686
+ pick up the type declarations.
687
+
688
+ **`@ifc-lite/geometry`** — re-exports `MetadataBootstrapEntitySummary` and
689
+ `MetadataBootstrapSpatialNode` from the package index (used by viewer
690
+ desktop services).
691
+
692
+ **`@ifc-lite/renderer`** — `GPUBufferDescriptor` ambient declaration gains
693
+ `mappedAtCreation?: boolean`. Internal change; the renderer was already
694
+ using it at runtime to skip a Mojo IPC round-trip on Chrome/Dawn.
695
+
696
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Duplicate-from-selection — pick any IfcRoot product, hit `⌘D` (or
697
+ right-click → Duplicate), get a fully-functional clone. The
698
+ duplicate is a first-class entity in the property panel, exports
699
+ cleanly to STEP with all its property associations preserved, and
700
+ ships in 6 directional variants sized to the source's bounding box.
701
+
702
+ **`@ifc-lite/create`**
703
+
704
+ - New `duplicateInStore(editor, source, options)` pure builder.
705
+ Emits a fresh placement chain (`IfcCartesianPoint` →
706
+ `IfcAxis2Placement3D` → `IfcLocalPlacement`) plus the duplicate
707
+ `IfcRoot` with a new GUID and the source's `Representation`
708
+ reference reused (geometry shared). Optional fresh
709
+ `IfcRelContainedInSpatialStructure` anchors to the source's
710
+ storey. Offset is configurable via `options.offset` — the slice
711
+ sizes it to the source's bbox.
712
+ - New `resolveDuplicateSource(store, expressId)` walks the parsed
713
+ `IfcDataStore` for placement / parent / location / storey /
714
+ associations.
715
+ - New `SourceAssociation` shape captures one
716
+ `IfcRelDefines*` / `IfcRelAssociates*` edge that references
717
+ the source. The builder replays each one against the duplicate
718
+ so the exported STEP carries identical psets / qsets /
719
+ materials / classifications / documents / type binding —
720
+ without modifying any existing rel.
721
+ - Resolver scans the five association rel types
722
+ (`IFCRELDEFINESBYPROPERTIES`, `IFCRELDEFINESBYTYPE`,
723
+ `IFCRELASSOCIATESMATERIAL`, `…CLASSIFICATION`, `…DOCUMENT`)
724
+ by direct numeric membership in `RelatedObjects`.
725
+ - `DuplicateBuildResult.associationRelIds: number[]` exposes the
726
+ fresh rel ids for caller introspection.
727
+ - 7 unit tests in `duplicate.test.ts`: full graph emission,
728
+ custom offset, no-storey path, root-placement parent, attribute
729
+ count guard, association replay (3 rel types in one go), and
730
+ the no-associations case.
731
+
732
+ **`@ifc-lite/mutations`**
733
+
734
+ - New `setEntityAlias(overlayId, sourceId | null)` /
735
+ `getEntityAlias(id)` / `resolveBaseEntityId(id)` public surface
736
+ on `MutablePropertyView`. Aliases redirect base property and
737
+ quantity reads from the duplicate to its source — so the
738
+ duplicate inherits psets/qsets without eagerly cloning them
739
+ into the overlay.
740
+ - Override slots stay scoped to the original (overlay) id, so
741
+ edits on the duplicate don't bleed into the source. Verified
742
+ by 4 new unit tests including the source-untouched path,
743
+ chain-cap (one hop, not transitive), and the self-alias guard.
744
+
745
+ **`@ifc-lite/viewer`**
746
+
747
+ - New `duplicateEntity(modelId, sourceExpressId, direction?)`
748
+ slice action. Wraps the create-package builder, sets the
749
+ mutation-view alias, and clones the source's mesh data into
750
+ the geometry result with the offset applied — so the duplicate
751
+ appears in 3D the moment the action fires, not just in the
752
+ export overlay. Per-vertex `entityIds` arrays are filled with
753
+ the new globalId so picking and selection resolve correctly.
754
+ - New `DuplicateDirection` type (`+X` / `-X` / `+Y` / `-Y` /
755
+ `+Z` / `-Z`). Magnitude per axis = the source's bounding-box
756
+ dimension on that axis, so a 3m wall steps 3m and a 0.4m
757
+ column steps 0.4m. Falls back to a 1m step when the source
758
+ has no mesh in geometry.
759
+ - Right-click menu's "Duplicate" item is now a `DuplicateRow`:
760
+ primary clickable label on the left (defaults to +X), 6 axis
761
+ chips on the right (→ ← ↗ ↙ ↑ ↓). Tooltips spell out
762
+ "+X (east)" through "−Z (down)".
763
+ - `⌘D` defaults to +X. `⇧⌘D` = +Z (up), `⌥⌘D` = +Y (north) —
764
+ modifier shortcuts for power users without forcing a mouse
765
+ trip to the chip row. Selection moves to the new globalId so
766
+ a Cmd+D chain ("stamp a row of columns") works without
767
+ re-clicking.
768
+ - **`resolveGlobalIdFromModels` two-pass overlay fallback** —
769
+ the federation resolver previously gated each model's id range
770
+ at parse-time `maxExpressId`, which excluded every
771
+ overlay-allocated id from selection. The fix: a second pass
772
+ consults each model's mutation view via `getNewEntity(localId)`
773
+ so overlay duplicates resolve to the right model with the
774
+ right local id. Without this, the property panel saw the
775
+ duplicate as "UNKNOWN / Unknown / no property sets" because
776
+ the alias couldn't take effect on a wrongly-resolved id.
777
+ - PropertiesPanel falls back to the overlay `NewEntity` record
778
+ for type / name / GUID / Description / ObjectType when the
779
+ parsed `entityNode` comes up empty. The bSDD attribute list
780
+ synthesises from the schema-defined positional names. The
781
+ Materials / Classifications / Documents / structural
782
+ Relationships sections all route through a new
783
+ `lookupExpressId` (alias-resolved) so they query the source's
784
+ parsed maps directly.
785
+
786
+ After: a freshly-duplicated wall is genuinely first-class — name
787
+ reads, properties show, quantities show, material layers show,
788
+ classifications show, documents show, and a round-tripped STEP
789
+ file carries every association.
790
+
791
+ - [#576](https://github.com/louistrue/ifc-lite/pull/576) [`1309f8c`](https://github.com/louistrue/ifc-lite/commit/1309f8cba128b3b6237ebfb9831bf359c426a742) Thanks [@louistrue](https://github.com/louistrue)! - Add the full IfcTask / 4D construction-schedule experience to the viewer.
792
+
793
+ **Gantt panel** — a lower-panel workspace combining a task tree, a zoomable
794
+ SVG timeline with task bars / milestones / dependency arrows / playback
795
+ cursor, a toolbar (work-schedule filter, play / pause / loop / speed, time
796
+ scale), and an empty state. Live Gantt ↔ 3D selection highlight (one-way,
797
+ no isolation) and playback-driven visibility through the rendererʼs
798
+ hidden-entity channel.
799
+
800
+ **Schedule editing** — Inspector Task card (name, identification,
801
+ predefined type, milestone, start / finish / duration with any-two-of-three
802
+ reconciliation, assigned products, delete with cascade). Undo / redo
803
+ (descriptor-based lightweight snapshots for field edits; full snapshot for
804
+ structural edits), store-scoped transactions (drag-coalesced), add / delete /
805
+ reorder tasks. IFC STEP export routes through a centralised schedule splice
806
+ helper so generated / edited schedules round-trip cleanly on every export
807
+ surface.
808
+
809
+ **Generate from hierarchy** — a Generate Schedule dialog produces a work
810
+ schedule + tasks from the modelʼs spatial hierarchy (Storey / Building) or
811
+ geometry (Height-slice, with optional Class / Type / Name subgroup). Linked
812
+ FS dependencies and ghost-preparation look-ahead are opt-in.
813
+
814
+ **4D animation** — Synchro-style phased lifecycle (preparation ghost →
815
+ ramp-in → active task-type colour → settling fade → complete), demolition
816
+ inversion, customizable palette, and configurable palette intensity /
817
+ look-ahead / hide-untasked products. Animation layers live in a priority-
818
+ composited overlay registry (`registerOverlayLayer`), with a single
819
+ compositor hook owning the write to the rendererʼs hidden-entity + colour-
820
+ override channels.
821
+
822
+ **LLM integration** — built-in "Construction schedule (4D)" script template,
823
+ PDF / spreadsheet chat attachments, and `bim.schedule.*` read APIs reachable
824
+ from the sandbox.
825
+
826
+ - [#598](https://github.com/louistrue/ifc-lite/pull/598) [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c) Thanks [@louistrue](https://github.com/louistrue)! - Raw STEP tab — drill into `#N` references and a tighter dev-leaning
827
+ visual treatment.
828
+
829
+ **Reference drill-through**
830
+
831
+ - Each `#N` token in the Raw STEP card is now a clickable chip.
832
+ Click → drills into the target entity and shows its positional
833
+ arguments inline; the breadcrumb at the top of the card tracks
834
+ the path back to the 3D-selected entity.
835
+ - **Auto-skip wrappers** — when the click target itself has only
836
+ a single positional arg and that arg is also a `#N`, the card
837
+ follows the chain in one click and lands on the first
838
+ "meaningful" entity. Capped at 16 hops to defend against
839
+ cyclic STEP graphs. So a real-world case like
840
+ `IfcRelDefinesByProperties → IfcPropertySet` steps cleanly,
841
+ and pure pass-through wrappers don't waste user clicks.
842
+ - Drill state resets when the 3D selection changes — drilling
843
+ stays scoped to a single click. Each breadcrumb segment is
844
+ clickable to jump back to that depth.
845
+ - Editing a `#N` ref still works via the pen icon — clicking the
846
+ chip itself navigates instead of entering edit mode, but the
847
+ hover-revealed pen still flips to inline-edit so a user can
848
+ re-type the reference target.
849
+ - Tombstoned entities short-circuit the auto-follow so the drill
850
+ doesn't render a deleted entity's body.
851
+
852
+ **True STEP literals on display**
853
+
854
+ - Tokens are read directly from the source bytes via a new
855
+ `extractRawStepTokens` helper, so refs render as `#42`, enums
856
+ stay `.AREA.`, and strings keep their on-disk quoted form. The
857
+ EntityExtractor's parsed JS shape strips reference prefixes
858
+ (it parses `#42` into the integer `42`), so the previous
859
+ formatter had no way to recover the distinction — `OwnerHistory`
860
+ would render as `18` instead of `#18`. Fixed.
861
+ - Overlay overrides serialize back through `serializeStepToken`
862
+ for parity with the unmodified base tokens.
863
+
864
+ **Overlay-aware row display**
865
+
866
+ - Edits to positional attributes now reflect immediately in the
867
+ row body. Previously the card re-extracted from the source
868
+ buffer and ignored the overlay map, so the displayed value
869
+ snapped back to the original after Save (only the purple
870
+ overlay-override dot updated correctly).
871
+
872
+ **Dev-leaning tab styling**
873
+
874
+ - Raw STEP tab restyled — replaces the "Raw" plain-text label
875
+ with a `</>` bracket glyph, shrinks the trigger to icon-only
876
+ width via `flex: 0 0 auto`. Frees up width so Properties /
877
+ Quantities / bSDD keep their text visible at the default
878
+ panel size, and signals "developer view" with a terminal-green
879
+ accent on hover / active state.
880
+
881
+ **Add-Column UI removed**
882
+
883
+ - The original `AddColumnDialog` + context-menu "Add column
884
+ here…" + EditToolbar "Column" button — premature for the
885
+ current workflow (single hard-coded element type with no
886
+ geometry preview). Removed cleanly:
887
+ `AddColumnDialog.tsx` (deleted), the `addColumnDialog` slice
888
+ state, the constructive `MenuItem` tone (only used by that
889
+ item), and the context-menu / toolbar entry points.
890
+ - Kept: the `addColumn` slice action and the
891
+ `bim.store.addColumn` SDK surface — those still drive scripts
892
+ and programmatic flows, just no UI affordance for now.
893
+
894
+ **Tombstoned mesh actually disappears**
895
+
896
+ - Delete entity now pairs the overlay tombstone with
897
+ `hideEntity(globalId)` so the rendered mesh is hidden from the
898
+ GPU buffers (and stops being pickable). Undo of `DELETE_ENTITY`
899
+ pairs `restoreFromTombstone` with `showEntity` so the entity
900
+ returns to the scene; redo re-hides. Symmetrical round-trip.
901
+
902
+ - [#588](https://github.com/louistrue/ifc-lite/pull/588) [`b75f0cc`](https://github.com/louistrue/ifc-lite/commit/b75f0cccb06c89f5e30272d6c04f986f3b47e574) Thanks [@louistrue](https://github.com/louistrue)! - Replace the SQL tab in the advanced search modal with a clean
903
+ chip-based **Filter** tab. Storey / IFC type / Predefined type / Name /
904
+ Property / Quantity rules compose with AND/OR + IsSet/IsNotSet and
905
+ run through an in-memory evaluator that scales to 4M-entity models
906
+ via `entityIndex.byType` / `spatialHierarchy.byStorey` prefilter,
907
+ cheap-first per-entity rule ordering, and async chunked yielding
908
+ with cancel + progress. The DuckDB engine, SQL editor, schema
909
+ browser, templates, error rewriter, and saved-SQL-queries module
910
+ have been removed — Builder is the whole UI now, with a single Run
911
+ button and CSV/JSON export. Builder dropdowns are schema-aware
912
+ (storeys + IFC types load eagerly, pset / qto names load lazily on
913
+ first use), the inline search-bar query promotes to a Name rule
914
+ with one click, multi-model row clicks route to the correct model,
915
+ and saved presets persist named `{name, combinator, rules}`
916
+ snapshots in localStorage.
917
+
918
+ ### Patch Changes
919
+
920
+ - [#588](https://github.com/louistrue/ifc-lite/pull/588) [`b75f0cc`](https://github.com/louistrue/ifc-lite/commit/b75f0cccb06c89f5e30272d6c04f986f3b47e574) Thanks [@louistrue](https://github.com/louistrue)! - Address PR #588 review feedback that survived the Filter migration:
921
+
922
+ - Inline-bar Enter now flushes the 80ms debounce by re-scanning against
923
+ the live `searchQuery`, so committing inside the debounce window
924
+ selects the entity matching what the input shows (not the prior
925
+ query) and records the correct recent.
926
+ - The 50ms `frameSelection` timer in the inline bar is tracked via a
927
+ ref and cleared on rapid selection changes / unmount instead of
928
+ leaking orphan callbacks.
929
+ - Shift+Enter additive selection in the inline bar and the row-level
930
+ additive path in the Search modal now TOGGLE via `toggleEntitySelection`,
931
+ so the same interaction can deselect a previously-added row.
932
+ - New `addEntitiesToSelection` batch action on the selection slice;
933
+ the Search modal's "Select all" path uses it so a 5K-row select-all
934
+ dispatches one Zustand `set` instead of N.
935
+ - Tier-0 scoring now keeps the max across name/type/objectType/description
936
+ fields (matching Tier-1's behaviour). Without this, an entity with a
937
+ substring name hit and a type-exact hit ranked lower than it should
938
+ on Tier-0, breaking the comparable-ordering guarantee when results
939
+ came from a mix of Tier-0 and Tier-1 models.
940
+
941
+ - Updated dependencies [[`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c), [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c), [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c), [`1309f8c`](https://github.com/louistrue/ifc-lite/commit/1309f8cba128b3b6237ebfb9831bf359c426a742), [`1309f8c`](https://github.com/louistrue/ifc-lite/commit/1309f8cba128b3b6237ebfb9831bf359c426a742), [`1309f8c`](https://github.com/louistrue/ifc-lite/commit/1309f8cba128b3b6237ebfb9831bf359c426a742), [`1309f8c`](https://github.com/louistrue/ifc-lite/commit/1309f8cba128b3b6237ebfb9831bf359c426a742), [`16d7a63`](https://github.com/louistrue/ifc-lite/commit/16d7a6361a78bb39a2bd61bba6990db5d3df0c04), [`945bb30`](https://github.com/louistrue/ifc-lite/commit/945bb30061ca044f4a51001f7299c17350ce99cf), [`25c9877`](https://github.com/louistrue/ifc-lite/commit/25c9877969d2dcccb9c4e61f57b188cbf5fbbc3c), [`370e084`](https://github.com/louistrue/ifc-lite/commit/370e084e94e8fce930bddf948344c4b639d196f3), [`18c6a37`](https://github.com/louistrue/ifc-lite/commit/18c6a37f1cc1426daa32ee60457dd0580a5257f5)]:
942
+ - @ifc-lite/mutations@1.15.0
943
+ - @ifc-lite/sdk@1.15.0
944
+ - @ifc-lite/sandbox@1.15.0
945
+ - @ifc-lite/parser@2.2.0
946
+ - @ifc-lite/geometry@1.16.6
947
+ - @ifc-lite/renderer@1.17.0
948
+ - @ifc-lite/query@1.14.7
949
+ - @ifc-lite/wasm@1.16.7
950
+ - @ifc-lite/export@1.18.0
951
+
3
952
  ## 1.17.6
4
953
 
5
954
  ### Patch Changes