@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
@@ -4,10 +4,110 @@ import { ObjectPool } from "../core/native_pool.js";
4
4
  import { MemoizationCapture, RegressionCaptureState } from "../core/regression_capture_state.js";
5
5
  import { ExtractResult } from "../core/shared_constants.js";
6
6
  import Logger from "../logging/logger.js";
7
- import { advanced_brep_shape_representation, advanced_face, annotation_occurrence, axis2_placement_2d, axis2_placement_3d, b_spline_curve, b_spline_curve_with_knots, b_spline_surface, b_spline_surface_with_knots, boolean_result, cartesian_point, cartesian_transformation_operator_3d, circle, colour_rgb, composite_curve, composite_curve_segment, conical_surface, context_dependent_shape_representation, conversion_based_unit, cylindrical_surface, draughting_model, edge_curve, edge_loop, ellipse, extruded_area_solid, face, face_based_surface_model, faceted_brep, fill_area_style_colour, geometric_curve_set, geometrically_bounded_2d_wireframe_representation, geometrically_bounded_wireframe_shape_representation, global_unit_assigned_context, half_space_solid, item_defined_transformation, length_unit, line, manifold_solid_brep, mapped_item, next_assembly_usage_occurrence, over_riding_styled_item, parameter_value, pcurve, placement, plane, poly_loop, polyline, presentation_layer_assignment, product, product_definition, product_definition_shape, property_definition, rational_b_spline_curve, rational_b_spline_surface, representation_item, representation_relationship_with_transformation, seam_curve, shape_definition_representation, shape_representation, shape_representation_relationship, shell_based_surface_model, si_prefix, si_unit, spherical_surface, styled_item, surface_curve, surface_of_linear_extrusion, surface_of_revolution, surface_side, surface_style_fill_area, surface_style_rendering, surface_style_usage, toroidal_surface, trimmed_curve, trimming_preference, vertex_loop, vertex_point, view_volume, } from "./AP214E3_2010_gen/index.js";
7
+ import { arrayToWasmHeap, wasmHeapView } from "../core/wasm_heap.js";
8
+ import { advanced_brep_shape_representation, advanced_face, annotation_occurrence, axis2_placement_2d, axis2_placement_3d, b_spline_curve, b_spline_curve_with_knots, b_spline_surface, b_spline_surface_with_knots, boolean_result, cartesian_point, cartesian_transformation_operator_3d, circle, colour_rgb, composite_curve, composite_curve_segment, conical_surface, context_dependent_shape_representation, conversion_based_unit, curve, cylindrical_surface, draughting_model, edge_curve, edge_loop, ellipse, extruded_area_solid, face, face_based_surface_model, faceted_brep, fill_area_style_colour, geometric_curve_set, geometrically_bounded_2d_wireframe_representation, geometrically_bounded_wireframe_shape_representation, global_unit_assigned_context, half_space_solid, item_defined_transformation, length_unit, line, manifold_solid_brep, mapped_item, next_assembly_usage_occurrence, over_riding_styled_item, parameter_value, pcurve, placement, plane, poly_loop, polyline, pre_defined_colour, presentation_layer_assignment, product, product_definition, product_definition_shape, property_definition, rational_b_spline_curve, rational_b_spline_surface, representation_item, representation_relationship_with_transformation, shape_definition_representation, shape_representation, shape_representation_relationship, shell_based_surface_model, si_prefix, si_unit, spherical_surface, styled_item, surface_curve, surface_of_linear_extrusion, surface_of_revolution, surface_side, surface_style_fill_area, surface_style_rendering, surface_style_usage, toroidal_surface, trimmed_curve, trimming_preference, vertex_loop, vertex_point, view_volume, } from "./AP214E3_2010_gen/index.js";
8
9
  import EntityTypesAP214 from "./AP214E3_2010_gen/entity_types_ap214.gen.js";
9
10
  import { AP214ProductShapeMap } from "./ap214_product_shape_map.js";
10
11
  import { AP214SceneBuilder } from "./ap214_scene_builder.js";
12
+ // Fewest points a bound can have and still span a plane, which is what
13
+ // GetBasisFromCoplanarPoints needs downstream.
14
+ const MINIMUM_BOUND_POINTS = 3;
15
+ // Ceiling on how finely one span of a pcurve's parameter curve is sampled
16
+ // when it is pushed through an angular surface parameterization. Nothing in
17
+ // STEP bounds how far a parameter curve may run in u or v - a helix wound a
18
+ // thousand times is a legal (if unusual) parameter-space line - and without a
19
+ // cap the sample count is a function of file content rather than of the
20
+ // extractor. At circleSegments density this is over twenty full turns of a
21
+ // single span - well past the point where the polyline is the limiting
22
+ // factor on any face it bounds.
23
+ const MAXIMUM_PCURVE_SPAN_SAMPLES = 256;
24
+ /**
25
+ * Whether an edge's vertices are the basis curve's own endpoints, making its
26
+ * trim a no-op that spans the whole curve.
27
+ *
28
+ * Only B-splines are considered. A LINE or CIRCLE has no endpoint entities to
29
+ * compare against — a circle is unbounded parametrically, and a line's trim is
30
+ * what gives it extent at all — so "whole curve" does not apply to them and
31
+ * their trims must be resolved.
32
+ *
33
+ * Compared by entity localID, not by coordinates: the file references the
34
+ * same CARTESIAN_POINT from both the control point list and the VERTEX_POINT,
35
+ * so this is exact and needs no tolerance.
36
+ *
37
+ * localID rather than object identity, deliberately. `===` looks equivalent
38
+ * and is not: getTypedElementByLocalID caches instances only while
39
+ * model.elementMemoization holds, and extraction turns it off for
40
+ * lowMemoryMode or a buffer over MEMOIZATION_THRESHOLD. StepModelBase says so
41
+ * outright — "it's not guaranteed element objects returned from this have
42
+ * referential equality even if they have ID equality". With `===` this guard
43
+ * would quietly stop firing on exactly the large models where a dropped face
44
+ * costs most.
45
+ *
46
+ * @param basisCurve The edge's underlying geometry.
47
+ * @param edgeStart The edge's start vertex.
48
+ * @param edgeEnd The edge's end vertex.
49
+ * @return {boolean} True when the edge covers the entire basis curve.
50
+ */
51
+ function isWholeCurveEdge(basisCurve, edgeStart, edgeEnd) {
52
+ if (!(edgeStart instanceof vertex_point) ||
53
+ !(edgeEnd instanceof vertex_point)) {
54
+ return false;
55
+ }
56
+ // An edge from a vertex back to ITSELF spans the whole of its basis curve:
57
+ // that is how STEP writes a closed edge, and trimming a circle by one point
58
+ // taken twice is how a torus's equator circles were resolving to a single
59
+ // point - which starved the revolution face of every angular sample it had
60
+ // and collapsed the swept surface flat (conway#461). The caller's gates make
61
+ // this safe for an open basis curve too: recovery only replaces the trimmed
62
+ // result when it was already degenerate AND the untrimmed extraction yields
63
+ // more points, and an identical-vertex edge over an open curve is degenerate
64
+ // whichever way it is read.
65
+ if (edgeStart.vertex_geometry.localID === edgeEnd.vertex_geometry.localID) {
66
+ return true;
67
+ }
68
+ if (!(basisCurve instanceof b_spline_curve)) {
69
+ return false;
70
+ }
71
+ const controlPoints = basisCurve.control_points_list;
72
+ if (controlPoints.length < 2) {
73
+ return false;
74
+ }
75
+ const first = controlPoints[0].localID;
76
+ const last = controlPoints[controlPoints.length - 1].localID;
77
+ const from = edgeStart.vertex_geometry.localID;
78
+ const to = edgeEnd.vertex_geometry.localID;
79
+ // Either orientation: same_sense is normalised separately, and an edge
80
+ // running end -> start still spans the whole curve. A closed curve whose
81
+ // first and last control points coincide also lands here, which is correct
82
+ // — that edge is the whole curve too.
83
+ return (from === first && to === last) || (from === last && to === first);
84
+ }
85
+ /**
86
+ * Render something thrown that is not an Error, for a log message.
87
+ *
88
+ * Interpolating a thrown value straight into a template string is how the
89
+ * public baseline ended up with 8 rows reading `[object Object]` — a message
90
+ * that names the face type and then says nothing about the failure. JSON
91
+ * first so a plain thrown object shows its fields; String() as the fallback
92
+ * for what JSON cannot take (cycles, BigInt, symbols).
93
+ *
94
+ * @param thrown The caught value, known not to be an Error.
95
+ * @return {string} A description with at least as much information as String().
96
+ */
97
+ function describeThrown(thrown) {
98
+ try {
99
+ const asJson = JSON.stringify(thrown);
100
+ // undefined for a symbol or a bare undefined; "{}" for an object whose
101
+ // own properties are all non-enumerable, which is no better than String().
102
+ if (asJson !== void 0 && asJson !== "{}") {
103
+ return asJson;
104
+ }
105
+ }
106
+ catch {
107
+ // Cyclic, BigInt, or a throwing toJSON - fall through.
108
+ }
109
+ return String(thrown);
110
+ }
11
111
  /**
12
112
  * Extract an AP214 Colour into our RGBA color, using premultiplied alpha.
13
113
  *
@@ -21,6 +121,19 @@ import { AP214SceneBuilder } from "./ap214_scene_builder.js";
21
121
  export function extractColorRGBPremultiplied(from, alpha = 1) {
22
122
  return [from.red * alpha, from.green * alpha, from.blue * alpha, alpha];
23
123
  }
124
+ // RGB values for the ISO 10303-46 pre-defined draughting colour names
125
+ // (draughting_pre_defined_colour / pre_defined_colour), which carry a name
126
+ // instead of components.
127
+ const PRE_DEFINED_COLOUR_RGB = {
128
+ "red": [1, 0, 0],
129
+ "green": [0, 1, 0],
130
+ "blue": [0, 0, 1],
131
+ "yellow": [1, 1, 0],
132
+ "magenta": [1, 0, 1],
133
+ "cyan": [0, 1, 1],
134
+ "black": [0, 0, 0],
135
+ "white": [1, 1, 1],
136
+ };
24
137
  /**
25
138
  * Extract an AP214 Colour into our RGBA color.
26
139
  *
@@ -59,16 +172,6 @@ export function extractColorOrFactor(from, surfaceColor, alpha = 1) {
59
172
  * Can export to OBJ, GLTF (Draco), GLB (Draco)
60
173
  */
