@ifc-lite/viewer 1.18.0 → 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 (36) hide show
  1. package/.turbo/turbo-build.log +16 -14
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +436 -0
  4. package/dist/assets/{basketViewActivator-Cm1QEk_R.js → basketViewActivator-RZy5c3Td.js} +1 -1
  5. package/dist/assets/decode-worker-Collf_X_.js +1320 -0
  6. package/dist/assets/{exporters-B_OBqIyD.js → exporters-BraHBeoi.js} +2540 -1958
  7. package/dist/assets/{geometry.worker-xHHy-9DV.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-BKq-M3Mk.js → index-BOi3BuUI.js} +25546 -23508
  11. package/dist/assets/index-XwKzDuw6.js +22 -0
  12. package/dist/assets/{native-bridge-SHXiQwFW.js → native-bridge-CpBeOPQa.js} +1 -1
  13. package/dist/assets/{sandbox-jez21HtV.js → sandbox-Baez7n-t.js} +1366 -1311
  14. package/dist/assets/{server-client-ncOQVNso.js → server-client-BB6cMAXE.js} +1 -1
  15. package/dist/assets/{wasm-bridge-DyfBSB8z.js → wasm-bridge-CAYCUHbE.js} +1 -1
  16. package/dist/index.html +5 -5
  17. package/package.json +7 -6
  18. package/src/components/viewer/MainToolbar.tsx +4 -2
  19. package/src/components/viewer/PointCloudPanel.tsx +174 -0
  20. package/src/components/viewer/Viewport.tsx +18 -1
  21. package/src/components/viewer/ViewportContainer.tsx +43 -5
  22. package/src/components/viewer/ViewportOverlays.tsx +13 -2
  23. package/src/components/viewer/tools/AddElementOverlay.tsx +43 -2
  24. package/src/components/viewer/usePointCloudLifecycle.ts +64 -0
  25. package/src/components/viewer/usePointCloudSync.ts +98 -0
  26. package/src/hooks/ingest/pointCloudIngest.ts +391 -0
  27. package/src/hooks/ingest/viewerModelIngest.ts +32 -3
  28. package/src/hooks/useIfcFederation.ts +72 -3
  29. package/src/hooks/useIfcLoader.ts +67 -3
  30. package/src/services/file-dialog.ts +4 -2
  31. package/src/store/index.ts +10 -1
  32. package/src/store/slices/pointCloudSlice.ts +102 -0
  33. package/src/store/types.ts +7 -0
  34. package/vite.config.ts +1 -0
  35. package/dist/assets/ifc-lite_bg-ADjKXSms.wasm +0 -0
  36. package/dist/assets/index-COnQRuqY.css +0 -1
@@ -1,10 +1,10 @@
1
1
 
2
- > @ifc-lite/viewer@1.18.0 build /home/runner/work/ifc-lite/ifc-lite/apps/viewer
2
+ > @ifc-lite/viewer@1.19.0 build /home/runner/work/ifc-lite/ifc-lite/apps/viewer
3
3
  > vite build
4
4
 
5
5
  vite v5.4.21 building for production...
6
6
  transforming...
7
- ✓ 4152 modules transformed.
7
+ ✓ 4187 modules transformed.
8
8
  ../../node_modules/.pnpm/protobufjs@8.0.0/node_modules/protobufjs/dist/minimal/protobuf.js (662:18): Use of eval in "../../node_modules/.pnpm/protobufjs@8.0.0/node_modules/protobufjs/dist/minimal/protobuf.js" is strongly discouraged as it poses security risks and may cause issues with minification.
9
9
  rendering chunks...
10
10
  [plugin:vite:reporter] [plugin vite:reporter]
@@ -14,10 +14,12 @@ rendering chunks...
14
14
  (!) /home/runner/work/ifc-lite/ifc-lite/apps/viewer/src/components/viewer/selectionHandlers.ts is dynamically imported by /home/runner/work/ifc-lite/ifc-lite/apps/viewer/src/hooks/useKeyboardShortcuts.ts but also statically imported by /home/runner/work/ifc-lite/ifc-lite/apps/viewer/src/components/viewer/useMouseControls.ts, dynamic import will not move module into another chunk.
15
15
  
16
16
  computing gzip size...
17
- dist/index.html  3.66 kB │ gzip: 1.23 kB
18
- dist/assets/geometry.worker-xHHy-9DV.js  42.60 kB
17
+ dist/index.html  3.66 kB │ gzip: 1.22 kB
18
+ dist/assets/geometry.worker-DQEZB2rB.js  42.60 kB
19
+ dist/assets/index-XwKzDuw6.js  54.12 kB
20
+ dist/assets/decode-worker-Collf_X_.js  56.14 kB
19
21
  dist/assets/emscripten-module-NWak2PoB.wasm  518.88 kB
