@bldrs-ai/conway 1.389.1221 → 1.394.1504

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 (382) hide show
  1. package/README.md +36 -22
  2. package/compiled/Dist/ConwayGeomWasmNode.js +0 -0
  3. package/compiled/Dist/ConwayGeomWasmNodeMT.js +0 -0
  4. package/compiled/Dist/ConwayGeomWasmWeb.js +0 -0
  5. package/compiled/Dist/ConwayGeomWasmWebMT.js +1 -1
  6. package/compiled/Dist/ConwayGeomWasmWebMT.wasm +0 -0
  7. package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmNode.js +0 -0
  8. package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmNodeMT.js +0 -0
  9. package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmWeb.js +0 -0
  10. package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmWebMT.js +1 -1
  11. package/compiled/dependencies/conway-geom/Dist/ConwayGeomWasmWebMT.wasm +0 -0
  12. package/compiled/dependencies/conway-geom/interface/conway_geometry.d.ts +19 -0
  13. package/compiled/dependencies/conway-geom/interface/conway_geometry.d.ts.map +1 -1
  14. package/compiled/dependencies/conway-geom/interface/conway_geometry.js +17 -0
  15. package/compiled/dependencies/conway-geom/interface/parameters/surface_object.d.ts +6 -0
  16. package/compiled/dependencies/conway-geom/interface/parameters/surface_object.d.ts.map +1 -1
  17. package/compiled/examples/browser-bundled.cjs +1569 -51
  18. package/compiled/examples/cli-bundled.cjs +2790 -436
  19. package/compiled/examples/cli-step-bundled.cjs +1904 -229
  20. package/compiled/examples/validator-bundled.cjs +1569 -51
  21. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/b_spline_curve.gen.d.ts +2 -2
  22. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/b_spline_curve.gen.d.ts.map +1 -1
  23. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/b_spline_curve.gen.js +2 -2
  24. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/b_spline_surface.gen.d.ts +3 -3
  25. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/b_spline_surface.gen.d.ts.map +1 -1
  26. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/b_spline_surface.gen.js +3 -3
  27. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/composite_curve.gen.d.ts +1 -1
  28. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/composite_curve.gen.d.ts.map +1 -1
  29. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/composite_curve.gen.js +1 -1
  30. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/offset_curve_2d.gen.d.ts +1 -1
  31. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/offset_curve_2d.gen.d.ts.map +1 -1
  32. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/offset_curve_2d.gen.js +1 -1
  33. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/offset_curve_3d.gen.d.ts +1 -1
  34. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/offset_curve_3d.gen.d.ts.map +1 -1
  35. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/offset_curve_3d.gen.js +1 -1
  36. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/offset_surface.gen.d.ts +1 -1
  37. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/offset_surface.gen.d.ts.map +1 -1
  38. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/offset_surface.gen.js +1 -1
  39. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/presentation_style_assignment.gen.d.ts.map +1 -1
  40. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/presentation_style_assignment.gen.js +2 -2
  41. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/schema_ap214.gen.js +11 -11
  42. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/shape_aspect.gen.d.ts +1 -1
  43. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/shape_aspect.gen.d.ts.map +1 -1
  44. package/compiled/src/AP214E3_2010/AP214E3_2010_gen/shape_aspect.gen.js +1 -1
  45. package/compiled/src/AP214E3_2010/ap214_command_line_main.js +7 -3
  46. package/compiled/src/AP214E3_2010/ap214_geometry_extraction.d.ts +181 -7
  47. package/compiled/src/AP214E3_2010/ap214_geometry_extraction.d.ts.map +1 -1
  48. package/compiled/src/AP214E3_2010/ap214_geometry_extraction.js +906 -137
  49. package/compiled/src/AP214E3_2010/ap214_geometry_extraction.test.js +10 -2
  50. package/compiled/src/AP214E3_2010/ap214_logical_attribute.test.d.ts +2 -0
  51. package/compiled/src/AP214E3_2010/ap214_logical_attribute.test.d.ts.map +1 -0
  52. package/compiled/src/AP214E3_2010/ap214_logical_attribute.test.js +114 -0
  53. package/compiled/src/AP214E3_2010/ap214_model_geometry.d.ts +6 -0
  54. package/compiled/src/AP214E3_2010/ap214_model_geometry.d.ts.map +1 -1
  55. package/compiled/src/AP214E3_2010/ap214_model_geometry.js +9 -0
  56. package/compiled/src/AP214E3_2010/ap214_pcurve_basis_surfaces.test.d.ts +2 -0
  57. package/compiled/src/AP214E3_2010/ap214_pcurve_basis_surfaces.test.d.ts.map +1 -0
  58. package/compiled/src/AP214E3_2010/ap214_pcurve_basis_surfaces.test.js +233 -0
  59. package/compiled/src/AP214E3_2010/ap214_sphere_vertex_loop.test.d.ts +2 -0
  60. package/compiled/src/AP214E3_2010/ap214_sphere_vertex_loop.test.d.ts.map +1 -0
  61. package/compiled/src/AP214E3_2010/ap214_sphere_vertex_loop.test.js +147 -0
  62. package/compiled/src/AP214E3_2010/ap214_step_model.d.ts +2 -1
  63. package/compiled/src/AP214E3_2010/ap214_step_model.d.ts.map +1 -1
  64. package/compiled/src/AP214E3_2010/ap214_step_model.js +1 -1
  65. package/compiled/src/AP214E3_2010/ap214_typed_select_enum.test.d.ts +2 -0
  66. package/compiled/src/AP214E3_2010/ap214_typed_select_enum.test.d.ts.map +1 -0
  67. package/compiled/src/AP214E3_2010/ap214_typed_select_enum.test.js +65 -0
  68. package/compiled/src/AP214E3_2010/ap214_unit_scale.test.d.ts +2 -0
  69. package/compiled/src/AP214E3_2010/ap214_unit_scale.test.d.ts.map +1 -0
  70. package/compiled/src/AP214E3_2010/ap214_unit_scale.test.js +111 -0
  71. package/compiled/src/compat/web-ifc/ap214_streamed_open.test.d.ts +2 -0
  72. package/compiled/src/compat/web-ifc/ap214_streamed_open.test.d.ts.map +1 -0
  73. package/compiled/src/compat/web-ifc/ap214_streamed_open.test.js +190 -0
  74. package/compiled/src/compat/web-ifc/coordination_export_order.test.d.ts +2 -0
  75. package/compiled/src/compat/web-ifc/coordination_export_order.test.d.ts.map +1 -0
  76. package/compiled/src/compat/web-ifc/coordination_export_order.test.js +110 -0
  77. package/compiled/src/compat/web-ifc/coordination_f64.d.ts +165 -0
  78. package/compiled/src/compat/web-ifc/coordination_f64.d.ts.map +1 -0
  79. package/compiled/src/compat/web-ifc/coordination_f64.js +286 -0
  80. package/compiled/src/compat/web-ifc/coordination_f64.test.d.ts +2 -0
  81. package/compiled/src/compat/web-ifc/coordination_f64.test.d.ts.map +1 -0
  82. package/compiled/src/compat/web-ifc/coordination_f64.test.js +225 -0
  83. package/compiled/src/compat/web-ifc/ifc_api.d.ts +154 -0
  84. package/compiled/src/compat/web-ifc/ifc_api.d.ts.map +1 -1
  85. package/compiled/src/compat/web-ifc/ifc_api.js +211 -3
  86. package/compiled/src/compat/web-ifc/ifc_api_deferred_open.test.d.ts +2 -0
  87. package/compiled/src/compat/web-ifc/ifc_api_deferred_open.test.d.ts.map +1 -0
  88. package/compiled/src/compat/web-ifc/ifc_api_deferred_open.test.js +288 -0
  89. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough.d.ts +39 -0
  90. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough.d.ts.map +1 -1
  91. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough_factory.d.ts +30 -0
  92. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough_factory.d.ts.map +1 -1
  93. package/compiled/src/compat/web-ifc/ifc_api_model_passthrough_factory.js +83 -0
  94. package/compiled/src/compat/web-ifc/ifc_api_preview_channel.test.d.ts +2 -0
  95. package/compiled/src/compat/web-ifc/ifc_api_preview_channel.test.d.ts.map +1 -0
  96. package/compiled/src/compat/web-ifc/ifc_api_preview_channel.test.js +383 -0
  97. package/compiled/src/compat/web-ifc/ifc_api_preview_coordination.test.d.ts +2 -0
  98. package/compiled/src/compat/web-ifc/ifc_api_preview_coordination.test.d.ts.map +1 -0
  99. package/compiled/src/compat/web-ifc/ifc_api_preview_coordination.test.js +124 -0
  100. package/compiled/src/compat/web-ifc/ifc_api_proxy_ap214.d.ts +132 -0
  101. package/compiled/src/compat/web-ifc/ifc_api_proxy_ap214.d.ts.map +1 -1
  102. package/compiled/src/compat/web-ifc/ifc_api_proxy_ap214.js +488 -92
  103. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts +268 -0
  104. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.d.ts.map +1 -1
  105. package/compiled/src/compat/web-ifc/ifc_api_proxy_ifc.js +996 -149
  106. package/compiled/src/compat/web-ifc/ifc_api_streamed_open.test.d.ts +2 -0
  107. package/compiled/src/compat/web-ifc/ifc_api_streamed_open.test.d.ts.map +1 -0
  108. package/compiled/src/compat/web-ifc/ifc_api_streamed_open.test.js +129 -0
  109. package/compiled/src/compat/web-ifc/spatial_imposter.d.ts +134 -0
  110. package/compiled/src/compat/web-ifc/spatial_imposter.d.ts.map +1 -0
  111. package/compiled/src/compat/web-ifc/spatial_imposter.js +933 -0
  112. package/compiled/src/compat/web-ifc/spatial_imposter.test.d.ts +2 -0
  113. package/compiled/src/compat/web-ifc/spatial_imposter.test.d.ts.map +1 -0
  114. package/compiled/src/compat/web-ifc/spatial_imposter.test.js +467 -0
  115. package/compiled/src/compat/web-ifc/store_preview_channel.d.ts +187 -0
  116. package/compiled/src/compat/web-ifc/store_preview_channel.d.ts.map +1 -0
  117. package/compiled/src/compat/web-ifc/store_preview_channel.js +524 -0
  118. package/compiled/src/compat/web-ifc/store_preview_channel.test.d.ts +2 -0
  119. package/compiled/src/compat/web-ifc/store_preview_channel.test.d.ts.map +1 -0
  120. package/compiled/src/compat/web-ifc/store_preview_channel.test.js +234 -0
  121. package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts +306 -0
  122. package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts.map +1 -0
  123. package/compiled/src/compat/web-ifc/streamed_preview_channel.js +580 -0
  124. package/compiled/src/compat/web-ifc/web_wasm_directory.test.d.ts +2 -0
  125. package/compiled/src/compat/web-ifc/web_wasm_directory.test.d.ts.map +1 -0
  126. package/compiled/src/compat/web-ifc/web_wasm_directory.test.js +22 -0
  127. package/compiled/src/core/composed_model_skeleton.d.ts +63 -0
  128. package/compiled/src/core/composed_model_skeleton.d.ts.map +1 -0
  129. package/compiled/src/core/composed_model_skeleton.js +66 -0
  130. package/compiled/src/core/composed_model_skeleton.test.d.ts +2 -0
  131. package/compiled/src/core/composed_model_skeleton.test.d.ts.map +1 -0
  132. package/compiled/src/core/composed_model_skeleton.test.js +69 -0
  133. package/compiled/src/core/demand_geometry_queue.d.ts +147 -0
  134. package/compiled/src/core/demand_geometry_queue.d.ts.map +1 -0
  135. package/compiled/src/core/demand_geometry_queue.js +223 -0
  136. package/compiled/src/core/demand_geometry_queue.test.d.ts +2 -0
  137. package/compiled/src/core/demand_geometry_queue.test.d.ts.map +1 -0
  138. package/compiled/src/core/demand_geometry_queue.test.js +133 -0
  139. package/compiled/src/core/demand_residency_pump.d.ts +93 -0
  140. package/compiled/src/core/demand_residency_pump.d.ts.map +1 -0
  141. package/compiled/src/core/demand_residency_pump.js +120 -0
  142. package/compiled/src/core/demand_residency_pump.test.d.ts +2 -0
  143. package/compiled/src/core/demand_residency_pump.test.d.ts.map +1 -0
  144. package/compiled/src/core/demand_residency_pump.test.js +148 -0
  145. package/compiled/src/core/geometry_tile_bindings.d.ts +110 -0
  146. package/compiled/src/core/geometry_tile_bindings.d.ts.map +1 -0
  147. package/compiled/src/core/geometry_tile_bindings.js +76 -0
  148. package/compiled/src/core/geometry_tile_bindings.test.d.ts +2 -0
  149. package/compiled/src/core/geometry_tile_bindings.test.d.ts.map +1 -0
  150. package/compiled/src/core/geometry_tile_bindings.test.js +211 -0
  151. package/compiled/src/core/geometry_tile_pool.d.ts +99 -0
  152. package/compiled/src/core/geometry_tile_pool.d.ts.map +1 -0
  153. package/compiled/src/core/geometry_tile_pool.js +104 -0
  154. package/compiled/src/core/geometry_tile_pool.test.d.ts +2 -0
  155. package/compiled/src/core/geometry_tile_pool.test.d.ts.map +1 -0
  156. package/compiled/src/core/geometry_tile_pool.test.js +180 -0
  157. package/compiled/src/core/mem/chunked_pool.d.ts +116 -0
  158. package/compiled/src/core/mem/chunked_pool.d.ts.map +1 -0
  159. package/compiled/src/core/mem/chunked_pool.js +149 -0
  160. package/compiled/src/core/mem/chunked_pool.test.d.ts +2 -0
  161. package/compiled/src/core/mem/chunked_pool.test.d.ts.map +1 -0
  162. package/compiled/src/core/mem/chunked_pool.test.js +63 -0
  163. package/compiled/src/core/mem/shared_asset_pool.d.ts +82 -0
  164. package/compiled/src/core/mem/shared_asset_pool.d.ts.map +1 -0
  165. package/compiled/src/core/mem/shared_asset_pool.js +113 -0
  166. package/compiled/src/core/mem/shared_asset_pool.test.d.ts +2 -0
  167. package/compiled/src/core/mem/shared_asset_pool.test.d.ts.map +1 -0
  168. package/compiled/src/core/mem/shared_asset_pool.test.js +49 -0
  169. package/compiled/src/core/model_registry.d.ts +87 -0
  170. package/compiled/src/core/model_registry.d.ts.map +1 -0
  171. package/compiled/src/core/model_registry.js +101 -0
  172. package/compiled/src/core/model_registry.test.d.ts +2 -0
  173. package/compiled/src/core/model_registry.test.d.ts.map +1 -0
  174. package/compiled/src/core/model_registry.test.js +60 -0
  175. package/compiled/src/core/model_uri.d.ts +57 -0
  176. package/compiled/src/core/model_uri.d.ts.map +1 -0
  177. package/compiled/src/core/model_uri.js +101 -0
  178. package/compiled/src/core/model_uri.test.d.ts +2 -0
  179. package/compiled/src/core/model_uri.test.d.ts.map +1 -0
  180. package/compiled/src/core/model_uri.test.js +49 -0
  181. package/compiled/src/core/progress.d.ts +16 -2
  182. package/compiled/src/core/progress.d.ts.map +1 -1
  183. package/compiled/src/core/progress.js +31 -5
  184. package/compiled/src/core/progress.test.js +11 -2
  185. package/compiled/src/core/progress_log.d.ts +1 -0
  186. package/compiled/src/core/progress_log.d.ts.map +1 -1
  187. package/compiled/src/core/progress_log.js +7 -2
  188. package/compiled/src/core/progress_log.test.js +21 -0
  189. package/compiled/src/core/shared_byte_budget.d.ts +60 -0
  190. package/compiled/src/core/shared_byte_budget.d.ts.map +1 -0
  191. package/compiled/src/core/shared_byte_budget.js +85 -0
  192. package/compiled/src/core/shared_byte_budget.test.d.ts +2 -0
  193. package/compiled/src/core/shared_byte_budget.test.d.ts.map +1 -0
  194. package/compiled/src/core/shared_byte_budget.test.js +55 -0
  195. package/compiled/src/core/wasm_heap.d.ts +208 -0
  196. package/compiled/src/core/wasm_heap.d.ts.map +1 -0
  197. package/compiled/src/core/wasm_heap.js +417 -0
  198. package/compiled/src/core/wasm_heap.test.d.ts +2 -0
  199. package/compiled/src/core/wasm_heap.test.d.ts.map +1 -0
  200. package/compiled/src/core/wasm_heap.test.js +546 -0
  201. package/compiled/src/demand/index.d.ts +23 -0
  202. package/compiled/src/demand/index.d.ts.map +1 -0
  203. package/compiled/src/demand/index.js +22 -0
  204. package/compiled/src/ifc/ifc4_gen/IfcLogical.gen.d.ts +1 -1
  205. package/compiled/src/ifc/ifc4_gen/IfcLogical.gen.d.ts.map +1 -1
  206. package/compiled/src/ifc/ifc4_gen/IfcLogical.gen.js +1 -1
  207. package/compiled/src/ifc/ifc4_gen/IfcPresentationStyleAssignment.gen.d.ts.map +1 -1
  208. package/compiled/src/ifc/ifc4_gen/IfcPresentationStyleAssignment.gen.js +2 -2
  209. package/compiled/src/ifc/ifc4_gen/IfcRelInterferesElements.gen.d.ts +1 -1
  210. package/compiled/src/ifc/ifc4_gen/IfcRelInterferesElements.gen.d.ts.map +1 -1
  211. package/compiled/src/ifc/ifc4_gen/IfcRelInterferesElements.gen.js +1 -1
  212. package/compiled/src/ifc/ifc4_gen/schema_ifc.gen.js +2 -2
  213. package/compiled/src/ifc/ifc_demand_extraction.test.d.ts +2 -0
  214. package/compiled/src/ifc/ifc_demand_extraction.test.d.ts.map +1 -0
  215. package/compiled/src/ifc/ifc_demand_extraction.test.js +251 -0
  216. package/compiled/src/ifc/ifc_geometry_extraction.d.ts +367 -6
  217. package/compiled/src/ifc/ifc_geometry_extraction.d.ts.map +1 -1
  218. package/compiled/src/ifc/ifc_geometry_extraction.js +1322 -389
  219. package/compiled/src/ifc/ifc_grid_placement.test.d.ts +2 -0
  220. package/compiled/src/ifc/ifc_grid_placement.test.d.ts.map +1 -0
  221. package/compiled/src/ifc/ifc_grid_placement.test.js +261 -0
  222. package/compiled/src/ifc/ifc_model_geometry.d.ts +12 -0
  223. package/compiled/src/ifc/ifc_model_geometry.d.ts.map +1 -1
  224. package/compiled/src/ifc/ifc_model_geometry.js +15 -0
  225. package/compiled/src/ifc/ifc_regression_batch_main.js +210 -12
  226. package/compiled/src/ifc/ifc_spatial_skeleton.d.ts +145 -0
  227. package/compiled/src/ifc/ifc_spatial_skeleton.d.ts.map +1 -0
  228. package/compiled/src/ifc/ifc_spatial_skeleton.js +266 -0
  229. package/compiled/src/ifc/ifc_spatial_skeleton.test.d.ts +2 -0
  230. package/compiled/src/ifc/ifc_spatial_skeleton.test.d.ts.map +1 -0
  231. package/compiled/src/ifc/ifc_spatial_skeleton.test.js +199 -0
  232. package/compiled/src/ifc/ifc_step_model.d.ts +6 -2
  233. package/compiled/src/ifc/ifc_step_model.d.ts.map +1 -1
  234. package/compiled/src/ifc/ifc_step_model.js +6 -4
  235. package/compiled/src/ifc/ifc_step_parser.d.ts +58 -0
  236. package/compiled/src/ifc/ifc_step_parser.d.ts.map +1 -1
  237. package/compiled/src/ifc/ifc_step_parser.js +64 -0
  238. package/compiled/src/ifc/ifc_stream_model.test.d.ts +2 -0
  239. package/compiled/src/ifc/ifc_stream_model.test.d.ts.map +1 -0
  240. package/compiled/src/ifc/ifc_stream_model.test.js +70 -0
  241. package/compiled/src/ifc/ifc_stream_open.d.ts +131 -0
  242. package/compiled/src/ifc/ifc_stream_open.d.ts.map +1 -0
  243. package/compiled/src/ifc/ifc_stream_open.js +116 -0
  244. package/compiled/src/ifc/ifc_stream_open.test.d.ts +2 -0
  245. package/compiled/src/ifc/ifc_stream_open.test.d.ts.map +1 -0
  246. package/compiled/src/ifc/ifc_stream_open.test.js +230 -0
  247. package/compiled/src/ifc/ifc_tile_extractor.d.ts +84 -0
  248. package/compiled/src/ifc/ifc_tile_extractor.d.ts.map +1 -0
  249. package/compiled/src/ifc/ifc_tile_extractor.js +134 -0
  250. package/compiled/src/ifc/ifc_tile_extractor.test.d.ts +2 -0
  251. package/compiled/src/ifc/ifc_tile_extractor.test.d.ts.map +1 -0
  252. package/compiled/src/ifc/ifc_tile_extractor.test.js +118 -0
  253. package/compiled/src/ifc/ifc_typed_select_enum.test.d.ts +2 -0
  254. package/compiled/src/ifc/ifc_typed_select_enum.test.d.ts.map +1 -0
  255. package/compiled/src/ifc/ifc_typed_select_enum.test.js +60 -0
  256. package/compiled/src/index.d.ts +21 -1
  257. package/compiled/src/index.d.ts.map +1 -1
  258. package/compiled/src/index.js +28 -1
  259. package/compiled/src/logging/logger.d.ts +16 -2
  260. package/compiled/src/logging/logger.d.ts.map +1 -1
  261. package/compiled/src/logging/logger.js +34 -7
  262. package/compiled/src/logging/logger.test.js +30 -0
  263. package/compiled/src/mem/index.d.ts +17 -0
  264. package/compiled/src/mem/index.d.ts.map +1 -0
  265. package/compiled/src/mem/index.js +16 -0
  266. package/compiled/src/namespace_surface.test.d.ts +2 -0
  267. package/compiled/src/namespace_surface.test.d.ts.map +1 -0
  268. package/compiled/src/namespace_surface.test.js +43 -0
  269. package/compiled/src/parsing/parsing_buffer.d.ts +23 -0
  270. package/compiled/src/parsing/parsing_buffer.d.ts.map +1 -1
  271. package/compiled/src/parsing/parsing_buffer.js +31 -0
  272. package/compiled/src/scripts/model_report_displacement.test.d.ts +2 -0
  273. package/compiled/src/scripts/model_report_displacement.test.d.ts.map +1 -0
  274. package/compiled/src/scripts/model_report_displacement.test.js +104 -0
  275. package/compiled/src/scripts/render_glb_paths.test.d.ts +2 -0
  276. package/compiled/src/scripts/render_glb_paths.test.d.ts.map +1 -0
  277. package/compiled/src/scripts/render_glb_paths.test.js +115 -0
  278. package/compiled/src/step/indexing/step_type_indexer.d.ts +20 -0
  279. package/compiled/src/step/indexing/step_type_indexer.d.ts.map +1 -1
  280. package/compiled/src/step/indexing/step_type_indexer.js +67 -0
  281. package/compiled/src/step/parsing/byte_source.d.ts +153 -0
  282. package/compiled/src/step/parsing/byte_source.d.ts.map +1 -0
  283. package/compiled/src/step/parsing/byte_source.js +106 -0
  284. package/compiled/src/step/parsing/byte_source.test.d.ts +2 -0
  285. package/compiled/src/step/parsing/byte_source.test.d.ts.map +1 -0
  286. package/compiled/src/step/parsing/byte_source.test.js +47 -0
  287. package/compiled/src/step/parsing/byte_source_node.d.ts +41 -0
  288. package/compiled/src/step/parsing/byte_source_node.d.ts.map +1 -0
  289. package/compiled/src/step/parsing/byte_source_node.js +54 -0
  290. package/compiled/src/step/parsing/columnar_index.d.ts +103 -0
  291. package/compiled/src/step/parsing/columnar_index.d.ts.map +1 -0
  292. package/compiled/src/step/parsing/columnar_index.js +224 -0
  293. package/compiled/src/step/parsing/columnar_index.test.d.ts +2 -0
  294. package/compiled/src/step/parsing/columnar_index.test.d.ts.map +1 -0
  295. package/compiled/src/step/parsing/columnar_index.test.js +155 -0
  296. package/compiled/src/step/parsing/cross_reference_registry.d.ts +95 -0
  297. package/compiled/src/step/parsing/cross_reference_registry.d.ts.map +1 -0
  298. package/compiled/src/step/parsing/cross_reference_registry.js +91 -0
  299. package/compiled/src/step/parsing/cross_reference_registry.test.d.ts +2 -0
  300. package/compiled/src/step/parsing/cross_reference_registry.test.d.ts.map +1 -0
  301. package/compiled/src/step/parsing/cross_reference_registry.test.js +66 -0
  302. package/compiled/src/step/parsing/decode_utf8.d.ts +15 -0
  303. package/compiled/src/step/parsing/decode_utf8.d.ts.map +1 -1
  304. package/compiled/src/step/parsing/decode_utf8.js +28 -0
  305. package/compiled/src/step/parsing/express_ref_scan.d.ts +20 -0
  306. package/compiled/src/step/parsing/express_ref_scan.d.ts.map +1 -0
  307. package/compiled/src/step/parsing/express_ref_scan.js +91 -0
  308. package/compiled/src/step/parsing/express_ref_scan.test.d.ts +2 -0
  309. package/compiled/src/step/parsing/express_ref_scan.test.d.ts.map +1 -0
  310. package/compiled/src/step/parsing/express_ref_scan.test.js +28 -0
  311. package/compiled/src/step/parsing/index_sidecar.d.ts +81 -0
  312. package/compiled/src/step/parsing/index_sidecar.d.ts.map +1 -0
  313. package/compiled/src/step/parsing/index_sidecar.js +283 -0
  314. package/compiled/src/step/parsing/index_sidecar.test.d.ts +2 -0
  315. package/compiled/src/step/parsing/index_sidecar.test.d.ts.map +1 -0
  316. package/compiled/src/step/parsing/index_sidecar.test.js +122 -0
  317. package/compiled/src/step/parsing/inf_recovery_index.test.d.ts +2 -0
  318. package/compiled/src/step/parsing/inf_recovery_index.test.d.ts.map +1 -0
  319. package/compiled/src/step/parsing/inf_recovery_index.test.js +108 -0
  320. package/compiled/src/step/parsing/prefix_type_index.d.ts +124 -0
  321. package/compiled/src/step/parsing/prefix_type_index.d.ts.map +1 -0
  322. package/compiled/src/step/parsing/prefix_type_index.js +207 -0
  323. package/compiled/src/step/parsing/prefix_type_index.test.d.ts +2 -0
  324. package/compiled/src/step/parsing/prefix_type_index.test.d.ts.map +1 -0
  325. package/compiled/src/step/parsing/prefix_type_index.test.js +298 -0
  326. package/compiled/src/step/parsing/range_byte_source.d.ts +69 -0
  327. package/compiled/src/step/parsing/range_byte_source.d.ts.map +1 -0
  328. package/compiled/src/step/parsing/range_byte_source.js +78 -0
  329. package/compiled/src/step/parsing/range_byte_source.test.d.ts +2 -0
  330. package/compiled/src/step/parsing/range_byte_source.test.d.ts.map +1 -0
  331. package/compiled/src/step/parsing/range_byte_source.test.js +70 -0
  332. package/compiled/src/step/parsing/record_event.d.ts +23 -0
  333. package/compiled/src/step/parsing/record_event.d.ts.map +1 -0
  334. package/compiled/src/step/parsing/record_event.js +1 -0
  335. package/compiled/src/step/parsing/record_field_cursor.d.ts +94 -0
  336. package/compiled/src/step/parsing/record_field_cursor.d.ts.map +1 -0
  337. package/compiled/src/step/parsing/record_field_cursor.js +189 -0
  338. package/compiled/src/step/parsing/step_deserialization_functions.d.ts +29 -0
  339. package/compiled/src/step/parsing/step_deserialization_functions.d.ts.map +1 -1
  340. package/compiled/src/step/parsing/step_deserialization_functions.js +45 -0
  341. package/compiled/src/step/parsing/step_deserialization_functions.test.js +59 -1
  342. package/compiled/src/step/parsing/step_parser.d.ts +66 -1
  343. package/compiled/src/step/parsing/step_parser.d.ts.map +1 -1
  344. package/compiled/src/step/parsing/step_parser.js +164 -10
  345. package/compiled/src/step/parsing/step_string_parser.js +8 -8
  346. package/compiled/src/step/parsing/streaming_index_builder.d.ts +141 -0
  347. package/compiled/src/step/parsing/streaming_index_builder.d.ts.map +1 -0
  348. package/compiled/src/step/parsing/streaming_index_builder.js +280 -0
  349. package/compiled/src/step/parsing/streaming_index_builder.test.d.ts +2 -0
  350. package/compiled/src/step/parsing/streaming_index_builder.test.d.ts.map +1 -0
  351. package/compiled/src/step/parsing/streaming_index_builder.test.js +107 -0
  352. package/compiled/src/step/parsing/streaming_index_builder_async.test.d.ts +2 -0
  353. package/compiled/src/step/parsing/streaming_index_builder_async.test.d.ts.map +1 -0
  354. package/compiled/src/step/parsing/streaming_index_builder_async.test.js +109 -0
  355. package/compiled/src/step/parsing/streaming_record_dispatcher.d.ts +71 -0
  356. package/compiled/src/step/parsing/streaming_record_dispatcher.d.ts.map +1 -0
  357. package/compiled/src/step/parsing/streaming_record_dispatcher.js +88 -0
  358. package/compiled/src/step/parsing/streaming_record_dispatcher.test.d.ts +2 -0
  359. package/compiled/src/step/parsing/streaming_record_dispatcher.test.d.ts.map +1 -0
  360. package/compiled/src/step/parsing/streaming_record_dispatcher.test.js +66 -0
  361. package/compiled/src/step/parsing/uint32_sink.d.ts +72 -0
  362. package/compiled/src/step/parsing/uint32_sink.d.ts.map +1 -0
  363. package/compiled/src/step/parsing/uint32_sink.js +180 -0
  364. package/compiled/src/step/parsing/uint32_sink.test.d.ts +2 -0
  365. package/compiled/src/step/parsing/uint32_sink.test.d.ts.map +1 -0
  366. package/compiled/src/step/parsing/uint32_sink.test.js +199 -0
  367. package/compiled/src/step/step_buffer_provider.d.ts +48 -3
  368. package/compiled/src/step/step_buffer_provider.d.ts.map +1 -1
  369. package/compiled/src/step/step_buffer_provider.js +60 -11
  370. package/compiled/src/step/step_buffer_provider.test.js +14 -0
  371. package/compiled/src/step/step_entity_base.d.ts +65 -3
  372. package/compiled/src/step/step_entity_base.d.ts.map +1 -1
  373. package/compiled/src/step/step_entity_base.js +135 -3
  374. package/compiled/src/step/step_model_base.d.ts +221 -8
  375. package/compiled/src/step/step_model_base.d.ts.map +1 -1
  376. package/compiled/src/step/step_model_base.js +542 -7
  377. package/compiled/src/stream/index.d.ts +34 -0
  378. package/compiled/src/stream/index.d.ts.map +1 -0
  379. package/compiled/src/stream/index.js +32 -0
  380. package/compiled/src/version/version.js +1 -1
  381. package/compiled/tsconfig.tsbuildinfo +1 -1
  382. package/package.json +16 -1
