@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
@@ -13,19 +13,304 @@
13
13
  import { useCallback } from 'react';
14
14
  import { useShallow } from 'zustand/react/shallow';
15
15
  import { useViewerStore, type FederatedModel, type SchemaVersion } from '../store.js';
16
- import { IfcParser, detectFormat, parseIfcx, parseFederatedIfcx, type IfcDataStore, type FederatedIfcxParseResult } from '@ifc-lite/parser';
17
- import { GeometryProcessor, GeometryQuality, type MeshData, type CoordinateInfo } from '@ifc-lite/geometry';
16
+ import {
17
+ detectFormat,
18
+ parseFederatedIfcx,
19
+ type IfcDataStore,
20
+ type FederatedIfcxParseResult,
21
+ type MapConversion,
22
+ type ProjectedCRS,
23
+ } from '@ifc-lite/parser';
24
+ import type { CoordinateInfo, MeshData } from '@ifc-lite/geometry';
18
25
  import { IfcQuery } from '@ifc-lite/query';
19
26
  import { buildSpatialIndexGuarded } from '../utils/loadingUtils.js';
20
- import { loadGLBToMeshData } from '@ifc-lite/cache';
21
-
22
27
  import { getDynamicBatchConfig } from '../utils/ifcConfig.js';
28
+ import { calculateMeshBounds, createCoordinateInfo } from '../utils/localParsingUtils.js';
23
29
  import {
24
- calculateMeshBounds,
25
- createCoordinateInfo,
26
- calculateStoreyHeights,
27
- normalizeColor,
28
- } from '../utils/localParsingUtils.js';
30
+ convertIfcxMeshes,
31
+ getMaxExpressId,
32
+ parseGlbViewerModel,
33
+ parseIfcxViewerModel,
34
+ parseStepBufferViewerModel,
35
+ } from './ingest/viewerModelIngest.js';
36
+ import { readNativeFile, type NativeFileHandle } from '../services/file-dialog.js';
37
+ import { getEffectiveGeoreference, type GeorefMutationDataLike } from '../lib/geo/effective-georef.js';
38
+
39
+ function isNativeFileHandle(file: File | NativeFileHandle): file is NativeFileHandle {
40
+ return typeof (file as NativeFileHandle).path === 'string';
41
+ }
42
+
43
+ function toExactArrayBuffer(bytes: Uint8Array): ArrayBuffer {
44
+ if (bytes.buffer instanceof ArrayBuffer && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength) {
45
+ return bytes.buffer;
46
+ }
47
+ return bytes.slice().buffer;
48
+ }
49
+
50
+ type FederatedGeometryResult = NonNullable<FederatedModel['geometryResult']>;
51
+
52
+ interface ModelGeoref {
53
+ mapConversion: MapConversion;
54
+ projectedCRS: ProjectedCRS;
55
+ lengthUnitScale: number;
56
+ coordinateInfo?: CoordinateInfo;
57
+ }
58
+
59
+ interface AffineTransform3D {
60
+ m00: number;
61
+ m01: number;
62
+ m02: number;
63
+ tx: number;
64
+ m10: number;
65
+ m11: number;
66
+ m12: number;
67
+ ty: number;
68
+ m20: number;
69
+ m21: number;
70
+ m22: number;
71
+ tz: number;
72
+ }
73
+
74
+ function getMapUnitScale(georef: ModelGeoref): number {
75
+ return georef.projectedCRS.mapUnitScale ?? georef.lengthUnitScale ?? 1;
76
+ }
77
+
78
+ function getAxis(conversion: MapConversion): { a: number; o: number; scale: number; denom: number } {
79
+ const a = conversion.xAxisAbscissa ?? 1;
80
+ const o = conversion.xAxisOrdinate ?? 0;
81
+ const scale = conversion.scale ?? 1;
82
+ const denom = Math.max(a * a + o * o, 1e-12);
83
+ return { a, o, scale, denom };
84
+ }
85
+
86
+ function extractModelGeoref(
87
+ dataStore: IfcDataStore,
88
+ coordinateInfo?: CoordinateInfo,
89
+ mutations?: GeorefMutationDataLike,
90
+ ): ModelGeoref | null {
91
+ const georef = getEffectiveGeoreference(dataStore, coordinateInfo, mutations);
92
+ if (!georef?.mapConversion || !georef.projectedCRS?.name) return null;
93
+ return {
94
+ mapConversion: georef.mapConversion,
95
+ projectedCRS: georef.projectedCRS,
96
+ lengthUnitScale: georef.lengthUnitScale,
97
+ coordinateInfo,
98
+ };
99
+ }
100
+
101
+ function crsKey(crs: ProjectedCRS): string {
102
+ return `${crs.name ?? ''}|${crs.geodeticDatum ?? ''}|${crs.mapProjection ?? ''}|${crs.mapZone ?? ''}`.toUpperCase();
103
+ }
104
+
105
+ function canAlignInSameProjectedCrs(a: ModelGeoref, b: ModelGeoref): boolean {
106
+ return crsKey(a.projectedCRS) === crsKey(b.projectedCRS);
107
+ }
108
+
109
+ function totalYupOffset(coordinateInfo?: CoordinateInfo): { x: number; y: number; z: number } {
110
+ const shift = coordinateInfo?.originShift ?? { x: 0, y: 0, z: 0 };
111
+ const rtc = coordinateInfo?.wasmRtcOffset;
112
+ const rtcYup = rtc ? { x: rtc.x, y: rtc.z, z: -rtc.y } : { x: 0, y: 0, z: 0 };
113
+ return {
114
+ x: shift.x + rtcYup.x,
115
+ y: shift.y + rtcYup.y,
116
+ z: shift.z + rtcYup.z,
117
+ };
118
+ }
119
+
120
+ function emptyBounds() {
121
+ return {
122
+ min: { x: Infinity, y: Infinity, z: Infinity },
123
+ max: { x: -Infinity, y: -Infinity, z: -Infinity },
124
+ };
125
+ }
126
+
127
+ function zeroBounds() {
128
+ return {
129
+ min: { x: 0, y: 0, z: 0 },
130
+ max: { x: 0, y: 0, z: 0 },
131
+ };
132
+ }
133
+
134
+ function updateBounds(bounds: ReturnType<typeof emptyBounds>, x: number, y: number, z: number): boolean {
135
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return false;
136
+ bounds.min.x = Math.min(bounds.min.x, x);
137
+ bounds.min.y = Math.min(bounds.min.y, y);
138
+ bounds.min.z = Math.min(bounds.min.z, z);
139
+ bounds.max.x = Math.max(bounds.max.x, x);
140
+ bounds.max.y = Math.max(bounds.max.y, y);
141
+ bounds.max.z = Math.max(bounds.max.z, z);
142
+ return true;
143
+ }
144
+
145
+ function buildGeorefAlignmentTransform(source: ModelGeoref, reference: ModelGeoref): AffineTransform3D | null {
146
+ const sourceConv = source.mapConversion;
147
+ const refConv = reference.mapConversion;
148
+ const sourceAxis = getAxis(sourceConv);
149
+ const refAxis = getAxis(refConv);
150
+ const refDenom = refAxis.scale * refAxis.denom;
151
+ if (Math.abs(refDenom) < 1e-12) return null;
152
+
153
+ const sourceMapUnitScale = getMapUnitScale(source);
154
+ const refMapUnitScale = getMapUnitScale(reference);
155
+ const sourceOffset = totalYupOffset(source.coordinateInfo);
156
+ const refOffset = totalYupOffset(reference.coordinateInfo);
157
+
158
+ const eVx = sourceAxis.scale * sourceAxis.a;
159
+ const eVz = sourceAxis.scale * sourceAxis.o;
160
+ const eC = sourceConv.eastings * sourceMapUnitScale
161
+ + sourceAxis.scale * (sourceAxis.a * sourceOffset.x + sourceAxis.o * sourceOffset.z)
162
+ - refConv.eastings * refMapUnitScale;
163
+
164
+ const nVx = sourceAxis.scale * sourceAxis.o;
165
+ const nVz = -sourceAxis.scale * sourceAxis.a;
166
+ const nC = sourceConv.northings * sourceMapUnitScale
167
+ + sourceAxis.scale * (sourceAxis.o * sourceOffset.x - sourceAxis.a * sourceOffset.z)
168
+ - refConv.northings * refMapUnitScale;
169
+
170
+ const hC = sourceConv.orthogonalHeight * sourceMapUnitScale
171
+ + sourceOffset.y
172
+ - refConv.orthogonalHeight * refMapUnitScale;
173
+
174
+ const invRefDenom = 1 / refDenom;
175
+ const xVx = (refAxis.a * eVx + refAxis.o * nVx) * invRefDenom;
176
+ const xVz = (refAxis.a * eVz + refAxis.o * nVz) * invRefDenom;
177
+ const xC = (refAxis.a * eC + refAxis.o * nC) * invRefDenom - refOffset.x;
178
+
179
+ const yVx = (-refAxis.o * eVx + refAxis.a * nVx) * invRefDenom;
180
+ const yVz = (-refAxis.o * eVz + refAxis.a * nVz) * invRefDenom;
181
+ const yC = (-refAxis.o * eC + refAxis.a * nC) * invRefDenom;
182
+
183
+ return {
184
+ m00: xVx,
185
+ m01: 0,
186
+ m02: xVz,
187
+ tx: xC,
188
+ m10: 0,
189
+ m11: 1,
190
+ m12: 0,
191
+ ty: hC - refOffset.y,
192
+ m20: -yVx,
193
+ m21: 0,
194
+ m22: -yVz,
195
+ tz: -yC - refOffset.z,
196
+ };
197
+ }
198
+
199
+ function isIdentityTransform(transform: AffineTransform3D): boolean {
200
+ const eps = 1e-7;
201
+ return Math.abs(transform.m00 - 1) < eps
202
+ && Math.abs(transform.m01) < eps
203
+ && Math.abs(transform.m02) < eps
204
+ && Math.abs(transform.tx) < eps
205
+ && Math.abs(transform.m10) < eps
206
+ && Math.abs(transform.m11 - 1) < eps
207
+ && Math.abs(transform.m12) < eps
208
+ && Math.abs(transform.ty) < eps
209
+ && Math.abs(transform.m20) < eps
210
+ && Math.abs(transform.m21) < eps
211
+ && Math.abs(transform.m22 - 1) < eps
212
+ && Math.abs(transform.tz) < eps;
213
+ }
214
+
215
+ function applyAlignmentTransformAndUpdateBounds(
216
+ geometry: FederatedGeometryResult,
217
+ transform: AffineTransform3D,
218
+ referenceInfo?: CoordinateInfo,
219
+ ): void {
220
+ const bounds = emptyBounds();
221
+ let found = false;
222
+
223
+ for (const mesh of geometry.meshes) {
224
+ const positions = mesh.positions;
225
+ for (let i = 0; i < positions.length; i += 3) {
226
+ const x = positions[i];
227
+ const y = positions[i + 1];
228
+ const z = positions[i + 2];
229
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
230
+ continue;
231
+ }
232
+
233
+ const alignedX = transform.m00 * x + transform.m01 * y + transform.m02 * z + transform.tx;
234
+ const alignedY = transform.m10 * x + transform.m11 * y + transform.m12 * z + transform.ty;
235
+ const alignedZ = transform.m20 * x + transform.m21 * y + transform.m22 * z + transform.tz;
236
+ positions[i] = alignedX;
237
+ positions[i + 1] = alignedY;
238
+ positions[i + 2] = alignedZ;
239
+ found = updateBounds(bounds, alignedX, alignedY, alignedZ) || found;
240
+ }
241
+
242
+ // Rotate normals by the transform's 3×3 linear part (translation omitted)
243
+ // and renormalize. CRS alignment is a rigid rotation, so the linear part
244
+ // itself is the correct transform for normals; degenerate results from
245
+ // zero-length or non-finite inputs are left in place.
246
+ const normals = mesh.normals;
247
+ if (normals && normals.length >= 3) {
248
+ for (let i = 0; i < normals.length; i += 3) {
249
+ const nx = normals[i];
250
+ const ny = normals[i + 1];
251
+ const nz = normals[i + 2];
252
+ if (!Number.isFinite(nx) || !Number.isFinite(ny) || !Number.isFinite(nz)) {
253
+ continue;
254
+ }
255
+ const rx = transform.m00 * nx + transform.m01 * ny + transform.m02 * nz;
256
+ const ry = transform.m10 * nx + transform.m11 * ny + transform.m12 * nz;
257
+ const rz = transform.m20 * nx + transform.m21 * ny + transform.m22 * nz;
258
+ const len = Math.sqrt(rx * rx + ry * ry + rz * rz);
259
+ if (!Number.isFinite(len) || len < 1e-12) {
260
+ continue;
261
+ }
262
+ normals[i] = rx / len;
263
+ normals[i + 1] = ry / len;
264
+ normals[i + 2] = rz / len;
265
+ }
266
+ }
267
+ }
268
+
269
+ geometry.coordinateInfo = {
270
+ originShift: referenceInfo?.originShift ?? { x: 0, y: 0, z: 0 },
271
+ originalBounds: found ? bounds : zeroBounds(),
272
+ shiftedBounds: found ? bounds : zeroBounds(),
273
+ hasLargeCoordinates: referenceInfo?.hasLargeCoordinates ?? false,
274
+ wasmRtcOffset: referenceInfo?.wasmRtcOffset,
275
+ buildingRotation: referenceInfo?.buildingRotation,
276
+ };
277
+ }
278
+
279
+ function alignGeometryToReferenceGeoref(
280
+ geometry: FederatedGeometryResult,
281
+ source: ModelGeoref,
282
+ reference: ModelGeoref,
283
+ ): boolean {
284
+ if (!canAlignInSameProjectedCrs(source, reference)) {
285
+ return false;
286
+ }
287
+
288
+ const transform = buildGeorefAlignmentTransform(source, reference);
289
+ if (!transform) {
290
+ return false;
291
+ }
292
+
293
+ if (!isIdentityTransform(transform)) {
294
+ applyAlignmentTransformAndUpdateBounds(geometry, transform, reference.coordinateInfo);
295
+ }
296
+ return true;
297
+ }
298
+
299
+ function findReferenceGeorefModel(): ModelGeoref | null {
300
+ const state = useViewerStore.getState();
301
+ const modelEntries = Array.from(state.models.entries()) as Array<[string, FederatedModel]>;
302
+ const sorted = [...modelEntries].sort(([, a], [, b]) => (a.loadedAt ?? 0) - (b.loadedAt ?? 0));
303
+ for (const [modelId, model] of sorted) {
304
+ if (!model.ifcDataStore || !model.geometryResult) continue;
305
+ const georef = extractModelGeoref(
306
+ model.ifcDataStore,
307
+ model.geometryResult.coordinateInfo,
308
+ state.georefMutations.get(modelId),
309
+ );
310
+ if (georef) return georef;
311
+ }
312
+ return null;
313
+ }
29
314
 