61
174
  export class AP214GeometryExtraction {
62
- /**
63
- * Construct a geometry extraction from an AP214 step model and conway model
64
- *
65
- * @param conwayModel
66
- * @param model
67
- * @param limitCSGDepth Whether to limit the depth of CSG operations.
68
- * @param csgDepthLimit The maximum depth for CSG operations when limit CSG depth is used,
69
- * or the maximum level for CSG memoization if it is not.
70
- * @param lowMemoryMode Whether to enable low memory mode for geometry extraction.
71
- */
72
175
  constructor(conwayModel, model, limitCSGDepth = true, csgDepthLimit = 20, lowMemoryMode = false) {
73
176
  this.conwayModel = conwayModel;
74
177
  this.model = model;
@@ -88,7 +191,9 @@ export class AP214GeometryExtraction {
88
191
  * Statistics by the loader/proxies after extraction.
89
192
  */
90
193
  this.geometryTypeCounts = new Map();
194
+ this.demandCursor_ = 0;
91
195
  this.csgMemoization = !this.lowMemoryMode;
196
+ this.quietRecoverableLogging = false;
92
197
  this.materials = model.materials;
93
198
  this.scene = new AP214SceneBuilder(model, conwayModel, this.materials);
94
199
  this.productShapeMap = new AP214ProductShapeMap();
@@ -508,14 +613,7 @@ export class AP214GeometryExtraction {
508
613
  * @return {number} Pointer/memory address
509
614
  */
510
615
  arrayToWasmHeap(array) {
511
- // Allocate memory for the array within the Wasm module
512
- const bytesPerElement = array.BYTES_PER_ELEMENT;
513
- const numBytes = array.length * bytesPerElement;
514
- const arrayPtr = this.wasmModule._malloc(numBytes);
515
- // Create a new Uint8Array view on the Wasm memory buffer, then set the array to it
516
- const arrayWasm = new Uint8Array(this.wasmModule.HEAPU8.buffer, arrayPtr, numBytes);
517
- arrayWasm.set(new Uint8Array(array.buffer));
518
- return arrayPtr;
616
+ return arrayToWasmHeap(this.wasmModule, array);
519
617
  }
520
618
  /**
521
619
  * @param array
@@ -791,18 +889,39 @@ export class AP214GeometryExtraction {
791
889
  const fillAreaColor = fillAreaStyles.find((value => value instanceof fill_area_style_colour));
792
890
  if (fillAreaColor !== void 0) {
793
891
  try {
794
- const fillColor = fillAreaColor.fill_colour;
795
- if (!(fillColor instanceof colour_rgb)) {
892
+ // `fill_colour` is the schema's colour select, but AP214
893
+ // gives pre_defined_colour TWO supertypes (colour AND
894
+ // pre_defined_item) and the single-inheritance generator
895
+ // kept only pre_defined_item — so the typed getter throws
896
+ // "incorrectly typed" for e.g.
897
+ // DRAUGHTING_PRE_DEFINED_COLOUR('white') (Onshape exports).
898
+ // Re-extract the same field (offset 1) under the type the
899
+ // generator did keep, and map the name to RGB.
900
+ let surfaceColor;
901
+ let fillColor;
902
+ try {
903
+ fillColor = fillAreaColor.fill_colour;
904
+ }
905
+ catch {
906
+ const preDefined = fillAreaColor.extractElement(1, 0, 0, true, pre_defined_colour);
907
+ const rgb = PRE_DEFINED_COLOUR_RGB[preDefined?.name?.toLowerCase() ?? ""];
908
+ if (rgb !== void 0) {
909
+ surfaceColor = [rgb[0], rgb[1], rgb[2], 1];
910
+ }
911
+ }
912
+ if (fillColor instanceof colour_rgb) {
913
+ surfaceColor = extractColorRGBPremultiplied(fillColor, 1);
914
+ }
915
+ if (surfaceColor === void 0) {
796
916
  continue;
797
917
  }
798
- const surfaceColor = extractColorRGBPremultiplied(fillColor, 1);
799
918
  newMaterial.baseColor = surfaceColor;
800
919
  newMaterial.legacyColor = surfaceColor;
801
920
  newMaterial.roughness = 1;
802
921
  }
803
922
  catch (error) {
804
- Logger.warning(`Error extracting fill area style color for expressID:
805
- #${from.expressID} - type:
923
+ Logger.warning(`Error extracting fill area style color for expressID:
924
+ #${from.expressID} - type:
806
925
  ${EntityTypesAP214[style.type]} - error: ${error}`);
807
926
  }
808
927
  }
@@ -994,9 +1113,18 @@ export class AP214GeometryExtraction {
994
1113
  */
995
1114
  extractCurve(from, parentSense = true, isEdge = false, trimmingArguments = void 0) {
996
1115
  let stepCurve;
997
- stepCurve = this.curves.get(from.localID);
998
- if (stepCurve !== void 0) {
999
- return stepCurve;
1116
+ // Edge-supplied trims are specific to one EDGE_CURVE, but `from` is the
1117
+ // shared basis curve — memoising a trimmed extraction under the basis
1118
+ // curve's localID would hand the first edge's arc to every other edge
1119
+ // sharing that curve. Trimmed extractions are memoised by the caller
1120
+ // under the EDGE_CURVE's localID instead (see extractAdvancedFace);
1121
+ // untrimmed extractions stay memoised here.
1122
+ const hasEdgeTrims = trimmingArguments?.exist === true;
1123
+ if (!hasEdgeTrims) {
1124
+ stepCurve = this.curves.get(from.localID);
1125
+ if (stepCurve !== void 0) {
1126
+ return stepCurve;
1127
+ }
1000
1128
  }
1001
1129
  // console.log("[extractCurve]: curve express ID: "
1002
1130
  // + from.expressID + " type: " + EntityTypesAP214[from.type])
@@ -1090,9 +1218,16 @@ export class AP214GeometryExtraction {
1090
1218
  stepCurve = this.extractCurve(parentCurve, sameSense, isEdge);
1091
1219
  }
1092
1220
  else if (from instanceof pcurve) {
1093
- const seamCurve = from.findVariant(seam_curve);
1094
- if (seamCurve !== void 0) {
1095
- stepCurve = this.extractCurve(seamCurve.curve_3d, parentSense, isEdge, trimmingArguments);
1221
+ // An explicit 3D representation beats the parameter-space mapping where
1222
+ // one is carried, as it already does for surface_curve and seam_curve
1223
+ // reached in their own right. surface_curve rather than seam_curve:
1224
+ // seam_curve and intersection_curve are both its subtypes and all three
1225
+ // carry curve_3d, so this is the same preference over the whole family
1226
+ // (bldrs-ai/conway#505).
1227
+ const surfaceCurve = from.findVariant(surface_curve);
1228
+ if (surfaceCurve !== void 0) {
1229
+ stepCurve =
1230
+ this.extractCurve(surfaceCurve.curve_3d, parentSense, isEdge, trimmingArguments);
1096
1231
  }
1097
1232
  else {
1098
1233
  stepCurve = this.extractPScurve1(from);
@@ -1102,7 +1237,9 @@ export class AP214GeometryExtraction {
1102
1237
  Logger.warning(`Unsupported Curve! Type: ${EntityTypesAP214[from.type]}`);
1103
1238
  return;
1104
1239
  }
1105
- this.curves.add(from.localID, stepCurve);
1240
+ if (!hasEdgeTrims) {
1241
+ this.curves.add(from.localID, stepCurve);
1242
+ }
1106
1243
  return stepCurve;
1107
1244
  }
1108
1245
  /**
@@ -1150,36 +1287,232 @@ export class AP214GeometryExtraction {
1150
1287
  return compositeCurve;
1151
1288
  }
1152
1289
  /**
1290
+ * The ISO 10303-42 parameterization of an elementary basis surface,
1291
+ * expressed in that surface's placement-local frame. Writing C for the
1292
+ * placement's origin, x/y/z for its axes, R (and r) for the radii and a for
1293
+ * conical_surface.semi_angle, the standard's forms are
1294
+ *
1295
+ * plane s(u,v) = C + u x + v y
1296
+ * cylindrical s(u,v) = C + R((cos u) x + (sin u) y) + v z
1297
+ * conical s(u,v) = C + (R + v tan a)((cos u) x + (sin u) y) + v z
1298
+ * spherical s(u,v) = C + R cos v ((cos u) x + (sin u) y) + R sin v z
1299
+ * toroidal s(u,v) = C + (R + r cos v)((cos u) x + (sin u) y)
1300
+ * + r sin v z
1301
+ *
1302
+ * Note v on a sphere is a LATITUDE in [-90, 90] degrees, not a polar angle,
1303
+ * and v on a torus runs around the tube. Those four are quoted here from the
1304
+ * entities IFC adopted verbatim from this part - IfcPlane,
1305
+ * IfcCylindricalSurface, IfcSphericalSurface and IfcToroidalSurface, each
1306
+ * carrying its equation under "Definition according to ISO/CD 10303-42" plus
1307
+ * "Entity adapted from <name> defined in ISO 10303-42".
1308
+ *
1309
+ * The cone is the one worth stating explicitly, because it is the arm a
1310
+ * review of this code read the other way (bldrs-ai/conway#520): its v is
1311
+ * distance along the AXIS, so the radius grows by tan a per unit v - not
1312
+ * distance along the generator, which would grow it by sin a. Two STEP readers
1313
+ * settle it the same way. Open CASCADE parameterizes its own
1314
+ * Geom_ConicalSurface along the generator, P(u,v) = O + (R + v sin a)(...)
1315
+ * + v cos a z, and therefore rescales v by 1/cos(a) coming in from STEP and
1316
+ * by cos(a) going back out - GeomConvert_Units::DegreeToRadian and
1317
+ * ::RadianToDegree, which is exactly the path its STEP reader pushes pcurves
1318
+ * through (StepToTopoDS_TranslateEdge::MakePCurve). truck's STEP reader
1319
+ * builds the cone by revolving the line (R,0,0) + t (tan a, 0, 1), which is
1320
+ * the form above with t = v.
1321
+ *
1322
+ * conway-geom tessellates the same surfaces by inverting these formulae:
1323
+ * TriangulateCylindricalSurface, TriangulateSphericalSurface,
1324
+ * TriangulateConicalSurface and TriangulateToroidalSurface (conway-geom
1325
+ * `conway_geometry/operations/mesh_utils.h`) recover (theta, height) or
1326
+ * (theta, latitude) from world points, so a pcurve mapped here lands on the
1327
+ * same surface the face it bounds is triangulated on. It differs in one
1328
+ * deliberate way, on the cone's sign - see that arm.
1329
+ *
1330
+ * Angular parameters are radians taken straight from the file. Conway
1331
+ * applies no plane-angle unit conversion anywhere on the STEP path -
1332
+ * conical_surface.semi_angle reaches conway-geom raw in
1333
+ * extractConicalSurface - so a degree-unit export is wrong here in exactly
1334
+ * the same way, and consistently so.
1335
+ *
1336
+ * @param from The basis surface.
1337
+ * @return {SurfaceParameterization | undefined} The parameterization, or
1338
+ * undefined for a surface that has none here (b-spline and swept surfaces).
1339
+ */
1340
+ surfaceParameterization(from) {
1341
+ if (from instanceof plane) {
1342
+ // The one non-angular case: (u, v) are distances along the placement's
1343
+ // x and y axes.
1344
+ return {
1345
+ position: from.position,
1346
+ evaluate: (u, v) => ({ x: u, y: v, z: 0 }),
1347
+ angularU: false,
1348
+ angularV: false,
1349
+ };
1350
+ }
1351
+ if (from instanceof cylindrical_surface) {
1352
+ const radius = from.radius;
1353
+ return {
1354
+ position: from.position,
1355
+ evaluate: (u, v) => ({ x: radius * Math.cos(u), y: radius * Math.sin(u), z: v }),
1356
+ angularU: true,
1357
+ angularV: false,
1358
+ };
1359
+ }
1360
+ if (from instanceof conical_surface) {
1361
+ const radius = from.radius;
1362
+ // tan, not sin, because v is axial distance rather than distance along
1363
+ // the generator - see the equations in this method's doc comment.
1364
+ //
1365
+ // Signed, unlike the native tesselator's tan(fabs(semi_angle)): that
1366
+ // one fits its generator line from boundary samples and only needs the
1367
+ // taper's magnitude, whereas the 2D curve here was authored against the
1368
+ // spec's parameterization, where the radius grows with v by the SIGNED
1369
+ // taper and a narrowing cone runs the other way.
1370
+ const taper = Math.tan(from.semi_angle);
1371
+ return {
1372
+ position: from.position,
1373
+ evaluate: (u, v) => {
1374
+ const ring = radius + (v * taper);
1375
+ return { x: ring * Math.cos(u), y: ring * Math.sin(u), z: v };
1376
+ },
1377
+ angularU: true,
1378
+ angularV: false,
1379
+ };
1380
+ }
1381
+ if (from instanceof spherical_surface) {
1382
+ const radius = from.radius;
1383
+ return {
1384
+ position: from.position,
1385
+ evaluate: (u, v) => {
1386
+ const ring = radius * Math.cos(v);
1387
+ return {
1388
+ x: ring * Math.cos(u),
1389
+ y: ring * Math.sin(u),
1390
+ z: radius * Math.sin(v),
1391
+ };
1392
+ },
1393
+ angularU: true,
1394
+ angularV: true,
1395
+ };
1396
+ }
1397
+ // Covers degenerate_toroidal_surface, which is a subtype and shares the
1398
+ // parameterization (it only restricts the range v is meaningful over).
1399
+ if (from instanceof toroidal_surface) {
1400
+ const majorRadius = from.major_radius;
1401
+ const minorRadius = from.minor_radius;
1402
+ return {
1403
+ position: from.position,
1404
+ evaluate: (u, v) => {
1405
+ const ring = majorRadius + (minorRadius * Math.cos(v));
1406
+ return {
1407
+ x: ring * Math.cos(u),
1408
+ y: ring * Math.sin(u),
1409
+ z: minorRadius * Math.sin(v),
1410
+ };
1411
+ },
1412
+ angularU: true,
1413
+ angularV: true,
1414
+ };
1415
+ }
1416
+ return void 0;
1417
+ }
1418
+ /**
1419
+ * Extract a pcurve: a curve given in the parameter space of a basis surface
1420
+ * (ISO 10303-42). The parameter curve is extracted through the ordinary
1421
+ * curve path and its (u, v) samples are pushed through the basis surface's
1422
+ * parameterization, giving the 3D polyline every other arm of extractCurve
1423
+ * hands back.
1153
1424
  *
1154
- * @param from
1155
- * @return {CurveObject | undefined}
1425
+ * Extent comes from the parameter curve itself: a bounded one (polyline,
1426
+ * b-spline, trimmed or composite curve) carries its own, and an unbounded
1427
+ * 2D LINE or CIRCLE gets whatever the ordinary line/circle extraction gives
1428
+ * it. An EDGE_CURVE's vertex trims are 3D and are deliberately NOT inverted
1429
+ * back into parameter space here, so an edge whose parameter curve is an
1430
+ * unbounded LINE comes out at that line's own extent rather than the edge's
1431
+ * - see https://github.com/bldrs-ai/conway/issues/505.
1432
+ *
1433
+ * @param from The pcurve to extract.
1434
+ * @return {CurveObject | undefined} The mapped 3D curve, or undefined where
1435
+ * the basis surface has no parameterization here or the parameter curve
1436
+ * yields no points. Both are warned about naming the type responsible, so
1437
+ * the residue is measurable per surface family rather than as one row.
1156
1438
  */
1157
1439
  extractPScurve1(from) {
1158
- const surface = from.basis_surface;
1159
- if (!(surface instanceof plane)) {
1160
- Logger.error("PSCurve not a plane, other curves unsupported");
1440
+ const basisSurface = from.basis_surface;
1441
+ const parameterization = this.surfaceParameterization(basisSurface);
1442
+ if (parameterization === void 0) {
1443
+ Logger.warning("Unsupported PCURVE basis surface, type: " +
1444
+ `${EntityTypesAP214[basisSurface.type]}`, from.expressID);
1161
1445
  return;
1162
1446
  }
1163
- const point = surface.position.location.coordinates;
1164
- const dim = point.length;
1165
- const pointsFlattened = new Float32Array(dim * 1);
1166
- pointsFlattened[0] = point[0];
1167
- pointsFlattened[1] = point[1];
1168
- if (dim > 2) {
1169
- pointsFlattened[2] = point[2];
1170
- // pointsFlattened[ 3 ] = point[ 0 ] + ( dir[ 0 ] * mag )
1171
- // pointsFlattened[ 4 ] = point[ 1 ] + ( dir[ 1 ] * mag )
1172
- // pointsFlattened[ 5 ] = point[ 2 ] + ( dir[ 2 ] * mag )
1447
+ // ISO 10303-42 constrains reference_to_curve to a single curve item in a
1448
+ // 2D parametric context, but the field is a general representation, so
1449
+ // the curve is searched for rather than indexed blindly.
1450
+ const parameterCurveItem = from.reference_to_curve.items.find((item) => item instanceof curve);
1451
+ if (parameterCurveItem === void 0) {
1452
+ Logger.warning("PCURVE reference_to_curve carries no curve item", from.expressID);
1453
+ return;
1173
1454
  }
1174
- else {
1175
- // pointsFlattened[ 2 ] = point[ 0 ] + ( dir[ 0 ] * mag )
1176
- // pointsFlattened[ 3 ] = point[ 1 ] + ( dir[ 1 ] * mag )
1455
+ const parameterCurve = this.extractCurve(parameterCurveItem);
1456
+ const parameterCount = parameterCurve?.getPointsSize() ?? 0;
1457
+ if (parameterCurve === void 0 || parameterCount === 0) {
1458
+ // extractCurve has already warned under the parameter curve's own type.
1459
+ return;
1177
1460
  }
1461
+ // The frame is built by the same native call the surface side uses
1462
+ // (extractCylindricalSurface and friends), so the pcurve is orthonormalised
1463
+ // identically to the surface it lies on rather than by a second, subtly
1464
+ // different implementation here.
1465
+ const placement = this.conwayModel.getAxis2Placement3D(this.extractAxis2Placement3D(parameterization.position, basisSurface.localID, true));
1466
+ const transform = placement.getValues();
1467
+ // Sample density for angular parameters: the same knob the ellipse path
1468
+ // uses, so a mapped arc is tessellated like every other arc conway emits.
1469
+ const angularStep = (2 * Math.PI) / this.circleSegments;
1470
+ const localPoints = [];
1471
+ let previous = parameterCurve.get2d(0);
1472
+ localPoints.push(parameterization.evaluate(previous.x, previous.y));
1473
+ for (let index = 1; index < parameterCount; ++index) {
1474
+ const current = parameterCurve.get2d(index);
1475
+ const deltaU = current.x - previous.x;
1476
+ const deltaV = current.y - previous.y;
1477
+ // A straight run in an angular parameter is an arc in space, so a span
1478
+ // that covers real angle is subdivided rather than chorded across.
1479
+ let samples = 1;
1480
+ if (parameterization.angularU) {
1481
+ samples = Math.max(samples, Math.ceil(Math.abs(deltaU) / angularStep));
1482
+ }
1483
+ if (parameterization.angularV) {
1484
+ samples = Math.max(samples, Math.ceil(Math.abs(deltaV) / angularStep));
1485
+ }
1486
+ samples = Math.min(samples, MAXIMUM_PCURVE_SPAN_SAMPLES);
1487
+ for (let sample = 1; sample <= samples; ++sample) {
1488
+ const ratio = sample / samples;
1489
+ localPoints.push(parameterization.evaluate(previous.x + (deltaU * ratio), previous.y + (deltaV * ratio)));
1490
+ }
1491
+ previous = current;
1492
+ }
1493
+ // Column-major Glmdmat4: basis columns at 0, 4, 8, translation at 12.
1494
+ const pointsFlattened = new Float64Array(localPoints.length * this.THREE_DIMENSIONS);
1495
+ let offset = 0;
1496
+ for (const localPoint of localPoints) {
1497
+ pointsFlattened[offset] =
1498
+ (transform[0] * localPoint.x) + (transform[4] * localPoint.y) +
1499
+ (transform[8] * localPoint.z) + transform[12];
1500
+ pointsFlattened[offset + 1] =
1501
+ (transform[1] * localPoint.x) + (transform[5] * localPoint.y) +
1502
+ (transform[9] * localPoint.z) + transform[13];
1503
+ pointsFlattened[offset + 2] =
1504
+ (transform[2] * localPoint.x) + (transform[6] * localPoint.y) +
1505
+ (transform[10] * localPoint.z) + transform[14];
1506
+ offset += this.THREE_DIMENSIONS;
1507
+ }
1508
+ placement.delete();
1178
1509
  const pointsPtr = this.arrayToWasmHeap(pointsFlattened);
1179
1510
  const parameters = this.paramsGetPolyCurvePool.acquire();
1180
1511
  parameters.points = pointsPtr;
1181
- parameters.pointsLength = 1;
1182
- parameters.dimensions = dim;
1512
+ parameters.pointsLength = localPoints.length;
1513
+ parameters.dimensions = this.THREE_DIMENSIONS;
1514
+ parameters.senseAgreement = true;
1515
+ parameters.isEdge = false;
1183
1516
  const curve_ = this.conwayModel.getPolyCurve(parameters);
1184
1517
  this.paramsGetPolyCurvePool.release(parameters);
1185
1518
  this.wasmModule._free(pointsPtr);
@@ -1576,15 +1909,20 @@ export class AP214GeometryExtraction {
1576
1909
  return void 0;
1577
1910
  }
1578
1911
  /**
1579
- * Efficiently flatten the points into a Float32Array
1912
+ * Efficiently flatten the points into a Float64Array.
1913
+ *
1914
+ * Float64 because getPolyCurve, the only consumer, reinterprets the buffer
1915
+ * as `const double *`: Float32 elements decode there as unrelated numbers
1916
+ * (and read twice as many bytes as were written). Matches the IFC side's
1917
+ * helper of the same name.
1580
1918
  *
1581
1919
  * @param points - Array of AP214CartesianPoint
1582
1920
  * @param dimensions - dimensions of points
1583
- * @return {Float32Array}
1921
+ * @return {Float64Array}
1584
1922
  */
1585
- flattenPointsToFloat32Array(points, dimensions) {
1923
+ flattenPointsToFloat64Array(points, dimensions) {
1586
1924
  const totalCoordinates = points.length * dimensions;
1587
- const flatCoordinates = new Float32Array(totalCoordinates);
1925
+ const flatCoordinates = new Float64Array(totalCoordinates);
1588
1926
  let offset = 0;
1589
1927
  points.forEach((point) => {
1590
1928
  flatCoordinates.set(point.coordinates, offset);
@@ -1604,7 +1942,7 @@ export class AP214GeometryExtraction {
1604
1942
  const pointsLength = points.length;
1605
1943
  const dim = pointsLength > 0 ? points[0].coordinates.length : this.THREE_DIMENSIONS;
1606
1944
  if (pointsLength > 0) {
1607
- const pointsFlattened = this.flattenPointsToFloat32Array(points, dim);
1945
+ const pointsFlattened = this.flattenPointsToFloat64Array(points, dim);
1608
1946
  const pointsPtr = this.arrayToWasmHeap(pointsFlattened);
1609
1947
  const parameters = this.paramsGetPolyCurvePool.acquire();
1610
1948
  parameters.points = pointsPtr;
@@ -1925,7 +2263,12 @@ export class AP214GeometryExtraction {
1925
2263
  Logger.error(`Error extracting face ${EntityTypesAP214[face_.type]} - ${error.message}\t\n${error.stack} - expressID: #${face_.expressID}`);
1926
2264
  }
1927
2265
  else {
1928
- Logger.error(`Error extracting face ${EntityTypesAP214[face_.type]} - ${error} - expressID: #${face_.expressID}`);
2266
+ // A non-Error throw interpolates as "[object Object]" in a template
2267
+ // string, which is what 8 occurrences in the public baseline
2268
+ // actually say - the message names the face type and nothing else.
2269
+ // JSON first so a plain thrown object shows its fields; String() as
2270
+ // the fallback for what JSON cannot take (cycles, BigInt, symbols).
2271
+ Logger.error(`Error extracting face ${EntityTypesAP214[face_.type]} - ${describeThrown(error)} - expressID: #${face_.expressID}`);
1929
2272
  }
1930
2273
  }
1931
2274
  }
@@ -2085,6 +2428,11 @@ export class AP214GeometryExtraction {
2085
2428
  }
2086
2429
  const conwayModel = this.conwayModel;
2087
2430
  const bound3DVector = this.nativeBound3DVector();
2431
+ // Loop curves are collected before bound creation so the outer-bound
2432
+ // heuristic below can compare their extents across the whole face.
2433
+ const loopCurves = [];
2434
+ const loopIsOuter = [];
2435
+ const loopOrientations = [];
2088
2436
  for (const bound of bounds) {
2089
2437
  let vec3Array;
2090
2438
  const innerBound = bound.bound;
@@ -2160,14 +2508,97 @@ export class AP214GeometryExtraction {
2160
2508
  };
2161
2509
  }
2162
2510
  }
2511
+ // EDGE_CURVE.same_sense: does the edge's start→end direction agree
2512
+ // with the basis curve's parametrisation? For same_sense=false the
2513
+ // edge sweeps from edge_start to edge_end AGAINST the curve
2514
+ // parameter. The native circle/ellipse trim (getAP214Circle) always
2515
+ // sweeps the positive parametric arc from trim1 to trim2 (its
2516
+ // senseAgreement=false path mis-negates the sweep), so ignoring
2517
+ // same_sense selects the COMPLEMENT arc — e.g. Onshape AP242
2518
+ // exports write near-straight edges as shallow arcs of large
2519
+ // circles, and the complement is a ~340° loop that explodes the
2520
+ // face boundary (Right_Hand.step spikes; the CDT "Intersecting
2521
+ // constraint edges" cascade). Normalise instead of forwarding the
2522
+ // flag: swap the trim ends so the positive sweep is the correct
2523
+ // arc, leaving the extracted (memoised) curve running edge_end →
2524
+ // edge_start; the orientation check below compensates.
2525
+ const sameSense = edgeElement.same_sense;
2163
2526
  const trimmingArguments = {
2164
2527
  exist: !!((trimmingStart !== void 0 && trimmingEnd !== void 0)),
2165
- start: trimmingStart,
2166
- end: trimmingEnd,
2528
+ start: sameSense ? trimmingStart : trimmingEnd,
2529
+ end: sameSense ? trimmingEnd : trimmingStart,
2167
2530
  };
2168
- const curve = this.extractCurve(edgeCurve, true, true, trimmingArguments);
2531
+ // Trimmed extractions are memoised under THIS edge's localID —
2532
+ // not the basis curve's — so edges sharing one basis curve with
2533
+ // different trims each get their own arc, while the second
2534
+ // ORIENTED_EDGE user of this edge (the adjacent face) still
2535
+ // reuses the extraction. extractCurve skips its basis-curve
2536
+ // memo whenever edge trims exist (see hasEdgeTrims there); the
2537
+ // shared this.curves container keeps ownership either way.
2538
+ let curve = this.curves.get(edgeElement.localID);
2539
+ if (curve === void 0) {
2540
+ curve = this.extractCurve(edgeCurve, true, true, trimmingArguments);
2541
+ // An edge whose vertices ARE the basis curve's own endpoints
2542
+ // spans the whole curve, so its trim carries no information —
2543
+ // and on some B-splines resolving it anyway comes back as just
2544
+ // the two endpoints. When such edges form a face's outer loop
2545
+ // the loop is collinear by construction, GetBasisFromCoplanarPoints
2546
+ // finds no basis, and TriangulateBounds discards the ENTIRE face,
2547
+ // eleven good 47-point inner bounds along with it (two edges on
2548
+ // nist_ctc_02_asme1_rc.stp, bldrs-ai/conway#492).
2549
+ //
2550
+ // Deliberately gated on the result being degenerate rather than
2551
+ // on the trim being a no-op. Most whole-curve trims resolve
2552
+ // correctly — 33 of 35 on that model — and bypassing those too
2553
+ // would re-route working edges onto a different extraction and
2554
+ // memoisation path for no reason, which is churn masquerading as
2555
+ // a fix. This only fires where the current path has already
2556
+ // failed, so an edge that works keeps its exact output.
2557
+ if (curve !== void 0 &&
2558
+ curve.getPointsSize() < MINIMUM_BOUND_POINTS &&
2559
+ isWholeCurveEdge(edgeCurve, edgeStart, edgeEnd)) {
2560
+ // extractCurve memoises an untrimmed extraction under the
2561
+ // BASIS curve's localID and owns it there.
2562
+ const untrimmed = this.extractCurve(edgeCurve, true, true, { exist: false, start: void 0, end: void 0 });
2563
+ const recovered = untrimmed !== void 0 &&
2564
+ untrimmed.getPointsSize() > curve.getPointsSize();
2565
+ if (recovered) {
2566
+ // Only now is the trimmed result known to be a defect rather
2567
+ // than the curve's honest shape - a degree-1 B-spline over
2568
+ // two control points really is two points, trimmed or not,
2569
+ // and warning about those would be the false-row noise the
2570
+ // comment further down argues against.
2571
+ Logger.warning(`Whole-curve trim on edge #${edgeElement.expressID} ` +
2572
+ `(${EntityTypesAP214[edgeCurve.type]}) resolved to ` +
2573
+ `${curve.getPointsSize()} point(s); recovered ` +
2574
+ `${untrimmed.getPointsSize()} untrimmed.`);
2575
+ // The trimmed CurveObject is being dropped, and nothing else
2576
+ // holds it: it was never added to this.curves, so without
2577
+ // this its native allocation is unreachable.
2578
+ curve.delete();
2579
+ curve = untrimmed;
2580
+ }
2581
+ // Memoise under the edge either way. The adjacent face's
2582
+ // ORIENTED_EDGE shares this edge, and without the entry it
2583
+ // re-runs the whole native trim extraction, re-warns, and
2584
+ // leaks a second trimmed curve.
2585
+ if (curve !== void 0 && trimmingArguments.exist) {
2586
+ this.curves.add(edgeElement.localID, curve);
2587
+ }
2588
+ }
2589
+ else if (curve !== void 0 && trimmingArguments.exist) {
2590
+ this.curves.add(edgeElement.localID, curve);
2591
+ }
2592
+ }
2169
2593
  if (curve !== void 0) {
2170
- if (!edge.orientation) {
2594
+ // The memoised curve runs edge_start→edge_end when same_sense
2595
+ // held, edge_end→edge_start when the trims were swapped above
2596
+ // (and, for untrimmed basis curves like full B-splines, native
2597
+ // point order is parametric order — reversed relative to the
2598
+ // edge exactly when same_sense is false). Traversal for this
2599
+ // ORIENTED_EDGE must run with `orientation`, so invert on the
2600
+ // XOR of the two flags rather than orientation alone.
2601
+ if (edge.orientation !== sameSense) {
2171
2602
  // reverse curve
2172
2603
  // Logger.info("edge orientation == true, inverting curve")
2173
2604
  const invertedCurve = curve.clone();
@@ -2224,9 +2655,13 @@ export class AP214GeometryExtraction {
2224
2655
  }
2225
2656
  else {
2226
2657
  Logger.warning(`Unsupported bound ${bound.bound}`);
2227
- // Free this iteration's edge-curve vector and the face's partial
2228
- // bound vector before bailing out, or they leak per bad face.
2658
+ // Free this iteration's edge-curve vector, previously collected
2659
+ // loop curves and the face's partial bound vector before bailing
2660
+ // out, or they leak per bad face.
2229
2661
  nativeEdgeCurves.delete();
2662
+ for (const loopCurve of loopCurves) {
2663
+ loopCurve.delete();
2664
+ }
2230
2665
  bound3DVector.delete();
2231
2666
  return;
2232
2667
  }
@@ -2236,11 +2671,67 @@ export class AP214GeometryExtraction {
2236
2671
  };
2237
2672
  // Logger.info("isEdgeLoop: " + (isEdgeLoop) ? "TRUE" : "FALSE")
2238
2673
  const curve = this.conwayModel.getLoop(parameters);
2239
- // create bound vector
2674
+ // No general "bound has fewer than 3 points" check here, deliberately.
2675
+ // It looks like the obvious backstop for the #479 family and it is not:
2676
+ // a VERTEX_LOOP is one point and zero edges BY DESIGN - the degenerate
2677
+ // loop at a sphere pole or cone apex - and it is legitimate input that
2678
+ // conway is expected to handle (bldrs-ai/conway#461). Measured on the
2679
+ // smoke subset, such a check fires 50 times across Right_Hand.step and
2680
+ // nist_ctc_01_asme1_rd.stp, and every one of those is a valid
2681
+ // VERTEX_LOOP or single-edge closed loop. Reporting them would put 50
2682
+ // false "the face will be dropped" rows into the blessed baseline, which
2683
+ // is the noise problem in #478 rather than a fix for it.
2684
+ //
2685
+ // The known real cause is handled above, where it IS distinguishable
2686
+ // from legitimate input.
2687
+ loopCurves.push(curve);
2688
+ loopIsOuter.push(bound.type === EntityTypesAP214.FACE_OUTER_BOUND);
2689
+ loopOrientations.push(bound.orientation);
2690
+ vec3Array.delete();
2691
+ nativeEdgeCurves.delete();
2692
+ }
2693
+ // STEP allows a face's loops to all be plain FACE_BOUNDs; without a
2694
+ // FACE_OUTER_BOUND the native triangulators would warn ("Expected outer
2695
+ // bound, using fallback tesselation") and treat loops[0] as outer, which
2696
+ // is only right by luck (Onshape AP242 exports order hole loops first on
2697
+ // some faces). A face's outer loop strictly contains its holes, so tag
2698
+ // the loop with the largest bounding-box diagonal as the outer one.
2699
+ if (loopCurves.length > 1 && !loopIsOuter.includes(true)) {
2700
+ let largestIndex = 0;
2701
+ let largestDiagonal2 = -1;
2702
+ for (let loopIndex = 0; loopIndex < loopCurves.length; ++loopIndex) {
2703
+ const loopCurve = loopCurves[loopIndex];
2704
+ const pointCount = loopCurve.getPointsSize();
2705
+ let minX = Infinity;
2706
+ let minY = Infinity;
2707
+ let minZ = Infinity;
2708
+ let maxX = -Infinity;
2709
+ let maxY = -Infinity;
2710
+ let maxZ = -Infinity;
2711
+ for (let where = 0; where < pointCount; ++where) {
2712
+ const point = loopCurve.get3d(where);
2713
+ minX = Math.min(minX, point.x);
2714
+ minY = Math.min(minY, point.y);
2715
+ minZ = Math.min(minZ, point.z);
2716
+ maxX = Math.max(maxX, point.x);
2717
+ maxY = Math.max(maxY, point.y);
2718
+ maxZ = Math.max(maxZ, point.z);
2719
+ }
2720
+ const diagonal2 = ((maxX - minX) * (maxX - minX)) +
2721
+ ((maxY - minY) * (maxY - minY)) +
2722
+ ((maxZ - minZ) * (maxZ - minZ));
2723
+ if (pointCount > 0 && diagonal2 > largestDiagonal2) {
2724
+ largestDiagonal2 = diagonal2;
2725
+ largestIndex = loopIndex;
2726
+ }
2727
+ }
2728
+ loopIsOuter[largestIndex] = true;
2729
+ }
2730
+ for (let loopIndex = 0; loopIndex < loopCurves.length; ++loopIndex) {
2240
2731
  const parametersCreateBounds3D = {
2241
- curve: curve,
2242
- orientation: bound.orientation,
2243
- type: (bound.type === EntityTypesAP214.FACE_OUTER_BOUND) ? 0 : 1,
2732
+ curve: loopCurves[loopIndex],
2733
+ orientation: loopOrientations[loopIndex],
2734
+ type: loopIsOuter[loopIndex] ? 0 : 1,
2244
2735
  };
2245
2736
  const bound3D = this.conwayModel.createBound3D(parametersCreateBounds3D);
2246
2737
  bound3DVector.push_back(bound3D);
@@ -2248,15 +2739,19 @@ export class AP214GeometryExtraction {
2248
2739
  // bound wrapper are temporaries that must be freed to avoid leaking
2249
2740
  // native memory on every face bound.
2250
2741
  bound3D.delete();
2251
- curve.delete();
2252
- vec3Array.delete();
2253
- nativeEdgeCurves.delete();
2742
+ loopCurves[loopIndex].delete();
2254
2743
  }
2255
2744
  const surface = from.face_geometry;
2256
2745
  // add face to geometry
2257
2746
  const nativeSurface = (new (this.wasmModule.IfcSurface));
2258
2747
  this.extractSurface(surface, nativeSurface);
2259
2748
  nativeSurface.sameSense = from.same_sense;
2749
+ // Declares the flag above as real, so the tessellator may orient the
2750
+ // face against the surface normal. Extractors that leave this false
2751
+ // (the IFC path today) keep the older projection-based orientation
2752
+ // rather than having a default-constructed sense read as an answer.
2753
+ // See https://github.com/bldrs-ai/conway/issues/459.
2754
+ nativeSurface.sameSenseKnown = true;
2260
2755
  const parameters = {
2261
2756
  boundsArray: bound3DVector,
2262
2757
  advancedBrep: true,
@@ -2346,11 +2841,24 @@ export class AP214GeometryExtraction {
2346
2841
  * @param nativeSurface
2347
2842
  */
2348
2843
  extractSurfaceOfLinearExtrusion(from, nativeSurface) {
2349
- const profile = this.extractProfile(from.swept_curve);
2350
- if (profile?.nativeProfile === void 0) {
2844
+ // AP214's swept_curve is a bare curve, not a profile_def, so it goes
2845
+ // through extractCurve + createNativeIfcProfile like the revolution
2846
+ // path does. extractProfile() never fills nativeProfile for bare
2847
+ // curves, which silently dropped every linear-extrusion face (1,370
2848
+ // faces on the Jetenginestep compressor shaft alone).
2849
+ const nativeCurve = this.extractCurve(from.swept_curve);
2850
+ if (nativeCurve === void 0) {
2351
2851
  Logger.warning("Couldn't get curve profile for linear extrusion surface");
2352
2852
  return;
2353
2853
  }
2854
+ const parameters = {
2855
+ curve: nativeCurve,
2856
+ holes: this.nativeVectorCurve(),
2857
+ isConvex: false,
2858
+ isComposite: false,
2859
+ profiles: this.nativeVectorProfile(),
2860
+ };
2861
+ const nativeProfile = this.conwayModel.createNativeIfcProfile(parameters);
2354
2862
  const extrusionAxis = from.extrusion_axis;
2355
2863
  const depth = extrusionAxis.magnitude;
2356
2864
  const directionCoords = extrusionAxis.orientation.direction_ratios;
@@ -2362,7 +2870,7 @@ export class AP214GeometryExtraction {
2362
2870
  y: directionCoords[1],
2363
2871
  z: directionCoords[2],
2364
2872
  },
2365
- profile: profile?.nativeProfile,
2873
+ profile: nativeProfile,
2366
2874
  };
2367
2875
  }
2368
2876
  /**
@@ -2473,9 +2981,13 @@ export class AP214GeometryExtraction {
2473
2981
  pointer = this.wasmModule._malloc(numBytes);
2474
2982
  capacity = maxPossibleFloats;
2475
2983
  }
2476
- // 2) Create a Float64Array view into WASM memory
2477
- // We only need to create a subarray up to the capacity
2478
- const wasmFloat64View = this.wasmModule.HEAPF64.subarray(pointer / bytesPerElement, pointer / bytesPerElement + capacity);
2984
+ // 2) Create a Float64Array view into WASM memory, over the heap as it is
2985
+ // after the _malloc above rather than over whatever view the module had
2986
+ // cached before it - see wasm_heap.ts for why those can differ. Built
2987
+ // rather than subarray'd off HEAPF64 for the same reason: subarray clamps
2988
+ // an out-of-range window into a short one, and the set() loop below would
2989
+ // then quietly write somewhere other than the allocation (#485).
2990
+ const wasmFloat64View = wasmHeapView(this.wasmModule, Float64Array, pointer, capacity);
2479
2991
  // 3) Single pass to skip consecutive duplicates, fill up the wasm array
2480
2992
  let offset = 0;
2481
2993
  let prevLocalID = -1;
@@ -2792,6 +3304,38 @@ export class AP214GeometryExtraction {
2792
3304
  result.setValues(v);
2793
3305
  return result;
2794
3306
  }
3307
+ /**
3308
+ * The root transform taking a shape representation's own length unit
3309
+ * into conway's world space.
3310
+ *
3311
+ * World space is METRES. That convention is set by the IFC path, which
3312
+ * folds `linearScalingFactor` — metres per file unit — into its
3313
+ * coordination matrix (`compat/web-ifc/coordination_f64.ts`). STEP
3314
+ * geometry is emitted in raw file coordinates instead, so the
3315
+ * whole-model unit conversion has to ride on this root transform.
3316
+ *
3317
+ * The factor is therefore metres-per-file-unit itself: a millimetre
3318
+ * file scales by 1e-3. It is NOT the reciprocal — using `1 / unitInM`
3319
+ * put every millimetre STEP model into world space 1e6x too large,
3320
+ * and 1e6x out of step with any IFC model federated beside it. That
3321
+ * went unseen because Share frames the camera from the model's own
3322
+ * bounds, which absorbs a uniform scale error, and because the
3323
+ * regression digests hash geometry in file coordinates, which this
3324
+ * transform never touches.
3325
+ * See https://github.com/bldrs-ai/conway/issues/458.
3326
+ *
3327
+ * @param shapeRepresentation The representation whose unit context to read.
3328
+ * @return The scale transform, or undefined if no length unit is declared.
3329
+ */
3330
+ rootUnitScaleTransform(shapeRepresentation) {
3331
+ const sourceShapeContext = shapeRepresentation.context_of_items.findVariant(global_unit_assigned_context)?.units?.
3332
+ find((unit) => unit.findVariant(length_unit))?.findVariant(length_unit);
3333
+ if (sourceShapeContext === void 0) {
3334
+ return void 0;
3335
+ }
3336
+ const sourceUnitInM = this.convertToMetres(sourceShapeContext) ?? 1;
3337
+ return this.uniformScaleAffine(this.identity3DNativeMatrix, sourceUnitInM);
3338
+ }
2795
3339
  /**
2796
3340
  *
2797
3341
  */
@@ -2809,7 +3353,14 @@ export class AP214GeometryExtraction {
2809
3353
  }
2810
3354
  }
2811
3355
  catch (error) {
2812
- Logger.error(`Error populating styled item map for item ${styledItem.localID}: ${error}`);
3356
+ if (!this.quietRecoverableLogging) {
3357
+ // toString(), not localID: localID is the dense internal index and
3358
+ // means nothing against the source file, and these land in the
3359
+ // regression run's `expressids` column where the whole point is to
3360
+ // be able to go look at the record. It also spells the inline case
3361
+ // honestly rather than passing off an index as a reference.
3362
+ Logger.error(`Error populating styled item map: ${error}`, styledItem.toString());
3363
+ }
2813
3364
  }
2814
3365
  }
2815
3366
  for (const overridingStyledItem of overridingStyledItems) {
@@ -2819,7 +3370,9 @@ export class AP214GeometryExtraction {
2819
3370
  }
2820
3371
  }
2821
3372
  catch (error) {
2822
- Logger.error(`Error populating styled item map for item ${overridingStyledItem.localID}: ${error}`);
3373
+ if (!this.quietRecoverableLogging) {
3374
+ Logger.error(`Error populating overriding styled item map: ${error}`, overridingStyledItem.toString());
3375
+ }
2823
3376
  }
2824
3377
  }
2825
3378
  }
@@ -2902,16 +3455,27 @@ export class AP214GeometryExtraction {
2902
3455
  * @return {[ExtractResult, AP214SceneBuilder]} - Enum indicating extraction result
2903
3456
  * + Geometry array
2904
3457
  */
2905
- extractAP214GeometryData(logTime = false) {
2906
- let result = ExtractResult.INCOMPLETE;
2907
- const startTime = Date.now();
2908
- // this.extractLinearScalingFactor()
2909
- const previousMemoizationState = this.model.elementMemoization;
2910
- // populate relMaterialsMap
2911
- // const relAssociatesMaterials = this.model.types(AP214RelAssociatesMaterial)
3458
+ /**
3459
+ * Prepare per-unit demand extraction (STEP demand parity phase 2)
3460
+ * without executing any geometry work: builds the assembly tree and
3461
+ * thunks exactly like the whole-model walk, but the root executions
3462
+ * are captured as an ordered list of UNITS — a childless root is one
3463
+ * unit; a root with children is flattened one level (one unit per
3464
+ * immediate child, in order, then one for the root's own items), so
3465
+ * single-root assemblies still pump progressively per part. Executing
3466
+ * every unit in order then calling {@link finishDemandExtraction}
3467
+ * reproduces the whole-model walk exactly — the classic
3468
+ * {@link extractAP214GeometryData} runs through this same path.
3469
+ * Idempotent.
3470
+ */
3471
+ // eslint-disable-next-line max-lines-per-function
3472
+ prepareDemandExtraction() {
3473
+ if (this.demandUnits_ !== void 0) {
3474
+ return;
3475
+ }
2912
3476
  const model = this.model;
2913
3477
  const treeMap = new Map();
2914
- const makeThunk = (representation, owningElementLocalID, mappedTreeNode) => {
3478
+ const makeThunk = (representation, owningElementLocalID, mappedTreeNode, slice) => {
2915
3479
  return (owningLocalID, parentTransform) => {
2916
3480
  owningLocalID ??= owningElementLocalID;
2917
3481
  const mappedItem = mappedTreeNode !== void 0 || parentTransform !== void 0;
@@ -2920,8 +3484,12 @@ export class AP214GeometryExtraction {
2920
3484
  if (parentTransform !== void 0) {
2921
3485
  this.scene.addTransform(representation.localID, parentTransform.getValues(), parentTransform, true);
2922
3486
  }
2923
- if (mappedTreeNode?.children !== void 0) {
2924
- for (const [childLocalID, childOwningLocalID, childTransform] of mappedTreeNode.children) {
3487
+ if (mappedTreeNode?.children !== void 0 &&
3488
+ (slice === void 0 || slice.childStart !== slice.childEnd)) {
3489
+ const sliceChildren = slice !== void 0 ?
3490
+ mappedTreeNode.children.slice(slice.childStart, slice.childEnd) :
3491
+ mappedTreeNode.children;
3492
+ for (const [childLocalID, childOwningLocalID, childTransform] of sliceChildren) {
2925
3493
  const mappedChild = treeMap.get(childLocalID);
2926
3494
  const enterChildStackDepth = this.scene.stackLength;
2927
3495
  const enterChildParent = this.scene.currentParent;
@@ -2955,7 +3523,10 @@ export class AP214GeometryExtraction {
2955
3523
  mappedChild.thunk(childOwningLocalID, childTransform);
2956
3524
  }
2957
3525
  catch (ex) {
2958
- if (ex instanceof Error) {
3526
+ if (this.quietRecoverableLogging) {
3527
+ // Preview prefix: dangling children are expected — skip quietly.
3528
+ }
3529
+ else if (ex instanceof Error) {
2959
3530
  Logger.error(`Error processing child shape_representation: \n\t${ex.name}\n\t${ex.message}\n\texpressID: #${this.model.getExpressIDByLocalID(childLocalID)}`);
2960
3531
  }
2961
3532
  else {
@@ -2966,12 +3537,14 @@ export class AP214GeometryExtraction {
2966
3537
  while (this.scene.stackLength > enterChildStackDepth) {
2967
3538
  this.scene.popTransform();
2968
3539
  }
2969
- if (this.scene.stackLength !== enterChildStackDepth || this.scene.currentParent !== enterChildParent) {
3540
+ if ((this.scene.stackLength !== enterChildStackDepth ||
3541
+ this.scene.currentParent !== enterChildParent) && !this.quietRecoverableLogging) {
2970
3542
  Logger.error(`Stack length mismatch after processing child shape_representation ${this.scene.currentParent} ${enterChildParent} expressID: #${representation.expressID}`);
2971
3543
  }
2972
3544
  }
2973
3545
  }
2974
- for (const item of representation.items) {
3546
+ const includeItems = slice?.includeItems ?? true;
3547
+ for (const item of includeItems ? representation.items : []) {
2975
3548
  try {
2976
3549
  if (item instanceof placement) {
2977
3550
  this.extractPlacement(item, mappedItem);
@@ -2995,7 +3568,12 @@ export class AP214GeometryExtraction {
2995
3568
  }
2996
3569
  catch (ex) {
2997
3570
  if (ex instanceof Error) {
2998
- Logger.error(`Error processing representation item: \n\t${ex.name}\n\t${ex.message}\n\texpressID: #${item.expressID}`);
3571
+ // Stack included for the same reason extractFaces includes it:
3572
+ // this family's message is the same string for every occurrence
3573
+ // ("Value in select must be populated" accounts for all 274 in
3574
+ // the NIST AP242 set), so without a stack there is nothing to
3575
+ // tell one occurrence from another or say WHICH select failed.
3576
+ Logger.error(`Error processing representation item: \n\t${ex.name}\n\t${ex.message}\n\t${ex.stack}\n\texpressID: #${item.expressID}`);
2999
3577
  }
3000
3578
  else {
3001
3579
  Logger.error(`Unknown exception processing representation item (${ex}) expressID: #${item.expressID}`);
@@ -3005,16 +3583,25 @@ export class AP214GeometryExtraction {
3005
3583
  while (this.scene.stackLength > entryTransformDepth) {
3006
3584
  this.scene.popTransform();
3007
3585
  }
3008
- if (this.scene.stackLength !== entryTransformDepth || this.scene.currentParent !== currentParent) {
3586
+ if ((this.scene.stackLength !== entryTransformDepth ||
3587
+ this.scene.currentParent !== currentParent) && !this.quietRecoverableLogging) {
3009
3588
  Logger.error(`Stack length mismatch after processing shape_representation ${this.scene.currentParent} ${currentParent} expressID: #${representation.expressID}`);
3010
3589
  }
3011
3590
  };
3012
3591
  };
3013
- try {
3592
+ // Roots the whole-model walk would execute, in execution order —
3593
+ // expanded into units at the end of preparation (children arrays
3594
+ // are final only once every edge loop has run).
3595
+ const pendingRoots = [];
3596
+ {
3014
3597
  this.scene.clearParentStack();
3015
3598
  // 256 meg limit for memoization - smaller models get a big
3016
3599
  // win from memoization, but much larger models it uses far too much heap.
3017
3600
  const MEMOIZATION_THRESHOLD = 256 * 1024 * 1024;
3601
+ // Remembered for finishDemandExtraction / the classic wrapper's
3602
+ // finally — the pump spans many calls, so restoration cannot live
3603
+ // in a single method's finally anymore.
3604
+ this.demandMemoizationRestore_ = this.model.elementMemoization;
3018
3605
  if (this.lowMemoryMode || model.bufferBytesize > MEMOIZATION_THRESHOLD) {
3019
3606
  this.model.elementMemoization = false;
3020
3607
  }
@@ -3040,9 +3627,21 @@ export class AP214GeometryExtraction {
3040
3627
  }
3041
3628
  }
3042
3629
  for (const contextDependentShapeRepresentation of contextDependentShapeRepresentations) {
3043
- const assembly = contextDependentShapeRepresentation.represented_product_relation;
3630
+ // Per-record containment: reference getters throw on
3631
+ // malformed/dangling records (a mid-parse PREFIX model — the
3632
+ // preview channel's snapshots — always has a truncated tail).
3633
+ // One bad edge record skips, it must not abort the whole
3634
+ // preparation.
3635
+ let assembly;
3636
+ let shapeRelationship;
3637
+ try {
3638
+ assembly = contextDependentShapeRepresentation.represented_product_relation;
3639
+ shapeRelationship = contextDependentShapeRepresentation.representation_relation;
3640
+ }
3641
+ catch {
3642
+ continue;
3643
+ }
3044
3644
  const owningLocalID = assembly.localID;
3045
- const shapeRelationship = contextDependentShapeRepresentation.representation_relation;
3046
3645
  shapeRepresentationRelationshipsSeen.add(shapeRelationship.localID);
3047
3646
  /* Exporters disagree on the rep_1/rep_2 order of an assembly
3048
3647
  * placement relationship: as1-style files write (child, parent),
@@ -3056,8 +3655,15 @@ export class AP214GeometryExtraction {
3056
3655
  * SDR-bound product definition against them, and keep the legacy
3057
3656
  * (child, parent) reading when the match is ambiguous/unresolvable.
3058
3657
  */
3059
- let sourceShape = shapeRelationship.rep_1;
3060
- let targetShape = shapeRelationship.rep_2;
3658
+ let sourceShape;
3659
+ let targetShape;
3660
+ try {
3661
+ sourceShape = shapeRelationship.rep_1;
3662
+ targetShape = shapeRelationship.rep_2;
3663
+ }
3664
+ catch {
3665
+ continue;
3666
+ }
3061
3667
  let orientationFlipped = false;
3062
3668
  try {
3063
3669
  const nauo = assembly.definition;
@@ -3078,7 +3684,15 @@ export class AP214GeometryExtraction {
3078
3684
  catch {
3079
3685
  // Malformed NAUO/PDS reference — keep the legacy orientation.
3080
3686
  }
3081
- const [transform, isContinue] = this.doTransforms(shapeRelationship, sourceShape, targetShape, owningLocalID, orientationFlipped);
3687
+ let transform;
3688
+ let isContinue;
3689
+ try {
3690
+ [transform, isContinue] = this.doTransforms(shapeRelationship, sourceShape, targetShape, owningLocalID, orientationFlipped);
3691
+ }
3692
+ catch {
3693
+ // Malformed transform record (prefix truncation) — skip the edge.
3694
+ continue;
3695
+ }
3082
3696
  if (isContinue) {
3083
3697
  continue;
3084
3698
  }
@@ -3089,6 +3703,8 @@ export class AP214GeometryExtraction {
3089
3703
  sourceNode = { parents: 1 };
3090
3704
  treeMap.set(sourceID, sourceNode);
3091
3705
  sourceNode.thunk = makeThunk(sourceShape, owningLocalID, sourceNode);
3706
+ sourceNode.rep = sourceShape;
3707
+ sourceNode.owningLocalID = owningLocalID;
3092
3708
  }
3093
3709
  else {
3094
3710
  sourceNode.parents ??= 0;
@@ -3117,9 +3733,21 @@ export class AP214GeometryExtraction {
3117
3733
  * source and rep_2 is the target, whereas in
3118
3734
  * context_dependent_shape_representation, the relationship is inverted.
3119
3735
  */
3120
- const sourceShape = shapeRelationship.rep_2;
3121
- const targetShape = shapeRelationship.rep_1;
3122
- const [transform, isContinue] = this.doTransforms(shapeRelationship, sourceShape, targetShape, owningLocalID);
3736
+ let sourceShape;
3737
+ let targetShape;
3738
+ let transform;
3739
+ let isContinue;
3740
+ try {
3741
+ sourceShape = shapeRelationship.rep_2;
3742
+ targetShape = shapeRelationship.rep_1;
3743
+ [transform, isContinue] =
3744
+ this.doTransforms(shapeRelationship, sourceShape, targetShape, owningLocalID);
3745
+ }
3746
+ catch {
3747
+ // Malformed relationship/transform record (prefix truncation)
3748
+ // — skip the edge.
3749
+ continue;
3750
+ }
3123
3751
  if (isContinue) {
3124
3752
  continue;
3125
3753
  }
@@ -3135,6 +3763,8 @@ export class AP214GeometryExtraction {
3135
3763
  ++sourceNode.parents;
3136
3764
  }
3137
3765
  sourceNode.thunk = makeThunk(sourceShape, owningLocalID, sourceNode);
3766
+ sourceNode.rep = sourceShape;
3767
+ sourceNode.owningLocalID = owningLocalID;
3138
3768
  let targetNode = treeMap.get(targetID);
3139
3769
  if (targetNode === void 0) {
3140
3770
  targetNode = { parents: 0, children: [[sourceID, owningLocalID, transform]] };
@@ -3147,12 +3777,20 @@ export class AP214GeometryExtraction {
3147
3777
  }
3148
3778
  const shapeDefinitions = model.types(shape_definition_representation);
3149
3779
  for (const shapeDefinitionRepresentation of shapeDefinitions) {
3150
- const shapeRepresentation = shapeDefinitionRepresentation.used_representation;
3151
- if (!(shapeRepresentation instanceof shape_representation)) {
3780
+ let shapeRepresentation;
3781
+ let definition;
3782
+ try {
3783
+ shapeRepresentation = shapeDefinitionRepresentation.used_representation;
3784
+ if (!(shapeRepresentation instanceof shape_representation)) {
3785
+ continue;
3786
+ }
3787
+ definition = shapeDefinitionRepresentation.definition;
3788
+ }
3789
+ catch {
3790
+ // Malformed SDR record (prefix truncation) — skip it.
3152
3791
  continue;
3153
3792
  }
3154
3793
  //this.scene.clearParentStack()
3155
- const definition = shapeDefinitionRepresentation.definition;
3156
3794
  const owningElementLocalID = definition.localID;
3157
3795
  let treeNode = treeMap.get(shapeRepresentation.localID);
3158
3796
  const hasMappedNode = treeNode !== void 0;
@@ -3166,18 +3804,26 @@ export class AP214GeometryExtraction {
3166
3804
  const mappedTreeNode = treeNode;
3167
3805
  const thunk = makeThunk(shapeRepresentation, owningElementLocalID, mappedTreeNode);
3168
3806
  mappedTreeNode.thunk = thunk;
3807
+ mappedTreeNode.rep = shapeRepresentation;
3808
+ mappedTreeNode.owningLocalID = owningElementLocalID;
3169
3809
  if (!hasMappedNode) {
3170
3810
  mappedTreeNode.processed = true;
3171
- const sourceShapeContext = shapeRepresentation.context_of_items.findVariant(global_unit_assigned_context)?.units?.
3172
- find(unit => unit.findVariant(length_unit))?.findVariant(length_unit);
3173
3811
  let scaleTransform = void 0;
3174
- if (sourceShapeContext !== void 0) {
3175
- const sourceUnitInM = (this.convertToMetres(sourceShapeContext) ?? 1);
3176
- scaleTransform =
3177
- this.uniformScaleAffine(this.identity3DNativeMatrix, 1 / sourceUnitInM);
3812
+ try {
3813
+ scaleTransform = this.rootUnitScaleTransform(shapeRepresentation);
3814
+ }
3815
+ catch {
3816
+ // Malformed unit context (prefix truncation) — no unit scale.
3817
+ scaleTransform = void 0;
3178
3818
  }
3179
- // not an assembly mapped item, just extract the representation
3180
- thunk(void 0, scaleTransform);
3819
+ // not an assembly mapped item — capture as a pending root
3820
+ // instead of executing inline (these nodes are childless by
3821
+ // construction: they were absent from the tree before this
3822
+ // loop, and all edges were added by the earlier loops).
3823
+ pendingRoots.push({
3824
+ node: mappedTreeNode,
3825
+ scaleTransform,
3826
+ });
3181
3827
  continue;
3182
3828
  }
3183
3829
  }
@@ -3195,45 +3841,168 @@ export class AP214GeometryExtraction {
3195
3841
  const mappedTreeNode = treeNode;
3196
3842
  const owningElementLocalID = shapeRepresentation.localID;
3197
3843
  mappedTreeNode.thunk = makeThunk(shapeRepresentation, owningElementLocalID, mappedTreeNode);
3844
+ mappedTreeNode.rep = shapeRepresentation;
3845
+ mappedTreeNode.owningLocalID = owningElementLocalID;
3198
3846
  }
3199
- // All thunks are set, now we can execute the full
3200
- // assembly tree.
3847
+ // All thunks are set — capture remaining assembly-tree roots as
3848
+ // pending roots in the exact order the whole-model walk executed
3849
+ // them.
3201
3850
  for (const [sourceID, mappedNode] of treeMap.entries()) {
3202
3851
  if ((mappedNode.parents ?? 0) === 0 && mappedNode.thunk !== void 0 && mappedNode.processed !== true) {
3203
- //this.scene.clearParentStack()
3204
- const shapeRepresentation = this.model.getTypedElementByLocalID(sourceID, shape_representation);
3205
- const sourceShapeContext = shapeRepresentation.context_of_items.findVariant(global_unit_assigned_context)?.units?.
3206
- find(unit => unit.findVariant(length_unit))?.findVariant(length_unit);
3207
3852
  let scaleTransform = void 0;
3208
- if (sourceShapeContext !== void 0) {
3209
- const sourceUnitInM = (this.convertToMetres(sourceShapeContext) ?? 1);
3210
- scaleTransform =
3211
- this.uniformScaleAffine(this.identity3DNativeMatrix, 1 / sourceUnitInM);
3853
+ try {
3854
+ const shapeRepresentation = this.model.getTypedElementByLocalID(sourceID, shape_representation);
3855
+ scaleTransform = this.rootUnitScaleTransform(shapeRepresentation);
3856
+ }
3857
+ catch {
3858
+ // Malformed unit context (prefix truncation) — no unit scale.
3859
+ scaleTransform = void 0;
3212
3860
  }
3213
- // not an assembly mapped item, just extract the representation
3214
- mappedNode.thunk(void 0, scaleTransform);
3861
+ pendingRoots.push({ node: mappedNode, scaleTransform });
3215
3862
  }
3216
3863
  }
3217
- // Run any deferred face tessellation across the native thread pool
3218
- // before geometry can be read or temporaries freed.
3219
- this.finalizeStagedFaces();
3220
- if (RegressionCaptureState.memoization !== MemoizationCapture.FULL) {
3221
- this.model.geometry.deleteTemporaries();
3864
+ // Expand pending roots into ordered execution units: childless
3865
+ // roots run their original thunk whole; roots with children are
3866
+ // flattened one level — one unit per immediate child (in order),
3867
+ // then one for the root's own representation items, matching the
3868
+ // thunk body's children-then-items order exactly.
3869
+ const units = [];
3870
+ for (const root of pendingRoots) {
3871
+ const node = root.node;
3872
+ const scaleTransform = root.scaleTransform;
3873
+ const childCount = node.children?.length ?? 0;
3874
+ if (childCount === 0 || node.rep === void 0) {
3875
+ units.push(() => node.thunk(void 0, scaleTransform));
3876
+ continue;
3877
+ }
3878
+ for (let child = 0; child < childCount; ++child) {
3879
+ const sliced = makeThunk(node.rep, node.owningLocalID, node, { childStart: child, childEnd: child + 1, includeItems: false });
3880
+ units.push(() => sliced(void 0, scaleTransform));
3881
+ }
3882
+ const itemsOnly = makeThunk(node.rep, node.owningLocalID, node, { childStart: 0, childEnd: 0, includeItems: true });
3883
+ units.push(() => itemsOnly(void 0, scaleTransform));
3884
+ }
3885
+ this.demandUnits_ = units;
3886
+ }
3887
+ }
3888
+ /**
3889
+ * Total demand units (see prepareDemandExtraction).
3890
+ *
3891
+ * @return {number} The unit count (0 before preparation).
3892
+ */
3893
+ get demandUnitCount() {
3894
+ return this.demandUnits_?.length ?? 0;
3895
+ }
3896
+ /**
3897
+ * Units already executed by extractDemandUnitBatch.
3898
+ *
3899
+ * @return {number} The cursor.
3900
+ */
3901
+ get demandUnitCursor() {
3902
+ return this.demandCursor_;
3903
+ }
3904
+ /**
3905
+ * Advance the unit cursor WITHOUT executing (parse-time preview
3906
+ * generations resume a global ordinal on a fresh prefix extraction —
3907
+ * earlier units were already emitted from earlier generations).
3908
+ *
3909
+ * @param count Units to skip.
3910
+ */
3911
+ skipDemandUnits(count) {
3912
+ const units = this.demandUnits_;
3913
+ if (units === void 0) {
3914
+ return;
3915
+ }
3916
+ this.demandCursor_ = Math.min(this.demandCursor_ + Math.max(count, 0), units.length);
3917
+ }
3918
+ /**
3919
+ * Execute the next `count` demand units (STEP demand parity phase 2)
3920
+ * — the per-unit pump behind the shim's ExtractGeometryBatch for
3921
+ * AP214 models. Staged face tessellation is finalized after every
3922
+ * batch so captured geometry is complete and readable.
3923
+ *
3924
+ * @param count Max units to execute this call (min 1).
3925
+ * @return {number} Units actually executed.
3926
+ */
3927
+ extractDemandUnitBatch(count) {
3928
+ const units = this.demandUnits_;
3929
+ if (units === void 0) {
3930
+ return 0;
3931
+ }
3932
+ const end = Math.min(this.demandCursor_ + Math.max(count, 1), units.length);
3933
+ let executed = 0;
3934
+ for (; this.demandCursor_ < end; ++this.demandCursor_) {
3935
+ try {
3936
+ units[this.demandCursor_]();
3937
+ ++executed;
3222
3938
  }
3223
- result = ExtractResult.COMPLETE;
3224
- // free buffer at the end if you don't need it anymore
3225
- if (this.pointBuffer?.pointer) {
3226
- this.wasmModule._free(this.pointBuffer.pointer);
3227
- this.pointBuffer = null;
3939
+ catch (ex) {
3940
+ if (this.quietRecoverableLogging) {
3941
+ // Preview prefix: failing units are expected — retried on a
3942
+ // richer generation, and the durable extraction re-runs all.
3943
+ }
3944
+ else if (ex instanceof Error) {
3945
+ Logger.error(`Error processing demand unit: \n\t${ex.name}\n\t${ex.message}`);
3946
+ }
3947
+ else {
3948
+ Logger.error(`Unknown exception processing demand unit (${ex})`);
3949
+ }
3228
3950
  }
3229
- return [result, this.scene, this.productShapeMap];
3951
+ }
3952
+ // Deferred face tessellation must land before geometry is read.
3953
+ this.finalizeStagedFaces();
3954
+ return executed;
3955
+ }
3956
+ /**
3957
+ * Complete a demand extraction after every unit has run: final staged
3958
+ * tessellation, temporaries cleanup, point-buffer release and
3959
+ * memoization restore — the tail the whole-model walk runs.
3960
+ *
3961
+ * @return {[ExtractResult, AP214SceneBuilder, AP214ProductShapeMap]}
3962
+ * The completed extraction result.
3963
+ */
3964
+ finishDemandExtraction() {
3965
+ this.finalizeStagedFaces();
3966
+ if (RegressionCaptureState.memoization !== MemoizationCapture.FULL) {
3967
+ this.model.geometry.deleteTemporaries();
3968
+ }
3969
+ if (this.pointBuffer?.pointer) {
3970
+ this.wasmModule._free(this.pointBuffer.pointer);
3971
+ this.pointBuffer = null;
3972
+ }
3973
+ if (this.demandMemoizationRestore_ !== void 0) {
3974
+ this.model.elementMemoization = this.demandMemoizationRestore_;
3975
+ this.demandMemoizationRestore_ = void 0;
3976
+ }
3977
+ return [ExtractResult.COMPLETE, this.scene, this.productShapeMap];
3978
+ }
3979
+ /**
3980
+ * Whole-model extraction: prepare + run every demand unit + finish —
3981
+ * a single code path with the per-unit pump, so pumped and classic
3982
+ * extractions are identical by construction.
3983
+ *
3984
+ * @param logTime Unused (kept for call compatibility).
3985
+ * @return {[ExtractResult, AP214SceneBuilder, AP214ProductShapeMap]}
3986
+ * The completed extraction result.
3987
+ */
3988
+ // eslint-disable-next-line no-unused-vars
3989
+ extractAP214GeometryData(logTime = false) {
3990
+ try {
3991
+ this.prepareDemandExtraction();
3992
+ while (this.demandCursor_ < this.demandUnitCount) {
3993
+ this.extractDemandUnitBatch(this.demandUnitCount);
3994
+ }
3995
+ return this.finishDemandExtraction();
3230
3996
  }
3231
3997
  finally {
3232
3998
  // Ensure no staged face jobs outlive extraction (their target
3233
3999
  // geometry may be freed once the model is invalidated), even if
3234
4000
  // extraction exits through an exception.
3235
4001
  this.finalizeStagedFaces();
3236
- this.model.elementMemoization = previousMemoizationState;
4002
+ if (this.demandMemoizationRestore_ !== void 0) {
4003
+ this.model.elementMemoization = this.demandMemoizationRestore_;
4004
+ this.demandMemoizationRestore_ = void 0;
4005
+ }
3237
4006
  }
3238
4007
  }
3239
4008
  /**