@@ -1,19 +1,32 @@
1
1
  import { BlendMode, } from "../../dependencies/conway-geom/index.js";
2
+ import { Uint32Sink } from "../step/parsing/uint32_sink.js";
3
+ import { StepBufferNotResidentError } from "../step/step_buffer_provider.js";
2
4
  import { exponentToRoughness } from "../core/canonical_material.js";
3
5
  import { CanonicalMeshType } from "../core/canonical_mesh.js";
4
6
  import { yieldToEventLoop } from "../core/progress.js";
5
7
  import { ObjectPool } from "../core/native_pool.js";
6
8
  import { ExtractResult } from "../core/shared_constants.js";
7
- import { IfcArbitraryClosedProfileDef, IfcAxis2Placement2D, IfcAxis2Placement3D, IfcBooleanResult, IfcCartesianPointList2D, IfcCartesianPointList3D, IfcCartesianTransformationOperator3D, IfcCircleProfileDef, IfcColourRgb, IfcCompositeProfileDef, IfcExtrudedAreaSolid, IfcGridPlacement, IfcIndexedPolyCurve, IfcIndexedPolygonalFaceWithVoids, IfcLocalPlacement, IfcMappedItem, IfcOpeningElement, IfcOpeningStandardCase, IfcPolygonalFaceSet, IfcProduct, IfcReflectanceMethodEnum, IfcSpace, IfcSpecularExponent, IfcStyledItem, IfcSurfaceStyle, IfcSurfaceStyleRefraction, IfcSurfaceStyleRendering, IfcSurfaceStyleShading, IfcPresentationStyleAssignment, IfcSurfaceSide, IfcBooleanClippingResult, IfcCompositeCurve, IfcPolyline, IfcTrimmedCurve, IfcCartesianPoint, IfcParameterValue, IfcTrimmingPreference, IfcCircle, IfcHalfSpaceSolid, IfcPlane, IfcBoundingBox, IfcShellBasedSurfaceModel, IfcPolyLoop, IfcFacetedBrep, IfcRelAssociatesMaterial, IfcShapeRepresentation, IfcMaterialList, IfcMaterialDefinitionRepresentation, IfcMaterialLayerSetUsage, IfcMaterial, IfcRelVoidsElement, IfcRectangleProfileDef, IfcMaterialProfileSet, IfcMaterialConstituentSet, IfcMaterialConstituent, IfcMaterialProfile, IfcProject, IfcSIUnit, IfcSIPrefix, IfcUnitEnum, IfcSIUnitName, IfcConversionBasedUnit, IfcFaceBasedSurfaceModel, IfcArbitraryProfileDefWithVoids, IfcAdvancedBrep, IfcBSplineCurve, IfcBSplineSurface, IfcBSplineSurfaceWithKnots, IfcRationalBSplineSurfaceWithKnots, IfcCylindricalSurface, IfcSurfaceOfRevolution, IfcSurfaceOfLinearExtrusion, IfcRationalBSplineCurveWithKnots, IfcArbitraryOpenProfileDef, IfcBSplineCurveWithKnots, IfcEdgeLoop, IfcEdgeCurve, IfcVertexPoint, IfcRectangleHollowProfileDef, IfcEllipseProfileDef, IfcCircleHollowProfileDef, IfcIShapeProfileDef, IfcUShapeProfileDef, IfcDerivedProfileDef, IfcCartesianTransformationOperator2DnonUniform, IfcLShapeProfileDef, IfcCShapeProfileDef, IfcTShapeProfileDef, IfcZShapeProfileDef, IfcRelAggregates, IfcTriangulatedFaceSet, IfcPolygonalBoundedHalfSpace, IfcLine, IfcEllipse, IfcMaterialLayerSet, IfcSweptDiskSolid, IfcBlock, IfcRoundedRectangleProfileDef, IfcMaterialProfileSetUsage, IfcCompositeCurveSegment, IfcRevolvedAreaSolid, IfcFaceSurface, } from "./ifc4_gen/index.js";
9
+ import { IfcArbitraryClosedProfileDef, IfcAxis2Placement2D, IfcAxis2Placement3D, IfcBooleanResult, IfcCartesianPointList2D, IfcCartesianPointList3D, IfcCartesianTransformationOperator3D, IfcCircleProfileDef, IfcColourRgb, IfcCompositeProfileDef, IfcDirection, IfcExtrudedAreaSolid, IfcGrid, IfcGridPlacement, IfcIndexedPolyCurve, IfcIndexedPolygonalFaceWithVoids, IfcLocalPlacement, IfcMappedItem, IfcOpeningElement, IfcOpeningStandardCase, IfcPolygonalFaceSet, IfcProduct, IfcReflectanceMethodEnum, IfcSpace, IfcSpecularExponent, IfcStyledItem, IfcSurfaceStyle, IfcSurfaceStyleRefraction, IfcSurfaceStyleRendering, IfcSurfaceStyleShading, IfcPresentationStyleAssignment, IfcSurfaceSide, IfcBooleanClippingResult, IfcCompositeCurve, IfcPolyline, IfcTrimmedCurve, IfcCartesianPoint, IfcParameterValue, IfcTrimmingPreference, IfcCircle, IfcHalfSpaceSolid, IfcPlane, IfcBoundingBox, IfcShellBasedSurfaceModel, IfcPolyLoop, IfcFacetedBrep, IfcRelAssociatesMaterial, IfcShapeRepresentation, IfcMaterialList, IfcMaterialDefinitionRepresentation, IfcMaterialLayerSetUsage, IfcMaterial, IfcRelVoidsElement, IfcRectangleProfileDef, IfcMaterialProfileSet, IfcMaterialConstituentSet, IfcMaterialConstituent, IfcMaterialProfile, IfcProject, IfcSIUnit, IfcSIPrefix, IfcUnitEnum, IfcSIUnitName, IfcConversionBasedUnit, IfcFaceBasedSurfaceModel, IfcArbitraryProfileDefWithVoids, IfcAdvancedBrep, IfcBSplineCurve, IfcBSplineSurface, IfcBSplineSurfaceWithKnots, IfcRationalBSplineSurfaceWithKnots, IfcCylindricalSurface, IfcSurfaceOfRevolution, IfcSurfaceOfLinearExtrusion, IfcRationalBSplineCurveWithKnots, IfcArbitraryOpenProfileDef, IfcBSplineCurveWithKnots, IfcEdgeLoop, IfcEdgeCurve, IfcVertexPoint, IfcRectangleHollowProfileDef, IfcEllipseProfileDef, IfcCircleHollowProfileDef, IfcIShapeProfileDef, IfcUShapeProfileDef, IfcDerivedProfileDef, IfcCartesianTransformationOperator2DnonUniform, IfcLShapeProfileDef, IfcCShapeProfileDef, IfcTShapeProfileDef, IfcZShapeProfileDef, IfcRelAggregates, IfcTriangulatedFaceSet, IfcPolygonalBoundedHalfSpace, IfcLine, IfcEllipse, IfcMaterialLayerSet, IfcSweptDiskSolid, IfcBlock, IfcRoundedRectangleProfileDef, IfcMaterialProfileSetUsage, IfcCompositeCurveSegment, IfcRevolvedAreaSolid, IfcFaceSurface, IfcVirtualGridIntersection, } from "./ifc4_gen/index.js";
8
10
  import EntityTypesIfc from "./ifc4_gen/entity_types_ifc.gen.js";
