@ifc-lite/viewer 1.18.0 → 1.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.turbo/turbo-build.log +19 -16
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +444 -0
  4. package/dist/assets/basketViewActivator-CA2CTcVo.js +71 -0
  5. package/dist/assets/{bcf-DOG9_WPX.js → bcf-4K724hw0.js} +18 -18
  6. package/dist/assets/decode-worker-Collf_X_.js +1320 -0
  7. package/dist/assets/{exporters-B_OBqIyD.js → exporters-xbXqEDlO.js} +2547 -1958
  8. package/dist/assets/{geometry.worker-xHHy-9DV.js → geometry.worker-DQEZB2rB.js} +1 -1
  9. package/dist/assets/ids-2WdONLlu.js +2033 -0
  10. package/dist/assets/ifc-lite_bg-4yUkDRD8.wasm +0 -0
  11. package/dist/assets/index-BXeEKqJG.css +1 -0
  12. package/dist/assets/{index-BKq-M3Mk.js → index-D8Epw-e7.js} +51781 -32599
  13. package/dist/assets/index-XwKzDuw6.js +22 -0
  14. package/dist/assets/{native-bridge-SHXiQwFW.js → native-bridge-DKmx1z95.js} +2 -2
  15. package/dist/assets/{sandbox-jez21HtV.js → sandbox-tccwm5Bo.js} +1402 -1329
  16. package/dist/assets/{server-client-ncOQVNso.js → server-client-LoWPK1N2.js} +1 -1
  17. package/dist/assets/three-CDRZThFA.js +4057 -0
  18. package/dist/assets/{wasm-bridge-DyfBSB8z.js → wasm-bridge-BsJGgPMs.js} +1 -1
  19. package/dist/index.html +8 -7
  20. package/dist/samples/building-architecture.ifc +453 -0
  21. package/dist/samples/hello-wall.ifc +1054 -0
  22. package/dist/samples/infra-bridge.ifc +962 -0
  23. package/package.json +13 -7
  24. package/public/samples/building-architecture.ifc +453 -0
  25. package/public/samples/hello-wall.ifc +1054 -0
  26. package/public/samples/infra-bridge.ifc +962 -0
  27. package/src/App.tsx +37 -3
  28. package/src/components/mcp/HeroScene.tsx +876 -0
  29. package/src/components/mcp/McpLanding.tsx +1318 -0
  30. package/src/components/mcp/McpPlayground.tsx +524 -0
  31. package/src/components/mcp/PlaygroundChat.tsx +1097 -0
  32. package/src/components/mcp/PlaygroundViewer.tsx +815 -0
  33. package/src/components/mcp/README.md +171 -0
  34. package/src/components/mcp/data.ts +659 -0
  35. package/src/components/mcp/playground-dispatcher.ts +1649 -0
  36. package/src/components/mcp/playground-files.ts +107 -0
  37. package/src/components/mcp/playground-uploads.ts +122 -0
  38. package/src/components/mcp/types.ts +65 -0
  39. package/src/components/mcp/use-mcp-page.ts +109 -0
  40. package/src/components/viewer/MainToolbar.tsx +23 -2
  41. package/src/components/viewer/PointCloudPanel.tsx +174 -0
  42. package/src/components/viewer/Viewport.tsx +18 -1
  43. package/src/components/viewer/ViewportContainer.tsx +78 -9
  44. package/src/components/viewer/ViewportOverlays.tsx +13 -2
  45. package/src/components/viewer/tools/AddElementOverlay.tsx +43 -2
  46. package/src/components/viewer/usePointCloudLifecycle.ts +64 -0
  47. package/src/components/viewer/usePointCloudSync.ts +98 -0
  48. package/src/generated/mcp-catalog.json +82 -0
  49. package/src/hooks/ingest/pointCloudIngest.ts +391 -0
  50. package/src/hooks/ingest/viewerModelIngest.ts +32 -3
  51. package/src/hooks/useIfcFederation.ts +72 -3
  52. package/src/hooks/useIfcLoader.ts +67 -3
  53. package/src/services/file-dialog.ts +4 -2
  54. package/src/store/index.ts +10 -1
  55. package/src/store/slices/pointCloudSlice.ts +102 -0
  56. package/src/store/types.ts +7 -0
  57. package/vite.config.ts +7 -0
  58. package/dist/assets/basketViewActivator-Cm1QEk_R.js +0 -1
  59. package/dist/assets/ids-DQ5jY0E8.js +0 -1
  60. package/dist/assets/ifc-lite_bg-ADjKXSms.wasm +0 -0
  61. 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.1 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
