@ifc-lite/viewer 1.17.2 → 1.17.4

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 (520) hide show
  1. package/.turbo/turbo-build.log +34 -24
  2. package/.turbo/turbo-typecheck.log +1 -42
  3. package/CHANGELOG.md +24 -0
  4. package/DESKTOP_CONTRACT_VERSION +1 -0
  5. package/dist/assets/arrow-CZ5kQ26f.js +20 -0
  6. package/dist/assets/basketViewActivator-BmnNtVfZ.js +1 -0
  7. package/dist/assets/bcf-DOG9_WPX.js +281 -0
  8. package/dist/assets/{browser-BDShTXzi.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-gWfpdfYe.js +257 -0
  12. package/dist/assets/epsg-index.generated-BjJrt_0S.js +1 -0
  13. package/dist/assets/exporters-ChAtBmlj.js +80367 -0
  14. package/dist/assets/geometry.worker-BQ0rzNo-.js +1 -0
  15. package/dist/assets/ids-B4jTqB1O.js +1 -0
  16. package/dist/assets/ifc-lite_bg-BX4E7TX8.wasm +0 -0
  17. package/dist/assets/index-Co8E2-FE.js +106013 -0
  18. package/dist/assets/index-DckuDqlv.css +1 -0
  19. package/dist/assets/lens-CSASnhAL.js +1 -0
  20. package/dist/assets/maplibre-gl-CGLcoNXc.js +811 -0
  21. package/dist/assets/native-bridge-BRvbckFQ.js +429 -0
  22. package/dist/assets/{arrow2-bb-jcVEo.js → parquet-CEXmQNRO.js} +2 -2
  23. package/dist/assets/sandbox-DZiNLNMk.js +5933 -0
  24. package/dist/assets/server-client-BV8zHZ7Y.js +626 -0
  25. package/dist/assets/tauri-core-stub-D8Fa-u43.js +1 -0
  26. package/dist/assets/tauri-dialog-stub-r7Wksg7o.js +1 -0
  27. package/dist/assets/tauri-fs-stub-BdeRC7aK.js +1 -0
  28. package/dist/assets/wasm-bridge-g01g7T9b.js +1 -0
  29. package/dist/assets/zip-DBEtpeu6.js +12 -0
  30. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_0.json +1 -0
  31. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_1.json +1 -0
  32. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_10.json +1 -0
  33. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_11.json +1 -0
  34. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_12.json +1 -0
  35. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_13.json +1 -0
  36. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_14.json +1 -0
  37. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_15.json +1 -0
  38. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_16.json +1 -0
  39. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_17.json +1 -0
  40. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_18.json +1 -0
  41. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_19.json +1 -0
  42. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_2.json +1 -0
  43. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_20.json +1 -0
  44. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_21.json +1 -0
  45. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_22.json +1 -0
  46. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_23.json +1 -0
  47. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_24.json +1 -0
  48. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_25.json +1 -0
  49. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_26.json +1 -0
  50. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_27.json +1 -0
  51. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_3.json +1 -0
  52. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_4.json +1 -0
  53. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_5.json +1 -0
  54. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_6.json +1 -0
  55. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_7.json +1 -0
  56. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_8.json +1 -0
  57. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_9.json +1 -0
  58. package/dist/cesium/Assets/Images/bing_maps_credit.png +0 -0
  59. package/dist/cesium/Assets/Images/cesium_credit.png +0 -0
  60. package/dist/cesium/Assets/Images/google_earth_credit.png +0 -0
  61. package/dist/cesium/Assets/Images/ion-credit.png +0 -0
  62. package/dist/cesium/Assets/Textures/LensFlare/DirtMask.jpg +0 -0
  63. package/dist/cesium/Assets/Textures/LensFlare/StarBurst.jpg +0 -0
  64. package/dist/cesium/Assets/Textures/NaturalEarthII/0/0/0.jpg +0 -0
  65. package/dist/cesium/Assets/Textures/NaturalEarthII/0/1/0.jpg +0 -0
  66. package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/0.jpg +0 -0
  67. package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/1.jpg +0 -0
  68. package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/0.jpg +0 -0
  69. package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/1.jpg +0 -0
  70. package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/0.jpg +0 -0
  71. package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/1.jpg +0 -0
  72. package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/0.jpg +0 -0
  73. package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/1.jpg +0 -0
  74. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/0.jpg +0 -0
  75. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/1.jpg +0 -0
  76. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/2.jpg +0 -0
  77. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/3.jpg +0 -0
  78. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/0.jpg +0 -0
  79. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/1.jpg +0 -0
  80. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/2.jpg +0 -0
  81. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/3.jpg +0 -0
  82. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/0.jpg +0 -0
  83. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/1.jpg +0 -0
  84. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/2.jpg +0 -0
  85. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/3.jpg +0 -0
  86. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/0.jpg +0 -0
  87. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/1.jpg +0 -0
  88. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/2.jpg +0 -0
  89. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/3.jpg +0 -0
  90. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/0.jpg +0 -0
  91. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/1.jpg +0 -0
  92. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/2.jpg +0 -0
  93. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/3.jpg +0 -0
  94. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/0.jpg +0 -0
  95. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/1.jpg +0 -0
  96. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/2.jpg +0 -0
  97. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/3.jpg +0 -0
  98. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/0.jpg +0 -0
  99. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/1.jpg +0 -0
  100. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/2.jpg +0 -0
  101. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/3.jpg +0 -0
  102. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/0.jpg +0 -0
  103. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/1.jpg +0 -0
  104. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/2.jpg +0 -0
  105. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/3.jpg +0 -0
  106. package/dist/cesium/Assets/Textures/NaturalEarthII/tilemapresource.xml +14 -0
  107. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mx.jpg +0 -0
  108. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_my.jpg +0 -0
  109. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mz.jpg +0 -0
  110. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_px.jpg +0 -0
  111. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_py.jpg +0 -0
  112. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_pz.jpg +0 -0
  113. package/dist/cesium/Assets/Textures/maki/airfield.png +0 -0
  114. package/dist/cesium/Assets/Textures/maki/airport.png +0 -0
  115. package/dist/cesium/Assets/Textures/maki/alcohol-shop.png +0 -0
  116. package/dist/cesium/Assets/Textures/maki/america-football.png +0 -0
  117. package/dist/cesium/Assets/Textures/maki/art-gallery.png +0 -0
  118. package/dist/cesium/Assets/Textures/maki/bakery.png +0 -0
  119. package/dist/cesium/Assets/Textures/maki/bank.png +0 -0
  120. package/dist/cesium/Assets/Textures/maki/bar.png +0 -0
  121. package/dist/cesium/Assets/Textures/maki/baseball.png +0 -0
  122. package/dist/cesium/Assets/Textures/maki/basketball.png +0 -0
  123. package/dist/cesium/Assets/Textures/maki/beer.png +0 -0
  124. package/dist/cesium/Assets/Textures/maki/bicycle.png +0 -0
  125. package/dist/cesium/Assets/Textures/maki/building.png +0 -0
  126. package/dist/cesium/Assets/Textures/maki/bus.png +0 -0
  127. package/dist/cesium/Assets/Textures/maki/cafe.png +0 -0
  128. package/dist/cesium/Assets/Textures/maki/camera.png +0 -0
  129. package/dist/cesium/Assets/Textures/maki/campsite.png +0 -0
  130. package/dist/cesium/Assets/Textures/maki/car.png +0 -0
  131. package/dist/cesium/Assets/Textures/maki/cemetery.png +0 -0
  132. package/dist/cesium/Assets/Textures/maki/cesium.png +0 -0
  133. package/dist/cesium/Assets/Textures/maki/chemist.png +0 -0
  134. package/dist/cesium/Assets/Textures/maki/cinema.png +0 -0
  135. package/dist/cesium/Assets/Textures/maki/circle-stroked.png +0 -0
  136. package/dist/cesium/Assets/Textures/maki/circle.png +0 -0
  137. package/dist/cesium/Assets/Textures/maki/city.png +0 -0
  138. package/dist/cesium/Assets/Textures/maki/clothing-store.png +0 -0
  139. package/dist/cesium/Assets/Textures/maki/college.png +0 -0
  140. package/dist/cesium/Assets/Textures/maki/commercial.png +0 -0
  141. package/dist/cesium/Assets/Textures/maki/cricket.png +0 -0
  142. package/dist/cesium/Assets/Textures/maki/cross.png +0 -0
  143. package/dist/cesium/Assets/Textures/maki/dam.png +0 -0
  144. package/dist/cesium/Assets/Textures/maki/danger.png +0 -0
  145. package/dist/cesium/Assets/Textures/maki/disability.png +0 -0
  146. package/dist/cesium/Assets/Textures/maki/dog-park.png +0 -0
  147. package/dist/cesium/Assets/Textures/maki/embassy.png +0 -0
  148. package/dist/cesium/Assets/Textures/maki/emergency-telephone.png +0 -0
  149. package/dist/cesium/Assets/Textures/maki/entrance.png +0 -0
  150. package/dist/cesium/Assets/Textures/maki/farm.png +0 -0
  151. package/dist/cesium/Assets/Textures/maki/fast-food.png +0 -0
  152. package/dist/cesium/Assets/Textures/maki/ferry.png +0 -0
  153. package/dist/cesium/Assets/Textures/maki/fire-station.png +0 -0
  154. package/dist/cesium/Assets/Textures/maki/fuel.png +0 -0
  155. package/dist/cesium/Assets/Textures/maki/garden.png +0 -0
  156. package/dist/cesium/Assets/Textures/maki/gift.png +0 -0
  157. package/dist/cesium/Assets/Textures/maki/golf.png +0 -0
  158. package/dist/cesium/Assets/Textures/maki/grocery.png +0 -0
  159. package/dist/cesium/Assets/Textures/maki/hairdresser.png +0 -0
  160. package/dist/cesium/Assets/Textures/maki/harbor.png +0 -0
  161. package/dist/cesium/Assets/Textures/maki/heart.png +0 -0
  162. package/dist/cesium/Assets/Textures/maki/heliport.png +0 -0
  163. package/dist/cesium/Assets/Textures/maki/hospital.png +0 -0
  164. package/dist/cesium/Assets/Textures/maki/ice-cream.png +0 -0
  165. package/dist/cesium/Assets/Textures/maki/industrial.png +0 -0
  166. package/dist/cesium/Assets/Textures/maki/land-use.png +0 -0
  167. package/dist/cesium/Assets/Textures/maki/laundry.png +0 -0
  168. package/dist/cesium/Assets/Textures/maki/library.png +0 -0
  169. package/dist/cesium/Assets/Textures/maki/lighthouse.png +0 -0
  170. package/dist/cesium/Assets/Textures/maki/lodging.png +0 -0
  171. package/dist/cesium/Assets/Textures/maki/logging.png +0 -0
  172. package/dist/cesium/Assets/Textures/maki/london-underground.png +0 -0
  173. package/dist/cesium/Assets/Textures/maki/marker-stroked.png +0 -0
  174. package/dist/cesium/Assets/Textures/maki/marker.png +0 -0
  175. package/dist/cesium/Assets/Textures/maki/minefield.png +0 -0
  176. package/dist/cesium/Assets/Textures/maki/mobilephone.png +0 -0
  177. package/dist/cesium/Assets/Textures/maki/monument.png +0 -0
  178. package/dist/cesium/Assets/Textures/maki/museum.png +0 -0
  179. package/dist/cesium/Assets/Textures/maki/music.png +0 -0
  180. package/dist/cesium/Assets/Textures/maki/oil-well.png +0 -0
  181. package/dist/cesium/Assets/Textures/maki/park.png +0 -0
  182. package/dist/cesium/Assets/Textures/maki/park2.png +0 -0
  183. package/dist/cesium/Assets/Textures/maki/parking-garage.png +0 -0
  184. package/dist/cesium/Assets/Textures/maki/parking.png +0 -0
  185. package/dist/cesium/Assets/Textures/maki/pharmacy.png +0 -0
  186. package/dist/cesium/Assets/Textures/maki/pitch.png +0 -0
  187. package/dist/cesium/Assets/Textures/maki/place-of-worship.png +0 -0
  188. package/dist/cesium/Assets/Textures/maki/playground.png +0 -0
  189. package/dist/cesium/Assets/Textures/maki/police.png +0 -0
  190. package/dist/cesium/Assets/Textures/maki/polling-place.png +0 -0
  191. package/dist/cesium/Assets/Textures/maki/post.png +0 -0
  192. package/dist/cesium/Assets/Textures/maki/prison.png +0 -0
  193. package/dist/cesium/Assets/Textures/maki/rail-above.png +0 -0
  194. package/dist/cesium/Assets/Textures/maki/rail-light.png +0 -0
  195. package/dist/cesium/Assets/Textures/maki/rail-metro.png +0 -0
  196. package/dist/cesium/Assets/Textures/maki/rail-underground.png +0 -0
  197. package/dist/cesium/Assets/Textures/maki/rail.png +0 -0
  198. package/dist/cesium/Assets/Textures/maki/religious-christian.png +0 -0
  199. package/dist/cesium/Assets/Textures/maki/religious-jewish.png +0 -0
  200. package/dist/cesium/Assets/Textures/maki/religious-muslim.png +0 -0
  201. package/dist/cesium/Assets/Textures/maki/restaurant.png +0 -0
  202. package/dist/cesium/Assets/Textures/maki/roadblock.png +0 -0
  203. package/dist/cesium/Assets/Textures/maki/rocket.png +0 -0
  204. package/dist/cesium/Assets/Textures/maki/school.png +0 -0
  205. package/dist/cesium/Assets/Textures/maki/scooter.png +0 -0
  206. package/dist/cesium/Assets/Textures/maki/shop.png +0 -0
  207. package/dist/cesium/Assets/Textures/maki/skiing.png +0 -0
  208. package/dist/cesium/Assets/Textures/maki/slaughterhouse.png +0 -0
  209. package/dist/cesium/Assets/Textures/maki/soccer.png +0 -0
  210. package/dist/cesium/Assets/Textures/maki/square-stroked.png +0 -0
  211. package/dist/cesium/Assets/Textures/maki/square.png +0 -0
  212. package/dist/cesium/Assets/Textures/maki/star-stroked.png +0 -0
  213. package/dist/cesium/Assets/Textures/maki/star.png +0 -0
  214. package/dist/cesium/Assets/Textures/maki/suitcase.png +0 -0
  215. package/dist/cesium/Assets/Textures/maki/swimming.png +0 -0
  216. package/dist/cesium/Assets/Textures/maki/telephone.png +0 -0
  217. package/dist/cesium/Assets/Textures/maki/tennis.png +0 -0
  218. package/dist/cesium/Assets/Textures/maki/theatre.png +0 -0
  219. package/dist/cesium/Assets/Textures/maki/toilets.png +0 -0
  220. package/dist/cesium/Assets/Textures/maki/town-hall.png +0 -0
  221. package/dist/cesium/Assets/Textures/maki/town.png +0 -0
  222. package/dist/cesium/Assets/Textures/maki/triangle-stroked.png +0 -0
  223. package/dist/cesium/Assets/Textures/maki/triangle.png +0 -0
  224. package/dist/cesium/Assets/Textures/maki/village.png +0 -0
  225. package/dist/cesium/Assets/Textures/maki/warehouse.png +0 -0
  226. package/dist/cesium/Assets/Textures/maki/waste-basket.png +0 -0
  227. package/dist/cesium/Assets/Textures/maki/water.png +0 -0
  228. package/dist/cesium/Assets/Textures/maki/wetland.png +0 -0
  229. package/dist/cesium/Assets/Textures/maki/zoo.png +0 -0
  230. package/dist/cesium/Assets/Textures/moonSmall.jpg +0 -0
  231. package/dist/cesium/Assets/Textures/pin.svg +1 -0
  232. package/dist/cesium/Assets/Textures/waterNormals.jpg +0 -0
  233. package/dist/cesium/Assets/Textures/waterNormalsSmall.jpg +0 -0
  234. package/dist/cesium/Assets/approximateTerrainHeights.json +1 -0
  235. package/dist/cesium/ThirdParty/Workers/package.json +1 -0
  236. package/dist/cesium/ThirdParty/Workers/zip-web-worker.js +1 -0
  237. package/dist/cesium/ThirdParty/basis_transcoder.wasm +0 -0
  238. package/dist/cesium/ThirdParty/draco_decoder.wasm +0 -0
  239. package/dist/cesium/ThirdParty/google-earth-dbroot-parser.js +1 -0
  240. package/dist/cesium/ThirdParty/wasm_splats_bg.wasm +0 -0
  241. package/dist/cesium/ThirdParty/zip-module.wasm +0 -0
  242. package/dist/cesium/Widgets/Animation/Animation.css +127 -0
  243. package/dist/cesium/Widgets/Animation/lighter.css +70 -0
  244. package/dist/cesium/Widgets/BaseLayerPicker/BaseLayerPicker.css +108 -0
  245. package/dist/cesium/Widgets/BaseLayerPicker/lighter.css +22 -0
  246. package/dist/cesium/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css +102 -0
  247. package/dist/cesium/Widgets/CesiumInspector/CesiumInspector.css +113 -0
  248. package/dist/cesium/Widgets/CesiumWidget/CesiumWidget.css +119 -0
  249. package/dist/cesium/Widgets/CesiumWidget/lighter.css +14 -0
  250. package/dist/cesium/Widgets/FullscreenButton/FullscreenButton.css +8 -0
  251. package/dist/cesium/Widgets/Geocoder/Geocoder.css +70 -0
  252. package/dist/cesium/Widgets/Geocoder/lighter.css +17 -0
  253. package/dist/cesium/Widgets/I3SBuildingSceneLayerExplorer/I3SBuildingSceneLayerExplorer.css +27 -0
  254. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldHillshade.png +0 -0
  255. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldImagery.png +0 -0
  256. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldOcean.png +0 -0
  257. package/dist/cesium/Widgets/Images/ImageryProviders/azureAerial.png +0 -0
  258. package/dist/cesium/Widgets/Images/ImageryProviders/azureRoads.png +0 -0
  259. package/dist/cesium/Widgets/Images/ImageryProviders/bingAerial.png +0 -0
  260. package/dist/cesium/Widgets/Images/ImageryProviders/bingAerialLabels.png +0 -0
  261. package/dist/cesium/Widgets/Images/ImageryProviders/bingRoads.png +0 -0
  262. package/dist/cesium/Widgets/Images/ImageryProviders/blueMarble.png +0 -0
  263. package/dist/cesium/Widgets/Images/ImageryProviders/earthAtNight.png +0 -0
  264. package/dist/cesium/Widgets/Images/ImageryProviders/googleContour.png +0 -0
  265. package/dist/cesium/Widgets/Images/ImageryProviders/googleRoadmap.png +0 -0
  266. package/dist/cesium/Widgets/Images/ImageryProviders/googleSatellite.png +0 -0
  267. package/dist/cesium/Widgets/Images/ImageryProviders/googleSatelliteLabels.png +0 -0
  268. package/dist/cesium/Widgets/Images/ImageryProviders/mapQuestOpenStreetMap.png +0 -0
  269. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxSatellite.png +0 -0
  270. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxStreets.png +0 -0
  271. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxTerrain.png +0 -0
  272. package/dist/cesium/Widgets/Images/ImageryProviders/naturalEarthII.png +0 -0
  273. package/dist/cesium/Widgets/Images/ImageryProviders/openStreetMap.png +0 -0
  274. package/dist/cesium/Widgets/Images/ImageryProviders/sentinel-2.png +0 -0
  275. package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmooth.png +0 -0
  276. package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmoothDark.png +0 -0
  277. package/dist/cesium/Widgets/Images/ImageryProviders/stamenToner.png +0 -0
  278. package/dist/cesium/Widgets/Images/ImageryProviders/stamenWatercolor.png +0 -0
  279. package/dist/cesium/Widgets/Images/NavigationHelp/Mouse.svg +84 -0
  280. package/dist/cesium/Widgets/Images/NavigationHelp/MouseLeft.svg +76 -0
  281. package/dist/cesium/Widgets/Images/NavigationHelp/MouseMiddle.svg +76 -0
  282. package/dist/cesium/Widgets/Images/NavigationHelp/MouseRight.svg +76 -0
  283. package/dist/cesium/Widgets/Images/NavigationHelp/Touch.svg +120 -0
  284. package/dist/cesium/Widgets/Images/NavigationHelp/TouchDrag.svg +129 -0
  285. package/dist/cesium/Widgets/Images/NavigationHelp/TouchRotate.svg +76 -0
  286. package/dist/cesium/Widgets/Images/NavigationHelp/TouchTilt.svg +135 -0
  287. package/dist/cesium/Widgets/Images/NavigationHelp/TouchZoom.svg +74 -0
  288. package/dist/cesium/Widgets/Images/TerrainProviders/CesiumWorldTerrain.png +0 -0
  289. package/dist/cesium/Widgets/Images/TerrainProviders/Ellipsoid.png +0 -0
  290. package/dist/cesium/Widgets/Images/TimelineIcons.png +0 -0
  291. package/dist/cesium/Widgets/Images/info-loading.gif +0 -0
  292. package/dist/cesium/Widgets/InfoBox/InfoBox.css +92 -0
  293. package/dist/cesium/Widgets/InfoBox/InfoBoxDescription.css +178 -0
  294. package/dist/cesium/Widgets/NavigationHelpButton/NavigationHelpButton.css +93 -0
  295. package/dist/cesium/Widgets/NavigationHelpButton/lighter.css +38 -0
  296. package/dist/cesium/Widgets/PerformanceWatchdog/PerformanceWatchdog.css +15 -0
  297. package/dist/cesium/Widgets/ProjectionPicker/ProjectionPicker.css +38 -0
  298. package/dist/cesium/Widgets/SceneModePicker/SceneModePicker.css +56 -0
  299. package/dist/cesium/Widgets/SelectionIndicator/SelectionIndicator.css +20 -0
  300. package/dist/cesium/Widgets/Timeline/Timeline.css +103 -0
  301. package/dist/cesium/Widgets/Timeline/lighter.css +23 -0
  302. package/dist/cesium/Widgets/VRButton/VRButton.css +8 -0
  303. package/dist/cesium/Widgets/Viewer/Viewer.css +107 -0
  304. package/dist/cesium/Widgets/VoxelInspector/VoxelInspector.css +16 -0
  305. package/dist/cesium/Widgets/lighter.css +237 -0
  306. package/dist/cesium/Widgets/lighterShared.css +46 -0
  307. package/dist/cesium/Widgets/shared.css +103 -0
  308. package/dist/cesium/Widgets/widgets.css +1342 -0
  309. package/dist/cesium/Workers/chunk-23ZQ2IVV.js +29 -0
  310. package/dist/cesium/Workers/chunk-2EQO3Q56.js +26 -0
  311. package/dist/cesium/Workers/chunk-2MJIIVP4.js +26 -0
  312. package/dist/cesium/Workers/chunk-2TE5NTVD.js +26 -0
  313. package/dist/cesium/Workers/chunk-2ZBHLJST.js +26 -0
  314. package/dist/cesium/Workers/chunk-5TJMAQVL.js +26 -0
  315. package/dist/cesium/Workers/chunk-6BD4U3VO.js +26 -0
  316. package/dist/cesium/Workers/chunk-7TVGLKQF.js +26 -0
  317. package/dist/cesium/Workers/chunk-BTSYJ5XU.js +26 -0
  318. package/dist/cesium/Workers/chunk-BXMEEOCS.js +63 -0
  319. package/dist/cesium/Workers/chunk-BYLCY7GP.js +29 -0
  320. package/dist/cesium/Workers/chunk-CTHM3W6I.js +26 -0
  321. package/dist/cesium/Workers/chunk-CUUSNIVQ.js +26 -0
  322. package/dist/cesium/Workers/chunk-E3JOOS3S.js +26 -0
  323. package/dist/cesium/Workers/chunk-E7KYDCM5.js +26 -0
  324. package/dist/cesium/Workers/chunk-EDVBB7SS.js +27 -0
  325. package/dist/cesium/Workers/chunk-EFBN7QNX.js +26 -0
  326. package/dist/cesium/Workers/chunk-EQ4YRVWL.js +26 -0
  327. package/dist/cesium/Workers/chunk-F6PRE7D6.js +26 -0
  328. package/dist/cesium/Workers/chunk-FC4ZZ65J.js +26 -0
  329. package/dist/cesium/Workers/chunk-FFBVWF2L.js +26 -0
  330. package/dist/cesium/Workers/chunk-GBAA6GVX.js +26 -0
  331. package/dist/cesium/Workers/chunk-ICALLYLG.js +26 -0
  332. package/dist/cesium/Workers/chunk-ILRYTWTP.js +26 -0
  333. package/dist/cesium/Workers/chunk-IRNLBSEJ.js +26 -0
  334. package/dist/cesium/Workers/chunk-IX4VMHEV.js +26 -0
  335. package/dist/cesium/Workers/chunk-L6QHHACZ.js +26 -0
  336. package/dist/cesium/Workers/chunk-LI2ZSORM.js +26 -0
  337. package/dist/cesium/Workers/chunk-LSLE2RL4.js +26 -0
  338. package/dist/cesium/Workers/chunk-M4HLDBCG.js +26 -0
  339. package/dist/cesium/Workers/chunk-MJHHSGEH.js +26 -0
  340. package/dist/cesium/Workers/chunk-NMVKML6W.js +26 -0
  341. package/dist/cesium/Workers/chunk-OCWJRAXS.js +26 -0
  342. package/dist/cesium/Workers/chunk-OIRKANTH.js +26 -0
  343. package/dist/cesium/Workers/chunk-OIT7J4IC.js +26 -0
  344. package/dist/cesium/Workers/chunk-OLZ3FYUM.js +26 -0
  345. package/dist/cesium/Workers/chunk-Q5BPHJQF.js +26 -0
  346. package/dist/cesium/Workers/chunk-QFM5DCMQ.js +26 -0
  347. package/dist/cesium/Workers/chunk-QKUIYMGC.js +28 -0
  348. package/dist/cesium/Workers/chunk-S44JILQT.js +26 -0
  349. package/dist/cesium/Workers/chunk-SLT4J352.js +26 -0
  350. package/dist/cesium/Workers/chunk-SQMIIXB7.js +26 -0
  351. package/dist/cesium/Workers/chunk-TJ4XLGBQ.js +26 -0
  352. package/dist/cesium/Workers/chunk-TNSUQXWK.js +27 -0
  353. package/dist/cesium/Workers/chunk-UBOGZS7F.js +26 -0
  354. package/dist/cesium/Workers/chunk-V3OSTMM6.js +26 -0
  355. package/dist/cesium/Workers/chunk-V7QEYVP3.js +26 -0
  356. package/dist/cesium/Workers/chunk-VUKYSU4H.js +26 -0
  357. package/dist/cesium/Workers/chunk-W37FE5GR.js +26 -0
  358. package/dist/cesium/Workers/chunk-WBOV35NL.js +26 -0
  359. package/dist/cesium/Workers/chunk-WPMZLB3Y.js +26 -0
  360. package/dist/cesium/Workers/chunk-WWWZVEEH.js +26 -0
  361. package/dist/cesium/Workers/chunk-XFIQ5DEQ.js +28 -0
  362. package/dist/cesium/Workers/chunk-XQHLGIO7.js +26 -0
  363. package/dist/cesium/Workers/chunk-XUSCFAVF.js +26 -0
  364. package/dist/cesium/Workers/chunk-YP7I5QBZ.js +26 -0
  365. package/dist/cesium/Workers/chunk-Z3QF2EHT.js +26 -0
  366. package/dist/cesium/Workers/combineGeometry.js +26 -0
  367. package/dist/cesium/Workers/createBoxGeometry.js +26 -0
  368. package/dist/cesium/Workers/createBoxOutlineGeometry.js +26 -0
  369. package/dist/cesium/Workers/createCircleGeometry.js +26 -0
  370. package/dist/cesium/Workers/createCircleOutlineGeometry.js +26 -0
  371. package/dist/cesium/Workers/createCoplanarPolygonGeometry.js +26 -0
  372. package/dist/cesium/Workers/createCoplanarPolygonOutlineGeometry.js +26 -0
  373. package/dist/cesium/Workers/createCorridorGeometry.js +26 -0
  374. package/dist/cesium/Workers/createCorridorOutlineGeometry.js +26 -0
  375. package/dist/cesium/Workers/createCylinderGeometry.js +26 -0
  376. package/dist/cesium/Workers/createCylinderOutlineGeometry.js +26 -0
  377. package/dist/cesium/Workers/createEllipseGeometry.js +26 -0
  378. package/dist/cesium/Workers/createEllipseOutlineGeometry.js +26 -0
  379. package/dist/cesium/Workers/createEllipsoidGeometry.js +26 -0
  380. package/dist/cesium/Workers/createEllipsoidOutlineGeometry.js +26 -0
  381. package/dist/cesium/Workers/createFrustumGeometry.js +26 -0
  382. package/dist/cesium/Workers/createFrustumOutlineGeometry.js +26 -0
  383. package/dist/cesium/Workers/createGeometry.js +26 -0
  384. package/dist/cesium/Workers/createGroundPolylineGeometry.js +26 -0
  385. package/dist/cesium/Workers/createPlaneGeometry.js +26 -0
  386. package/dist/cesium/Workers/createPlaneOutlineGeometry.js +26 -0
  387. package/dist/cesium/Workers/createPolygonGeometry.js +26 -0
  388. package/dist/cesium/Workers/createPolygonOutlineGeometry.js +26 -0
  389. package/dist/cesium/Workers/createPolylineGeometry.js +26 -0
  390. package/dist/cesium/Workers/createPolylineVolumeGeometry.js +26 -0
  391. package/dist/cesium/Workers/createPolylineVolumeOutlineGeometry.js +26 -0
  392. package/dist/cesium/Workers/createRectangleGeometry.js +26 -0
  393. package/dist/cesium/Workers/createRectangleOutlineGeometry.js +26 -0
  394. package/dist/cesium/Workers/createSimplePolylineGeometry.js +26 -0
  395. package/dist/cesium/Workers/createSphereGeometry.js +26 -0
  396. package/dist/cesium/Workers/createSphereOutlineGeometry.js +26 -0
  397. package/dist/cesium/Workers/createTaskProcessorWorker.js +26 -0
  398. package/dist/cesium/Workers/createVectorTileClampedPolylines.js +26 -0
  399. package/dist/cesium/Workers/createVectorTileGeometries.js +26 -0
  400. package/dist/cesium/Workers/createVectorTilePoints.js +26 -0
  401. package/dist/cesium/Workers/createVectorTilePolygons.js +26 -0
  402. package/dist/cesium/Workers/createVectorTilePolylines.js +26 -0
  403. package/dist/cesium/Workers/createVerticesFromCesium3DTilesTerrain.js +26 -0
  404. package/dist/cesium/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +26 -0
  405. package/dist/cesium/Workers/createVerticesFromHeightmap.js +26 -0
  406. package/dist/cesium/Workers/createVerticesFromQuantizedTerrainMesh.js +26 -0
  407. package/dist/cesium/Workers/createWallGeometry.js +26 -0
  408. package/dist/cesium/Workers/createWallOutlineGeometry.js +26 -0
  409. package/dist/cesium/Workers/decodeDraco.js +26 -0
  410. package/dist/cesium/Workers/decodeGoogleEarthEnterprisePacket.js +26 -0
  411. package/dist/cesium/Workers/decodeI3S.js +26 -0
  412. package/dist/cesium/Workers/gaussianSplatSorter.js +26 -0
  413. package/dist/cesium/Workers/gaussianSplatTextureGenerator.js +26 -0
  414. package/dist/cesium/Workers/incrementallyBuildTerrainPicker.js +26 -0
  415. package/dist/cesium/Workers/transcodeKTX2.js +56 -0
  416. package/dist/cesium/Workers/transferTypedArrayTest.js +26 -0
  417. package/dist/cesium/Workers/upsampleQuantizedTerrainMesh.js +26 -0
  418. package/dist/cesium/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +26 -0
  419. package/dist/index.html +13 -2
  420. package/package.json +27 -19
  421. package/src/App.tsx +1 -17
  422. package/src/components/viewer/BCFPanel.tsx +46 -4
  423. package/src/components/viewer/CesiumOverlay.tsx +672 -0
  424. package/src/components/viewer/CesiumSettingsDialog.tsx +100 -0
  425. package/src/components/viewer/ChatPanel.tsx +54 -16
  426. package/src/components/viewer/CommandPalette.tsx +6 -1
  427. package/src/components/viewer/DesktopEntitlementBanner.tsx +74 -0
  428. package/src/components/viewer/ExportChangesButton.tsx +24 -4
  429. package/src/components/viewer/ExportDialog.tsx +38 -9
  430. package/src/components/viewer/HierarchyPanel.tsx +202 -6
  431. package/src/components/viewer/IDSPanel.tsx +52 -3
  432. package/src/components/viewer/KeyboardShortcutsDialog.tsx +1 -1
  433. package/src/components/viewer/MainToolbar.tsx +353 -27
  434. package/src/components/viewer/PropertiesPanel.tsx +306 -131
  435. package/src/components/viewer/ScriptPanel.tsx +34 -8
  436. package/src/components/viewer/Section2DPanel.tsx +3 -2
  437. package/src/components/viewer/SettingsPage.tsx +430 -0
  438. package/src/components/viewer/StatusBar.tsx +17 -1
  439. package/src/components/viewer/UpgradePage.tsx +6 -4
  440. package/src/components/viewer/ViewerLayout.tsx +47 -6
  441. package/src/components/viewer/Viewport.tsx +49 -8
  442. package/src/components/viewer/ViewportContainer.tsx +285 -32
  443. package/src/components/viewer/ViewportOverlays.tsx +129 -27
  444. package/src/components/viewer/hierarchy/treeDataBuilder.ts +2 -1
  445. package/src/components/viewer/properties/EpsgLookupDialog.tsx +418 -0
  446. package/src/components/viewer/properties/GeoreferencingPanel.tsx +703 -0
  447. package/src/components/viewer/properties/LocationMap.tsx +730 -0
  448. package/src/components/viewer/properties/ModelMetadataPanel.tsx +3 -70
  449. package/src/components/viewer/selectionHandlers.ts +4 -3
  450. package/src/components/viewer/useAnimationLoop.ts +4 -0
  451. package/src/components/viewer/useGeometryStreaming.ts +127 -40
  452. package/src/components/viewer/useMouseControls.ts +4 -1
  453. package/src/hooks/bcfIdLookup.ts +13 -11
  454. package/src/hooks/ids/idsColorSystem.ts +3 -8
  455. package/src/hooks/ingest/viewerModelIngest.ts +275 -0
  456. package/src/hooks/useIDS.ts +32 -17
  457. package/src/hooks/useIfc.ts +7 -1
  458. package/src/hooks/useIfcCache.ts +28 -15
  459. package/src/hooks/useIfcFederation.ts +59 -227
  460. package/src/hooks/useIfcLoader.ts +1656 -130
  461. package/src/hooks/useIfcServer.ts +0 -69
  462. package/src/lib/desktop/ClerkDesktopEntitlementSync.tsx +175 -0
  463. package/src/lib/desktop/desktopEntitlementEvents.ts +39 -0
  464. package/src/lib/desktop-entitlement.ts +45 -0
  465. package/src/lib/desktop-product.ts +124 -0
  466. package/src/lib/geo/cesium-bridge.ts +310 -0
  467. package/src/lib/geo/kmz-exporter.ts +112 -0
  468. package/src/lib/geo/reproject.ts +370 -0
  469. package/src/lib/lens/adapter.ts +3 -1
  470. package/src/lib/recent-files.ts +2 -1
  471. package/src/main.tsx +1 -0
  472. package/src/sdk/adapters/export-adapter.ts +14 -1
  473. package/src/sdk/adapters/viewer-adapter.ts +5 -9
  474. package/src/sdk/adapters/visibility-adapter.ts +6 -9
  475. package/src/services/analysis-extensions.ts +125 -0
  476. package/src/services/app-navigation.ts +13 -0
  477. package/src/services/bsdd.ts +53 -4
  478. package/src/services/cacheService.ts +1 -1
  479. package/src/services/desktop-cache.ts +43 -0
  480. package/src/services/desktop-export.ts +77 -0
  481. package/src/services/desktop-harness.ts +196 -0
  482. package/src/services/desktop-logger.ts +20 -0
  483. package/src/services/desktop-native-metadata.ts +207 -0
  484. package/src/services/desktop-panel-actions.ts +43 -0
  485. package/src/services/desktop-preferences.ts +44 -0
  486. package/src/services/file-dialog.ts +147 -0
  487. package/src/services/tauri-core-stub.ts +7 -0
  488. package/src/services/tauri-dialog-stub.ts +7 -0
  489. package/src/services/tauri-fs-stub.ts +7 -0
  490. package/src/store/basketVisibleSet.ts +3 -4
  491. package/src/store/globalId.ts +79 -0
  492. package/src/store/index.ts +41 -2
  493. package/src/store/slices/cesiumSlice.ts +122 -0
  494. package/src/store/slices/chatSlice.ts +5 -1
  495. package/src/store/slices/dataSlice.ts +139 -28
  496. package/src/store/slices/desktopEntitlementSlice.ts +86 -0
  497. package/src/store/slices/loadingSlice.ts +14 -2
  498. package/src/store/slices/modelSlice.ts +58 -3
  499. package/src/store/slices/mutationSlice.ts +178 -0
  500. package/src/store/slices/pinboardSlice.ts +4 -8
  501. package/src/store/types.ts +96 -2
  502. package/src/store.ts +1 -1
  503. package/src/utils/desktopModelSnapshot.ts +358 -0
  504. package/src/utils/ifcConfig.ts +6 -1
  505. package/src/utils/nativeSpatialDataStore.ts +250 -0
  506. package/src/utils/serverDataModel.ts +4 -0
  507. package/src/utils/spatialHierarchy.ts +10 -11
  508. package/vite.config.ts +41 -0
  509. package/dist/assets/Arrow.dom-BhOg9lpn.js +0 -20
  510. package/dist/assets/arrow2_bg-BlXl-cSQ.js +0 -1
  511. package/dist/assets/basketViewActivator-BRG5DBmM.js +0 -1
  512. package/dist/assets/desktop-cache-oPzaWXYE.js +0 -1
  513. package/dist/assets/geometry.worker-kgiT_Qhh.js +0 -1
  514. package/dist/assets/ifc-lite_bg-FNRmpSvM.wasm +0 -0
  515. package/dist/assets/index-B1Ecw4AU.js +0 -189756
  516. package/dist/assets/index-Ba4eoTe7.css +0 -1
  517. package/dist/assets/index-CrgYBjTn.js +0 -229
  518. package/dist/assets/module-6F3E5H7Y-tx0BadV3.js +0 -6
  519. package/dist/assets/native-bridge-Crsb7TKz.js +0 -111
  520. package/dist/assets/wasm-bridge-mJUhb7uk.js +0 -1
@@ -9,12 +9,28 @@
9
9
  import type { StateCreator } from 'zustand';
10
10
  import type { IfcDataStore } from '@ifc-lite/parser';
11
11
  import type { GeometryResult, CoordinateInfo } from '@ifc-lite/geometry';
12
+ import type { FederatedModel } from '../types.js';
12
13
  import { DATA_DEFAULTS } from '../constants.js';
13
14
 
15
+ /**
16
+ * Cross-slice state that dataSlice reads/writes via the combined store.
17
+ *
18
+ * Data updaters sync `ifcDataStore` / `geometryResult` into the per-model
19
+ * entry inside the ModelSlice `models` map so that federation stays
20
+ * consistent. The types below describe the minimal ModelSlice surface
21
+ * that dataSlice accesses through the merged Zustand state.
22
+ */
23
+ interface DataCrossSliceState {
24
+ activeModelId: string | null;
25
+ models: Map<string, FederatedModel>;
26
+ }
27
+
14
28
  export interface DataSlice {
15
29
  // State
16
30
  ifcDataStore: IfcDataStore | null;
17
31
  geometryResult: GeometryResult | null;
32
+ geometryUpdateTick: number;
33
+ boundedGeometryMode: boolean;
18
34
  /** Transient overlay colors (lens/IDS/sdk overlays). */
19
35
  pendingColorUpdates: Map<number, [number, number, number, number]> | null;
20
36
  /** Persistent mesh color updates (IFC deferred style/material colors). */
@@ -23,7 +39,9 @@ export interface DataSlice {
23
39
  // Actions
24
40
  setIfcDataStore: (result: IfcDataStore | null) => void;
25
41
  setGeometryResult: (result: GeometryResult | null) => void;
42
+ setBoundedGeometryMode: (enabled: boolean) => void;
26
43
  appendGeometryBatch: (meshes: GeometryResult['meshes'], coordinateInfo?: CoordinateInfo) => void;
44
+ releaseGeometryMemory: () => void;
27
45
  /** Persist mesh color changes in geometryResult (used for IFC style/material updates). */
28
46
  updateMeshColors: (updates: Map<number, [number, number, number, number]>) => void;
29
47
  /** Set pending color updates for the renderer without cloning mesh data.
@@ -49,17 +67,53 @@ const getDefaultCoordinateInfo = (): CoordinateInfo => ({
49
67
  hasLargeCoordinates: DATA_DEFAULTS.HAS_LARGE_COORDINATES,
50
68
  });
51
69
 
52
- export const createDataSlice: StateCreator<DataSlice, [], [], DataSlice> = (set) => ({
70
+ const EMPTY_POSITIONS = new Float32Array(0);
71
+ const EMPTY_NORMALS = new Float32Array(0);
72
+ const EMPTY_INDICES = new Uint32Array(0);
73
+
74
+ export const createDataSlice: StateCreator<DataSlice & DataCrossSliceState, [], [], DataSlice> = (set, get) => ({
53
75
  // Initial state
54
76
  ifcDataStore: null,
55
77
  geometryResult: null,
78
+ geometryUpdateTick: 0,
79
+ boundedGeometryMode: false,
56
80
  pendingColorUpdates: null,
57
81
  pendingMeshColorUpdates: null,
58
82
 
59
83
  // Actions
60
- setIfcDataStore: (ifcDataStore) => set({ ifcDataStore }),
84
+ setIfcDataStore: (ifcDataStore) => set((state) => {
85
+ const modelId = state.activeModelId;
86
+ if (!modelId) {
87
+ return { ifcDataStore };
88
+ }
89
+
90
+ const model = state.models.get(modelId);
91
+ if (!model) {
92
+ return { ifcDataStore };
93
+ }
94
+
95
+ const models = new Map(state.models);
96
+ models.set(modelId, { ...model, ifcDataStore });
97
+ return { ifcDataStore, models };
98
+ }),
61
99
 
62
- setGeometryResult: (geometryResult) => set({ geometryResult }),
100
+ setGeometryResult: (geometryResult) => set((state) => {
101
+ const modelId = state.activeModelId;
102
+ if (!modelId) {
103
+ return { geometryResult, geometryUpdateTick: state.geometryUpdateTick + 1 };
104
+ }
105
+
106
+ const model = state.models.get(modelId);
107
+ if (!model) {
108
+ return { geometryResult, geometryUpdateTick: state.geometryUpdateTick + 1 };
109
+ }
110
+
111
+ const models = new Map(state.models);
112
+ models.set(modelId, { ...model, geometryResult });
113
+ return { geometryResult, models, geometryUpdateTick: state.geometryUpdateTick + 1 };
114
+ }),
115
+
116
+ setBoundedGeometryMode: (boundedGeometryMode) => set({ boundedGeometryMode }),
63
117
 
64
118
  appendGeometryBatch: (meshes, coordinateInfo) => set((state) => {
65
119
  // Incremental totals: O(batch_size) instead of O(total_accumulated) .reduce()
@@ -71,33 +125,81 @@ export const createDataSlice: StateCreator<DataSlice, [], [], DataSlice> = (set)
71
125
  }
72
126
 
73
127
  if (!state.geometryResult) {
74
- return {
75
- geometryResult: {
76
- meshes: meshes.slice(),
77
- totalTriangles: batchTriangles,
78
- totalVertices: batchVertices,
79
- coordinateInfo: coordinateInfo || getDefaultCoordinateInfo(),
80
- },
128
+ const geometryResult = {
129
+ meshes: meshes.slice(),
130
+ totalTriangles: batchTriangles,
131
+ totalVertices: batchVertices,
132
+ coordinateInfo: coordinateInfo || getDefaultCoordinateInfo(),
81
133
  };
134
+ const modelId = state.activeModelId;
135
+ if (!modelId) {
136
+ return { geometryResult, geometryUpdateTick: state.geometryUpdateTick + 1 };
137
+ }
138
+ const model = state.models.get(modelId);
139
+ if (!model) {
140
+ return { geometryResult, geometryUpdateTick: state.geometryUpdateTick + 1 };
141
+ }
142
+ const models = new Map(state.models);
143
+ models.set(modelId, { ...model, geometryResult });
144
+ return { geometryResult, models, geometryUpdateTick: state.geometryUpdateTick + 1 };
82
145
  }
83
146
 
84
- // PERF FIX: Push into existing array O(batch_size) instead of O(total).
85
- // The old [...old, ...new] spread copied ALL accumulated meshes every batch,
86
- // causing O(N²) total work (e.g., 176K meshes × 350 batches = 31M copies).
87
- // Zustand detects changes via the new geometryResult object reference below.
88
- const existing = state.geometryResult.meshes;
147
+ // Mutate the existing array in-place (O(batch) per append) instead of
148
+ // .concat() (O(total) per append) to avoid O(N²) for large files.
149
+ // The new geometryResult object reference below is sufficient for
150
+ // Zustand/React change detection array identity doesn't need to change.
151
+ const existingMeshes = state.geometryResult.meshes;
89
152
  for (let i = 0; i < meshes.length; i++) {
90
- existing.push(meshes[i]);
153
+ existingMeshes.push(meshes[i]);
91
154
  }
92
155
 
93
- return {
94
- geometryResult: {
95
- ...state.geometryResult,
96
- totalTriangles: state.geometryResult.totalTriangles + batchTriangles,
97
- totalVertices: state.geometryResult.totalVertices + batchVertices,
98
- coordinateInfo: coordinateInfo || state.geometryResult.coordinateInfo,
99
- },
156
+ const geometryResult = {
157
+ ...state.geometryResult,
158
+ meshes: existingMeshes,
159
+ totalTriangles: state.geometryResult.totalTriangles + batchTriangles,
160
+ totalVertices: state.geometryResult.totalVertices + batchVertices,
161
+ coordinateInfo: coordinateInfo || state.geometryResult.coordinateInfo,
162
+ };
163
+ const modelId = state.activeModelId;
164
+ if (!modelId) {
165
+ return { geometryResult, geometryUpdateTick: state.geometryUpdateTick + 1 };
166
+ }
167
+ const model = state.models.get(modelId);
168
+ if (!model) {
169
+ return { geometryResult, geometryUpdateTick: state.geometryUpdateTick + 1 };
170
+ }
171
+ const models = new Map(state.models);
172
+ models.set(modelId, { ...model, geometryResult });
173
+ return { geometryResult, models, geometryUpdateTick: state.geometryUpdateTick + 1 };
174
+ }),
175
+
176
+ releaseGeometryMemory: () => set((state) => {
177
+ if (!state.geometryResult || !state.boundedGeometryMode) {
178
+ return {};
179
+ }
180
+
181
+ const meshes = state.geometryResult.meshes;
182
+ for (let i = 0; i < meshes.length; i++) {
183
+ meshes[i].positions = EMPTY_POSITIONS;
184
+ meshes[i].normals = EMPTY_NORMALS;
185
+ meshes[i].indices = EMPTY_INDICES;
186
+ }
187
+
188
+ const geometryResult = {
189
+ ...state.geometryResult,
190
+ meshes,
100
191
  };
192
+ const modelId = state.activeModelId;
193
+ if (!modelId) {
194
+ return { geometryResult, geometryUpdateTick: state.geometryUpdateTick + 1 };
195
+ }
196
+ const model = state.models.get(modelId);
197
+ if (!model) {
198
+ return { geometryResult, geometryUpdateTick: state.geometryUpdateTick + 1 };
199
+ }
200
+ const models = new Map(state.models);
201
+ models.set(modelId, { ...model, geometryResult });
202
+ return { geometryResult, models, geometryUpdateTick: state.geometryUpdateTick + 1 };
101
203
  }),
102
204
 
103
205
  updateMeshColors: (updates) => set((state) => {
@@ -136,11 +238,20 @@ export const createDataSlice: StateCreator<DataSlice, [], [], DataSlice> = (set)
136
238
 
137
239
  updateCoordinateInfo: (coordinateInfo) => set((state) => {
138
240
  if (!state.geometryResult) return {};
139
- return {
140
- geometryResult: {
141
- ...state.geometryResult,
142
- coordinateInfo,
143
- },
241
+ const geometryResult = {
242
+ ...state.geometryResult,
243
+ coordinateInfo,
144
244
  };
245
+ const modelId = state.activeModelId;
246
+ if (!modelId) {
247
+ return { geometryResult, geometryUpdateTick: state.geometryUpdateTick + 1 };
248
+ }
249
+ const model = state.models.get(modelId);
250
+ if (!model) {
251
+ return { geometryResult, geometryUpdateTick: state.geometryUpdateTick + 1 };
252
+ }
253
+ const models = new Map(state.models);
254
+ models.set(modelId, { ...model, geometryResult });
255
+ return { geometryResult, models, geometryUpdateTick: state.geometryUpdateTick + 1 };
145
256
  }),
146
257
  });
@@ -0,0 +1,86 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+
5
+ import type { StateCreator } from 'zustand';
6
+ import {
7
+ getDefaultDesktopEntitlement,
8
+ type DesktopEntitlement,
9
+ type DesktopEntitlementSource,
10
+ type DesktopEntitlementStatus,
11
+ } from '@/lib/desktop-product';
12
+
13
+ const STORAGE_KEY = 'ifc-lite:desktop-entitlement:v1';
14
+
15
+ function sanitizeTimestamp(value: unknown): number | null {
16
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
17
+ }
18
+
19
+ function parseStoredEntitlement(raw: string | null): DesktopEntitlement {
20
+ if (!raw) {
21
+ return getDefaultDesktopEntitlement();
22
+ }
23
+
24
+ try {
25
+ const parsed = JSON.parse(raw) as Partial<DesktopEntitlement> | null;
26
+ const fallback = getDefaultDesktopEntitlement();
27
+ return {
28
+ tier: parsed?.tier === 'pro' ? 'pro' : 'free',
29
+ status: isEntitlementStatus(parsed?.status) ? parsed.status : fallback.status,
30
+ source: isEntitlementSource(parsed?.source) ? parsed.source : fallback.source,
31
+ userId: typeof parsed?.userId === 'string' ? parsed.userId : null,
32
+ validatedAt: sanitizeTimestamp(parsed?.validatedAt),
33
+ graceUntil: sanitizeTimestamp(parsed?.graceUntil),
34
+ trialEndsAt: sanitizeTimestamp(parsed?.trialEndsAt),
35
+ };
36
+ } catch {
37
+ return getDefaultDesktopEntitlement();
38
+ }
39
+ }
40
+
41
+ function isEntitlementStatus(value: unknown): value is DesktopEntitlementStatus {
42
+ return value === 'anonymous'
43
+ || value === 'signed_out'
44
+ || value === 'active'
45
+ || value === 'trial'
46
+ || value === 'expired'
47
+ || value === 'grace_offline';
48
+ }
49
+
50
+ function isEntitlementSource(value: unknown): value is DesktopEntitlementSource {
51
+ return value === 'anonymous' || value === 'clerk_claims' || value === 'cached';
52
+ }
53
+
54
+ function persistEntitlement(entitlement: DesktopEntitlement): void {
55
+ try {
56
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(entitlement));
57
+ } catch {
58
+ // ignore storage failures
59
+ }
60
+ }
61
+
62
+ export interface DesktopEntitlementSlice {
63
+ desktopEntitlement: DesktopEntitlement;
64
+ setDesktopEntitlement: (entitlement: DesktopEntitlement) => void;
65
+ clearDesktopEntitlement: () => void;
66
+ }
67
+
68
+ export const createDesktopEntitlementSlice: StateCreator<
69
+ DesktopEntitlementSlice,
70
+ [],
71
+ [],
72
+ DesktopEntitlementSlice
73
+ > = (set) => ({
74
+ desktopEntitlement: parseStoredEntitlement(typeof localStorage === 'undefined' ? null : localStorage.getItem(STORAGE_KEY)),
75
+
76
+ setDesktopEntitlement: (desktopEntitlement) => {
77
+ persistEntitlement(desktopEntitlement);
78
+ set({ desktopEntitlement });
79
+ },
80
+
81
+ clearDesktopEntitlement: () => {
82
+ const next = getDefaultDesktopEntitlement();
83
+ persistEntitlement(next);
84
+ set({ desktopEntitlement: next });
85
+ },
86
+ });
@@ -11,23 +11,35 @@ import type { StateCreator } from 'zustand';
11
11
  export interface LoadingSlice {
12
12
  // State
13
13
  loading: boolean;
14
- progress: { phase: string; percent: number } | null;
14
+ geometryStreamingActive: boolean;
15
+ progress: { phase: string; percent: number; indeterminate?: boolean } | null;
16
+ geometryProgress: { phase: string; percent: number; indeterminate?: boolean } | null;
17
+ metadataProgress: { phase: string; percent: number; indeterminate?: boolean } | null;
15
18
  error: string | null;
16
19
 
17
20
  // Actions
18
21
  setLoading: (loading: boolean) => void;
19
- setProgress: (progress: { phase: string; percent: number } | null) => void;
22
+ setGeometryStreamingActive: (active: boolean) => void;
23
+ setProgress: (progress: { phase: string; percent: number; indeterminate?: boolean } | null) => void;
24
+ setGeometryProgress: (progress: { phase: string; percent: number; indeterminate?: boolean } | null) => void;
25
+ setMetadataProgress: (progress: { phase: string; percent: number; indeterminate?: boolean } | null) => void;
20
26
  setError: (error: string | null) => void;
21
27
  }
22
28
 
23
29
  export const createLoadingSlice: StateCreator<LoadingSlice, [], [], LoadingSlice> = (set) => ({
24
30
  // Initial state
25
31
  loading: false,
32
+ geometryStreamingActive: false,
26
33
  progress: null,
34
+ geometryProgress: null,
35
+ metadataProgress: null,
27
36
  error: null,
28
37
 
29
38
  // Actions
30
39
  setLoading: (loading) => set({ loading }),
40
+ setGeometryStreamingActive: (geometryStreamingActive) => set({ geometryStreamingActive }),
31
41
  setProgress: (progress) => set({ progress }),
42
+ setGeometryProgress: (geometryProgress) => set({ geometryProgress }),
43
+ setMetadataProgress: (metadataProgress) => set({ metadataProgress }),
32
44
  setError: (error) => set({ error }),
33
45
  });
@@ -25,6 +25,10 @@ export interface ModelSlice {
25
25
  // Actions
26
26
  /** Add a new model to the federation */
27
27
  addModel: (model: FederatedModel) => void;
28
+ /** Add or merge a model in place */
29
+ upsertModel: (model: FederatedModel) => void;
30
+ /** Update an existing model with partial fields */
31
+ updateModel: (modelId: string, patch: Partial<FederatedModel>) => void;
28
32
  /** Remove a model from the federation */
29
33
  removeModel: (modelId: string) => void;
30
34
  /** Clear all models */
@@ -81,7 +85,12 @@ export const createModelSlice: StateCreator<ModelSlice, [], [], ModelSlice> = (s
81
85
  // If first model, make it active
82
86
  // If adding more models, collapse all existing by default
83
87
  if (state.models.size === 0) {
84
- return { models: newModels, activeModelId: model.id };
88
+ return {
89
+ models: newModels,
90
+ activeModelId: model.id,
91
+ ifcDataStore: model.ifcDataStore ?? null,
92
+ geometryResult: model.geometryResult ?? null,
93
+ };
85
94
  } else {
86
95
  // Collapse existing models when adding new ones
87
96
  for (const [id, m] of newModels) {
@@ -93,6 +102,36 @@ export const createModelSlice: StateCreator<ModelSlice, [], [], ModelSlice> = (s
93
102
  }
94
103
  }),
95
104
 
105
+ upsertModel: (model) => set((state) => {
106
+ const newModels = new Map(state.models);
107
+ const existing = newModels.get(model.id);
108
+ newModels.set(model.id, existing ? { ...existing, ...model } : model);
109
+ const activeModelId = state.activeModelId ?? model.id;
110
+ const activeModel = newModels.get(activeModelId) ?? null;
111
+
112
+ return {
113
+ models: newModels,
114
+ activeModelId,
115
+ ifcDataStore: activeModel?.ifcDataStore ?? null,
116
+ geometryResult: activeModel?.geometryResult ?? null,
117
+ };
118
+ }),
119
+
120
+ updateModel: (modelId, patch) => set((state) => {
121
+ const model = state.models.get(modelId);
122
+ if (!model) return {};
123
+
124
+ const updatedModel = { ...model, ...patch };
125
+ const newModels = new Map(state.models);
126
+ newModels.set(modelId, updatedModel);
127
+
128
+ return {
129
+ models: newModels,
130
+ ifcDataStore: state.activeModelId === modelId ? updatedModel.ifcDataStore : state.ifcDataStore,
131
+ geometryResult: state.activeModelId === modelId ? updatedModel.geometryResult : state.geometryResult,
132
+ };
133
+ }),
134
+
96
135
  removeModel: (modelId) => set((state) => {
97
136
  const newModels = new Map(state.models);
98
137
  newModels.delete(modelId);
@@ -107,7 +146,14 @@ export const createModelSlice: StateCreator<ModelSlice, [], [], ModelSlice> = (s
107
146
  newActiveId = remaining.length > 0 ? remaining[0] : null;
108
147
  }
109
148
 
110
- return { models: newModels, activeModelId: newActiveId };
149
+ const activeModel = newActiveId ? newModels.get(newActiveId) : null;
150
+
151
+ return {
152
+ models: newModels,
153
+ activeModelId: newActiveId,
154
+ ifcDataStore: activeModel?.ifcDataStore ?? null,
155
+ geometryResult: activeModel?.geometryResult ?? null,
156
+ };
111
157
  }),
112
158
 
113
159
  clearAllModels: () => {
@@ -116,10 +162,19 @@ export const createModelSlice: StateCreator<ModelSlice, [], [], ModelSlice> = (s
116
162
  return set({
117
163
  models: new Map(),
118
164
  activeModelId: null,
165
+ ifcDataStore: null,
166
+ geometryResult: null,
119
167
  });
120
168
  },
121
169
 
122
- setActiveModel: (modelId) => set({ activeModelId: modelId }),
170
+ setActiveModel: (modelId) => set((state) => {
171
+ const activeModel = modelId ? state.models.get(modelId) : null;
172
+ return {
173
+ activeModelId: modelId,
174
+ ifcDataStore: activeModel?.ifcDataStore ?? null,
175
+ geometryResult: activeModel?.geometryResult ?? null,
176
+ };
177
+ }),
123
178
 
124
179
  setModelVisibility: (modelId, visible) => set((state) => {
125
180
  const model = state.models.get(modelId);
@@ -11,6 +11,13 @@ import type { ViewerState } from '../index.js';
11
11
  import type { MutablePropertyView } from '@ifc-lite/mutations';
12
12
  import type { Mutation, ChangeSet, PropertyValue } from '@ifc-lite/mutations';
13
13
  import { PropertyValueType, QuantityType } from '@ifc-lite/data';
14
+ import type { MapConversion, ProjectedCRS } from '@ifc-lite/parser';
15
+
16
+ /** Tracks georeferencing field mutations per model */
17
+ export interface GeorefMutationData {
18
+ projectedCRS?: Partial<ProjectedCRS>;
19
+ mapConversion?: Partial<MapConversion>;
20
+ }
14
21
 
15
22
  export interface MutationSlice {
16
23
  // State
@@ -28,6 +35,26 @@ export interface MutationSlice {
28
35
  dirtyModels: Set<string>;
29
36
  /** Version counter to trigger re-renders when mutations change */
30
37
  mutationVersion: number;
38
+ /** Georeferencing mutations per model */
39
+ georefMutations: Map<string, GeorefMutationData>;
40
+
41
+ // Actions - Georeferencing Mutations
42
+ /** Set a georeferencing field value */
43
+ setGeorefField: (
44
+ modelId: string,
45
+ entity: 'projectedCRS' | 'mapConversion',
46
+ field: string,
47
+ value: string | number,
48
+ oldValue?: string | number
49
+ ) => void;
50
+ /** Set multiple georeferencing field values atomically */
51
+ setGeorefFields: (
52
+ modelId: string,
53
+ entity: 'projectedCRS' | 'mapConversion',
54
+ fields: Array<{ field: string; value: string | number; oldValue?: string | number }>
55
+ ) => void;
56
+ /** Get merged georef mutations for a model */
57
+ getGeorefMutations: (modelId: string) => GeorefMutationData | undefined;
31
58
 
32
59
  // Actions - Mutation View Management
33
60
  /** Get or create mutation view for a model */
@@ -154,6 +181,60 @@ export const createMutationSlice: StateCreator<
154
181
  redoStacks: new Map(),
155
182
  dirtyModels: new Set(),
156
183
  mutationVersion: 0,
184
+ georefMutations: new Map(),
185
+
186
+ // Georeferencing Mutations
187
+ setGeorefField: (modelId, entity, field, value, oldValue) => {
188
+ get().setGeorefFields(modelId, entity, [{ field, value, oldValue }]);
189
+ },
190
+
191
+ setGeorefFields: (modelId, entity, fields) => {
192
+ if (fields.length === 0) return;
193
+ set((state) => {
194
+ const newGeorefMuts = new Map(state.georefMutations);
195
+ const modelMuts = { ...(newGeorefMuts.get(modelId) || {}) };
196
+ const entityMuts = { ...(modelMuts[entity] || {}) } as Record<string, unknown>;
197
+ for (const entry of fields) {
198
+ entityMuts[entry.field] = entry.value;
199
+ }
200
+ newGeorefMuts.set(modelId, { ...modelMuts, [entity]: entityMuts });
201
+
202
+ // Track undo
203
+ const newUndoStacks = new Map(state.undoStacks);
204
+ const stack = newUndoStacks.get(modelId) || [];
205
+ const nextMutations: Mutation[] = fields.map(entry => ({
206
+ id: `mut_georef_${entity}_${entry.field}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
207
+ type: 'UPDATE_ATTRIBUTE',
208
+ timestamp: Date.now(),
209
+ modelId,
210
+ entityId: 0, // georef entities don't map to a specific element
211
+ attributeName: `georef.${entity}.${entry.field}`,
212
+ oldValue: entry.oldValue,
213
+ newValue: entry.value,
214
+ propName: entry.field,
215
+ psetName: entity,
216
+ }));
217
+ newUndoStacks.set(modelId, [...stack, ...nextMutations]);
218
+
219
+ const newRedoStacks = new Map(state.redoStacks);
220
+ newRedoStacks.set(modelId, []);
221
+
222
+ const newDirty = new Set(state.dirtyModels);
223
+ newDirty.add(modelId);
224
+
225
+ return {
226
+ georefMutations: newGeorefMuts,
227
+ undoStacks: newUndoStacks,
228
+ redoStacks: newRedoStacks,
229
+ dirtyModels: newDirty,
230
+ mutationVersion: state.mutationVersion + 1,
231
+ };
232
+ });
233
+ },
234
+
235
+ getGeorefMutations: (modelId) => {
236
+ return get().georefMutations.get(modelId);
237
+ },
157
238
 
158
239
  // Mutation View Management
159
240
  getMutationView: (modelId) => {
@@ -388,6 +469,48 @@ export const createMutationSlice: StateCreator<
388
469
  if (undoStack.length === 0) return;
389
470
 
390
471
  const mutation = undoStack[undoStack.length - 1];
472
+
473
+ // Handle georef mutations directly on georefMutations map
474
+ if (mutation.type === 'UPDATE_ATTRIBUTE' && mutation.attributeName?.startsWith('georef.')) {
475
+ const parts = mutation.attributeName.split('.');
476
+ const entity = parts[1] as 'projectedCRS' | 'mapConversion';
477
+ const field = parts[2];
478
+ set((s) => {
479
+ const newGeorefMuts = new Map(s.georefMutations);
480
+ const modelMuts = { ...(newGeorefMuts.get(modelId) || {}) };
481
+ const entityMuts = { ...(modelMuts[entity] || {}) } as Record<string, unknown>;
482
+ if (mutation.oldValue !== undefined && mutation.oldValue !== null) {
483
+ entityMuts[field] = mutation.oldValue;
484
+ } else {
485
+ delete entityMuts[field];
486
+ }
487
+ if (Object.keys(entityMuts).length === 0) {
488
+ delete modelMuts[entity];
489
+ } else {
490
+ modelMuts[entity] = entityMuts as typeof modelMuts[typeof entity];
491
+ }
492
+ if (Object.keys(modelMuts).length === 0) {
493
+ newGeorefMuts.delete(modelId);
494
+ } else {
495
+ newGeorefMuts.set(modelId, modelMuts);
496
+ }
497
+
498
+ const newUndoStacks = new Map(s.undoStacks);
499
+ newUndoStacks.set(modelId, undoStack.slice(0, -1));
500
+ const newRedoStacks = new Map(s.redoStacks);
501
+ const redoStack = newRedoStacks.get(modelId) || [];
502
+ newRedoStacks.set(modelId, [...redoStack, mutation]);
503
+
504
+ return {
505
+ georefMutations: newGeorefMuts,
506
+ undoStacks: newUndoStacks,
507
+ redoStacks: newRedoStacks,
508
+ mutationVersion: s.mutationVersion + 1,
509
+ };
510
+ });
511
+ return;
512
+ }
513
+
391
514
  const view = state.mutationViews.get(modelId);
392
515
  if (!view) return;
393
516
 
@@ -465,6 +588,48 @@ export const createMutationSlice: StateCreator<
465
588
  if (redoStack.length === 0) return;
466
589
 
467
590
  const mutation = redoStack[redoStack.length - 1];
591
+
592
+ // Handle georef mutations directly
593
+ if (mutation.type === 'UPDATE_ATTRIBUTE' && mutation.attributeName?.startsWith('georef.')) {
594
+ const parts = mutation.attributeName.split('.');
595
+ const entity = parts[1] as 'projectedCRS' | 'mapConversion';
596
+ const field = parts[2];
597
+ set((s) => {
598
+ const newGeorefMuts = new Map(s.georefMutations);
599
+ const modelMuts = { ...(newGeorefMuts.get(modelId) || {}) };
600
+ const entityMuts = { ...(modelMuts[entity] || {}) } as Record<string, unknown>;
601
+ if (mutation.newValue !== undefined && mutation.newValue !== null) {
602
+ entityMuts[field] = mutation.newValue;
603
+ } else {
604
+ delete entityMuts[field];
605
+ }
606
+ if (Object.keys(entityMuts).length === 0) {
607
+ delete modelMuts[entity];
608
+ } else {
609
+ modelMuts[entity] = entityMuts as typeof modelMuts[typeof entity];
610
+ }
611
+ if (Object.keys(modelMuts).length === 0) {
612
+ newGeorefMuts.delete(modelId);
613
+ } else {
614
+ newGeorefMuts.set(modelId, modelMuts);
615
+ }
616
+
617
+ const newRedoStacks = new Map(s.redoStacks);
618
+ newRedoStacks.set(modelId, redoStack.slice(0, -1));
619
+ const newUndoStacks = new Map(s.undoStacks);
620
+ const undoStack = newUndoStacks.get(modelId) || [];
621
+ newUndoStacks.set(modelId, [...undoStack, mutation]);
622
+
623
+ return {
624
+ georefMutations: newGeorefMuts,
625
+ undoStacks: newUndoStacks,
626
+ redoStacks: newRedoStacks,
627
+ mutationVersion: s.mutationVersion + 1,
628
+ };
629
+ });
630
+ return;
631
+ }
632
+
468
633
  const view = state.mutationViews.get(modelId);
469
634
  if (!view) return;
470
635
 
@@ -606,6 +771,14 @@ export const createMutationSlice: StateCreator<
606
771
  for (const view of get().mutationViews.values()) {
607
772
  count += view.getModifiedEntityCount();
608
773
  }
774
+ // Include models with georef-only edits
775
+ for (const [modelId, gm] of get().georefMutations) {
776
+ const hasGeoref = (gm.projectedCRS && Object.keys(gm.projectedCRS).length > 0)
777
+ || (gm.mapConversion && Object.keys(gm.mapConversion).length > 0);
778
+ if (hasGeoref && !get().mutationViews.has(modelId)) {
779
+ count += 1; // count the model as having modifications
780
+ }
781
+ }
609
782
  return count;
610
783
  },
611
784
 
@@ -626,10 +799,14 @@ export const createMutationSlice: StateCreator<
626
799
  const newDirty = new Set(state.dirtyModels);
627
800
  newDirty.delete(modelId);
628
801
 
802
+ const newGeorefMuts = new Map(state.georefMutations);
803
+ newGeorefMuts.delete(modelId);
804
+
629
805
  return {
630
806
  undoStacks: newUndoStacks,
631
807
  redoStacks: newRedoStacks,
632
808
  dirtyModels: newDirty,
809
+ georefMutations: newGeorefMuts,
633
810
  mutationVersion: state.mutationVersion + 1,
634
811
  };
635
812
  });
@@ -644,6 +821,7 @@ export const createMutationSlice: StateCreator<
644
821
  undoStacks: new Map(),
645
822
  redoStacks: new Map(),
646
823
  dirtyModels: new Set(),
824
+ georefMutations: new Map(),
647
825
  mutationVersion: state.mutationVersion + 1,
648
826
  }));
649
827
  },