9
11
  import { IfcSceneBuilder } from "./ifc_scene_builder.js";
10
12
  import Logger from "../logging/logger.js";
13
+ import { arraysToWasmHeap, arrayToWasmHeap, freeAll, wasmHeapView, withRelease, } from "../core/wasm_heap.js";
11
14
  import { REFLECTANCE_METHOD_PERMISSIVE, MATERIAL_RELATED_OBJECTS_PERMISSIVE } from "./ifc_parser_quirks.js";
12
15
  import { CsgOperationType } from "../core/csg_operations.js";
13
16
  import { MemoizationCapture, RegressionCaptureState } from "../core/regression_capture_state.js";
14
17
  // Await cadence for extractIFCGeometryDataAsync: long enough that yielding
15
18
  // doesn't dominate, short enough that browser paint + watchdogs stay live.
16
19
  const DEFAULT_GEOMETRY_YIELD_INTERVAL_MS = 50;
20
+ // IfcVirtualGridIntersection.IntersectingAxes is LIST [2:2] and
21
+ // OffsetDistances LIST [2:3], the third entry displacing the intersection
22
+ // along the grid placement's z axis.
23
+ const GRID_INTERSECTION_AXIS_COUNT = 2;
24
+ const GRID_INTERSECTION_Z_OFFSET = 2;
25
+ // Sine of the smallest angle two grid axes may cross at and still be treated
26
+ // as intersecting. Grid axes cross at architectural angles, so this only has
27
+ // to separate "crossing" from "drawn along each other" (or from an axis with
28
+ // no length at all).
29
+ const GRID_AXIS_PARALLEL_EPSILON = 1e-9;
17
30
  /**
18
31
  * Extract a specular highlight, converting specular exponents to a roughness value.
19
32
  *
@@ -108,23 +121,44 @@ export function extractColorOrFactorMultiply(from, surfaceColor, alpha = 1) {
108
121
  * Handles Geometry data extraction from a populated IfcStepModel
109
122
  * Can export to OBJ, GLTF (Draco), GLB (Draco)
110
123
  */