+ ✓ 4459 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.74 kB │ gzip: 1.23 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,33 @@ 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-BXeEKqJG.css  241.24 kB │ gzip: 35.39 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
34
35
  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
36
+ dist/assets/wasm-bridge-BsJGgPMs.js  1.66 kB │ gzip: 0.80 kB
37
+ dist/assets/basketViewActivator-CA2CTcVo.js  2.31 kB │ gzip: 0.81 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-DKmx1z95.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
43
- dist/assets/bcf-DOG9_WPX.js  40.76 kB │ gzip: 12.67 kB
44
- dist/assets/ids-DQ5jY0E8.js  57.15 kB │ gzip: 12.98 kB
44
+ dist/assets/server-client-LoWPK1N2.js  31.43 kB │ gzip: 7.05 kB
45
+ dist/assets/bcf-4K724hw0.js  41.08 kB │ gzip: 12.74 kB
45
46
  dist/assets/browser-C5TFR7sH.js  67.65 kB │ gzip: 18.94 kB
46
47
  dist/assets/drawing-2d-DoxKMqbO.js  71.45 kB │ gzip: 20.98 kB
48
+ dist/assets/ids-2WdONLlu.js  89.26 kB │ gzip: 15.06 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-tccwm5Bo.js  386.03 kB │ gzip: 62.78 kB
52
+ dist/assets/three-CDRZThFA.js  527.54 kB │ gzip: 132.19 kB
50
53
  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
54
+ dist/assets/exporters-xbXqEDlO.js  2,914.44 kB │ gzip: 163.67 kB
52
55
  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
54
56
  dist/assets/cesium-DUOzBlqv.js  5,543.11 kB │ gzip: 1,373.81 kB
57
+ dist/assets/index-D8Epw-e7.js  5,984.89 kB │ gzip: 1,082.26 kB
55
58
  [vite-plugin-static-copy] Copied 4 items.
56
- ✓ built in 55.14s
59
+ ✓ built in 1m 4s
@@ -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.1 typecheck /home/runner/work/ifc-lite/ifc-lite/apps/viewer
3
3
  > tsc --noEmit
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,449 @@
1
1
  # @ifc-lite/viewer
2
2
 
