brep-io-kernel 1.0.0

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 (403) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +144 -0
  3. package/dist-kernel/brep-kernel.js +74699 -0
  4. package/dist-kernel/help/CONTRIBUTING.html +248 -0
  5. package/dist-kernel/help/LICENSE.html +248 -0
  6. package/dist-kernel/help/MODELING.png +0 -0
  7. package/dist-kernel/help/PMI.png +0 -0
  8. package/dist-kernel/help/SKETCH.png +0 -0
  9. package/dist-kernel/help/assembly-constraints__Coincident_Constraint_dialog.png +0 -0
  10. package/dist-kernel/help/assembly-constraints___Angle_Constraint_dialog.png +0 -0
  11. package/dist-kernel/help/assembly-constraints___Distance_Constraint_dialog.png +0 -0
  12. package/dist-kernel/help/assembly-constraints___Fixed_Constraint_dialog.png +0 -0
  13. package/dist-kernel/help/assembly-constraints___Parallel_Constraint_dialog.png +0 -0
  14. package/dist-kernel/help/assembly-constraints___Touch_Align_Constraint_dialog.png +0 -0
  15. package/dist-kernel/help/assembly-constraints__angle-constraint.html +248 -0
  16. package/dist-kernel/help/assembly-constraints__coincident-constraint.html +248 -0
  17. package/dist-kernel/help/assembly-constraints__distance-constraint.html +248 -0
  18. package/dist-kernel/help/assembly-constraints__fixed-constraint.html +248 -0
  19. package/dist-kernel/help/assembly-constraints__parallel-constraint.html +248 -0
  20. package/dist-kernel/help/assembly-constraints__solver.html +248 -0
  21. package/dist-kernel/help/assembly-constraints__touch-align-constraint.html +248 -0
  22. package/dist-kernel/help/brep-api.html +263 -0
  23. package/dist-kernel/help/brep-kernel.html +258 -0
  24. package/dist-kernel/help/brep-model.html +248 -0
  25. package/dist-kernel/help/cylindrical-face-radius-embedding.html +290 -0
  26. package/dist-kernel/help/dialog-screenshots.html +248 -0
  27. package/dist-kernel/help/extruded-sketch-radius-embedding.html +336 -0
  28. package/dist-kernel/help/features__Assembly_Component_dialog.png +0 -0
  29. package/dist-kernel/help/features__Boolean_dialog.png +0 -0
  30. package/dist-kernel/help/features__Chamfer_dialog.png +0 -0
  31. package/dist-kernel/help/features__Datium_dialog.png +0 -0
  32. package/dist-kernel/help/features__Extrude_dialog.png +0 -0
  33. package/dist-kernel/help/features__Fillet_dialog.png +0 -0
  34. package/dist-kernel/help/features__Helix_dialog.png +0 -0
  35. package/dist-kernel/help/features__Hole_dialog.png +0 -0
  36. package/dist-kernel/help/features__Image_Heightmap_Solid_dialog.png +0 -0
  37. package/dist-kernel/help/features__Image_to_Face_dialog.png +0 -0
  38. package/dist-kernel/help/features__Import_3D_Model_dialog.png +0 -0
  39. package/dist-kernel/help/features__Loft_dialog.png +0 -0
  40. package/dist-kernel/help/features__Mirror_dialog.png +0 -0
  41. package/dist-kernel/help/features__Offset_Shell_dialog.png +0 -0
  42. package/dist-kernel/help/features__Overlap_Cleanup_dialog.png +0 -0
  43. package/dist-kernel/help/features__Pattern_Linear_dialog.png +0 -0
  44. package/dist-kernel/help/features__Pattern_Radial_dialog.png +0 -0
  45. package/dist-kernel/help/features__Pattern_dialog.png +0 -0
  46. package/dist-kernel/help/features__Plane_dialog.png +0 -0
  47. package/dist-kernel/help/features__Primitive_Cone_dialog.png +0 -0
  48. package/dist-kernel/help/features__Primitive_Cube_dialog.png +0 -0
  49. package/dist-kernel/help/features__Primitive_Cylinder_dialog.png +0 -0
  50. package/dist-kernel/help/features__Primitive_Pyramid_dialog.png +0 -0
  51. package/dist-kernel/help/features__Primitive_Sphere_dialog.png +0 -0
  52. package/dist-kernel/help/features__Primitive_Torus_dialog.png +0 -0
  53. package/dist-kernel/help/features__Remesh_dialog.png +0 -0
  54. package/dist-kernel/help/features__Revolve_dialog.png +0 -0
  55. package/dist-kernel/help/features__Sheet_Metal_Contour_Flange_dialog.png +0 -0
  56. package/dist-kernel/help/features__Sheet_Metal_Cutout_dialog.png +0 -0
  57. package/dist-kernel/help/features__Sheet_Metal_Flange_dialog.png +0 -0
  58. package/dist-kernel/help/features__Sheet_Metal_Tab_dialog.png +0 -0
  59. package/dist-kernel/help/features__Sketch_dialog.png +0 -0
  60. package/dist-kernel/help/features__Spline_dialog.png +0 -0
  61. package/dist-kernel/help/features__Sweep_dialog.png +0 -0
  62. package/dist-kernel/help/features__Transform_dialog.png +0 -0
  63. package/dist-kernel/help/features__Tube_dialog.png +0 -0
  64. package/dist-kernel/help/features__assembly-component.html +248 -0
  65. package/dist-kernel/help/features__boolean.html +248 -0
  66. package/dist-kernel/help/features__chamfer.html +248 -0
  67. package/dist-kernel/help/features__datium.html +248 -0
  68. package/dist-kernel/help/features__datum.html +248 -0
  69. package/dist-kernel/help/features__extrude.html +248 -0
  70. package/dist-kernel/help/features__fillet.html +248 -0
  71. package/dist-kernel/help/features__helix.html +248 -0
  72. package/dist-kernel/help/features__hole.html +248 -0
  73. package/dist-kernel/help/features__image-heightmap-solid.html +248 -0
  74. package/dist-kernel/help/features__image-to-face-2D_dialog.png +0 -0
  75. package/dist-kernel/help/features__image-to-face-3D_dialog.png +0 -0
  76. package/dist-kernel/help/features__image-to-face.html +248 -0
  77. package/dist-kernel/help/features__import-3d-model.html +248 -0
  78. package/dist-kernel/help/features__index.html +248 -0
  79. package/dist-kernel/help/features__loft.html +248 -0
  80. package/dist-kernel/help/features__mirror.html +248 -0
  81. package/dist-kernel/help/features__offset-shell.html +248 -0
  82. package/dist-kernel/help/features__pattern-linear.html +248 -0
  83. package/dist-kernel/help/features__pattern-radial.html +248 -0
  84. package/dist-kernel/help/features__pattern.html +248 -0
  85. package/dist-kernel/help/features__plane.html +248 -0
  86. package/dist-kernel/help/features__primitive-cone.html +248 -0
  87. package/dist-kernel/help/features__primitive-cube.html +248 -0
  88. package/dist-kernel/help/features__primitive-cylinder.html +248 -0
  89. package/dist-kernel/help/features__primitive-pyramid.html +248 -0
  90. package/dist-kernel/help/features__primitive-sphere.html +248 -0
  91. package/dist-kernel/help/features__primitive-torus.html +248 -0
  92. package/dist-kernel/help/features__remesh.html +248 -0
  93. package/dist-kernel/help/features__revolve.html +248 -0
  94. package/dist-kernel/help/features__sheet-metal-contour-flange.html +248 -0
  95. package/dist-kernel/help/features__sheet-metal-flange.html +248 -0
  96. package/dist-kernel/help/features__sheet-metal-tab.html +248 -0
  97. package/dist-kernel/help/features__sketch.html +248 -0
  98. package/dist-kernel/help/features__spline.html +248 -0
  99. package/dist-kernel/help/features__sweep.html +248 -0
  100. package/dist-kernel/help/features__transform.html +248 -0
  101. package/dist-kernel/help/features__tube.html +248 -0
  102. package/dist-kernel/help/file-formats.html +248 -0
  103. package/dist-kernel/help/getting-started.html +248 -0
  104. package/dist-kernel/help/highlights.html +248 -0
  105. package/dist-kernel/help/history-systems.html +248 -0
  106. package/dist-kernel/help/how-it-works.html +248 -0
  107. package/dist-kernel/help/index.html +862 -0
  108. package/dist-kernel/help/input-params-schema.html +363 -0
  109. package/dist-kernel/help/inspector-improvements.html +248 -0
  110. package/dist-kernel/help/inspector.html +248 -0
  111. package/dist-kernel/help/modes__modeling.html +248 -0
  112. package/dist-kernel/help/modes__pmi.html +248 -0
  113. package/dist-kernel/help/modes__sketch.html +248 -0
  114. package/dist-kernel/help/plugins.html +248 -0
  115. package/dist-kernel/help/pmi-annotations__Angle_Dimension_dialog.png +0 -0
  116. package/dist-kernel/help/pmi-annotations__Explode_Body_dialog.png +0 -0
  117. package/dist-kernel/help/pmi-annotations__Hole_Callout_dialog.png +0 -0
  118. package/dist-kernel/help/pmi-annotations__Leader_dialog.png +0 -0
  119. package/dist-kernel/help/pmi-annotations__Linear_Dimension_dialog.png +0 -0
  120. package/dist-kernel/help/pmi-annotations__Note_dialog.png +0 -0
  121. package/dist-kernel/help/pmi-annotations__Radial_Dimension_dialog.png +0 -0
  122. package/dist-kernel/help/pmi-annotations__angle-dimension.html +248 -0
  123. package/dist-kernel/help/pmi-annotations__explode-body.html +248 -0
  124. package/dist-kernel/help/pmi-annotations__hole-callout.html +248 -0
  125. package/dist-kernel/help/pmi-annotations__index.html +248 -0
  126. package/dist-kernel/help/pmi-annotations__leader.html +248 -0
  127. package/dist-kernel/help/pmi-annotations__linear-dimension.html +248 -0
  128. package/dist-kernel/help/pmi-annotations__note.html +248 -0
  129. package/dist-kernel/help/pmi-annotations__radial-dimension.html +248 -0
  130. package/dist-kernel/help/search-index.json +464 -0
  131. package/dist-kernel/help/simplified-radial-dimensions.html +298 -0
  132. package/dist-kernel/help/solid-methods.html +359 -0
  133. package/dist-kernel/help/table-of-contents.html +330 -0
  134. package/dist-kernel/help/ui-overview.html +248 -0
  135. package/dist-kernel/help/whats-new.html +248 -0
  136. package/package.json +54 -0
  137. package/src/BREP/AssemblyComponent.js +42 -0
  138. package/src/BREP/BREP.js +43 -0
  139. package/src/BREP/BetterSolid.js +805 -0
  140. package/src/BREP/Edge.js +103 -0
  141. package/src/BREP/Extrude.js +403 -0
  142. package/src/BREP/Face.js +187 -0
  143. package/src/BREP/MeshRepairer.js +634 -0
  144. package/src/BREP/OffsetShellSolid.js +614 -0
  145. package/src/BREP/PointCloudWrap.js +302 -0
  146. package/src/BREP/Revolve.js +345 -0
  147. package/src/BREP/SolidMethods/authoring.js +112 -0
  148. package/src/BREP/SolidMethods/booleanOps.js +230 -0
  149. package/src/BREP/SolidMethods/chamfer.js +122 -0
  150. package/src/BREP/SolidMethods/edgeResolution.js +25 -0
  151. package/src/BREP/SolidMethods/fillet.js +792 -0
  152. package/src/BREP/SolidMethods/index.js +72 -0
  153. package/src/BREP/SolidMethods/io.js +105 -0
  154. package/src/BREP/SolidMethods/lifecycle.js +103 -0
  155. package/src/BREP/SolidMethods/manifoldOps.js +375 -0
  156. package/src/BREP/SolidMethods/meshCleanup.js +2512 -0
  157. package/src/BREP/SolidMethods/meshQueries.js +264 -0
  158. package/src/BREP/SolidMethods/metadata.js +106 -0
  159. package/src/BREP/SolidMethods/metrics.js +51 -0
  160. package/src/BREP/SolidMethods/transforms.js +361 -0
  161. package/src/BREP/SolidMethods/visualize.js +508 -0
  162. package/src/BREP/SolidShared.js +26 -0
  163. package/src/BREP/Sweep.js +1596 -0
  164. package/src/BREP/Tube.js +857 -0
  165. package/src/BREP/Vertex.js +43 -0
  166. package/src/BREP/applyBooleanOperation.js +704 -0
  167. package/src/BREP/boundsUtils.js +48 -0
  168. package/src/BREP/chamfer.js +551 -0
  169. package/src/BREP/edgePolylineUtils.js +85 -0
  170. package/src/BREP/fillets/common.js +388 -0
  171. package/src/BREP/fillets/fillet.js +1422 -0
  172. package/src/BREP/fillets/filletGeometry.js +15 -0
  173. package/src/BREP/fillets/inset.js +389 -0
  174. package/src/BREP/fillets/offsetHelper.js +143 -0
  175. package/src/BREP/fillets/outset.js +88 -0
  176. package/src/BREP/helix.js +193 -0
  177. package/src/BREP/meshToBrep.js +234 -0
  178. package/src/BREP/primitives.js +279 -0
  179. package/src/BREP/setupManifold.js +71 -0
  180. package/src/BREP/threadGeometry.js +1120 -0
  181. package/src/BREP/triangleUtils.js +8 -0
  182. package/src/BREP/triangulate.js +608 -0
  183. package/src/FeatureRegistry.js +183 -0
  184. package/src/PartHistory.js +1132 -0
  185. package/src/UI/AccordionWidget.js +292 -0
  186. package/src/UI/CADmaterials.js +850 -0
  187. package/src/UI/EnvMonacoEditor.js +522 -0
  188. package/src/UI/FloatingWindow.js +396 -0
  189. package/src/UI/HistoryWidget.js +457 -0
  190. package/src/UI/MainToolbar.js +131 -0
  191. package/src/UI/ModelLibraryView.js +194 -0
  192. package/src/UI/OrthoCameraIdle.js +206 -0
  193. package/src/UI/PluginsWidget.js +280 -0
  194. package/src/UI/SceneListing.js +606 -0
  195. package/src/UI/SelectionFilter.js +629 -0
  196. package/src/UI/ViewCube.js +389 -0
  197. package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +329 -0
  198. package/src/UI/assembly/AssemblyConstraintControlsWidget.js +282 -0
  199. package/src/UI/assembly/AssemblyConstraintsWidget.css +292 -0
  200. package/src/UI/assembly/AssemblyConstraintsWidget.js +1373 -0
  201. package/src/UI/assembly/constraintFaceUtils.js +115 -0
  202. package/src/UI/assembly/constraintHighlightUtils.js +70 -0
  203. package/src/UI/assembly/constraintLabelUtils.js +31 -0
  204. package/src/UI/assembly/constraintPointUtils.js +64 -0
  205. package/src/UI/assembly/constraintSelectionUtils.js +185 -0
  206. package/src/UI/assembly/constraintStatusUtils.js +142 -0
  207. package/src/UI/componentSelectorModal.js +240 -0
  208. package/src/UI/controls/CombinedTransformControls.js +386 -0
  209. package/src/UI/dialogs.js +351 -0
  210. package/src/UI/expressionsManager.js +100 -0
  211. package/src/UI/featureDialogWidgets/booleanField.js +25 -0
  212. package/src/UI/featureDialogWidgets/booleanOperationField.js +97 -0
  213. package/src/UI/featureDialogWidgets/buttonField.js +45 -0
  214. package/src/UI/featureDialogWidgets/componentSelectorField.js +102 -0
  215. package/src/UI/featureDialogWidgets/defaultField.js +23 -0
  216. package/src/UI/featureDialogWidgets/fileField.js +66 -0
  217. package/src/UI/featureDialogWidgets/index.js +34 -0
  218. package/src/UI/featureDialogWidgets/numberField.js +165 -0
  219. package/src/UI/featureDialogWidgets/optionsField.js +33 -0
  220. package/src/UI/featureDialogWidgets/referenceSelectionField.js +208 -0
  221. package/src/UI/featureDialogWidgets/stringField.js +24 -0
  222. package/src/UI/featureDialogWidgets/textareaField.js +28 -0
  223. package/src/UI/featureDialogWidgets/threadDesignationField.js +160 -0
  224. package/src/UI/featureDialogWidgets/transformField.js +252 -0
  225. package/src/UI/featureDialogWidgets/utils.js +43 -0
  226. package/src/UI/featureDialogWidgets/vec3Field.js +133 -0
  227. package/src/UI/featureDialogs.js +1414 -0
  228. package/src/UI/fileManagerWidget.js +615 -0
  229. package/src/UI/history/HistoryCollectionWidget.js +1294 -0
  230. package/src/UI/history/historyCollectionWidget.css.js +257 -0
  231. package/src/UI/history/historyDisplayInfo.js +133 -0
  232. package/src/UI/mobile.js +28 -0
  233. package/src/UI/objectDump.js +442 -0
  234. package/src/UI/pmi/AnnotationCollectionWidget.js +120 -0
  235. package/src/UI/pmi/AnnotationHistory.js +353 -0
  236. package/src/UI/pmi/AnnotationRegistry.js +90 -0
  237. package/src/UI/pmi/BaseAnnotation.js +269 -0
  238. package/src/UI/pmi/LabelOverlay.css +102 -0
  239. package/src/UI/pmi/LabelOverlay.js +191 -0
  240. package/src/UI/pmi/PMIMode.js +1550 -0
  241. package/src/UI/pmi/PMIViewsWidget.js +1098 -0
  242. package/src/UI/pmi/annUtils.js +729 -0
  243. package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +647 -0
  244. package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +507 -0
  245. package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +462 -0
  246. package/src/UI/pmi/dimensions/LeaderAnnotation.js +403 -0
  247. package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +532 -0
  248. package/src/UI/pmi/dimensions/NoteAnnotation.js +110 -0
  249. package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +659 -0
  250. package/src/UI/pmi/pmiStyle.js +44 -0
  251. package/src/UI/sketcher/SketchMode3D.js +4095 -0
  252. package/src/UI/sketcher/dimensions.js +674 -0
  253. package/src/UI/sketcher/glyphs.js +236 -0
  254. package/src/UI/sketcher/highlights.js +60 -0
  255. package/src/UI/toolbarButtons/aboutButton.js +5 -0
  256. package/src/UI/toolbarButtons/exportButton.js +609 -0
  257. package/src/UI/toolbarButtons/flatPatternButton.js +307 -0
  258. package/src/UI/toolbarButtons/importButton.js +160 -0
  259. package/src/UI/toolbarButtons/inspectorToggleButton.js +12 -0
  260. package/src/UI/toolbarButtons/metadataButton.js +1063 -0
  261. package/src/UI/toolbarButtons/orientToFaceButton.js +114 -0
  262. package/src/UI/toolbarButtons/registerDefaultButtons.js +46 -0
  263. package/src/UI/toolbarButtons/saveButton.js +99 -0
  264. package/src/UI/toolbarButtons/scriptRunnerButton.js +302 -0
  265. package/src/UI/toolbarButtons/testsButton.js +26 -0
  266. package/src/UI/toolbarButtons/undoRedoButtons.js +25 -0
  267. package/src/UI/toolbarButtons/wireframeToggleButton.js +5 -0
  268. package/src/UI/toolbarButtons/zoomToFitButton.js +5 -0
  269. package/src/UI/triangleDebuggerWindow.js +945 -0
  270. package/src/UI/viewer.js +4228 -0
  271. package/src/assemblyConstraints/AssemblyConstraintHistory.js +1576 -0
  272. package/src/assemblyConstraints/AssemblyConstraintRegistry.js +120 -0
  273. package/src/assemblyConstraints/BaseAssemblyConstraint.js +66 -0
  274. package/src/assemblyConstraints/constraintExpressionUtils.js +35 -0
  275. package/src/assemblyConstraints/constraintUtils/parallelAlignment.js +676 -0
  276. package/src/assemblyConstraints/constraints/AngleConstraint.js +485 -0
  277. package/src/assemblyConstraints/constraints/CoincidentConstraint.js +194 -0
  278. package/src/assemblyConstraints/constraints/DistanceConstraint.js +616 -0
  279. package/src/assemblyConstraints/constraints/FixedConstraint.js +78 -0
  280. package/src/assemblyConstraints/constraints/ParallelConstraint.js +252 -0
  281. package/src/assemblyConstraints/constraints/TouchAlignConstraint.js +961 -0
  282. package/src/core/entities/HistoryCollectionBase.js +72 -0
  283. package/src/core/entities/ListEntityBase.js +109 -0
  284. package/src/core/entities/schemaProcesser.js +121 -0
  285. package/src/exporters/sheetMetalFlatPattern.js +659 -0
  286. package/src/exporters/sheetMetalUnfold.js +862 -0
  287. package/src/exporters/step.js +1135 -0
  288. package/src/exporters/threeMF.js +575 -0
  289. package/src/features/assemblyComponent/AssemblyComponentFeature.js +780 -0
  290. package/src/features/boolean/BooleanFeature.js +94 -0
  291. package/src/features/chamfer/ChamferFeature.js +116 -0
  292. package/src/features/datium/DatiumFeature.js +80 -0
  293. package/src/features/edgeFeatureUtils.js +41 -0
  294. package/src/features/extrude/ExtrudeFeature.js +143 -0
  295. package/src/features/fillet/FilletFeature.js +197 -0
  296. package/src/features/helix/HelixFeature.js +405 -0
  297. package/src/features/hole/HoleFeature.js +1050 -0
  298. package/src/features/hole/screwClearance.js +86 -0
  299. package/src/features/hole/threadDesignationCatalog.js +149 -0
  300. package/src/features/imageHeightSolid/ImageHeightmapSolidFeature.js +463 -0
  301. package/src/features/imageToFace/ImageToFaceFeature.js +727 -0
  302. package/src/features/imageToFace/imageEditor.js +1270 -0
  303. package/src/features/imageToFace/traceUtils.js +971 -0
  304. package/src/features/import3dModel/Import3dModelFeature.js +151 -0
  305. package/src/features/loft/LoftFeature.js +605 -0
  306. package/src/features/mirror/MirrorFeature.js +151 -0
  307. package/src/features/offsetFace/OffsetFaceFeature.js +370 -0
  308. package/src/features/offsetShell/OffsetShellFeature.js +89 -0
  309. package/src/features/overlapCleanup/OverlapCleanupFeature.js +85 -0
  310. package/src/features/pattern/PatternFeature.js +275 -0
  311. package/src/features/patternLinear/PatternLinearFeature.js +120 -0
  312. package/src/features/patternRadial/PatternRadialFeature.js +186 -0
  313. package/src/features/plane/PlaneFeature.js +154 -0
  314. package/src/features/primitiveCone/primitiveConeFeature.js +99 -0
  315. package/src/features/primitiveCube/primitiveCubeFeature.js +70 -0
  316. package/src/features/primitiveCylinder/primitiveCylinderFeature.js +91 -0
  317. package/src/features/primitivePyramid/primitivePyramidFeature.js +72 -0
  318. package/src/features/primitiveSphere/primitiveSphereFeature.js +62 -0
  319. package/src/features/primitiveTorus/primitiveTorusFeature.js +109 -0
  320. package/src/features/remesh/RemeshFeature.js +97 -0
  321. package/src/features/revolve/RevolveFeature.js +111 -0
  322. package/src/features/selectionUtils.js +118 -0
  323. package/src/features/sheetMetal/SheetMetalContourFlangeFeature.js +1656 -0
  324. package/src/features/sheetMetal/SheetMetalCutoutFeature.js +1056 -0
  325. package/src/features/sheetMetal/SheetMetalFlangeFeature.js +1568 -0
  326. package/src/features/sheetMetal/SheetMetalHemFeature.js +43 -0
  327. package/src/features/sheetMetal/SheetMetalObject.js +141 -0
  328. package/src/features/sheetMetal/SheetMetalTabFeature.js +176 -0
  329. package/src/features/sheetMetal/UNFOLD_NEUTRAL_REQUIREMENTS.md +153 -0
  330. package/src/features/sheetMetal/contour-flange-rebuild-spec.md +261 -0
  331. package/src/features/sheetMetal/profileUtils.js +25 -0
  332. package/src/features/sheetMetal/sheetMetalCleanup.js +9 -0
  333. package/src/features/sheetMetal/sheetMetalFaceTypes.js +146 -0
  334. package/src/features/sheetMetal/sheetMetalMetadata.js +165 -0
  335. package/src/features/sheetMetal/sheetMetalPipeline.js +169 -0
  336. package/src/features/sheetMetal/sheetMetalProfileUtils.js +216 -0
  337. package/src/features/sheetMetal/sheetMetalTabUtils.js +29 -0
  338. package/src/features/sheetMetal/sheetMetalTree.js +210 -0
  339. package/src/features/sketch/SketchFeature.js +955 -0
  340. package/src/features/sketch/sketchSolver2D/ConstraintEngine.js +800 -0
  341. package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +704 -0
  342. package/src/features/sketch/sketchSolver2D/mathHelpersMod.js +307 -0
  343. package/src/features/spline/SplineEditorSession.js +988 -0
  344. package/src/features/spline/SplineFeature.js +1388 -0
  345. package/src/features/spline/splineUtils.js +218 -0
  346. package/src/features/sweep/SweepFeature.js +110 -0
  347. package/src/features/transform/TransformFeature.js +152 -0
  348. package/src/features/tube/TubeFeature.js +635 -0
  349. package/src/fs.proxy.js +625 -0
  350. package/src/idbStorage.js +254 -0
  351. package/src/index.js +12 -0
  352. package/src/main.js +15 -0
  353. package/src/metadataManager.js +64 -0
  354. package/src/path.proxy.js +277 -0
  355. package/src/plugins/ghLoader.worker.js +151 -0
  356. package/src/plugins/pluginManager.js +286 -0
  357. package/src/pmi/PMIViewsManager.js +134 -0
  358. package/src/services/componentLibrary.js +198 -0
  359. package/src/tests/ConsoleCapture.js +189 -0
  360. package/src/tests/S7-diagnostics-2025-12-23T18-37-23-570Z.json +630 -0
  361. package/src/tests/browserTests.js +597 -0
  362. package/src/tests/debugBoolean.js +225 -0
  363. package/src/tests/partFiles/badBoolean.json +957 -0
  364. package/src/tests/partFiles/extrudeTest.json +88 -0
  365. package/src/tests/partFiles/filletFail.json +58 -0
  366. package/src/tests/partFiles/import_TEst.part.part.json +646 -0
  367. package/src/tests/partFiles/sheetMetalHem.BREP.json +734 -0
  368. package/src/tests/test_boolean_subtract.js +27 -0
  369. package/src/tests/test_chamfer.js +17 -0
  370. package/src/tests/test_extrudeFace.js +24 -0
  371. package/src/tests/test_fillet.js +17 -0
  372. package/src/tests/test_fillet_nonClosed.js +45 -0
  373. package/src/tests/test_filletsMoreDifficult.js +46 -0
  374. package/src/tests/test_history_features_basic.js +149 -0
  375. package/src/tests/test_hole.js +282 -0
  376. package/src/tests/test_mirror.js +16 -0
  377. package/src/tests/test_offsetShellGrouping.js +85 -0
  378. package/src/tests/test_plane.js +4 -0
  379. package/src/tests/test_primitiveCone.js +11 -0
  380. package/src/tests/test_primitiveCube.js +7 -0
  381. package/src/tests/test_primitiveCylinder.js +8 -0
  382. package/src/tests/test_primitivePyramid.js +9 -0
  383. package/src/tests/test_primitiveSphere.js +17 -0
  384. package/src/tests/test_primitiveTorus.js +21 -0
  385. package/src/tests/test_pushFace.js +126 -0
  386. package/src/tests/test_sheetMetalContourFlange.js +125 -0
  387. package/src/tests/test_sheetMetal_features.js +80 -0
  388. package/src/tests/test_sketch_openLoop.js +45 -0
  389. package/src/tests/test_solidMetrics.js +58 -0
  390. package/src/tests/test_stlLoader.js +1889 -0
  391. package/src/tests/test_sweepFace.js +55 -0
  392. package/src/tests/test_tube.js +45 -0
  393. package/src/tests/test_tube_closedLoop.js +67 -0
  394. package/src/tests/tests.js +493 -0
  395. package/src/tools/assemblyConstraintDialogCapturePage.js +56 -0
  396. package/src/tools/dialogCapturePageFactory.js +227 -0
  397. package/src/tools/featureDialogCapturePage.js +47 -0
  398. package/src/tools/pmiAnnotationDialogCapturePage.js +60 -0
  399. package/src/utils/axisHelpers.js +99 -0
  400. package/src/utils/deepClone.js +69 -0
  401. package/src/utils/geometryTolerance.js +37 -0
  402. package/src/utils/normalizeTypeString.js +8 -0
  403. package/src/utils/xformMath.js +51 -0