30
315
  /**
31
316
  * Extended data store type for IFCX (IFC5) files.
@@ -43,41 +328,6 @@ export interface IfcxDataStore extends IfcDataStore {
43
328
  _layerInfo?: Array<{ id: string; name: string; meshCount: number }>;
44
329
  }
45
330
 
46
- /** Shape of raw meshes returned from IFCX parsers before conversion to MeshData */
47
- interface RawIfcxMesh {
48
- expressId?: number;
49
- express_id?: number;
50
- id?: number;
51
- positions: Float32Array | number[];
52
- indices: Uint32Array | number[];
53
- normals: Float32Array | number[];
54
- color?: [number, number, number, number] | [number, number, number];
55
- ifcType?: string;
56
- ifc_type?: string;
57
- }
58
-
59
- /**
60
- * Convert raw IFCX parser meshes to viewer-compatible MeshData[].
61
- * Ensures typed arrays, normalizes colors, and filters out empty meshes.
62
- */
63
- function convertIfcxMeshes(rawMeshes: RawIfcxMesh[]): MeshData[] {
64
- return rawMeshes.map((m) => {
65
- const positions = m.positions instanceof Float32Array ? m.positions : new Float32Array(m.positions || []);
66
- const indices = m.indices instanceof Uint32Array ? m.indices : new Uint32Array(m.indices || []);
67
- const normals = m.normals instanceof Float32Array ? m.normals : new Float32Array(m.normals || []);
68
- const color = normalizeColor(m.color);
69
-
70
- return {
71
- expressId: m.expressId ?? m.express_id ?? m.id ?? 0,
72
- positions,
73
- indices,
74
- normals,
75
- color,
76
- ifcType: m.ifcType ?? m.ifc_type ?? 'IfcProduct',
77
- };
78
- }).filter((m) => m.positions.length > 0 && m.indices.length > 0);
79
- }
80
-
81
331
  /**
82
332
  * Hook providing multi-model federation operations
83
333
  * Includes addModel, removeModel, federated IFCX loading, overlay management,
@@ -122,12 +372,17 @@ export function useIfcFederation() {
122
372
  * Returns the model ID on success, null on failure
123
373
  */