20
- dist/assets/ifc-lite_bg-ADjKXSms.wasm  1,050.82 kB
22
+ dist/assets/ifc-lite_bg-4yUkDRD8.wasm  1,059.81 kB
21
23
  dist/assets/emscripten-module-CGIn_cMh.wasm  1,070.86 kB
22
24
  dist/assets/emscripten-module-DYvzWiHh.wasm  1,561.30 kB
23
25
  dist/assets/arrow2_bg-4Y7xYo54.wasm  5,000.39 kB
@@ -25,32 +27,32 @@ computing gzip size...
25
27
  dist/assets/emscripten-module-BTRCZGcB.wasm  6,623.52 kB
26
28
  dist/assets/esbuild-Cpd5nU_H.wasm 13,524.82 kB
27
29
  dist/assets/cesium-ADbP7waU.css  24.30 kB │ gzip: 5.49 kB
28
- dist/assets/index-COnQRuqY.css  227.78 kB │ gzip: 33.44 kB
30
+ dist/assets/index-0XpVr_S5.css  227.87 kB │ gzip: 33.45 kB
29
31
  dist/assets/tauri-dialog-stub-r7Wksg7o.js  0.05 kB │ gzip: 0.07 kB
30
32
  dist/assets/esbuild-COv63sf-.js  0.06 kB │ gzip: 0.08 kB
31
33
  dist/assets/tauri-core-stub-D8Fa-u43.js  0.11 kB │ gzip: 0.12 kB
32
34
  dist/assets/tauri-fs-stub-BdeRC7aK.js  0.13 kB │ gzip: 0.13 kB
33
- dist/assets/basketViewActivator-Cm1QEk_R.js  1.13 kB │ gzip: 0.60 kB
35
+ dist/assets/basketViewActivator-RZy5c3Td.js  1.13 kB │ gzip: 0.60 kB
34
36
  dist/assets/event-DIOks52T.js  1.42 kB │ gzip: 0.66 kB
35
- dist/assets/wasm-bridge-DyfBSB8z.js  1.66 kB │ gzip: 0.80 kB
37
+ dist/assets/wasm-bridge-CAYCUHbE.js  1.66 kB │ gzip: 0.80 kB
36
38
  dist/assets/ifc-cache-BAN4vcd4.js  2.77 kB │ gzip: 0.94 kB
37
39
  dist/assets/ffi-DlhRHxHv.js  6.14 kB │ gzip: 0.98 kB
38
40
  dist/assets/lens-CSASnhAL.js  9.07 kB │ gzip: 2.81 kB
39
41
  dist/assets/emscripten-module.browser-CY5t0Vfq.js  12.70 kB │ gzip: 5.09 kB
40
- dist/assets/native-bridge-SHXiQwFW.js  18.70 kB │ gzip: 3.80 kB
42
+ dist/assets/native-bridge-CpBeOPQa.js  18.70 kB │ gzip: 3.80 kB
41
43
  dist/assets/parquet-CEXmQNRO.js  29.67 kB │ gzip: 6.89 kB
42
- dist/assets/server-client-ncOQVNso.js  31.43 kB │ gzip: 7.06 kB
44
+ dist/assets/server-client-BB6cMAXE.js  31.43 kB │ gzip: 7.05 kB
43
45
  dist/assets/bcf-DOG9_WPX.js  40.76 kB │ gzip: 12.67 kB
44
46
  dist/assets/ids-DQ5jY0E8.js  57.15 kB │ gzip: 12.98 kB
45
47
  dist/assets/browser-C5TFR7sH.js  67.65 kB │ gzip: 18.94 kB
46
48
  dist/assets/drawing-2d-DoxKMqbO.js  71.45 kB │ gzip: 20.98 kB
47
49
  dist/assets/zip-DBEtpeu6.js  97.03 kB │ gzip: 30.09 kB
48
50
  dist/assets/arrow-CZ5kQ26f.js  208.84 kB │ gzip: 50.09 kB
49
- dist/assets/sandbox-jez21HtV.js  384.96 kB │ gzip: 62.02 kB
51
+ dist/assets/sandbox-Baez7n-t.js  385.25 kB │ gzip: 62.52 kB
50
52
  dist/assets/maplibre-gl-CGLcoNXc.js  1,046.19 kB │ gzip: 282.97 kB