@@ -0,0 +1,1135 @@
1
+ // STEP (ISO 10303-21) exporter for triangulated solids.
2
+ // This writes a faceted BREP and merges coplanar regions into polygon faces.
3
+ // Optional: emit AP242 tessellated faces for non-planar regions.
4
+
5
+ function _escapeStepString(value) {
6
+ const s = String(value == null ? '' : value);
7
+ // STEP strings escape single quotes by doubling them.
8
+ return s.replace(/'/g, "''");
9
+ }
10
+
11
+ function _safeStepName(value, fallback = 'NAME') {
12
+ const raw = String(value ?? '').trim();
13
+ return _escapeStepString(raw || fallback);
14
+ }
15
+
16
+ function _fmtNumber(n, precision) {
17
+ if (!Number.isFinite(n)) return '0';
18
+ let s = Number(n).toFixed(precision);
19
+ // Trim trailing zeros for smaller files, but keep integers intact.
20
+ if (s.includes('.')) s = s.replace(/\.?0+$/, '');
21
+ if (s === '-0') s = '0';
22
+ return s;
23
+ }
24
+
25
+ function _fmtExp(n, precision = 6) {
26
+ if (!Number.isFinite(n) || n === 0) return '0.';
27
+ const s = Number(n).toExponential(precision);
28
+ const [mant, exp] = s.split('e');
29
+ const expInt = Number(exp);
30
+ return `${mant.toUpperCase()}E${expInt >= 0 ? '+' : ''}${expInt}`;
31
+ }
32
+
33
+ function _isIdentityMatrixElements(elements, epsilon = 1e-12) {
34
+ if (!elements || elements.length !== 16) return true;
35
+ const id = [
36
+ 1, 0, 0, 0,
37
+ 0, 1, 0, 0,
38
+ 0, 0, 1, 0,
39
+ 0, 0, 0, 1,
40
+ ];
41
+ for (let i = 0; i < 16; i++) {
42
+ if (Math.abs((elements[i] ?? id[i]) - id[i]) > epsilon) return false;
43
+ }
44
+ return true;
45
+ }
46
+
47
+ function _applyMatrix4(elements, x, y, z) {
48
+ if (!elements || elements.length !== 16) return [x, y, z];
49
+ const nx = elements[0] * x + elements[4] * y + elements[8] * z + elements[12];
50
+ const ny = elements[1] * x + elements[5] * y + elements[9] * z + elements[13];
51
+ const nz = elements[2] * x + elements[6] * y + elements[10] * z + elements[14];
52
+ const nw = elements[3] * x + elements[7] * y + elements[11] * z + elements[15];
53
+ if (nw && Math.abs(nw - 1) > 1e-12) {
54
+ return [nx / nw, ny / nw, nz / nw];
55
+ }
56
+ return [nx, ny, nz];
57
+ }
58
+
59
+ function _normalize(vec, fallback = [0, 0, 1]) {
60
+ const x = vec[0];
61
+ const y = vec[1];
62
+ const z = vec[2];
63
+ const len = Math.hypot(x, y, z);
64
+ if (!len || !Number.isFinite(len)) return fallback.slice();
65
+ return [x / len, y / len, z / len];
66
+ }
67
+
68
+ function _cross(a, b) {
69
+ return [
70
+ a[1] * b[2] - a[2] * b[1],
71
+ a[2] * b[0] - a[0] * b[2],
72
+ a[0] * b[1] - a[1] * b[0],
73
+ ];
74
+ }
75
+
76
+ function _dot(a, b) {
77
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
78
+ }
79
+
80
+ function _sub(a, b) {
81
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
82
+ }
83
+
84
+ function _orthogonalRefDir(normal) {
85
+ // Pick a stable axis that is not parallel to the normal.
86
+ const nz = Math.abs(normal[2]);
87
+ const axis = nz < 0.9 ? [0, 0, 1] : [0, 1, 0];
88
+ const ref = _cross(axis, normal);
89
+ return _normalize(ref, [1, 0, 0]);
90
+ }
91
+
92
+ function _formatEntityList(ids, breakEvery = 40) {
93
+ if (!ids || ids.length === 0) return '()';
94
+ if (ids.length <= breakEvery) return `(${ids.map((id) => `#${id}`).join(',')})`;
95
+ const chunks = [];
96
+ for (let i = 0; i < ids.length; i += breakEvery) {
97
+ const slice = ids.slice(i, i + breakEvery).map((id) => `#${id}`).join(',');
98
+ chunks.push(slice);
99
+ }
100
+ return `(\n ${chunks.join(',\n ')}\n)`;
101
+ }
102
+
103
+ function _quantize(value, tol) {
104
+ if (!Number.isFinite(value) || !Number.isFinite(tol) || tol <= 0) return 0;
105
+ return Math.round(value / tol);
106
+ }
107
+
108
+ function _planeKey(normal, d, normalTol, distTol) {
109
+ const qx = _quantize(normal[0], normalTol);
110
+ const qy = _quantize(normal[1], normalTol);
111
+ const qz = _quantize(normal[2], normalTol);
112
+ const qd = _quantize(d, distTol);
113
+ return `${qx},${qy},${qz},${qd}`;
114
+ }
115
+
116
+ function _edgeKey(a, b) {
117
+ return a < b ? `${a}_${b}` : `${b}_${a}`;
118
+ }
119
+
120
+ function _collectBoundaryEdges(triIndices, triData) {
121
+ const edgeMap = new Map();
122
+ for (const t of triIndices) {
123
+ const tri = triData[t];
124
+ if (!tri) continue;
125
+ const edges = [
126
+ [tri.i0, tri.i1],
127
+ [tri.i1, tri.i2],
128
+ [tri.i2, tri.i0],
129
+ ];
130
+ for (const [a, b] of edges) {
131
+ const key = _edgeKey(a, b);
132
+ const entry = edgeMap.get(key);
133
+ if (!entry) {
134
+ edgeMap.set(key, { count: 1, from: a, to: b });
135
+ } else {
136
+ entry.count += 1;
137
+ }
138
+ }
139
+ }
140
+ const boundary = [];
141
+ for (const entry of edgeMap.values()) {
142
+ if (entry.count === 1) boundary.push({ from: entry.from, to: entry.to });
143
+ }
144
+ return boundary;
145
+ }
146
+
147
+ function _buildLoopsFromEdges(boundaryEdges) {
148
+ const startMap = new Map();
149
+ for (const e of boundaryEdges) {
150
+ const list = startMap.get(e.from) || [];
151
+ list.push(e);
152
+ startMap.set(e.from, list);
153
+ }
154
+
155
+ const used = new Set();
156
+ const loops = [];
157
+ const edgeId = (e) => `${e.from},${e.to}`;
158
+
159
+ for (const e of boundaryEdges) {
160
+ const key = edgeId(e);
161
+ if (used.has(key)) continue;
162
+ const loop = [e.from];
163
+ let current = e;
164
+ used.add(key);
165
+ let guard = 0;
166
+ while (guard++ < boundaryEdges.length + 5) {
167
+ loop.push(current.to);
168
+ if (current.to === loop[0]) {
169
+ loop.pop();
170
+ if (loop.length >= 3) loops.push(loop);
171
+ break;
172
+ }
173
+ const nextEdges = startMap.get(current.to) || [];
174
+ let next = null;
175
+ for (const cand of nextEdges) {
176
+ const k = edgeId(cand);
177
+ if (!used.has(k)) { next = cand; break; }
178
+ }
179
+ if (!next) {
180
+ return { loops: [], ok: false };
181
+ }
182
+ current = next;
183
+ used.add(edgeId(current));
184
+ }
185
+ }
186
+
187
+ return { loops, ok: loops.length > 0 };
188
+ }
189
+
190
+ function _buildBoundaryEdgePolylines(mesh, idToFaceName) {
191
+ const { vertProperties, triVerts, faceID } = mesh || {};
192
+ if (!vertProperties || !triVerts || !faceID) return [];
193
+ const triCount = (triVerts.length / 3) | 0;
194
+ const nv = (vertProperties.length / 3) | 0;
195
+ if (triCount === 0 || nv === 0) return [];
196
+
197
+ const NV = BigInt(nv);
198
+ const ukey = (a, b) => {
199
+ const A = BigInt(a);
200
+ const B = BigInt(b);
201
+ return A < B ? (A * NV + B) : (B * NV + A);
202
+ };
203
+
204
+ const e2t = new Map(); // key -> [{id, a, b, tri}...]
205
+ for (let t = 0; t < triCount; t++) {
206
+ const id = faceID ? faceID[t] : undefined;
207
+ const base = t * 3;
208
+ const i0 = triVerts[base + 0];
209
+ const i1 = triVerts[base + 1];
210
+ const i2 = triVerts[base + 2];
211
+ const edges = [[i0, i1], [i1, i2], [i2, i0]];
212
+ for (let k = 0; k < 3; k++) {
213
+ const a = edges[k][0];
214
+ const b = edges[k][1];
215
+ const key = ukey(a, b);
216
+ let arr = e2t.get(key);
217
+ if (!arr) { arr = []; e2t.set(key, arr); }
218
+ arr.push({ id, a, b, tri: t });
219
+ }
220
+ }
221
+
222
+ const pairToEdges = new Map(); // pairKey -> array of [u,v]
223
+ for (const [, arr] of e2t.entries()) {
224
+ if (arr.length !== 2) continue;
225
+ const a = arr[0];
226
+ const b = arr[1];
227
+ if (a.id === b.id) continue;
228
+ const nameA = idToFaceName?.get(a.id) || `FACE_${a.id}`;
229
+ const nameB = idToFaceName?.get(b.id) || `FACE_${b.id}`;
230
+ const pair = nameA < nameB ? [nameA, nameB] : [nameB, nameA];
231
+ const pairKey = JSON.stringify(pair);
232
+ let list = pairToEdges.get(pairKey);
233
+ if (!list) { list = []; pairToEdges.set(pairKey, list); }
234
+ const v0 = Math.min(a.a, a.b);
235
+ const v1 = Math.max(a.a, a.b);
236
+ list.push([v0, v1]);
237
+ }
238
+
239
+ const polylines = [];
240
+ for (const [pairKey, edges] of pairToEdges.entries()) {
241
+ const adj = new Map(); // v -> Set(neighbors)
242
+ const edgeVisited = new Set(); // `${min},${max}`
243
+ const ek = (u, v) => (u < v ? `${u},${v}` : `${v},${u}`);
244
+ for (const [u, v] of edges) {
245
+ if (!adj.has(u)) adj.set(u, new Set());
246
+ if (!adj.has(v)) adj.set(v, new Set());
247
+ adj.get(u).add(v);
248
+ adj.get(v).add(u);
249
+ }
250
+
251
+ const [faceA, faceB] = JSON.parse(pairKey);
252
+ let idx = 0;
253
+
254
+ const visitChainFrom = (start) => {
255
+ const chain = [];
256
+ let prev = -1;
257
+ let curr = start;
258
+ chain.push(curr);
259
+ while (true) {
260
+ const nbrs = adj.get(curr) || new Set();
261
+ let next = undefined;
262
+ for (const n of nbrs) {
263
+ const key = ek(curr, n);
264
+ if (edgeVisited.has(key)) continue;
265
+ if (n === prev) continue;
266
+ next = n;
267
+ edgeVisited.add(key);
268
+ break;
269
+ }
270
+ if (next === undefined) break;
271
+ prev = curr;
272
+ curr = next;
273
+ chain.push(curr);
274
+ }
275
+ return chain;
276
+ };
277
+
278
+ for (const [v, nbrs] of adj.entries()) {
279
+ if ((nbrs.size | 0) === 1) {
280
+ const n = [...nbrs][0];
281
+ const key = ek(v, n);
282
+ if (edgeVisited.has(key)) continue;
283
+ const chain = visitChainFrom(v);
284
+ polylines.push({ name: `${faceA}|${faceB}[${idx++}]`, faceA, faceB, indices: chain, closedLoop: false });
285
+ }
286
+ }
287
+
288
+ const buildLoopFromEdge = (startU, startV) => {
289
+ const chain = [startU, startV];
290
+ let prev = startU;
291
+ let curr = startV;
292
+ edgeVisited.add(ek(startU, startV));
293
+ while (true) {
294
+ const nbrs = adj.get(curr) || new Set();
295
+ let next = undefined;
296
+ for (const n of nbrs) {
297
+ if (n === prev) continue;
298
+ const key = ek(curr, n);
299
+ if (edgeVisited.has(key)) continue;
300
+ next = n;
301
+ break;
302
+ }
303
+ if (next === undefined) break;
304
+ edgeVisited.add(ek(curr, next));
305
+ chain.push(next);
306
+ prev = curr;
307
+ curr = next;
308
+ }
309
+ const start = chain[0];
310
+ const last = chain[chain.length - 1];
311
+ const nbrsLast = adj.get(last) || new Set();
312
+ if (nbrsLast.has(start)) {
313
+ edgeVisited.add(ek(last, start));
314
+ chain.push(start);
315
+ }
316
+ return chain;
317
+ };
318
+
319
+ for (const [u, nbrs] of adj.entries()) {
320
+ for (const v of nbrs) {
321
+ const key = ek(u, v);
322
+ if (edgeVisited.has(key)) continue;
323
+ const chain = buildLoopFromEdge(u, v);
324
+ const closed = chain.length >= 3 && chain[0] === chain[chain.length - 1];
325
+ polylines.push({ name: `${faceA}|${faceB}[${idx++}]`, faceA, faceB, indices: chain, closedLoop: closed });
326
+ }
327
+ }
328
+ }
329
+
330
+ return polylines;
331
+ }
332
+
333
+ function _loopArea(loop, vertexPoints, u, v) {
334
+ let area = 0;
335
+ const n = loop.length;
336
+ for (let i = 0; i < n; i++) {
337
+ const p = vertexPoints[loop[i]]?.pos;
338
+ const q = vertexPoints[loop[(i + 1) % n]]?.pos;
339
+ if (!p || !q) continue;
340
+ const px = _dot(p, u);
341
+ const py = _dot(p, v);
342
+ const qx = _dot(q, u);
343
+ const qy = _dot(q, v);
344
+ area += (px * qy) - (qx * py);
345
+ }
346
+ return area * 0.5;
347
+ }
348
+
349
+ function _planeBasis(normal, refDir) {
350
+ let u = _normalize(refDir, _orthogonalRefDir(normal));
351
+ if (Math.abs(_dot(u, normal)) > 0.99) {
352
+ u = _orthogonalRefDir(normal);
353
+ }
354
+ u = _normalize(u);
355
+ const v = _normalize(_cross(normal, u), _orthogonalRefDir(normal));
356
+ return { u, v };
357
+ }
358
+
359
+ function _splitComponents(triIndices, triData) {
360
+ const edgeToTris = new Map();
361
+ for (const t of triIndices) {
362
+ const tri = triData[t];
363
+ if (!tri) continue;
364
+ const edges = [
365
+ [tri.i0, tri.i1],
366
+ [tri.i1, tri.i2],
367
+ [tri.i2, tri.i0],
368
+ ];
369
+ for (const [a, b] of edges) {
370
+ const key = _edgeKey(a, b);
371
+ const list = edgeToTris.get(key) || [];
372
+ list.push(t);
373
+ edgeToTris.set(key, list);
374
+ }
375
+ }
376
+
377
+ const visited = new Set();
378
+ const components = [];
379
+ for (const t of triIndices) {
380
+ if (visited.has(t)) continue;
381
+ const stack = [t];
382
+ const comp = [];
383
+ visited.add(t);
384
+ while (stack.length) {
385
+ const cur = stack.pop();
386
+ comp.push(cur);
387
+ const tri = triData[cur];
388
+ if (!tri) continue;
389
+ const edges = [
390
+ [tri.i0, tri.i1],
391
+ [tri.i1, tri.i2],
392
+ [tri.i2, tri.i0],
393
+ ];
394
+ for (const [a, b] of edges) {
395
+ const key = _edgeKey(a, b);
396
+ const list = edgeToTris.get(key) || [];
397
+ if (list.length < 2) continue;
398
+ for (const nei of list) {
399
+ if (!visited.has(nei)) {
400
+ visited.add(nei);
401
+ stack.push(nei);
402
+ }
403
+ }
404
+ }
405
+ }
406
+ if (comp.length) components.push(comp);
407
+ }
408
+ return components;
409
+ }
410
+
411
+ function _isCoplanarGroup(triIndices, triData, normalTol, distTol) {
412
+ if (!triIndices || triIndices.length === 0) return false;
413
+ const tri0 = triData[triIndices[0]];
414
+ if (!tri0) return false;
415
+ let n0 = tri0.normal;
416
+ let d0 = tri0.d;
417
+
418
+ for (const idx of triIndices) {
419
+ const tri = triData[idx];
420
+ if (!tri) continue;
421
+ let n = tri.normal;
422
+ let d = tri.d;
423
+ let dot = _dot(n, n0);
424
+ if (dot < 0) {
425
+ n = [-n[0], -n[1], -n[2]];
426
+ d = -d;
427
+ dot = -dot;
428
+ }
429
+ if ((1 - dot) > normalTol) return false;
430
+ if (Math.abs(d - d0) > distTol) return false;
431
+ }
432
+ return true;
433
+ }
434
+
435
+ function _emitTriangleFace(tri, vertexPoints, builder, getDirectionId, precision, faceName = '') {
436
+ const v0 = vertexPoints[tri.i0];
437
+ const v1 = vertexPoints[tri.i1];
438
+ const v2 = vertexPoints[tri.i2];
439
+ if (!v0 || !v1 || !v2) return null;
440
+
441
+ const normal = tri.normal;
442
+ const refBasis = _planeBasis(normal, tri.e1);
443
+ const axisDirId = getDirectionId(normal);
444
+ const refDirId = getDirectionId(refBasis.u);
445
+ const axis2Id = builder.add(`AXIS2_PLACEMENT_3D('',#${v0.pointId},#${axisDirId},#${refDirId})`);
446
+ const planeId = builder.add(`PLANE('',#${axis2Id})`);
447
+ const loopId = builder.add(`POLY_LOOP('',(#${v0.pointId},#${v1.pointId},#${v2.pointId}))`);
448
+ const boundId = builder.add(`FACE_OUTER_BOUND('',#${loopId},.T.)`);
449
+ const faceId = builder.add(`ADVANCED_FACE('${_safeStepName(faceName, '')}',(#${boundId}),#${planeId},.T.)`);
450
+ return faceId;
451
+ }
452
+
453
+ function _emitPlanarComponent(component, triData, vertexPoints, builder, getDirectionId, precision, faceName = '', edgeContext = null) {
454
+ if (!component.length) return { ok: false };
455
+ const tri0 = triData[component[0]];
456
+ if (!tri0) return { ok: false };
457
+
458
+ const boundaryEdges = _collectBoundaryEdges(component, triData);
459
+ if (boundaryEdges.length < 3) return { ok: false };
460
+
461
+ const { loops, ok } = _buildLoopsFromEdges(boundaryEdges);
462
+ if (!ok || loops.length === 0) return { ok: false };
463
+
464
+ const normal = tri0.normal;
465
+ const basis = _planeBasis(normal, tri0.e1);
466
+
467
+ const loopAreas = loops.map((loop) => _loopArea(loop, vertexPoints, basis.u, basis.v));
468
+ let outerIdx = 0;
469
+ let maxAbs = Math.abs(loopAreas[0] || 0);
470
+ for (let i = 1; i < loopAreas.length; i++) {
471
+ const absA = Math.abs(loopAreas[i] || 0);
472
+ if (absA > maxAbs) {
473
+ maxAbs = absA;
474
+ outerIdx = i;
475
+ }
476
+ }
477
+
478
+ // Ensure outer loop is CCW (positive area)
479
+ if (loopAreas[outerIdx] < 0) {
480
+ loops[outerIdx].reverse();
481
+ loopAreas[outerIdx] = -loopAreas[outerIdx];
482
+ }
483
+
484
+ // Ensure inner loops are CW (negative area)
485
+ for (let i = 0; i < loops.length; i++) {
486
+ if (i === outerIdx) continue;
487
+ if (loopAreas[i] > 0) {
488
+ loops[i].reverse();
489
+ loopAreas[i] = -loopAreas[i];
490
+ }
491
+ }
492
+
493
+ const originIndex = loops[outerIdx][0];
494
+ const originPoint = vertexPoints[originIndex];
495
+ if (!originPoint) return { ok: false };
496
+
497
+ const axisDirId = getDirectionId(normal);
498
+ const refDirId = getDirectionId(basis.u);
499
+ const axis2Id = builder.add(`AXIS2_PLACEMENT_3D('',#${originPoint.pointId},#${axisDirId},#${refDirId})`);
500
+ const planeId = builder.add(`PLANE('',#${axis2Id})`);
501
+
502
+ const boundIds = [];
503
+ const buildEdgeLoop = (loop, loopName) => {
504
+ if (!edgeContext) return null;
505
+ const orientedEdges = [];
506
+ const count = loop.length;
507
+ for (let i = 0; i < count; i++) {
508
+ const a = loop[i];
509
+ const b = loop[(i + 1) % count];
510
+ const key = _edgeKey(a, b);
511
+ let edgeRec = edgeContext.edgeCurveCache.get(key);
512
+ if (!edgeRec) {
513
+ const name = edgeContext.edgeNameByKey.get(key)
514
+ || edgeContext.edgeNameAllocator.allocate(`EDGE_${edgeContext.edgeCurveCache.size}`);
515
+ const vStart = vertexPoints[a];
516
+ const vEnd = vertexPoints[b];
517
+ if (!vStart || !vEnd) return null;
518
+ const polylineId = builder.add(`POLYLINE('',(#${vStart.pointId},#${vEnd.pointId}))`);
519
+ const edgeCurveId = builder.add(`EDGE_CURVE('${_safeStepName(name)}',#${vStart.vertexId},#${vEnd.vertexId},#${polylineId},.T.)`);
520
+ edgeRec = { id: edgeCurveId, start: a, end: b };
521
+ edgeContext.edgeCurveCache.set(key, edgeRec);
522
+ }
523
+ const sense = (edgeRec.start === a && edgeRec.end === b) ? '.T.' : '.F.';
524
+ const oeId = builder.add(`ORIENTED_EDGE('',*,*,#${edgeRec.id},${sense})`);
525
+ orientedEdges.push(oeId);
526
+ }
527
+ if (!orientedEdges.length) return null;
528
+ const loopId = builder.add(`EDGE_LOOP('${_safeStepName(loopName, '')}',(${orientedEdges.map((id) => `#${id}`).join(',')}))`);
529
+ return loopId;
530
+ };
531
+ for (let i = 0; i < loops.length; i++) {
532
+ const loop = loops[i];
533
+ if (!loop || loop.length < 3) continue;
534
+ const pointRefs = loop.map((idx) => {
535
+ const pointId = vertexPoints[idx]?.pointId;
536
+ return pointId ? `#${pointId}` : null;
537
+ });
538
+ if (pointRefs.length < 3 || pointRefs.some((p) => !p)) {
539
+ return { ok: false };
540
+ }
541
+ let loopId = null;
542
+ if (edgeContext) {
543
+ loopId = buildEdgeLoop(loop, `${faceName}_LOOP_${i}`);
544
+ }
545
+ if (!loopId) {
546
+ loopId = builder.add(`POLY_LOOP('',(${pointRefs.join(',')}))`);
547
+ }
548
+ if (i === outerIdx) {
549
+ const boundId = builder.add(`FACE_OUTER_BOUND('',#${loopId},.T.)`);
550
+ boundIds.push(boundId);
551
+ } else {
552
+ const boundId = builder.add(`FACE_BOUND('',#${loopId},.F.)`);
553
+ boundIds.push(boundId);
554
+ }
555
+ }
556
+
557
+ if (boundIds.length === 0) return { ok: false };
558
+
559
+ const faceId = builder.add(`ADVANCED_FACE('${_safeStepName(faceName, '')}',(${boundIds.map((id) => `#${id}`).join(',')}),#${planeId},.T.)`);
560
+ return { ok: true, faceId };
561
+ }
562
+
563
+ function _emitTessellatedFace(component, triData, vertexPoints, builder, precision, faceName = '') {
564
+ if (!component || component.length === 0) return { ok: false };
565
+
566
+ const usedVertices = new Map(); // mesh vertex index -> tessellated index
567
+ const coords = [];
568
+ const tris = [];
569
+
570
+ const addVertex = (idx) => {
571
+ let mapped = usedVertices.get(idx);
572
+ if (mapped != null) return mapped;
573
+ const v = vertexPoints[idx];
574
+ if (!v) return null;
575
+ const p = v.pos;
576
+ const mappedIdx = coords.length;
577
+ coords.push([p[0], p[1], p[2]]);
578
+ usedVertices.set(idx, mappedIdx);
579
+ return mappedIdx;
580
+ };
581
+
582
+ for (const triIdx of component) {
583
+ const tri = triData[triIdx];
584
+ if (!tri) continue;
585
+ const a = addVertex(tri.i0);
586
+ const b = addVertex(tri.i1);
587
+ const c = addVertex(tri.i2);
588
+ if (a == null || b == null || c == null) continue;
589
+ tris.push([a + 1, b + 1, c + 1]); // 1-based indices
590
+ }
591
+
592
+ if (coords.length < 3 || tris.length === 0) return { ok: false };
593
+
594
+ const coordList = coords
595
+ .map((p) => `(${_fmtNumber(p[0], precision)},${_fmtNumber(p[1], precision)},${_fmtNumber(p[2], precision)})`)
596
+ .join(',');
597
+ const pointListId = builder.add(`CARTESIAN_POINT_LIST_3D('',(${coordList}))`);
598
+
599
+ const triList = tris.map((t) => `(${t[0]},${t[1]},${t[2]})`).join(',');
600
+ const faceId = builder.add(`TRIANGULATED_FACE('${_safeStepName(faceName, '')}',#${pointListId},(${triList}),$)`);
601
+ return { ok: true, faceId };
602
+ }
603
+
604
+ class StepBuilder {
605
+ constructor() {
606
+ this.nextId = 1;
607
+ this.lines = [];
608
+ }
609
+
610
+ add(entityBody) {
611
+ const id = this.nextId++;
612
+ this.lines.push(`#${id}=${entityBody};`);
613
+ return id;
614
+ }
615
+ }
616
+
617
+ class NameAllocator {
618
+ constructor() {
619
+ this.counts = new Map();
620
+ }
621
+
622
+ allocate(base) {
623
+ const name = _safeStepName(base, 'NAME');
624
+ const count = this.counts.get(name) || 0;
625
+ this.counts.set(name, count + 1);
626
+ if (count === 0) return name;
627
+ return _safeStepName(`${name}[${count}]`);
628
+ }
629
+ }
630
+
631
+ function _buildLengthUnit(builder, unitName) {
632
+ const u = String(unitName || 'millimeter').toLowerCase();
633
+
634
+ // Base SI metre (used directly or as a conversion reference).
635
+ const metreUnitId = builder.add('(LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT($,.METRE.))');
636
+
637
+ const siPrefixByUnit = {
638
+ millimeter: '.MILLI.',
639
+ centimeter: '.CENTI.',
640
+ meter: '$',
641
+ micron: '.MICRO.',
642
+ };
643
+
644
+ if (Object.prototype.hasOwnProperty.call(siPrefixByUnit, u)) {
645
+ const prefix = siPrefixByUnit[u];
646
+ if (prefix === '$') return metreUnitId;
647
+ return builder.add(`(LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(${prefix},.METRE.))`);
648
+ }
649
+
650
+ if (u === 'inch' || u === 'foot') {
651
+ const factor = u === 'inch' ? 0.0254 : 0.3048;
652
+ const name = u === 'inch' ? 'INCH' : 'FOOT';
653
+ const measureId = builder.add(
654
+ `LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(${_fmtNumber(factor, 8)}),#${metreUnitId})`,
655
+ );
656
+ return builder.add(`(CONVERSION_BASED_UNIT('${name}',#${measureId}) LENGTH_UNIT() NAMED_UNIT(*))`);
657
+ }
658
+
659
+ // Fallback to millimeters.
660
+ return builder.add('(LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.))');
661
+ }
662
+
663
+ /**
664
+ * Generate a STEP string for one or more solids.
665
+ * @param {Array} solids SOLID-like objects exposing getMesh() and name.
666
+ * @param {{name?: string, unit?: string, precision?: number, scale?: number, applyWorldTransform?: boolean, mergePlanarFaces?: boolean, planarNormalTolerance?: number, planarDistanceTolerance?: number, useTessellatedFaces?: boolean, exportFaces?: boolean, exportEdgesAsPolylines?: boolean}} opts
667
+ * @returns {{data: string, exported: number, skipped: string[]}}
668
+ */
669
+ export function generateSTEP(solids, opts = {}) {
670
+ const unit = opts.unit || 'millimeter';
671
+ const precision = Number.isFinite(opts.precision) ? opts.precision : 6;
672
+ const scale = Number.isFinite(opts.scale) ? opts.scale : 1;
673
+ const applyWorldTransform = opts.applyWorldTransform !== false;
674
+ const mergePlanarFaces = opts.mergePlanarFaces !== false;
675
+ const useTessellatedFaces = opts.useTessellatedFaces !== false;
676
+ const exportFaces = opts.exportFaces !== false;
677
+ const exportEdgesAsPolylines = opts.exportEdgesAsPolylines !== false;
678
+ const baseName = _escapeStepString(opts.name || solids?.[0]?.name || 'part');
679
+
680
+ const builder = new StepBuilder();
681
+
682
+ // Application context + protocol.
683
+ const appCtxId = builder.add("APPLICATION_CONTEXT('automotive_design')");
684
+ const protocolName = useTessellatedFaces ? 'ap242' : 'automotive_design';
685
+ builder.add(`APPLICATION_PROTOCOL_DEFINITION('international standard','${protocolName}',2000,#${appCtxId})`);
686
+
687
+ // Units + representation context.
688
+ const lengthUnitId = _buildLengthUnit(builder, unit);
689
+ const angleUnitId = builder.add('(PLANE_ANGLE_UNIT() NAMED_UNIT(*) SI_UNIT($,.RADIAN.))');
690
+ const solidAngleUnitId = builder.add('(SOLID_ANGLE_UNIT() NAMED_UNIT(*) SI_UNIT($,.STERADIAN.))');
691
+ const uncertaintyVal = Math.max(1e-9, Math.abs(scale) * 1e-6);
692
+ const uncertaintyId = builder.add(
693
+ `UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(${_fmtExp(uncertaintyVal, 6)}),#${lengthUnitId},'distance_accuracy_value','')`,
694
+ );
695
+ const geomCtxId = builder.add(
696
+ `(GEOMETRIC_REPRESENTATION_CONTEXT(3) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#${uncertaintyId})) GLOBAL_UNIT_ASSIGNED_CONTEXT((#${lengthUnitId},#${angleUnitId},#${solidAngleUnitId})) REPRESENTATION_CONTEXT('',''))`,
697
+ );
698
+
699
+ // Product definition structure.
700
+ const prodCtxId = builder.add(`PRODUCT_CONTEXT('',#${appCtxId},'mechanical')`);
701
+ const productId = builder.add(`PRODUCT('${baseName}','${baseName}','',(#${prodCtxId}))`);
702
+ const formationId = builder.add(`PRODUCT_DEFINITION_FORMATION('','',#${productId})`);
703
+ const prodDefCtxId = builder.add(`PRODUCT_DEFINITION_CONTEXT('part definition',#${appCtxId},'design')`);
704
+ const prodDefId = builder.add(`PRODUCT_DEFINITION('design','',#${formationId},#${prodDefCtxId})`);
705
+ const prodShapeId = builder.add(`PRODUCT_DEFINITION_SHAPE('','',#${prodDefId})`);
706
+
707
+ const brepItems = [];
708
+ const curveItems = [];
709
+ const skipped = [];
710
+
711
+ const directionCache = new Map();
712
+ const getDirectionId = (vec) => {
713
+ const n = _normalize(vec);
714
+ const key = `${_fmtNumber(n[0], 9)},${_fmtNumber(n[1], 9)},${_fmtNumber(n[2], 9)}`;
715
+ let id = directionCache.get(key);
716
+ if (id) return id;
717
+ id = builder.add(`DIRECTION('',(${_fmtNumber(n[0], precision)},${_fmtNumber(n[1], precision)},${_fmtNumber(n[2], precision)}))`);
718
+ directionCache.set(key, id);
719
+ return id;
720
+ };
721
+
722
+ for (let solidIdx = 0; solidIdx < (solids || []).length; solidIdx++) {
723
+ const s = solids[solidIdx];
724
+ if (!s || typeof s.getMesh !== 'function') continue;
725
+
726
+ let mesh = null;
727
+ try {
728
+ mesh = s.getMesh();
729
+ if (!mesh || !mesh.vertProperties || !mesh.triVerts) {
730
+ skipped.push(String(s?.name || `solid_${solidIdx + 1}`));
731
+ continue;
732
+ }
733
+
734
+ let worldMatrixElements = null;
735
+ if (applyWorldTransform) {
736
+ try {
737
+ if (typeof s.updateWorldMatrix === 'function') {
738
+ s.updateWorldMatrix(true, false);
739
+ } else if (typeof s.updateMatrixWorld === 'function') {
740
+ s.updateMatrixWorld(true);
741
+ }
742
+ } catch { /* best-effort */ }
743
+ const wm = s?.matrixWorld;
744
+ if (wm?.elements && wm.elements.length === 16 && !_isIdentityMatrixElements(wm.elements)) {
745
+ worldMatrixElements = wm.elements;
746
+ }
747
+ }
748
+
749
+ const vp = mesh.vertProperties;
750
+ const tv = mesh.triVerts;
751
+ const vertexCount = (vp.length / 3) | 0;
752
+ const triCount = (tv.length / 3) | 0;
753
+
754
+ const vertexPoints = new Array(vertexCount);
755
+ const boundsMin = [Infinity, Infinity, Infinity];
756
+ const boundsMax = [-Infinity, -Infinity, -Infinity];
757
+
758
+ for (let i = 0; i < vertexCount; i++) {
759
+ const x0 = vp[i * 3 + 0];
760
+ const y0 = vp[i * 3 + 1];
761
+ const z0 = vp[i * 3 + 2];
762
+ const [wx, wy, wz] = worldMatrixElements ? _applyMatrix4(worldMatrixElements, x0, y0, z0) : [x0, y0, z0];
763
+ const x = wx * scale;
764
+ const y = wy * scale;
765
+ const z = wz * scale;
766
+ boundsMin[0] = Math.min(boundsMin[0], x);
767
+ boundsMin[1] = Math.min(boundsMin[1], y);
768
+ boundsMin[2] = Math.min(boundsMin[2], z);
769
+ boundsMax[0] = Math.max(boundsMax[0], x);
770
+ boundsMax[1] = Math.max(boundsMax[1], y);
771
+ boundsMax[2] = Math.max(boundsMax[2], z);
772
+ const pointId = builder.add(
773
+ `CARTESIAN_POINT('',(${_fmtNumber(x, precision)},${_fmtNumber(y, precision)},${_fmtNumber(z, precision)}))`,
774
+ );
775
+ const vertexId = builder.add(`VERTEX_POINT('',#${pointId})`);
776
+ vertexPoints[i] = { pointId, vertexId, pos: [x, y, z] };
777
+ }
778
+
779
+ const faceIds = [];
780
+ const degenerateEps = 1e-16;
781
+ const bboxDx = boundsMax[0] - boundsMin[0];
782
+ const bboxDy = boundsMax[1] - boundsMin[1];
783
+ const bboxDz = boundsMax[2] - boundsMin[2];
784
+ const bboxDiag = Math.hypot(bboxDx, bboxDy, bboxDz) || 1;
785
+ const normalTol = Number.isFinite(opts.planarNormalTolerance) ? opts.planarNormalTolerance : 2e-4;
786
+ const distTol = Number.isFinite(opts.planarDistanceTolerance)
787
+ ? opts.planarDistanceTolerance
788
+ : Math.max(2e-5, bboxDiag * 2e-5);
789
+
790
+ const triData = new Array(triCount);
791
+ const faceIDs = (mesh.faceID && mesh.faceID.length === triCount) ? mesh.faceID : null;
792
+ const idToFaceName = (s && s._idToFaceName instanceof Map) ? s._idToFaceName : null;
793
+ const faceGroups = faceIDs ? new Map() : null;
794
+ const planeGroups = faceIDs ? null : new Map();
795
+ const faceNameAllocator = new NameAllocator();
796
+ const edgeNameAllocator = new NameAllocator();
797
+ const edgeCurveCache = new Map();
798
+ const edgeNameByKey = new Map();
799
+
800
+ for (let t = 0; t < triCount; t++) {
801
+ const i0 = tv[t * 3 + 0] >>> 0;
802
+ const i1 = tv[t * 3 + 1] >>> 0;
803
+ const i2 = tv[t * 3 + 2] >>> 0;
804
+
805
+ const v0 = vertexPoints[i0];
806
+ const v1 = vertexPoints[i1];
807
+ const v2 = vertexPoints[i2];
808
+ if (!v0 || !v1 || !v2) continue;
809
+
810
+ const p0 = v0.pos;
811
+ const p1 = v1.pos;
812
+ const p2 = v2.pos;
813
+
814
+ const e1 = _sub(p1, p0);
815
+ const e2 = _sub(p2, p0);
816
+ const normalRaw = _cross(e1, e2);
817
+ const normalLen = Math.hypot(normalRaw[0], normalRaw[1], normalRaw[2]);
818
+ if (!normalLen || normalLen < degenerateEps) continue;
819
+ const normal = [normalRaw[0] / normalLen, normalRaw[1] / normalLen, normalRaw[2] / normalLen];
820
+ const d = _dot(normal, p0);
821
+
822
+ const tri = { i0, i1, i2, normal, d, e1 };
823
+ triData[t] = tri;
824
+
825
+ if (faceIDs) {
826
+ const key = faceIDs[t] >>> 0;
827
+ const list = faceGroups.get(key) || [];
828
+ list.push(t);
829
+ faceGroups.set(key, list);
830
+ } else {
831
+ const key = mergePlanarFaces ? _planeKey(normal, d, normalTol, distTol) : `tri_${t}`;
832
+ const list = planeGroups.get(key) || [];
833
+ list.push(t);
834
+ planeGroups.set(key, list);
835
+ }
836
+ }
837
+
838
+ const buildEdgeNames = exportEdgesAsPolylines || exportFaces;
839
+
840
+ if (faceGroups) {
841
+ // Build edge name map from face adjacency
842
+ if (mergePlanarFaces && buildEdgeNames) {
843
+ const edgeInfo = new Map(); // key -> { faces:Set }
844
+ for (let t = 0; t < triCount; t++) {
845
+ const tri = triData[t];
846
+ if (!tri) continue;
847
+ const fid = faceIDs[t] >>> 0;
848
+ const edges = [
849
+ [tri.i0, tri.i1],
850
+ [tri.i1, tri.i2],
851
+ [tri.i2, tri.i0],
852
+ ];
853
+ for (const [a, b] of edges) {
854
+ const key = _edgeKey(a, b);
855
+ let entry = edgeInfo.get(key);
856
+ if (!entry) {
857
+ entry = { faces: new Set() };
858
+ edgeInfo.set(key, entry);
859
+ }
860
+ entry.faces.add(fid);
861
+ }
862
+ }
863
+
864
+ const baseToKeys = new Map();
865
+ for (const [key, entry] of edgeInfo.entries()) {
866
+ const faces = [...entry.faces];
867
+ if (faces.length === 0) continue;
868
+ let base = null;
869
+ if (faces.length === 1) {
870
+ const name = idToFaceName?.get(faces[0]) || `FACE_${faces[0]}`;
871
+ base = `${name}|BOUNDARY`;
872
+ } else {
873
+ const nameA = idToFaceName?.get(faces[0]) || `FACE_${faces[0]}`;
874
+ const nameB = idToFaceName?.get(faces[1]) || `FACE_${faces[1]}`;
875
+ const pair = nameA < nameB ? [nameA, nameB] : [nameB, nameA];
876
+ base = `${pair[0]}|${pair[1]}`;
877
+ }
878
+ const list = baseToKeys.get(base) || [];
879
+ list.push(key);
880
+ baseToKeys.set(base, list);
881
+ }
882
+
883
+ for (const [base, keys] of baseToKeys.entries()) {
884
+ keys.sort();
885
+ if (keys.length === 1) {
886
+ edgeNameByKey.set(keys[0], _safeStepName(base));
887
+ continue;
888
+ }
889
+ for (let i = 0; i < keys.length; i++) {
890
+ edgeNameByKey.set(keys[i], _safeStepName(`${base}[${i}]`));
891
+ }
892
+ }
893
+ }
894
+
895
+ for (const [faceId, group] of faceGroups.entries()) {
896
+ const baseFaceName = idToFaceName?.get(faceId) || `FACE_${faceId}`;
897
+ const components = _splitComponents(group, triData);
898
+
899
+ // If tessellated faces are enabled, treat non-planar face groups as tessellated.
900
+ if (useTessellatedFaces && exportFaces) {
901
+ const isPlanarGroup = _isCoplanarGroup(group, triData, normalTol, distTol);
902
+ if (isPlanarGroup && mergePlanarFaces) {
903
+ for (const comp of components) {
904
+ const faceName = faceNameAllocator.allocate(baseFaceName);
905
+ const edgeContext = (mergePlanarFaces && exportEdgesAsPolylines) ? { edgeNameByKey, edgeCurveCache, edgeNameAllocator } : null;
906
+ const merged = _emitPlanarComponent(comp, triData, vertexPoints, builder, getDirectionId, precision, faceName, edgeContext);
907
+ if (merged.ok && merged.faceId) {
908
+ faceIds.push(merged.faceId);
909
+ continue;
910
+ }
911
+ const tess = _emitTessellatedFace(comp, triData, vertexPoints, builder, precision, faceName);
912
+ if (tess.ok && tess.faceId) {
913
+ faceIds.push(tess.faceId);
914
+ continue;
915
+ }
916
+ for (const triIdx of comp) {
917
+ const tri = triData[triIdx];
918
+ if (!tri) continue;
919
+ const triName = faceNameAllocator.allocate(baseFaceName);
920
+ const faceId = _emitTriangleFace(tri, vertexPoints, builder, getDirectionId, precision, triName);
921
+ if (faceId) faceIds.push(faceId);
922
+ }
923
+ }
924
+ continue;
925
+ }
926
+
927
+ if (!isPlanarGroup) {
928
+ for (const comp of components) {
929
+ const faceName = faceNameAllocator.allocate(baseFaceName);
930
+ const tess = _emitTessellatedFace(comp, triData, vertexPoints, builder, precision, faceName);
931
+ if (tess.ok && tess.faceId) {
932
+ faceIds.push(tess.faceId);
933
+ } else {
934
+ for (const triIdx of comp) {
935
+ const tri = triData[triIdx];
936
+ if (!tri) continue;
937
+ const triName = faceNameAllocator.allocate(baseFaceName);
938
+ const faceId = _emitTriangleFace(tri, vertexPoints, builder, getDirectionId, precision, triName);
939
+ if (faceId) faceIds.push(faceId);
940
+ }
941
+ }
942
+ }
943
+ continue;
944
+ }
945
+ }
946
+
947
+ // No tessellated faces: still merge planar regions within the face group.
948
+ if (mergePlanarFaces && exportFaces) {
949
+ const subPlanes = new Map();
950
+ for (const triIdx of group) {
951
+ const tri = triData[triIdx];
952
+ if (!tri) continue;
953
+ const key = _planeKey(tri.normal, tri.d, normalTol, distTol);
954
+ const list = subPlanes.get(key) || [];
955
+ list.push(triIdx);
956
+ subPlanes.set(key, list);
957
+ }
958
+ for (const planeGroup of subPlanes.values()) {
959
+ const planeComponents = _splitComponents(planeGroup, triData);
960
+ for (const comp of planeComponents) {
961
+ const faceName = faceNameAllocator.allocate(baseFaceName);
962
+ const edgeContext = exportEdgesAsPolylines ? { edgeNameByKey, edgeCurveCache, edgeNameAllocator } : null;
963
+ const merged = _emitPlanarComponent(comp, triData, vertexPoints, builder, getDirectionId, precision, faceName, edgeContext);
964
+ if (merged.ok && merged.faceId) {
965
+ faceIds.push(merged.faceId);
966
+ } else {
967
+ for (const triIdx of comp) {
968
+ const tri = triData[triIdx];
969
+ if (!tri) continue;
970
+ const triName = faceNameAllocator.allocate(baseFaceName);
971
+ const faceId = _emitTriangleFace(tri, vertexPoints, builder, getDirectionId, precision, triName);
972
+ if (faceId) faceIds.push(faceId);
973
+ }
974
+ }
975
+ }
976
+ }
977
+ } else if (exportFaces) {
978
+ for (const triIdx of group) {
979
+ const tri = triData[triIdx];
980
+ if (!tri) continue;
981
+ const faceName = faceNameAllocator.allocate(baseFaceName);
982
+ const faceId = _emitTriangleFace(tri, vertexPoints, builder, getDirectionId, precision, faceName);
983
+ if (faceId) faceIds.push(faceId);
984
+ }
985
+ }
986
+ }
987
+ } else if (planeGroups) {
988
+ let planeIndex = 0;
989
+ for (const group of planeGroups.values()) {
990
+ const components = mergePlanarFaces ? _splitComponents(group, triData) : [group];
991
+ const baseFaceName = `FACE_${++planeIndex}`;
992
+ for (const comp of components) {
993
+ if (mergePlanarFaces && exportFaces) {
994
+ const faceName = faceNameAllocator.allocate(baseFaceName);
995
+ const edgeContext = (mergePlanarFaces && exportEdgesAsPolylines) ? { edgeNameByKey, edgeCurveCache, edgeNameAllocator } : null;
996
+ const merged = _emitPlanarComponent(comp, triData, vertexPoints, builder, getDirectionId, precision, faceName, edgeContext);
997
+ if (merged.ok && merged.faceId) {
998
+ faceIds.push(merged.faceId);
999
+ continue;
1000
+ }
1001
+ }
1002
+ if (exportFaces) {
1003
+ for (const triIdx of comp) {
1004
+ const tri = triData[triIdx];
1005
+ if (!tri) continue;
1006
+ const faceName = faceNameAllocator.allocate(baseFaceName);
1007
+ const faceId = _emitTriangleFace(tri, vertexPoints, builder, getDirectionId, precision, faceName);
1008
+ if (faceId) faceIds.push(faceId);
1009
+ }
1010
+ }
1011
+ }
1012
+ }
1013
+ }
1014
+
1015
+ if (exportFaces) {
1016
+ if (faceIds.length === 0) {
1017
+ skipped.push(String(s?.name || `solid_${solidIdx + 1}`));
1018
+ continue;
1019
+ }
1020
+
1021
+ const shellId = builder.add(`CLOSED_SHELL('',${_formatEntityList(faceIds)})`);
1022
+ const brepId = builder.add(`FACETED_BREP('',#${shellId})`);
1023
+ brepItems.push(brepId);
1024
+ }
1025
+
1026
+ if (exportEdgesAsPolylines) {
1027
+ const boundaryPolys = _buildBoundaryEdgePolylines(mesh, idToFaceName);
1028
+ for (const poly of boundaryPolys) {
1029
+ const indices = Array.isArray(poly.indices) ? poly.indices : [];
1030
+ if (indices.length < 2) continue;
1031
+ const pointRefs = [];
1032
+ let hasMissing = false;
1033
+ for (const idx of indices) {
1034
+ const pointId = vertexPoints[idx]?.pointId;
1035
+ if (!pointId) { hasMissing = true; break; }
1036
+ pointRefs.push(`#${pointId}`);
1037
+ }
1038
+ if (hasMissing || pointRefs.length < 2) continue;
1039
+ const baseName = poly.name || `${poly.faceA}|${poly.faceB}`;
1040
+ const name = edgeNameAllocator.allocate(baseName);
1041
+ const polyId = builder.add(`POLYLINE('${_safeStepName(name)}',(${pointRefs.join(',')}))`);
1042
+ curveItems.push(polyId);
1043
+ }
1044
+
1045
+ const auxEdges = Array.isArray(s?._auxEdges) ? s._auxEdges : [];
1046
+ for (const aux of auxEdges) {
1047
+ if (!aux) continue;
1048
+ const auxName = aux?.name || 'CENTERLINE';
1049
+ const isCenterline = !!aux?.centerline || (typeof auxName === 'string' && /centerline/i.test(auxName));
1050
+ if (!isCenterline) continue;
1051
+ const pts = Array.isArray(aux?.points) ? aux.points : [];
1052
+ if (pts.length < 2) continue;
1053
+
1054
+ const polyPoints = [];
1055
+ for (const p of pts) {
1056
+ if (!Array.isArray(p) || p.length < 3) continue;
1057
+ let x = p[0];
1058
+ let y = p[1];
1059
+ let z = p[2];
1060
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) continue;
1061
+ if (applyWorldTransform && worldMatrixElements && !aux?.polylineWorld) {
1062
+ const t = _applyMatrix4(worldMatrixElements, x, y, z);
1063
+ x = t[0]; y = t[1]; z = t[2];
1064
+ }
1065
+ polyPoints.push([x * scale, y * scale, z * scale]);
1066
+ }
1067
+ if (polyPoints.length < 2) continue;
1068
+
1069
+ if (aux?.closedLoop && polyPoints.length >= 2) {
1070
+ const first = polyPoints[0];
1071
+ const last = polyPoints[polyPoints.length - 1];
1072
+ if (first[0] !== last[0] || first[1] !== last[1] || first[2] !== last[2]) {
1073
+ polyPoints.push([first[0], first[1], first[2]]);
1074
+ }
1075
+ }
1076
+
1077
+ const pointRefs = [];
1078
+ let invalid = false;
1079
+ for (const pt of polyPoints) {
1080
+ if (!pt || pt.length < 3) { invalid = true; break; }
1081
+ const pointId = builder.add(
1082
+ `CARTESIAN_POINT('',(${_fmtNumber(pt[0], precision)},${_fmtNumber(pt[1], precision)},${_fmtNumber(pt[2], precision)}))`,
1083
+ );
1084
+ pointRefs.push(`#${pointId}`);
1085
+ }
1086
+ if (invalid || pointRefs.length < 2) continue;
1087
+ const name = edgeNameAllocator.allocate(auxName);
1088
+ const polyId = builder.add(`POLYLINE('${_safeStepName(name)}',(${pointRefs.join(',')}))`);
1089
+ curveItems.push(polyId);
1090
+ }
1091
+ }
1092
+ } catch (err) {
1093
+ skipped.push(String(s?.name || `solid_${solidIdx + 1}`));
1094
+ } finally {
1095
+ try { if (mesh && typeof mesh.delete === 'function') mesh.delete(); } catch { /* ignore */ }
1096
+ }
1097
+ }
1098
+
1099
+ const repItems = [];
1100
+ if (exportFaces && brepItems.length) repItems.push(...brepItems);
1101
+ if (exportEdgesAsPolylines && curveItems.length) {
1102
+ const curveSetId = builder.add(`GEOMETRIC_CURVE_SET('EDGES',${_formatEntityList(curveItems)})`);
1103
+ repItems.push(curveSetId);
1104
+ }
1105
+ if (repItems.length === 0) {
1106
+ return { data: '', exported: 0, skipped: solids?.map((s) => s?.name || 'solid') || [] };
1107
+ }
1108
+ const shapeRepName = useTessellatedFaces ? 'tessellated' : '';
1109
+ const repEntity = exportEdgesAsPolylines ? 'SHAPE_REPRESENTATION' : 'ADVANCED_BREP_SHAPE_REPRESENTATION';
1110
+ const shapeRepId = builder.add(
1111
+ `${repEntity}('${shapeRepName}',${_formatEntityList(repItems)},#${geomCtxId})`,
1112
+ );
1113
+ builder.add(`SHAPE_DEFINITION_REPRESENTATION(#${prodShapeId},#${shapeRepId})`);
1114
+
1115
+ const now = new Date();
1116
+ const isoNoMs = now.toISOString().replace(/\.\d+Z$/, '');
1117
+
1118
+ const header = [
1119
+ 'ISO-10303-21;',
1120
+ 'HEADER;',
1121
+ "FILE_DESCRIPTION(('BREP STEP export'),'2;1');",
1122
+ `FILE_NAME('${baseName}','${_escapeStepString(isoNoMs)}',('BREP'),('BREP'),'Codex','BREP','');`,
1123
+ `FILE_SCHEMA(('${useTessellatedFaces ? 'AP242_MANAGED_MODEL_BASED_3D_ENGINEERING' : 'AUTOMOTIVE_DESIGN'}'));`,
1124
+ 'ENDSEC;',
1125
+ 'DATA;',
1126
+ ];
1127
+
1128
+ const footer = [
1129
+ 'ENDSEC;',
1130
+ 'END-ISO-10303-21;',
1131
+ ];
1132
+
1133
+ const data = `${header.join('\n')}\n${builder.lines.join('\n')}\n${footer.join('\n')}\n`;
1134
+ return { data, exported: repItems.length, skipped };
1135
+ }