3
+ ## 1.19.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`7a7cf79`](https://github.com/louistrue/ifc-lite/commit/7a7cf79c181004f9974bd303181aeeaa97d6869d), [`7a7cf79`](https://github.com/louistrue/ifc-lite/commit/7a7cf79c181004f9974bd303181aeeaa97d6869d)]:
8
+ - @ifc-lite/ids@1.14.11
9
+ - @ifc-lite/mcp@0.2.0
10
+
11
+ ## 1.19.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [#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.
16
+
17
+ **E57 (ASTM E2807-11) reader.**
18
+
19
+ - 48-byte FileHeader parser (`ASTM-E57` magic + xmlPhysicalOffset/Length
20
+ - pageSize).
21
+ - Page-CRC stripping: every 1024-byte physical page ends with 4 bytes
22
+ of CRC32-C; we strip them to get the logical view that XML offsets
23
+ reference. CRCs aren't validated (faster + still correct on
24
+ well-formed files).
25
+ - XML parser via `DOMParser` walks `e57Root → data3D → vectorChild` and
26
+ extracts each scan's record count, binary fileOffset, and prototype
27
+ fields.
28
+ - Binary section decoder walks DataPackets, reads bytestream length
29
+ table, decodes uncompressed Float32 / Float64 cartesianX/Y/Z plus
30
+ optional Float colors and Integer u8 colorRed/Green/Blue.
31
+ - ScaledIntegerNode encoding throws a clear error so the host can guide
32
+ the user to a Float-encoded export.
33
+
34
+ **Drop UX.** Dropping a file we can't load (Recap `.rwp/.rwi/.rwcx/.dmt`,
35
+ `.skp`, `.zip`, Faro `.fls`, ASCII `.pts/.xyz`) now shows an
36
+ explanatory toast describing what the format is and what to do
37
+ (typically: "export to E57 / LAS / PLY"). Previously the drop was
38
+ silently rejected.
39
+
40
+ **File picker** accepts `.e57` in browser drop, the native dialog, and
41
+ the recent-files command palette.
42
+
43
+ 7 new pointcloud unit tests cover the FileHeader parser, page-CRC
44
+ stripping (full pages and partial trailing page), the binary packet
45
+ walker on a hand-built single-packet scan with Float64 cartesianX/Y/Z
46
+
47
+ - uint8 RGB, and the ScaledInteger error path.
48
+
49
+ Tests: 48 pointcloud unit tests pass, full repo typecheck (24/24),
50
+ test suite green (22 runs), viewer Vite build emits decode-worker
51
+ chunk correctly.
52
+
53
+ - [#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
54
+ responsive on first contact.
55
+
56
+ **LAZ silently failed to load.** `laz-perf` is shipped as CommonJS,
57
+ which Vite/webpack wrap under `.default` differently across builds.
58
+ The previous probe only checked `lazPerf.createLazPerf` and
59
+ `lazPerf.default` (as a function), so all real-world LAZ loads threw
60
+ "could not find createLazPerf factory". The probe now walks four
61
+ candidate shapes (named export, `default.createLazPerf`, `default` as
62
+ function, namespace-as-function) and reports the visible keys when
63
+ none match.
64
+
65
+ **PLY + PCD now load directly.** Two new streaming sources backed by
66
+ the existing format decoders:
67
+
68
+ - `PlyStreamingSource` — ASCII + binary little/big-endian, optional
69
+ RGB (uchar) + intensity. Header probe (64 KB) + whole-file decode.
70
+ - `PcdStreamingSource` — wraps `decodePcd` (already supported PCD
71
+ ASCII / binary / binary_compressed via inline LZF).
72
+
73
+ Both use stride downsampling for the host's 25M-point cap.
74
+
75
+ **Format detection** sniffs `.ply` (magic "ply"), `.pcd` (`# .P` or
76
+ `.PCD` token), and the existing `.las/.laz` paths.
77
+
78
+ **File picker** accepts `.ply` and `.pcd` in browser drop, the native
79
+ dialog, and the recent-files command palette.
80
+
81
+ **Slider UX.** Default size mode is now `fixed-px` (was `attenuated`).
82
+ The previous default felt inert because the slider in `attenuated` mode
83
+ is the upper _cap_ on adaptive sizing — at typical wide views the
84
+ projected world-radius sat well below the cap, so dragging the slider
85
+ 1↔20 px never engaged. `fixed-px` always uses the slider value, and
86
+ "Auto" is one click away when users want adaptive behaviour.
87
+
88
+ **Worker URL fix.** `worker-client.ts` now imports
89
+ `./decode-worker.ts` (matching geometry's pattern) so Vite's worker
90
+ plugin resolves through the source-alias path. The package's build
91
+ script post-rewrites that to `.js` for dist consumers.
92
+
93
+ Tests: 41 pointcloud unit tests pass (7 new for PLY ascii/binary +
94
+ header probe + truncation), full repo typecheck (24/24), full test
95
+ suite (22 runs green), viewer Vite build emits the decode-worker
96
+ chunk correctly.
97
+
98
+ - [#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.
99
+
100
+ - **LAS streaming** (`.las` files) — header parser + per-point record decoder
101
+ for ASPRS Point Data Formats 0–10, with auto-detection of "8-bit RGB
102
+ in u16 channels" producers and on-the-fly rescaling.
103
+ - **LAZ streaming** (`.laz` files) — wraps `laz-perf` (Apache-2.0) as a
104
+ runtime dep, decoded inside a Web Worker so the main thread stays
105
+ responsive.
106
+ - **Streaming pipeline** — Blob-backed byte source, decode worker with a
107
+ postMessage protocol that ships chunks back as transferable typed-array
108
+ buffers, host-side controller that paces decode, applies a 25M-point
109
+ memory cap with stride downsampling, and reports progress / completion.
110
+ - **Renderer streaming API** — `Renderer.beginPointCloudStream`,
111
+ `appendPointCloudChunk`, `endPointCloudStream`, `removePointCloudAsset`,
112
+ `setPointCloudOptions`. Streamed assets coexist with IFCx-derived
113
+ assets in separate ownership buckets so `setPointClouds` doesn't clobber
114
+ active streams.
115
+ - **Color modes** — `rgb` / `classification` (ASPRS palette) / `intensity` /
116
+ `height` (cool-warm ramp) / `fixed`. Per-point classification + intensity
117
+ travel through the GPU vertex layout and the WGSL shader picks the
118
+ channel based on the active mode uniform.
119
+ - **Viewer integration** — file picker accepts `.las,.laz` (browser drop +
120
+ native dialog), a small bottom-left panel exposes the color modes when
121
+ point clouds are loaded, and the federation registry's `modelIndex`
122
+ flows through streaming ingest for multi-model picking parity.
123
+
124
+ GPU-based point picking is deferred to a follow-up; clicks on points
125
+ return null and don't crash existing mesh selection.
126
+
127
+ - [#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.
128
+
129
+ The 1-pixel `point-list` rendering looked great from far away but turned
130
+ into a halftone screen as you zoomed in — `point-list` topology has no
131
+ `gl_PointSize` equivalent in WebGPU, so density was fixed in screen space.
132
+
133
+ This swaps the pipeline for instanced 6-vertex quad splats and adds a
134
+ post-pass EDL for depth perception.
135
+
136
+ **Splat pipeline**
137
+
138
+ - `topology: 'triangle-list'`, vertex buffer `stepMode: 'instance'`,
139
+ 6 verts emitted per source point. Vertex shader picks a corner from
140
+ `vertex_index` and inflates clip-space position by the active size.
141
+ - Three size modes:
142
+ - `fixed-px` — every splat is N pixels (1..20)
143
+ - `adaptive-world` — splat covers a world-space radius, projected each
144
+ frame; closer = bigger
145
+ - `attenuated` (default) — adaptive but clamped to [1, N] px so splats
146
+ stay visible at far plane and don't blow up to half the screen up close
147
+ - Round shape: fragment discards corners outside the unit disc, so splats
148
+ render as discs not squares.
149
+
150
+ **Eye-Dome Lighting**
151
+
152
+ - New `EdlPass` runs after the existing PostProcessor. Samples 4 (low) or
153
+ 8 (high) neighbouring depths at radius R px, computes mean log-depth-
154
+ diff, darkens by `1 - exp(-300 * meanLog * strength)`. ~9 texture taps
155
+ per pixel. Only active when point clouds are loaded.
156
+ - Reverse-Z aware (`max(0, log(centre) - log(neighbour))`), early-out at
157
+ the far plane.
158
+
159
+ **UI**
160
+
161
+ - `PointCloudPanel` gains size-mode buttons, a 1–20 px slider, a 1–100 mm
162
+ world-radius slider (visible in adaptive/attenuated modes), and an EDL
163
+ toggle with a 0–3 strength slider.
164
+ - New `pointCloudSlice` fields: `pointCloudSizeMode`, `pointCloudPointSize`,
165
+ `pointCloudWorldRadius`, `pointCloudRoundShape`, `pointCloudEdlEnabled`,
166
+ `pointCloudEdlStrength`. Slice clamps numeric ranges.
167
+
168
+ Renderer API additions: `setEdlOptions({enabled, strength, radiusPx,
169
+ highQuality})`. `setPointCloudOptions` now also accepts `sizeMode`,
170
+ `worldRadius`, `roundShape`.
171
+
172
+ ### Patch Changes
173
+
174
+ - [#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.
175
+
176
+ **Streamed point cloud assets leaked across model removal.** The
177
+ renderer handle returned from `beginPointCloudStream` was discarded,
178
+ and streamed nodes are intentionally outside the IFCx
179
+ `setPointClouds` bucket, so removing a model left the GPU buffers
180
+ allocated for the rest of the session. `FederatedModel` now carries
181
+ an optional `pointCloudHandleId`; both ingest sites populate it; a
182
+ new `usePointCloudLifecycle` hook diffs the model map on every
183
+ change and frees handles for models that disappear.
184
+
185
+ **Double cleanup on ingest failure.** The outer `try/catch` in both
186
+ ingest sites called `removePointCloudAsset` + `incCount(-1)`, but
187
+ `ingestPointCloud`'s `onError` already does the same before
188
+ rethrowing. The duplicate cleanup pushed the asset counter negative
189
+ and caused a "remove twice" warning. The outer `catch` now only
190
+ handles store / UI state.
191
+
192
+ **PCD header probe.** The streaming source used the file's reported
193
+ size as the upper bound for the header probe; on truncated files
194
+ that walked off the end with a confusing error. Capped the probe at
195
+ 4 KiB so malformed PCD headers fail with a clear "header > 4 KiB"
196
+ message.
197
+
198
+ - [#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:
199
+
200
+ 1. **IFCx samples extracted zero points.** The entity extractor required
201
+ `bsi::ifc::class` on every node before assigning an `expressId`, but the
202
+ buildingSMART Point*Cloud*\*.ifcx fixtures place `pcd::base64` /
203
+ `points::array` / `points::base64` on nodes that carry only USD
204
+ `xformop`. Those nodes now also become first-class entities (synthetic
205
+ `IfcGeographicElement` type) so the point cloud extractor can emit
206
+ them. Added regression assertions in `verify-dist-hello-wall.mjs`.
207
+
208
+ 2. **`.las` / `.laz` files were silently ignored on single-file load.**
209
+ The drop / picker single-file path goes through `useIfcLoader.loadFile`,
210
+ which only branched on `ifcx` / `glb` / `ifc`. Added the LAS/LAZ branch
211
+ there and wired it into the streaming ingest. Camera fit-to-view now
212
+ triggers from `usePointCloudSync` for points-only scenes (the geometry
213
+ streaming hook bails out early when there are no meshes).
214
+
215
+ - [#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
216
+ point on a streamed point cloud (LAS / LAZ / PLY / PCD / E57).
217
+
218
+ The synthetic `IfcDataStore` that `pointCloudIngest.ts` builds for
219
+ point-cloud-only models stubbed `entities` with only a handful of
220
+ methods (`getId`, `getType`, `getName`, `getGlobalId`) and used method
221
+ names that don't match the real `EntityTable` interface. Picking
222
+ selects the synthetic expressId, which routes through the regular
223
+ property / hover / properties-panel pipeline — that pipeline calls
224
+ `entities.getTypeName`, `entities.getTypeEnum`,
225
+ `properties.getForEntity`, etc., and crashed on the missing
226
+ `getTypeName`.
227
+
228
+ `emptyDataStore()` now produces a stub that matches the real shape:
229
+
230
+ - `entities`: `count=1`, `expressId=Uint32Array([id])`, `typeEnum`,
231
+ plus `getTypeName` → `'IfcGeographicElement'`, `getName` → file
232
+ name, `getGlobalId` → `pointcloud-<id>`, and `getTypeEnum`,
233
+ `getByType`, `hasGeometry`, `getExpressIdByGlobalId`,
234
+ `getGlobalIdMap` covered.
235
+ - `properties`: real `PropertyTable` shape — `entityIndex`,
236
+ `psetIndex`, `propIndex`, `getForEntity`, `getPropertyValue`,
237
+ `findByProperty` (all empty / no-op).
238
+ - `quantities` / `relationships`: matching empty stubs.
239
+ - `entityIndex.byType` includes `IFCGEOGRAPHICELEMENT → [id]` so type
240
+ filters resolve.
241
+
242
+ `emptyDataStore` now takes the synthetic `expressId` and `fileName` so
243
+ the stub round-trips real data instead of `undefined`.
244
+
245
+ - [#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
246
+ sessions and silent rendering stalls.
247
+
248
+ **Federation relabel for streamed point clouds.**
249
+ `ingestPointCloud` now emits a synthetic entry on
250
+ `geometryResult.pointClouds`. Without this, `useIfcFederation`'s
251
+ `idOffset` fold + `relabelPointCloudAsset` call never fired for
252
+ LAS/LAZ/PLY/PCD/E57 streams, so picked `expressId`s for streamed
253
+ assets collided across federated models.
254
+
255
+ **Sync-throw cleanup.** Wrap `streamPointCloud()` in `try/catch`
256
+ inside `ingestPointCloud`. The renderer asset and asset-count
257
+ increment happen before the worker spins up, so a sync throw during
258
+ validation/worker setup used to leak both. We now `removePointCloudAsset`
259
+
260
+ - `onCountChange(-1)` before re-throwing.
261
+
262
+ **`setPointClouds()` shrinks bounds correctly.** The replace path
263
+ called `expandModelBoundsForPointClouds` (grow-only). Reloading IFCx
264
+ with a smaller scan kept stale extents until `clear`. Switched to
265
+ `recomputeModelBounds()` so bounds re-baseline from current state.
266
+
267
+ **`requestRender()` after every mutation.** `appendPointCloudChunk`,
268
+ `setPointCloudOptions`, `setEdlOptions`, `setPointClouds`,
269
+ `addPointClouds`, `clearPointClouds`, `removePointCloudAsset`,
270
+ `endPointCloudStream` now schedule a frame. Previously streamed
271
+ chunks could sit invisible until an unrelated camera move triggered
272
+ the next render.
273
+
274
+ **Worker cancel race.** `worker-client.next()` now re-checks
275
+ `signal.aborted` after `await session.send()`. A chunk that won the
276
+ race against `cancel()` would otherwise still call `onChunk` after
277
+ the host returned to the caller.
278
+
279
+ **Multi-scan E57 rejection.** `parseE57Xml` now records `hasPose` per
280
+ Data3D entry. `decodeE57` rejects multi-scan files where any entry
281
+ carries a `<pose>` element, with a clear "registered multi-scan;
282
+ re-export as merged" error. Previously such files silently
283
+ concatenated in scan-local space and rendered misaligned.
284
+
285
+ Verified: 62 pointcloud unit tests (1 new for pose flag), full repo
286
+ typecheck (24/24), viewer Vite build green.
287
+
288
+ - [#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.
289
+
290
+ Critical visual / correctness fixes:
291
+
292
+ - Point splats rendered ~2× too large because the shader treated the
293
+ user-facing `pointSizePx` (diameter) as the splat radius. Fixed in
294
+ both the live splat shader and the picker shader so click targets
295
+ match the rendered disc.
296
+ - Routed every detected point-cloud format (`ply`, `pcd`, `e57`) through
297
+ the streaming ingest in both `useIfcLoader` (single-file drop) and
298
+ `useIfcFederation` (multi-file). Previously only `las/laz` got the
299
+ pointcloud branch; `ply/pcd/e57` fell through into the IFC STEP path.
300
+ - Federation: applied `idOffset` to `geometryResult.pointClouds` too so
301
+ multi-pointcloud-model loads don't collide on local `expressId`.
302
+ - `expressId` defaulted to `1` on every ingest, so multiple inline LAS
303
+ loads collided. Now uses a process-local synthetic counter.
304
+ - E57 integer color channels are commonly u16 (0..65535); reader was
305
+ forcing u8 reads, distorting RGB. Now picks element width from the
306
+ declared min/max range.
307
+ - PCD `applyStride` preserved positions + colors but dropped intensity
308
+ and classification, so those color modes silently broke on files
309
+ past the 25M-point downsample cap.
310
+ - Inline `uploadAssetToGpu` forwards `intensities` + `classifications`
311
+ (added to `PointCloudAsset.chunk` shape).
312
+ - Model bounds recomputed after `removePointCloudAsset` /
313
+ `clearPointClouds` — previously stayed oversized, breaking
314
+ fit-to-view and section sliders.
315
+ - `usePointCloudLifecycle` disposes a model's GPU asset when the model
316
+ stays in the store but its `pointCloudHandleId` changes (re-stream of
317
+ the same file used to leak the old handle).
318
+ - `resetViewerState` now clears the point-cloud slice runtime fields so
319
+ loading a new file doesn't inherit the previous file's color mode /
320
+ size / EDL state.
321
+
322
+ Correctness / robustness:
323
+
324
+ - `streamPointCloud`'s host now closes the source on probe + onOpen
325
+ failures (single try/finally wrapping the whole open-and-decode
326
+ flow), so worker-backed sources don't leak the decoder on parse
327
+ errors or aborts.
328
+ - `worker-client.close()` clears cached `info`; subsequent `open()`
329
+ actually re-opens instead of returning stale info next to a null
330
+ `sourceId`.
331
+ - `LasStreamingSource.open()` and `LazStreamingSource.open()` are
332
+ atomic on failure: state is committed only after every step
333
+ succeeds, so a retry rerruns the probe + RGB-scale detection
334
+ cleanly. LAZ also frees malloc'd wasm pointers in the catch path.
335
+ - PLY decoder rejects files where `vertex` isn't the first element
336
+ (decoder reads from `header.bodyOffset`; non-leading vertex would
337
+ silently produce garbage).
338
+ - `decodePointsArray` validates each `colors[i]` is a `[r,g,b]` triple
339
+ before indexing, so malformed schemas fail with a clear message.
340
+ - `useIfcLoader` LAS/LAZ/PLY/PCD/E57 branch is guarded by
341
+ `loadSessionRef` on both error and success paths so a newer load can
342
+ replace an in-flight one without overwriting the newer model state;
343
+ stale renderer handle is freed.
344
+
345
+ Critical webhook fixes:
346
+
347
+ - `ViewportOverlays.tsx` had three imports between executable code;
348
+ hoisted them above the `const isDesktop = isTauri()` declaration.
349
+ - `edl-pass.ts` used `0u` for `texture_depth_multisampled_2d`'s
350
+ `sample_index`; WGSL spec requires `i32`.
351
+ - `pcd.test.ts` switched from `__dirname` to
352
+ `fileURLToPath(import.meta.url)` so it works outside vitest's
353
+ CommonJS-compat shim.
354
+
355
+ UX polish:
356
+
357
+ - `PointCloudPanel` toggle buttons expose `aria-pressed` so screen
358
+ readers announce the active option.
359
+ - `pointCloudSlice` setters reject `NaN`/`Infinity` (Math.min/max
360
+ passes them through unchanged).
361
+ - `BlobByteSource.read` clamps a negative `start` to `0`.
362
+ - File-dialog filters split GLB out of the IFC bucket into a "Mesh
363
+ Files" group.
364
+
365
+ The flattenMatrix transpose flagged in the review is actually correct
366
+ for USD's row-major-with-translation-in-row-3 convention (verified by
367
+ inspecting the Point_Cloud_S1 sample's transform; the rendered scan is
368
+ at the right world position). Added a clarifying comment so future
369
+ reviewers don't reach for the wrong fix.
370
+
371
+ - [#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.
372
+
373
+ P1 (real correctness):
374
+
375
+ - Federation: streamed point clouds now get the post-`idOffset` global
376
+ expressId in picking output. New `Renderer.relabelPointCloudAsset()`
377
+ updates a per-asset uniform (`flags.x`) the shader prefers over the
378
+ per-vertex attribute, so federation is just a metadata write — no
379
+ GPU buffer rewrite. `useIfcFederation.addModel` calls it after the
380
+ pointClouds offset is applied.
381
+ - Section-plane range now folds in `pointCloudRenderer.getBounds()`, so
382
+ pure point-cloud scenes don't fall through to `[-100, 100]` and mixed
383
+ scenes don't clip points outside a smaller mesh-only range.
384
+ - `recomputeModelBounds()` now recomputes from scratch (mesh baseline +
385
+ current pc bounds) instead of growing-only. Previously, removing one
386
+ of several point clouds left stale oversized extents until every
387
+ point cloud was gone.
388
+ - `streamPointCloud` validates `chunkSize > 0` upfront; `LasStreamingSource`
389
+ and `LazStreamingSource` reject `maxPoints <= 0`. Prevents
390
+ zero-progress decode loops from accidental misuse.
391
+ - E57 merge uses `some()` instead of `every()`; mixed-attribute files
392
+ no longer drop colour/intensity for the whole merged cloud just
393
+ because one scan lacks the channel.
394
+ - E57 intensity is now allocated for `Integer`-encoded prototypes too
395
+ (was silently dropped); `ScaledInteger` throws a clear error.
396
+
397
+ P2 (robustness):
398
+
399
+ - `xml-mini` rejects truncated input — unclosed elements throw instead
400
+ of silently returning a partial tree.
401
+ - `worker-client.next()` now sends a `kind: 'abort'` to the worker when
402
+ the signal fires mid-flight. Previously cancel returned to the caller
403
+ while the worker kept decoding.
404
+ - `decodePointsArray` rejects empty arrays (was producing ±Infinity
405
+ bbox); `decodePointsBase64` rejects empty strings (no silent
406
+ downgrade to uncoloured cloud).
407
+ - `transformPositionsZUpToYUp` guards against zero / non-finite
408
+ homogeneous `w` (malformed `usd::xformop` matrices).
409
+
410
+ P3 (polish):
411
+
412
+ - `POINT_CLOUD_DEFAULTS` is now an exported constant shared by the
413
+ slice initializer and `resetViewerState`, so the two paths can't
414
+ drift.
415
+ - Replaced `as any` cast around `AbortSignal.any` with a typed
416
+ intersection.
417
+ - Doc comment on `pointCloudSizeMode` now matches the actual default
418
+ (`fixed-px`).
419
+
420
+ Verified: 61 pointcloud unit tests pass, full repo typecheck (24/24),
421
+ test suite green (22 runs), viewer Vite build emits decode-worker
422
+ chunk correctly.
423
+
424
+ - [#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
425
+ the renderer's Y-up convention, matching the IFCx ingest path.
426
+
427
+ Without this, scans rendered rotated 90° onto their side because the
428
+ renderer is Y-up internally and LIDAR / surveying formats store data
429
+ Z-up by convention. The IFCx path applied the swap inside
430
+ `pointcloud-extractor.ts`; the streaming path went straight from the
431
+ worker's decoded chunk into `appendPointCloudChunk`, skipping the
432
+ swap.
433
+
434
+ `ingestPointCloud` now wraps `onChunk` to re-orient positions and
435
+ bbox before forwarding to the renderer:
436
+ Z-up: X=right, Y=forward, Z=up
437
+ Y-up: X=right, Y=up, Z=back (negate Y to keep right-hand rule)
438
+
439
+ Mirrors the geometry / pointcloud extractors' existing handling.
440
+
441
+ - 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)]:
442
+ - @ifc-lite/pointcloud@0.2.0
443
+ - @ifc-lite/renderer@1.18.0
444
+ - @ifc-lite/geometry@1.17.0
445
+ - @ifc-lite/parser@2.3.0
446
+
3
447
  ## 1.18.0
4
448
 
5
449
  ### Minor Changes
@@ -0,0 +1,71 @@
1
+ import { u as o, __tla as __tla_0 } from "./index-D8Epw-e7.js";
2
+ import "./cesium-DUOzBlqv.js";
3
+ import "./arrow-CZ5kQ26f.js";
4
+ import { __tla as __tla_1 } from "./exporters-xbXqEDlO.js";
5
+ import "./bcf-4K724hw0.js";
6
+ import "./zip-DBEtpeu6.js";
7
+ import { __tla as __tla_2 } from "./sandbox-tccwm5Bo.js";
8
+ import "./lens-CSASnhAL.js";
9
+ import "./drawing-2d-DoxKMqbO.js";
10
+ import { __tla as __tla_3 } from "./server-client-LoWPK1N2.js";
11
+ import { __tla as __tla_4 } from "./ids-2WdONLlu.js";
12
+ import "./three-CDRZThFA.js";
13
+ let T;
14
+ let __tla = Promise.all([
15
+ (()=>{
16
+ try {
17
+ return __tla_0;
18
+ } catch {}
19
+ })(),
20
+ (()=>{
21
+ try {
22
+ return __tla_1;
23
+ } catch {}
24
+ })(),
25
+ (()=>{
26
+ try {
27
+ return __tla_2;
28
+ } catch {}
29
+ })(),
30
+ (()=>{
31
+ try {
32
+ return __tla_3;
33
+ } catch {}
34
+ })(),
35
+ (()=>{
36
+ try {
37
+ return __tla_4;
38
+ } catch {}
39
+ })()
40
+ ]).then(async ()=>{
41
+ const n = 700;
42
+ T = function(s) {
43
+ const e = o.getState(), i = e.basketViews.find((t)=>t.id === s);
44
+ if (i) {
45
+ if (e.setDrawing2D(null), e.setDrawing2DPanelVisible(!1), e.updateDrawing2DDisplayOptions({
46
+ show3DOverlay: !1
47
+ }), e.clearEntitySelection(), e.restoreBasketEntities(i.entityRefs, s), i.viewpoint) {
48
+ const t = i.transitionMs ?? n;
49
+ e.cameraCallbacks.applyViewpoint?.(i.viewpoint, !0, t);
50
+ }
51
+ if (i.section) {
52
+ const t = i.section;
53
+ o.setState({
54
+ sectionPlane: {
55
+ ...t.plane
56
+ },
57
+ drawing2DPanelVisible: !1
58
+ }), t.plane.enabled ? (e.activeTool !== "section" && e.setSuppressNextSection2DPanelAutoOpen(!0), e.setActiveTool("section")) : e.activeTool === "section" && e.setActiveTool("select");
59
+ } else {
60
+ const t = o.getState().sectionPlane;
61
+ o.setState({
62
+ sectionPlane: {
63
+ ...t,
64
+ enabled: !1
65
+ }
66
+ }), e.activeTool === "section" && e.setActiveTool("select");
67
+ }
68
+ }
69
+ };
70
+ });
71
+ export { T as activateBasketViewFromStore, __tla };