51
- dist/assets/exporters-B_OBqIyD.js  2,889.87 kB │ gzip: 157.97 kB
53
+ dist/assets/exporters-BraHBeoi.js  2,914.25 kB │ gzip: 163.62 kB
52
54
  dist/assets/epsg-index.generated-BjJrt_0S.js  4,284.21 kB │ gzip: 440.44 kB
53
- dist/assets/index-BKq-M3Mk.js  5,258.30 kB │ gzip: 949.82 kB
55
+ dist/assets/index-BOi3BuUI.js  5,341.23 kB │ gzip: 964.43 kB
54
56
  dist/assets/cesium-DUOzBlqv.js  5,543.11 kB │ gzip: 1,373.81 kB
55
57
  [vite-plugin-static-copy] Copied 4 items.
56
- ✓ built in 55.14s
58
+ ✓ built in 1m
@@ -1,4 +1,4 @@
1
1
 
2
- > @ifc-lite/viewer@1.18.0 typecheck /home/runner/work/ifc-lite/ifc-lite/apps/viewer
2
+ > @ifc-lite/viewer@1.19.0 typecheck /home/runner/work/ifc-lite/ifc-lite/apps/viewer
3
3
  > tsc --noEmit
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,441 @@
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
+
3
439
  ## 1.18.0
4
440
 
5
441
  ### Minor Changes
@@ -1 +1 @@
1
- import{u as o}from"./index-BKq-M3Mk.js";import"./cesium-DUOzBlqv.js";import"./arrow-CZ5kQ26f.js";import"./exporters-B_OBqIyD.js";import"./bcf-DOG9_WPX.js";import"./zip-DBEtpeu6.js";import"./sandbox-jez21HtV.js";import"./lens-CSASnhAL.js";import"./drawing-2d-DoxKMqbO.js";import"./server-client-ncOQVNso.js";import"./ids-DQ5jY0E8.js";const n=700;function v(s){const e=o.getState(),i=e.basketViews.find(t=>t.id===s);if(i){if(e.setDrawing2D(null),e.setDrawing2DPanelVisible(!1),e.updateDrawing2DDisplayOptions({show3DOverlay:!1}),e.clearEntitySelection(),e.restoreBasketEntities(i.entityRefs,s),i.viewpoint){const t=i.transitionMs??n;e.cameraCallbacks.applyViewpoint?.(i.viewpoint,!0,t)}if(i.section){const t=i.section;o.setState({sectionPlane:{...t.plane},drawing2DPanelVisible:!1}),t.plane.enabled?(e.activeTool!=="section"&&e.setSuppressNextSection2DPanelAutoOpen(!0),e.setActiveTool("section")):e.activeTool==="section"&&e.setActiveTool("select")}else{const t=o.getState().sectionPlane;o.setState({sectionPlane:{...t,enabled:!1}}),e.activeTool==="section"&&e.setActiveTool("select")}}}export{v as activateBasketViewFromStore};
1
+ import{u as o}from"./index-BOi3BuUI.js";import"./cesium-DUOzBlqv.js";import"./arrow-CZ5kQ26f.js";import"./exporters-BraHBeoi.js";import"./bcf-DOG9_WPX.js";import"./zip-DBEtpeu6.js";import"./sandbox-Baez7n-t.js";import"./lens-CSASnhAL.js";import"./drawing-2d-DoxKMqbO.js";import"./server-client-BB6cMAXE.js";import"./ids-DQ5jY0E8.js";const n=700;function v(s){const e=o.getState(),i=e.basketViews.find(t=>t.id===s);if(i){if(e.setDrawing2D(null),e.setDrawing2DPanelVisible(!1),e.updateDrawing2DDisplayOptions({show3DOverlay:!1}),e.clearEntitySelection(),e.restoreBasketEntities(i.entityRefs,s),i.viewpoint){const t=i.transitionMs??n;e.cameraCallbacks.applyViewpoint?.(i.viewpoint,!0,t)}if(i.section){const t=i.section;o.setState({sectionPlane:{...t.plane},drawing2DPanelVisible:!1}),t.plane.enabled?(e.activeTool!=="section"&&e.setSuppressNextSection2DPanelAutoOpen(!0),e.setActiveTool("section")):e.activeTool==="section"&&e.setActiveTool("select")}else{const t=o.getState().sectionPlane;o.setState({sectionPlane:{...t,enabled:!1}}),e.activeTool==="section"&&e.setActiveTool("select")}}}export{v as activateBasketViewFromStore};