@ifc-lite/viewer 1.17.3 → 1.17.6

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 (530) hide show
  1. package/.turbo/turbo-build.log +39 -30
  2. package/.turbo/turbo-typecheck.log +1 -1
  3. package/CHANGELOG.md +132 -0
  4. package/DESKTOP_CONTRACT_VERSION +1 -0
  5. package/dist/assets/arrow-CZ5kQ26f.js +20 -0
  6. package/dist/assets/basketViewActivator-86rgogji.js +1 -0
  7. package/dist/assets/{bcf-D5-QWGO9.js → bcf-DOG9_WPX.js} +1 -1
  8. package/dist/assets/{browser-CKs-FY1P.js → browser-C5TFR7sH.js} +1 -1
  9. package/dist/assets/cesium-ADbP7waU.css +1 -0
  10. package/dist/assets/cesium-DUOzBlqv.js +17817 -0
  11. package/dist/assets/drawing-2d-DoxKMqbO.js +257 -0
  12. package/dist/assets/{exporters-C_6J153K.js → exporters-CcPS9MK5.js} +2898 -2380
  13. package/dist/assets/geometry.worker-BFUYA08u.js +1 -0
  14. package/dist/assets/ids-DQ5jY0E8.js +1 -0
  15. package/dist/assets/ifc-lite_bg-BINvzoCP.wasm +0 -0
  16. package/dist/assets/{index-jhBr1wbn.js → index-Bfms9I4A.js} +41036 -34613
  17. package/dist/assets/index-_bfZsDCC.css +1 -0
  18. package/dist/assets/{maplibre-gl-BpvwNKKy.js → maplibre-gl-CGLcoNXc.js} +1 -1
  19. package/dist/assets/native-bridge-DUyLCMZS.js +429 -0
  20. package/dist/assets/{sandbox-B79eavQ3.js → sandbox-C8575tul.js} +4342 -4324
  21. package/dist/assets/{server-client-D3bUPJJc.js → server-client-BuZK7OST.js} +4 -4
  22. package/dist/assets/tauri-core-stub-D8Fa-u43.js +1 -0
  23. package/dist/assets/tauri-dialog-stub-r7Wksg7o.js +1 -0
  24. package/dist/assets/tauri-fs-stub-BdeRC7aK.js +1 -0
  25. package/dist/assets/wasm-bridge-JsqEGDV8.js +1 -0
  26. package/dist/assets/{zip-B-jFFAGa.js → zip-DBEtpeu6.js} +3 -3
  27. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_0.json +1 -0
  28. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_1.json +1 -0
  29. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_10.json +1 -0
  30. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_11.json +1 -0
  31. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_12.json +1 -0
  32. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_13.json +1 -0
  33. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_14.json +1 -0
  34. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_15.json +1 -0
  35. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_16.json +1 -0
  36. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_17.json +1 -0
  37. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_18.json +1 -0
  38. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_19.json +1 -0
  39. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_2.json +1 -0
  40. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_20.json +1 -0
  41. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_21.json +1 -0
  42. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_22.json +1 -0
  43. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_23.json +1 -0
  44. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_24.json +1 -0
  45. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_25.json +1 -0
  46. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_26.json +1 -0
  47. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_27.json +1 -0
  48. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_3.json +1 -0
  49. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_4.json +1 -0
  50. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_5.json +1 -0
  51. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_6.json +1 -0
  52. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_7.json +1 -0
  53. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_8.json +1 -0
  54. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_9.json +1 -0
  55. package/dist/cesium/Assets/Images/bing_maps_credit.png +0 -0
  56. package/dist/cesium/Assets/Images/cesium_credit.png +0 -0
  57. package/dist/cesium/Assets/Images/google_earth_credit.png +0 -0
  58. package/dist/cesium/Assets/Images/ion-credit.png +0 -0
  59. package/dist/cesium/Assets/Textures/LensFlare/DirtMask.jpg +0 -0
  60. package/dist/cesium/Assets/Textures/LensFlare/StarBurst.jpg +0 -0
  61. package/dist/cesium/Assets/Textures/NaturalEarthII/0/0/0.jpg +0 -0
  62. package/dist/cesium/Assets/Textures/NaturalEarthII/0/1/0.jpg +0 -0
  63. package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/0.jpg +0 -0
  64. package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/1.jpg +0 -0
  65. package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/0.jpg +0 -0
  66. package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/1.jpg +0 -0
  67. package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/0.jpg +0 -0
  68. package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/1.jpg +0 -0
  69. package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/0.jpg +0 -0
  70. package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/1.jpg +0 -0
  71. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/0.jpg +0 -0
  72. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/1.jpg +0 -0
  73. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/2.jpg +0 -0
  74. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/3.jpg +0 -0
  75. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/0.jpg +0 -0
  76. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/1.jpg +0 -0
  77. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/2.jpg +0 -0
  78. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/3.jpg +0 -0
  79. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/0.jpg +0 -0
  80. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/1.jpg +0 -0
  81. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/2.jpg +0 -0
  82. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/3.jpg +0 -0
  83. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/0.jpg +0 -0
  84. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/1.jpg +0 -0
  85. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/2.jpg +0 -0
  86. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/3.jpg +0 -0
  87. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/0.jpg +0 -0
  88. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/1.jpg +0 -0
  89. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/2.jpg +0 -0
  90. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/3.jpg +0 -0
  91. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/0.jpg +0 -0
  92. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/1.jpg +0 -0
  93. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/2.jpg +0 -0
  94. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/3.jpg +0 -0
  95. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/0.jpg +0 -0
  96. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/1.jpg +0 -0
  97. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/2.jpg +0 -0
  98. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/3.jpg +0 -0
  99. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/0.jpg +0 -0
  100. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/1.jpg +0 -0
  101. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/2.jpg +0 -0
  102. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/3.jpg +0 -0
  103. package/dist/cesium/Assets/Textures/NaturalEarthII/tilemapresource.xml +14 -0
  104. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mx.jpg +0 -0
  105. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_my.jpg +0 -0
  106. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mz.jpg +0 -0
  107. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_px.jpg +0 -0
  108. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_py.jpg +0 -0
  109. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_pz.jpg +0 -0
  110. package/dist/cesium/Assets/Textures/maki/airfield.png +0 -0
  111. package/dist/cesium/Assets/Textures/maki/airport.png +0 -0
  112. package/dist/cesium/Assets/Textures/maki/alcohol-shop.png +0 -0
  113. package/dist/cesium/Assets/Textures/maki/america-football.png +0 -0
  114. package/dist/cesium/Assets/Textures/maki/art-gallery.png +0 -0
  115. package/dist/cesium/Assets/Textures/maki/bakery.png +0 -0
  116. package/dist/cesium/Assets/Textures/maki/bank.png +0 -0
  117. package/dist/cesium/Assets/Textures/maki/bar.png +0 -0
  118. package/dist/cesium/Assets/Textures/maki/baseball.png +0 -0
  119. package/dist/cesium/Assets/Textures/maki/basketball.png +0 -0
  120. package/dist/cesium/Assets/Textures/maki/beer.png +0 -0
  121. package/dist/cesium/Assets/Textures/maki/bicycle.png +0 -0
  122. package/dist/cesium/Assets/Textures/maki/building.png +0 -0
  123. package/dist/cesium/Assets/Textures/maki/bus.png +0 -0
  124. package/dist/cesium/Assets/Textures/maki/cafe.png +0 -0
  125. package/dist/cesium/Assets/Textures/maki/camera.png +0 -0
  126. package/dist/cesium/Assets/Textures/maki/campsite.png +0 -0
  127. package/dist/cesium/Assets/Textures/maki/car.png +0 -0
  128. package/dist/cesium/Assets/Textures/maki/cemetery.png +0 -0
  129. package/dist/cesium/Assets/Textures/maki/cesium.png +0 -0
  130. package/dist/cesium/Assets/Textures/maki/chemist.png +0 -0
  131. package/dist/cesium/Assets/Textures/maki/cinema.png +0 -0
  132. package/dist/cesium/Assets/Textures/maki/circle-stroked.png +0 -0
  133. package/dist/cesium/Assets/Textures/maki/circle.png +0 -0
  134. package/dist/cesium/Assets/Textures/maki/city.png +0 -0
  135. package/dist/cesium/Assets/Textures/maki/clothing-store.png +0 -0
  136. package/dist/cesium/Assets/Textures/maki/college.png +0 -0
  137. package/dist/cesium/Assets/Textures/maki/commercial.png +0 -0
  138. package/dist/cesium/Assets/Textures/maki/cricket.png +0 -0
  139. package/dist/cesium/Assets/Textures/maki/cross.png +0 -0
  140. package/dist/cesium/Assets/Textures/maki/dam.png +0 -0
  141. package/dist/cesium/Assets/Textures/maki/danger.png +0 -0
  142. package/dist/cesium/Assets/Textures/maki/disability.png +0 -0
  143. package/dist/cesium/Assets/Textures/maki/dog-park.png +0 -0
  144. package/dist/cesium/Assets/Textures/maki/embassy.png +0 -0
  145. package/dist/cesium/Assets/Textures/maki/emergency-telephone.png +0 -0
  146. package/dist/cesium/Assets/Textures/maki/entrance.png +0 -0
  147. package/dist/cesium/Assets/Textures/maki/farm.png +0 -0
  148. package/dist/cesium/Assets/Textures/maki/fast-food.png +0 -0
  149. package/dist/cesium/Assets/Textures/maki/ferry.png +0 -0
  150. package/dist/cesium/Assets/Textures/maki/fire-station.png +0 -0
  151. package/dist/cesium/Assets/Textures/maki/fuel.png +0 -0
  152. package/dist/cesium/Assets/Textures/maki/garden.png +0 -0
  153. package/dist/cesium/Assets/Textures/maki/gift.png +0 -0
  154. package/dist/cesium/Assets/Textures/maki/golf.png +0 -0
  155. package/dist/cesium/Assets/Textures/maki/grocery.png +0 -0
  156. package/dist/cesium/Assets/Textures/maki/hairdresser.png +0 -0
  157. package/dist/cesium/Assets/Textures/maki/harbor.png +0 -0
  158. package/dist/cesium/Assets/Textures/maki/heart.png +0 -0
  159. package/dist/cesium/Assets/Textures/maki/heliport.png +0 -0
  160. package/dist/cesium/Assets/Textures/maki/hospital.png +0 -0
  161. package/dist/cesium/Assets/Textures/maki/ice-cream.png +0 -0
  162. package/dist/cesium/Assets/Textures/maki/industrial.png +0 -0
  163. package/dist/cesium/Assets/Textures/maki/land-use.png +0 -0
  164. package/dist/cesium/Assets/Textures/maki/laundry.png +0 -0
  165. package/dist/cesium/Assets/Textures/maki/library.png +0 -0
  166. package/dist/cesium/Assets/Textures/maki/lighthouse.png +0 -0
  167. package/dist/cesium/Assets/Textures/maki/lodging.png +0 -0
  168. package/dist/cesium/Assets/Textures/maki/logging.png +0 -0
  169. package/dist/cesium/Assets/Textures/maki/london-underground.png +0 -0
  170. package/dist/cesium/Assets/Textures/maki/marker-stroked.png +0 -0
  171. package/dist/cesium/Assets/Textures/maki/marker.png +0 -0
  172. package/dist/cesium/Assets/Textures/maki/minefield.png +0 -0
  173. package/dist/cesium/Assets/Textures/maki/mobilephone.png +0 -0
  174. package/dist/cesium/Assets/Textures/maki/monument.png +0 -0
  175. package/dist/cesium/Assets/Textures/maki/museum.png +0 -0
  176. package/dist/cesium/Assets/Textures/maki/music.png +0 -0
  177. package/dist/cesium/Assets/Textures/maki/oil-well.png +0 -0
  178. package/dist/cesium/Assets/Textures/maki/park.png +0 -0
  179. package/dist/cesium/Assets/Textures/maki/park2.png +0 -0
  180. package/dist/cesium/Assets/Textures/maki/parking-garage.png +0 -0
  181. package/dist/cesium/Assets/Textures/maki/parking.png +0 -0
  182. package/dist/cesium/Assets/Textures/maki/pharmacy.png +0 -0
  183. package/dist/cesium/Assets/Textures/maki/pitch.png +0 -0
  184. package/dist/cesium/Assets/Textures/maki/place-of-worship.png +0 -0
  185. package/dist/cesium/Assets/Textures/maki/playground.png +0 -0
  186. package/dist/cesium/Assets/Textures/maki/police.png +0 -0
  187. package/dist/cesium/Assets/Textures/maki/polling-place.png +0 -0
  188. package/dist/cesium/Assets/Textures/maki/post.png +0 -0
  189. package/dist/cesium/Assets/Textures/maki/prison.png +0 -0
  190. package/dist/cesium/Assets/Textures/maki/rail-above.png +0 -0
  191. package/dist/cesium/Assets/Textures/maki/rail-light.png +0 -0
  192. package/dist/cesium/Assets/Textures/maki/rail-metro.png +0 -0
  193. package/dist/cesium/Assets/Textures/maki/rail-underground.png +0 -0
  194. package/dist/cesium/Assets/Textures/maki/rail.png +0 -0
  195. package/dist/cesium/Assets/Textures/maki/religious-christian.png +0 -0
  196. package/dist/cesium/Assets/Textures/maki/religious-jewish.png +0 -0
  197. package/dist/cesium/Assets/Textures/maki/religious-muslim.png +0 -0
  198. package/dist/cesium/Assets/Textures/maki/restaurant.png +0 -0
  199. package/dist/cesium/Assets/Textures/maki/roadblock.png +0 -0
  200. package/dist/cesium/Assets/Textures/maki/rocket.png +0 -0
  201. package/dist/cesium/Assets/Textures/maki/school.png +0 -0
  202. package/dist/cesium/Assets/Textures/maki/scooter.png +0 -0
  203. package/dist/cesium/Assets/Textures/maki/shop.png +0 -0
  204. package/dist/cesium/Assets/Textures/maki/skiing.png +0 -0
  205. package/dist/cesium/Assets/Textures/maki/slaughterhouse.png +0 -0
  206. package/dist/cesium/Assets/Textures/maki/soccer.png +0 -0
  207. package/dist/cesium/Assets/Textures/maki/square-stroked.png +0 -0
  208. package/dist/cesium/Assets/Textures/maki/square.png +0 -0
  209. package/dist/cesium/Assets/Textures/maki/star-stroked.png +0 -0
  210. package/dist/cesium/Assets/Textures/maki/star.png +0 -0
  211. package/dist/cesium/Assets/Textures/maki/suitcase.png +0 -0
  212. package/dist/cesium/Assets/Textures/maki/swimming.png +0 -0
  213. package/dist/cesium/Assets/Textures/maki/telephone.png +0 -0
  214. package/dist/cesium/Assets/Textures/maki/tennis.png +0 -0
  215. package/dist/cesium/Assets/Textures/maki/theatre.png +0 -0
  216. package/dist/cesium/Assets/Textures/maki/toilets.png +0 -0
  217. package/dist/cesium/Assets/Textures/maki/town-hall.png +0 -0
  218. package/dist/cesium/Assets/Textures/maki/town.png +0 -0
  219. package/dist/cesium/Assets/Textures/maki/triangle-stroked.png +0 -0
  220. package/dist/cesium/Assets/Textures/maki/triangle.png +0 -0
  221. package/dist/cesium/Assets/Textures/maki/village.png +0 -0
  222. package/dist/cesium/Assets/Textures/maki/warehouse.png +0 -0
  223. package/dist/cesium/Assets/Textures/maki/waste-basket.png +0 -0
  224. package/dist/cesium/Assets/Textures/maki/water.png +0 -0
  225. package/dist/cesium/Assets/Textures/maki/wetland.png +0 -0
  226. package/dist/cesium/Assets/Textures/maki/zoo.png +0 -0
  227. package/dist/cesium/Assets/Textures/moonSmall.jpg +0 -0
  228. package/dist/cesium/Assets/Textures/pin.svg +1 -0
  229. package/dist/cesium/Assets/Textures/waterNormals.jpg +0 -0
  230. package/dist/cesium/Assets/Textures/waterNormalsSmall.jpg +0 -0
  231. package/dist/cesium/Assets/approximateTerrainHeights.json +1 -0
  232. package/dist/cesium/ThirdParty/Workers/package.json +1 -0
  233. package/dist/cesium/ThirdParty/Workers/zip-web-worker.js +1 -0
  234. package/dist/cesium/ThirdParty/basis_transcoder.wasm +0 -0
  235. package/dist/cesium/ThirdParty/draco_decoder.wasm +0 -0
  236. package/dist/cesium/ThirdParty/google-earth-dbroot-parser.js +1 -0
  237. package/dist/cesium/ThirdParty/wasm_splats_bg.wasm +0 -0
  238. package/dist/cesium/ThirdParty/zip-module.wasm +0 -0
  239. package/dist/cesium/Widgets/Animation/Animation.css +127 -0
  240. package/dist/cesium/Widgets/Animation/lighter.css +70 -0
  241. package/dist/cesium/Widgets/BaseLayerPicker/BaseLayerPicker.css +108 -0
  242. package/dist/cesium/Widgets/BaseLayerPicker/lighter.css +22 -0
  243. package/dist/cesium/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css +102 -0
  244. package/dist/cesium/Widgets/CesiumInspector/CesiumInspector.css +113 -0
  245. package/dist/cesium/Widgets/CesiumWidget/CesiumWidget.css +119 -0
  246. package/dist/cesium/Widgets/CesiumWidget/lighter.css +14 -0
  247. package/dist/cesium/Widgets/FullscreenButton/FullscreenButton.css +8 -0
  248. package/dist/cesium/Widgets/Geocoder/Geocoder.css +70 -0
  249. package/dist/cesium/Widgets/Geocoder/lighter.css +17 -0
  250. package/dist/cesium/Widgets/I3SBuildingSceneLayerExplorer/I3SBuildingSceneLayerExplorer.css +27 -0
  251. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldHillshade.png +0 -0
  252. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldImagery.png +0 -0
  253. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldOcean.png +0 -0
  254. package/dist/cesium/Widgets/Images/ImageryProviders/azureAerial.png +0 -0
  255. package/dist/cesium/Widgets/Images/ImageryProviders/azureRoads.png +0 -0
  256. package/dist/cesium/Widgets/Images/ImageryProviders/bingAerial.png +0 -0
  257. package/dist/cesium/Widgets/Images/ImageryProviders/bingAerialLabels.png +0 -0
  258. package/dist/cesium/Widgets/Images/ImageryProviders/bingRoads.png +0 -0
  259. package/dist/cesium/Widgets/Images/ImageryProviders/blueMarble.png +0 -0
  260. package/dist/cesium/Widgets/Images/ImageryProviders/earthAtNight.png +0 -0
  261. package/dist/cesium/Widgets/Images/ImageryProviders/googleContour.png +0 -0
  262. package/dist/cesium/Widgets/Images/ImageryProviders/googleRoadmap.png +0 -0
  263. package/dist/cesium/Widgets/Images/ImageryProviders/googleSatellite.png +0 -0
  264. package/dist/cesium/Widgets/Images/ImageryProviders/googleSatelliteLabels.png +0 -0
  265. package/dist/cesium/Widgets/Images/ImageryProviders/mapQuestOpenStreetMap.png +0 -0
  266. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxSatellite.png +0 -0
  267. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxStreets.png +0 -0
  268. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxTerrain.png +0 -0
  269. package/dist/cesium/Widgets/Images/ImageryProviders/naturalEarthII.png +0 -0
  270. package/dist/cesium/Widgets/Images/ImageryProviders/openStreetMap.png +0 -0
  271. package/dist/cesium/Widgets/Images/ImageryProviders/sentinel-2.png +0 -0
  272. package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmooth.png +0 -0
  273. package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmoothDark.png +0 -0
  274. package/dist/cesium/Widgets/Images/ImageryProviders/stamenToner.png +0 -0
  275. package/dist/cesium/Widgets/Images/ImageryProviders/stamenWatercolor.png +0 -0
  276. package/dist/cesium/Widgets/Images/NavigationHelp/Mouse.svg +84 -0
  277. package/dist/cesium/Widgets/Images/NavigationHelp/MouseLeft.svg +76 -0
  278. package/dist/cesium/Widgets/Images/NavigationHelp/MouseMiddle.svg +76 -0
  279. package/dist/cesium/Widgets/Images/NavigationHelp/MouseRight.svg +76 -0
  280. package/dist/cesium/Widgets/Images/NavigationHelp/Touch.svg +120 -0
  281. package/dist/cesium/Widgets/Images/NavigationHelp/TouchDrag.svg +129 -0
  282. package/dist/cesium/Widgets/Images/NavigationHelp/TouchRotate.svg +76 -0
  283. package/dist/cesium/Widgets/Images/NavigationHelp/TouchTilt.svg +135 -0
  284. package/dist/cesium/Widgets/Images/NavigationHelp/TouchZoom.svg +74 -0
  285. package/dist/cesium/Widgets/Images/TerrainProviders/CesiumWorldTerrain.png +0 -0
  286. package/dist/cesium/Widgets/Images/TerrainProviders/Ellipsoid.png +0 -0
  287. package/dist/cesium/Widgets/Images/TimelineIcons.png +0 -0
  288. package/dist/cesium/Widgets/Images/info-loading.gif +0 -0
  289. package/dist/cesium/Widgets/InfoBox/InfoBox.css +92 -0
  290. package/dist/cesium/Widgets/InfoBox/InfoBoxDescription.css +178 -0
  291. package/dist/cesium/Widgets/NavigationHelpButton/NavigationHelpButton.css +93 -0
  292. package/dist/cesium/Widgets/NavigationHelpButton/lighter.css +38 -0
  293. package/dist/cesium/Widgets/PerformanceWatchdog/PerformanceWatchdog.css +15 -0
  294. package/dist/cesium/Widgets/ProjectionPicker/ProjectionPicker.css +38 -0
  295. package/dist/cesium/Widgets/SceneModePicker/SceneModePicker.css +56 -0
  296. package/dist/cesium/Widgets/SelectionIndicator/SelectionIndicator.css +20 -0
  297. package/dist/cesium/Widgets/Timeline/Timeline.css +103 -0
  298. package/dist/cesium/Widgets/Timeline/lighter.css +23 -0
  299. package/dist/cesium/Widgets/VRButton/VRButton.css +8 -0
  300. package/dist/cesium/Widgets/Viewer/Viewer.css +107 -0
  301. package/dist/cesium/Widgets/VoxelInspector/VoxelInspector.css +16 -0
  302. package/dist/cesium/Widgets/lighter.css +237 -0
  303. package/dist/cesium/Widgets/lighterShared.css +46 -0
  304. package/dist/cesium/Widgets/shared.css +103 -0
  305. package/dist/cesium/Widgets/widgets.css +1342 -0
  306. package/dist/cesium/Workers/chunk-23ZQ2IVV.js +29 -0
  307. package/dist/cesium/Workers/chunk-2EQO3Q56.js +26 -0
  308. package/dist/cesium/Workers/chunk-2MJIIVP4.js +26 -0
  309. package/dist/cesium/Workers/chunk-2TE5NTVD.js +26 -0
  310. package/dist/cesium/Workers/chunk-2ZBHLJST.js +26 -0
  311. package/dist/cesium/Workers/chunk-5TJMAQVL.js +26 -0
  312. package/dist/cesium/Workers/chunk-6BD4U3VO.js +26 -0
  313. package/dist/cesium/Workers/chunk-7TVGLKQF.js +26 -0
  314. package/dist/cesium/Workers/chunk-BTSYJ5XU.js +26 -0
  315. package/dist/cesium/Workers/chunk-BXMEEOCS.js +63 -0
  316. package/dist/cesium/Workers/chunk-BYLCY7GP.js +29 -0
  317. package/dist/cesium/Workers/chunk-CTHM3W6I.js +26 -0
  318. package/dist/cesium/Workers/chunk-CUUSNIVQ.js +26 -0
  319. package/dist/cesium/Workers/chunk-E3JOOS3S.js +26 -0
  320. package/dist/cesium/Workers/chunk-E7KYDCM5.js +26 -0
  321. package/dist/cesium/Workers/chunk-EDVBB7SS.js +27 -0
  322. package/dist/cesium/Workers/chunk-EFBN7QNX.js +26 -0
  323. package/dist/cesium/Workers/chunk-EQ4YRVWL.js +26 -0
  324. package/dist/cesium/Workers/chunk-F6PRE7D6.js +26 -0
  325. package/dist/cesium/Workers/chunk-FC4ZZ65J.js +26 -0
  326. package/dist/cesium/Workers/chunk-FFBVWF2L.js +26 -0
  327. package/dist/cesium/Workers/chunk-GBAA6GVX.js +26 -0
  328. package/dist/cesium/Workers/chunk-ICALLYLG.js +26 -0
  329. package/dist/cesium/Workers/chunk-ILRYTWTP.js +26 -0
  330. package/dist/cesium/Workers/chunk-IRNLBSEJ.js +26 -0
  331. package/dist/cesium/Workers/chunk-IX4VMHEV.js +26 -0
  332. package/dist/cesium/Workers/chunk-L6QHHACZ.js +26 -0
  333. package/dist/cesium/Workers/chunk-LI2ZSORM.js +26 -0
  334. package/dist/cesium/Workers/chunk-LSLE2RL4.js +26 -0
  335. package/dist/cesium/Workers/chunk-M4HLDBCG.js +26 -0
  336. package/dist/cesium/Workers/chunk-MJHHSGEH.js +26 -0
  337. package/dist/cesium/Workers/chunk-NMVKML6W.js +26 -0
  338. package/dist/cesium/Workers/chunk-OCWJRAXS.js +26 -0
  339. package/dist/cesium/Workers/chunk-OIRKANTH.js +26 -0
  340. package/dist/cesium/Workers/chunk-OIT7J4IC.js +26 -0
  341. package/dist/cesium/Workers/chunk-OLZ3FYUM.js +26 -0
  342. package/dist/cesium/Workers/chunk-Q5BPHJQF.js +26 -0
  343. package/dist/cesium/Workers/chunk-QFM5DCMQ.js +26 -0
  344. package/dist/cesium/Workers/chunk-QKUIYMGC.js +28 -0
  345. package/dist/cesium/Workers/chunk-S44JILQT.js +26 -0
  346. package/dist/cesium/Workers/chunk-SLT4J352.js +26 -0
  347. package/dist/cesium/Workers/chunk-SQMIIXB7.js +26 -0
  348. package/dist/cesium/Workers/chunk-TJ4XLGBQ.js +26 -0
  349. package/dist/cesium/Workers/chunk-TNSUQXWK.js +27 -0
  350. package/dist/cesium/Workers/chunk-UBOGZS7F.js +26 -0
  351. package/dist/cesium/Workers/chunk-V3OSTMM6.js +26 -0
  352. package/dist/cesium/Workers/chunk-V7QEYVP3.js +26 -0
  353. package/dist/cesium/Workers/chunk-VUKYSU4H.js +26 -0
  354. package/dist/cesium/Workers/chunk-W37FE5GR.js +26 -0
  355. package/dist/cesium/Workers/chunk-WBOV35NL.js +26 -0
  356. package/dist/cesium/Workers/chunk-WPMZLB3Y.js +26 -0
  357. package/dist/cesium/Workers/chunk-WWWZVEEH.js +26 -0
  358. package/dist/cesium/Workers/chunk-XFIQ5DEQ.js +28 -0
  359. package/dist/cesium/Workers/chunk-XQHLGIO7.js +26 -0
  360. package/dist/cesium/Workers/chunk-XUSCFAVF.js +26 -0
  361. package/dist/cesium/Workers/chunk-YP7I5QBZ.js +26 -0
  362. package/dist/cesium/Workers/chunk-Z3QF2EHT.js +26 -0
  363. package/dist/cesium/Workers/combineGeometry.js +26 -0
  364. package/dist/cesium/Workers/createBoxGeometry.js +26 -0
  365. package/dist/cesium/Workers/createBoxOutlineGeometry.js +26 -0
  366. package/dist/cesium/Workers/createCircleGeometry.js +26 -0
  367. package/dist/cesium/Workers/createCircleOutlineGeometry.js +26 -0
  368. package/dist/cesium/Workers/createCoplanarPolygonGeometry.js +26 -0
  369. package/dist/cesium/Workers/createCoplanarPolygonOutlineGeometry.js +26 -0
  370. package/dist/cesium/Workers/createCorridorGeometry.js +26 -0
  371. package/dist/cesium/Workers/createCorridorOutlineGeometry.js +26 -0
  372. package/dist/cesium/Workers/createCylinderGeometry.js +26 -0
  373. package/dist/cesium/Workers/createCylinderOutlineGeometry.js +26 -0
  374. package/dist/cesium/Workers/createEllipseGeometry.js +26 -0
  375. package/dist/cesium/Workers/createEllipseOutlineGeometry.js +26 -0
  376. package/dist/cesium/Workers/createEllipsoidGeometry.js +26 -0
  377. package/dist/cesium/Workers/createEllipsoidOutlineGeometry.js +26 -0
  378. package/dist/cesium/Workers/createFrustumGeometry.js +26 -0
  379. package/dist/cesium/Workers/createFrustumOutlineGeometry.js +26 -0
  380. package/dist/cesium/Workers/createGeometry.js +26 -0
  381. package/dist/cesium/Workers/createGroundPolylineGeometry.js +26 -0
  382. package/dist/cesium/Workers/createPlaneGeometry.js +26 -0
  383. package/dist/cesium/Workers/createPlaneOutlineGeometry.js +26 -0
  384. package/dist/cesium/Workers/createPolygonGeometry.js +26 -0
  385. package/dist/cesium/Workers/createPolygonOutlineGeometry.js +26 -0
  386. package/dist/cesium/Workers/createPolylineGeometry.js +26 -0
  387. package/dist/cesium/Workers/createPolylineVolumeGeometry.js +26 -0
  388. package/dist/cesium/Workers/createPolylineVolumeOutlineGeometry.js +26 -0
  389. package/dist/cesium/Workers/createRectangleGeometry.js +26 -0
  390. package/dist/cesium/Workers/createRectangleOutlineGeometry.js +26 -0
  391. package/dist/cesium/Workers/createSimplePolylineGeometry.js +26 -0
  392. package/dist/cesium/Workers/createSphereGeometry.js +26 -0
  393. package/dist/cesium/Workers/createSphereOutlineGeometry.js +26 -0
  394. package/dist/cesium/Workers/createTaskProcessorWorker.js +26 -0
  395. package/dist/cesium/Workers/createVectorTileClampedPolylines.js +26 -0
  396. package/dist/cesium/Workers/createVectorTileGeometries.js +26 -0
  397. package/dist/cesium/Workers/createVectorTilePoints.js +26 -0
  398. package/dist/cesium/Workers/createVectorTilePolygons.js +26 -0
  399. package/dist/cesium/Workers/createVectorTilePolylines.js +26 -0
  400. package/dist/cesium/Workers/createVerticesFromCesium3DTilesTerrain.js +26 -0
  401. package/dist/cesium/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +26 -0
  402. package/dist/cesium/Workers/createVerticesFromHeightmap.js +26 -0
  403. package/dist/cesium/Workers/createVerticesFromQuantizedTerrainMesh.js +26 -0
  404. package/dist/cesium/Workers/createWallGeometry.js +26 -0
  405. package/dist/cesium/Workers/createWallOutlineGeometry.js +26 -0
  406. package/dist/cesium/Workers/decodeDraco.js +26 -0
  407. package/dist/cesium/Workers/decodeGoogleEarthEnterprisePacket.js +26 -0
  408. package/dist/cesium/Workers/decodeI3S.js +26 -0
  409. package/dist/cesium/Workers/gaussianSplatSorter.js +26 -0
  410. package/dist/cesium/Workers/gaussianSplatTextureGenerator.js +26 -0
  411. package/dist/cesium/Workers/incrementallyBuildTerrainPicker.js +26 -0
  412. package/dist/cesium/Workers/transcodeKTX2.js +56 -0
  413. package/dist/cesium/Workers/transferTypedArrayTest.js +26 -0
  414. package/dist/cesium/Workers/upsampleQuantizedTerrainMesh.js +26 -0
  415. package/dist/cesium/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +26 -0
  416. package/dist/index.html +13 -10
  417. package/index.html +1 -0
  418. package/package.json +18 -15
  419. package/src/App.tsx +7 -9
  420. package/src/components/viewer/BCFPanel.tsx +46 -4
  421. package/src/components/viewer/CesiumOverlay.tsx +715 -0
  422. package/src/components/viewer/CesiumSettingsDialog.tsx +100 -0
  423. package/src/components/viewer/ChatPanel.tsx +232 -90
  424. package/src/components/viewer/CommandPalette.tsx +6 -1
  425. package/src/components/viewer/DesktopEntitlementBanner.tsx +74 -0
  426. package/src/components/viewer/ExportChangesButton.tsx +6 -1
  427. package/src/components/viewer/ExportDialog.tsx +22 -6
  428. package/src/components/viewer/HierarchyPanel.tsx +196 -0
  429. package/src/components/viewer/IDSPanel.tsx +52 -3
  430. package/src/components/viewer/KeyboardShortcutsDialog.tsx +1 -1
  431. package/src/components/viewer/MainToolbar.tsx +355 -28
  432. package/src/components/viewer/PropertiesPanel.tsx +234 -81
  433. package/src/components/viewer/ScriptPanel.tsx +34 -8
  434. package/src/components/viewer/SettingsPage.tsx +581 -0
  435. package/src/components/viewer/StatusBar.tsx +17 -1
  436. package/src/components/viewer/ThemeSwitch.tsx +63 -7
  437. package/src/components/viewer/ViewerLayout.tsx +48 -6
  438. package/src/components/viewer/Viewport.tsx +61 -8
  439. package/src/components/viewer/ViewportContainer.tsx +265 -28
  440. package/src/components/viewer/ViewportOverlays.tsx +132 -27
  441. package/src/components/viewer/bcf/BCFTopicDetail.tsx +4 -4
  442. package/src/components/viewer/chat/ModelSelector.tsx +90 -54
  443. package/src/components/viewer/properties/GeoreferencingPanel.tsx +229 -55
  444. package/src/components/viewer/properties/LocationMap.tsx +462 -19
  445. package/src/components/viewer/properties/ModelMetadataPanel.tsx +1 -1
  446. package/src/components/viewer/selectionHandlers.ts +4 -3
  447. package/src/components/viewer/tools/SectionCapControls.tsx +237 -0
  448. package/src/components/viewer/tools/SectionPanel.tsx +39 -18
  449. package/src/components/viewer/useAnimationLoop.ts +13 -1
  450. package/src/components/viewer/useGeometryStreaming.ts +127 -40
  451. package/src/components/viewer/useMouseControls.ts +4 -1
  452. package/src/components/viewer/useRenderUpdates.ts +1 -1
  453. package/src/hooks/ids/idsDataAccessor.ts +60 -24
  454. package/src/hooks/ingest/viewerModelIngest.ts +280 -0
  455. package/src/hooks/useIDS.ts +1 -1
  456. package/src/hooks/useIfc.ts +7 -1
  457. package/src/hooks/useIfcCache.ts +28 -15
  458. package/src/hooks/useIfcFederation.ts +378 -291
  459. package/src/hooks/useIfcLoader.ts +1657 -130
  460. package/src/hooks/useIfcServer.ts +0 -69
  461. package/src/hooks/useViewControls.ts +13 -5
  462. package/src/index.css +484 -10
  463. package/src/lib/desktop/desktopEntitlementEvents.ts +39 -0
  464. package/src/lib/desktop-entitlement.ts +43 -0
  465. package/src/lib/desktop-product.ts +124 -0
  466. package/src/lib/geo/cesium-bridge.ts +318 -0
  467. package/src/lib/geo/effective-georef.test.ts +73 -0
  468. package/src/lib/geo/effective-georef.ts +111 -0
  469. package/src/lib/geo/reproject.ts +249 -37
  470. package/src/lib/llm/byok-guard.test.ts +77 -0
  471. package/src/lib/llm/byok-guard.ts +39 -0
  472. package/src/lib/llm/free-models.test.ts +0 -6
  473. package/src/lib/llm/models.ts +104 -42
  474. package/src/lib/llm/stream-client.ts +74 -110
  475. package/src/lib/llm/stream-direct.test.ts +130 -0
  476. package/src/lib/llm/stream-direct.ts +316 -0
  477. package/src/lib/llm/types.ts +14 -2
  478. package/src/lib/recent-files.ts +2 -1
  479. package/src/main.tsx +1 -10
  480. package/src/services/analysis-extensions.ts +125 -0
  481. package/src/services/api-keys.ts +73 -0
  482. package/src/services/app-navigation.ts +13 -0
  483. package/src/services/bsdd.ts +53 -4
  484. package/src/services/cacheService.ts +1 -1
  485. package/src/services/desktop-cache.ts +43 -0
  486. package/src/services/desktop-export.ts +77 -0
  487. package/src/services/desktop-harness.ts +196 -0
  488. package/src/services/desktop-logger.ts +20 -0
  489. package/src/services/desktop-native-metadata.ts +207 -0
  490. package/src/services/desktop-panel-actions.ts +43 -0
  491. package/src/services/desktop-preferences.ts +44 -0
  492. package/src/services/file-dialog.ts +147 -0
  493. package/src/services/tauri-core-stub.ts +7 -0
  494. package/src/services/tauri-dialog-stub.ts +7 -0
  495. package/src/services/tauri-fs-stub.ts +7 -0
  496. package/src/store/constants.ts +20 -2
  497. package/src/store/index.ts +52 -7
  498. package/src/store/slices/cesiumSlice.ts +127 -0
  499. package/src/store/slices/chatSlice.test.ts +6 -76
  500. package/src/store/slices/chatSlice.ts +21 -58
  501. package/src/store/slices/dataSlice.ts +139 -28
  502. package/src/store/slices/desktopEntitlementSlice.ts +86 -0
  503. package/src/store/slices/loadingSlice.ts +14 -2
  504. package/src/store/slices/modelSlice.ts +58 -3
  505. package/src/store/slices/sectionSlice.test.ts +87 -7
  506. package/src/store/slices/sectionSlice.ts +151 -5
  507. package/src/store/slices/uiSlice.ts +28 -5
  508. package/src/store/types.ts +122 -2
  509. package/src/store.ts +1 -1
  510. package/src/utils/desktopModelSnapshot.ts +358 -0
  511. package/src/utils/ifcConfig.ts +6 -1
  512. package/src/utils/nativeSpatialDataStore.ts +253 -0
  513. package/src/utils/serverDataModel.ts +4 -0
  514. package/src/utils/spatialHierarchy.ts +10 -11
  515. package/src/utils/viewportUtils.ts +7 -2
  516. package/src/vite-env.d.ts +0 -4
  517. package/vite.config.ts +24 -0
  518. package/dist/assets/arrow-DJf2ErbF.js +0 -20
  519. package/dist/assets/basketViewActivator-aojwdomq.js +0 -1
  520. package/dist/assets/desktop-cache-oPzaWXYE.js +0 -1
  521. package/dist/assets/drawing-2d-gWfpdfYe.js +0 -257
  522. package/dist/assets/geometry.worker-Nz9_YIqh.js +0 -1
  523. package/dist/assets/ids-B4jTqB1O.js +0 -1
  524. package/dist/assets/ifc-lite_bg-eSkBTizQ.wasm +0 -0
  525. package/dist/assets/index-pbE7itQS.css +0 -1
  526. package/dist/assets/native-bridge-DSIyEYXG.js +0 -113
  527. package/dist/assets/wasm-bridge-B0J07fZZ.js +0 -1
  528. package/src/components/viewer/UpgradePage.tsx +0 -69
  529. package/src/lib/llm/ClerkChatSync.tsx +0 -74
  530. package/src/lib/llm/clerk-auth.ts +0 -62