124
374
  const addModel = useCallback(async (
125
- file: File,
126
- options?: { name?: string }
375
+ file: File | NativeFileHandle,
376
+ options?: {
377
+ name?: string;
378
+ modelId?: string;
379
+ loadedAt?: number;
380
+ visible?: boolean;
381
+ collapsed?: boolean;
382
+ }
127
383
  ): Promise<string | null> => {
128
- const modelId = crypto.randomUUID();
129
- const totalStartTime = performance.now();
130
-
384
+ const modelId = options?.modelId ?? crypto.randomUUID();
385
+ const addStart = performance.now();
131
386
  try {
132
387
  // IMPORTANT: Before adding a new model, check if there's a legacy model
133
388
  // (loaded via loadFile) that's not in the Map yet. If so, migrate it first.
@@ -167,11 +422,11 @@ export function useIfcFederation() {
167
422
  schemaVersion: 'IFC4',
168
423
  loadedAt: Date.now() - 1000,
169
424
  fileSize: 0,
425
+ sourceFile: undefined,
170
426
  idOffset: legacyOffset,
171
427
  maxExpressId: legacyMaxExpressId,
172
428
  };
173
429
  storeAddModel(legacyModel);
174
- console.log(`[useIfc] Migrated legacy model "${legacyModel.name}" to federation (offset: ${legacyOffset}, maxId: ${legacyMaxExpressId})`);
175
430
  }
176
431
 
177
432
  setLoading(true);
@@ -179,180 +434,93 @@ export function useIfcFederation() {
179
434
  setProgress({ phase: 'Loading file', percent: 0 });
180
435
 
181
436
  // Read file from disk
182
- const buffer = await file.arrayBuffer();
437
+ const buffer = isNativeFileHandle(file)
438
+ ? toExactArrayBuffer(await readNativeFile(file.path))
439
+ : await file.arrayBuffer();
183
440
  const fileSizeMB = buffer.byteLength / (1024 * 1024);
184
441
 
185
442
  // Detect file format
186
443
  const format = detectFormat(buffer);
187
444
 
188
445
  let parsedDataStore: IfcDataStore | null = null;
189
- let parsedGeometry: { meshes: MeshData[]; totalVertices: number; totalTriangles: number; coordinateInfo: CoordinateInfo } | null = null;
446
+ let parsedGeometry: FederatedModel['geometryResult'] = null;
190
447
  let schemaVersion: SchemaVersion = 'IFC4';
191
448
 
192
- // IFCX files must be parsed client-side
193
449
  if (format === 'ifcx') {
194
450
  setProgress({ phase: 'Parsing IFCX (client-side)', percent: 10 });
195
-
196
- const ifcxResult = await parseIfcx(buffer, {
197
- onProgress: (prog: { phase: string; percent: number }) => {
198
- setProgress({ phase: `IFCX ${prog.phase}`, percent: 10 + (prog.percent * 0.8) });
199
- },
200
- });
201
-
202
- // Convert IFCX meshes to viewer format
203
- const meshes: MeshData[] = convertIfcxMeshes(ifcxResult.meshes);
204
-
205
- // Check if this is an overlay-only IFCX file (no geometry)
206
- if (meshes.length === 0 && ifcxResult.entityCount > 0) {
207
- console.warn(`[useIfc] IFCX file "${file.name}" has no geometry - this is an overlay file.`);
208
- setError(`"${file.name}" is an overlay file with no geometry. Please load it together with a base IFCX file (select all files at once for federated loading).`);
209
- setLoading(false);
210
- return null;
451
+ try {
452
+ const result = await parseIfcxViewerModel(buffer, setProgress);
453
+ parsedDataStore = result.dataStore;
454
+ parsedGeometry = result.geometryResult;
455
+ schemaVersion = result.schemaVersion;
456
+ } catch (error) {
457
+ if (error instanceof Error && error.message === 'overlay-only-ifcx') {
458
+ console.warn(`[useIfc] IFCX file "${file.name}" has no geometry - this is an overlay file.`);
459
+ setError(`"${file.name}" is an overlay file with no geometry. Please load it together with a base IFCX file (select all files at once for federated loading).`);
460
+ setLoading(false);
461
+ return null;
462
+ }
463
+ throw error;
211
464
  }
212
-
213
- const { bounds, stats } = calculateMeshBounds(meshes);
214
- const coordinateInfo = createCoordinateInfo(bounds);
215
-
216
- parsedGeometry = {
217
- meshes,
218
- totalVertices: stats.totalVertices,
219
- totalTriangles: stats.totalTriangles,
220
- coordinateInfo,
221
- };
222
-
223
- parsedDataStore = {
224
- fileSize: ifcxResult.fileSize,
225
- schemaVersion: 'IFC5' as const,
226
- entityCount: ifcxResult.entityCount,
227
- parseTime: ifcxResult.parseTime,
228
- source: new Uint8Array(buffer),
229
- entityIndex: { byId: new Map(), byType: new Map() },
230
- strings: ifcxResult.strings,
231
- entities: ifcxResult.entities,
232
- properties: ifcxResult.properties,
233
- quantities: ifcxResult.quantities,
234
- relationships: ifcxResult.relationships,
235
- spatialHierarchy: ifcxResult.spatialHierarchy,
236
- } as IfcxDataStore;
237
-
238
- schemaVersion = 'IFC5';
239
-
240
465
  } else if (format === 'glb') {
241
- // GLB files: parse directly to MeshData (geometry only, no IFC data model)
242
466
  setProgress({ phase: 'Parsing GLB', percent: 10 });
243
-
244
- const meshes = loadGLBToMeshData(new Uint8Array(buffer));
245
-
246
- if (meshes.length === 0) {
247
- setError('GLB file contains no geometry');
248
- setLoading(false);
249
- return null;
250
- }
251
-
252
- const { bounds, stats } = calculateMeshBounds(meshes);
253
- const coordinateInfo = createCoordinateInfo(bounds);
254
-
255
- parsedGeometry = {
256
- meshes,
257
- totalVertices: stats.totalVertices,
258
- totalTriangles: stats.totalTriangles,
259
- coordinateInfo,
260
- };
261
-
262
- // Create a minimal data store for GLB (no IFC properties)
263
- parsedDataStore = {
264
- fileSize: buffer.byteLength,
265
- schemaVersion: 'IFC4' as const,
266
- entityCount: meshes.length,
267
- parseTime: 0,
268
- source: new Uint8Array(0),
269
- entityIndex: { byId: new Map(), byType: new Map() },
270
- strings: { getString: () => undefined, getStringId: () => undefined, count: 0 } as unknown as IfcDataStore['strings'],
271
- entities: { count: 0, getId: () => 0, getType: () => 0, getName: () => undefined, getGlobalId: () => undefined } as unknown as IfcDataStore['entities'],
272
- properties: { count: 0, getPropertiesForEntity: () => [], getPropertySetForEntity: () => [] } as unknown as IfcDataStore['properties'],
273
- quantities: { count: 0, getQuantitiesForEntity: () => [] } as unknown as IfcDataStore['quantities'],
274
- relationships: { count: 0, getRelationships: () => [], getRelated: () => [] } as unknown as IfcDataStore['relationships'],
275
- spatialHierarchy: null as unknown as IfcDataStore['spatialHierarchy'],
276
- } as unknown as IfcDataStore;
277
-
278
- schemaVersion = 'IFC4'; // GLB doesn't have a schema version, use IFC4 as default
279
-
467
+ const result = await parseGlbViewerModel(buffer);
468
+ parsedDataStore = result.dataStore;
469
+ parsedGeometry = result.geometryResult;
470
+ schemaVersion = result.schemaVersion;
280
471
  } else {
281
- // IFC4/IFC2X3 STEP format - use WASM parsing
282
472
  setProgress({ phase: 'Starting geometry streaming', percent: 10 });
283
473
 
284
- const geometryProcessor = new GeometryProcessor({ quality: GeometryQuality.Balanced });
285
- await geometryProcessor.init();
286
-
287
- // Parse data model
288
- const parser = new IfcParser();
289
- const wasmApi = geometryProcessor.getApi();
290
-
291
- const dataStorePromise = parser.parseColumnar(buffer, { wasmApi });
292
-
293
- // Process geometry
294
- const allMeshes: MeshData[] = [];
295
- let finalCoordinateInfo: CoordinateInfo | null = null;
296
- // Capture RTC offset from WASM for proper multi-model alignment
297
- let capturedRtcOffset: { x: number; y: number; z: number } | null = null;
298
-
299
- const dynamicBatchConfig = getDynamicBatchConfig(fileSizeMB);
300
-
301
- for await (const event of geometryProcessor.processAdaptive(new Uint8Array(buffer), {
302
- sizeThreshold: 2 * 1024 * 1024,
303
- batchSize: dynamicBatchConfig,
304
- })) {
305
- switch (event.type) {
306
- case 'batch': {
307
- for (let i = 0; i < event.meshes.length; i++) allMeshes.push(event.meshes[i]);
308
- finalCoordinateInfo = event.coordinateInfo ?? null;
309
- const progressPercent = 10 + Math.min(80, (allMeshes.length / 1000) * 0.8);
310
- setProgress({ phase: `Processing geometry (${allMeshes.length} meshes)`, percent: progressPercent });
311
- break;
312
- }
313
- case 'rtcOffset': {
314
- // Capture RTC offset from WASM for multi-model alignment
315
- if (event.hasRtc) {
316
- capturedRtcOffset = event.rtcOffset;
317
- }
318
- break;
319
- }
320
- case 'complete':
321
- finalCoordinateInfo = event.coordinateInfo ?? null;
322
- break;
323
- }
324
- }
325
-
326
- parsedDataStore = await dataStorePromise;
327
-
328
- // Calculate storey heights
329
- if (parsedDataStore.spatialHierarchy && parsedDataStore.spatialHierarchy.storeyHeights.size === 0 && parsedDataStore.spatialHierarchy.storeyElevations.size > 1) {
330
- const calculatedHeights = calculateStoreyHeights(parsedDataStore.spatialHierarchy.storeyElevations);
331
- for (const [storeyId, height] of calculatedHeights) {
332
- parsedDataStore.spatialHierarchy.storeyHeights.set(storeyId, height);
333
- }
334
- }
335
-
336
- parsedGeometry = {
337
- meshes: allMeshes,
338
- totalVertices: allMeshes.reduce((sum, m) => sum + m.positions.length / 3, 0),
339
- totalTriangles: allMeshes.reduce((sum, m) => sum + m.indices.length / 3, 0),
340
- coordinateInfo: finalCoordinateInfo || createCoordinateInfo(calculateMeshBounds(allMeshes).bounds),
341
- };
342
-
343
- // Store captured RTC offset in coordinate info for multi-model alignment
344
- if (parsedGeometry.coordinateInfo && capturedRtcOffset) {
345
- parsedGeometry.coordinateInfo.wasmRtcOffset = capturedRtcOffset;
474
+ // For federated models: use the first model's RTC offset so all models
475
+ // share the same coordinate origin. This ensures pixel-perfect alignment
476
+ // without error-prone delta adjustments.
477
+ let sharedRtcOffset: { x: number; y: number; z: number } | undefined;
478
+ const existingModelsForRtc = Array.from(useViewerStore.getState().models.values()) as FederatedModel[];
479
+ if (existingModelsForRtc.length > 0) {
480
+ const sorted = [...existingModelsForRtc].sort((a, b) => (a.loadedAt ?? 0) - (b.loadedAt ?? 0));
481
+ sharedRtcOffset = sorted.find(
482
+ (model) => model.geometryResult?.coordinateInfo?.wasmRtcOffset != null,
483
+ )?.geometryResult?.coordinateInfo?.wasmRtcOffset;
346
484
  }
347
485
 
348
- schemaVersion = parsedDataStore.schemaVersion === 'IFC4X3' ? 'IFC4X3' :
349
- parsedDataStore.schemaVersion === 'IFC4' ? 'IFC4' : 'IFC2X3';
486
+ const result = await parseStepBufferViewerModel({
487
+ fileName: file.name,
488
+ buffer,
489
+ fileSizeMB,
490
+ getDynamicBatchSize: getDynamicBatchConfig,
491
+ onProgress: setProgress,
492
+ sharedRtcOffset,
493
+ });
494
+ parsedDataStore = result.dataStore;
495
+ parsedGeometry = result.geometryResult;
496
+ schemaVersion = result.schemaVersion;
350
497
  }
351
498
 
352
499
  if (!parsedDataStore || !parsedGeometry) {
353
500
  throw new Error('Failed to parse file');
354
501
  }
355
502
 
503
+ const referenceGeoref = findReferenceGeorefModel();
504
+ // Include any georef edits the user has already saved for this model so
505
+ // that a reload after editing reflects the new placement. Without this,
506
+ // extractModelGeoref reads only the raw parsed metadata and mutations
507
+ // are silently ignored.
508
+ const parsedGeorefMutations = useViewerStore.getState().georefMutations.get(modelId);
509
+ const parsedGeoref = extractModelGeoref(
510
+ parsedDataStore,
511
+ parsedGeometry.coordinateInfo,
512
+ parsedGeorefMutations,
513
+ );
514
+ if (referenceGeoref && parsedGeoref) {
515
+ setProgress({ phase: 'Aligning georeferenced model', percent: 90 });
516
+ const aligned = alignGeometryToReferenceGeoref(parsedGeometry, parsedGeoref, referenceGeoref);
517
+ if (!aligned) {
518
+ console.warn(
519
+ `[ifc-lite] Skipped georeferenced federation alignment for "${file.name}" because CRS differs from the reference model.`,
520
+ );
521
+ }
522
+ }
523
+
356
524
  // =========================================================================
357
525
  // FEDERATION REGISTRY: Transform expressIds to globally unique IDs
358
526
  // This is the BULLETPROOF fix for multi-model ID collisions
@@ -361,15 +529,7 @@ export function useIfcFederation() {
361
529
  // Step 1: Find max expressId in this model
362
530
  // IMPORTANT: Use ALL entities from data store, not just meshes
363
531
  // Spatial containers (IfcProject, IfcSite, etc.) don't have geometry but need valid globalId resolution
364
- const maxExpressIdFromMeshes = parsedGeometry.meshes.reduce((max, m) => Math.max(max, m.expressId), 0);
365
- // FIXED: Use iteration instead of spread to avoid stack overflow with large Maps
366
- let maxExpressIdFromEntities = 0;
367
- if (parsedDataStore.entityIndex?.byId) {
368
- for (const key of parsedDataStore.entityIndex.byId.keys()) {
369
- if (key > maxExpressIdFromEntities) maxExpressIdFromEntities = key;
370
- }
371
- }
372
- const maxExpressId = Math.max(maxExpressIdFromMeshes, maxExpressIdFromEntities);
532
+ const maxExpressId = getMaxExpressId(parsedDataStore, parsedGeometry.meshes);
373
533
 
374
534
  // Step 2: Register with federation registry to get unique offset
375
535
  const idOffset = registerModelOffset(modelId, maxExpressId);
@@ -384,74 +544,10 @@ export function useIfcFederation() {
384
544
  }
385
545
 
386
546
  // =========================================================================
387
- // COORDINATE ALIGNMENT: Align new model with existing models using RTC delta
388
- // WASM applies per-model RTC offsets. To align models from the same project,
389
- // we calculate the difference in RTC offsets and apply it to the new model.
390
- //
391
- // RTC offset is in IFC coordinates (Z-up). After Z-up to Y-up conversion:
392
- // - IFC X → WebGL X
393
- // - IFC Y → WebGL -Z
394
- // - IFC Z → WebGL Y (vertical)
547
+ // COORDINATE ALIGNMENT: All federated models use the same shared RTC offset
548
+ // (passed to WASM during parsing above), so no post-processing vertex
549
+ // adjustment is needed. All models are already in the same coordinate space.
395
550
  // =========================================================================
396
- const existingModels = Array.from(useViewerStore.getState().models.values()) as FederatedModel[];
397
- if (existingModels.length > 0) {
398
- const firstModel = existingModels[0];
399
- const firstRtc = firstModel.geometryResult?.coordinateInfo?.wasmRtcOffset;
400
- const newRtc = parsedGeometry.coordinateInfo?.wasmRtcOffset;
401
-
402
- // If both models have RTC offsets, use RTC delta for precise alignment
403
- if (firstRtc && newRtc) {
404
- // Calculate what adjustment is needed to align new model with first model
405
- // First model: pos = original - firstRtc
406
- // New model: pos = original - newRtc
407
- // To align: newPos + adjustment = firstPos (assuming same original)
408
- // adjustment = firstRtc - newRtc (add back new's RTC, subtract first's RTC)
409
- const adjustX = firstRtc.x - newRtc.x; // IFC X adjustment
410
- const adjustY = firstRtc.y - newRtc.y; // IFC Y adjustment
411
- const adjustZ = firstRtc.z - newRtc.z; // IFC Z adjustment (vertical)
412
-
413
- // Convert to WebGL coordinates:
414
- // IFC X → WebGL X (no change)
415
- // IFC Y → WebGL -Z (swap and negate)
416
- // IFC Z → WebGL Y (vertical)
417
- const webglAdjustX = adjustX;
418
- const webglAdjustY = adjustZ; // IFC Z is WebGL Y (vertical)
419
- const webglAdjustZ = -adjustY; // IFC Y is WebGL -Z
420
-
421
- const hasSignificantAdjust = Math.abs(webglAdjustX) > 0.01 ||
422
- Math.abs(webglAdjustY) > 0.01 ||
423
- Math.abs(webglAdjustZ) > 0.01;
424
-
425
- if (hasSignificantAdjust) {
426
- console.log(`[useIfc] Aligning model "${file.name}" using RTC adjustment: X=${webglAdjustX.toFixed(2)}m, Y=${webglAdjustY.toFixed(2)}m, Z=${webglAdjustZ.toFixed(2)}m`);
427
-
428
- // Apply adjustment to all mesh vertices
429
- // SUBTRACT adjustment: if firstRtc > newRtc, first was shifted MORE,
430
- // so new model needs to be shifted in same direction (subtract more)
431
- for (const mesh of parsedGeometry.meshes) {
432
- const positions = mesh.positions;
433
- for (let i = 0; i < positions.length; i += 3) {
434
- positions[i] -= webglAdjustX;
435
- positions[i + 1] -= webglAdjustY;
436
- positions[i + 2] -= webglAdjustZ;
437
- }
438
- }
439
-
440
- // Update coordinate info bounds
441
- if (parsedGeometry.coordinateInfo) {
442
- parsedGeometry.coordinateInfo.shiftedBounds.min.x -= webglAdjustX;
443
- parsedGeometry.coordinateInfo.shiftedBounds.max.x -= webglAdjustX;
444
- parsedGeometry.coordinateInfo.shiftedBounds.min.y -= webglAdjustY;
445
- parsedGeometry.coordinateInfo.shiftedBounds.max.y -= webglAdjustY;
446
- parsedGeometry.coordinateInfo.shiftedBounds.min.z -= webglAdjustZ;
447
- parsedGeometry.coordinateInfo.shiftedBounds.max.z -= webglAdjustZ;
448
- }
449
- }
450
- } else {
451
- // No RTC info - can't align reliably. This happens with old cache entries.
452
- console.warn(`[useIfc] Cannot align "${file.name}" - missing RTC offset. Clear cache and reload.`);
453
- }
454
- }
455
551
 
456
552
  // Build spatial index AFTER ID offset + RTC alignment so it stores
457
553
  // correct globalIds and final world-space positions.
@@ -463,11 +559,12 @@ export function useIfcFederation() {
463
559
  name: options?.name ?? file.name,
464
560
  ifcDataStore: parsedDataStore,
465
561
  geometryResult: parsedGeometry,
466
- visible: true,
467
- collapsed: hasModels(), // Collapse if not first model
562
+ visible: options?.visible ?? true,
563
+ collapsed: options?.collapsed ?? hasModels(), // Collapse if not first model
468
564
  schemaVersion,
469
- loadedAt: Date.now(),
565
+ loadedAt: options?.loadedAt ?? Date.now(),
470
566
  fileSize: buffer.byteLength,
567
+ sourceFile: file,
471
568
  idOffset,
472
569
  maxExpressId,
473
570
  };
@@ -481,9 +578,7 @@ export function useIfcFederation() {
481
578
 
482
579
  setProgress({ phase: 'Complete', percent: 100 });
483
580
  setLoading(false);
484
-
485
- const totalElapsedMs = performance.now() - totalStartTime;
486
- console.log(`[useIfc] ✓ Added model ${file.name} (${fileSizeMB.toFixed(1)}MB) | ${totalElapsedMs.toFixed(0)}ms`);
581
+ console.log(`[ifc-lite] Added model ${file.name} (${fileSizeMB.toFixed(1)}MB) in ${(performance.now() - addStart).toFixed(0)}ms`);
487
582
 
488
583
  return modelId;
489
584
 
@@ -519,7 +614,7 @@ export function useIfcFederation() {
519
614
  */
520
615
  const getQueryForModel = useCallback((modelId: string): IfcQuery | null => {
521
616
  const model = getModel(modelId);
522
- if (!model) return null;
617
+ if (!model || !model.ifcDataStore) return null;
523
618
  return new IfcQuery(model.ifcDataStore);
524
619
  }, [getModel]);
525
620
 
@@ -684,10 +779,6 @@ export function useIfcFederation() {
684
779
  storeAddModel(layerModel);
685
780
  }
686
781
 
687
- console.log(`[useIfc] Federated IFCX loaded: ${layers.length} layers, ${result.entityCount} entities, ${meshes.length} meshes`);
688
- console.log(`[useIfc] Composition stats: ${result.compositionStats.inheritanceResolutions} inheritance resolutions, ${result.compositionStats.crossLayerReferences} cross-layer refs`);
689
- console.log(`[useIfc] Layers in Models panel: ${layers.map((l: { name: string }) => l.name).join(', ')}`);
690
-
691
782
  setProgress({ phase: 'Complete', percent: 100 });
692
783
  setLoading(false);
693
784
  } catch (err: unknown) {
@@ -753,7 +844,6 @@ export function useIfcFederation() {
753
844
  ) as ArrayBuffer;
754
845
 
755
846
  existingBuffers = [{ buffer: sourceBuffer, name: modelName }];
756
- console.log(`[useIfc] Converting single IFCX file "${modelName}" to federated mode`);
757
847
  } else {
758
848
  setError('Cannot add overlays: no IFCX model loaded');
759
849
  return;
@@ -774,9 +864,6 @@ export function useIfcFederation() {
774
864
  // Combine: existing layers + new overlays (new overlays are strongest = first in array)
775
865
  const allBuffers = [...newBuffers, ...existingBuffers];
776
866
 
777
- console.log(`[useIfc] Re-composing federated IFCX with ${newBuffers.length} new overlay(s)`);
778
- console.log(`[useIfc] Total layers: ${allBuffers.length} (${existingBuffers.length} existing + ${newBuffers.length} new)`);
779
-
780
867
  await loadFederatedIfcxFromBuffers(allBuffers, { resetState: false });
781
868
  }, [setError, loadFederatedIfcxFromBuffers]);
782
869