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,727 @@
1
+ import { BREP } from "../../BREP/BREP.js";
2
+ const THREE = BREP.THREE;
3
+ import { LineGeometry } from 'three/examples/jsm/Addons.js';
4
+ import { ImageEditorUI } from './imageEditor.js';
5
+ import { traceImageDataToPolylines, applyCurveFit, rdp, assignBreaksToLoops, splitLoopIntoEdges, sanitizeLoopsForExtrude, dropIntersectingLoops } from './traceUtils.js';
6
+
7
+ const renderHiddenField = ({ id, row }) => {
8
+ if (row && row.style) row.style.display = 'none';
9
+ const input = document.createElement('input');
10
+ input.type = 'hidden';
11
+ input.id = id;
12
+ return { inputEl: input, inputRegistered: false, skipDefaultRefresh: true };
13
+ };
14
+
15
+ const inputParamsSchema = {
16
+ id: {
17
+ type: "string",
18
+ default_value: null,
19
+ hint: "unique identifier for the image trace feature",
20
+ },
21
+ fileToImport: {
22
+ type: "file",
23
+ default_value: "",
24
+ accept: ".png,image/png",
25
+ hint: "Monochrome PNG data (click to choose a file)",
26
+ },
27
+ editImage: {
28
+ type: "button",
29
+ label: "Edit Image",
30
+ default_value: null,
31
+ hint: "Launch the paint like image editor",
32
+ actionFunction: (ctx) => {
33
+ let { fileToImport } = ctx.feature.inputParams;
34
+ // If no image, start with a blank 300x300 transparent canvas
35
+ if (!fileToImport) {
36
+ try {
37
+ const c = document.createElement('canvas');
38
+ c.width = 300; c.height = 300;
39
+ const ctx2d = c.getContext('2d');
40
+ ctx2d.fillStyle = '#ffffff';
41
+ ctx2d.fillRect(0, 0, c.width, c.height);
42
+ fileToImport = c.toDataURL('image/png');
43
+ } catch (_) { fileToImport = null; }
44
+ }
45
+ const imageEditor = new ImageEditorUI(fileToImport, {
46
+ onSave: (editedImage) => {
47
+ // Update both live feature params and dialog params
48
+ try { ctx.feature.inputParams.fileToImport = editedImage; } catch (_) {}
49
+ try { if (ctx.params) ctx.params.fileToImport = editedImage; } catch (_) {}
50
+ // Trigger recompute akin to onChange
51
+ try {
52
+ if (ctx.partHistory) {
53
+ ctx.partHistory.currentHistoryStepId = ctx.feature.inputParams.featureID;
54
+ if (typeof ctx.partHistory.runHistory === 'function') {
55
+ const runPromise = ctx.partHistory.runHistory();
56
+ if (runPromise && typeof runPromise.then === 'function') {
57
+ runPromise.then(() => ctx.partHistory?.queueHistorySnapshot?.({ debounceMs: 0, reason: 'image-edit' }));
58
+ } else {
59
+ ctx.partHistory?.queueHistorySnapshot?.({ debounceMs: 0, reason: 'image-edit' });
60
+ }
61
+ }
62
+ }
63
+ } catch (_) {}
64
+ },
65
+ onCancel: () => { /* no-op */ }
66
+ }, {
67
+ featureSchema: inputParamsSchema,
68
+ featureParams: ctx && ctx.feature && ctx.feature.inputParams ? ctx.feature.inputParams : (ctx?.params || {}),
69
+ partHistory: ctx && ctx.partHistory ? ctx.partHistory : null,
70
+ viewer: ctx && ctx.viewer ? ctx.viewer : (ctx && ctx.partHistory && ctx.partHistory.viewer ? ctx.partHistory.viewer : null),
71
+ onParamsChange: () => {
72
+ try {
73
+ if (ctx && ctx.partHistory) {
74
+ ctx.partHistory.currentHistoryStepId = ctx.feature?.inputParams?.featureID;
75
+ if (typeof ctx.partHistory.runHistory === 'function') {
76
+ const runPromise = ctx.partHistory.runHistory();
77
+ if (runPromise && typeof runPromise.then === 'function') {
78
+ runPromise.then(() => ctx.partHistory?.queueHistorySnapshot?.({ debounceMs: 0, reason: 'image-edit' }));
79
+ } else {
80
+ ctx.partHistory?.queueHistorySnapshot?.({ debounceMs: 0, reason: 'image-edit' });
81
+ }
82
+ }
83
+ }
84
+ } catch (_) { /* ignore */ }
85
+ }
86
+ });
87
+ imageEditor.open();
88
+ }
89
+ },
90
+
91
+ threshold: {
92
+ type: "number",
93
+ default_value: 128,
94
+ hint: "Pixel threshold (0-255) to classify foreground vs background",
95
+ },
96
+ invert: {
97
+ type: "boolean",
98
+ default_value: false,
99
+ hint: "Invert classification (swap foreground/background)",
100
+ },
101
+ pixelScale: {
102
+ type: "number",
103
+ default_value: 1,
104
+ hint: "World units per pixel (scale for the traced face)",
105
+ },
106
+ center: {
107
+ type: "boolean",
108
+ default_value: true,
109
+ hint: "Center the traced result around the origin",
110
+ },
111
+ smoothCurves: {
112
+ type: "boolean",
113
+ default_value: true,
114
+ hint: "Fit curved segments (Potrace-like) to smooth the traced outlines",
115
+ },
116
+ curveTolerance: {
117
+ type: "number",
118
+ default_value: 0.75,
119
+ step:0.1,
120
+ hint: "Max deviation (world units) for curve smoothing/flattening; larger = smoother",
121
+ },
122
+ speckleArea: {
123
+ type: "number",
124
+ default_value: 2,
125
+ hint: "Discard tiny traced loops below this pixel-area (turd size)",
126
+ },
127
+ simplifyCollinear: {
128
+ type: "boolean",
129
+ default_value: false,
130
+ hint: "Remove intermediate points on straight segments",
131
+ },
132
+ rdpTolerance: {
133
+ type: "number",
134
+ default_value: 1,
135
+ hint: "Optional Ramer–Douglas–Peucker tolerance in world units (0 to disable)",
136
+ },
137
+ edgeSplitAngle: {
138
+ type: "number",
139
+ default_value: 70,
140
+ step: 1,
141
+ hint: "Corner angle (deg) for splitting traced loops into edge segments",
142
+ },
143
+ edgeMinSpacing: {
144
+ type: "number",
145
+ default_value: 0,
146
+ step: 0.5,
147
+ hint: "Minimum edge length between corner splits (world units)",
148
+ },
149
+ edgeBreakPoints: {
150
+ type: "string",
151
+ default_value: [],
152
+ label: "Edge Break Points",
153
+ hint: "Internal use: manual edge breaks from the image editor",
154
+ renderWidget: renderHiddenField,
155
+ },
156
+ edgeSuppressedBreaks: {
157
+ type: "string",
158
+ default_value: [],
159
+ label: "Edge Suppressed Breaks",
160
+ hint: "Internal use: suppressed auto breaks from the image editor",
161
+ renderWidget: renderHiddenField,
162
+ },
163
+ placementPlane: {
164
+ type: "reference_selection",
165
+ selectionFilter: ["PLANE", "FACE"],
166
+ multiple: false,
167
+ default_value: null,
168
+ hint: "Select a plane or face where the traced image will be placed",
169
+ },
170
+ };
171
+
172
+ export class ImageToFaceFeature {
173
+ static shortName = "IMAGE";
174
+ static longName = "Image to Face";
175
+ static inputParamsSchema = inputParamsSchema;
176
+
177
+ constructor() {
178
+ this.inputParams = {};
179
+ this.persistentData = {};
180
+ }
181
+
182
+ async run(partHistory) {
183
+ const { fileToImport, threshold, invert, pixelScale, center, smoothCurves, curveTolerance, speckleArea, simplifyCollinear, rdpTolerance, edgeSplitAngle, edgeMinSpacing, edgeBreakPoints, edgeSuppressedBreaks } = this.inputParams;
184
+
185
+ const imageData = await decodeToImageData(fileToImport);
186
+ if (!imageData) {
187
+ console.warn('[IMAGE] No image data decoded');
188
+ return { added: [], removed: [] };
189
+ }
190
+
191
+ const scale = Number(pixelScale) || 1;
192
+ const traceLoops = traceImageDataToPolylines(imageData, {
193
+ threshold: Number.isFinite(Number(threshold)) ? Number(threshold) : 128,
194
+ mode: "luma+alpha",
195
+ invert: !!invert,
196
+ mergeCollinear: !!simplifyCollinear,
197
+ simplify: (rdpTolerance && Number(rdpTolerance) > 0) ? (Number(rdpTolerance) / Math.max(Math.abs(scale) || 1, 1e-9)) : 0,
198
+ minArea: Number.isFinite(Number(speckleArea)) ? Math.max(0, Number(speckleArea)) : 0,
199
+ });
200
+ const loopsGrid = traceLoops.map((loop) => loop.map((p) => [p.x, p.y]));
201
+ if (!loopsGrid.length) {
202
+ console.warn('[IMAGE] No contours found in image');
203
+ return { added: [], removed: [] };
204
+ }
205
+
206
+ // Convert grid loops (integer node coords in image space, y-down) to world 2D loops (x, y-up)
207
+ const loops2D = loopsGrid.map((pts) => gridToWorld2D(pts, scale));
208
+
209
+ // Optional curve fitting (Potrace-like) then simplification/cleanup
210
+ let workingLoops = loops2D;
211
+ const fallbackLoops = loops2D.map((l) => simplifyLoop(l, { simplifyCollinear: true, rdpTolerance: 0 }));
212
+ if (smoothCurves !== false) {
213
+ workingLoops = applyCurveFit(workingLoops, {
214
+ tolerance: Number.isFinite(Number(curveTolerance)) ? Math.max(0.01, Number(curveTolerance)) : Math.max(0.05, Math.abs(scale) * 0.75),
215
+ cornerThresholdDeg: 70,
216
+ iterations: 3,
217
+ });
218
+ }
219
+ const cleanCollinear = smoothCurves === false;
220
+ let simpLoops = workingLoops.map((l) => simplifyLoop(l, { simplifyCollinear: cleanCollinear, rdpTolerance: 0 }));
221
+ const sanitizeEps = Math.max(1e-6, 1e-6 * Math.max(Math.abs(scale) || 1, 1));
222
+ simpLoops = sanitizeLoopsForExtrude(simpLoops, fallbackLoops, { eps: sanitizeEps });
223
+ const invalidCount = simpLoops.filter((l) => !Array.isArray(l) || l.length < 3).length;
224
+ if (invalidCount) console.warn(`[IMAGE] Dropped ${invalidCount} degenerate or self-intersecting loop(s)`);
225
+ const beforeIntersect = simpLoops.length;
226
+ simpLoops = dropIntersectingLoops(simpLoops, { eps: sanitizeEps });
227
+ const droppedIntersect = beforeIntersect - simpLoops.length;
228
+ if (droppedIntersect) console.warn(`[IMAGE] Dropped ${droppedIntersect} intersecting loop(s) to keep output manifold`);
229
+ simpLoops = simpLoops.filter((l) => Array.isArray(l) && l.length >= 3);
230
+ if (!simpLoops.length) {
231
+ console.warn('[IMAGE] All loops invalid after cleanup; aborting');
232
+ return { added: [], removed: [] };
233
+ }
234
+
235
+ // Optionally center (only if there are any points)
236
+ let centerOffset = { x: 0, y: 0 };
237
+ if (center) {
238
+ const allPts = simpLoops.flat();
239
+ if (allPts.length) {
240
+ const bb = bounds2D(allPts);
241
+ const cx = 0.5 * (bb.minX + bb.maxX);
242
+ const cy = 0.5 * (bb.minY + bb.maxY);
243
+ centerOffset = { x: cx, y: cy };
244
+ simpLoops = simpLoops.map((loop) => loop.map(([x, y]) => [x - cx, y - cy]));
245
+ }
246
+ }
247
+
248
+ const cornerThresholdDeg = Number.isFinite(Number(edgeSplitAngle))
249
+ ? Math.max(1, Math.min(179, Number(edgeSplitAngle)))
250
+ : 70;
251
+ const cornerSpacing = Number.isFinite(Number(edgeMinSpacing))
252
+ ? Math.max(0, Number(edgeMinSpacing))
253
+ : 0;
254
+ const minSegLen = Math.max(0.5 * Math.abs(scale || 1), 1e-6);
255
+
256
+ const manualBreaksWorld = normalizeBreakPoints(edgeBreakPoints, {
257
+ scale,
258
+ offsetX: centerOffset.x,
259
+ offsetY: centerOffset.y,
260
+ });
261
+ const suppressedBreaksWorld = normalizeBreakPoints(edgeSuppressedBreaks, {
262
+ scale,
263
+ offsetX: centerOffset.x,
264
+ offsetY: centerOffset.y,
265
+ });
266
+ const initialBreaksByLoop = manualBreaksWorld.length
267
+ ? assignBreaksToLoops(simpLoops, manualBreaksWorld)
268
+ : simpLoops.map(() => []);
269
+ const loopsWithBreaks = initialBreaksByLoop.some((arr) => arr.length)
270
+ ? simpLoops.map((loop, idx) => {
271
+ const breaks = initialBreaksByLoop[idx] || [];
272
+ if (!breaks.length) return loop;
273
+ const info = splitLoopIntoEdges(loop, {
274
+ angleDeg: cornerThresholdDeg,
275
+ minSegLen,
276
+ cornerSpacing,
277
+ manualBreaks: breaks,
278
+ autoBreaks: false,
279
+ returnDebug: true,
280
+ });
281
+ return Array.isArray(info?.ring) && info.ring.length ? info.ring : loop;
282
+ })
283
+ : simpLoops;
284
+ const breaksByLoop = manualBreaksWorld.length
285
+ ? assignBreaksToLoops(loopsWithBreaks, manualBreaksWorld)
286
+ : loopsWithBreaks.map(() => []);
287
+ const suppressedByLoop = suppressedBreaksWorld.length
288
+ ? assignBreaksToLoops(loopsWithBreaks, suppressedBreaksWorld)
289
+ : loopsWithBreaks.map(() => []);
290
+
291
+ // Group into outer + holes by nesting parity
292
+ const groups = groupLoopsOuterHoles(loopsWithBreaks);
293
+
294
+ // Determine placement transform from selected plane/face
295
+ const basis = getPlacementBasis(this.inputParams?.placementPlane, partHistory);
296
+ const bO = new THREE.Vector3().fromArray(basis.origin);
297
+ const bX = new THREE.Vector3().fromArray(basis.x);
298
+ const bY = new THREE.Vector3().fromArray(basis.y);
299
+ const bZ = new THREE.Vector3().fromArray(basis.z);
300
+ const m = new THREE.Matrix4().makeBasis(bX, bY, bZ).setPosition(bO);
301
+ // Quantize world coordinates to reduce FP drift and guarantee identical
302
+ // vertices between caps and walls. Use a small absolute grid (~1e-6).
303
+ const Q = 1e-6;
304
+ const q = (n) => Math.abs(n) < Q ? 0 : Math.round(n / Q) * Q;
305
+ const toW = (x, y) => {
306
+ const v = new THREE.Vector3(x, y, 0).applyMatrix4(m);
307
+ return [q(v.x), q(v.y), q(v.z)];
308
+ };
309
+
310
+ // Build triangulated Face and boundary Edges
311
+ const sceneGroup = new THREE.Group();
312
+ const featureId = (this.inputParams?.featureID != null && String(this.inputParams.featureID).length)
313
+ ? String(this.inputParams.featureID)
314
+ : 'IMAGE_Sketch';
315
+ const edgeNamePrefix = featureId ? `${featureId}:` : '';
316
+ sceneGroup.name = featureId;
317
+ sceneGroup.type = 'SKETCH';
318
+ sceneGroup.onClick = () => { };
319
+ sceneGroup.userData = sceneGroup.userData || {};
320
+ sceneGroup.userData.sketchBasis = {
321
+ origin: Array.isArray(basis.origin) ? basis.origin.slice() : [0, 0, 0],
322
+ x: Array.isArray(basis.x) ? basis.x.slice() : [1, 0, 0],
323
+ y: Array.isArray(basis.y) ? basis.y.slice() : [0, 1, 0],
324
+ z: Array.isArray(basis.z) ? basis.z.slice() : [0, 0, 1],
325
+ };
326
+
327
+ // Build triangulation using THREE.ShapeUtils
328
+ const triPositions = [];
329
+ const boundaryLoopsWorld = [];
330
+ const profileGroups = [];
331
+
332
+ for (const grp of groups) {
333
+ let contour = grp.outer.slice();
334
+ // Drop duplicate last point if present for triangulation API
335
+ if (contour.length >= 2) {
336
+ const f = contour[0], l = contour[contour.length - 1];
337
+ if (f[0] === l[0] && f[1] === l[1]) contour.pop();
338
+ }
339
+ if (signedArea([...contour, contour[0]]) > 0) contour = contour.reverse(); // ensure CW for outer
340
+ const holes = grp.holes.map((h) => {
341
+ let hh = h.slice();
342
+ if (hh.length >= 2) {
343
+ const f = hh[0], l = hh[hh.length - 1];
344
+ if (f[0] === l[0] && f[1] === l[1]) hh.pop();
345
+ }
346
+ if (signedArea([...hh, hh[0]]) < 0) hh = hh.reverse(); // ensure CCW for holes
347
+ return hh;
348
+ });
349
+
350
+ const contourV2 = contour.map((p) => new THREE.Vector2(p[0], p[1]));
351
+ const holesV2 = holes.map((arr) => arr.map((p) => new THREE.Vector2(p[0], p[1])));
352
+ const tris = THREE.ShapeUtils.triangulateShape(contourV2, holesV2);
353
+
354
+ const allPts = contour.concat(...holes);
355
+ for (const t of tris) {
356
+ const a = allPts[t[0]], b = allPts[t[1]], c = allPts[t[2]];
357
+ triPositions.push(a[0], a[1], 0, b[0], b[1], 0, c[0], c[1], 0);
358
+ }
359
+
360
+ // Boundary loop records for downstream Sweep side construction
361
+ const contourClosed = (contour.length && (contour[0][0] === contour[contour.length - 1][0] && contour[0][1] === contour[contour.length - 1][1])) ? contour : contour.concat([contour[0]]);
362
+ const contourClosedW = contourClosed.map(([x, y]) => toW(x, y));
363
+ boundaryLoopsWorld.push({ pts: contourClosedW, isHole: false });
364
+ const holesClosed = holes.map((h) => (h.length && (h[0][0] === h[h.length - 1][0] && h[0][1] === h[h.length - 1][1])) ? h : h.concat([h[0]]));
365
+ const holesClosedW = holesClosed.map((h) => h.map(([x, y]) => toW(x, y)));
366
+ for (const hw of holesClosedW) boundaryLoopsWorld.push({ pts: hw, isHole: true });
367
+
368
+ // For profileGroups used by Sweep caps, store OPEN loops (no duplicate last point)
369
+ const contourOpen = contourClosed.slice(0, -1);
370
+ const holesOpen = holesClosed.map(h => h.slice(0, -1));
371
+ profileGroups.push({
372
+ contour2D: contourOpen.slice(),
373
+ holes2D: holesOpen.map(h => h.slice()),
374
+ contourW: contourClosedW.slice(0, -1),
375
+ holesW: holesClosedW.map(hw => hw.slice(0, -1))
376
+ });
377
+ }
378
+
379
+ if (!triPositions.length) {
380
+ console.warn('[IMAGE] Triangulation produced no area');
381
+ return { added: [], removed: [] };
382
+ }
383
+
384
+ const geom = new THREE.BufferGeometry();
385
+ geom.setAttribute('position', new THREE.Float32BufferAttribute(triPositions, 3));
386
+ // Transform triangles from local plane to world placement
387
+ geom.applyMatrix4(m);
388
+ // Quantize geometry to the same grid as boundary loops/edges.
389
+ const posAttr = geom.getAttribute('position');
390
+ if (posAttr && posAttr.itemSize === 3) {
391
+ for (let i = 0; i < posAttr.count; i++) {
392
+ posAttr.setXYZ(
393
+ i,
394
+ q(posAttr.getX(i)),
395
+ q(posAttr.getY(i)),
396
+ q(posAttr.getZ(i))
397
+ );
398
+ }
399
+ posAttr.needsUpdate = true;
400
+ }
401
+ geom.computeVertexNormals();
402
+ geom.computeBoundingSphere();
403
+
404
+ const face = new BREP.Face(geom);
405
+ face.type = 'FACE';
406
+ face.name = `${edgeNamePrefix}PROFILE`;
407
+ face.userData.faceName = face.name;
408
+ face.userData.boundaryLoopsWorld = boundaryLoopsWorld;
409
+ face.userData.profileGroups = profileGroups;
410
+
411
+ // Edges from loops, split at corners to enable per-edge sidewalls
412
+ const edges = [];
413
+ let edgeIdx = 0;
414
+ let loopIdx = 0;
415
+ const addEdgeSegmentsFromLoop = (loop2D, isHole, manualBreaks, suppressedBreaks) => {
416
+ if (!loop2D || loop2D.length < 2) return;
417
+ const segments = splitLoopIntoEdges(loop2D, {
418
+ angleDeg: cornerThresholdDeg,
419
+ minSegLen,
420
+ cornerSpacing,
421
+ manualBreaks,
422
+ suppressedBreaks,
423
+ autoBreaks: false
424
+ });
425
+ let segIdx = 0;
426
+ for (const seg of segments) {
427
+ if (!seg || seg.length < 2) continue;
428
+ const positions = [];
429
+ const worldPts = [];
430
+ for (let i = 0; i < seg.length; i++) {
431
+ const p = seg[i];
432
+ const w = toW(p[0], p[1]);
433
+ positions.push(w[0], w[1], w[2]);
434
+ worldPts.push([w[0], w[1], w[2]]);
435
+ }
436
+ if (positions.length < 6) continue;
437
+ const lg = new LineGeometry();
438
+ lg.setPositions(positions);
439
+ try { lg.computeBoundingSphere(); } catch { }
440
+ const e = new BREP.Edge(lg);
441
+ e.type = 'EDGE';
442
+ e.name = `${edgeNamePrefix}L${edgeIdx++}`;
443
+ e.closedLoop = false;
444
+ e.userData = {
445
+ polylineLocal: worldPts,
446
+ polylineWorld: true,
447
+ isHole: !!isHole,
448
+ loopIndex: loopIdx,
449
+ segmentIndex: segIdx++
450
+ };
451
+ edges.push(e);
452
+ }
453
+ loopIdx++;
454
+ };
455
+ // Emit edge segments for outer and hole loops
456
+ for (const grp of groups) {
457
+ const outerClosed = grp.outer[0] && grp.outer[grp.outer.length - 1] && (grp.outer[0][0] === grp.outer[grp.outer.length - 1][0] && grp.outer[0][1] === grp.outer[grp.outer.length - 1][1]) ? grp.outer : grp.outer.concat([grp.outer[0]]);
458
+ const outerBreaks = breaksByLoop[grp.outerIndex] || [];
459
+ const outerSuppressed = suppressedByLoop[grp.outerIndex] || [];
460
+ addEdgeSegmentsFromLoop(outerClosed, false, outerBreaks, outerSuppressed);
461
+ for (let hi = 0; hi < grp.holes.length; hi++) {
462
+ const h = grp.holes[hi];
463
+ const hClosed = h[0] && h[h.length - 1] && (h[0][0] === h[h.length - 1][0] && h[0][1] === h[h.length - 1][1]) ? h : h.concat([h[0]]);
464
+ const holeIndex = grp.holeIndices[hi];
465
+ const holeBreaks = breaksByLoop[holeIndex] || [];
466
+ const holeSuppressed = suppressedByLoop[holeIndex] || [];
467
+ addEdgeSegmentsFromLoop(hClosed, true, holeBreaks, holeSuppressed);
468
+ }
469
+ }
470
+
471
+ // Attach edge references to face for convenience
472
+ try { face.edges = edges.slice(); } catch { }
473
+
474
+ sceneGroup.add(face);
475
+ for (const e of edges) sceneGroup.add(e);
476
+
477
+ return { added: [sceneGroup], removed: [] };
478
+ }
479
+ }
480
+
481
+ // --- Helpers -----------------------------------------------------------------
482
+
483
+ async function decodeToImageData(raw) {
484
+ try {
485
+ if (!raw) return null;
486
+ if (raw instanceof ImageData) return raw;
487
+ if (raw instanceof ArrayBuffer) {
488
+ // Attempt to decode as PNG
489
+ try {
490
+ const blob = new Blob([raw], { type: 'image/png' });
491
+ const img = await createImageBitmap(blob);
492
+ const c = document.createElement('canvas');
493
+ c.width = img.width; c.height = img.height;
494
+ const ctx = c.getContext('2d');
495
+ ctx.drawImage(img, 0, 0);
496
+ const id = ctx.getImageData(0, 0, img.width, img.height);
497
+ try { img.close && img.close(); } catch { }
498
+ return id;
499
+ } catch { }
500
+ return null;
501
+ }
502
+ if (typeof raw === 'string') {
503
+ if (raw.startsWith('data:')) {
504
+ const img = await createImageBitmap(await (await fetch(raw)).blob());
505
+ const c = document.createElement('canvas');
506
+ c.width = img.width; c.height = img.height;
507
+ const ctx = c.getContext('2d');
508
+ ctx.drawImage(img, 0, 0);
509
+ const id = ctx.getImageData(0, 0, img.width, img.height);
510
+ try { img.close && img.close(); } catch { }
511
+ return id;
512
+ }
513
+ // Try to parse as binary base64 (png)
514
+ try {
515
+ const b64 = raw;
516
+ const binaryStr = (typeof atob === 'function') ? atob(b64) : (typeof Buffer !== 'undefined' ? Buffer.from(b64, 'base64').toString('binary') : '');
517
+ const len = binaryStr.length | 0;
518
+ const bytes = new Uint8Array(len);
519
+ for (let i = 0; i < len; i++) bytes[i] = binaryStr.charCodeAt(i) & 0xff;
520
+ const blob = new Blob([bytes], { type: 'image/png' });
521
+ const img = await createImageBitmap(blob);
522
+ const c = document.createElement('canvas');
523
+ c.width = img.width; c.height = img.height;
524
+ const ctx = c.getContext('2d');
525
+ ctx.drawImage(img, 0, 0);
526
+ const id = ctx.getImageData(0, 0, img.width, img.height);
527
+ try { img.close && img.close(); } catch { }
528
+ return id;
529
+ } catch { }
530
+ return null;
531
+ }
532
+ } catch (e) {
533
+ console.warn('[IMAGE] Failed to decode input as image data', e);
534
+ }
535
+ return null;
536
+ }
537
+
538
+ function gridToWorld2D(gridLoop, scale = 1) {
539
+ // gridLoop: list of [xNode, yNode], y grows down; map to world with y up, z=0
540
+ const out = [];
541
+ for (let i = 0; i < gridLoop.length; i++) {
542
+ const gx = gridLoop[i][0];
543
+ const gy = gridLoop[i][1];
544
+ out.push([gx * scale, -gy * scale]);
545
+ }
546
+ return out;
547
+ }
548
+
549
+ function simplifyLoop(loop, { simplifyCollinear = true, rdpTolerance = 0 } = {}) {
550
+ let pts = loop.slice();
551
+ // Ensure closed for area/orientation helpers
552
+ if (pts.length && (pts[0][0] !== pts[pts.length - 1][0] || pts[0][1] !== pts[pts.length - 1][1])) {
553
+ pts.push([pts[0][0], pts[0][1]]);
554
+ }
555
+ if (simplifyCollinear) pts = removeCollinear2D(pts);
556
+ if (rdpTolerance && rdpTolerance > 0) pts = rdp(pts, rdpTolerance);
557
+ // Guarantee closure
558
+ if (pts.length && (pts[0][0] !== pts[pts.length - 1][0] || pts[0][1] !== pts[pts.length - 1][1])) pts.push([pts[0][0], pts[0][1]]);
559
+ return pts;
560
+ }
561
+
562
+ function removeCollinear2D(loop) {
563
+ if (loop.length < 4) return loop.slice();
564
+ const out = [];
565
+ for (let i = 0; i < loop.length - 1; i++) { // leave duplicate last for closure
566
+ const a = loop[(i + loop.length - 2) % (loop.length - 1)];
567
+ const b = loop[(i + loop.length - 1) % (loop.length - 1)];
568
+ const c = loop[i];
569
+ const abx = b[0] - a[0], aby = b[1] - a[1];
570
+ const bcx = c[0] - b[0], bcy = c[1] - b[1];
571
+ const cross = abx * bcy - aby * bcx;
572
+ if (Math.abs(cross) > 1e-12) out.push(b);
573
+ }
574
+ if (out.length >= 1) {
575
+ out.push([out[0][0], out[0][1]]);
576
+ return out;
577
+ }
578
+ // If fully collinear or degenerate, keep original loop to avoid empty result
579
+ return loop.slice();
580
+ }
581
+
582
+ function signedArea(loop) {
583
+ let area = 0;
584
+ for (let i = 0; i < loop.length - 1; i++) {
585
+ const a = loop[i], b = loop[i + 1];
586
+ area += a[0] * b[1] - a[1] * b[0];
587
+ }
588
+ return 0.5 * area;
589
+ }
590
+
591
+ function bounds2D(pts) {
592
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
593
+ for (const p of pts) {
594
+ if (p[0] < minX) minX = p[0];
595
+ if (p[1] < minY) minY = p[1];
596
+ if (p[0] > maxX) maxX = p[0];
597
+ if (p[1] > maxY) maxY = p[1];
598
+ }
599
+ return { minX, minY, maxX, maxY };
600
+ }
601
+
602
+ // Point-in-polygon using winding number. Accepts closed or open polygon arrays.
603
+ function pointInPoly(pt, poly) {
604
+ const n = Array.isArray(poly) ? poly.length : 0;
605
+ if (n < 3) return false;
606
+ let ring = poly;
607
+ const first = ring[0], last = ring[ring.length - 1];
608
+ if (first && last && first[0] === last[0] && first[1] === last[1]) ring = ring.slice(0, ring.length - 1);
609
+ const x = pt[0], y = pt[1];
610
+ let wn = 0;
611
+ const isLeft = (ax, ay, bx, by, cx, cy) => (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
612
+ for (let i = 0; i < ring.length; i++) {
613
+ const a = ring[i];
614
+ const b = ring[(i + 1) % ring.length];
615
+ if ((a[1] <= y) && (b[1] > y) && isLeft(a[0], a[1], b[0], b[1], x, y) > 0) wn++;
616
+ else if ((a[1] > y) && (b[1] <= y) && isLeft(a[0], a[1], b[0], b[1], x, y) < 0) wn--;
617
+ }
618
+ return wn !== 0;
619
+ }
620
+
621
+ function groupLoopsOuterHoles(loops) {
622
+ // Normalize: ensure each loop is closed and oriented CCW for holes, CW for outers
623
+ const closed = loops.map((l) => {
624
+ const c = l.slice();
625
+ if (c.length && (c[0][0] !== c[c.length - 1][0] || c[0][1] !== c[c.length - 1][1])) c.push([c[0][0], c[0][1]]);
626
+ return c;
627
+ });
628
+ const norm = closed.map((l) => {
629
+ const A = signedArea(l);
630
+ if (A < 0) return l.slice();
631
+ const r = l.slice(); r.reverse(); return r;
632
+ });
633
+
634
+ const reps = norm.map((l) => l[0]);
635
+ const depth = new Array(norm.length).fill(0);
636
+ for (let i = 0; i < norm.length; i++) {
637
+ for (let j = 0; j < norm.length; j++) {
638
+ if (i === j) continue;
639
+ if (pointInPoly(reps[i], norm[j])) depth[i]++;
640
+ }
641
+ }
642
+
643
+ // Even depth -> outer; holes are immediate odd-depth children
644
+ const groups = [];
645
+ for (let i = 0; i < norm.length; i++) if ((depth[i] % 2) === 0) groups.push({ outer: i, holes: [] });
646
+ for (let h = 0; h < norm.length; h++) if ((depth[h] % 2) === 1) {
647
+ let best = -1, bestDepth = Infinity;
648
+ for (let g = 0; g < groups.length; g++) {
649
+ const oi = groups[g].outer;
650
+ if (pointInPoly(reps[h], norm[oi])) {
651
+ if (depth[oi] < bestDepth) { best = g; bestDepth = depth[oi]; }
652
+ }
653
+ }
654
+ if (best >= 0) groups[best].holes.push(h);
655
+ }
656
+
657
+ return groups.map((g) => ({
658
+ outer: norm[g.outer].slice(),
659
+ outerIndex: g.outer,
660
+ holes: g.holes.map((h) => norm[h].slice()),
661
+ holeIndices: g.holes.slice(),
662
+ }));
663
+ }
664
+
665
+ function normalizeBreakPoints(raw, { scale = 1, offsetX = 0, offsetY = 0 } = {}) {
666
+ if (!Array.isArray(raw)) return [];
667
+ const out = [];
668
+ for (const bp of raw) {
669
+ let x;
670
+ let y;
671
+ if (Array.isArray(bp)) {
672
+ x = Number(bp[0]);
673
+ y = Number(bp[1]);
674
+ } else if (bp && typeof bp === 'object') {
675
+ x = Number(bp.x ?? bp[0]);
676
+ y = Number(bp.y ?? bp[1]);
677
+ }
678
+ if (!Number.isFinite(x) || !Number.isFinite(y)) continue;
679
+ out.push([x * scale - offsetX, -y * scale - offsetY]);
680
+ }
681
+ return out;
682
+ }
683
+
684
+ function getPlacementBasis(ref, partHistory) {
685
+ // Returns { origin:[x,y,z], x:[x,y,z], y:[x,y,z], z:[x,y,z] }
686
+ const x = new THREE.Vector3(1, 0, 0);
687
+ const y = new THREE.Vector3(0, 1, 0);
688
+ const z = new THREE.Vector3(0, 0, 1);
689
+ const origin = new THREE.Vector3(0, 0, 0);
690
+
691
+ let refObj = null;
692
+ try {
693
+ if (Array.isArray(ref)) refObj = ref[0] || null;
694
+ else if (ref && typeof ref === 'object') refObj = ref;
695
+ else if (ref) refObj = partHistory?.scene?.getObjectByName(ref);
696
+ } catch { }
697
+
698
+ if (refObj) {
699
+ try { refObj.updateWorldMatrix(true, true); } catch { }
700
+ // Origin: geometric center if available else world pos
701
+ try {
702
+ const g = refObj.geometry;
703
+ if (g) {
704
+ const bs = g.boundingSphere || (g.computeBoundingSphere(), g.boundingSphere);
705
+ if (bs) origin.copy(refObj.localToWorld(bs.center.clone()));
706
+ else origin.copy(refObj.getWorldPosition(new THREE.Vector3()));
707
+ } else origin.copy(refObj.getWorldPosition(new THREE.Vector3()));
708
+ } catch { origin.copy(refObj.getWorldPosition(new THREE.Vector3())); }
709
+
710
+ // Orientation: FACE uses average normal; PLANE/others use object z-axis
711
+ let n = null;
712
+ if (refObj.type === 'FACE' && typeof refObj.getAverageNormal === 'function') {
713
+ try { n = refObj.getAverageNormal().normalize(); } catch { n = null; }
714
+ }
715
+ if (!n) {
716
+ try { n = new THREE.Vector3(0, 0, 1).applyQuaternion(refObj.getWorldQuaternion(new THREE.Quaternion())).normalize(); } catch { n = new THREE.Vector3(0, 0, 1); }
717
+ }
718
+ const worldUp = new THREE.Vector3(0, 1, 0);
719
+ const tmp = new THREE.Vector3();
720
+ const zx = Math.abs(n.dot(worldUp)) > 0.9 ? new THREE.Vector3(1, 0, 0) : worldUp;
721
+ x.copy(tmp.crossVectors(zx, n).normalize());
722
+ y.copy(tmp.crossVectors(n, x).normalize());
723
+ z.copy(n);
724
+ }
725
+
726
+ return { origin: [origin.x, origin.y, origin.z], x: [x.x, x.y, x.z], y: [y.x, y.y, y.z], z: [z.x, z.y, z.z] };
727
+ }