124
+ /**
125
+ * Starting capacities for the reused polygonal-faceset sinks: enough that a
126
+ * typical faceset never reallocates, while still growing for outliers.
127
+ */
128
+ /**
129
+ * Vtable coordinates of IfcPolygonalFaceSet.Faces and
130
+ * IfcIndexedPolygonalFace.CoordIndex, mirroring their generated
131
+ * getters. The equivalence test in uint32_sink.test.ts fails loudly if
132
+ * codegen ever moves them.
133
+ */
134
+ const FACES_FIELD_OFFSET = 2;
135
+ const FACES_FIELD_BASE_OFFSET = 1;
136
+ const FACES_FIELD_DEPTH = 4;
137
+ /** IfcRelAggregates.RelatedObjects — see IfcRelAggregates.gen.ts. */
138
+ const REL_AGGREGATES_RELATED_OFFSET = 5;
139
+ const REL_AGGREGATES_RELATED_BASE = 4;
140
+ const REL_AGGREGATES_RELATED_DEPTH = 3;
141
+ /** IfcRelAssociates.RelatedObjects — see IfcRelAssociates.gen.ts. */
142
+ const REL_ASSOCIATES_RELATED_OFFSET = 4;
143
+ const REL_ASSOCIATES_RELATED_BASE = 4;
144
+ const REL_ASSOCIATES_RELATED_DEPTH = 2;
145
+ const COORD_INDEX_FIELD_OFFSET = 0;
146
+ const POLYGONAL_INDEX_SINK_CAPACITY = 65536;
147
+ const POLYGONAL_FACE_SINK_CAPACITY = 16384;
111
148
  export class IfcGeometryExtraction {
112
- /**
113
- * Construct a geometry extraction from an IFC step model and conway model
114
- *
115
- * @param conwayModel The ConwayGeometry instance to use for geometry operations.
116
- * @param model The IfcStepModel instance representing the IFC model.
117
- * @param limitCSGDepth Whether to limit the depth of CSG operations.
118
- * @param csgDepthLimit The maximum depth for CSG operations when limit CSG depth is used,
119
- * or the maximum level for CSG memoization if it is not.
120
- * @param lowMemoryMode Whether to enable low memory mode for geometry extraction.
121
- */
122
149
  constructor(conwayModel, model, limitCSGDepth = true, csgDepthLimit = 20, lowMemoryMode = false) {
123
150
  this.conwayModel = conwayModel;
124
151
  this.model = model;
125
152
  this.limitCSGDepth = limitCSGDepth;
126
153
  this.csgDepthLimit = csgDepthLimit;
127
154
  this.lowMemoryMode = lowMemoryMode;
155
+ /* Reused across polygonal-faceset extractions; see extractPolygonalFaceSet. */
156
+ this.polygonalIndexSink_ = new Uint32Sink(POLYGONAL_INDEX_SINK_CAPACITY);
157
+ this.polygonalStartIndexSink_ = new Uint32Sink(POLYGONAL_FACE_SINK_CAPACITY);
158
+ this.polygonalFaceOffsetSink_ = new Uint32Sink(POLYGONAL_FACE_SINK_CAPACITY);
159
+ this.polygonalStartOffsetSink_ = new Uint32Sink(POLYGONAL_FACE_SINK_CAPACITY);
160
+ /* Guards the sinks against a re-entrant faceset extraction. */
161
+ this.polygonalSinksInUse_ = false;
128
162
  this.TWO_DIMENSIONS = 2;
129
163
  this.THREE_DIMENSIONS = 3;
130
164
  this.circleSegments = 12;
@@ -138,8 +172,28 @@ export class IfcGeometryExtraction {
138
172
  */
139
173
  this.geometryTypeCounts = new Map();
140
174
  this.csgDepth = 0;
175
+ /** When true, a product whose ObjectPlacement — or any record in its
176
+ * placement chain — is a dangling forward reference is treated as not
177
+ * yet extractable (the extraction throws, and the caller skips the
178
+ * product) instead of being silently extracted UNPLACED.
179
+ *
180
+ * Set by the parse-time preview channel's PREFIX extractions. A
181
+ * mid-parse prefix nulls dangling optional references
182
+ * (`nullOnErrors`), so a product whose placement record sits beyond
183
+ * the prefix — Revit writes placements near the end of the file, e.g.
184
+ * Snowdon's first door is record #5014 with its IFCLOCALPLACEMENT at
185
+ * #789283 — read as "no placement" and extracted at the origin. On a
186
+ * georeferenced model the preview then composed
187
+ * coordination x identity, emitting geometry ~425km from the model
188
+ * (Share#1744). The durable extraction runs on the full model, where
189
+ * nothing dangles, and keeps the classic lenient read. */
190
+ this.deferDanglingPlacements = false;
141
191
  this.freeVectorPolygonalFaces_ = [];
192
+ // Guards prepareExtractionMaps_ so the demand path and the whole-model
193
+ // walk can both trigger it without duplicating work.
194
+ this.extractionMapsPrepared_ = false;
142
195
  this.csgMemoization = !this.lowMemoryMode;
196
+ this.quietRecoverableLogging = false;
143
197
  this.materials = model.materials;
144
198
  this.scene = new IfcSceneBuilder(model, conwayModel, this.materials);
145
199
  this.voidMaterials = model.voidMaterials;
@@ -523,20 +577,173 @@ export class IfcGeometryExtraction {
523
577
  this.model.voidGeometry.add(canonicalMesh);
524
578
  }
525
579
  }
580
+ /**
581
+ * Hand a faceset's accumulated indices to wasm and register the
582
+ * resulting mesh. Shared by the fast reference-level path and the
583
+ * generated-getter fallback so they cannot diverge.
584
+ *
585
+ * @param entity The faceset.
586
+ * @param temporary Is this a temporary (preview//operand) extraction?
587
+ * @param isRelVoid Is this geometry a rel-void operand?
588
+ * @param allIndices Face indices, concatenated.
589
+ * @param allStartIndices Per-face start indices.
590
+ * @param polygonalFaceBufferOffsets Per-face offsets into allIndices.
591
+ * @param startIndicesBufferOffsets Per-face offsets into allStartIndices.
592
+ * @return {ExtractResult} The extraction result.
593
+ */
594
+ finishPolygonalFaceSet_(entity, temporary, isRelVoid, allIndices, allStartIndices, polygonalFaceBufferOffsets, startIndicesBufferOffsets) {
595
+ // Add the final entry
596
+ polygonalFaceBufferOffsets.push(allIndices.length);
597
+ startIndicesBufferOffsets.push(allStartIndices.length);
598
+ // Views over exactly the appended elements — no intermediate copy.
599
+ const indicesArray = allIndices.view;
600
+ const startIndicesArray = allStartIndices.view;
601
+ const polygonalFaceBufferOffsetsArray = polygonalFaceBufferOffsets.view;
602
+ const startIndicesBufferOffsetsArray = startIndicesBufferOffsets.view;
603
+ // All four together, so a failed allocation part-way through cannot strand
604
+ // the ones already taken. Everything after this point is inside the
605
+ // try/finally below, which is what actually guarantees they come back.
606
+ const [indicesArrayPtr, startIndicesArrayPtr, polygonalFaceBufferOffsetsArrayPtr, startIndicesBufferOffsetsArrayPtr,] = arraysToWasmHeap(this.wasmModule, [
607
+ indicesArray,
608
+ startIndicesArray,
609
+ polygonalFaceBufferOffsetsArray,
610
+ startIndicesBufferOffsetsArray,
611
+ ]);
612
+ // A throw here used to strand all four heap pointers plus the pooled
613
+ // parse buffer, once per faceset (15,777 on PSB — a leak large enough
614
+ // to cause the exhaustion it would then be blamed on). withRelease
615
+ // reclaims them; it suppresses a release failure only while something
616
+ // is already unwinding, so a wasm-runtime fault is not replaced by a
617
+ // generic teardown error in the per-record catch upstream.
618
+ const geometry = withRelease(() => {
619
+ const pointsParseBuffer = this.conwayModel.nativeParseBuffer();
620
+ // Packed triangulation: the four heap buffers are already the
621
+ // face index layout. Unpacking them into 9.1M IndexedPolygonalFace
622
+ // std::vectors was the rest of the PSB pump after #448, and is
623
+ // why feeding the ThreadPool lost (the pool was paying to copy
624
+ // those vectors).
625
+ const pointsArrayNative = withRelease(() => {
626
+ if (!entity.Coordinates.extractParseBuffer(0, 0, 0, pointsParseBuffer, this.wasmModule, true)) {
627
+ pointsParseBuffer.resize(0);
628
+ }
629
+ return this.wasmModule.parseVertexVector(pointsParseBuffer);
630
+ }, () => this.conwayModel.freeParseBuffer(pointsParseBuffer));
631
+ return withRelease(() => this.conwayModel.getPolygonalFaceSetGeometryPacked(pointsArrayNative, indicesArrayPtr, polygonalFaceBufferOffsetsArrayPtr, polygonalFaceBufferOffsetsArray.length, startIndicesArrayPtr, startIndicesBufferOffsetsArrayPtr), () => pointsArrayNative.delete());
632
+ },
633
+ // freeAll, not four statements in one closure: there the first
634
+ // failure would abandon the other three.
635
+ () => freeAll(this.wasmModule, [
636
+ indicesArrayPtr,
637
+ startIndicesArrayPtr,
638
+ polygonalFaceBufferOffsetsArrayPtr,
639
+ startIndicesBufferOffsetsArrayPtr,
640
+ ]));
641
+ const canonicalMesh = {
642
+ type: CanonicalMeshType.BUFFER_GEOMETRY,
643
+ geometry: geometry,
644
+ localID: entity.localID,
645
+ model: this.model,
646
+ temporary: temporary,
647
+ };
648
+ if (!isRelVoid) {
649
+ this.model.geometry.add(canonicalMesh);
650
+ }
651
+ else {
652
+ this.model.voidGeometry.add(canonicalMesh);
653
+ }
654
+ return ExtractResult.COMPLETE;
655
+ }
656
+ /**
657
+ * Extract a polygonal faceset.
658
+ *
659
+ * The index sinks are extractor-scoped, so a nested faceset extraction
660
+ * reached from inside this one would silently clobber them. Nothing
661
+ * recurses there today, so this guards the invariant rather than a
662
+ * live bug — but the release must survive a throw: per-record
663
+ * extraction errors are caught upstream and are expected on malformed
664
+ * models, and a leaked flag would turn one bad record into a failure
665
+ * for every remaining faceset.
666
+ *
667
+ * @param entity The faceset.
668
+ * @param temporary Is this a temporary (preview/operand) extraction?
669
+ * @param isRelVoid Is this geometry a rel-void operand?
670
+ * @return {ExtractResult} The extraction result.
671
+ */
672
+ extractPolygonalFaceSet(entity, temporary = false, isRelVoid = false) {
673
+ if (this.polygonalSinksInUse_) {
674
+ throw new Error("extractPolygonalFaceSet re-entered: the shared index sinks are already in use");
675
+ }
676
+ this.polygonalSinksInUse_ = true;
677
+ try {
678
+ return this.extractPolygonalFaceSetGuarded_(entity, temporary, isRelVoid);
679
+ }
680
+ finally {
681
+ this.polygonalSinksInUse_ = false;
682
+ }
683
+ }
526
684
  /**
527
685
  * @param entity
528
686
  * @param temporary
529
687
  * @param isRelVoid
530
688
  * @return {ExtractResult}
531
689
  */
532
- extractPolygonalFaceSet(entity, temporary = false, isRelVoid = false) {
690
+ extractPolygonalFaceSetGuarded_(entity, temporary, isRelVoid) {
533
691
  const result = ExtractResult.COMPLETE;
534
- // Temporary storage for indices and start indices
535
- const allIndices = [];
536
- const allStartIndices = [];
537
- const polygonalFaceBufferOffsets = [];
538
- const startIndicesBufferOffsets = [];
539
- let indicesPerFace = -1;
692
+ // Reusable typed sinks. A tessellated model can carry millions of
693
+ // IfcIndexedPolygonalFace records, and the boxed `Array< number >`
694
+ // this loop used to build per face — which the generated CoordIndex
695
+ // getter also cached on the face, keeping it alive — dominated both
696
+ // GC time and retained heap during extraction. Indices are parsed
697
+ // straight from the STEP buffer into these instead, reused across
698
+ // facesets.
699
+ const allIndices = this.polygonalIndexSink_;
700
+ const allStartIndices = this.polygonalStartIndexSink_;
701
+ const polygonalFaceBufferOffsets = this.polygonalFaceOffsetSink_;
702
+ const startIndicesBufferOffsets = this.polygonalStartOffsetSink_;
703
+ allIndices.reset();
704
+ allStartIndices.reset();
705
+ polygonalFaceBufferOffsets.reset();
706
+ startIndicesBufferOffsets.reset();
707
+ // Fast path: read each face's CoordIndex straight from its record,
708
+ // never constructing the 9M+ IfcIndexedPolygonalFace entities a
709
+ // large tessellated model would otherwise materialise (and retain,
710
+ // since the generated Faces getter caches them). It handles only
711
+ // plain, single-class IFCINDEXEDPOLYGONALFACE records; anything
712
+ // else (voided faces, inline elements, multi-mapped records)
713
+ // abandons the whole faceset back to the getter path below, so
714
+ // correctness never depends on the fast path's coverage.
715
+ let fastPathOk = true;
716
+ let hintLocalID;
717
+ const facesWalked = entity.forEachReferenceInField(FACES_FIELD_OFFSET, FACES_FIELD_BASE_OFFSET, FACES_FIELD_DEPTH, (expressID) => {
718
+ if (expressID === void 0) {
719
+ fastPathOk = false;
720
+ return false;
721
+ }
722
+ const localID = this.model.resolveExpressID(expressID, hintLocalID);
723
+ if (localID === void 0) {
724
+ fastPathOk = false;
725
+ return false;
726
+ }
727
+ const count = this.model.extractIntegerArrayByLocalIDInto(localID, COORD_INDEX_FIELD_OFFSET, EntityTypesIfc.IFCINDEXEDPOLYGONALFACE, allIndices);
728
+ if (count === void 0) {
729
+ fastPathOk = false;
730
+ return false;
731
+ }
732
+ hintLocalID = localID;
733
+ polygonalFaceBufferOffsets.push(allIndices.length - count);
734
+ startIndicesBufferOffsets.push(allStartIndices.length);
735
+ allStartIndices.push(0);
736
+ return true;
737
+ });
738
+ if (fastPathOk && facesWalked) {
739
+ this.finishPolygonalFaceSet_(entity, temporary, isRelVoid, allIndices, allStartIndices, polygonalFaceBufferOffsets, startIndicesBufferOffsets);
740
+ return result;
741
+ }
742
+ // Fallback: something in this faceset needs the generated getters.
743
+ allIndices.reset();
744
+ allStartIndices.reset();
745
+ polygonalFaceBufferOffsets.reset();
746
+ startIndicesBufferOffsets.reset();
540
747
  // Prepare indices and start indices for all faces
541
748
  entity.Faces.forEach((polygonalFace) => {
542
749
  // set the offets for the memory buffers so we can rebuild the
@@ -545,68 +752,29 @@ export class IfcGeometryExtraction {
545
752
  startIndicesBufferOffsets.push(allStartIndices.length);
546
753
  let coordIndex = 0;
547
754
  if (polygonalFace instanceof IfcIndexedPolygonalFaceWithVoids) {
548
- indicesPerFace = polygonalFace.CoordIndex.length;
755
+ // Voided faces are rare; keep the straightforward getter path.
756
+ const indicesPerFace = polygonalFace.CoordIndex.length;
549
757
  allStartIndices.push(0);
550
758
  coordIndex += indicesPerFace;
551
- allIndices.push(...polygonalFace.CoordIndex);
759
+ for (const index of polygonalFace.CoordIndex) {
760
+ allIndices.push(index);
761
+ }
552
762
  polygonalFace.InnerCoordIndices.forEach((innerIndices) => {
553
763
  allStartIndices.push(coordIndex);
554
- allIndices.push(...innerIndices);
764
+ for (const index of innerIndices) {
765
+ allIndices.push(index);
766
+ }
555
767
  coordIndex += innerIndices.length;
556
768
  });
557
769
  }
558
770
  else {
559
- indicesPerFace = polygonalFace.CoordIndex.length;
771
+ // CoordIndex is vtable offset 0 on IfcIndexedPolygonalFace (see
772
+ // its generated getter) — parsed in place, never allocated or cached.
773
+ polygonalFace.extractIntegerArrayInto(0, 0, 3, allIndices);
560
774
  allStartIndices.push(0);
561
- allIndices.push(...polygonalFace.CoordIndex);
562
775
  }
563
776
  });
564
- // Add the final entry
565
- polygonalFaceBufferOffsets.push(allIndices.length);
566
- startIndicesBufferOffsets.push(allStartIndices.length);
567
- // Convert to typed arrays for transfer to WebAssembly
568
- const indicesArray = new Uint32Array(allIndices);
569
- const indicesArrayPtr = this.arrayToWasmHeap(indicesArray);
570
- const startIndicesArray = new Uint32Array(allStartIndices);
571
- const startIndicesArrayPtr = this.arrayToWasmHeap(startIndicesArray);
572
- const polygonalFaceBufferOffsetsArray = new Uint32Array(polygonalFaceBufferOffsets);
573
- const polygonalFaceBufferOffsetsArrayPtr = this.arrayToWasmHeap(polygonalFaceBufferOffsetsArray);
574
- const startIndicesBufferOffsetsArray = new Uint32Array(startIndicesBufferOffsets);
575
- const startIndicesBufferOffsetsArrayPtr = this.arrayToWasmHeap(startIndicesBufferOffsetsArray);
576
- const polygonalFaceVector = this.wasmModule.buildIndexedPolygonalFaceVector(indicesArrayPtr, indicesArray.length, startIndicesArrayPtr, polygonalFaceBufferOffsetsArrayPtr, polygonalFaceBufferOffsets.length, startIndicesBufferOffsetsArrayPtr, startIndicesBufferOffsets.length);
577
- const pointsParseBuffer = this.conwayModel.nativeParseBuffer();
578
- if (!entity.Coordinates.extractParseBuffer(0, 0, 0, pointsParseBuffer, this.wasmModule, true)) {
579
- pointsParseBuffer.resize(0);
580
- }
581
- const pointsArrayNative = this.wasmModule.parseVertexVector(pointsParseBuffer);
582
- this.conwayModel.freeParseBuffer(pointsParseBuffer);
583
- const parameters = {
584
- indicesPerFace: indicesPerFace,
585
- points: pointsArrayNative,
586
- faces: polygonalFaceVector,
587
- };
588
- const geometry = this.conwayModel.getPolygonalFaceSetGeometry(parameters);
589
- // free allocated wasm vectors
590
- pointsArrayNative.delete();
591
- this.wasmModule._free(indicesArrayPtr);
592
- this.wasmModule._free(startIndicesArrayPtr);
593
- this.wasmModule._free(polygonalFaceBufferOffsetsArrayPtr);
594
- this.wasmModule._free(startIndicesBufferOffsetsArrayPtr);
595
- polygonalFaceVector.delete();
596
- const canonicalMesh = {
597
- type: CanonicalMeshType.BUFFER_GEOMETRY,
598
- geometry: geometry,
599
- localID: entity.localID,
600
- model: this.model,
601
- temporary: temporary,
602
- };
603
- // add mesh to the list of mesh objects
604
- if (!isRelVoid) {
605
- this.model.geometry.add(canonicalMesh);
606
- }
607
- else {
608
- this.model.voidGeometry.add(canonicalMesh);
609
- }
777
+ this.finishPolygonalFaceSet_(entity, temporary, isRelVoid, allIndices, allStartIndices, polygonalFaceBufferOffsets, startIndicesBufferOffsets);
610
778
  return result;
611
779
  }
612
780
  /**
@@ -614,14 +782,7 @@ export class IfcGeometryExtraction {
614
782
  * @return {number} Pointer/memory address
615
783
  */
616
784
  arrayToWasmHeap(array) {
617
- // Allocate memory for the array within the Wasm module
618
- const bytesPerElement = array.BYTES_PER_ELEMENT;
619
- const numBytes = array.length * bytesPerElement;
620
- const arrayPtr = this.wasmModule._malloc(numBytes);
621
- // Create a new Uint8Array view on the Wasm memory buffer, then set the array to it
622
- const arrayWasm = new Uint8Array(this.wasmModule.HEAPU8.buffer, arrayPtr, numBytes);
623
- arrayWasm.set(new Uint8Array(array.buffer));
624
- return arrayPtr;
785
+ return arrayToWasmHeap(this.wasmModule, array);
625
786
  }
626
787
  /**
627
788
  * @param array
@@ -2646,9 +2807,13 @@ export class IfcGeometryExtraction {
2646
2807
  pointer = this.wasmModule._malloc(numBytes);
2647
2808
  capacity = maxPossibleFloats;
2648
2809
  }
2649
- // 2) Create a Float64Array view into WASM memory
2650
- // We only need to create a subarray up to the capacity
2651
- const wasmFloat64View = this.wasmModule.HEAPF64.subarray(pointer / bytesPerElement, pointer / bytesPerElement + capacity);
2810
+ // 2) Create a Float64Array view into WASM memory, over the heap as it is
2811
+ // after the _malloc above rather than over whatever view the module had
2812
+ // cached before it - see wasm_heap.ts for why those can differ. Built
2813
+ // rather than subarray'd off HEAPF64 for the same reason: subarray clamps
2814
+ // an out-of-range window into a short one, and the set() loop below would
2815
+ // then quietly write somewhere other than the allocation (#485).
2816
+ const wasmFloat64View = wasmHeapView(this.wasmModule, Float64Array, pointer, capacity);
2652
2817
  // 3) Single pass to skip consecutive duplicates, fill up the wasm array
2653
2818
  let offset = 0;
2654
2819
  let prevLocalID = -1;
@@ -3893,6 +4058,341 @@ export class IfcGeometryExtraction {
3893
4058
  .getAxis2Placement3D(axis2Placement3DParameters);
3894
4059
  this.voidScene.addTransform(parentLocalId, axis2PlacementTransform.getValues(), axis2PlacementTransform);
3895
4060
  }
4061
+ /**
4062
+ * Resolve and extract a product's placement with dangling references
4063
+ * treated as errors (deferDanglingPlacements mode).
4064
+ *
4065
+ * The model's `nullOnErrors` (default true) turns a reference to an
4066
+ * unindexed record into a silent null, which is indistinguishable from
4067
+ * a genuinely absent (`$`) optional attribute. On a mid-parse prefix
4068
+ * that conflation is exactly wrong: "placement record not indexed
4069
+ * yet" must defer the product, while "product has no placement" may
4070
+ * extract unplaced, matching the classic path. Flipping
4071
+ * `nullOnErrors` off for the duration makes the step layer throw on
4072
+ * the dangling read (`extractElement`), anywhere in the chain —
4073
+ * IFCLOCALPLACEMENT ancestors and their axis/point records included,
4074
+ * since `extractPlacement` resolves them through the same layer.
4075
+ *
4076
+ * The throw intentionally propagates: the demand seam
4077
+ * (`extractProductGeometryByLocalID` callers, the preview adapter's
4078
+ * per-unit catch) already treats a throwing product as
4079
+ * not-yet-extractable and leaves it to the durable pump — the IFC
4080
+ * channel's forward-only unit cursor does not re-run passed
4081
+ * ordinals. Nothing is memoized on the throwing path — the generated
4082
+ * getters only cache on successful extraction — so an adapter mode
4083
+ * that does retry (AP214's retryEmptyUnits, or a future IFC
4084
+ * equivalent) re-reads cleanly against its longer prefix.
4085
+ *
4086
+ * @param product The product whose placement to resolve.
4087
+ */
4088
+ extractPlacementStrict_(product) {
4089
+ const model = this.model;
4090
+ const priorNullOnErrors = model.nullOnErrors;
4091
+ model.nullOnErrors = false;
4092
+ try {
4093
+ const objectPlacement = product.ObjectPlacement;
4094
+ if (objectPlacement !== null) {
4095
+ this.extractPlacement(objectPlacement);
4096
+ }
4097
+ }
4098
+ finally {
4099
+ model.nullOnErrors = priorNullOnErrors;
4100
+ }
4101
+ }
4102
+ /**
4103
+ * Reduce a grid axis to an unbounded line in the plane of the grid's
4104
+ * placement: a point on it, and its tangent there.
4105
+ *
4106
+ * Only the straight axis curves are handled — an IfcLine, or the single
4107
+ * segment of a two-point IfcPolyline — which is what grid axes are in
4108
+ * practice. Anything else returns undefined after warning, so the
4109
+ * placement lands in errors.csv rather than being dropped in silence.
4110
+ *
4111
+ * `SameSense` is folded into the tangent because everything downstream is
4112
+ * measured from it: the offset in IfcVirtualGridIntersection is normal to
4113
+ * the tangent, and the tangent is also an IfcGridPlacement's default
4114
+ * x-axis, so a reversed axis offsets to the other side and points the
4115
+ * other way.
4116
+ *
4117
+ * @param axis The grid axis to reduce.
4118
+ * @return {{x, y, dx, dy} | undefined} A point and (unnormalised) tangent
4119
+ * in the grid's coordinate system, or undefined for an axis curve this
4120
+ * does not handle.
4121
+ */
4122
+ static gridAxisLine(axis) {
4123
+ const curve = axis.AxisCurve;
4124
+ let x;
4125
+ let y;
4126
+ let dx;
4127
+ let dy;
4128
+ if (curve instanceof IfcPolyline) {
4129
+ const points = curve.Points;
4130
+ if (points.length !== 2) {
4131
+ // A multi-segment axis needs the segment nearest the intersection
4132
+ // picked per axis pair, which this does not do.
4133
+ Logger.warning("IfcGridPlacement: grid axis IfcPolyline is not a single segment.", axis.expressID);
4134
+ return void 0;
4135
+ }
4136
+ const start = points[0].Coordinates;
4137
+ const end = points[1].Coordinates;
4138
+ x = start[0];
4139
+ y = start[1];
4140
+ dx = end[0] - start[0];
4141
+ dy = end[1] - start[1];
4142
+ }
4143
+ else if (curve instanceof IfcLine) {
4144
+ const origin = curve.Pnt.Coordinates;
4145
+ const direction = curve.Dir.Orientation.DirectionRatios;
4146
+ // IfcVector.Magnitude scales the direction; the axis is unbounded
4147
+ // here, so it changes neither the line nor the normalised tangent.
4148
+ x = origin[0];
4149
+ y = origin[1];
4150
+ dx = direction[0];
4151
+ dy = direction[1];
4152
+ }
4153
+ else {
4154
+ Logger.warning(`IfcGridPlacement: unsupported grid axis curve, type: ${EntityTypesIfc[curve.type]}`, axis.expressID);
4155
+ return void 0;
4156
+ }
4157
+ if (!axis.SameSense) {
4158
+ dx = -dx;
4159
+ dy = -dy;
4160
+ }
4161
+ return { x: x, y: y, dx: dx, dy: dy };
4162
+ }
4163
+ /**
4164
+ * Resolve an IfcVirtualGridIntersection to a point in the coordinate system
4165
+ * of the IfcGrid that owns its axes, along with the tangent of its first
4166
+ * axis — which is the default x-axis of an IfcGridPlacement located here.
4167
+ *
4168
+ * Per IFC4 each axis is replaced by its offset curve before intersecting:
4169
+ * OffsetDistances[n] is measured from axis n normal to its tangent,
4170
+ * positive towards the tangent turned a quarter turn anti-clockwise. Both
4171
+ * axes here are straight, so their offset curves are parallel to them and
4172
+ * the tangents are unchanged. A third offset, when the file gives one,
4173
+ * displaces the result along the grid placement's z axis.
4174
+ *
4175
+ * @param from The intersection to resolve.
4176
+ * @return {{position, tangent} | undefined} The intersection point and the
4177
+ * first axis' unit tangent, or undefined when the axes cannot be reduced to
4178
+ * two lines that meet (warned at the point of failure).
4179
+ */
4180
+ resolveVirtualGridIntersection(from) {
4181
+ const axes = from.IntersectingAxes;
4182
+ if (axes.length < GRID_INTERSECTION_AXIS_COUNT) {
4183
+ // LIST [2:2] in the schema, so this is a malformed or absent list.
4184
+ Logger.warning("IfcGridPlacement: virtual grid intersection has fewer than two axes.", from.expressID);
4185
+ return void 0;
4186
+ }
4187
+ const line0 = IfcGeometryExtraction.gridAxisLine(axes[0]);
4188
+ const line1 = IfcGeometryExtraction.gridAxisLine(axes[1]);
4189
+ if (line0 === void 0 || line1 === void 0) {
4190
+ return void 0;
4191
+ }
4192
+ const length0 = Math.sqrt(line0.dx * line0.dx + line0.dy * line0.dy);
4193
+ const length1 = Math.sqrt(line1.dx * line1.dx + line1.dy * line1.dy);
4194
+ const cross = line0.dx * line1.dy - line0.dy * line1.dx;
4195
+ // |cross| / (length0 * length1) is the sine of the angle between the
4196
+ // axes, so the test is independent of the model's units. A zero-length
4197
+ // tangent (a polyline whose two points coincide) also lands here,
4198
+ // before it can divide its way into a NaN below.
4199
+ if (Math.abs(cross) <= GRID_AXIS_PARALLEL_EPSILON * length0 * length1) {
4200
+ Logger.warning("IfcGridPlacement: intersecting grid axes are parallel or degenerate.", from.expressID);
4201
+ return void 0;
4202
+ }
4203
+ const offsets = from.OffsetDistances;
4204
+ const offset0 = offsets[0] ?? 0;
4205
+ const offset1 = offsets[1] ?? 0;
4206
+ // Offset curve origins: the axis point pushed along the tangent's
4207
+ // anti-clockwise normal, (-dy, dx) normalised.
4208
+ const x0 = line0.x - (offset0 * line0.dy) / length0;
4209
+ const y0 = line0.y + (offset0 * line0.dx) / length0;
4210
+ const x1 = line1.x - (offset1 * line1.dy) / length1;
4211
+ const y1 = line1.y + (offset1 * line1.dx) / length1;
4212
+ // Parameter along the first offset curve where the two meet, from
4213
+ // crossing (p1 - p0) + t * d0 = s * d1 with d1.
4214
+ const t = ((x1 - x0) * line1.dy - (y1 - y0) * line1.dx) / cross;
4215
+ return {
4216
+ position: {
4217
+ x: x0 + t * line0.dx,
4218
+ y: y0 + t * line0.dy,
4219
+ z: offsets[GRID_INTERSECTION_Z_OFFSET] ?? 0,
4220
+ },
4221
+ tangent: { x: line0.dx / length0, y: line0.dy / length0 },
4222
+ };
4223
+ }
4224
+ /**
4225
+ * The IfcGrid that lists a given axis, or undefined if no grid does.
4226
+ *
4227
+ * IfcGridAxis's route back to its grid is the PartOfU/PartOfV/PartOfW
4228
+ * inverse attributes, and the generated schema layer carries no inverses,
4229
+ * so this is a scan of every IfcGrid's axis lists, memoised. It is built on
4230
+ * the first grid placement extracted and never at all for a model without
4231
+ * one.
4232
+ *
4233
+ * @param axis The axis to look up.
4234
+ * @return {IfcGrid | undefined} The grid listing the axis.
4235
+ */
4236
+ gridByAxis(axis) {
4237
+ let gridByAxis = this.gridByAxis_;
4238
+ if (gridByAxis === void 0) {
4239
+ gridByAxis = new Map();
4240
+ for (const grid of this.model.types(IfcGrid)) {
4241
+ for (const gridAxis of grid.UAxes) {
4242
+ gridByAxis.set(gridAxis.localID, grid);
4243
+ }
4244
+ for (const gridAxis of grid.VAxes) {
4245
+ gridByAxis.set(gridAxis.localID, grid);
4246
+ }
4247
+ for (const gridAxis of grid.WAxes ?? []) {
4248
+ gridByAxis.set(gridAxis.localID, grid);
4249
+ }
4250
+ }
4251
+ this.gridByAxis_ = gridByAxis;
4252
+ }
4253
+ return gridByAxis.get(axis.localID);
4254
+ }
4255
+ /**
4256
+ * Extract an IfcGridPlacement, registering a transform against its local ID
4257
+ * the same way the IfcLocalPlacement arm does — same scene, same
4258
+ * memoisation, same composition onto the current parent.
4259
+ *
4260
+ * The origin is the virtual intersection of two grid axes and the x-axis
4261
+ * comes from PlacementRefDirection (defaulting to the first axis' tangent),
4262
+ * both expressed in the coordinate system of the IfcGrid that owns the
4263
+ * axes. IfcGridPlacement has no PlacementRelTo of its own in IFC4, so that
4264
+ * grid's object placement IS the parent: extracting it first leaves it as
4265
+ * the scene's current transform, which `addTransform` then composes onto.
4266
+ *
4267
+ * @param from The grid placement to extract.
4268
+ * @param isRelVoid Whether this placement is being resolved into the void
4269
+ * scene rather than the main one.
4270
+ */
4271
+ extractGridPlacement(from, isRelVoid) {
4272
+ const placementLocation = from.PlacementLocation;
4273
+ const location = this.resolveVirtualGridIntersection(placementLocation);
4274
+ if (location === void 0) {
4275
+ // Warned where it failed, with the axis or curve responsible.
4276
+ return;
4277
+ }
4278
+ let xAxisRef = { x: location.tangent.x, y: location.tangent.y, z: 0 };
4279
+ const refDirection = from.PlacementRefDirection;
4280
+ if (refDirection instanceof IfcDirection) {
4281
+ const ratios = refDirection.DirectionRatios;
4282
+ xAxisRef = { x: ratios[0], y: ratios[1], z: ratios[2] ?? 0 };
4283
+ }
4284
+ else if (refDirection instanceof IfcVirtualGridIntersection) {
4285
+ // The x-axis runs from this placement's intersection to the reference
4286
+ // one. If the reference cannot be resolved the resolver has already
4287
+ // said why, and the schema's own default — the first axis' tangent —
4288
+ // stands in.
4289
+ const reference = this.resolveVirtualGridIntersection(refDirection);
4290
+ if (reference !== void 0) {
4291
+ xAxisRef = {
4292
+ x: reference.position.x - location.position.x,
4293
+ y: reference.position.y - location.position.y,
4294
+ z: reference.position.z - location.position.z,
4295
+ };
4296
+ }
4297
+ }
4298
+ // getAxis2Placement3D below is called with a forced z of (0,0,1) and
4299
+ // normalizeX, and the native side (ConwayGeometryProcessor's
4300
+ // getAxis2Placement3D) has no degeneracy guard of its own: an xAxisRef
4301
+ // with nothing off that z has no component left after the projection,
4302
+ // normalises to NaN, and addTransform then bakes the NaN into this
4303
+ // placement and every descendant of it. Measured on data/grid_placement.ifc
4304
+ // product E, whose x axis came out (NaN,NaN,NaN) before this guard.
4305
+ //
4306
+ // Two schema-reachable states produce it, and because z is exactly +Z both
4307
+ // reduce to one test - the xy magnitude vanishing relative to the vector's
4308
+ // own, in the same units-independent form as the axis-parallel check in
4309
+ // resolveVirtualGridIntersection (a zero vector fails it too, 0 <= 0):
4310
+ //
4311
+ // - PlacementRefDirection is an IfcVirtualGridIntersection resolving to
4312
+ // the same point as PlacementLocation, so the difference above is the
4313
+ // zero vector;
4314
+ // - PlacementRefDirection is IFCDIRECTION((0.,0.,1.)), parallel to the
4315
+ // forced z.
4316
+ //
4317
+ // The fallback is the schema's own default for an absent
4318
+ // PlacementRefDirection, and it is already in hand and already known
4319
+ // non-degenerate: resolveVirtualGridIntersection returns the first axis'
4320
+ // tangent normalised, and only after rejecting a zero-length one.
4321
+ const xAxisRefPlanar = Math.sqrt(xAxisRef.x * xAxisRef.x + xAxisRef.y * xAxisRef.y);
4322
+ const xAxisRefLength = Math.sqrt(xAxisRefPlanar * xAxisRefPlanar + xAxisRef.z * xAxisRef.z);
4323
+ if (xAxisRefPlanar <= GRID_AXIS_PARALLEL_EPSILON * xAxisRefLength) {
4324
+ Logger.warning("IfcGridPlacement: the placement reference direction is parallel " +
4325
+ "to the grid placement's z axis, or has no length - falling back " +
4326
+ "to the schema default, the first axis' tangent.", from.expressID);
4327
+ xAxisRef = { x: location.tangent.x, y: location.tangent.y, z: 0 };
4328
+ }
4329
+ // Two guarded regions rather than one, because the two reads degrade to
4330
+ // DIFFERENT states and a single catch could only describe one of them
4331
+ // truthfully. Neither may fail the product: dropping it entirely is
4332
+ // strictly worse than the pre-implementation rendered-at-origin.
4333
+ //
4334
+ // Region 1 - the gridByAxis scan. It reads IfcGrid records, which are
4335
+ // outside the forward-reference closure a windowed source pages for a
4336
+ // product (the grid references the axes, not vice versa - see
4337
+ // ensureResidentForProductExtract), so this is the read most likely to
4338
+ // miss. Nothing has been applied when it throws, so the intersection
4339
+ // lands in the current frame - the same degraded state the no-grid arm
4340
+ // gives. gridByAxis memoises only a COMPLETE scan, so a later product on
4341
+ // a fully resident model rebuilds rather than trusting a partial index.
4342
+ let grid;
4343
+ let gridScanned = false;
4344
+ try {
4345
+ grid = this.gridByAxis(placementLocation.IntersectingAxes[0]);
4346
+ gridScanned = true;
4347
+ }
4348
+ catch (error) {
4349
+ if (!(error instanceof StepBufferNotResidentError)) {
4350
+ throw error;
4351
+ }
4352
+ Logger.warning("IfcGridPlacement: grid records are not resident on this " +
4353
+ "windowed source - placing at the grid intersection in the " +
4354
+ "current frame, without the grid's own placement.", from.expressID);
4355
+ }
4356
+ if (gridScanned && grid === void 0) {
4357
+ // Axes belonging to no grid: the intersection is still a point, but
4358
+ // there is no grid placement to measure it from, so it lands in
4359
+ // whatever frame is current.
4360
+ Logger.warning("IfcGridPlacement: no IfcGrid lists the axes of this placement.", from.expressID);
4361
+ }
4362
+ else if (grid !== void 0) {
4363
+ // Region 2 - the grid's own placement chain. extractPlacement walks
4364
+ // PlacementRelTo upwards and pushes a transform per level as it
4365
+ // returns, so a residency miss PART WAY UP leaves some ancestors
4366
+ // already applied. Region 1's message would be a lie about this state
4367
+ // (it says the grid's placement was not applied at all), and the frame
4368
+ // this placement then composes onto is partial rather than current.
4369
+ try {
4370
+ const gridPlacement = grid.ObjectPlacement;
4371
+ if (gridPlacement !== null) {
4372
+ this.extractPlacement(gridPlacement, isRelVoid);
4373
+ }
4374
+ }
4375
+ catch (error) {
4376
+ if (!(error instanceof StepBufferNotResidentError)) {
4377
+ throw error;
4378
+ }
4379
+ Logger.warning("IfcGridPlacement: the grid's own placement chain is not fully " +
4380
+ "resident on this windowed source - it was applied as far as it " +
4381
+ "reads, so this placement composes onto a partial frame.", from.expressID);
4382
+ }
4383
+ }
4384
+ const placementTransform = this.conwayModel.getAxis2Placement3D({
4385
+ position: location.position,
4386
+ // A grid is planar in its own placement, so this placement's z axis is
4387
+ // the grid's z axis; only the x axis is steered here.
4388
+ zAxisRef: { x: 0, y: 0, z: 1 },
4389
+ xAxisRef: xAxisRef,
4390
+ normalizeZ: false,
4391
+ normalizeX: true,
4392
+ });
4393
+ const scene = isRelVoid ? this.voidScene : this.scene;
4394
+ scene.addTransform(from.localID, placementTransform.getValues(), placementTransform);
4395
+ }
3896
4396
  /**
3897
4397
  *
3898
4398
  * @param from
@@ -3931,8 +4431,7 @@ export class IfcGeometryExtraction {
3931
4431
  }
3932
4432
  }
3933
4433
  else if (from instanceof IfcGridPlacement) {
3934
- // TODO(nickcastel50) Implement IfcGridPlacement
3935
- // Logger.warning('IfcGridPlacement: unimplemented.')
4434
+ this.extractGridPlacement(from, isRelVoid);
3936
4435
  }
3937
4436
  }
3938
4437
  /**
@@ -4211,14 +4710,53 @@ export class IfcGeometryExtraction {
4211
4710
  }
4212
4711
  return styledItemID;
4213
4712
  }
4713
+ /**
4714
+ * Handle an error raised while building the cross-product extraction
4715
+ * maps. Each map sweep reads references off relationship entities
4716
+ * (materials, styled items, rel-voids); a single malformed reference in
4717
+ * a large real-world model (STEP where an entity reference is expected)
4718
+ * must not abort the whole model's extraction. Both the whole-model walk
4719
+ * and the demand path depend on map prep being tolerant here — the
4720
+ * browser demand pump swallows per-batch extraction throws, so a model
4721
+ * that renders in prod would otherwise hard-fail the headless regression
4722
+ * batch. Honours the strict-mode escape hatch so a strict run still
4723
+ * surfaces the error.
4724
+ *
4725
+ * @param context Short label for the relationship being processed.
4726
+ * @param expressID The offending record's express ID (may be undefined).
4727
+ * @param ex The caught value.
4728
+ */
4729
+ handleMapPrepError_(context, expressID, ex) {
4730
+ if (ex instanceof Error) {
4731
+ if (MATERIAL_RELATED_OBJECTS_PERMISSIVE) {
4732
+ Logger.error(`Error processing ${context} expressID: ${expressID}, error: ${ex.message}`);
4733
+ }
4734
+ else {
4735
+ throw ex;
4736
+ }
4737
+ }
4738
+ else {
4739
+ Logger.error(`Unknown exception processing ${context}.`);
4740
+ }
4741
+ }
4214
4742
  /**
4215
4743
  *
4216
4744
  */
4217
4745
  populateStyledItemsMap() {
4218
4746
  const styledItems = this.model.types(IfcStyledItem);
4219
4747
  for (const styledItem of styledItems) {
4220
- if (styledItem.Item !== null) {
4221
- this.materials.styledItemMap.set(styledItem.Item.localID, styledItem.localID);
4748
+ try {
4749
+ // IfcStyledItem.Item — same vtable slot as the generated
4750
+ // getter's extractElement(0, 0, 1, true, ...). Hydrating Item
4751
+ // would acquire the faceset record (often the packed geometry
4752
+ // itself) for every styled item at once.
4753
+ const itemLocalID = styledItem.extractReferenceLocalID(0, 0, 1, true);
4754
+ if (itemLocalID !== null) {
4755
+ this.materials.styledItemMap.set(itemLocalID, styledItem.localID);
4756
+ }
4757
+ }
4758
+ catch (ex) {
4759
+ this.handleMapPrepError_("IfcStyledItem", styledItem.expressID, ex);
4222
4760
  }
4223
4761
  }
4224
4762
  }
@@ -4229,12 +4767,17 @@ export class IfcGeometryExtraction {
4229
4767
  // populate MaterialDefinitionsMap
4230
4768
  const materialDefinitionRepresentations = this.model.types(IfcMaterialDefinitionRepresentation);
4231
4769
  for (const materialDefinitionRep of materialDefinitionRepresentations) {
4232
- for (const representation of materialDefinitionRep.Representations) {
4233
- for (let itemIndex = 0; itemIndex < representation.Items.length; ++itemIndex) {
4234
- // save mapping of IfcMaterial --> IfcStyledItem
4235
- this.materials.materialDefinitionsMap.set(materialDefinitionRep.RepresentedMaterial.localID, representation.Items[itemIndex].localID);
4770
+ try {
4771
+ for (const representation of materialDefinitionRep.Representations) {
4772
+ for (let itemIndex = 0; itemIndex < representation.Items.length; ++itemIndex) {
4773
+ // save mapping of IfcMaterial --> IfcStyledItem
4774
+ this.materials.materialDefinitionsMap.set(materialDefinitionRep.RepresentedMaterial.localID, representation.Items[itemIndex].localID);
4775
+ }
4236
4776
  }
4237
4777
  }
4778
+ catch (ex) {
4779
+ this.handleMapPrepError_("IfcMaterialDefinitionRepresentation", materialDefinitionRep.expressID, ex);
4780
+ }
4238
4781
  }
4239
4782
  }
4240
4783
  /**
@@ -4244,17 +4787,22 @@ export class IfcGeometryExtraction {
4244
4787
  // populate relvoids map
4245
4788
  const relVoids = this.model.types(IfcRelVoidsElement);
4246
4789
  for (const relVoid of relVoids) {
4247
- // map product --> relvoids opening elements
4248
- const geometryLocalIDArray = this.productToVoidGeometryMap.get(relVoid.RelatingBuildingElement.localID);
4249
- if (geometryLocalIDArray === void 0) {
4250
- const localIDArray = [];
4251
- localIDArray.push(relVoid.RelatedOpeningElement.localID);
4252
- this.productToVoidGeometryMap.set(relVoid.RelatingBuildingElement.localID, localIDArray);
4790
+ try {
4791
+ // map product --> relvoids opening elements
4792
+ const geometryLocalIDArray = this.productToVoidGeometryMap.get(relVoid.RelatingBuildingElement.localID);
4793
+ if (geometryLocalIDArray === void 0) {
4794
+ const localIDArray = [];
4795
+ localIDArray.push(relVoid.RelatedOpeningElement.localID);
4796
+ this.productToVoidGeometryMap.set(relVoid.RelatingBuildingElement.localID, localIDArray);
4797
+ }
4798
+ else {
4799
+ // append to it
4800
+ geometryLocalIDArray.push(relVoid.RelatedOpeningElement.localID);
4801
+ this.productToVoidGeometryMap.set(relVoid.RelatingBuildingElement.localID, geometryLocalIDArray);
4802
+ }
4253
4803
  }
4254
- else {
4255
- // append to it
4256
- geometryLocalIDArray.push(relVoid.RelatedOpeningElement.localID);
4257
- this.productToVoidGeometryMap.set(relVoid.RelatingBuildingElement.localID, geometryLocalIDArray);
4804
+ catch (ex) {
4805
+ this.handleMapPrepError_("IfcRelVoidsElement", relVoid.expressID, ex);
4258
4806
  }
4259
4807
  }
4260
4808
  }
@@ -4364,6 +4912,657 @@ export class IfcGeometryExtraction {
4364
4912
  fs.writeFileSync(outputFilePath, objContent, 'utf8')
4365
4913
  } */
4366
4914
  }
4915
+ /**
4916
+ * Local IDs of every IfcProduct that is a RelatedObject of any
4917
+ * IfcRelAggregates — the exact set the rel-aggregates pass
4918
+ * ({@link extractRelAggregateGeometry}) re-extracts with the relating
4919
+ * object's master rel-voids.
4920
+ *
4921
+ * Both product walks SKIP these in their first pass and extract them
4922
+ * only in the aggregates pass, for two invariants consumers rely on:
4923
+ * 1. one scene instance per placement (the first-pass node plus the
4924
+ * re-extraction node used to coincide exactly — a duplicate draw
4925
+ * that z-fights), and
4926
+ * 2. a part's canonical mesh content is never REPLACED after its
4927
+ * instances were already emitted — incremental consumers copy
4928
+ * content at delivery time, so a first-pass emission served the
4929
+ * uncut (or empty) content and the later replacement never
4930
+ * reached the screen (the ILNA missing-facades class).
4931
+ *
4932
+ * @return {Set<number>} product localIDs deferred to the aggregates
4933
+ * pass; empty for models without IfcRelAggregates.
4934
+ */
4935
+ aggregateTargetLocalIDs() {
4936
+ if (this.aggregateTargetLocalIDs_ !== void 0) {
4937
+ return this.aggregateTargetLocalIDs_;
4938
+ }
4939
+ const targets = new Set();
4940
+ const productTypes = new Set(IfcProduct.query);
4941
+ for (const relAggregate of this.model.types(IfcRelAggregates)) {
4942
+ try {
4943
+ // RelatedObjects as express IDs — do not hydrate every child
4944
+ // product (that paged most of the file during demand prep).
4945
+ relAggregate.forEachReferenceInField(REL_AGGREGATES_RELATED_OFFSET, REL_AGGREGATES_RELATED_BASE, REL_AGGREGATES_RELATED_DEPTH, (expressID) => {
4946
+ if (expressID === void 0) {
4947
+ return true;
4948
+ }
4949
+ const relatedLocalID = this.model.resolveExpressID(expressID);
4950
+ if (relatedLocalID === void 0) {
4951
+ return true;
4952
+ }
4953
+ const typeID = this.model.typeIDOf(relatedLocalID);
4954
+ if (typeID !== void 0 && productTypes.has(typeID)) {
4955
+ targets.add(relatedLocalID);
4956
+ }
4957
+ return true;
4958
+ });
4959
+ }
4960
+ catch {
4961
+ // Malformed relationship rows are tolerated exactly like the
4962
+ // aggregates pass itself tolerates them (permissive catch) —
4963
+ // their targets simply stay in the first-pass walk.
4964
+ }
4965
+ }
4966
+ this.aggregateTargetLocalIDs_ = targets;
4967
+ return targets;
4968
+ }
4969
+ /**
4970
+ * One-time extraction setup shared by the whole-model walk and the
4971
+ * per-product demand path (Phase B2): linear scaling factor and the
4972
+ * cross-product maps (materials, material definitions, rel-voids, styled
4973
+ * items). Idempotent.
4974
+ */
4975
+ prepareExtractionMaps_() {
4976
+ if (this.extractionMapsPrepared_) {
4977
+ return;
4978
+ }
4979
+ this.extractionMapsPrepared_ = true;
4980
+ this.extractLinearScalingFactor();
4981
+ // populate relMaterialsMap
4982
+ const relAssociatesMaterials = this.model.types(IfcRelAssociatesMaterial);
4983
+ const productTypes = new Set(IfcProduct.query);
4984
+ for (const relAssociateMaterial of relAssociatesMaterials) {
4985
+ try {
4986
+ // Read RelatingMaterial INSIDE the guard: a malformed material
4987
+ // reference (e.g. a value where an entity ref is expected) throws
4988
+ // "Value in STEP was incorrectly typed", and pulling this read above
4989
+ // the try let one bad relationship abort the whole model's extraction
4990
+ // instead of being tolerated like every other malformed relationship
4991
+ // here. The rel-aggregates loop below already reads its RelatingObject
4992
+ // inside its own try for exactly this reason.
4993
+ const relatingMaterial = relAssociateMaterial.RelatingMaterial;
4994
+ const materialLocalID = relatingMaterial.localID;
4995
+ relAssociateMaterial.forEachReferenceInField(REL_ASSOCIATES_RELATED_OFFSET, REL_ASSOCIATES_RELATED_BASE, REL_ASSOCIATES_RELATED_DEPTH, (expressID) => {
4996
+ if (expressID === void 0) {
4997
+ return true;
4998
+ }
4999
+ const productLocalID = this.model.resolveExpressID(expressID);
5000
+ if (productLocalID === void 0) {
5001
+ return true;
5002
+ }
5003
+ const typeID = this.model.typeIDOf(productLocalID);
5004
+ if (typeID === EntityTypesIfc.IFCOPENINGELEMENT ||
5005
+ typeID === EntityTypesIfc.IFCSPACE ||
5006
+ typeID === EntityTypesIfc.IFCOPENINGSTANDARDCASE) {
5007
+ return true;
5008
+ }
5009
+ if (typeID !== void 0 && productTypes.has(typeID)) {
5010
+ this.materials.relMaterialsMap.set(productLocalID, materialLocalID);
5011
+ }
5012
+ return true;
5013
+ });
5014
+ }
5015
+ catch (ex) {
5016
+ // Reference the relationship record, not RelatingMaterial: the
5017
+ // latter may be exactly what threw and would be undefined here.
5018
+ this.handleMapPrepError_("IfcRelAssociatesMaterial", relAssociateMaterial.expressID, ex);
5019
+ }
5020
+ }
5021
+ // populate MaterialDefinitionsMap
5022
+ this.populateMaterialDefinitionsMap();
5023
+ // populate relvoids map
5024
+ this.populateRelVoidsMap();
5025
+ // populate styled items map
5026
+ this.populateStyledItemsMap();
5027
+ }
5028
+ /**
5029
+ * Prepare for per-product demand extraction (Phase B2) without running the
5030
+ * whole-model walk: populates the shared extraction maps so
5031
+ * {@link extractProductGeometryByLocalID} can extract any single product.
5032
+ * Idempotent; the whole-model walk shares the same preparation.
5033
+ *
5034
+ * @param lightweightPreview Preview-only preparation (parse-time
5035
+ * preview generations): skips the relationship sweeps
5036
+ * (rel-materials, rel-voids) whose entity materialization dominates
5037
+ * preparation cost on large models — extracted products then miss
5038
+ * openings and rel-bound materials, which preview consumers accept
5039
+ * by contract. Styled-item and material-definition maps are kept
5040
+ * (colors), as is the linear scaling factor.
5041
+ */
5042
+ prepareDemandExtraction(lightweightPreview = false) {
5043
+ if (lightweightPreview && !this.extractionMapsPrepared_) {
5044
+ this.extractionMapsPrepared_ = true;
5045
+ this.extractLinearScalingFactor();
5046
+ this.populateMaterialDefinitionsMap();
5047
+ this.populateStyledItemsMap();
5048
+ return;
5049
+ }
5050
+ this.prepareExtractionMaps_();
5051
+ }
5052
+ /**
5053
+ * Extract one product's geometry into the scene by local ID — the demand
5054
+ * path's entry (Phase B2). Requires {@link prepareDemandExtraction} (or a
5055
+ * prior whole-model walk) so the shared maps exist.
5056
+ *
5057
+ * Note: this extracts a single product with its OWN voids. Products that
5058
+ * are targets of IfcRelAggregates whose relating object carries rel-voids
5059
+ * additionally need the whole-model walk's second pass
5060
+ * ({@link extractRelAggregatesGeometry}) — the demand pump runs it once
5061
+ * after the last product batch so pumped extractions converge to the
5062
+ * classic walk exactly.
5063
+ *
5064
+ * @param localID The product's local ID.
5065
+ * @return {boolean} True if the local ID was an IfcProduct and was
5066
+ * extracted; false if it wasn't a product.
5067
+ */
5068
+ extractProductGeometryByLocalID(localID) {
5069
+ this.prepareExtractionMaps_();
5070
+ const element = this.model.getElementByLocalID(localID);
5071
+ if (!(element instanceof IfcProduct)) {
5072
+ return false;
5073
+ }
5074
+ this.extractProductGeometry(element);
5075
+ return true;
5076
+ }
5077
+ /**
5078
+ * Page in every record {@link prepareDemandExtraction} will read, so
5079
+ * the relationship sweeps succeed on a windowed source. Call before
5080
+ * the first prepare on a model whose source is external.
5081
+ *
5082
+ * Pins the prep-type records themselves (and a 1-hop of product /
5083
+ * material records the relationship getters hydrate). Does **not**
5084
+ * BFS-pin styled-item Item closures — those are facesets, and
5085
+ * walking them here pulled most of the file into the windowed LRU
5086
+ * before the first product extract.
5087
+ *
5088
+ * @return {Promise<Set<number>>} Pinned local IDs — caller must
5089
+ * {@link StepModelBase.unpinLocalIDs} after prepare/extract.
5090
+ */
5091
+ async ensureResidentForDemandPrep() {
5092
+ const pinned = new Set();
5093
+ if (!this.model.isSourceExternal) {
5094
+ return pinned;
5095
+ }
5096
+ // Small trees: project units, material-definition style stacks.
5097
+ for (const type of [IfcProject, IfcMaterialDefinitionRepresentation]) {
5098
+ for (const expressID of this.model.expressIDsOfTypes(type)) {
5099
+ await this.model.ensureResidentClosureByExpressID(expressID, void 0, pinned);
5100
+ }
5101
+ }
5102
+ const recordOnlyTypes = [
5103
+ IfcRelAssociatesMaterial,
5104
+ IfcRelVoidsElement,
5105
+ IfcStyledItem,
5106
+ IfcRelAggregates,
5107
+ ];
5108
+ for (const type of recordOnlyTypes) {
5109
+ for (const expressID of this.model.expressIDsOfTypes(type)) {
5110
+ const localID = this.model.resolveExpressID(expressID);
5111
+ if (localID === void 0 || pinned.has(localID)) {
5112
+ continue;
5113
+ }
5114
+ await this.model.ensureResidentByLocalID(localID);
5115
+ this.model.pinByLocalID(localID);
5116
+ pinned.add(localID);
5117
+ }
5118
+ }
5119
+ // Relationship getters hydrate the related product / material
5120
+ // records (not their geometry). Pin that 1-hop so populate can
5121
+ // read `.localID` without a full closure walk.
5122
+ const oneHopTypes = [
5123
+ IfcRelAssociatesMaterial,
5124
+ IfcRelVoidsElement,
5125
+ ];
5126
+ for (const type of oneHopTypes) {
5127
+ for (const expressID of this.model.expressIDsOfTypes(type)) {
5128
+ const localID = this.model.resolveExpressID(expressID);
5129
+ if (localID === void 0) {
5130
+ continue;
5131
+ }
5132
+ for (const refExpressID of this.model.referencedExpressIDs(localID)) {
5133
+ const refLocalID = this.model.resolveExpressID(refExpressID);
5134
+ if (refLocalID === void 0 || pinned.has(refLocalID)) {
5135
+ continue;
5136
+ }
5137
+ await this.model.ensureResidentByLocalID(refLocalID);
5138
+ this.model.pinByLocalID(refLocalID);
5139
+ pinned.add(refLocalID);
5140
+ }
5141
+ }
5142
+ }
5143
+ return pinned;
5144
+ }
5145
+ /**
5146
+ * Page in the `#ref` closure a per-product extract will acquire,
5147
+ * including this product's voids, bound materials and styled items
5148
+ * (those are inverse — not written in the product record).
5149
+ *
5150
+ * @param localID The product's local ID.
5151
+ * @param seen Optional set to extend.
5152
+ * @param leafSpans Coalesced Faces[] pin ranges — caller unpins.
5153
+ * @return {Promise<Set<number>>} Pinned local IDs — caller must unpin
5154
+ * after extract.
5155
+ */
5156
+ async ensureResidentForProductExtract(localID, seen, leafSpans) {
5157
+ if (!this.model.isSourceExternal) {
5158
+ return seen ?? new Set();
5159
+ }
5160
+ // Paging failures are contained to their product (conway#526).
5161
+ // `extractGeometryBatchAsync` prefetches a batch through one
5162
+ // Promise.all, so a rejection here used to escape the pump and
5163
+ // abort the entire load — a whole model lost to one unreadable
5164
+ // product. The extract that follows already tolerates a product it
5165
+ // cannot read (its own per-product catch logs and moves on), so
5166
+ // degrading to "this product is missing" is both the smaller
5167
+ // failure and the one the pipeline is already built for.
5168
+ //
5169
+ // Deliberately no retry: a second attempt sharing `seen` would
5170
+ // re-claim nothing and re-ensure nothing, and giving it a fresh set
5171
+ // would double-pin every record it re-walked — pins are refcounted
5172
+ // but the caller unpins once per unique ID, so the surplus would
5173
+ // hold those chunks resident for the model's life and grow the
5174
+ // window without bound.
5175
+ try {
5176
+ return await this.ensureResidentForProductExtractUnguarded_(localID, seen, leafSpans);
5177
+ }
5178
+ catch (error) {
5179
+ Logger.error(`Error paging product localID ${localID} for extract: ` +
5180
+ `${error instanceof Error ? error.message : String(error)}`);
5181
+ return seen ?? new Set();
5182
+ }
5183
+ }
5184
+ /**
5185
+ * {@link ensureResidentForProductExtract} without the per-product
5186
+ * fault containment — split out so the guard cannot accidentally
5187
+ * swallow a failure raised by its own bookkeeping.
5188
+ *
5189
+ * @param localID The product's local ID.
5190
+ * @param seen Optional set to extend.
5191
+ * @param leafSpans Coalesced Faces[] pin ranges — caller unpins.
5192
+ * @return {Promise<Set<number>>} Pinned local IDs.
5193
+ */
5194
+ async ensureResidentForProductExtractUnguarded_(localID, seen, leafSpans) {
5195
+ const extra = [];
5196
+ const voids = this.productToVoidGeometryMap.get(localID);
5197
+ if (voids !== void 0) {
5198
+ extra.push(...voids);
5199
+ }
5200
+ const material = this.materials.relMaterialsMap.get(localID);
5201
+ if (material !== void 0) {
5202
+ extra.push(material);
5203
+ }
5204
+ // Facesets are visited but not scanned (Faces[] is a later span).
5205
+ // Face records are spanned, not visited — 78k pins / 15s otherwise.
5206
+ const isFace = (id) => {
5207
+ const typeID = this.model.typeIDOf(id);
5208
+ return typeID === EntityTypesIfc.IFCINDEXEDPOLYGONALFACE ||
5209
+ typeID === EntityTypesIfc.IFCINDEXEDPOLYGONALFACEWITHVOIDS;
5210
+ };
5211
+ const descend = (id) => {
5212
+ const typeID = this.model.typeIDOf(id);
5213
+ return !isFace(id) &&
5214
+ typeID !== EntityTypesIfc.IFCPOLYGONALFACESET &&
5215
+ typeID !== EntityTypesIfc.IFCTRIANGULATEDFACESET;
5216
+ };
5217
+ // IDs THIS call claimed and awaited, as opposed to `visited`, which
5218
+ // is the caller's shared set when a batch prefetches concurrently.
5219
+ // Only the former may be read synchronously afterwards (conway#526).
5220
+ const claimed = new Set();
5221
+ const visited = await this.model.ensureResidentClosureByLocalID(localID, extra, seen, descend, leafSpans, isFace, claimed);
5222
+ const styleExtra = [];
5223
+ for (const visitedID of visited) {
5224
+ const styled = this.materials.styledItemMap.get(visitedID);
5225
+ if (styled !== void 0) {
5226
+ styleExtra.push(styled);
5227
+ }
5228
+ const definition = this.materials.materialDefinitionsMap.get(visitedID);
5229
+ if (definition !== void 0) {
5230
+ styleExtra.push(definition);
5231
+ }
5232
+ }
5233
+ if (styleExtra.length > 0) {
5234
+ await this.model.ensureResidentClosureByLocalID(localID, styleExtra, visited, descend, leafSpans, isFace, claimed);
5235
+ }
5236
+ await this.ensureResidentVisitedFacesetPayloads_(claimed, visited, leafSpans);
5237
+ return visited;
5238
+ }
5239
+ /**
5240
+ * Page a faceset's Coordinates record and its Faces[] run as one
5241
+ * or more tight address spans. The generic closure visits the
5242
+ * faceset but does not scan Faces[].
5243
+ *
5244
+ * **Iterates `claimed`, not `visited`, and re-ensures before the
5245
+ * scan** — both halves of conway#526. `referencedExpressIDs` is a
5246
+ * SYNCHRONOUS acquire of the faceset's own bytes, and when a batch
5247
+ * prefetches products concurrently through one shared `visited` set
5248
+ * (`extractGeometryBatchAsync`'s Promise.all over 8 products), the
5249
+ * closure walk skips any ID a sibling product already claimed. That
5250
+ * sibling has pinned the range but its read may still be in flight,
5251
+ * so scanning the shared set threw
5252
+ * `StepBufferNotResidentError: STEP source range [...] is not
5253
+ * resident` out of the prefetch and killed the whole load — a pin
5254
+ * reserves a chunk against eviction, it does not make it resident.
5255
+ * Iterating this call's own claims fixes that (and drops the
5256
+ * quadratic re-scan of every sibling's facesets); the `await` below
5257
+ * makes the guarantee local rather than inherited, since a resident
5258
+ * chunk costs only a microtask there.
5259
+ *
5260
+ * @param claimed Faceset local IDs this call claimed and awaited.
5261
+ * @param visited The (possibly shared) visited set — read for
5262
+ * Coordinates de-duplication and extended with the ones pinned here,
5263
+ * so the caller's unpin covers them.
5264
+ * @param leafSpans Coalesced Faces[] pin ranges — caller unpins.
5265
+ */
5266
+ async ensureResidentVisitedFacesetPayloads_(claimed, visited, leafSpans) {
5267
+ for (const localID of claimed) {
5268
+ const typeID = this.model.typeIDOf(localID);
5269
+ if (typeID !== EntityTypesIfc.IFCPOLYGONALFACESET &&
5270
+ typeID !== EntityTypesIfc.IFCTRIANGULATEDFACESET) {
5271
+ continue;
5272
+ }
5273
+ // Cheap when already resident (a Map hit and a microtask), and
5274
+ // the difference between a guarantee and an assumption when the
5275
+ // window has churned since this record was claimed.
5276
+ await this.model.ensureResidentByLocalID(localID);
5277
+ const refs = this.model.referencedExpressIDs(localID);
5278
+ if (refs.length === 0) {
5279
+ continue;
5280
+ }
5281
+ // First #ref is Coordinates (IfcTessellatedFaceSet). Pin it as a
5282
+ // normal record — one IfcCartesianPointList3D, no child #refs.
5283
+ const coordsLocalID = this.model.resolveExpressID(refs[0]);
5284
+ if (coordsLocalID !== void 0 && !visited.has(coordsLocalID)) {
5285
+ visited.add(coordsLocalID);
5286
+ this.model.pinByLocalID(coordsLocalID);
5287
+ await this.model.ensureResidentByLocalID(coordsLocalID);
5288
+ }
5289
+ if (refs.length < 2) {
5290
+ continue;
5291
+ }
5292
+ const spans = this.model.spansOfExpressIDs(refs, 1);
5293
+ for (const span of spans) {
5294
+ this.model.pinAddressRange(span.address, span.length);
5295
+ leafSpans?.push(span);
5296
+ await this.model.ensureResidentRange(span.address, span.length);
5297
+ }
5298
+ }
5299
+ }
5300
+ /**
5301
+ * Page in the records {@link extractRelAggregateGeometry} will
5302
+ * acquire: the relationship, the relating product (and its voids)
5303
+ * and every related product.
5304
+ *
5305
+ * @param relAggregate The relationship to prefetch.
5306
+ * @return {Promise<Set<number>>} Pinned local IDs — caller must unpin
5307
+ * after extract.
5308
+ */
5309
+ async ensureResidentForAggregateExtract(relAggregate, leafSpans) {
5310
+ if (!this.model.isSourceExternal) {
5311
+ return new Set();
5312
+ }
5313
+ // Pin the rel record, page its 1-hop so RelatingObject /
5314
+ // RelatedObjects getters can hydrate, then walk each product
5315
+ // through the bounded extract. The 1-hop IDs must NOT go into
5316
+ // `pinned`/`seen` — that would skip their geometry closures.
5317
+ // A full BFS of the rel used to follow every RelatedObject into
5318
+ // Faces[] (the 78k-pin path) before descend could skip them.
5319
+ const pinned = new Set();
5320
+ const hop = [];
5321
+ this.model.pinByLocalID(relAggregate.localID);
5322
+ pinned.add(relAggregate.localID);
5323
+ try {
5324
+ await this.model.ensureResidentByLocalID(relAggregate.localID);
5325
+ for (const expressID of this.model.referencedExpressIDs(relAggregate.localID)) {
5326
+ const localID = this.model.resolveExpressID(expressID);
5327
+ if (localID === void 0 || pinned.has(localID)) {
5328
+ continue;
5329
+ }
5330
+ this.model.pinByLocalID(localID);
5331
+ hop.push(localID);
5332
+ await this.model.ensureResidentByLocalID(localID);
5333
+ }
5334
+ const relating = relAggregate.RelatingObject;
5335
+ if (relating !== null && relating !== void 0) {
5336
+ await this.ensureResidentForProductExtract(relating.localID, pinned, leafSpans);
5337
+ }
5338
+ const related = relAggregate.RelatedObjects;
5339
+ if (related !== null && related !== void 0) {
5340
+ for (const product of related) {
5341
+ await this.ensureResidentForProductExtract(product.localID, pinned, leafSpans);
5342
+ }
5343
+ }
5344
+ return pinned;
5345
+ }
5346
+ finally {
5347
+ for (const localID of hop) {
5348
+ this.model.unpinByLocalID(localID);
5349
+ }
5350
+ }
5351
+ }
5352
+ /**
5353
+ * The whole-model walk's SECOND product pass, shared with the demand
5354
+ * pump: for every IfcRelAggregates, re-extract the related products
5355
+ * with the relating object's rel-voids as a master override. This
5356
+ * re-extraction REPLACES the canonical mesh cached under each
5357
+ * representation item's localID (the scene resolves geometry by
5358
+ * localID at walk time), so aggregate parts whose parent carries
5359
+ * openings end up CUT — a demand pump that only ran the per-product
5360
+ * pass would leave them uncut and serve different GetGeometry content
5361
+ * than the classic walk (the vyzn romana/DOWA scatter). Call exactly
5362
+ * once, after every product has been extracted.
5363
+ */
5364
+ extractRelAggregatesGeometry() {
5365
+ const relAggregates = this.model.types(IfcRelAggregates);
5366
+ for (const relAggregate of relAggregates) {
5367
+ this.extractRelAggregateGeometry(relAggregate);
5368
+ }
5369
+ }
5370
+ /**
5371
+ * Extract one IfcRelAggregates' related products with the relating
5372
+ * object's ("master") rel-voids — the shared per-item body of the
5373
+ * whole-model walk's rel-aggregates loop and
5374
+ * {@link extractRelAggregatesGeometry}. Public so the deferred pump
5375
+ * can run the pass incrementally (batch-budgeted) instead of as one
5376
+ * end-of-load stall.
5377
+ *
5378
+ * @param relAggregate The rel-aggregates relationship to process.
5379
+ */
5380
+ extractRelAggregateGeometry(relAggregate) {
5381
+ try {
5382
+ // Note, this is required because the relating object
5383
+ // can be where the rel-void is applied, so we
5384
+ // used this as a top level over-ride for a rel-void.
5385
+ // for all objects in the aggregate - CS
5386
+ const relatingObject = relAggregate.RelatingObject;
5387
+ const masterRelVoids = relatingObject instanceof IfcProduct ?
5388
+ this.extractRelVoids(relatingObject) :
5389
+ void 0;
5390
+ const relatedObjects = relAggregate.RelatedObjects;
5391
+ for (const productRepresentation of relatedObjects) {
5392
+ if (productRepresentation instanceof IfcProduct) {
5393
+ this.extractProductGeometry(productRepresentation, masterRelVoids);
5394
+ }
5395
+ }
5396
+ if (masterRelVoids !== void 0) {
5397
+ masterRelVoids[0].delete();
5398
+ }
5399
+ }
5400
+ catch (ex) {
5401
+ if (ex instanceof Error) {
5402
+ if (MATERIAL_RELATED_OBJECTS_PERMISSIVE) {
5403
+ Logger.error(`Error processing relAggregate\n\terror: ${ex.message}`, relAggregate.expressID);
5404
+ }
5405
+ else {
5406
+ throw ex;
5407
+ }
5408
+ }
5409
+ else {
5410
+ Logger.error("Unknown exception processing IfcRelAssociateMaterials.");
5411
+ }
5412
+ }
5413
+ }
5414
+ /**
5415
+ * Extract one product's geometry into the scene: placement, material /
5416
+ * style extraction, representation items (including mapped items) and
5417
+ * rel-void application. The deduplicated body of the whole-model walk's
5418
+ * two product loops (plain products + rel-aggregates), shared with the
5419
+ * per-product demand path.
5420
+ *
5421
+ * @param product The product to extract.
5422
+ * @param precomputedRelVoids Optional caller-owned rel-voids (the
5423
+ * rel-aggregates "master" case): when supplied the caller retains
5424
+ * ownership and this method does not delete the mesh vector; when
5425
+ * omitted, voids are extracted per product and freed here.
5426
+ */
5427
+ extractProductGeometry(product, precomputedRelVoids) {
5428
+ this.scene.clearParentStack();
5429
+ const isSpace = product instanceof IfcOpeningElement ||
5430
+ product instanceof IfcSpace ||
5431
+ product instanceof IfcOpeningStandardCase;
5432
+ if (this.deferDanglingPlacements) {
5433
+ // Strict placement resolution for prefix extractions: dangling
5434
+ // references throw here (see extractPlacementStrict_) and the
5435
+ // demand seam reports the product as not-yet-extractable —
5436
+ // instead of extracting the product unplaced. The IFC preview
5437
+ // channel's forward-only cursor does not re-run a passed unit, so
5438
+ // a deferred product simply never previews and the durable pump
5439
+ // renders it; that is the existing contract for every product
5440
+ // whose extraction throws on an unparsed forward reference, and
5441
+ // an empty slot beats geometry at the wrong placement. This read
5442
+ // must be the entity's FIRST ObjectPlacement access: the
5443
+ // generated getters memoize, and a lenient read would cache null
5444
+ // for a dangling reference.
5445
+ this.extractPlacementStrict_(product);
5446
+ }
5447
+ else {
5448
+ const objectPlacement = product.ObjectPlacement;
5449
+ if (objectPlacement !== null) {
5450
+ this.extractPlacement(objectPlacement);
5451
+ }
5452
+ }
5453
+ const representations = product.Representation;
5454
+ if (representations === null) {
5455
+ return;
5456
+ }
5457
+ // extract styledItem material
5458
+ const styledItemID = this.extractMaterialStyle(product);
5459
+ let hasRelVoid = false;
5460
+ const extractRelVoidsResult = precomputedRelVoids ?? this.extractRelVoids(product);
5461
+ let relVoidsMeshVector;
5462
+ let relVoidLocalIDs;
5463
+ if (extractRelVoidsResult !== void 0) {
5464
+ [relVoidsMeshVector, relVoidLocalIDs] = extractRelVoidsResult;
5465
+ }
5466
+ if (relVoidsMeshVector !== void 0) {
5467
+ hasRelVoid = true;
5468
+ }
5469
+ if (styledItemID !== void 0) {
5470
+ // optimization: extract the first representation item and cache
5471
+ // the styleID to apply to the rest of the product geometry
5472
+ const styledItem = this.model.getElementByLocalID(styledItemID);
5473
+ let reusableStyleID;
5474
+ for (const representation of representations.Representations) {
5475
+ if (representation instanceof IfcShapeRepresentation) {
5476
+ // this check is essential -
5477
+ // if RepresentationIdentifier !== Body or Facetation we must skip it
5478
+ if (representation.RepresentationIdentifier !== "Body" &&
5479
+ representation.RepresentationIdentifier !== "Facetation") {
5480
+ continue;
5481
+ }
5482
+ }
5483
+ for (const item of representation.Items) {
5484
+ if (item instanceof IfcMappedItem) {
5485
+ this.extractMappedItem(item, product, hasRelVoid, isSpace);
5486
+ }
5487
+ else {
5488
+ try {
5489
+ this.extractRepresentationItem(item, product.localID, hasRelVoid, isSpace);
5490
+ if (hasRelVoid) {
5491
+ this.applyRelVoidToRepresentation(item, relVoidsMeshVector, product.localID, relVoidLocalIDs, void 0, isSpace);
5492
+ }
5493
+ }
5494
+ catch (error) {
5495
+ if (this.quietRecoverableLogging) {
5496
+ // Preview prefix: expected on a truncated tail — skip.
5497
+ }
5498
+ else if (error instanceof Error) {
5499
+ Logger.error(`Error extracting representation item\n\t${error.message}\n\tExpress ID: #${item.expressID}`);
5500
+ }
5501
+ else {
5502
+ Logger.error(`Error extracting representation item\n\tExpress ID: #${item.expressID}`);
5503
+ }
5504
+ }
5505
+ }
5506
+ const styledItemLocalID_ = this.materials.styledItemMap.get(item.localID);
5507
+ if (styledItemLocalID_ !== undefined) {
5508
+ const styledItem_ = this.model.getElementByLocalID(styledItemLocalID_);
5509
+ this.extractStyledItem(styledItem_);
5510
+ }
5511
+ else if (reusableStyleID !== void 0) {
5512
+ this.materials.addGeometryMapping(item.localID, reusableStyleID);
5513
+ }
5514
+ else if (styledItem instanceof IfcStyledItem) {
5515
+ // here we have the styled item, apply it to all geometry in this IfcProduct
5516
+ reusableStyleID = this.extractStyledItem(styledItem, item);
5517
+ }
5518
+ }
5519
+ }
5520
+ }
5521
+ else {
5522
+ for (const representation of representations.Representations) {
5523
+ if (representation instanceof IfcShapeRepresentation) {
5524
+ // this check is essential -
5525
+ // if RepresentationIdentifier !== Body or Facetation we must skip it
5526
+ if (representation.RepresentationIdentifier !== "Body" &&
5527
+ representation.RepresentationIdentifier !== "Facetation") {
5528
+ continue;
5529
+ }
5530
+ }
5531
+ for (const item of representation.Items) {
5532
+ if (item instanceof IfcMappedItem) {
5533
+ this.extractMappedItem(item, product, hasRelVoid, isSpace);
5534
+ }
5535
+ else {
5536
+ try {
5537
+ this.extractRepresentationItem(item, product.localID, hasRelVoid, isSpace);
5538
+ if (hasRelVoid) {
5539
+ this.applyRelVoidToRepresentation(item, relVoidsMeshVector, product.localID, relVoidLocalIDs, void 0, isSpace);
5540
+ }
5541
+ const styledItemLocalID_ = this.materials.styledItemMap.get(item.localID);
5542
+ if (styledItemLocalID_ !== void 0) {
5543
+ const styledItem_ = this.model.getElementByLocalID(styledItemLocalID_);
5544
+ this.extractStyledItem(styledItem_);
5545
+ }
5546
+ }
5547
+ catch (error) {
5548
+ if (this.quietRecoverableLogging) {
5549
+ // Preview prefix: expected on a truncated tail — skip.
5550
+ }
5551
+ else if (error instanceof Error) {
5552
+ Logger.error(`Error extracting representation item\n\t${error.message}\n\t${error.stack}\n\tExpress ID: #${item.localID}`);
5553
+ }
5554
+ else {
5555
+ Logger.error(`Unknown error extracting representation item Express ID: #${item.localID}`);
5556
+ }
5557
+ }
5558
+ }
5559
+ }
5560
+ }
5561
+ }
5562
+ if (precomputedRelVoids === void 0) {
5563
+ relVoidsMeshVector?.delete();
5564
+ }
5565
+ }
4367
5566
  /**
4368
5567
  * Extract the geometry data from the IFC
4369
5568
  *
@@ -4444,10 +5643,7 @@ export class IfcGeometryExtraction {
4444
5643
  */
4445
5644
  *extractIFCGeometryDataIncremental() {
4446
5645
  let result = ExtractResult.INCOMPLETE;
4447
- this.extractLinearScalingFactor();
4448
5646
  const previousMemoizationState = this.model.elementMemoization;
4449
- // populate relMaterialsMap
4450
- const relAssociatesMaterials = this.model.types(IfcRelAssociatesMaterial);
4451
5647
  try {
4452
5648
  // 256 meg limit for memoization - smaller models get a big
4453
5649
  // win from memoization, but much larger models it uses far too much heap.
@@ -4456,317 +5652,54 @@ export class IfcGeometryExtraction {
4456
5652
  if (this.lowMemoryMode || this.model.bufferBytesize > MEMOIZATION_THRESHOLD) {
4457
5653
  this.model.elementMemoization = false;
4458
5654
  }
4459
- for (const relAssociateMaterial of relAssociatesMaterials) {
4460
- const relatingMaterial = relAssociateMaterial.RelatingMaterial;
4461
- try {
4462
- const relatedObjects = relAssociateMaterial.RelatedObjects;
4463
- for (const relatedObject of relatedObjects) {
4464
- const product = relatedObject;
4465
- if (product instanceof IfcProduct) {
4466
- if (product instanceof IfcOpeningElement ||
4467
- product instanceof IfcSpace ||
4468
- product instanceof IfcOpeningStandardCase) {
4469
- continue;
4470
- }
4471
- // save mapping of IfcProduct --> IfcMaterial
4472
- this.materials.relMaterialsMap.set(product.localID, relatingMaterial.localID);
4473
- }
4474
- else {
4475
- // Logger.warning(`type other than IfcProduct: ${EntityTypesIfc[product.type]}`)
4476
- }
4477
- }
4478
- }
4479
- catch (ex) {
4480
- if (ex instanceof Error) {
4481
- if (MATERIAL_RELATED_OBJECTS_PERMISSIVE) {
4482
- Logger.error("Error processing relatingMaterial expressID: " +
4483
- `${relatingMaterial.expressID}, error: ${ex.message}`);
4484
- }
4485
- else {
4486
- throw ex;
4487
- }
4488
- }
4489
- else {
4490
- Logger.error("Unknown exception processing IfcRelAssociateMaterials.");
4491
- }
4492
- }
4493
- }
4494
- // populate MaterialDefinitionsMap
4495
- this.populateMaterialDefinitionsMap();
4496
- // populate relvoids map
4497
- this.populateRelVoidsMap();
4498
- // populate styled items map
4499
- this.populateStyledItemsMap();
5655
+ this.prepareExtractionMaps_();
4500
5656
  const products = this.model.types(IfcProduct);
4501
5657
  // Prefix-sum counts (no iteration/materialization) — see typeCount.
4502
5658
  // Slight over-count from multi-mapped elements just leaves the bar
4503
5659
  // shy of 100% until endPhase; fine for progress.
4504
5660
  const totalItems = this.model.typeCount(IfcProduct) + this.model.typeCount(IfcRelAggregates);
4505
5661
  let completedItems = 0;
5662
+ // Products the aggregates pass re-extracts are SKIPPED here and
5663
+ // extracted exactly once, below, with the master rel-voids — one
5664
+ // scene instance per placement and no content replacement after
5665
+ // emission (see aggregateTargetLocalIDs).
5666
+ const aggregateTargets = this.aggregateTargetLocalIDs();
4506
5667
  for (const product of products) {
4507
5668
  yield [++completedItems, totalItems];
4508
- this.scene.clearParentStack();
4509
- const isSpace = product instanceof IfcOpeningElement ||
4510
- product instanceof IfcSpace ||
4511
- product instanceof IfcOpeningStandardCase;
4512
- const objectPlacement = product.ObjectPlacement;
4513
- if (objectPlacement !== null) {
4514
- this.extractPlacement(objectPlacement);
4515
- }
4516
- const representations = product.Representation;
4517
- if (representations !== null) {
4518
- // extract styledItem material
4519
- const styledItemID = this.extractMaterialStyle(product);
4520
- let hasRelVoid = false;
4521
- const extractRelVoidsResult = this.extractRelVoids(product);
4522
- let relVoidsMeshVector;
4523
- let relVoidLocalIDs;
4524
- if (extractRelVoidsResult !== void 0) {
4525
- [relVoidsMeshVector, relVoidLocalIDs] = extractRelVoidsResult;
4526
- }
4527
- if (relVoidsMeshVector !== void 0) {
4528
- hasRelVoid = true;
4529
- }
4530
- if (styledItemID !== void 0) {
4531
- // optimization: extract the first representation item and cache
4532
- // the styleID to apply to the rest of the product geometry
4533
- const styledItem = this.model.getElementByLocalID(styledItemID);
4534
- let reusableStyleID;
4535
- for (const representation of representations.Representations) {
4536
- if (representation instanceof IfcShapeRepresentation) {
4537
- // this check is essential -
4538
- // if RepresentationIdentifier !== Body or Facetation we must skip it
4539
- if (representation.RepresentationIdentifier !== "Body" &&
4540
- representation.RepresentationIdentifier !== "Facetation") {
4541
- continue;
4542
- }
4543
- }
4544
- for (const item of representation.Items) {
4545
- if (item instanceof IfcMappedItem) {
4546
- this.extractMappedItem(item, product, hasRelVoid, isSpace);
4547
- }
4548
- else {
4549
- try {
4550
- this.extractRepresentationItem(item, product.localID, hasRelVoid, isSpace);
4551
- if (hasRelVoid) {
4552
- this.applyRelVoidToRepresentation(item, relVoidsMeshVector, product.localID, relVoidLocalIDs, void 0, isSpace);
4553
- }
4554
- }
4555
- catch (error) {
4556
- if (error instanceof Error) {
4557
- Logger.error(`Error extracting representation item\n\t${error.message}\n\tExpress ID: #${item.expressID}`);
4558
- }
4559
- else {
4560
- Logger.error(`Error extracting representation item\n\tExpress ID: #${item.expressID}`);
4561
- }
4562
- }
4563
- }
4564
- const styledItemLocalID_ = this.materials.styledItemMap.get(item.localID);
4565
- if (styledItemLocalID_ !== undefined) {
4566
- const styledItem_ = this.model.getElementByLocalID(styledItemLocalID_);
4567
- this.extractStyledItem(styledItem_);
4568
- }
4569
- else if (reusableStyleID !== void 0) {
4570
- this.materials.addGeometryMapping(item.localID, reusableStyleID);
4571
- }
4572
- else if (styledItem instanceof IfcStyledItem) {
4573
- // here we have the styled item, apply it to all geometry in this IfcProduct
4574
- reusableStyleID = this.extractStyledItem(styledItem, item);
4575
- }
4576
- }
4577
- }
4578
- }
4579
- else {
4580
- for (const representation of representations.Representations) {
4581
- if (representation instanceof IfcShapeRepresentation) {
4582
- // this check is essential -
4583
- // if RepresentationIdentifier !== Body or Facetation we must skip it
4584
- if (representation.RepresentationIdentifier !== "Body" &&
4585
- representation.RepresentationIdentifier !== "Facetation") {
4586
- continue;
4587
- }
4588
- }
4589
- for (const item of representation.Items) {
4590
- if (item instanceof IfcMappedItem) {
4591
- this.extractMappedItem(item, product, hasRelVoid, isSpace);
4592
- }
4593
- else {
4594
- try {
4595
- this.extractRepresentationItem(item, product.localID, hasRelVoid, isSpace);
4596
- if (hasRelVoid) {
4597
- this.applyRelVoidToRepresentation(item, relVoidsMeshVector, product.localID, relVoidLocalIDs, void 0, isSpace);
4598
- }
4599
- const styledItemLocalID_ = this.materials.styledItemMap.get(item.localID);
4600
- if (styledItemLocalID_ !== void 0) {
4601
- const styledItem_ = this.model.getElementByLocalID(styledItemLocalID_);
4602
- this.extractStyledItem(styledItem_);
4603
- }
4604
- }
4605
- catch (error) {
4606
- if (error instanceof Error) {
4607
- Logger.error(`Error extracting representation item\n\t${error.message}\n\t${error.stack}\n\tExpress ID: #${item.localID}`);
4608
- }
4609
- else {
4610
- Logger.error(`Unknown error extracting representation item Express ID: #${item.localID}`);
4611
- }
4612
- }
4613
- }
4614
- }
4615
- }
4616
- }
4617
- relVoidsMeshVector?.delete();
5669
+ if (aggregateTargets.has(product.localID)) {
5670
+ continue;
4618
5671
  }
4619
- }
4620
- const relAggregates = this.model.types(IfcRelAggregates);
4621
- for (const relAggregate of relAggregates) {
4622
- yield [++completedItems, totalItems];
4623
5672
  try {
4624
- // Note, this is required because the relating object
4625
- // can be where the rel-void is applied, so we
4626
- // used this as a top level over-ride for a rel-void.
4627
- // for all objects in the aggregate - CS
4628
- const relatingObject = relAggregate.RelatingObject;
4629
- const masterRelVoids = relatingObject instanceof IfcProduct ?
4630
- this.extractRelVoids(relatingObject) :
4631
- void 0;
4632
- const relatedObjects = relAggregate.RelatedObjects;
4633
- for (const productRepresentation of relatedObjects) {
4634
- if (productRepresentation instanceof IfcProduct) {
4635
- const isSpace = productRepresentation instanceof IfcOpeningElement ||
4636
- productRepresentation instanceof IfcSpace ||
4637
- productRepresentation instanceof IfcOpeningStandardCase;
4638
- const product = productRepresentation;
4639
- this.scene.clearParentStack();
4640
- const objectPlacement = product.ObjectPlacement;
4641
- if (objectPlacement !== null) {
4642
- this.extractPlacement(objectPlacement);
4643
- }
4644
- const representations = product.Representation;
4645
- if (representations !== null) {
4646
- // extract styledItem material
4647
- const styledItemID = this.extractMaterialStyle(product);
4648
- const extractRelVoidsResult = masterRelVoids ?? this.extractRelVoids(product);
4649
- let hasRelVoid = false;
4650
- let relVoidsMeshVector;
4651
- let relVoidLocalIDs;
4652
- if (extractRelVoidsResult !== void 0) {
4653
- [relVoidsMeshVector, relVoidLocalIDs] = extractRelVoidsResult;
4654
- }
4655
- if (relVoidsMeshVector !== void 0) {
4656
- hasRelVoid = true;
4657
- }
4658
- if (styledItemID !== void 0) {
4659
- // optimization: extract the first representation item and cache
4660
- // the styleID to apply to the rest of the product geometry
4661
- const styledItem = this.model.getElementByLocalID(styledItemID);
4662
- let reusableStyleID;
4663
- for (const representation of representations.Representations) {
4664
- if (representation instanceof IfcShapeRepresentation) {
4665
- // this check is essential -
4666
- // if RepresentationIdentifier !== Body or Facetation we must skip it
4667
- if (representation.RepresentationIdentifier !== "Body" &&
4668
- representation.RepresentationIdentifier !== "Facetation") {
4669
- continue;
4670
- }
4671
- }
4672
- for (const item of representation.Items) {
4673
- if (item instanceof IfcMappedItem) {
4674
- this.extractMappedItem(item, product, hasRelVoid, isSpace);
4675
- }
4676
- else {
4677
- try {
4678
- this.extractRepresentationItem(item, product.localID, hasRelVoid, isSpace);
4679
- if (hasRelVoid) {
4680
- this.applyRelVoidToRepresentation(item, relVoidsMeshVector, product.localID, relVoidLocalIDs, void 0, isSpace);
4681
- }
4682
- }
4683
- catch (error) {
4684
- if (error instanceof Error) {
4685
- Logger.error(`Error extracting rel aggregate representation item\n\t${error.message}\n\t${error.stack}\n\t Express ID: #${item.expressID}`);
4686
- }
4687
- else {
4688
- Logger.error(`Unknown error extracting rel aggregate representation item Express ID: #${item.expressID}`);
4689
- }
4690
- }
4691
- }
4692
- const styledItemLocalID_ = this.materials.styledItemMap.get(item.localID);
4693
- if (styledItemLocalID_ !== undefined) {
4694
- const styledItem_ = this.model.getElementByLocalID(styledItemLocalID_);
4695
- this.extractStyledItem(styledItem_);
4696
- }
4697
- else if (reusableStyleID !== void 0) {
4698
- this.materials.addGeometryMapping(item.localID, reusableStyleID);
4699
- }
4700
- else if (styledItem instanceof IfcStyledItem) {
4701
- // here we have the styled item, apply it to all geometry in this IfcProduct
4702
- reusableStyleID = this.extractStyledItem(styledItem, item);
4703
- }
4704
- }
4705
- }
4706
- }
4707
- else {
4708
- for (const representation of representations.Representations) {
4709
- if (representation instanceof IfcShapeRepresentation) {
4710
- // this check is essential -
4711
- // if RepresentationIdentifier !== Body or Facetation we must skip it
4712
- if (representation.RepresentationIdentifier !== "Body" &&
4713
- representation.RepresentationIdentifier !== "Facetation") {
4714
- continue;
4715
- }
4716
- }
4717
- for (const item of representation.Items) {
4718
- if (item instanceof IfcMappedItem) {
4719
- this.extractMappedItem(item, product, hasRelVoid, isSpace);
4720
- }
4721
- else {
4722
- try {
4723
- this.extractRepresentationItem(item, product.localID, hasRelVoid, isSpace);
4724
- if (hasRelVoid) {
4725
- this.applyRelVoidToRepresentation(item, relVoidsMeshVector, product.localID, relVoidLocalIDs, void 0, isSpace);
4726
- }
4727
- const styledItemLocalID_ = this.materials.styledItemMap.get(item.localID);
4728
- if (styledItemLocalID_ !== void 0) {
4729
- const styledItem_ = this.model.getElementByLocalID(styledItemLocalID_);
4730
- this.extractStyledItem(styledItem_);
4731
- }
4732
- }
4733
- catch (error) {
4734
- if (error instanceof Error) {
4735
- Logger.error(`Error extracting rel aggregate representation item\n\t${error.message}\n\t${error.stack}\n\t Express ID: #${item.expressID}`);
4736
- }
4737
- else {
4738
- Logger.error(`Unknown error extracting rel aggregate representation item Express ID: #${item.expressID}`);
4739
- }
4740
- }
4741
- }
4742
- }
4743
- }
4744
- }
4745
- if (masterRelVoids === void 0) {
4746
- relVoidsMeshVector?.delete();
4747
- }
4748
- }
4749
- }
4750
- }
4751
- if (masterRelVoids !== void 0) {
4752
- masterRelVoids[0].delete();
4753
- }
5673
+ this.extractProductGeometry(product);
4754
5674
  }
4755
5675
  catch (ex) {
5676
+ // A single malformed product must not abort the whole model. A
5677
+ // reference field carrying a value where an entity is expected
5678
+ // throws "Value in STEP was incorrectly typed" deep in material/
5679
+ // representation extraction; the rel-aggregates pass below already
5680
+ // tolerates this per-relationship, and the browser demand pump
5681
+ // swallows per-product throws, so a model that renders in prod
5682
+ // would otherwise hard-fail the headless regression batch. Skip
5683
+ // the offending product and continue; strict mode re-throws.
4756
5684
  if (ex instanceof Error) {
4757
5685
  if (MATERIAL_RELATED_OBJECTS_PERMISSIVE) {
4758
- Logger.error("Error processing relAggregate\n\t" +
4759
- `error: ${ex.message}\n\t express ID: #${relAggregate.expressID}`);
5686
+ Logger.error(`Error extracting product expressID: ${product.expressID}, ` +
5687
+ `error: ${ex.message}`);
4760
5688
  }
4761
5689
  else {
4762
5690
  throw ex;
4763
5691
  }
4764
5692
  }
4765
5693
  else {
4766
- Logger.error("Unknown exception processing IfcRelAssociateMaterials.");
5694
+ Logger.error("Unknown exception extracting product.");
4767
5695
  }
4768
5696
  }
4769
5697
  }
5698
+ const relAggregates = this.model.types(IfcRelAggregates);
5699
+ for (const relAggregate of relAggregates) {
5700
+ yield [++completedItems, totalItems];
5701
+ this.extractRelAggregateGeometry(relAggregate);
5702
+ }
4770
5703
  if (RegressionCaptureState.memoization !== MemoizationCapture.FULL) {
4771
5704
  this.model.geometry.deleteTemporaries();
4772
5705
  this.model.voidGeometry.deleteTemporaries();