@ifc-lite/viewer 1.17.4 → 1.18.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 (196) hide show
  1. package/.turbo/turbo-build.log +20 -17
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +630 -0
  4. package/DESKTOP_CONTRACT_VERSION +1 -1
  5. package/dist/assets/{basketViewActivator-BmnNtVfZ.js → basketViewActivator-Cm1QEk_R.js} +1 -1
  6. package/dist/assets/drawing-2d-DoxKMqbO.js +257 -0
  7. package/dist/assets/{exporters-ChAtBmlj.js → exporters-B_OBqIyD.js} +3479 -2845
  8. package/dist/assets/{geometry.worker-BQ0rzNo-.js → geometry.worker-xHHy-9DV.js} +1 -1
  9. package/dist/assets/ids-DQ5jY0E8.js +1 -0
  10. package/dist/assets/ifc-lite_bg-ADjKXSms.wasm +0 -0
  11. package/dist/assets/{index-Co8E2-FE.js → index-BKq-M3Mk.js} +55873 -40593
  12. package/dist/assets/index-COnQRuqY.css +1 -0
  13. package/dist/assets/{native-bridge-BRvbckFQ.js → native-bridge-SHXiQwFW.js} +104 -104
  14. package/dist/assets/sandbox-jez21HtV.js +9627 -0
  15. package/dist/assets/{server-client-BV8zHZ7Y.js → server-client-ncOQVNso.js} +1 -1
  16. package/dist/assets/{wasm-bridge-g01g7T9b.js → wasm-bridge-DyfBSB8z.js} +1 -1
  17. package/dist/index.html +8 -7
  18. package/index.html +1 -0
  19. package/package.json +13 -13
  20. package/src/App.tsx +16 -2
  21. package/src/apache-arrow.d.ts +30 -0
  22. package/src/components/viewer/AddElementPanel.tsx +758 -0
  23. package/src/components/viewer/BulkPropertyEditor.tsx +7 -0
  24. package/src/components/viewer/CesiumOverlay.tsx +62 -19
  25. package/src/components/viewer/ChatPanel.tsx +259 -93
  26. package/src/components/viewer/CommandPalette.tsx +56 -7
  27. package/src/components/viewer/EntityContextMenu.tsx +168 -4
  28. package/src/components/viewer/ExportChangesButton.tsx +25 -5
  29. package/src/components/viewer/ExportDialog.tsx +19 -1
  30. package/src/components/viewer/MainToolbar.tsx +73 -13
  31. package/src/components/viewer/PropertiesPanel.tsx +237 -23
  32. package/src/components/viewer/SearchInline.tsx +669 -0
  33. package/src/components/viewer/SearchModal.filter.builder.tsx +766 -0
  34. package/src/components/viewer/SearchModal.filter.tsx +514 -0
  35. package/src/components/viewer/SearchModal.text.tsx +388 -0
  36. package/src/components/viewer/SearchModal.tsx +235 -0
  37. package/src/components/viewer/SettingsPage.tsx +252 -101
  38. package/src/components/viewer/ThemeSwitch.tsx +63 -7
  39. package/src/components/viewer/ToolOverlays.tsx +5 -0
  40. package/src/components/viewer/ViewerLayout.tsx +25 -4
  41. package/src/components/viewer/Viewport.tsx +25 -3
  42. package/src/components/viewer/ViewportContainer.tsx +51 -64
  43. package/src/components/viewer/ViewportOverlays.tsx +5 -2
  44. package/src/components/viewer/annotations/AnnotationDropInput.tsx +203 -0
  45. package/src/components/viewer/annotations/AnnotationLayer.tsx +287 -0
  46. package/src/components/viewer/annotations/AnnotationPin.tsx +90 -0
  47. package/src/components/viewer/annotations/AnnotationPopover.tsx +296 -0
  48. package/src/components/viewer/bcf/BCFTopicDetail.tsx +4 -4
  49. package/src/components/viewer/chat/ModelSelector.tsx +90 -54
  50. package/src/components/viewer/lists/ListPanel.tsx +14 -21
  51. package/src/components/viewer/properties/GeoreferencingPanel.tsx +113 -51
  52. package/src/components/viewer/properties/LocationMap.tsx +9 -7
  53. package/src/components/viewer/properties/ModelMetadataPanel.tsx +1 -1
  54. package/src/components/viewer/properties/RawStepCard.tsx +332 -0
  55. package/src/components/viewer/properties/RawStepRow.tsx +261 -0
  56. package/src/components/viewer/properties/ScheduleCard.tsx +224 -0
  57. package/src/components/viewer/properties/TaskEditCard.tsx +510 -0
  58. package/src/components/viewer/properties/raw-step-format.ts +193 -0
  59. package/src/components/viewer/schedule/AnimationSettingsPopover.tsx +542 -0
  60. package/src/components/viewer/schedule/GanttDependencyArrows.tsx +89 -0
  61. package/src/components/viewer/schedule/GanttDragTooltip.tsx +48 -0
  62. package/src/components/viewer/schedule/GanttEmptyState.tsx +97 -0
  63. package/src/components/viewer/schedule/GanttPanel.tsx +295 -0
  64. package/src/components/viewer/schedule/GanttTaskBar.tsx +199 -0
  65. package/src/components/viewer/schedule/GanttTaskTree.tsx +250 -0
  66. package/src/components/viewer/schedule/GanttTimeline.tsx +305 -0
  67. package/src/components/viewer/schedule/GanttToolbar.tsx +406 -0
  68. package/src/components/viewer/schedule/GenerateAdvancedPanel.tsx +147 -0
  69. package/src/components/viewer/schedule/GenerateScheduleDialog.tsx +392 -0
  70. package/src/components/viewer/schedule/HeightStrategyPanel.tsx +120 -0
  71. package/src/components/viewer/schedule/generate-schedule.test.ts +439 -0
  72. package/src/components/viewer/schedule/generate-schedule.ts +648 -0
  73. package/src/components/viewer/schedule/schedule-animator.test.ts +452 -0
  74. package/src/components/viewer/schedule/schedule-animator.ts +488 -0
  75. package/src/components/viewer/schedule/schedule-selection.test.ts +148 -0
  76. package/src/components/viewer/schedule/schedule-selection.ts +163 -0
  77. package/src/components/viewer/schedule/schedule-utils.ts +223 -0
  78. package/src/components/viewer/schedule/useConstructionSequence.ts +156 -0
  79. package/src/components/viewer/schedule/useGanttBarDrag.test.ts +90 -0
  80. package/src/components/viewer/schedule/useGanttBarDrag.ts +305 -0
  81. package/src/components/viewer/schedule/useGanttSelection3DHighlight.ts +152 -0
  82. package/src/components/viewer/schedule/useOverlayCompositor.ts +108 -0
  83. package/src/components/viewer/selectionHandlers.ts +446 -0
  84. package/src/components/viewer/tools/AddElementOverlay.tsx +540 -0
  85. package/src/components/viewer/tools/SectionCapControls.tsx +237 -0
  86. package/src/components/viewer/tools/SectionPanel.tsx +39 -18
  87. package/src/components/viewer/useAnimationLoop.ts +9 -1
  88. package/src/components/viewer/useDuplicateShortcut.ts +77 -0
  89. package/src/components/viewer/useMouseControls.ts +9 -1
  90. package/src/components/viewer/useRenderUpdates.ts +1 -1
  91. package/src/hooks/ids/idsDataAccessor.ts +60 -24
  92. package/src/hooks/ingest/viewerModelIngest.ts +7 -2
  93. package/src/hooks/useIfcFederation.ts +326 -71
  94. package/src/hooks/useIfcLoader.ts +23 -10
  95. package/src/hooks/useKeyboardShortcuts.ts +25 -0
  96. package/src/hooks/useSandbox.ts +1 -1
  97. package/src/hooks/useSearchIndex.ts +125 -0
  98. package/src/hooks/useViewControls.ts +13 -5
  99. package/src/index.css +550 -10
  100. package/src/lib/desktop-entitlement.ts +2 -4
  101. package/src/lib/geo/cesium-bridge.ts +15 -7
  102. package/src/lib/geo/effective-georef.test.ts +73 -0
  103. package/src/lib/geo/effective-georef.ts +111 -0
  104. package/src/lib/geo/reproject.ts +105 -19
  105. package/src/lib/llm/byok-guard.test.ts +77 -0
  106. package/src/lib/llm/byok-guard.ts +39 -0
  107. package/src/lib/llm/free-models.test.ts +0 -6
  108. package/src/lib/llm/models.ts +104 -42
  109. package/src/lib/llm/stream-client.ts +74 -110
  110. package/src/lib/llm/stream-direct.test.ts +130 -0
  111. package/src/lib/llm/stream-direct.ts +316 -0
  112. package/src/lib/llm/system-prompt.test.ts +14 -0
  113. package/src/lib/llm/system-prompt.ts +102 -1
  114. package/src/lib/llm/types.ts +20 -2
  115. package/src/lib/recent-files.ts +38 -4
  116. package/src/lib/scripts/templates/bim-globals.d.ts +136 -114
  117. package/src/lib/scripts/templates/construction-schedule.ts +223 -0
  118. package/src/lib/scripts/templates.ts +7 -0
  119. package/src/lib/search/common-ifc-types.ts +36 -0
  120. package/src/lib/search/filter-evaluate.test.ts +537 -0
  121. package/src/lib/search/filter-evaluate.ts +610 -0
  122. package/src/lib/search/filter-rules.test.ts +119 -0
  123. package/src/lib/search/filter-rules.ts +198 -0
  124. package/src/lib/search/filter-schema.test.ts +233 -0
  125. package/src/lib/search/filter-schema.ts +146 -0
  126. package/src/lib/search/recent-searches.test.ts +116 -0
  127. package/src/lib/search/recent-searches.ts +93 -0
  128. package/src/lib/search/result-export.test.ts +101 -0
  129. package/src/lib/search/result-export.ts +104 -0
  130. package/src/lib/search/saved-filters.test.ts +118 -0
  131. package/src/lib/search/saved-filters.ts +154 -0
  132. package/src/lib/search/tier0-scan.test.ts +196 -0
  133. package/src/lib/search/tier0-scan.ts +237 -0
  134. package/src/lib/search/tier1-index.test.ts +242 -0
  135. package/src/lib/search/tier1-index.ts +448 -0
  136. package/src/main.tsx +1 -10
  137. package/src/sdk/adapters/export-adapter.test.ts +434 -1
  138. package/src/sdk/adapters/export-adapter.ts +404 -1
  139. package/src/sdk/adapters/export-schedule-splice.test.ts +127 -0
  140. package/src/sdk/adapters/export-schedule-splice.ts +87 -0
  141. package/src/sdk/adapters/model-compat.ts +8 -2
  142. package/src/sdk/adapters/schedule-adapter.ts +73 -0
  143. package/src/sdk/adapters/store-adapter.ts +201 -0
  144. package/src/sdk/adapters/visibility-adapter.ts +3 -0
  145. package/src/sdk/local-backend.ts +16 -8
  146. package/src/services/api-keys.ts +73 -0
  147. package/src/services/desktop-export.ts +3 -1
  148. package/src/services/desktop-native-metadata.ts +41 -18
  149. package/src/services/file-dialog.ts +4 -1
  150. package/src/services/tauri-modules.d.ts +25 -0
  151. package/src/store/basketVisibleSet.ts +3 -0
  152. package/src/store/constants.ts +20 -2
  153. package/src/store/globalId.ts +4 -1
  154. package/src/store/index.ts +82 -6
  155. package/src/store/slices/addElementMeshes.ts +365 -0
  156. package/src/store/slices/addElementSlice.ts +275 -0
  157. package/src/store/slices/annotationsSlice.test.ts +133 -0
  158. package/src/store/slices/annotationsSlice.ts +251 -0
  159. package/src/store/slices/cesiumSlice.ts +5 -0
  160. package/src/store/slices/chatSlice.test.ts +6 -76
  161. package/src/store/slices/chatSlice.ts +17 -58
  162. package/src/store/slices/dataSlice.test.ts +23 -4
  163. package/src/store/slices/dataSlice.ts +1 -1
  164. package/src/store/slices/modelSlice.test.ts +67 -9
  165. package/src/store/slices/modelSlice.ts +39 -7
  166. package/src/store/slices/mutationSlice.ts +964 -3
  167. package/src/store/slices/overlayCompositor.test.ts +164 -0
  168. package/src/store/slices/overlaySlice.test.ts +93 -0
  169. package/src/store/slices/overlaySlice.ts +151 -0
  170. package/src/store/slices/pinboardSlice.test.ts +6 -1
  171. package/src/store/slices/playbackSlice.ts +128 -0
  172. package/src/store/slices/schedule-edit-helpers.test.ts +97 -0
  173. package/src/store/slices/schedule-edit-helpers.ts +179 -0
  174. package/src/store/slices/scheduleSlice.test.ts +694 -0
  175. package/src/store/slices/scheduleSlice.ts +1330 -0
  176. package/src/store/slices/searchSlice.test.ts +342 -0
  177. package/src/store/slices/searchSlice.ts +341 -0
  178. package/src/store/slices/sectionSlice.test.ts +87 -7
  179. package/src/store/slices/sectionSlice.ts +151 -5
  180. package/src/store/slices/selectionSlice.test.ts +46 -0
  181. package/src/store/slices/selectionSlice.ts +20 -0
  182. package/src/store/slices/uiSlice.ts +28 -5
  183. package/src/store/types.ts +26 -0
  184. package/src/store.ts +14 -0
  185. package/src/utils/nativeSpatialDataStore.ts +4 -1
  186. package/src/utils/viewportUtils.ts +7 -2
  187. package/src/vite-env.d.ts +0 -4
  188. package/dist/assets/drawing-2d-gWfpdfYe.js +0 -257
  189. package/dist/assets/ids-B4jTqB1O.js +0 -1
  190. package/dist/assets/ifc-lite_bg-BX4E7TX8.wasm +0 -0
  191. package/dist/assets/index-DckuDqlv.css +0 -1
  192. package/dist/assets/sandbox-DZiNLNMk.js +0 -5933
  193. package/src/components/viewer/UpgradePage.tsx +0 -71
  194. package/src/lib/desktop/ClerkDesktopEntitlementSync.tsx +0 -175
  195. package/src/lib/llm/ClerkChatSync.tsx +0 -74
  196. package/src/lib/llm/clerk-auth.ts +0 -62