@@ -4,11 +4,14 @@
4
4
 
5
5
  /**
6
6
  * ModelSelector — dropdown to pick the LLM model.
7
- * Free models available to everyone. Pro models show cost indicator and lock icon.
7
+ * Free models available to everyone via server proxy.
8
+ * BYOK models selectable always — greyed out with lock icon when key is missing,
9
+ * fully styled when key is configured. Selecting a locked model triggers the
10
+ * inline key prompt in ChatPanel.
8
11
  */
9
12
 
10
- import { useCallback } from 'react';
11
- import { Lock } from 'lucide-react';
13
+ import { useCallback, useEffect, useState } from 'react';
14
+ import { Check, Key } from 'lucide-react';
12
15
  import {
13
16
  Select,
14
17
  SelectContent,
@@ -17,27 +20,43 @@ import {
17
20
  SelectValue,
18
21
  } from '@/components/ui/select';
19
22
  import { useViewerStore } from '@/store';
20
- import { FREE_MODELS, PRO_MODELS, getModelById } from '@/lib/llm/models';
21
-
22
- interface ModelSelectorProps {
23
- /** Whether the user has a pro subscription */
24
- hasPro?: boolean;
25
- }
23
+ import { FREE_MODELS, getModelById, getByokModelsForSource } from '@/lib/llm/models';
24
+ import type { LLMModel } from '@/lib/llm/types';
25
+ import { hasAnthropicKey, hasOpenaiKey, subscribeApiKeys } from '@/services/api-keys';
26
26
 
27
27
  function formatContextWindow(tokens: number): string {
28
28
  if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(0)}M`;
29
29
  return `${(tokens / 1_000).toFixed(0)}K`;
30
30
  }
31
31
 
32
- export function ModelSelector({ hasPro = false }: ModelSelectorProps) {
32
+ function CostBadge({ cost }: { cost?: LLMModel['cost'] }) {
33
+ if (!cost) return null;
34
+ const color = cost === '$$$' ? 'text-amber-500' : cost === '$$' ? 'text-blue-500' : 'text-emerald-500';
35
+ return <span className={`text-[10px] font-mono ${color}`}>{cost}</span>;
36
+ }
37
+
38
+ export function ModelSelector() {
33
39
  const activeModel = useViewerStore((s) => s.chatActiveModel);
34
40
  const setActiveModel = useViewerStore((s) => s.setChatActiveModel);
35
41
 
42
+ const [hasAnthropic, setHasAnthropic] = useState(hasAnthropicKey);
43
+ const [hasOpenai, setHasOpenai] = useState(hasOpenaiKey);
44
+
45
+ useEffect(() => {
46
+ const refresh = () => {
47
+ setHasAnthropic(hasAnthropicKey());
48
+ setHasOpenai(hasOpenaiKey());
49
+ };
50
+ return subscribeApiKeys(refresh);
51
+ }, []);
52
+
36
53
  const handleChange = useCallback((value: string) => {
37
54
  setActiveModel(value);
38
55
  }, [setActiveModel]);
39
56
 
40
57
  const current = getModelById(activeModel);
58
+ const anthropicModels = getByokModelsForSource('anthropic');
59
+ const openaiModels = getByokModelsForSource('openai');
41
60
 
42
61
  return (
43
62
  <Select value={activeModel} onValueChange={handleChange}>
@@ -45,57 +64,74 @@ export function ModelSelector({ hasPro = false }: ModelSelectorProps) {
45
64
  <SelectValue>
46
65
  <span className="truncate flex items-center gap-1">
47
66
  {current?.name ?? activeModel}
48
- {current?.cost && (
49
- <span className={`text-[10px] font-mono ${
50
- current.cost === '$$$' ? 'text-amber-500' : current.cost === '$$' ? 'text-blue-500' : 'text-emerald-500'
51
- }`}>
52
- {current.cost}
53
- </span>
54
- )}
67
+ <CostBadge cost={current?.cost} />
55
68
  </span>
56
69
  </SelectValue>
57
70
  </SelectTrigger>
58
71
  <SelectContent>
59
72
  {/* Free tier */}
60
- <div className="px-2 py-1 text-[10px] font-semibold text-muted-foreground uppercase tracking-wider">
61
- Free
62
- </div>
63
- {FREE_MODELS.map((m) => (
64
- <SelectItem key={m.id} value={m.id} className="text-xs">
65
- <span className="flex items-center gap-1.5">
66
- <span>{m.name}</span>
67
- <span className="text-muted-foreground text-[10px]">{m.provider}</span>
68
- <span className="text-muted-foreground/50 text-[10px]">{formatContextWindow(m.contextWindow)}</span>
69
- </span>
70
- </SelectItem>
71
- ))}
73
+ {FREE_MODELS.length > 0 && (
74
+ <>
75
+ <div className="px-2 py-1 text-[10px] font-semibold text-muted-foreground uppercase tracking-wider">
76
+ Free
77
+ </div>
78
+ {FREE_MODELS.map((m) => (
79
+ <SelectItem key={m.id} value={m.id} className="text-xs">
80
+ <span className="flex items-center gap-1.5">
81
+ <span>{m.name}</span>
82
+ <span className="text-muted-foreground text-[10px]">{m.provider}</span>
83
+ <span className="text-muted-foreground/50 text-[10px]">{formatContextWindow(m.contextWindow)}</span>
84
+ </span>
85
+ </SelectItem>
86
+ ))}
87
+ </>
88
+ )}
89
+
90
+ {/* Anthropic BYOK */}
91
+ {anthropicModels.length > 0 && (
92
+ <>
93
+ <div className="px-2 py-1 mt-1 text-[10px] font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-1">
94
+ Anthropic
95
+ {hasAnthropic
96
+ ? <Check className="h-2.5 w-2.5 text-emerald-500" />
97
+ : <Key className="h-2.5 w-2.5" />
98
+ }
99
+ </div>
100
+ {anthropicModels.map((m) => (
101
+ <SelectItem key={m.id} value={m.id} className={`text-xs ${!hasAnthropic ? 'opacity-50' : ''}`}>
102
+ <span className="flex items-center gap-1.5">
103
+ <span>{m.name}</span>
104
+ <CostBadge cost={m.cost} />
105
+ <span className="text-muted-foreground/50 text-[10px]">{formatContextWindow(m.contextWindow)}</span>
106
+ {!hasAnthropic && <Key className="h-3 w-3 text-muted-foreground/50" />}
107
+ </span>
108
+ </SelectItem>
109
+ ))}
110
+ </>
111
+ )}
72
112
 
73
- {/* Pro tier */}
74
- <div className="px-2 py-1 mt-1 text-[10px] font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-1">
75
- Pro
76
- </div>
77
- {PRO_MODELS.map((m) => (
78
- <SelectItem
79
- key={m.id}
80
- value={m.id}
81
- disabled={!hasPro}
82
- className="text-xs"
83
- >
84
- <span className="flex items-center gap-1.5">
85
- <span>{m.name}</span>
86
- <span className="text-muted-foreground text-[10px]">{m.provider}</span>
87
- {m.cost && (
88
- <span className={`text-[10px] font-mono ${
89
- m.cost === '$$$' ? 'text-amber-500' : m.cost === '$$' ? 'text-blue-500' : 'text-emerald-500'
90
- }`}>
91
- {m.cost}
113
+ {/* OpenAI BYOK */}
114
+ {openaiModels.length > 0 && (
115
+ <>
116
+ <div className="px-2 py-1 mt-1 text-[10px] font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-1">
117
+ OpenAI
118
+ {hasOpenai
119
+ ? <Check className="h-2.5 w-2.5 text-emerald-500" />
120
+ : <Key className="h-2.5 w-2.5" />
121
+ }
122
+ </div>
123
+ {openaiModels.map((m) => (
124
+ <SelectItem key={m.id} value={m.id} className={`text-xs ${!hasOpenai ? 'opacity-50' : ''}`}>
125
+ <span className="flex items-center gap-1.5">
126
+ <span>{m.name}</span>
127
+ <CostBadge cost={m.cost} />
128
+ <span className="text-muted-foreground/50 text-[10px]">{formatContextWindow(m.contextWindow)}</span>
129
+ {!hasOpenai && <Key className="h-3 w-3 text-muted-foreground/50" />}
92
130
  </span>
93
- )}
94
- <span className="text-muted-foreground/50 text-[10px]">{formatContextWindow(m.contextWindow)}</span>
95
- {!hasPro && <Lock className="h-3 w-3 text-muted-foreground/50" />}
96
- </span>
97
- </SelectItem>
98
- ))}
131
+ </SelectItem>
132
+ ))}
133
+ </>
134
+ )}
99
135
  </SelectContent>
100
136
  </Select>
101
137
  );
@@ -8,14 +8,17 @@
8
8
  */
9
9
 
10
10
  import { useState, useCallback, useMemo } from 'react';
11
- import { Globe, MapPin, PenLine, Check, X, Search, ChevronRight } from 'lucide-react';
11
+ import { Globe, MapPin, PenLine, Check, X, Search, ChevronRight, Mountain } from 'lucide-react';
12
12
  import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
13
13
  import { Badge } from '@/components/ui/badge';
14
14
  import { computeAngleToGridNorth, type GeoreferenceInfo, type MapConversion, type ProjectedCRS } from '@ifc-lite/parser';
15
15
  import { useViewerStore } from '@/store';
16
16
  import type { CoordinateInfo, GeometryResult } from '@ifc-lite/geometry';
17
17
  import { EpsgLookupDialog, type EpsgResult } from './EpsgLookupDialog';
18
- import { LocationMap } from './LocationMap';
18
+ import { LocationMap, type PickedPosition } from './LocationMap';
19
+ import { mergeMapConversion, mergeProjectedCRS } from '@/lib/geo/effective-georef';
20
+ import { useIfc } from '@/hooks/useIfc';
21
+ import { toast } from '@/components/ui/toast';
19
22
 
20
23
  // ── Field-specific assistance data ─────────────────────────────────────
21
24
 
@@ -71,9 +74,11 @@ interface GeorefRowProps {
71
74
  fieldEntity?: string;
72
75
  fieldName?: string;
73
76
  onSave?: (value: string | number) => void;
77
+ /** Extra inline content rendered after the value (e.g. terrain height button) */
78
+ children?: React.ReactNode;
74
79
  }
75
80
 
76
- function GeorefRow({ label, value, suffix, isComputed, isNumber, editable, isMutated, fieldEntity, fieldName, onSave }: GeorefRowProps) {
81
+ function GeorefRow({ label, value, suffix, isComputed, isNumber, editable, isMutated, fieldEntity, fieldName, onSave, children }: GeorefRowProps) {
77
82
  const [editing, setEditing] = useState(false);
78
83
  const [editValue, setEditValue] = useState('');
79
84
 
@@ -214,6 +219,7 @@ function GeorefRow({ label, value, suffix, isComputed, isNumber, editable, isMut
214
219
  </>
215
220
  )}
216
221
  </div>
222
+ {children}
217
223
  </div>
218
224
  </div>
219
225
  );
@@ -316,14 +322,27 @@ export interface GeoreferencingPanelProps {
316
322
  coordinateInfo?: CoordinateInfo;
317
323
  /** GeometryResult for KMZ export */
318
324
  geometryResult?: GeometryResult | null;
325
+ /** IFC project length unit → metres (e.g. 0.001 for mm models). Default 1. */
326
+ lengthUnitScale?: number;
319
327
  }
320
328
 
321
- export function GeoreferencingPanel({ georef, modelId, enableEditing, schemaVersion, coordinateInfo, geometryResult }: GeoreferencingPanelProps) {
329
+ export function GeoreferencingPanel({ georef, modelId, enableEditing, schemaVersion, coordinateInfo, geometryResult, lengthUnitScale }: GeoreferencingPanelProps) {
322
330
  const georefMutations = useViewerStore(s => s.georefMutations);
323
331
  const setGeorefField = useViewerStore(s => s.setGeorefField);
324
332
  const setGeorefFields = useViewerStore(s => s.setGeorefFields);
333
+ const cesiumEnabled = useViewerStore(s => s.cesiumEnabled);
334
+ const terrainClamp = useViewerStore(s => s.cesiumTerrainClamp);
335
+ const setCesiumTerrainClamp = useViewerStore(s => s.setCesiumTerrainClamp);
336
+ const cesiumTerrainHeight = useViewerStore(s => s.cesiumTerrainHeight);
337
+ const cesiumSourceModelId = useViewerStore(s => s.cesiumSourceModelId);
338
+ const models = useViewerStore(s => s.models);
339
+ const loading = useViewerStore(s => s.loading);
340
+ const { addModel, clearAllModels } = useIfc();
341
+ // Only show terrain actions when this panel's model is the one backing the Cesium overlay
342
+ const isActiveCesiumModel = !!modelId && modelId === cesiumSourceModelId;
325
343
  const [crsOpen, setCrsOpen] = useState(false);
326
344
  const [conversionOpen, setConversionOpen] = useState(false);
345
+ const [showReloadPrompt, setShowReloadPrompt] = useState(false);
327
346
 
328
347
  useViewerStore(s => s.mutationVersion);
329
348
 
@@ -331,37 +350,12 @@ export function GeoreferencingPanel({ georef, modelId, enableEditing, schemaVers
331
350
  const supportsStandardGeoreferencing = !schemaVersion?.toUpperCase().includes('2X3');
332
351
 
333
352
  const mergedCRS = useMemo((): ProjectedCRS | undefined => {
334
- const base = georef?.projectedCRS;
335
- const muts = mutations?.projectedCRS;
336
- if (!base && !muts) return undefined;
337
- return {
338
- id: base?.id ?? 0,
339
- name: muts?.name ?? base?.name ?? '',
340
- description: muts?.description ?? base?.description,
341
- geodeticDatum: muts?.geodeticDatum ?? base?.geodeticDatum,
342
- verticalDatum: muts?.verticalDatum ?? base?.verticalDatum,
343
- mapProjection: muts?.mapProjection ?? base?.mapProjection,
344
- mapZone: muts?.mapZone ?? base?.mapZone,
345
- mapUnit: muts?.mapUnit ?? base?.mapUnit,
346
- };
347
- }, [georef, mutations]);
353
+ return mergeProjectedCRS(georef?.projectedCRS, mutations?.projectedCRS, lengthUnitScale ?? 1);
354
+ }, [georef?.projectedCRS, mutations?.projectedCRS, lengthUnitScale]);
348
355
 
349
356
  const mergedConversion = useMemo((): MapConversion | undefined => {
350
- const base = georef?.mapConversion;
351
- const muts = mutations?.mapConversion;
352
- if (!base && !muts) return undefined;
353
- return {
354
- id: base?.id ?? 0,
355
- sourceCRS: base?.sourceCRS ?? 0,
356
- targetCRS: base?.targetCRS ?? 0,
357
- eastings: muts?.eastings ?? base?.eastings ?? 0,
358
- northings: muts?.northings ?? base?.northings ?? 0,
359
- orthogonalHeight: muts?.orthogonalHeight ?? base?.orthogonalHeight ?? 0,
360
- xAxisAbscissa: muts?.xAxisAbscissa ?? base?.xAxisAbscissa,
361
- xAxisOrdinate: muts?.xAxisOrdinate ?? base?.xAxisOrdinate,
362
- scale: muts?.scale ?? base?.scale,
363
- };
364
- }, [georef, mutations]);
357
+ return mergeMapConversion(georef?.mapConversion, mutations?.mapConversion);
358
+ }, [georef?.mapConversion, mutations?.mapConversion]);
365
359
 
366
360
  const angleToGridNorth = useMemo(() => {
367
361
  return computeAngleToGridNorth(mergedConversion?.xAxisAbscissa, mergedConversion?.xAxisOrdinate);
@@ -375,6 +369,13 @@ export function GeoreferencingPanel({ georef, modelId, enableEditing, schemaVers
375
369
  return 'm';
376
370
  }, [mergedCRS?.mapUnit]);
377
371
 
372
+ // Convert meters to map units (Cesium always returns meters)
373
+ const metersToMapUnit = useCallback((meters: number): number => {
374
+ if (mapUnitSuffix === 'ftUS') return meters / 0.3048006096;
375
+ if (mapUnitSuffix === 'ft') return meters / 0.3048;
376
+ return meters; // already meters
377
+ }, [mapUnitSuffix]);
378
+
378
379
  const isMutated = useCallback((entity: 'projectedCRS' | 'mapConversion', field: string): boolean => {
379
380
  if (!mutations) return false;
380
381
  const entityMuts = mutations[entity];
@@ -382,50 +383,117 @@ export function GeoreferencingPanel({ georef, modelId, enableEditing, schemaVers
382
383
  return field in entityMuts;
383
384
  }, [mutations]);
384
385
 
386
+ const requestAlignmentReload = useCallback(() => {
387
+ if (models.size > 1) {
388
+ setShowReloadPrompt(true);
389
+ }
390
+ }, [models.size]);
391
+
392
+ const reloadModelsForAlignment = useCallback(async () => {
393
+ const state = useViewerStore.getState();
394
+ const snapshot = Array.from(state.models.values()).sort((a, b) => (a.loadedAt ?? 0) - (b.loadedAt ?? 0));
395
+ const missingSource = snapshot.find(model => !model.sourceFile);
396
+ if (snapshot.length < 2) {
397
+ setShowReloadPrompt(false);
398
+ return;
399
+ }
400
+ if (missingSource) {
401
+ toast.error(`Cannot reload ${missingSource.name}: source file is not available`);
402
+ return;
403
+ }
404
+
405
+ try {
406
+ clearAllModels();
407
+ for (const model of snapshot) {
408
+ const sourceFile = model.sourceFile;
409
+ if (!sourceFile) continue;
410
+ const reloadedModelId = await addModel(sourceFile, {
411
+ name: model.name,
412
+ modelId: model.id,
413
+ loadedAt: model.loadedAt,
414
+ visible: model.visible,
415
+ collapsed: model.collapsed,
416
+ });
417
+ if (!reloadedModelId) {
418
+ throw new Error(`Failed to reload ${model.name}`);
419
+ }
420
+ if (model.visible === false) {
421
+ useViewerStore.getState().setModelVisibility(model.id, false);
422
+ }
423
+ }
424
+ setShowReloadPrompt(false);
425
+ toast.success('Reloaded models for edited georeferencing');
426
+ } catch (error) {
427
+ toast.error(error instanceof Error ? error.message : 'Reload failed');
428
+ }
429
+ }, [addModel, clearAllModels]);
430
+
385
431
  const handleSave = useCallback((entity: 'projectedCRS' | 'mapConversion', field: string, value: string | number) => {
386
432
  if (!modelId || !setGeorefField) return;
387
433
  const oldValue = entity === 'projectedCRS'
388
- ? georef?.projectedCRS?.[field as keyof ProjectedCRS]
389
- : georef?.mapConversion?.[field as keyof MapConversion];
434
+ ? mergedCRS?.[field as keyof ProjectedCRS]
435
+ : mergedConversion?.[field as keyof MapConversion];
390
436
  setGeorefField(modelId, entity, field, value, oldValue as string | number | undefined);
391
- }, [modelId, setGeorefField, georef]);
437
+ requestAlignmentReload();
438
+ }, [modelId, setGeorefField, mergedCRS, mergedConversion, requestAlignmentReload]);
392
439
 
393
440
  // Handle angle edit: compute and set both XAxisAbscissa and XAxisOrdinate
394
441
  const handleAngleChange = useCallback((abscissa: number, ordinate: number) => {
395
442
  if (!modelId || !setGeorefFields) return;
396
443
  setGeorefFields(modelId, 'mapConversion', [
397
- { field: 'xAxisAbscissa', value: abscissa, oldValue: georef?.mapConversion?.xAxisAbscissa },
398
- { field: 'xAxisOrdinate', value: ordinate, oldValue: georef?.mapConversion?.xAxisOrdinate },
444
+ { field: 'xAxisAbscissa', value: abscissa, oldValue: mergedConversion?.xAxisAbscissa },
445
+ { field: 'xAxisOrdinate', value: ordinate, oldValue: mergedConversion?.xAxisOrdinate },
399
446
  ]);
400
- }, [modelId, setGeorefFields, georef]);
447
+ requestAlignmentReload();
448
+ }, [modelId, setGeorefFields, mergedConversion, requestAlignmentReload]);
449
+
450
+ // Handle position picked from the map (reverse-projected easting/northing + optional terrain height)
451
+ const handleApplyPosition = useCallback((position: PickedPosition) => {
452
+ if (!modelId || !setGeorefFields) return;
453
+ const fields: Array<{ field: string; value: number; oldValue?: number }> = [
454
+ { field: 'eastings', value: position.easting, oldValue: mergedConversion?.eastings },
455
+ { field: 'northings', value: position.northing, oldValue: mergedConversion?.northings },
456
+ ];
457
+ if (position.terrainHeight !== null) {
458
+ fields.push({
459
+ field: 'orthogonalHeight',
460
+ value: Math.round(position.terrainHeight * 10) / 10,
461
+ oldValue: mergedConversion?.orthogonalHeight,
462
+ });
463
+ }
464
+ setGeorefFields(modelId, 'mapConversion', fields);
465
+ setConversionOpen(true);
466
+ requestAlignmentReload();
467
+ }, [modelId, setGeorefFields, mergedConversion, requestAlignmentReload]);
401
468
 
402
469
  const initializeMapConversionDefaults = useCallback(() => {
403
470
  if (!modelId || !setGeorefFields) return;
404
471
  setGeorefFields(modelId, 'mapConversion', [
405
- { field: 'eastings', value: georef?.mapConversion?.eastings ?? 0, oldValue: georef?.mapConversion?.eastings },
406
- { field: 'northings', value: georef?.mapConversion?.northings ?? 0, oldValue: georef?.mapConversion?.northings },
407
- { field: 'orthogonalHeight', value: georef?.mapConversion?.orthogonalHeight ?? 0, oldValue: georef?.mapConversion?.orthogonalHeight },
408
- { field: 'xAxisAbscissa', value: georef?.mapConversion?.xAxisAbscissa ?? 1, oldValue: georef?.mapConversion?.xAxisAbscissa },
409
- { field: 'xAxisOrdinate', value: georef?.mapConversion?.xAxisOrdinate ?? 0, oldValue: georef?.mapConversion?.xAxisOrdinate },
410
- { field: 'scale', value: georef?.mapConversion?.scale ?? 1, oldValue: georef?.mapConversion?.scale },
472
+ { field: 'eastings', value: mergedConversion?.eastings ?? 0, oldValue: mergedConversion?.eastings },
473
+ { field: 'northings', value: mergedConversion?.northings ?? 0, oldValue: mergedConversion?.northings },
474
+ { field: 'orthogonalHeight', value: mergedConversion?.orthogonalHeight ?? 0, oldValue: mergedConversion?.orthogonalHeight },
475
+ { field: 'xAxisAbscissa', value: mergedConversion?.xAxisAbscissa ?? 1, oldValue: mergedConversion?.xAxisAbscissa },
476
+ { field: 'xAxisOrdinate', value: mergedConversion?.xAxisOrdinate ?? 0, oldValue: mergedConversion?.xAxisOrdinate },
477
+ { field: 'scale', value: mergedConversion?.scale ?? 1, oldValue: mergedConversion?.scale },
411
478
  ]);
412
479
  setConversionOpen(true);
413
- }, [modelId, setGeorefFields, georef]);
480
+ requestAlignmentReload();
481
+ }, [modelId, setGeorefFields, mergedConversion, requestAlignmentReload]);
414
482
 
415
483
  const handleEpsgSelect = useCallback((result: EpsgResult) => {
416
484
  if (!modelId || !setGeorefFields) return;
417
485
  const epsgName = `EPSG:${result.code}`;
418
486
  const fieldUpdates: Array<{ field: string; value: string | number; oldValue?: string | number }> = [
419
- { field: 'name', value: epsgName, oldValue: georef?.projectedCRS?.name },
487
+ { field: 'name', value: epsgName, oldValue: mergedCRS?.name },
420
488
  ];
421
489
  if (result.name) {
422
- fieldUpdates.push({ field: 'description', value: result.name, oldValue: georef?.projectedCRS?.description });
490
+ fieldUpdates.push({ field: 'description', value: result.name, oldValue: mergedCRS?.description });
423
491
  }
424
492
  if (result.datum) {
425
- fieldUpdates.push({ field: 'geodeticDatum', value: result.datum, oldValue: georef?.projectedCRS?.geodeticDatum });
493
+ fieldUpdates.push({ field: 'geodeticDatum', value: result.datum, oldValue: mergedCRS?.geodeticDatum });
426
494
  }
427
495
  if (result.projection) {
428
- fieldUpdates.push({ field: 'mapProjection', value: result.projection, oldValue: georef?.projectedCRS?.mapProjection });
496
+ fieldUpdates.push({ field: 'mapProjection', value: result.projection, oldValue: mergedCRS?.mapProjection });
429
497
  }
430
498
  if (result.unit) {
431
499
  const unitUpper = result.unit.toUpperCase();
@@ -436,14 +504,15 @@ export function GeoreferencingPanel({ georef, modelId, enableEditing, schemaVers
436
504
  : unitUpper.includes('FOOT') || unitUpper.includes('FEET')
437
505
  ? 'FOOT'
438
506
  : result.unit;
439
- fieldUpdates.push({ field: 'mapUnit', value: mapUnit, oldValue: georef?.projectedCRS?.mapUnit });
507
+ fieldUpdates.push({ field: 'mapUnit', value: mapUnit, oldValue: mergedCRS?.mapUnit });
440
508
  }
441
509
  setGeorefFields(modelId, 'projectedCRS', fieldUpdates);
442
- if (!georef?.mapConversion && !mutations?.mapConversion) {
510
+ if (!mergedConversion && !mutations?.mapConversion) {
443
511
  initializeMapConversionDefaults();
444
512
  }
445
513
  setCrsOpen(true);
446
- }, [modelId, setGeorefFields, georef, mutations, initializeMapConversionDefaults]);
514
+ requestAlignmentReload();
515
+ }, [modelId, setGeorefFields, mergedCRS, mergedConversion, mutations, initializeMapConversionDefaults, requestAlignmentReload]);
447
516
 
448
517
  const hasData = mergedCRS || mergedConversion;
449
518
  const editable = enableEditing && !!modelId && supportsStandardGeoreferencing;
@@ -478,6 +547,33 @@ export function GeoreferencingPanel({ georef, modelId, enableEditing, schemaVers
478
547
 
479
548
  return (
480
549
  <div>
550
+ {showReloadPrompt && (
551
+ <div className="mx-2 my-2 border border-teal-300 dark:border-teal-700 bg-teal-50 dark:bg-teal-950/40 px-2.5 py-2">
552
+ <div className="flex items-start gap-2">
553
+ <MapPin className="h-3.5 w-3.5 text-teal-600 dark:text-teal-400 shrink-0 mt-0.5" />
554
+ <div className="min-w-0 flex-1">
555
+ <p className="text-[10px] text-zinc-700 dark:text-zinc-300">
556
+ Georeference saved. Reload loaded models to recompute 3D alignment?
557
+ </p>
558
+ <div className="mt-1.5 flex items-center gap-2">
559
+ <button
560
+ onClick={reloadModelsForAlignment}
561
+ disabled={loading}
562
+ className="px-2 py-0.5 text-[10px] font-medium text-white bg-teal-600 hover:bg-teal-700 disabled:opacity-50 disabled:cursor-not-allowed"
563
+ >
564
+ Reload models
565
+ </button>
566
+ <button
567
+ onClick={() => setShowReloadPrompt(false)}
568
+ className="px-2 py-0.5 text-[10px] text-zinc-600 dark:text-zinc-300 hover:bg-zinc-200/60 dark:hover:bg-zinc-800"
569
+ >
570
+ Later
571
+ </button>
572
+ </div>
573
+ </div>
574
+ </div>
575
+ </div>
576
+ )}
481
577
  {/* CRS summary — always visible */}
482
578
  <div className="px-2 py-1.5 flex items-center gap-2">
483
579
  <Globe className="h-3 w-3 text-teal-500 shrink-0" />
@@ -561,7 +657,9 @@ export function GeoreferencingPanel({ georef, modelId, enableEditing, schemaVers
561
657
  <GeorefRow label="Type" value="IfcMapConversion" />
562
658
  <GeorefRow label="Eastings" value={mergedConversion.eastings} suffix={mapUnitSuffix} isNumber editable={editable} isMutated={isMutated('mapConversion', 'eastings')} fieldEntity="mapConversion" fieldName="eastings" onSave={v => handleSave('mapConversion', 'eastings', v)} />
563
659
  <GeorefRow label="Northings" value={mergedConversion.northings} suffix={mapUnitSuffix} isNumber editable={editable} isMutated={isMutated('mapConversion', 'northings')} fieldEntity="mapConversion" fieldName="northings" onSave={v => handleSave('mapConversion', 'northings', v)} />
564
- <GeorefRow label="OrthogonalHeight" value={mergedConversion.orthogonalHeight} suffix={mapUnitSuffix} isNumber editable={editable} isMutated={isMutated('mapConversion', 'orthogonalHeight')} fieldEntity="mapConversion" fieldName="orthogonalHeight" onSave={v => handleSave('mapConversion', 'orthogonalHeight', v)} />
660
+ <GeorefRow label="OrthogonalHeight" value={mergedConversion.orthogonalHeight} suffix={mapUnitSuffix} isNumber editable={editable} isMutated={isMutated('mapConversion', 'orthogonalHeight')} fieldEntity="mapConversion" fieldName="orthogonalHeight" onSave={v => handleSave('mapConversion', 'orthogonalHeight', v)}>
661
+ <TerrainHeightButton modelId={modelId} editable={editable} onApply={(h) => handleSave('mapConversion', 'orthogonalHeight', Math.round(metersToMapUnit(h) * 100) / 100)} />
662
+ </GeorefRow>
565
663
  <GeorefRow label="XAxisAbscissa" value={mergedConversion.xAxisAbscissa} isNumber editable={editable} isMutated={isMutated('mapConversion', 'xAxisAbscissa')} fieldEntity="mapConversion" fieldName="xAxisAbscissa" onSave={v => handleSave('mapConversion', 'xAxisAbscissa', v)} />
566
664
  <GeorefRow label="XAxisOrdinate" value={mergedConversion.xAxisOrdinate} isNumber editable={editable} isMutated={isMutated('mapConversion', 'xAxisOrdinate')} fieldEntity="mapConversion" fieldName="xAxisOrdinate" onSave={v => handleSave('mapConversion', 'xAxisOrdinate', v)} />
567
665
  <AngleRow angle={angleToGridNorth} editable={editable} onAngleChange={handleAngleChange} />
@@ -584,8 +682,84 @@ export function GeoreferencingPanel({ georef, modelId, enableEditing, schemaVers
584
682
  </div>
585
683
  )}
586
684
 
685
+ {/* Terrain clamp toggle — only when Cesium overlay is active */}
686
+ {cesiumEnabled && isActiveCesiumModel && mergedConversion && (
687
+ <div className="px-3 py-1.5 border-t border-zinc-100 dark:border-zinc-900 space-y-1">
688
+ <div className="flex items-center gap-2">
689
+ <Mountain className="h-3 w-3 text-teal-500 shrink-0" />
690
+ <label className="flex items-center gap-1.5 cursor-pointer flex-1">
691
+ <input
692
+ type="checkbox"
693
+ checked={terrainClamp}
694
+ onChange={(e) => setCesiumTerrainClamp(e.target.checked)}
695
+ className="accent-teal-500 h-3 w-3"
696
+ />
697
+ <span className="text-[10px] text-zinc-600 dark:text-zinc-400">Clamp to terrain</span>
698
+ </label>
699
+ {cesiumTerrainHeight !== null ? (
700
+ <span className="text-[9px] font-mono text-teal-500">
701
+ {cesiumTerrainHeight.toFixed(1)} m
702
+ </span>
703
+ ) : (
704
+ <span className="text-[9px] font-mono text-zinc-400">querying...</span>
705
+ )}
706
+ </div>
707
+ {cesiumTerrainHeight !== null && editable && modelId && (
708
+ <div className="flex items-center gap-1 ml-5">
709
+ <button
710
+ onClick={() => handleSave('mapConversion', 'orthogonalHeight', Math.round(metersToMapUnit(cesiumTerrainHeight) * 100) / 100)}
711
+ className="text-[9px] text-teal-500 hover:text-teal-700 dark:hover:text-teal-300 transition-colors flex items-center gap-0.5"
712
+ >
713
+ <Mountain className="h-2.5 w-2.5" />
714
+ Set OrthogonalHeight to {cesiumTerrainHeight.toFixed(1)} m
715
+ </button>
716
+ </div>
717
+ )}
718
+ </div>
719
+ )}
720
+
587
721
  {/* Location minimap */}
588
- <LocationMap mapConversion={mergedConversion} projectedCRS={mergedCRS} coordinateInfo={coordinateInfo} geometryResult={geometryResult} />
722
+ <LocationMap
723
+ mapConversion={mergedConversion}
724
+ projectedCRS={mergedCRS}
725
+ coordinateInfo={coordinateInfo}
726
+ geometryResult={geometryResult}
727
+ lengthUnitScale={lengthUnitScale}
728
+ editable={editable}
729
+ onApplyPosition={editable ? handleApplyPosition : undefined}
730
+ />
589
731
  </div>
590
732
  );
591
733
  }
734
+
735
+ /** Small button to apply Cesium terrain height to OrthogonalHeight field */
736
+ function TerrainHeightButton({ modelId, editable, onApply }: {
737
+ modelId?: string;
738
+ editable?: boolean;
739
+ onApply: (height: number) => void;
740
+ }) {
741
+ const cesiumEnabled = useViewerStore(s => s.cesiumEnabled);
742
+ const terrainHeight = useViewerStore(s => s.cesiumTerrainHeight);
743
+ const sourceModelId = useViewerStore(s => s.cesiumSourceModelId);
744
+
745
+ // Only show when this panel's model is the active Cesium model
746
+ if (!cesiumEnabled || terrainHeight === null || !editable || !modelId || modelId !== sourceModelId) return null;
747
+
748
+ return (
749
+ <Tooltip>
750
+ <TooltipTrigger asChild>
751
+ <button
752
+ onClick={(e) => {
753
+ e.stopPropagation();
754
+ onApply(terrainHeight);
755
+ }}
756
+ className="flex items-center gap-0.5 text-[9px] text-teal-500 hover:text-teal-700 dark:hover:text-teal-300 transition-colors mt-0.5"
757
+ >
758
+ <Mountain className="h-2.5 w-2.5" />
759
+ <span>{terrainHeight.toFixed(1)} m</span>
760
+ </button>
761
+ </TooltipTrigger>
762
+ <TooltipContent>Set OrthogonalHeight to Cesium terrain elevation ({terrainHeight.toFixed(1)} m)</TooltipContent>
763
+ </Tooltip>
764
+ );
765
+ }