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,1576 @@
1
+ import * as THREE from 'three';
2
+ import { AssemblyConstraintRegistry } from './AssemblyConstraintRegistry.js';
3
+ import { evaluateConstraintNumericValue } from './constraintExpressionUtils.js';
4
+ import { deepClone } from '../utils/deepClone.js';
5
+ import { normalizeTypeString } from '../utils/normalizeTypeString.js';
6
+
7
+ const RESERVED_KEYS = new Set(['type', 'persistentData', '__open']);
8
+
9
+ function shallowArrayEqual(a, b) {
10
+ if (a === b) return true;
11
+ if (!Array.isArray(a) || !Array.isArray(b)) return false;
12
+ if (a.length !== b.length) return false;
13
+ for (let i = 0; i < a.length; i += 1) {
14
+ if (a[i] !== b[i]) return false;
15
+ }
16
+ return true;
17
+ }
18
+
19
+ function extractDefaults(schema) {
20
+ const result = {};
21
+ if (!schema || typeof schema !== 'object') return result;
22
+ for (const key in schema) {
23
+ if (!Object.prototype.hasOwnProperty.call(schema, key)) continue;
24
+ if (RESERVED_KEYS.has(key)) continue;
25
+ const def = schema[key] ? schema[key].default_value : undefined;
26
+ result[key] = deepClone(def);
27
+ }
28
+ return result;
29
+ }
30
+
31
+ function formatUnknownConstraintMessage(type) {
32
+ const label = type != null ? String(type) : 'unknown';
33
+ return `Unknown constraint type: ${label}`;
34
+ }
35
+
36
+ const DUPLICATE_TYPE_MAP = new Map([
37
+ ['touch_align', 'touch_align'],
38
+ ['touch-align', 'touch_align'],
39
+ ['touchalign', 'touch_align'],
40
+ ['touch align', 'touch_align'],
41
+ ['touch align constraint', 'touch_align'],
42
+ ['touchalignconstraint', 'touch_align'],
43
+ ['touch', 'touch_align'],
44
+ ['distance', 'distance'],
45
+ ['distance constraint', 'distance'],
46
+ ['distanceconstraint', 'distance'],
47
+ ['angle', 'angle'],
48
+ ['angle constraint', 'angle'],
49
+ ['angleconstraint', 'angle'],
50
+ ]);
51
+
52
+ const DUPLICATE_TYPE_LABELS = {
53
+ touch_align: 'Touch Align',
54
+ distance: 'Distance',
55
+ angle: 'Angle',
56
+ };
57
+
58
+ const DEFAULT_SOLVER_TOLERANCE = 1e-6;
59
+ const DEFAULT_SOLVER_ITERATIONS = 1;
60
+ const DEFAULT_TRANSLATION_GAIN = 0.5;
61
+ const DEFAULT_ROTATION_GAIN = 0.5;
62
+ const AUTO_RUN_ITERATIONS = Math.max(1, DEFAULT_SOLVER_ITERATIONS);
63
+ const DISABLED_STATUS_MESSAGE = 'Constraint disabled.';
64
+
65
+ function toFiniteNumber(value, fallback) {
66
+ const num = Number(value);
67
+ return Number.isFinite(num) ? num : fallback;
68
+ }
69
+
70
+ function clampIterations(value) {
71
+ const num = Math.floor(toFiniteNumber(value, DEFAULT_SOLVER_ITERATIONS));
72
+ if (!Number.isFinite(num) || num < 1) return DEFAULT_SOLVER_ITERATIONS;
73
+
74
+ return num;
75
+ }
76
+
77
+ function clampGain(value, fallback) {
78
+ const num = toFiniteNumber(value, fallback);
79
+ if (!Number.isFinite(num)) return fallback;
80
+ if (num < 0) return 0;
81
+ if (num > 1) return 1;
82
+ return num;
83
+ }
84
+
85
+ function removeExistingDebugArrows(scene) {
86
+ if (!scene || typeof scene.traverse !== 'function') return;
87
+ const toRemove = [];
88
+ const prefixes = [
89
+ 'parallel-constraint-normal-',
90
+ 'distance-constraint-normal-',
91
+ 'touch-align-normal-',
92
+ ];
93
+ scene.traverse((obj) => {
94
+ if (!obj || typeof obj.name !== 'string') return;
95
+ if (prefixes.some((prefix) => obj.name.startsWith(prefix))) {
96
+ toRemove.push(obj);
97
+ }
98
+ });
99
+ for (const obj of toRemove) {
100
+ try { obj.parent?.remove?.(obj); }
101
+ catch {}
102
+ }
103
+ }
104
+
105
+ function resolveSelectionObject(scene, selection) {
106
+ if (!scene || selection == null) return null;
107
+ let target = selection;
108
+ if (Array.isArray(selection)) {
109
+ target = selection.find((item) => item != null) ?? null;
110
+ }
111
+ if (!target) return null;
112
+ if (target.isObject3D) return target;
113
+ try {
114
+ if (typeof target === 'string') {
115
+ if (typeof scene.traverse === 'function') {
116
+ let best = null;
117
+ scene.traverse((obj) => {
118
+ if (!obj || obj.name !== target) return;
119
+ if (!best) best = obj;
120
+ const component = resolveComponentFromObject(obj);
121
+ const bestComponent = best ? resolveComponentFromObject(best) : null;
122
+ if (component && !bestComponent) {
123
+ best = obj;
124
+ }
125
+ });
126
+ if (best) return best;
127
+ }
128
+ return typeof scene.getObjectByName === 'function'
129
+ ? scene.getObjectByName(target)
130
+ : null;
131
+ }
132
+ if (typeof target?.uuid === 'string' && typeof scene.getObjectByProperty === 'function') {
133
+ const found = scene.getObjectByProperty('uuid', target.uuid);
134
+ if (found) return found;
135
+ }
136
+ if (typeof target?.name === 'string' && typeof scene.getObjectByName === 'function') {
137
+ const found = scene.getObjectByName(target.name);
138
+ if (found) return found;
139
+ }
140
+ } catch {
141
+ return null;
142
+ }
143
+ return null;
144
+ }
145
+
146
+ function resolveComponentFromObject(obj) {
147
+ let current = obj;
148
+ while (current) {
149
+ if (current.isAssemblyComponent || current.type === 'COMPONENT') return current;
150
+ current = current.parent || null;
151
+ }
152
+ return null;
153
+ }
154
+
155
+ function vectorFrom(value) {
156
+ if (!value) return null;
157
+ if (value instanceof THREE.Vector3) return value.clone();
158
+ if (typeof value === 'object') {
159
+ const { x, y, z } = value;
160
+ const vx = Number.isFinite(x) ? x : 0;
161
+ const vy = Number.isFinite(y) ? y : 0;
162
+ const vz = Number.isFinite(z) ? z : 0;
163
+ return new THREE.Vector3(vx, vy, vz);
164
+ }
165
+ return null;
166
+ }
167
+
168
+ function resolveConstraintEntryId(entry, fallback = null) {
169
+ if (!entry) return fallback;
170
+ const params = entry.inputParams || {};
171
+ const rawId = params.id ?? params.constraintID ?? entry.id ?? fallback;
172
+ if (rawId == null) return fallback;
173
+ return String(rawId);
174
+ }
175
+
176
+ function normalizeConstraintEntryId(entry) {
177
+ return normalizeTypeString(resolveConstraintEntryId(entry));
178
+ }
179
+
180
+ export class AssemblyConstraintHistory {
181
+ constructor(partHistory = null, registry = null) {
182
+ this.partHistory = partHistory || null;
183
+ this.registry = registry || new AssemblyConstraintRegistry();
184
+ this.constraints = [];
185
+ this.idCounter = 0;
186
+ this._listeners = new Set();
187
+ this._autoRunScheduled = false;
188
+ this._autoRunActive = false;
189
+ this._autoRunOptions = null;
190
+ }
191
+
192
+ /**
193
+ * Runs serialized constraint migrations before the data enters runtime structures.
194
+ * All future migrations for persisted constraint payloads should be handled here.
195
+ * @param {object|null} rawEntry
196
+ * @returns {object|null}
197
+ */
198
+ #runConstraintEntryMigrations(rawEntry) {
199
+ if (!rawEntry || typeof rawEntry !== 'object') return rawEntry;
200
+ const migrated = { ...rawEntry };
201
+
202
+ const paramsSource = (rawEntry.inputParams && typeof rawEntry.inputParams === 'object')
203
+ ? rawEntry.inputParams
204
+ : null;
205
+ if (paramsSource) {
206
+ const params = { ...paramsSource };
207
+ const legacyId = Object.prototype.hasOwnProperty.call(params, 'constraintID')
208
+ ? params.constraintID
209
+ : undefined;
210
+ if ((params.id == null || params.id === '') && legacyId != null) {
211
+ params.id = legacyId;
212
+ }
213
+ if (Object.prototype.hasOwnProperty.call(params, 'constraintID')) {
214
+ delete params.constraintID;
215
+ }
216
+ migrated.inputParams = params;
217
+ }
218
+
219
+ const topLevelLegacyId = Object.prototype.hasOwnProperty.call(migrated, 'constraintID')
220
+ ? migrated.constraintID
221
+ : undefined;
222
+ if ((migrated.id == null || migrated.id === '') && topLevelLegacyId != null) {
223
+ migrated.id = topLevelLegacyId;
224
+ }
225
+ if (Object.prototype.hasOwnProperty.call(migrated, 'constraintID')) {
226
+ delete migrated.constraintID;
227
+ }
228
+ if ((migrated.id == null || migrated.id === '') && migrated.inputParams?.id != null) {
229
+ migrated.id = migrated.inputParams.id;
230
+ }
231
+
232
+ return migrated;
233
+ }
234
+
235
+ #syncEntryIds(entry) {
236
+ if (!entry || !entry.inputParams) return;
237
+ const params = entry.inputParams;
238
+ const rawId = params.id ?? params.constraintID ?? entry.id;
239
+ if (!rawId) return;
240
+ const normalized = String(rawId);
241
+ params.id = normalized;
242
+ entry.id = normalized;
243
+ }
244
+
245
+ #linkEntryParams(entry) {
246
+ if (!entry || typeof entry !== 'object') return;
247
+ if (!entry.inputParams || typeof entry.inputParams !== 'object') {
248
+ entry.inputParams = {};
249
+ }
250
+ const params = entry.inputParams;
251
+ const descriptor = { configurable: true, enumerable: false };
252
+ this.#syncEntryIds(entry);
253
+
254
+ const legacyConstraintId = Object.prototype.hasOwnProperty.call(params, 'constraintID')
255
+ ? params.constraintID
256
+ : undefined;
257
+ if (legacyConstraintId != null && (params.id == null || params.id === '')) {
258
+ params.id = legacyConstraintId;
259
+ }
260
+ if (Object.prototype.hasOwnProperty.call(params, 'constraintID')) {
261
+ try { delete params.constraintID; } catch { /* ignore */ }
262
+ }
263
+ if (!Object.getOwnPropertyDescriptor(params, 'constraintID')) {
264
+ Object.defineProperty(params, 'constraintID', {
265
+ ...descriptor,
266
+ get: () => params.id,
267
+ set: (value) => {
268
+ if (value == null) {
269
+ params.id = value;
270
+ entry.id = value;
271
+ return;
272
+ }
273
+ const normalized = String(value);
274
+ params.id = normalized;
275
+ entry.id = normalized;
276
+ },
277
+ });
278
+ }
279
+
280
+ const existingOpen = Object.prototype.hasOwnProperty.call(params, '__open')
281
+ ? params.__open
282
+ : entry.__open;
283
+ if (Object.prototype.hasOwnProperty.call(params, '__open')) {
284
+ try { delete params.__open; } catch { /* ignore */ }
285
+ }
286
+ const normalizedOpen = existingOpen !== false;
287
+ entry.__open = normalizedOpen;
288
+ const runtimeAttributes = (entry.runtimeAttributes && typeof entry.runtimeAttributes === 'object')
289
+ ? entry.runtimeAttributes
290
+ : {};
291
+ runtimeAttributes.__open = normalizedOpen;
292
+ try {
293
+ Object.defineProperty(entry, 'runtimeAttributes', {
294
+ value: runtimeAttributes,
295
+ configurable: true,
296
+ writable: true,
297
+ enumerable: false,
298
+ });
299
+ } catch {
300
+ entry.runtimeAttributes = runtimeAttributes;
301
+ }
302
+
303
+ if (!Object.prototype.hasOwnProperty.call(params, '__entityRef')) {
304
+ Object.defineProperty(params, '__entityRef', {
305
+ ...descriptor,
306
+ value: entry,
307
+ });
308
+ }
309
+
310
+ let persistentSeed;
311
+ if (Object.prototype.hasOwnProperty.call(params, 'persistentData')) {
312
+ persistentSeed = params.persistentData;
313
+ try { delete params.persistentData; } catch { /* ignore */ }
314
+ }
315
+ if (persistentSeed && typeof persistentSeed === 'object') {
316
+ entry.persistentData = deepClone(persistentSeed);
317
+ } else if (!entry.persistentData || typeof entry.persistentData !== 'object') {
318
+ entry.persistentData = {};
319
+ }
320
+ Object.defineProperty(params, 'persistentData', {
321
+ ...descriptor,
322
+ get: () => entry.persistentData || (entry.persistentData = {}),
323
+ set: (value) => {
324
+ const next = (value && typeof value === 'object') ? value : {};
325
+ entry.persistentData = next;
326
+ },
327
+ });
328
+
329
+ Object.defineProperty(params, '__open', {
330
+ ...descriptor,
331
+ get: () => entry.runtimeAttributes.__open !== false,
332
+ set: (value) => {
333
+ const next = value !== false;
334
+ entry.runtimeAttributes.__open = next;
335
+ entry.__open = next;
336
+ },
337
+ });
338
+
339
+ if (entry.type && !params.type) {
340
+ params.type = entry.type;
341
+ }
342
+ entry.entityType = entry.type || params.type || entry.entityType || null;
343
+ }
344
+
345
+ setPartHistory(partHistory) {
346
+ this.partHistory = partHistory || null;
347
+ if (this.partHistory && this.constraints.length) {
348
+ this.#scheduleAutoRun();
349
+ }
350
+ }
351
+
352
+ setRegistry(registry) {
353
+ this.registry = registry || this.registry;
354
+ }
355
+
356
+ addListener(listener) {
357
+ if (typeof listener !== 'function') return () => {};
358
+ this._listeners.add(listener);
359
+ return () => {
360
+ this._listeners.delete(listener);
361
+ };
362
+ }
363
+
364
+ removeListener(listener) {
365
+ if (typeof listener !== 'function') return;
366
+ this._listeners.delete(listener);
367
+ }
368
+
369
+ onChange(listener) {
370
+ if (typeof listener !== 'function') return () => {};
371
+ const wrapped = () => {
372
+ try { listener(this); }
373
+ catch { /* ignore */ }
374
+ };
375
+ return this.addListener(wrapped);
376
+ }
377
+
378
+ list() {
379
+ return this.entries;
380
+ }
381
+
382
+ get entries() {
383
+ return this.constraints;
384
+ }
385
+
386
+ set entries(value) {
387
+ if (Array.isArray(value)) {
388
+ this.constraints = value;
389
+ } else {
390
+ this.constraints = [];
391
+ }
392
+ }
393
+
394
+ get size() {
395
+ return this.constraints.length;
396
+ }
397
+
398
+ findById(constraintID) {
399
+ const id = normalizeTypeString(constraintID);
400
+ if (!id) return null;
401
+ return this.constraints.find((entry) => normalizeConstraintEntryId(entry) === id) || null;
402
+ }
403
+
404
+ async addConstraint(type, initialInput = null) {
405
+ const ConstraintClass = this.#resolveConstraint(type);
406
+ if (!ConstraintClass) throw new Error(`Constraint type "${type}" is not registered.`);
407
+
408
+ const schema = ConstraintClass.inputParamsSchema || {};
409
+ const defaults = extractDefaults(schema);
410
+ const normalizedType = normalizeTypeString(ConstraintClass.constraintType || type || ConstraintClass.name);
411
+ const entry = {
412
+ type: normalizedType,
413
+ inputParams: { ...defaults },
414
+ persistentData: {},
415
+ __open: true,
416
+ enabled: true,
417
+ };
418
+
419
+ Object.defineProperty(entry, 'constraintClass', {
420
+ value: ConstraintClass,
421
+ configurable: true,
422
+ writable: true,
423
+ enumerable: false,
424
+ });
425
+
426
+ const shortName = ConstraintClass?.shortName || ConstraintClass?.constraintShortName || normalizedType || 'CONST';
427
+ const nextId = this.generateId(shortName);
428
+ const existingId = entry.inputParams.id ?? entry.inputParams.constraintID;
429
+ entry.inputParams.id = existingId || nextId;
430
+ this.#syncEntryIds(entry);
431
+
432
+ if (initialInput && typeof initialInput === 'object') {
433
+ Object.assign(entry.inputParams, deepClone(initialInput));
434
+ }
435
+
436
+ entry.inputParams.applyImmediately = true;
437
+ this.#linkEntryParams(entry);
438
+
439
+ this.constraints.push(entry);
440
+ this.#emitChange('add', entry);
441
+ this.checkConstraintErrors(this.partHistory);
442
+ this.#scheduleAutoRun();
443
+ return entry;
444
+ }
445
+
446
+ removeConstraint(constraintID) {
447
+ const id = normalizeTypeString(constraintID);
448
+ if (!id) return false;
449
+ const index = this.constraints.findIndex((entry) => normalizeConstraintEntryId(entry) === id);
450
+ if (index < 0) return false;
451
+ const [removed] = this.constraints.splice(index, 1);
452
+ this.#emitChange('remove', removed || null);
453
+ this.checkConstraintErrors(this.partHistory);
454
+ this.#scheduleAutoRun();
455
+ return true;
456
+ }
457
+
458
+ moveConstraint(constraintID, delta) {
459
+ const id = normalizeTypeString(constraintID);
460
+ if (!id) return false;
461
+ const index = this.constraints.findIndex((entry) => normalizeConstraintEntryId(entry) === id);
462
+ if (index < 0) return false;
463
+ const target = index + delta;
464
+ if (target < 0 || target >= this.constraints.length) return false;
465
+ const [entry] = this.constraints.splice(index, 1);
466
+ this.constraints.splice(target, 0, entry);
467
+ this.#emitChange('reorder', entry || null);
468
+ this.#scheduleAutoRun();
469
+ return true;
470
+ }
471
+
472
+ updateConstraintParams(constraintID, mutateFn) {
473
+ const entry = this.findById(constraintID);
474
+ if (!entry || typeof mutateFn !== 'function') return false;
475
+ mutateFn(entry.inputParams);
476
+ this.#syncEntryIds(entry);
477
+ this.#emitChange('update', entry);
478
+ this.checkConstraintErrors(this.partHistory);
479
+ this.#scheduleAutoRun();
480
+ return true;
481
+ }
482
+
483
+ setConstraintEnabled(constraintID, enabled) {
484
+ const entry = this.findById(constraintID);
485
+ if (!entry) return false;
486
+
487
+ const next = enabled !== false;
488
+ const prev = entry.enabled !== false;
489
+
490
+ if (prev === next) {
491
+ if (entry.enabled !== next) entry.enabled = next;
492
+ if (!next && entry.persistentData?.status !== 'disabled') {
493
+ const pd = { ...(entry.persistentData || {}) };
494
+ pd.status = 'disabled';
495
+ if (!pd.message) pd.message = DISABLED_STATUS_MESSAGE;
496
+ entry.persistentData = pd;
497
+ this.#emitChange('update', entry);
498
+ }
499
+ return false;
500
+ }
501
+
502
+ entry.enabled = next;
503
+ const pd = { ...(entry.persistentData || {}) };
504
+
505
+ if (!next) {
506
+ pd.status = 'disabled';
507
+ if (!pd.message) pd.message = DISABLED_STATUS_MESSAGE;
508
+ entry.persistentData = pd;
509
+ this.#emitChange('update', entry);
510
+ this.checkConstraintErrors(this.partHistory);
511
+ this.#scheduleAutoRun();
512
+ return true;
513
+ }
514
+
515
+ if (pd.status === 'disabled') {
516
+ pd.status = 'pending';
517
+ if (pd.message === DISABLED_STATUS_MESSAGE) delete pd.message;
518
+ entry.persistentData = pd;
519
+ }
520
+
521
+ this.#emitChange('update', entry);
522
+ this.checkConstraintErrors(this.partHistory);
523
+ this.#scheduleAutoRun();
524
+ return true;
525
+ }
526
+
527
+ setOpenState(constraintID, isOpen) {
528
+ const entry = this.findById(constraintID);
529
+ if (!entry) return false;
530
+ const next = isOpen !== false;
531
+ const current = entry.__open !== false;
532
+ if (current === next) return false;
533
+ const params = entry.inputParams || {};
534
+ if (Object.prototype.hasOwnProperty.call(params, '__open')) {
535
+ try { params.__open = next; }
536
+ catch { entry.__open = next; }
537
+ } else {
538
+ entry.__open = next;
539
+ }
540
+ if (!entry.runtimeAttributes || typeof entry.runtimeAttributes !== 'object') {
541
+ entry.runtimeAttributes = {};
542
+ }
543
+ entry.runtimeAttributes.__open = next;
544
+ this.#emitChange('open-state', entry);
545
+ return true;
546
+ }
547
+
548
+ setExclusiveOpen(constraintID) {
549
+ const targetId = normalizeTypeString(constraintID);
550
+ if (!targetId) return false;
551
+ let changed = false;
552
+ for (const entry of this.constraints) {
553
+ if (!entry) continue;
554
+ const entryId = normalizeConstraintEntryId(entry);
555
+ const shouldOpen = entryId === targetId;
556
+ const currentOpen = entry.__open !== false;
557
+ if (currentOpen !== shouldOpen) {
558
+ const params = entry.inputParams || {};
559
+ if (Object.prototype.hasOwnProperty.call(params, '__open')) {
560
+ try { params.__open = shouldOpen; }
561
+ catch { entry.__open = shouldOpen; }
562
+ } else {
563
+ entry.__open = shouldOpen;
564
+ }
565
+ if (!entry.runtimeAttributes || typeof entry.runtimeAttributes !== 'object') {
566
+ entry.runtimeAttributes = {};
567
+ }
568
+ entry.runtimeAttributes.__open = shouldOpen;
569
+ changed = true;
570
+ }
571
+ }
572
+ if (changed) {
573
+ this.#emitChange('open-state', this.findById(constraintID));
574
+ }
575
+ return changed;
576
+ }
577
+
578
+ clear() {
579
+ this.constraints = [];
580
+ this.idCounter = 0;
581
+ this.#emitChange('clear');
582
+ }
583
+
584
+ snapshot() {
585
+ return {
586
+ idCounter: this.idCounter,
587
+ constraints: this.constraints.map((entry) => ({
588
+ type: entry?.type || null,
589
+ inputParams: deepClone(entry?.inputParams) || {},
590
+ persistentData: deepClone(entry?.persistentData) || {},
591
+ open: entry?.__open !== false,
592
+ enabled: entry?.enabled !== false,
593
+ })),
594
+ };
595
+ }
596
+
597
+ async replaceAll(constraints = [], idCounter = 0) {
598
+ const resolved = [];
599
+ const list = Array.isArray(constraints) ? constraints : [];
600
+ let maxId = Number.isFinite(Number(idCounter)) ? Number(idCounter) : 0;
601
+
602
+ for (const rawItem of list) {
603
+ const item = this.#runConstraintEntryMigrations(rawItem);
604
+ if (!item) continue;
605
+ const typeHint = item.type || item.constraintType || null;
606
+ const ConstraintClass = this.#resolveConstraint(typeHint);
607
+ if (!ConstraintClass) continue;
608
+
609
+ const defaults = extractDefaults(ConstraintClass.inputParamsSchema);
610
+ const normalizedType = normalizeTypeString(ConstraintClass.constraintType || typeHint || ConstraintClass.name);
611
+ const entry = {
612
+ type: normalizedType,
613
+ inputParams: { ...defaults, ...deepClone(item.inputParams || {}) },
614
+ persistentData: deepClone(item.persistentData || {}),
615
+ __open: item.open !== false,
616
+ enabled: item.enabled !== false,
617
+ };
618
+
619
+ const existingId = entry.inputParams.id ?? entry.inputParams.constraintID;
620
+ if (!existingId) {
621
+ const prefix = (ConstraintClass?.shortName || ConstraintClass?.constraintShortName || normalizedType || 'CONST')
622
+ .replace(/[^a-z0-9]/gi, '')
623
+ .toUpperCase() || 'CONST';
624
+ maxId += 1;
625
+ entry.inputParams.id = `${prefix}${maxId}`;
626
+ } else {
627
+ const match = String(existingId).match(/(\d+)$/);
628
+ if (match) {
629
+ const numeric = Number(match[1]);
630
+ if (Number.isFinite(numeric)) maxId = Math.max(maxId, numeric);
631
+ }
632
+ entry.inputParams.id = existingId;
633
+ }
634
+ this.#syncEntryIds(entry);
635
+
636
+ entry.inputParams.applyImmediately = true;
637
+
638
+ Object.defineProperty(entry, 'constraintClass', {
639
+ value: ConstraintClass,
640
+ configurable: true,
641
+ writable: true,
642
+ enumerable: false,
643
+ });
644
+
645
+ this.#linkEntryParams(entry);
646
+ resolved.push(entry);
647
+ }
648
+
649
+ this.idCounter = maxId;
650
+ this.constraints = resolved;
651
+ this.#emitChange('replace');
652
+ if (this.constraints.length) {
653
+ this.checkConstraintErrors(this.partHistory);
654
+ this.#scheduleAutoRun();
655
+ }
656
+ }
657
+
658
+ async deserialize(serialized) {
659
+ const payload = serialized && typeof serialized === 'object' ? serialized : {};
660
+ const list = Array.isArray(payload.constraints)
661
+ ? payload.constraints
662
+ : Array.isArray(serialized) ? serialized : [];
663
+ const counter = Number.isFinite(Number(payload.idCounter)) ? Number(payload.idCounter) : undefined;
664
+ await this.replaceAll(list, counter);
665
+ }
666
+
667
+ generateId(typeHint = 'CONST') {
668
+ const prefix = normalizeTypeString(typeHint).replace(/[^a-z0-9]/gi, '').toUpperCase() || 'CONST';
669
+ this.idCounter += 1;
670
+ return `${prefix}${this.idCounter}`;
671
+ }
672
+
673
+ checkConstraintErrors(partHistory = this.partHistory, options = {}) {
674
+ const opts = options && typeof options === 'object' ? options : {};
675
+ const emit = opts.emit !== false;
676
+ const updatePersistentData = opts.updatePersistentData !== false;
677
+
678
+ const ph = partHistory || this.partHistory || null;
679
+ if (ph) this.partHistory = ph;
680
+
681
+ const duplicates = this.#detectDuplicateConstraints();
682
+ const results = [];
683
+ let changed = false;
684
+
685
+ const mutatePersistentData = (entry, mutator) => {
686
+ const previous = entry?.persistentData && typeof entry.persistentData === 'object'
687
+ ? entry.persistentData
688
+ : {};
689
+ const next = { ...previous };
690
+ let modified = false;
691
+
692
+ const set = (key, value) => {
693
+ const prevValue = next[key];
694
+ if (value === undefined) {
695
+ if (Object.prototype.hasOwnProperty.call(next, key)) {
696
+ delete next[key];
697
+ modified = true;
698
+ }
699
+ return;
700
+ }
701
+ if (Array.isArray(prevValue) && Array.isArray(value)) {
702
+ if (shallowArrayEqual(prevValue, value)) return;
703
+ } else if (prevValue === value) {
704
+ return;
705
+ }
706
+ next[key] = value;
707
+ modified = true;
708
+ };
709
+
710
+ const remove = (key) => {
711
+ if (Object.prototype.hasOwnProperty.call(next, key)) {
712
+ delete next[key];
713
+ modified = true;
714
+ }
715
+ };
716
+
717
+ try {
718
+ mutator({ set, remove, data: next, previous });
719
+ } catch (error) {
720
+ console.warn('[AssemblyConstraintHistory] Failed to update persistent data:', error);
721
+ }
722
+
723
+ if (modified) {
724
+ entry.persistentData = next;
725
+ }
726
+ return modified;
727
+ };
728
+
729
+ for (const entry of this.constraints) {
730
+ if (!entry) continue;
731
+
732
+ const constraintID = resolveConstraintEntryId(entry);
733
+ const type = entry?.type || null;
734
+ const result = {
735
+ id: constraintID,
736
+ constraintID,
737
+ type,
738
+ status: 'ok',
739
+ message: '',
740
+ duplicateConstraintIDs: null,
741
+ duplicateSignature: null,
742
+ };
743
+
744
+ if (entry.enabled === false) {
745
+ result.status = 'disabled';
746
+ result.message = DISABLED_STATUS_MESSAGE;
747
+ if (updatePersistentData) {
748
+ changed = mutatePersistentData(entry, ({ set, remove, previous }) => {
749
+ set('status', 'disabled');
750
+ if (!previous.message || previous.message === DISABLED_STATUS_MESSAGE) {
751
+ set('message', DISABLED_STATUS_MESSAGE);
752
+ }
753
+ set('satisfied', false);
754
+ remove('duplicateConstraintIDs');
755
+ remove('duplicateSignature');
756
+ }) || changed;
757
+ }
758
+ results.push(result);
759
+ continue;
760
+ }
761
+
762
+ const constraintClass = this.#resolveConstraint(type);
763
+ const duplicate = duplicates.get(entry);
764
+
765
+ if (!constraintClass) {
766
+ const message = formatUnknownConstraintMessage(type);
767
+ result.status = 'error';
768
+ result.message = message;
769
+ if (updatePersistentData) {
770
+ changed = mutatePersistentData(entry, ({ set, remove }) => {
771
+ set('status', 'error');
772
+ set('message', message);
773
+ set('satisfied', false);
774
+ remove('duplicateConstraintIDs');
775
+ remove('duplicateSignature');
776
+ }) || changed;
777
+ }
778
+ results.push(result);
779
+ continue;
780
+ }
781
+
782
+ if (duplicate) {
783
+ const relatedIds = Array.isArray(duplicate.relatedIds)
784
+ ? duplicate.relatedIds.filter(Boolean)
785
+ : [];
786
+ const signature = duplicate.signature || null;
787
+
788
+ result.status = 'duplicate';
789
+ result.message = duplicate.message || 'Duplicate constraint selections.';
790
+ result.duplicateConstraintIDs = relatedIds.length ? relatedIds : null;
791
+ result.duplicateSignature = signature;
792
+
793
+ if (updatePersistentData) {
794
+ changed = mutatePersistentData(entry, ({ set, remove }) => {
795
+ set('status', 'duplicate');
796
+ set('message', result.message);
797
+ set('satisfied', false);
798
+ if (result.duplicateConstraintIDs) set('duplicateConstraintIDs', result.duplicateConstraintIDs);
799
+ else remove('duplicateConstraintIDs');
800
+ if (signature) set('duplicateSignature', signature);
801
+ else remove('duplicateSignature');
802
+ }) || changed;
803
+ }
804
+ results.push(result);
805
+ continue;
806
+ }
807
+
808
+ if (updatePersistentData) {
809
+ changed = mutatePersistentData(entry, ({ set, remove, previous }) => {
810
+ if (previous.status === 'duplicate') {
811
+ set('status', 'pending');
812
+ if (typeof previous.message === 'string'
813
+ && previous.message.startsWith('Duplicate constraint selections')) {
814
+ remove('message');
815
+ }
816
+ }
817
+ if (previous.status === 'error'
818
+ && typeof previous.message === 'string'
819
+ && previous.message.startsWith('Unknown constraint type:')) {
820
+ set('status', 'pending');
821
+ remove('message');
822
+ }
823
+ remove('duplicateConstraintIDs');
824
+ remove('duplicateSignature');
825
+ }) || changed;
826
+ }
827
+
828
+ results.push(result);
829
+ }
830
+
831
+ if (updatePersistentData && changed && emit) {
832
+ this.#emitChange('update');
833
+ }
834
+
835
+ return results;
836
+ }
837
+
838
+ async runAll(partHistory = this.partHistory, options = {}) {
839
+ const ph = partHistory || this.partHistory;
840
+ if (!ph) return [];
841
+
842
+ this.partHistory = ph;
843
+ this.checkConstraintErrors(ph, { emit: false });
844
+
845
+ const tolerance = Math.abs(toFiniteNumber(options?.tolerance, DEFAULT_SOLVER_TOLERANCE)) || DEFAULT_SOLVER_TOLERANCE;
846
+ const maxIterations = clampIterations(options?.iterations);
847
+ const translationGain = clampGain(options?.translationGain, DEFAULT_TRANSLATION_GAIN);
848
+ const rotationGain = clampGain(options?.rotationGain, DEFAULT_ROTATION_GAIN);
849
+ const debugMode = options?.debugMode === true;
850
+ const defaultDelay = debugMode ? 500 : 0;
851
+ const iterationDelayMsRaw = toFiniteNumber(options?.delayMs ?? options?.iterationDelayMs, defaultDelay);
852
+ const iterationDelayMs = Math.max(0, Number.isFinite(iterationDelayMsRaw) ? iterationDelayMsRaw : defaultDelay);
853
+
854
+ const viewer = options?.viewer || ph.viewer || null;
855
+ const renderScene = () => {
856
+ try { viewer?.render?.(); } catch {}
857
+ try { viewer?.requestRender?.(); } catch {}
858
+ };
859
+
860
+ const controller = options?.controller && typeof options.controller === 'object'
861
+ ? options.controller
862
+ : null;
863
+ const signal = controller?.signal || options?.signal || null;
864
+
865
+ let aborted = false;
866
+ const shouldAbort = () => {
867
+ if (signal?.aborted) {
868
+ aborted = true;
869
+ return true;
870
+ }
871
+ return false;
872
+ };
873
+
874
+ const rawHooks = controller?.hooks || options?.hooks;
875
+ const hooks = rawHooks && typeof rawHooks === 'object' ? rawHooks : {};
876
+ const safeCallHook = async (name, payload = {}) => {
877
+ const fn = hooks?.[name];
878
+ if (typeof fn !== 'function') return;
879
+ try {
880
+ await fn({ controller, signal, aborted, ...payload });
881
+ } catch (error) {
882
+ console.warn(`[AssemblyConstraintHistory] hook "${name}" failed:`, error);
883
+ }
884
+ };
885
+
886
+ const scene = ph.scene || null;
887
+
888
+ const features = Array.isArray(ph.features) ? ph.features.filter(Boolean) : [];
889
+ const featureById = new Map();
890
+ for (const feature of features) {
891
+ const id = normalizeTypeString(feature?.inputParams?.featureID);
892
+ if (id) featureById.set(id, feature);
893
+ }
894
+
895
+ const updatedComponents = new Set();
896
+
897
+ const resolveObject = (selection) => resolveSelectionObject(scene, selection);
898
+ const resolveComponent = (selection) => {
899
+ const obj = resolveObject(selection);
900
+ return resolveComponentFromObject(obj);
901
+ };
902
+
903
+ const getFeatureForComponent = (component) => {
904
+ if (!component) return null;
905
+ const featureId = normalizeTypeString(component.owningFeatureID);
906
+ if (!featureId) return null;
907
+ return featureById.get(featureId) || null;
908
+ };
909
+
910
+ const isComponentFixed = (component) => {
911
+ if (!component) return true;
912
+ if (component.fixed) return true;
913
+ if (component.userData?.fixedByConstraint) return true;
914
+ const feature = getFeatureForComponent(component);
915
+ if (feature?.inputParams?.isFixed) return true;
916
+ return false;
917
+ };
918
+
919
+ const markUpdated = (component) => {
920
+ if (!component) return;
921
+ updatedComponents.add(component);
922
+ };
923
+
924
+ const applyTranslation = (component, delta) => {
925
+ const vec = vectorFrom(delta);
926
+ if (!component || !vec || vec.lengthSq() === 0) return false;
927
+ component.position.add(vec);
928
+ component.updateMatrixWorld?.(true);
929
+ markUpdated(component);
930
+ return true;
931
+ };
932
+
933
+ const applyRotation = (component, quaternion) => {
934
+ if (!component || !quaternion) return false;
935
+ let q;
936
+ if (quaternion instanceof THREE.Quaternion) {
937
+ q = quaternion.clone();
938
+ } else {
939
+ const x = toFiniteNumber(quaternion?.x, 0);
940
+ const y = toFiniteNumber(quaternion?.y, 0);
941
+ const z = toFiniteNumber(quaternion?.z, 0);
942
+ const w = Number.isFinite(quaternion?.w) ? quaternion.w : 1;
943
+ q = new THREE.Quaternion(x, y, z, w);
944
+ }
945
+ if (!Number.isFinite(q.x) || !Number.isFinite(q.y) || !Number.isFinite(q.z) || !Number.isFinite(q.w)) {
946
+ return false;
947
+ }
948
+ if (Math.abs(1 - q.lengthSq()) > 1e-6) q.normalize();
949
+ component.quaternion.premultiply(q);
950
+ component.updateMatrixWorld?.(true);
951
+ markUpdated(component);
952
+ return true;
953
+ };
954
+
955
+ const baseContext = {
956
+ partHistory: ph,
957
+ scene,
958
+ tolerance,
959
+ translationGain,
960
+ rotationGain,
961
+ resolveObject,
962
+ resolveComponent,
963
+ applyTranslation,
964
+ applyRotation,
965
+ isComponentFixed,
966
+ getFeatureForComponent,
967
+ markUpdated,
968
+ viewer,
969
+ renderScene,
970
+ debugMode,
971
+ };
972
+
973
+
974
+ removeExistingDebugArrows(scene);
975
+
976
+ const duplicateInfo = this.#detectDuplicateConstraints();
977
+
978
+ const runtimeEntries = this.constraints.map((entry) => {
979
+ const constraintID = resolveConstraintEntryId(entry);
980
+ if (entry?.enabled === false) {
981
+ const result = {
982
+ id: constraintID,
983
+ ok: true,
984
+ status: 'disabled',
985
+ message: DISABLED_STATUS_MESSAGE,
986
+ applied: false,
987
+ satisfied: false,
988
+ iteration: 0,
989
+ constraintID,
990
+ };
991
+ return {
992
+ entry,
993
+ instance: null,
994
+ result,
995
+ skipReason: 'disabled',
996
+ };
997
+ }
998
+
999
+ const duplicate = duplicateInfo.get(entry);
1000
+ if (duplicate) {
1001
+ const relatedIds = Array.isArray(duplicate.relatedIds) ? duplicate.relatedIds.slice() : [];
1002
+ const result = {
1003
+ id: constraintID,
1004
+ ok: false,
1005
+ status: 'duplicate',
1006
+ message: duplicate.message,
1007
+ applied: false,
1008
+ satisfied: false,
1009
+ iteration: 0,
1010
+ constraintID,
1011
+ duplicateConstraintIDs: relatedIds,
1012
+ duplicateSignature: duplicate.signature,
1013
+ };
1014
+ return {
1015
+ entry,
1016
+ instance: null,
1017
+ result,
1018
+ skipReason: 'duplicate',
1019
+ };
1020
+ }
1021
+
1022
+ const ConstraintClass = this.#resolveConstraint(entry.type);
1023
+ if (!ConstraintClass) {
1024
+ const message = formatUnknownConstraintMessage(entry.type);
1025
+ entry.persistentData = {
1026
+ status: 'error',
1027
+ message,
1028
+ lastRunAt: Date.now(),
1029
+ lastIteration: 0,
1030
+ };
1031
+ return {
1032
+ entry,
1033
+ instance: null,
1034
+ result: {
1035
+ id: constraintID,
1036
+ ok: false,
1037
+ status: 'error',
1038
+ message,
1039
+ applied: false,
1040
+ satisfied: false,
1041
+ iteration: 0,
1042
+ constraintID,
1043
+ },
1044
+ skipReason: 'unregistered',
1045
+ };
1046
+ }
1047
+
1048
+ const originalInputParams = deepClone(entry.inputParams) || {};
1049
+ const runtimeInputParams = deepClone(entry.inputParams) || {};
1050
+
1051
+ const instance = new ConstraintClass(ph);
1052
+ this.#applyNumericExpressions(runtimeInputParams, ConstraintClass.inputParamsSchema || {});
1053
+
1054
+ try { instance.inputParams = runtimeInputParams; }
1055
+ catch { instance.inputParams = { ...runtimeInputParams }; }
1056
+ try { Object.assign(instance.persistentData, deepClone(entry.persistentData)); }
1057
+ catch { instance.persistentData = { ...(entry.persistentData || {}) }; }
1058
+
1059
+ return { entry, instance, result: null, originalInputParams };
1060
+ });
1061
+
1062
+ for (const runtime of runtimeEntries) {
1063
+ runtime.instance?.clearDebugArrows?.({ scene });
1064
+ }
1065
+
1066
+ await safeCallHook('onStart', {
1067
+ maxIterations,
1068
+ constraintCount: runtimeEntries.length,
1069
+ });
1070
+
1071
+ let iterationsCompleted = 0;
1072
+ const totalConstraints = runtimeEntries.length;
1073
+
1074
+ outerLoop:
1075
+ for (let iter = 0; iter < maxIterations; iter += 1) {
1076
+ if (shouldAbort()) break;
1077
+
1078
+ await safeCallHook('onIterationStart', {
1079
+ iteration: iter,
1080
+ maxIterations,
1081
+ });
1082
+ if (shouldAbort()) break;
1083
+
1084
+ let iterationApplied = false;
1085
+
1086
+ for (let idx = 0; idx < runtimeEntries.length; idx += 1) {
1087
+ if (shouldAbort()) break outerLoop;
1088
+ const runtime = runtimeEntries[idx];
1089
+ const constraintID = resolveConstraintEntryId(runtime?.entry);
1090
+ const constraintType = runtime?.entry?.type || null;
1091
+ const hookBase = {
1092
+ iteration: iter,
1093
+ index: idx,
1094
+ id: constraintID,
1095
+ constraintID,
1096
+ constraintType,
1097
+ totalConstraints,
1098
+ };
1099
+
1100
+ if (!runtime.instance) {
1101
+ await safeCallHook('onConstraintSkipped', { ...hookBase, skipReason: runtime.skipReason || null });
1102
+ continue;
1103
+ }
1104
+
1105
+ await safeCallHook('onConstraintStart', hookBase);
1106
+ if (shouldAbort()) break outerLoop;
1107
+
1108
+ const context = { ...baseContext, iteration: iter, maxIterations };
1109
+
1110
+ let result;
1111
+ try {
1112
+ if (typeof runtime.instance.solve === 'function') {
1113
+ result = await runtime.instance.solve(context);
1114
+ } else {
1115
+ result = await runtime.instance.run(context);
1116
+ }
1117
+ } catch (error) {
1118
+ console.warn('[AssemblyConstraintHistory] Constraint solve failed:', error);
1119
+ result = {
1120
+ ok: false,
1121
+ status: 'error',
1122
+ message: error?.message || 'Constraint evaluation failed.',
1123
+ error,
1124
+ };
1125
+ runtime.instance.persistentData = runtime.instance.persistentData || {};
1126
+ runtime.instance.persistentData.status = 'error';
1127
+ runtime.instance.persistentData.message = result.message;
1128
+ }
1129
+
1130
+ runtime.result = this.#finalizeConstraintResult(runtime.instance, result, iter);
1131
+
1132
+ if (runtime.result.applied) iterationApplied = true;
1133
+
1134
+ await safeCallHook('onConstraintEnd', {
1135
+ ...hookBase,
1136
+ result: runtime.result,
1137
+ });
1138
+ if (shouldAbort()) break outerLoop;
1139
+ }
1140
+
1141
+ if (typeof baseContext.renderScene === 'function') {
1142
+ try { baseContext.renderScene(); }
1143
+ catch {}
1144
+ }
1145
+ if (iterationDelayMs > 0) {
1146
+ await new Promise((resolve) => setTimeout(resolve, iterationDelayMs));
1147
+ }
1148
+
1149
+ if (shouldAbort()) break;
1150
+
1151
+ iterationsCompleted = iter + 1;
1152
+
1153
+ await safeCallHook('onIterationComplete', {
1154
+ iteration: iter,
1155
+ maxIterations,
1156
+ applied: iterationApplied,
1157
+ });
1158
+ if (shouldAbort()) break;
1159
+
1160
+ if (!iterationApplied) break;
1161
+ }
1162
+
1163
+ aborted = aborted || signal?.aborted || false;
1164
+
1165
+ const now = Date.now();
1166
+ const finalResults = [];
1167
+
1168
+ for (const runtime of runtimeEntries) {
1169
+ const { entry, instance } = runtime;
1170
+ const result = runtime.result || this.#finalizeConstraintResult(
1171
+ instance,
1172
+ { ok: false, status: 'pending', message: 'Constraint was not evaluated.' },
1173
+ Math.max(0, maxIterations - 1),
1174
+ );
1175
+
1176
+ const sourcePD = instance?.persistentData && Object.keys(instance.persistentData).length
1177
+ ? instance.persistentData
1178
+ : entry.persistentData || {};
1179
+
1180
+ const nextPersistent = { ...sourcePD };
1181
+ if (result.status) nextPersistent.status = result.status;
1182
+ if (result.message !== undefined) {
1183
+ if (result.message) nextPersistent.message = result.message;
1184
+ else if (nextPersistent.message) delete nextPersistent.message;
1185
+ }
1186
+ nextPersistent.satisfied = !!result.satisfied;
1187
+ if (typeof result.error === 'number' && Number.isFinite(result.error)) {
1188
+ nextPersistent.error = result.error;
1189
+ }
1190
+ nextPersistent.lastRunAt = now;
1191
+ nextPersistent.lastIteration = result.iteration;
1192
+ nextPersistent.lastRequestedIterations = maxIterations;
1193
+ if (result.status === 'duplicate') {
1194
+ if (Array.isArray(result.duplicateConstraintIDs) && result.duplicateConstraintIDs.length) {
1195
+ nextPersistent.duplicateConstraintIDs = result.duplicateConstraintIDs.slice();
1196
+ } else if (nextPersistent.duplicateConstraintIDs) {
1197
+ delete nextPersistent.duplicateConstraintIDs;
1198
+ }
1199
+ if (result.duplicateSignature) {
1200
+ nextPersistent.duplicateSignature = result.duplicateSignature;
1201
+ } else if (nextPersistent.duplicateSignature) {
1202
+ delete nextPersistent.duplicateSignature;
1203
+ }
1204
+ } else {
1205
+ if (nextPersistent.duplicateConstraintIDs) delete nextPersistent.duplicateConstraintIDs;
1206
+ if (nextPersistent.duplicateSignature) delete nextPersistent.duplicateSignature;
1207
+ }
1208
+
1209
+ entry.persistentData = nextPersistent;
1210
+ if (runtime.originalInputParams) {
1211
+ const target = (entry.inputParams && typeof entry.inputParams === 'object')
1212
+ ? entry.inputParams
1213
+ : {};
1214
+ for (const key of Object.keys(target)) {
1215
+ if (!Object.prototype.hasOwnProperty.call(runtime.originalInputParams, key)) {
1216
+ delete target[key];
1217
+ }
1218
+ }
1219
+ Object.assign(target, runtime.originalInputParams);
1220
+ entry.inputParams = target;
1221
+ } else {
1222
+ entry.inputParams = { ...(entry.inputParams || {}) };
1223
+ }
1224
+
1225
+ const constraintID = resolveConstraintEntryId(entry);
1226
+ finalResults.push({
1227
+ id: constraintID,
1228
+ constraintID,
1229
+ type: entry?.type || null,
1230
+ ...result,
1231
+ });
1232
+ }
1233
+
1234
+ try {
1235
+ ph.syncAssemblyComponentTransforms?.();
1236
+ } catch (error) {
1237
+ console.warn('[AssemblyConstraintHistory] Failed to sync component transforms:', error);
1238
+ }
1239
+
1240
+ this.#emitChange('solve');
1241
+
1242
+ await safeCallHook('onComplete', {
1243
+ results: finalResults.slice(),
1244
+ aborted,
1245
+ iterationsCompleted,
1246
+ maxIterations,
1247
+ });
1248
+
1249
+ return finalResults;
1250
+ }
1251
+
1252
+ #applyNumericExpressions(inputParams, schema) {
1253
+ if (!inputParams || !schema) return;
1254
+ for (const key in schema) {
1255
+ if (!Object.prototype.hasOwnProperty.call(schema, key)) continue;
1256
+ const def = schema[key];
1257
+ if (!def || def.type !== 'number') continue;
1258
+ const evaluated = evaluateConstraintNumericValue(this.partHistory, inputParams[key]);
1259
+ if (evaluated != null) {
1260
+ inputParams[key] = evaluated;
1261
+ }
1262
+ }
1263
+ }
1264
+
1265
+ #detectDuplicateConstraints() {
1266
+ const grouped = new Map();
1267
+ for (const entry of this.constraints) {
1268
+ if (!entry) continue;
1269
+ const baseType = this.#normalizeDuplicateConstraintType(entry?.type);
1270
+ if (!baseType) continue;
1271
+ const signature = this.#buildSelectionSignature(entry?.inputParams);
1272
+ if (!signature) continue;
1273
+ if (!grouped.has(signature)) grouped.set(signature, []);
1274
+ const constraintID = normalizeConstraintEntryId(entry) || null;
1275
+ const typeLabel = DUPLICATE_TYPE_LABELS[baseType] || baseType;
1276
+ grouped.get(signature).push({
1277
+ entry,
1278
+ type: baseType,
1279
+ typeLabel,
1280
+ id: constraintID,
1281
+ });
1282
+ }
1283
+
1284
+ const duplicates = new Map();
1285
+ for (const [signature, list] of grouped.entries()) {
1286
+ if (!Array.isArray(list) || list.length <= 1) continue;
1287
+ const byType = new Map();
1288
+ for (const item of list) {
1289
+ if (!byType.has(item.type)) byType.set(item.type, []);
1290
+ byType.get(item.type).push(item);
1291
+ }
1292
+ for (const item of list) {
1293
+ const sameType = (byType.get(item.type) || []).filter((other) => other !== item);
1294
+ const otherTypes = [];
1295
+ for (const [type, entries] of byType.entries()) {
1296
+ if (type === item.type) continue;
1297
+ otherTypes.push({ type, entries });
1298
+ }
1299
+
1300
+ const formatConstraintPhrase = (base, ids) => {
1301
+ const unique = Array.from(new Set(
1302
+ ids.map((id) => (id && id.trim()) ? id.trim() : null).filter(Boolean),
1303
+ ));
1304
+ if (unique.length === 0) {
1305
+ return `${base} constraint`;
1306
+ }
1307
+ const plural = unique.length > 1 ? 'constraints' : 'constraint';
1308
+ return `${base} ${plural} ${unique.join(', ')}`;
1309
+ };
1310
+
1311
+ const parts = [];
1312
+ if (sameType.length) {
1313
+ const ids = sameType.map((other) => other.id);
1314
+ parts.push(formatConstraintPhrase(`shares selections with ${item.typeLabel}`, ids));
1315
+ }
1316
+ for (const group of otherTypes) {
1317
+ const label = DUPLICATE_TYPE_LABELS[group.type] || group.type;
1318
+ const ids = group.entries.map((other) => other.id);
1319
+ parts.push(formatConstraintPhrase(`conflicts with ${label}`, ids));
1320
+ }
1321
+ const message = parts.length
1322
+ ? `Duplicate constraint selections: ${parts.join('. ')}.`
1323
+ : 'Duplicate constraint selections.';
1324
+
1325
+ const otherIds = list
1326
+ .filter((other) => other !== item)
1327
+ .map((other) => other.id)
1328
+ .filter(Boolean);
1329
+ duplicates.set(item.entry, {
1330
+ message,
1331
+ signature,
1332
+ relatedIds: Array.from(new Set(otherIds)),
1333
+ type: item.type,
1334
+ });
1335
+ }
1336
+ }
1337
+
1338
+ return duplicates;
1339
+ }
1340
+
1341
+ #normalizeDuplicateConstraintType(type) {
1342
+ const normalized = normalizeTypeString(type).toLowerCase();
1343
+ if (!normalized) return null;
1344
+ return DUPLICATE_TYPE_MAP.get(normalized) || null;
1345
+ }
1346
+
1347
+ #scheduleAutoRun(options = null) {
1348
+ if (!this.partHistory) return;
1349
+ const opts = options && typeof options === 'object' ? { ...options } : null;
1350
+ this._autoRunOptions = opts;
1351
+ if (this._autoRunScheduled) return;
1352
+ this._autoRunScheduled = true;
1353
+ Promise.resolve().then(() => {
1354
+ try { this.#executeAutoRun(); }
1355
+ catch (error) { console.warn('[AssemblyConstraintHistory] Failed to schedule auto run:', error); }
1356
+ });
1357
+ }
1358
+
1359
+ async #executeAutoRun() {
1360
+ if (!this._autoRunScheduled) return;
1361
+ if (this._autoRunActive) return;
1362
+
1363
+ const options = this._autoRunOptions ? { ...this._autoRunOptions } : {};
1364
+ this._autoRunOptions = null;
1365
+ this._autoRunScheduled = false;
1366
+
1367
+ const iterationsRaw = Number(options.iterations);
1368
+ const iterations = Number.isFinite(iterationsRaw) && iterationsRaw >= 1
1369
+ ? Math.floor(iterationsRaw)
1370
+ : AUTO_RUN_ITERATIONS;
1371
+
1372
+ const ph = this.partHistory;
1373
+ if (!ph) return;
1374
+
1375
+ this._autoRunActive = true;
1376
+ try {
1377
+ await this.runAll(ph, { ...options, iterations });
1378
+ } catch (error) {
1379
+ console.warn('[AssemblyConstraintHistory] Auto run failed:', error);
1380
+ } finally {
1381
+ this._autoRunActive = false;
1382
+ if (this._autoRunScheduled) {
1383
+ Promise.resolve().then(() => {
1384
+ try { this.#executeAutoRun(); }
1385
+ catch (error) { console.warn('[AssemblyConstraintHistory] Failed to re-run auto cycle:', error); }
1386
+ });
1387
+ }
1388
+ }
1389
+ }
1390
+
1391
+ #buildSelectionSignature(params) {
1392
+ if (!params || typeof params !== 'object') return null;
1393
+ const selections = this.#extractSelectionPair(params);
1394
+ if (!selections) return null;
1395
+ const keys = [];
1396
+ for (const selection of selections) {
1397
+ const key = this.#selectionKey(selection, 0, new Set());
1398
+ if (!key) return null;
1399
+ keys.push(key);
1400
+ }
1401
+ if (keys.length !== 2) return null;
1402
+ keys.sort();
1403
+ return `${keys[0]}|${keys[1]}`;
1404
+ }
1405
+
1406
+ #extractSelectionPair(params) {
1407
+ const raw = Array.isArray(params?.elements) ? params.elements : [];
1408
+ const picks = [];
1409
+ for (const item of raw) {
1410
+ if (item == null) continue;
1411
+ picks.push(item);
1412
+ if (picks.length >= 2) break;
1413
+ }
1414
+ return picks.length >= 2 ? picks.slice(0, 2) : null;
1415
+ }
1416
+
1417
+ #selectionKey(selection, depth = 0, seen = new Set()) {
1418
+ if (selection == null) return null;
1419
+ if (depth > 5) return null;
1420
+
1421
+ if (Array.isArray(selection)) {
1422
+ for (const item of selection) {
1423
+ const key = this.#selectionKey(item, depth + 1, seen);
1424
+ if (key) return key;
1425
+ }
1426
+ return null;
1427
+ }
1428
+
1429
+ const type = typeof selection;
1430
+ if (type === 'string' || type === 'number' || type === 'boolean') {
1431
+ return `${type}:${String(selection)}`;
1432
+ }
1433
+ if (type !== 'object') return null;
1434
+
1435
+ if (seen.has(selection)) return null;
1436
+ seen.add(selection);
1437
+ try {
1438
+ if (selection.isObject3D && typeof selection.uuid === 'string' && selection.uuid) {
1439
+ return `uuid:${selection.uuid}`;
1440
+ }
1441
+
1442
+ const preferredFields = [
1443
+ 'selectionID', 'selectionId', 'fullName', 'fullPath',
1444
+ 'pathKey', 'pathId', 'pathID', 'id', 'uuid', 'entityUUID',
1445
+ 'brepId', 'brepID', 'objectUUID', 'objectId', 'objectID',
1446
+ ];
1447
+ for (const field of preferredFields) {
1448
+ const value = selection[field];
1449
+ if (typeof value === 'string' && value.trim()) return `${field}:${value.trim()}`;
1450
+ }
1451
+
1452
+ const nameFields = [
1453
+ 'name',
1454
+ 'label',
1455
+ 'displayName',
1456
+ 'componentName',
1457
+ 'faceName',
1458
+ 'edgeName',
1459
+ 'vertexName',
1460
+ 'reference',
1461
+ 'refName',
1462
+ ];
1463
+ for (const field of nameFields) {
1464
+ const value = selection[field];
1465
+ if (typeof value === 'string' && value.trim()) return `${field}:${value.trim()}`;
1466
+ }
1467
+
1468
+ if (Array.isArray(selection.path) && selection.path.length) {
1469
+ const joined = selection.path.map((part) => String(part)).join('/');
1470
+ if (joined) return `path:${joined}`;
1471
+ }
1472
+
1473
+ if (selection.component && typeof selection.component === 'object') {
1474
+ const compKey = this.#selectionKey(selection.component, depth + 1, seen);
1475
+ if (compKey) return `component:${compKey}`;
1476
+ }
1477
+
1478
+ if (selection.object && typeof selection.object === 'object') {
1479
+ const objKey = this.#selectionKey(selection.object, depth + 1, seen);
1480
+ if (objKey) return `object:${objKey}`;
1481
+ }
1482
+
1483
+ const keys = Object.keys(selection).filter((key) => key !== '__proto__').sort();
1484
+ const parts = [];
1485
+ for (const key of keys) {
1486
+ const value = selection[key];
1487
+ if (value == null) continue;
1488
+ if (typeof value === 'function' || typeof value === 'symbol') continue;
1489
+ let repr = null;
1490
+ if (typeof value === 'object') {
1491
+ const nested = this.#selectionKey(value, depth + 1, seen);
1492
+ if (nested) repr = nested;
1493
+ } else {
1494
+ repr = `${typeof value}:${String(value)}`;
1495
+ }
1496
+ if (repr) {
1497
+ parts.push(`${key}=${repr}`);
1498
+ if (parts.length >= 8) break;
1499
+ }
1500
+ }
1501
+ if (parts.length) {
1502
+ return `obj:${parts.join(',')}`;
1503
+ }
1504
+ return null;
1505
+ } finally {
1506
+ seen.delete(selection);
1507
+ }
1508
+ }
1509
+
1510
+ #finalizeConstraintResult(instance, rawResult, iteration) {
1511
+ const result = rawResult && typeof rawResult === 'object' ? rawResult : {};
1512
+ const satisfied = !!result.satisfied;
1513
+ const applied = !!result.applied;
1514
+ const ok = result.ok !== false;
1515
+ const message = typeof result.message === 'string' ? result.message : '';
1516
+ let status = typeof result.status === 'string' && result.status.trim()
1517
+ ? result.status.trim()
1518
+ : null;
1519
+ if (!status) {
1520
+ if (!ok) status = 'error';
1521
+ else if (satisfied) status = 'satisfied';
1522
+ else if (applied) status = 'adjusted';
1523
+ else status = 'pending';
1524
+ }
1525
+ const errorValue = Number.isFinite(result.error) ? result.error : null;
1526
+
1527
+ // Ensure persistent data on the instance reflects the normalized status.
1528
+ if (instance) {
1529
+ instance.persistentData = instance.persistentData || {};
1530
+ if (!instance.persistentData.status) instance.persistentData.status = status;
1531
+ if (message && !instance.persistentData.message) instance.persistentData.message = message;
1532
+ instance.persistentData.satisfied = satisfied;
1533
+ if (errorValue != null) instance.persistentData.error = errorValue;
1534
+ }
1535
+
1536
+ return {
1537
+ ok,
1538
+ status,
1539
+ satisfied,
1540
+ applied,
1541
+ error: errorValue,
1542
+ message,
1543
+ iteration,
1544
+ diagnostics: result.diagnostics || null,
1545
+ };
1546
+ }
1547
+
1548
+ #resolveConstraint(type) {
1549
+ const t = normalizeTypeString(type);
1550
+ if (!t) return null;
1551
+ if (this.registry && typeof this.registry.getSafe === 'function') {
1552
+ const found = this.registry.getSafe(t);
1553
+ if (found) return found;
1554
+ }
1555
+ if (this.registry && typeof this.registry.get === 'function') {
1556
+ try { return this.registry.get(t); }
1557
+ catch { return null; }
1558
+ }
1559
+ return null;
1560
+ }
1561
+
1562
+ #emitChange(reason = 'update', entry = null, extra = null) {
1563
+ if (!(this._listeners instanceof Set) || this._listeners.size === 0) return;
1564
+ const basePayload = {
1565
+ ...(extra && typeof extra === 'object' ? extra : {}),
1566
+ history: this,
1567
+ entry: entry || null,
1568
+ reason: reason || 'update',
1569
+ };
1570
+ for (const listener of Array.from(this._listeners)) {
1571
+ try {
1572
+ listener({ ...basePayload });
1573
+ } catch { /* ignore listener errors */ }
1574
+ }
1575
+ }
1576
+ }