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,857 @@
1
+ import { Solid } from './BetterSolid.js';
2
+ import { Manifold, THREE } from './SolidShared.js';
3
+
4
+ const DEFAULT_SEGMENTS = 32;
5
+ const EPS = 1e-9;
6
+ const EPS_SQ = EPS * EPS;
7
+
8
+ function toVector3Array(points) {
9
+ const out = [];
10
+ if (!Array.isArray(points)) return out;
11
+ for (const p of points) {
12
+ if (!Array.isArray(p) || p.length < 3) continue;
13
+ const x = Number(p[0]);
14
+ const y = Number(p[1]);
15
+ const z = Number(p[2]);
16
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) continue;
17
+ out.push(new THREE.Vector3(x, y, z));
18
+ }
19
+ return out;
20
+ }
21
+
22
+ function dedupeVectors(vectors, eps = 1e-7) {
23
+ if (!Array.isArray(vectors) || vectors.length === 0) return [];
24
+ const epsSq = eps * eps;
25
+ const out = [vectors[0].clone()];
26
+ for (let i = 1; i < vectors.length; i++) {
27
+ const v = vectors[i];
28
+ if (!v) continue;
29
+ if (v.distanceToSquared(out[out.length - 1]) > epsSq) out.push(v.clone());
30
+ }
31
+ return out;
32
+ }
33
+
34
+ function calculateTubeIntersectionTrimming(points, tubeRadius) {
35
+ if (!Array.isArray(points) || points.length < 2) {
36
+ return Array.isArray(points) ? points.map(p => p.clone()) : [];
37
+ }
38
+
39
+ if (points.length === 2) {
40
+ return points.map(p => p.clone());
41
+ }
42
+
43
+ const out = [];
44
+ out.push(points[0].clone());
45
+
46
+ for (let i = 1; i < points.length - 1; i++) {
47
+ const prev = points[i - 1];
48
+ const curr = points[i];
49
+ const next = points[i + 1];
50
+
51
+ if (!prev || !curr || !next) {
52
+ out.push(curr.clone());
53
+ continue;
54
+ }
55
+
56
+ const vPrev = curr.clone().sub(prev);
57
+ const vNext = next.clone().sub(curr);
58
+
59
+ if (vPrev.lengthSq() < EPS_SQ || vNext.lengthSq() < EPS_SQ) {
60
+ out.push(curr.clone());
61
+ continue;
62
+ }
63
+
64
+ vPrev.normalize();
65
+ vNext.normalize();
66
+
67
+ const dot = THREE.MathUtils.clamp(vPrev.dot(vNext), -1, 1);
68
+ const angle = Math.acos(Math.abs(dot));
69
+
70
+ if (angle > Math.PI / 3) {
71
+ const halfAngle = angle * 0.5;
72
+ const intersectionDist = tubeRadius / Math.tan(halfAngle);
73
+ const distPrev = prev.distanceTo(curr);
74
+ const distNext = curr.distanceTo(next);
75
+ const trimDistPrev = Math.min(intersectionDist * 0.8, distPrev * 0.6);
76
+ const trimDistNext = Math.min(intersectionDist * 0.8, distNext * 0.6);
77
+
78
+ if (trimDistPrev > tubeRadius * 0.1 && trimDistNext > tubeRadius * 0.1) {
79
+ const trimmedPrev = curr.clone().addScaledVector(vPrev, -trimDistPrev);
80
+ const trimmedNext = curr.clone().addScaledVector(vNext, trimDistNext);
81
+
82
+ if (out[out.length - 1].distanceTo(trimmedPrev) > 1e-6) {
83
+ out.push(trimmedPrev);
84
+ }
85
+
86
+ out.push(trimmedNext);
87
+ } else {
88
+ out.push(curr.clone());
89
+ }
90
+ } else {
91
+ out.push(curr.clone());
92
+ }
93
+ }
94
+
95
+ out.push(points[points.length - 1].clone());
96
+ return dedupeVectors(out, 1e-6);
97
+ }
98
+
99
+ function smoothPath(points, tubeRadius) {
100
+ try {
101
+ const trimmedPoints = calculateTubeIntersectionTrimming(points, tubeRadius);
102
+ if (!Array.isArray(trimmedPoints) || trimmedPoints.length < 2) {
103
+ return Array.isArray(points) ? points.map(p => p.clone()) : [];
104
+ }
105
+ return dedupeVectors(trimmedPoints, 1e-9);
106
+ } catch (error) {
107
+ console.error('Error in smoothPath:', error);
108
+ return points.map(p => p.clone());
109
+ }
110
+ }
111
+
112
+ const tmpVecA = new THREE.Vector3();
113
+ const tmpVecB = new THREE.Vector3();
114
+ const tmpVecC = new THREE.Vector3();
115
+ const tmpNormal = new THREE.Vector3();
116
+
117
+ function computeFrames(points, closed = false) {
118
+ const tangents = [];
119
+ const normals = [];
120
+ const binormals = [];
121
+ if (!Array.isArray(points) || points.length < 2) return { tangents, normals, binormals };
122
+
123
+ const normalSeed = new THREE.Vector3();
124
+
125
+ for (let i = 0; i < points.length; i++) {
126
+ const tangent = new THREE.Vector3();
127
+
128
+ if (closed) {
129
+ const prevIdx = (i - 1 + points.length) % points.length;
130
+ const nextIdx = (i + 1) % points.length;
131
+ const forward = new THREE.Vector3().subVectors(points[nextIdx], points[i]);
132
+ const backward = new THREE.Vector3().subVectors(points[i], points[prevIdx]);
133
+
134
+ const forwardLen = forward.length();
135
+ const backwardLen = backward.length();
136
+
137
+ if (forwardLen > EPS && backwardLen > EPS) {
138
+ forward.normalize();
139
+ backward.normalize();
140
+ tangent.addVectors(forward, backward).normalize();
141
+ } else if (forwardLen > EPS) {
142
+ tangent.copy(forward).normalize();
143
+ } else if (backwardLen > EPS) {
144
+ tangent.copy(backward).normalize();
145
+ } else {
146
+ tangent.set(0, 0, 1);
147
+ }
148
+ } else {
149
+ if (i === 0) {
150
+ tangent.subVectors(points[1], points[0]);
151
+ } else if (i === points.length - 1) {
152
+ tangent.subVectors(points[i], points[i - 1]);
153
+ } else {
154
+ const forward = new THREE.Vector3().subVectors(points[i + 1], points[i]);
155
+ const backward = new THREE.Vector3().subVectors(points[i], points[i - 1]);
156
+ const forwardLen = forward.length();
157
+ const backwardLen = backward.length();
158
+
159
+ if (forwardLen > EPS && backwardLen > EPS) {
160
+ forward.normalize();
161
+ backward.normalize();
162
+ tangent.addVectors(forward, backward).normalize();
163
+ } else if (forwardLen > EPS) {
164
+ tangent.copy(forward).normalize();
165
+ } else if (backwardLen > EPS) {
166
+ tangent.copy(backward).normalize();
167
+ } else {
168
+ tangent.copy(i > 0 ? tangents[i - 1] : new THREE.Vector3(0, 0, 1));
169
+ }
170
+ }
171
+ }
172
+
173
+ tangents.push(tangent.normalize());
174
+ }
175
+
176
+ normalSeed.set(0, 0, 1);
177
+ if (Math.abs(tangents[0].dot(normalSeed)) > 0.99) {
178
+ normalSeed.set(1, 0, 0);
179
+ }
180
+
181
+ normals.push(new THREE.Vector3().crossVectors(tangents[0], normalSeed).cross(tangents[0]).normalize());
182
+ binormals.push(new THREE.Vector3().crossVectors(tangents[0], normals[0]).normalize());
183
+
184
+ for (let i = 1; i < points.length; i++) {
185
+ normals.push(normals[i - 1].clone());
186
+ binormals.push(binormals[i - 1].clone());
187
+
188
+ const deltaT = tangents[i - 1].dot(tangents[i]);
189
+ if (deltaT <= 1 - EPS) {
190
+ const axis = new THREE.Vector3().crossVectors(tangents[i - 1], tangents[i]).normalize();
191
+ const angle = Math.acos(THREE.MathUtils.clamp(deltaT, -1, 1));
192
+ const mat = new THREE.Matrix4().makeRotationAxis(axis, angle);
193
+ normals[i].applyMatrix4(mat).normalize();
194
+ binormals[i].crossVectors(tangents[i], normals[i]).normalize();
195
+ }
196
+ }
197
+
198
+ if (closed && points.length > 2) {
199
+ const avgNormal = normals.reduce((acc, n) => acc.add(n), new THREE.Vector3()).normalize();
200
+ for (let i = 0; i < normals.length; i++) {
201
+ const projection = normals[i].clone().sub(tangents[i].clone().multiplyScalar(tangents[i].dot(normals[i])));
202
+ if (projection.lengthSq() > EPS_SQ) {
203
+ normals[i].copy(projection.normalize());
204
+ binormals[i].crossVectors(tangents[i], projection).normalize();
205
+ } else {
206
+ normals[i].copy(avgNormal);
207
+ binormals[i].crossVectors(tangents[i], avgNormal).normalize();
208
+ }
209
+ }
210
+ }
211
+
212
+ return { tangents, normals, binormals };
213
+ }
214
+
215
+ function buildRings(points, normals, binormals, radius, innerRadius, segments) {
216
+ const outer = [];
217
+ const inner = innerRadius > 0 ? [] : null;
218
+
219
+ for (let i = 0; i < points.length; i++) {
220
+ const normal = normals[i];
221
+ const binormal = binormals[i];
222
+ const ringOuter = [];
223
+ const ringInner = inner ? [] : null;
224
+
225
+ for (let j = 0; j < segments; j++) {
226
+ const theta = (j / segments) * Math.PI * 2;
227
+ const cos = Math.cos(theta);
228
+ const sin = Math.sin(theta);
229
+ const offset = normal.clone().multiplyScalar(cos).add(binormal.clone().multiplyScalar(sin));
230
+
231
+ const outerPoint = points[i].clone().addScaledVector(offset, radius);
232
+ ringOuter.push([outerPoint.x, outerPoint.y, outerPoint.z]);
233
+
234
+ if (inner && innerRadius > 0) {
235
+ const innerPoint = points[i].clone().addScaledVector(offset, innerRadius);
236
+ ringInner.push([innerPoint.x, innerPoint.y, innerPoint.z]);
237
+ }
238
+ }
239
+
240
+ outer.push(ringOuter);
241
+ if (inner && innerRadius > 0) inner.push(ringInner);
242
+ }
243
+
244
+ return { outer, inner };
245
+ }
246
+
247
+ function addTriangleOriented(solid, name, a, b, c, outwardDir) {
248
+ if (!outwardDir || outwardDir.lengthSq() < 1e-10) {
249
+ solid.addTriangle(name, a, b, c);
250
+ return;
251
+ }
252
+ tmpVecA.set(a[0], a[1], a[2]);
253
+ tmpVecB.set(b[0], b[1], b[2]).sub(tmpVecA);
254
+ tmpVecC.set(c[0], c[1], c[2]).sub(tmpVecA);
255
+ tmpNormal.copy(tmpVecB).cross(tmpVecC);
256
+ if (tmpNormal.dot(outwardDir) < 0) {
257
+ solid.addTriangle(name, a, c, b);
258
+ } else {
259
+ solid.addTriangle(name, a, b, c);
260
+ }
261
+ }
262
+
263
+ function addQuadOriented(solid, name, a, b, c, d, outwardDir) {
264
+ addTriangleOriented(solid, name, a, b, c, outwardDir);
265
+ addTriangleOriented(solid, name, a, c, d, outwardDir);
266
+ }
267
+
268
+ function addDiskCap(solid, name, center, ring, outwardDir) {
269
+ for (let j = 0; j < ring.length; j++) {
270
+ const j1 = (j + 1) % ring.length;
271
+ addTriangleOriented(solid, name, center, ring[j], ring[j1], outwardDir);
272
+ }
273
+ }
274
+
275
+ function addRingCap(solid, name, outerRing, innerRing, outwardDir) {
276
+ const count = outerRing.length;
277
+ for (let j = 0; j < count; j++) {
278
+ const j1 = (j + 1) % count;
279
+ addQuadOriented(solid, name, outerRing[j], outerRing[j1], innerRing[j1], innerRing[j], outwardDir);
280
+ }
281
+ }
282
+
283
+ function copySolidState(target, source, { auxEdges } = {}) {
284
+ target._numProp = source._numProp;
285
+ target._vertProperties = source._vertProperties;
286
+ target._triVerts = source._triVerts;
287
+ target._triIDs = source._triIDs;
288
+ target._vertKeyToIndex = new Map(source._vertKeyToIndex);
289
+
290
+ target._idToFaceName = new Map(source._idToFaceName);
291
+ target._faceNameToID = new Map(source._faceNameToID);
292
+ target._faceMetadata = new Map(source._faceMetadata);
293
+ target._edgeMetadata = new Map(source._edgeMetadata);
294
+
295
+ target._auxEdges = auxEdges !== undefined ? auxEdges : Array.isArray(source._auxEdges) ? source._auxEdges : [];
296
+ target._manifold = source._manifold;
297
+ target._dirty = false;
298
+ target._faceIndex = null;
299
+ }
300
+
301
+ function normalizePath(points, requestedClosed, tol) {
302
+ const clean = dedupeVectors(points, tol);
303
+ if (clean.length < 2) return { points: clean, closed: false };
304
+
305
+ const start = clean[0];
306
+ const end = clean[clean.length - 1];
307
+ const closureTol = Math.max(tol * 4, EPS);
308
+ const isClosed = !!requestedClosed || start.distanceToSquared(end) <= closureTol * closureTol;
309
+ if (isClosed && start.distanceToSquared(end) <= closureTol * closureTol) {
310
+ clean.pop(); // drop the duplicate end point
311
+ }
312
+ return { points: clean, closed: isClosed };
313
+ }
314
+
315
+ function trimPlaneFromPoints(anchor, neighbor, invert = false) {
316
+ if (!anchor || !neighbor) return null;
317
+ const normalVec = new THREE.Vector3().subVectors(neighbor, anchor);
318
+ if (normalVec.lengthSq() <= EPS) return null;
319
+ if (invert) normalVec.negate();
320
+ normalVec.normalize();
321
+ return {
322
+ anchor,
323
+ normalVec,
324
+ normalArray: [normalVec.x, normalVec.y, normalVec.z],
325
+ offset: normalVec.dot(anchor),
326
+ };
327
+ }
328
+
329
+ function applyTrimPlaneSequentially(spheres, points, radius, plane, iterateForward = true) {
330
+ if (!plane || !Array.isArray(spheres) || !Array.isArray(points) || !(radius > 0)) return;
331
+
332
+ const start = iterateForward ? 0 : spheres.length - 1;
333
+ const end = iterateForward ? spheres.length : -1;
334
+ const step = iterateForward ? 1 : -1;
335
+ for (let idx = start; idx !== end; idx += step) {
336
+ const center = points[idx];
337
+ if (!center) continue;
338
+ if (center.distanceTo(plane.anchor) > radius) break;
339
+ const sphere = spheres[idx];
340
+ if (!sphere) continue;
341
+ const trimmed = sphere.trimByPlane(plane.normalArray, plane.offset);
342
+ if (!trimmed) {
343
+ spheres[idx] = trimmed;
344
+ continue;
345
+ }
346
+ if (trimmed !== sphere) {
347
+ try { if (typeof sphere.delete === 'function') sphere.delete(); } catch { }
348
+ spheres[idx] = trimmed;
349
+ } else {
350
+ spheres[idx] = trimmed;
351
+ }
352
+ }
353
+ }
354
+
355
+ function buildHullChain(points, radius, resolution, closed, { keepSpheres = false, trimPlanes = null } = {}) {
356
+ if (!Array.isArray(points) || points.length < 2) return { hull: null, spheres: [] };
357
+
358
+ const baseSphere = Manifold.sphere(radius, resolution);
359
+ const spheres = points.map(pt => baseSphere.translate([pt.x, pt.y, pt.z]));
360
+ try { if (typeof baseSphere.delete === 'function') baseSphere.delete(); } catch { }
361
+
362
+ if (!closed && trimPlanes) {
363
+ if (trimPlanes.start) applyTrimPlaneSequentially(spheres, points, radius, trimPlanes.start, true);
364
+ if (trimPlanes.end) applyTrimPlaneSequentially(spheres, points, radius, trimPlanes.end, false);
365
+ }
366
+
367
+ const hulls = [];
368
+ const segmentCount = closed ? points.length : points.length - 1;
369
+ for (let i = 0; i < segmentCount; i++) {
370
+ const a = points[i];
371
+ const b = points[(i + 1) % points.length];
372
+ const sphereA = spheres[i];
373
+ const sphereB = spheres[(i + 1) % spheres.length];
374
+ if (!a || !b || !sphereA || !sphereB) continue;
375
+ if (a.distanceToSquared(b) < EPS * EPS) continue;
376
+ hulls.push(Manifold.hull([sphereA, sphereB]));
377
+ }
378
+
379
+ if (!keepSpheres) {
380
+ for (const s of spheres) { try { if (s && typeof s.delete === 'function') s.delete(); } catch { } }
381
+ }
382
+
383
+ if (!hulls.length) return { hull: null, spheres: keepSpheres ? spheres : [] };
384
+ if (hulls.length === 1) return { hull: hulls[0], spheres: keepSpheres ? spheres : [] };
385
+
386
+ let combined = null;
387
+ try {
388
+ combined = Manifold.union(hulls);
389
+ return { hull: combined, spheres: keepSpheres ? spheres : [] };
390
+ } finally {
391
+ for (const h of hulls) {
392
+ if (h && h !== combined) {
393
+ try { if (typeof h.delete === 'function') h.delete(); } catch { }
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ function buildHullTube(points, radius, resolution, closed, keepSpheres = false, trimPlanes = null) {
400
+ const { hull, spheres } = buildHullChain(points, radius, resolution, closed, { keepSpheres, trimPlanes });
401
+ if (!hull) throw new Error('Unable to build tube hulls from the supplied path.');
402
+ return { manifold: hull, spheres };
403
+ }
404
+
405
+ function rebuildSolidFromManifold(target, manifold, faceMap) {
406
+ const rebuilt = Solid._fromManifold(manifold, faceMap);
407
+
408
+ // Copy authoring buffers and metadata without clobbering THREE.Object3D fields
409
+ target._numProp = rebuilt._numProp;
410
+ target._vertProperties = rebuilt._vertProperties;
411
+ target._triVerts = rebuilt._triVerts;
412
+ target._triIDs = rebuilt._triIDs;
413
+ target._vertKeyToIndex = new Map(rebuilt._vertKeyToIndex);
414
+
415
+ target._idToFaceName = new Map(rebuilt._idToFaceName);
416
+ target._faceNameToID = new Map(rebuilt._faceNameToID);
417
+ target._faceMetadata = new Map(rebuilt._faceMetadata);
418
+ target._edgeMetadata = new Map(rebuilt._edgeMetadata);
419
+
420
+ target._manifold = rebuilt._manifold;
421
+ target._dirty = false;
422
+ target._faceIndex = null;
423
+ return target;
424
+ }
425
+
426
+ function distanceToSegmentSquared(p, a, b) {
427
+ const ab = b.clone().sub(a);
428
+ const ap = p.clone().sub(a);
429
+ const t = THREE.MathUtils.clamp(ap.dot(ab) / ab.lengthSq(), 0, 1);
430
+ const closest = a.clone().addScaledVector(ab, t);
431
+ return p.distanceToSquared(closest);
432
+ }
433
+
434
+ function minDistanceToPolyline(points, polyline) {
435
+ if (!Array.isArray(polyline) || polyline.length < 2) return Infinity;
436
+ let minSq = Infinity;
437
+ for (let i = 0; i < polyline.length - 1; i++) {
438
+ const a = polyline[i];
439
+ const b = polyline[i + 1];
440
+ minSq = Math.min(minSq, distanceToSegmentSquared(points, a, b));
441
+ }
442
+ return Math.sqrt(minSq);
443
+ }
444
+
445
+ function relabelFaces(solid, pathPoints, startNormal, endNormal, outerRadius, innerRadius, closed, faceTag) {
446
+ if (!solid || !solid._vertProperties || !solid._triVerts) return solid;
447
+ const triCount = (solid._triVerts.length / 3) | 0;
448
+ if (!triCount) return solid;
449
+
450
+ // Reset face ID maps so we allocate fresh, globally unique IDs. Manifold-built
451
+ // hulls often reuse low IDs (e.g., 0) which collide across multiple tube
452
+ // solids during booleans and cause distinct faces to merge under one label.
453
+ solid._faceNameToID = new Map();
454
+ solid._idToFaceName = new Map();
455
+
456
+ const nStart = startNormal ? startNormal.clone().normalize() : null;
457
+ const nEnd = endNormal ? endNormal.clone().normalize() : null;
458
+ const startOffset = nStart ? nStart.dot(pathPoints[0]) : 0;
459
+ const endOffset = nEnd ? nEnd.dot(pathPoints[pathPoints.length - 1]) : 0;
460
+ const capTol = Math.max(outerRadius * 1e-2, 1e-5);
461
+
462
+ const idOuter = solid._getOrCreateID(`${faceTag}_Outer`);
463
+ const idInner = innerRadius > 0 ? solid._getOrCreateID(`${faceTag}_Inner`) : idOuter;
464
+ const idCapStart = (!closed && nStart) ? solid._getOrCreateID(`${faceTag}_CapStart`) : idOuter;
465
+ const idCapEnd = (!closed && nEnd) ? solid._getOrCreateID(`${faceTag}_CapEnd`) : idOuter;
466
+
467
+ const newIDs = new Array(triCount);
468
+ const vp = solid._vertProperties;
469
+ const tv = solid._triVerts;
470
+ const polyline = pathPoints;
471
+ const innerOuterThreshold = innerRadius > 0 ? (innerRadius + outerRadius) * 0.5 : outerRadius * 0.5;
472
+
473
+ for (let t = 0; t < triCount; t++) {
474
+ const i0 = tv[t * 3 + 0] * 3;
475
+ const i1 = tv[t * 3 + 1] * 3;
476
+ const i2 = tv[t * 3 + 2] * 3;
477
+ const cx = (vp[i0 + 0] + vp[i1 + 0] + vp[i2 + 0]) / 3;
478
+ const cy = (vp[i0 + 1] + vp[i1 + 1] + vp[i2 + 1]) / 3;
479
+ const cz = (vp[i0 + 2] + vp[i1 + 2] + vp[i2 + 2]) / 3;
480
+ const centroid = new THREE.Vector3(cx, cy, cz);
481
+
482
+ let assigned = idOuter;
483
+ const distToStart = centroid.distanceTo(pathPoints[0]);
484
+ const distToEnd = centroid.distanceTo(pathPoints[pathPoints.length - 1]);
485
+ if (!closed && nStart && Math.abs(nStart.dot(centroid) - startOffset) <= capTol && distToStart <= outerRadius + capTol) {
486
+ assigned = idCapStart;
487
+ } else if (!closed && nEnd && Math.abs(nEnd.dot(centroid) - endOffset) <= capTol && distToEnd <= outerRadius + capTol) {
488
+ assigned = idCapEnd;
489
+ } else if (innerRadius > 0) {
490
+ const dist = minDistanceToPolyline(centroid, polyline);
491
+ assigned = dist <= innerOuterThreshold ? idInner : idOuter;
492
+ }
493
+ newIDs[t] = assigned;
494
+ }
495
+
496
+ solid._triIDs = newIDs;
497
+ solid._idToFaceName = new Map([
498
+ [idOuter, `${faceTag}_Outer`],
499
+ ...(innerRadius > 0 ? [[idInner, `${faceTag}_Inner`]] : []),
500
+ ...(!closed && nStart ? [[idCapStart, `${faceTag}_CapStart`]] : []),
501
+ ...(!closed && nEnd ? [[idCapEnd, `${faceTag}_CapEnd`]] : []),
502
+ ]);
503
+ solid._faceNameToID = new Map(
504
+ [...solid._idToFaceName.entries()].map(([id, name]) => [name, id]),
505
+ );
506
+
507
+ // Rebuild manifold with the new face IDs
508
+ try { if (typeof solid.free === 'function') solid.free(); } catch { }
509
+ solid._dirty = true;
510
+ solid._faceIndex = null;
511
+ try { solid._manifoldize(); } catch { /* leave dirty if rebuild fails */ }
512
+ return solid;
513
+ }
514
+
515
+ function firstTangent(points) {
516
+ if (!Array.isArray(points) || points.length < 2) return null;
517
+ for (let i = 1; i < points.length; i++) {
518
+ const dir = new THREE.Vector3().subVectors(points[i], points[i - 1]);
519
+ if (dir.lengthSq() > EPS) return dir.normalize();
520
+ }
521
+ return null;
522
+ }
523
+
524
+ function lastTangent(points) {
525
+ if (!Array.isArray(points) || points.length < 2) return null;
526
+ for (let i = points.length - 1; i >= 1; i--) {
527
+ const dir = new THREE.Vector3().subVectors(points[i], points[i - 1]);
528
+ if (dir.lengthSq() > EPS) return dir.normalize();
529
+ }
530
+ return null;
531
+ }
532
+
533
+ function singleFaceSolidFromManifold(manifold, faceName) {
534
+ const name = faceName || 'Sphere';
535
+ const solid = Solid._fromManifold(manifold, new Map([[0, name]]));
536
+ const id = solid._getOrCreateID(name);
537
+ const triCount = (solid._triVerts.length / 3) | 0;
538
+ solid._triIDs = new Array(triCount).fill(id);
539
+ solid._idToFaceName = new Map([[id, name]]);
540
+ solid._faceNameToID = new Map([[name, id]]);
541
+ solid._dirty = true;
542
+ try { solid._manifoldize(); } catch { }
543
+ return solid;
544
+ }
545
+
546
+ export class Tube extends Solid {
547
+ /**
548
+ * Build a tube solid along a polyline using convex hulls between spheres.
549
+ * @param {object} [opts]
550
+ * @param {Array<[number,number,number]>} [opts.points=[]] Path points for the tube centerline
551
+ * @param {number} [opts.radius=1] Outer radius
552
+ * @param {number} [opts.innerRadius=0] Optional inner radius (0 for solid tube)
553
+ * @param {number} [opts.resolution=32] Sphere segment count (controls smoothness)
554
+ * @param {boolean} [opts.closed=false] Whether the path is closed (auto-detected if endpoints match)
555
+ * @param {string} [opts.name='Tube'] Name for the solid
556
+ */
557
+ constructor(opts = {}) {
558
+ super();
559
+ const {
560
+ points = [],
561
+ radius = 1,
562
+ innerRadius = 0,
563
+ resolution = DEFAULT_SEGMENTS,
564
+ closed = false,
565
+ name = 'Tube',
566
+ debugSpheres = false,
567
+ preferFast = true,
568
+ } = opts;
569
+ this.params = { points, radius, innerRadius, resolution, closed, name, debugSpheres, preferFast };
570
+ this.name = name;
571
+
572
+ if (Array.isArray(points) && points.length >= 2) {
573
+ const firstPoint = points[0];
574
+ const lastPoint = points[points.length - 1];
575
+ if (firstPoint[0] === lastPoint[0] && firstPoint[1] === lastPoint[1] && firstPoint[2] === lastPoint[2]) {
576
+ this.params.closed = true;
577
+ }
578
+ }
579
+
580
+ try {
581
+ const hasPath = Array.isArray(points) && points.length >= 2;
582
+ const validRadius = Number(radius) > 0;
583
+ if (hasPath && validRadius) {
584
+ this.generate();
585
+ this.visualize();
586
+ }
587
+ } catch {
588
+ // Fail-quietly to keep boolean reconstruction safe
589
+ }
590
+ }
591
+
592
+ generate(){
593
+ const preferFast = this.params?.preferFast !== false;
594
+ if (preferFast) {
595
+ try {
596
+ this.generateFast();
597
+ const stats = this._selfUnionStats;
598
+ if (stats?.selfIntersectionLikely) {
599
+ if (typeof this.free === 'function') { try { this.free(); } catch { } }
600
+ return this.generateSlow();
601
+ }
602
+ return this;
603
+ } catch (error) {
604
+ console.warn('Tube fast generation failed; falling back to slow.', error);
605
+ if (typeof this.free === 'function') { try { this.free(); } catch { } }
606
+ }
607
+ }
608
+ return this.generateSlow();
609
+ }
610
+
611
+ generateFast() {
612
+ let { points, radius, innerRadius, resolution, closed, name } = this.params;
613
+ let isClosed = !!closed;
614
+ if (!(radius > 0)) throw new Error('Tube radius must be greater than zero.');
615
+ const inner = Number(innerRadius) || 0;
616
+ if (inner < 0) throw new Error('Inside radius cannot be negative.');
617
+ if (inner > 0 && inner >= radius) throw new Error('Inside radius must be smaller than the outer radius.');
618
+ const segs = Math.max(8, Math.floor(Number(resolution) || DEFAULT_SEGMENTS));
619
+
620
+ const vecPoints = dedupeVectors(toVector3Array(points));
621
+ if (vecPoints.length < 2) throw new Error(`Tube requires at least two distinct path points. Got ${vecPoints.length} valid points from ${points.length} input points.`);
622
+
623
+ const scaleEstimate = vecPoints.reduce((m, p) => Math.max(m, Math.abs(p.x), Math.abs(p.y), Math.abs(p.z)), Math.max(1e-6, radius));
624
+ const closureTol = Math.max(1e-7, radius * 1e-5, scaleEstimate * 1e-6);
625
+ const closureTolSq = closureTol * closureTol;
626
+
627
+ if (!isClosed && vecPoints.length >= 2) {
628
+ const rawDistSq = vecPoints[0].distanceToSquared(vecPoints[vecPoints.length - 1]);
629
+ if (rawDistSq <= closureTolSq) isClosed = true;
630
+ }
631
+
632
+ let smoothed;
633
+ try {
634
+ smoothed = smoothPath(vecPoints, radius);
635
+ } catch (error) {
636
+ console.error('Error in smoothPath:', error);
637
+ throw new Error(`Path smoothing failed: ${error.message}`);
638
+ }
639
+
640
+ if (smoothed.length < 2) {
641
+ throw new Error(`Tube path collapsed after smoothing; check input. Original: ${vecPoints.length}, Smoothed: ${smoothed.length}`);
642
+ }
643
+
644
+ if (smoothed.length > 1) {
645
+ const first = smoothed[0];
646
+ const last = smoothed[smoothed.length - 1];
647
+ const closureDistSq = first.distanceToSquared(last);
648
+ if (!isClosed && closureDistSq <= closureTolSq) isClosed = true;
649
+ if (isClosed && closureDistSq <= closureTolSq && smoothed.length > 2) {
650
+ smoothed = smoothed.slice(0, -1);
651
+ }
652
+ }
653
+
654
+ this.params.closed = isClosed;
655
+
656
+ const { tangents, normals, binormals } = computeFrames(smoothed, isClosed);
657
+ if (tangents.length < 2) throw new Error('Unable to compute frames for tube path.');
658
+
659
+ // reset authoring buffers before building
660
+ this._numProp = 3;
661
+ this._vertProperties = [];
662
+ this._triVerts = [];
663
+ this._triIDs = [];
664
+ this._vertKeyToIndex = new Map();
665
+ this._idToFaceName = new Map();
666
+ this._faceNameToID = new Map();
667
+ this._faceMetadata = new Map();
668
+ this._edgeMetadata = new Map();
669
+ this._auxEdges = [];
670
+ this._dirty = true;
671
+
672
+ const { outer, inner: innerRings } = buildRings(smoothed, normals, binormals, radius, inner, segs);
673
+ const faceTag = name || 'Tube';
674
+
675
+ const ringCount = isClosed ? outer.length : outer.length - 1;
676
+ for (let i = 0; i < ringCount; i++) {
677
+ const ringA = outer[i];
678
+ const ringB = outer[(i + 1) % outer.length];
679
+ const nextIdx = (i + 1) % smoothed.length;
680
+ const pathDir = smoothed[nextIdx].clone().sub(smoothed[i]).normalize();
681
+
682
+ for (let j = 0; j < segs; j++) {
683
+ const j1 = (j + 1) % segs;
684
+ addQuadOriented(this, `${faceTag}_Outer`, ringA[j], ringA[j1], ringB[j1], ringB[j], pathDir);
685
+ }
686
+ }
687
+
688
+ if (innerRings) {
689
+ const innerRingCount = isClosed ? innerRings.length : innerRings.length - 1;
690
+ for (let i = 0; i < innerRingCount; i++) {
691
+ const ringA = innerRings[i];
692
+ const ringB = innerRings[(i + 1) % innerRings.length];
693
+ const nextIdx = (i + 1) % smoothed.length;
694
+ const pathDir = smoothed[nextIdx].clone().sub(smoothed[i]).normalize();
695
+ const inwardDir = pathDir.clone().negate();
696
+
697
+ for (let j = 0; j < segs; j++) {
698
+ const j1 = (j + 1) % segs;
699
+ addQuadOriented(this, `${faceTag}_Inner`, ringA[j], ringB[j], ringB[j1], ringA[j1], inwardDir);
700
+ }
701
+ }
702
+ }
703
+
704
+ if (!isClosed) {
705
+ const startCenter = [smoothed[0].x, smoothed[0].y, smoothed[0].z];
706
+ const endCenter = [smoothed[smoothed.length - 1].x, smoothed[smoothed.length - 1].y, smoothed[smoothed.length - 1].z];
707
+ const startDir = tangents[0].clone().negate();
708
+ const endDir = tangents[tangents.length - 1].clone();
709
+
710
+ if (innerRings) {
711
+ addRingCap(this, `${faceTag}_CapStart`, outer[0], innerRings[0], startDir);
712
+ addRingCap(this, `${faceTag}_CapEnd`, outer[outer.length - 1], innerRings[innerRings.length - 1], endDir);
713
+ } else {
714
+ addDiskCap(this, `${faceTag}_CapStart`, startCenter, outer[0], startDir);
715
+ addDiskCap(this, `${faceTag}_CapEnd`, endCenter, outer[outer.length - 1], endDir);
716
+ }
717
+ }
718
+
719
+ try {
720
+ const auxPath = smoothed.map(p => [p.x, p.y, p.z]);
721
+ if (isClosed && auxPath.length >= 2) {
722
+ const first = auxPath[0];
723
+ const last = auxPath[auxPath.length - 1];
724
+ const dx = first[0] - last[0];
725
+ const dy = first[1] - last[1];
726
+ const dz = first[2] - last[2];
727
+ const distSq = dx * dx + dy * dy + dz * dz;
728
+ if (distSq > 0) {
729
+ auxPath.push([first[0], first[1], first[2]]);
730
+ }
731
+ }
732
+ this.addAuxEdge(`${faceTag}_PATH`, auxPath, { polylineWorld: true, materialKey: 'OVERLAY', closedLoop: !!isClosed, centerline: true });
733
+ } catch (_) { /* ignore auxiliary path errors */ }
734
+
735
+ const preTriCount = (this._triVerts?.length || 0) / 3 | 0;
736
+ let postTriCount = preTriCount;
737
+ let unionSucceeded = false;
738
+ const auxEdgesSnapshot = Array.isArray(this._auxEdges)
739
+ ? this._auxEdges.map(e => ({
740
+ name: e?.name,
741
+ closedLoop: !!e?.closedLoop,
742
+ polylineWorld: !!e?.polylineWorld,
743
+ materialKey: e?.materialKey,
744
+ centerline: !!e?.centerline,
745
+ points: Array.isArray(e?.points)
746
+ ? e.points.map(p => (Array.isArray(p) ? [p[0], p[1], p[2]] : p))
747
+ : [],
748
+ }))
749
+ : [];
750
+ let inputManifold = null;
751
+ try { inputManifold = this._manifoldize(); } catch { }
752
+ try {
753
+ const booleaned = this.union(this);
754
+ postTriCount = (booleaned?._triVerts?.length || 0) / 3 | 0;
755
+ copySolidState(this, booleaned, { auxEdges: auxEdgesSnapshot });
756
+ if (inputManifold && inputManifold !== this._manifold) {
757
+ try { if (typeof inputManifold.delete === 'function') inputManifold.delete(); } catch { }
758
+ }
759
+ unionSucceeded = true;
760
+ } catch (error) {
761
+ console.warn('Self-union failed; returning raw tube geometry.', error);
762
+ }
763
+ this._selfUnionStats = {
764
+ preTriangles: preTriCount,
765
+ postTriangles: postTriCount,
766
+ selfIntersectionLikely: postTriCount > preTriCount,
767
+ unionSucceeded,
768
+ };
769
+ this.name = name;
770
+ return this;
771
+ }
772
+
773
+ generateSlow() {
774
+ const { points, radius, innerRadius, resolution, closed, name, debugSpheres } = this.params;
775
+ if (!(radius > 0)) {
776
+ throw new Error('Tube radius must be greater than zero.');
777
+ }
778
+ const inner = Number(innerRadius) || 0;
779
+ if (inner < 0) {
780
+ throw new Error('Inside radius cannot be negative.');
781
+ }
782
+ if (inner > 0 && inner >= radius) {
783
+ throw new Error('Inside radius must be smaller than the outer radius.');
784
+ }
785
+
786
+ const segs = Math.max(8, Math.floor(Number(resolution) || DEFAULT_SEGMENTS));
787
+ const vecPoints = toVector3Array(points);
788
+ const tolerance = Math.max(1e-7, radius * 1e-5);
789
+ const { points: cleanPoints, closed: isClosed } = normalizePath(vecPoints, !!closed, tolerance);
790
+
791
+ if (cleanPoints.length < 2) {
792
+ throw new Error(`Tube requires at least two distinct path points. Got ${cleanPoints.length} valid points from ${points.length} input points.`);
793
+ }
794
+ if (isClosed && cleanPoints.length < 3) {
795
+ throw new Error('Closed tubes require at least three unique points.');
796
+ }
797
+
798
+ if (typeof this.free === 'function') {
799
+ this.free();
800
+ }
801
+
802
+ const faceTag = name || 'Tube';
803
+ const keepSpheres = !!debugSpheres;
804
+ const startNormal = isClosed ? null : firstTangent(cleanPoints);
805
+ const endNormal = isClosed ? null : lastTangent(cleanPoints);
806
+ const endCutNormal = endNormal ? endNormal.clone().negate() : null; // point back into tube
807
+ const trimPlanes = isClosed
808
+ ? null
809
+ : {
810
+ start: trimPlaneFromPoints(cleanPoints[0], cleanPoints[1]),
811
+ end: trimPlaneFromPoints(cleanPoints[cleanPoints.length - 1], cleanPoints[cleanPoints.length - 2]),
812
+ };
813
+
814
+ const { manifold: outerManifold, spheres: outerSpheres } = buildHullTube(cleanPoints, radius, segs, isClosed, keepSpheres, trimPlanes);
815
+ let finalSolid;
816
+
817
+ if (inner > 0) {
818
+ const { manifold: innerManifold, spheres: innerSpheres } = buildHullTube(cleanPoints, inner, segs, isClosed, keepSpheres, trimPlanes);
819
+
820
+ const outerSolid = Solid._fromManifold(outerManifold, new Map([[0, `${faceTag}_Outer`]]));
821
+ const innerSolid = Solid._fromManifold(innerManifold, new Map([[0, `${faceTag}_Inner`]]));
822
+ finalSolid = outerSolid.subtract(innerSolid);
823
+ try { outerSolid.free(); } catch { }
824
+ try { innerSolid.free(); } catch { }
825
+ try { if (innerManifold && typeof innerManifold.delete === 'function') innerManifold.delete(); } catch { }
826
+ if (keepSpheres) {
827
+ this.debugSphereSolids = [
828
+ ...(this.debugSphereSolids || []),
829
+ ...outerSpheres.map((m, idx) => singleFaceSolidFromManifold(m, `${faceTag}_sphere_outer_${idx + 1}`)),
830
+ ...innerSpheres.map((m, idx) => singleFaceSolidFromManifold(m, `${faceTag}_sphere_inner_${idx + 1}`)),
831
+ ];
832
+ }
833
+ } else {
834
+ finalSolid = Solid._fromManifold(outerManifold, new Map([[0, `${faceTag}_Outer`]]));
835
+ if (keepSpheres) {
836
+ this.debugSphereSolids = outerSpheres.map((m, idx) => singleFaceSolidFromManifold(m, `${faceTag}_sphere_${idx + 1}`));
837
+ }
838
+ }
839
+
840
+ let relabeled = relabelFaces(finalSolid, cleanPoints, startNormal, endCutNormal, radius, inner, isClosed, faceTag);
841
+ // Ensure we have a manifold to copy from; if rebuild failed, fall back
842
+ const manifoldForCopy = relabeled?._manifold || finalSolid._manifold;
843
+ const faceMapForCopy = relabeled?._idToFaceName || finalSolid._idToFaceName;
844
+ rebuildSolidFromManifold(this, manifoldForCopy, faceMapForCopy);
845
+ this.name = name;
846
+ this.params.closed = isClosed;
847
+
848
+ try {
849
+ const auxPath = cleanPoints.map(p => [p.x, p.y, p.z]);
850
+ this.addAuxEdge(`${faceTag}_PATH`, auxPath, { polylineWorld: true, materialKey: 'OVERLAY', closedLoop: !!isClosed, centerline: true });
851
+ } catch (_) {
852
+ // ignore auxiliary path errors
853
+ }
854
+ this._selfUnionStats = null;
855
+ return this;
856
+ }
857
+ }