@@ -225,6 +225,13 @@ export function BulkPropertyEditor({ trigger }: BulkPropertyEditorProps) {
225
225
  // Yield to browser so dialog shell + spinner paint first
226
226
  initTimerRef.current = setTimeout(() => {
227
227
  const dataStore = selectedModel.ifcDataStore;
228
+ // The early return above already gates on `selectedModel?.ifcDataStore`,
229
+ // but the closure-captured `selectedModel` reference is union-typed,
230
+ // so re-narrow here for the timeout callback.
231
+ if (!dataStore) {
232
+ setIsInitializing(false);
233
+ return;
234
+ }
228
235
  const entities = dataStore.entities;
229
236
 
230
237
  // Storeys
@@ -175,6 +175,7 @@ function buildModelMatrix(
175
175
  const absc = mapConversion?.xAxisAbscissa ?? 1.0;
176
176
  const ordi = mapConversion?.xAxisOrdinate ?? 0.0;
177
177
  const bounds = coordinateInfo?.originalBounds;
178
+ // Viewer bounds are already in metres (geometry engine converts from IFC native unit)
178
179
  const mvx = bounds ? (bounds.min.x + bounds.max.x) / 2 : 0;
179
180
  const mvy = bounds ? (bounds.min.y + bounds.max.y) / 2 : 0;
180
181
  const mvz = bounds ? (bounds.min.z + bounds.max.z) / 2 : 0;
@@ -182,7 +183,7 @@ function buildModelMatrix(
182
183
  let placementHeight = bridge.modelOrigin.height;
183
184
  if (clamp && terrainH !== null) {
184
185
  const minY = bounds?.min.y ?? 0;
185
- const bottomOffset = mvy - minY;
186
+ const bottomOffset = mvy - minY; // already in metres
186
187
  placementHeight = terrainH + bottomOffset;
187
188
  }
188
189
 
@@ -190,12 +191,16 @@ function buildModelMatrix(
190
191
  bridge.modelOrigin.longitude, bridge.modelOrigin.latitude, placementHeight,
191
192
  );
192
193
  const enuToEcef = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
194
+ // No lengthUnitScale here — viewer-space GLB vertices are already in metres.
193
195
  const sa = hScale * absc, so = hScale * ordi;
196
+ const tx = -(sa * mvx + so * mvz);
197
+ const ty = -(so * mvx - sa * mvz);
198
+ const tz = -mvy;
194
199
  const ifcToEnu = new Cesium.Matrix4(
195
- sa, -so, 0, -(sa * mvx + so * mvz),
196
- so, sa, 0, -(so * mvx - sa * mvz),
197
- 0, 0, 1, -mvy,
198
- 0, 0, 0, 1,
200
+ sa, 0, so, tx,
201
+ so, 0, -sa, ty,
202
+ 0, 1, 0, tz,
203
+ 0, 0, 0, 1,
199
204
  );
200
205
  return Cesium.Matrix4.multiply(enuToEcef, ifcToEnu, new Cesium.Matrix4());
201
206
  }
@@ -205,6 +210,8 @@ export interface CesiumOverlayProps {
205
210
  projectedCRS?: ProjectedCRS;
206
211
  coordinateInfo?: CoordinateInfo;
207
212
  geometryResult?: GeometryResult | null;
213
+ /** IFC project length unit → metres (e.g. 0.001 for mm models). Default 1. */
214
+ lengthUnitScale?: number;
208
215
  }
209
216
 
210
217
  export function CesiumOverlay({
@@ -212,6 +219,7 @@ export function CesiumOverlay({
212
219
  projectedCRS,
213
220
  coordinateInfo,
214
221
  geometryResult,
222
+ lengthUnitScale = 1,
215
223
  }: CesiumOverlayProps) {
216
224
  const containerRef = useRef<HTMLDivElement>(null);
217
225
  const viewerRef = useRef<InstanceType<typeof import('cesium').Viewer> | null>(null);
@@ -227,6 +235,7 @@ export function CesiumOverlay({
227
235
  const ionToken = useViewerStore((s) => s.cesiumIonToken);
228
236
  const terrainEnabled = useViewerStore((s) => s.cesiumTerrainEnabled);
229
237
  const terrainClamp = useViewerStore((s) => s.cesiumTerrainClamp);
238
+ const setCesiumTerrainClamp = useViewerStore((s) => s.setCesiumTerrainClamp);
230
239
  const terrainHeight = useViewerStore((s) => s.cesiumTerrainHeight);
231
240
  const setCesiumTerrainHeight = useViewerStore((s) => s.setCesiumTerrainHeight);
232
241
  const setCesiumTerrainClipY = useViewerStore((s) => s.setCesiumTerrainClipY);
@@ -238,6 +247,9 @@ export function CesiumOverlay({
238
247
  terrainClampRef.current = terrainClamp;
239
248
  terrainHeightRef.current = terrainHeight;
240
249
 
250
+ // Track whether we've auto-clamped to terrain (only once, so user can still uncheck)
251
+ const autoClampedRef = useRef(false);
252
+
241
253
  // Track the Cesium model (IFC geometry loaded as glTF for correct world positioning)
242
254
  const cesiumModelRef = useRef<{ modelMatrix: any; destroy?: () => void } | null>(null);
243
255
  const glbCacheRef = useRef<{ meshCount: number; glb: Uint8Array } | null>(null);
@@ -386,7 +398,7 @@ export function CesiumOverlay({
386
398
  let cancelled = false;
387
399
 
388
400
  (async () => {
389
- const bridge = await createCesiumBridge(mapConversion, projectedCRS, coordinateInfo);
401
+ const bridge = await createCesiumBridge(mapConversion, projectedCRS, coordinateInfo, lengthUnitScale);
390
402
  if (cancelled) return;
391
403
 
392
404
  if (!bridge) {
@@ -396,6 +408,7 @@ export function CesiumOverlay({
396
408
 
397
409
  const prevBridge = bridgeRef.current;
398
410
  bridgeRef.current = bridge;
411
+ autoClampedRef.current = false; // reset for new bridge
399
412
  // Bump version so terrain query effect re-runs now that bridge is ready
400
413
  setBridgeVersion((v) => v + 1);
401
414
 
@@ -411,11 +424,22 @@ export function CesiumOverlay({
411
424
  );
412
425
 
413
426
  if (isFirstPosition) {
414
- // First time: instant jump to isometric view
415
- const hpr = new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-45), 300);
416
- viewer.camera.lookAt(target, hpr);
417
- viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
418
- viewer.scene.requestRender();
427
+ // First time: fly to the model location.
428
+ // On first load terrain tiles may not be ready, so globe.getHeight
429
+ // can return undefined. Use flyTo which handles terrain automatically,
430
+ // targeting a safe altitude above the model origin.
431
+ const safeHeight = Math.max(modelOrigin.height, 100);
432
+ viewer.camera.flyTo({
433
+ destination: Cesium.Cartesian3.fromDegrees(
434
+ modelOrigin.longitude, modelOrigin.latitude, safeHeight + 500,
435
+ ),
436
+ orientation: {
437
+ heading: 0,
438
+ pitch: Cesium.Math.toRadians(-45),
439
+ roll: 0,
440
+ },
441
+ duration: 0, // instant
442
+ });
419
443
  } else if (prevBridge) {
420
444
  // Georef edit: just re-render, the camera sync loop will pick
421
445
  // up the new bridge on the next frame. No dramatic fly animation.
@@ -425,7 +449,7 @@ export function CesiumOverlay({
425
449
  })();
426
450
 
427
451
  return () => { cancelled = true; };
428
- }, [status, mapConversion, projectedCRS, coordinateInfo]);
452
+ }, [status, mapConversion, projectedCRS, coordinateInfo, lengthUnitScale]);
429
453
 
430
454
  // ─── Effect 2b: Query terrain height when bridge is ready ───────────────
431
455
  // Also re-queries when terrainClamp is toggled on (in case first query failed)
@@ -448,13 +472,25 @@ export function CesiumOverlay({
448
472
  bridge.queryTerrainHeight(Cesium, viewer).then((h) => {
449
473
  if (!cancelled && h !== null) {
450
474
  setCesiumTerrainHeight(h);
451
- // Compute terrain clip Y in viewer space:
452
- // terrain is at height h (meters). Model origin is at bridge.modelOrigin.height.
453
- // In viewer Y-up, the bottom of the model is at modelMinY.
454
- // Terrain clip Y = modelMinY + (terrainHeight - modelOriginHeight)
455
- // This places the clip plane at the terrain surface relative to model geometry.
475
+ // Compute terrain clip Y in viewer space (both h and modelOrigin.height are metres,
476
+ // bounds are also in metres since the geometry engine converts during extraction)
456
477
  const terrainClipY = modelMinY + (h - bridge.modelOrigin.height);
457
478
  setCesiumTerrainClipY(terrainClipY);
479
+
480
+ // Auto-enable terrain clamping if the model is significantly below terrain
481
+ // (only once — don't override if the user has manually toggled)
482
+ if (!autoClampedRef.current && h > bridge.modelOrigin.height + 5) {
483
+ autoClampedRef.current = true;
484
+ setCesiumTerrainClamp(true);
485
+ // Fly camera to the clamped position so the model is visible
486
+ viewer.camera.flyTo({
487
+ destination: Cesium.Cartesian3.fromDegrees(
488
+ bridge.modelOrigin.longitude, bridge.modelOrigin.latitude, h + 500,
489
+ ),
490
+ orientation: { heading: 0, pitch: Cesium.Math.toRadians(-45), roll: 0 },
491
+ duration: 0,
492
+ });
493
+ }
458
494
  }
459
495
  });
460
496
  };
@@ -516,7 +552,14 @@ export function CesiumOverlay({
516
552
  let model: { modelMatrix: any; destroy?: () => void } | null = null;
517
553
  try {
518
554
  model = await Cesium.Model.fromGltfAsync({
519
- url: glbUrl, modelMatrix, shadows: Cesium.ShadowMode.DISABLED,
555
+ url: glbUrl,
556
+ modelMatrix,
557
+ shadows: Cesium.ShadowMode.DISABLED,
558
+ // The generated GLB stores viewer-space vertices and buildModelMatrix
559
+ // already maps viewer axes into ENU. Avoid Cesium's default glTF
560
+ // Y-up/Z-forward correction or the model is rotated onto its side.
561
+ upAxis: Cesium.Axis.Z,
562
+ forwardAxis: Cesium.Axis.X,
520
563
  });
521
564
  } finally {
522
565
  URL.revokeObjectURL(glbUrl);
@@ -561,7 +604,7 @@ export function CesiumOverlay({
561
604
  const newMatrix = buildModelMatrix(Cesium, bridge, mapConversion, coordinateInfo, terrainClamp, terrainHeight);
562
605
  model.modelMatrix = newMatrix;
563
606
  viewer.scene.requestRender();
564
- }, [terrainClamp, terrainHeight, mapConversion, coordinateInfo]);
607
+ }, [terrainClamp, terrainHeight, mapConversion, coordinateInfo, lengthUnitScale]);
565
608
 
566
609
  // ─── Effect 3: Camera sync loop ─────────────────────────────────────────
567
610
  useEffect(() => {