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,1373 @@
1
+ import * as THREE from 'three';
2
+ import { SelectionFilter } from '../SelectionFilter.js';
3
+ import { LabelOverlay } from '../pmi/LabelOverlay.js';
4
+ import { resolveSelectionObject } from './constraintSelectionUtils.js';
5
+ import {
6
+ isFaceObject,
7
+ computeFaceOrigin,
8
+ computeFaceNormal,
9
+ estimateArrowLength,
10
+ } from './constraintFaceUtils.js';
11
+ import { applyHighlightMaterial, restoreHighlightRecords } from './constraintHighlightUtils.js';
12
+ import { extractWorldPoint } from './constraintPointUtils.js';
13
+ import { constraintStatusInfo } from './constraintStatusUtils.js';
14
+ import { constraintLabelText } from './constraintLabelUtils.js';
15
+ import { AssemblyConstraintControlsWidget } from './AssemblyConstraintControlsWidget.js';
16
+ import { AssemblyConstraintCollectionWidget } from './AssemblyConstraintCollectionWidget.js';
17
+ import { MODEL_STORAGE_PREFIX } from '../../services/componentLibrary.js';
18
+ import './AssemblyConstraintsWidget.css';
19
+
20
+
21
+ const ROOT_CLASS = 'constraints-history';
22
+ const DEFAULT_CONSTRAINT_COLOR = '#ffd60a';
23
+
24
+ function resolveConstraintId(entry, fallback = null) {
25
+ if (!entry) return fallback;
26
+ const params = entry.inputParams || {};
27
+ if (params.id != null) return String(params.id);
28
+ if (params.constraintID != null) return String(params.constraintID);
29
+ if (entry.id != null) return String(entry.id);
30
+ return fallback;
31
+ }
32
+
33
+ export class AssemblyConstraintsWidget {
34
+ constructor(viewer) {
35
+ this.viewer = viewer || null;
36
+ this.partHistory = viewer?.partHistory || null;
37
+ this.registry = this.partHistory?.assemblyConstraintRegistry || null;
38
+ this.history = this.partHistory?.assemblyConstraintHistory || null;
39
+ if (this.history) this.history.setPartHistory?.(this.partHistory);
40
+
41
+ this._highlighted = new Map();
42
+ this._highlightPalette = ['#ffd60a', '#30d158', '#0a84ff', '#ff3b30',];
43
+
44
+ this._defaultIterations = 1000;
45
+ this._normalArrows = new Set();
46
+ this._debugMode = false;
47
+ this._constraintLines = new Map();
48
+ this._labelPositions = new Map();
49
+ this._onControlsChange = () => this._refreshConstraintLabels();
50
+ this._onWindowResize = () => this._refreshConstraintLabels();
51
+ this._constraintGraphicsEnabled = true;
52
+ this._constraintGraphicsPreferred = this._constraintGraphicsEnabled;
53
+ this._constraintGraphicsCheckbox = null;
54
+ this._hoverHighlights = new Map();
55
+ this._activeHoverConstraintId = null;
56
+ this._syncScheduled = false;
57
+ this._solverRun = null;
58
+ this._startButton = null;
59
+ this._stopButton = null;
60
+ this._solverStatusLabel = null;
61
+ this._solverLoopLabel = null;
62
+ this._solverConstraintLabel = null;
63
+ this._pauseCheckbox = null;
64
+ this._solverContinueButton = null;
65
+ this._animateCheckbox = null;
66
+ this._animateDelayInput = null;
67
+ this._animateDelayContainer = null;
68
+ this._animateEnabled = true;
69
+ this._animateDelayMs = 1;
70
+ this._controlsWidget = null;
71
+ this._updateComponentsBtn = null;
72
+ this._updatingComponents = false;
73
+ this._onStorageEvent = null;
74
+ this._fullSolveOnChange = false;
75
+ this._pendingFullSolve = false;
76
+ this._ignoreFullSolveChangeCount = 0;
77
+ this._fullSolveCheckbox = null;
78
+ this._pmiVisibilityLock = 0;
79
+ this._constraintList = null;
80
+
81
+ this.uiElement = document.createElement('div');
82
+ this.uiElement.className = ROOT_CLASS;
83
+
84
+ if (this.viewer?.scene) {
85
+ this._constraintGroup = new THREE.Group();
86
+ this._constraintGroup.name = 'assembly-constraint-overlays';
87
+ this._constraintGroup.userData.excludeFromFit = true;
88
+ try { this.viewer.scene.add(this._constraintGroup); }
89
+ catch { this._constraintGroup = null; }
90
+ } else {
91
+ this._constraintGroup = null;
92
+ }
93
+
94
+ if (this.viewer) {
95
+ this._labelOverlay = new LabelOverlay(
96
+ this.viewer,
97
+ null,
98
+ null,
99
+ (idx, ann, ev) => { try { this.#handleLabelClick(idx, ann, ev); } catch { } },
100
+ );
101
+ try { this.viewer.controls?.addEventListener('change', this._onControlsChange); } catch { }
102
+ try { window.addEventListener('resize', this._onWindowResize); } catch { }
103
+ } else {
104
+ this._labelOverlay = null;
105
+ }
106
+
107
+ this._controlsWidget = new AssemblyConstraintControlsWidget(this);
108
+ this.uiElement.appendChild(this._controlsWidget.element);
109
+ this._setConstraintGraphicsEnabled(this._constraintGraphicsEnabled);
110
+
111
+ this._updateComponentsBtn = this._buildUpdateComponentsButton();
112
+ if (this._updateComponentsBtn && this._controlsWidget?.element) {
113
+ const target = this._controlsWidget.element;
114
+ const solverSection = target.querySelector('.control-panel-section.solver-controls');
115
+ if (solverSection) {
116
+ target.insertBefore(this._updateComponentsBtn, solverSection);
117
+ } else {
118
+ target.insertBefore(this._updateComponentsBtn, target.firstChild);
119
+ }
120
+ }
121
+ this._refreshUpdateComponentsButton();
122
+
123
+ try {
124
+ this._onStorageEvent = (ev) => {
125
+ try {
126
+ const key = (ev && (ev.key ?? ev.detail?.key)) || '';
127
+ if (typeof key === 'string' && key.startsWith(MODEL_STORAGE_PREFIX)) {
128
+ this._refreshUpdateComponentsButton();
129
+ }
130
+ } catch {
131
+ /* ignore */
132
+ }
133
+ };
134
+ window.addEventListener('storage', this._onStorageEvent);
135
+ } catch {
136
+ this._onStorageEvent = null;
137
+ }
138
+
139
+ this._constraintList = new AssemblyConstraintCollectionWidget({
140
+ history: this.history,
141
+ viewer: this.viewer,
142
+ partHistory: this.partHistory,
143
+ onBeforeConstraintChange: () => this._stopSolver({ wait: true }),
144
+ onHighlightRequest: (entry) => {
145
+ if (!entry) return;
146
+ const cls = this._resolveConstraintClass(entry);
147
+ this._highlightConstraint(entry, cls);
148
+ },
149
+ onClearHighlight: () => { this._clearHighlights(); },
150
+ });
151
+ this.uiElement.appendChild(this._constraintList.uiElement);
152
+
153
+ this._unsubscribe = this.history?.onChange(() => this.#handleHistoryChange()) || null;
154
+
155
+ this.render();
156
+ }
157
+
158
+ dispose() {
159
+ this._clearHighlights();
160
+ this._clearConstraintVisuals();
161
+ try { this.viewer?.controls?.removeEventListener('change', this._onControlsChange); } catch { }
162
+ try { window.removeEventListener('resize', this._onWindowResize); } catch { }
163
+ try {
164
+ if (this._onStorageEvent) window.removeEventListener('storage', this._onStorageEvent);
165
+ } catch {
166
+ /* ignore */
167
+ }
168
+ this._onStorageEvent = null;
169
+ if (this._constraintGroup && this.viewer?.scene) {
170
+ try { this.viewer.scene.remove(this._constraintGroup); } catch { }
171
+ }
172
+ this._constraintGroup = null;
173
+ this._constraintLines.clear();
174
+ this._labelPositions.clear();
175
+ try { this._labelOverlay?.dispose?.(); } catch { }
176
+ this._labelOverlay = null;
177
+ if (this._unsubscribe) {
178
+ try { this._unsubscribe(); } catch { /* ignore */ }
179
+ this._unsubscribe = null;
180
+ }
181
+ const stopPromise = this._stopSolver({ wait: false });
182
+ if (stopPromise?.catch) {
183
+ try { stopPromise.catch(() => { }); } catch { /* ignore */ }
184
+ }
185
+ this._solverRun = null;
186
+ this._iterationInput = null;
187
+ this._startButton = null;
188
+ this._stopButton = null;
189
+ this._solverStatusLabel = null;
190
+ this._solverLoopLabel = null;
191
+ this._solverConstraintLabel = null;
192
+ this._pauseCheckbox = null;
193
+ this._solverContinueButton = null;
194
+ this._animateCheckbox = null;
195
+ this._animateDelayInput = null;
196
+ this._animateDelayContainer = null;
197
+ this._constraintGraphicsCheckbox = null;
198
+ this._fullSolveCheckbox = null;
199
+ this._pendingFullSolve = false;
200
+ this._fullSolveOnChange = false;
201
+ this._ignoreFullSolveChangeCount = 0;
202
+ this._controlsWidget?.dispose?.();
203
+ this._controlsWidget = null;
204
+ this._constraintList?.dispose?.();
205
+ this._constraintList = null;
206
+ }
207
+
208
+ render() {
209
+ this._scheduleSync();
210
+ }
211
+
212
+ #handleHistoryChange() {
213
+ this._scheduleSync();
214
+ if (this._ignoreFullSolveChangeCount > 0) {
215
+ this._ignoreFullSolveChangeCount -= 1;
216
+ return;
217
+ }
218
+ if (!this._fullSolveOnChange) return;
219
+ this.#scheduleFullSolveOnChange();
220
+ }
221
+
222
+ #scheduleFullSolveOnChange() {
223
+ if (this._pendingFullSolve) return;
224
+ this._pendingFullSolve = true;
225
+ const trigger = () => {
226
+ if (!this._pendingFullSolve) return;
227
+ if (!this._fullSolveOnChange) {
228
+ this._pendingFullSolve = false;
229
+ return;
230
+ }
231
+ if (this._solverRun?.running) {
232
+ setTimeout(() => this.#attemptFullSolveOnChange(), 75);
233
+ return;
234
+ }
235
+ this.#attemptFullSolveOnChange();
236
+ };
237
+ if (typeof queueMicrotask === 'function') {
238
+ queueMicrotask(trigger);
239
+ } else if (typeof Promise === 'function') {
240
+ Promise.resolve().then(trigger).catch(() => trigger());
241
+ } else {
242
+ setTimeout(trigger, 0);
243
+ }
244
+ }
245
+
246
+ #attemptFullSolveOnChange() {
247
+ if (!this._pendingFullSolve) return;
248
+ if (!this._fullSolveOnChange) {
249
+ this._pendingFullSolve = false;
250
+ return;
251
+ }
252
+ if (this._solverRun?.running) {
253
+ setTimeout(() => this.#attemptFullSolveOnChange(), 75);
254
+ return;
255
+ }
256
+ this._pendingFullSolve = false;
257
+ try {
258
+ const promise = this._handleStartClick();
259
+ if (promise?.catch) {
260
+ try { promise.catch(() => { }); } catch { /* ignore */ }
261
+ }
262
+ } catch (error) {
263
+ console.warn('[AssemblyConstraintsWidget] Auto solve failed:', error);
264
+ }
265
+ }
266
+
267
+ _scheduleSync() {
268
+ if (this._syncScheduled) return;
269
+ this._syncScheduled = true;
270
+ const doSync = () => {
271
+ this._syncScheduled = false;
272
+ try {
273
+ this._syncNow();
274
+ } catch (error) {
275
+ console.warn('[AssemblyConstraintsWidget] Sync failed:', error);
276
+ }
277
+ };
278
+ if (typeof requestAnimationFrame === 'function') {
279
+ requestAnimationFrame(() => {
280
+ if (typeof queueMicrotask === 'function') queueMicrotask(doSync);
281
+ else Promise.resolve().then(doSync);
282
+ });
283
+ } else {
284
+ setTimeout(doSync, 0);
285
+ }
286
+ }
287
+
288
+ #requestViewerRender() {
289
+ try { this.viewer?.render?.(); } catch { /* ignore */ }
290
+ }
291
+
292
+ _syncNow() {
293
+ this._refreshUpdateComponentsButton();
294
+ this._clearHighlights();
295
+ const entries = this.history?.list?.();
296
+ const list = Array.isArray(entries) ? entries : [];
297
+ this._updateConstraintVisuals(list);
298
+ this.#requestViewerRender();
299
+ }
300
+ async _handleStartClick() {
301
+ const iterations = this.#normalizeIterationInputValue();
302
+ if (iterations < 1) return;
303
+
304
+ await this._stopSolver({ wait: true });
305
+ this._startSolver(iterations);
306
+ }
307
+
308
+ _startSolver(iterations) {
309
+ if (!this.history) return;
310
+ this.#clearConstraintDebugArrows();
311
+ this._clearNormalArrows();
312
+
313
+ const abortController = new AbortController();
314
+ const run = {
315
+ abortController,
316
+ running: true,
317
+ iterations,
318
+ maxIterations: iterations,
319
+ currentIteration: 0,
320
+ iterationsCompleted: 0,
321
+ currentConstraintID: null,
322
+ awaitingContinue: false,
323
+ continueDeferred: null,
324
+ aborted: false,
325
+ promise: null,
326
+ iterationDelayMs: 0,
327
+ labelRefreshCounter: 0,
328
+ };
329
+
330
+ this._solverRun = run;
331
+ this._updateSolverUI();
332
+
333
+ const hooks = {
334
+ onStart: ({ maxIterations: total }) => {
335
+ if (this._solverRun !== run) return;
336
+ if (Number.isFinite(total)) run.maxIterations = total;
337
+ this._updateSolverUI();
338
+ },
339
+ onIterationStart: ({ iteration, maxIterations: total }) => {
340
+ if (this._solverRun !== run) return;
341
+ run.currentIteration = Number.isFinite(iteration) ? iteration : 0;
342
+ if (Number.isFinite(total)) run.maxIterations = total;
343
+ run.currentConstraintID = null;
344
+ this._updateSolverUI();
345
+ },
346
+ onConstraintStart: ({ id, constraintID }) => {
347
+ if (this._solverRun !== run) return;
348
+ run.currentConstraintID = id || constraintID || null;
349
+ this._updateSolverUI();
350
+ },
351
+ onIterationComplete: async ({ iteration }) => {
352
+ if (this._solverRun !== run) return;
353
+ run.iterationsCompleted = Number.isFinite(iteration) ? iteration + 1 : run.iterationsCompleted;
354
+ this._updateSolverUI();
355
+ this.#maybeRefreshLabelsDuringSolve(run);
356
+ if (this._shouldPauseBetweenLoops() && !run.abortController.signal.aborted) {
357
+ await this.#waitForIterationContinue(run);
358
+ }
359
+ },
360
+ onComplete: ({ aborted, iterationsCompleted }) => {
361
+ if (this._solverRun !== run) return;
362
+ run.aborted = !!aborted;
363
+ if (Number.isFinite(iterationsCompleted)) {
364
+ run.iterationsCompleted = iterationsCompleted;
365
+ }
366
+ this._updateSolverUI();
367
+ },
368
+ };
369
+
370
+ const iterationDelayMs = this.#computeIterationDelay();
371
+ run.iterationDelayMs = Number.isFinite(iterationDelayMs) ? Math.max(0, iterationDelayMs) : 0;
372
+
373
+ this._ignoreFullSolveChangeCount += 1;
374
+
375
+ run.promise = (async () => {
376
+ try {
377
+ await this.history.runAll(this.partHistory, {
378
+ iterations,
379
+ viewer: this.viewer || null,
380
+ debugMode: !!this._debugMode,
381
+ iterationDelayMs,
382
+ controller: {
383
+ signal: abortController.signal,
384
+ hooks,
385
+ },
386
+ });
387
+ } catch (error) {
388
+ console.warn('[AssemblyConstraintsWidget] Solve failed:', error);
389
+ } finally {
390
+ if (this._solverRun === run) {
391
+ this._solverRun = null;
392
+ }
393
+ run.running = false;
394
+ this._resolveIterationGate(run);
395
+ this._updateSolverUI();
396
+ if (this._ignoreFullSolveChangeCount > 0) {
397
+ this._ignoreFullSolveChangeCount -= 1;
398
+ }
399
+ }
400
+ })();
401
+ }
402
+
403
+ async _stopSolver({ wait = false } = {}) {
404
+ const run = this._solverRun;
405
+ if (!run) return;
406
+ try {
407
+ if (!run.abortController.signal.aborted) {
408
+ run.abortController.abort();
409
+ }
410
+ } catch { /* ignore */ }
411
+ this._resolveIterationGate(run);
412
+ this._updateSolverUI();
413
+ if (wait && run.promise) {
414
+ try { await run.promise; }
415
+ catch { /* ignore */ }
416
+ }
417
+ }
418
+
419
+ _resolveIterationGate(run = this._solverRun) {
420
+ if (!run || !run.continueDeferred) return;
421
+ const { resolve } = run.continueDeferred;
422
+ run.continueDeferred = null;
423
+ run.awaitingContinue = false;
424
+ if (typeof resolve === 'function') {
425
+ try { resolve(); } catch { /* ignore */ }
426
+ }
427
+ }
428
+
429
+ async #waitForIterationContinue(run) {
430
+ if (!run) return;
431
+ if (run.continueDeferred) {
432
+ try { await run.continueDeferred.promise; }
433
+ catch { /* ignore */ }
434
+ return;
435
+ }
436
+ run.awaitingContinue = true;
437
+ this._updateSolverUI();
438
+ run.continueDeferred = {};
439
+ run.continueDeferred.promise = new Promise((resolve) => {
440
+ run.continueDeferred.resolve = () => {
441
+ run.awaitingContinue = false;
442
+ run.continueDeferred = null;
443
+ resolve();
444
+ };
445
+ });
446
+ try {
447
+ await run.continueDeferred.promise;
448
+ } catch { /* ignore */ }
449
+ }
450
+
451
+ _handlePauseCheckboxChange() {
452
+ if (!this._shouldPauseBetweenLoops()) {
453
+ this._resolveIterationGate();
454
+ }
455
+ this._updateSolverUI();
456
+ }
457
+
458
+ _shouldPauseBetweenLoops() {
459
+ return this._pauseCheckbox?.checked === true;
460
+ }
461
+
462
+ _handleContinueClick() {
463
+ this._resolveIterationGate();
464
+ this._updateSolverUI();
465
+ }
466
+
467
+ #normalizeIterationInputValue() {
468
+ let iterations = Number(this._iterationInput?.value ?? this._defaultIterations ?? 1);
469
+ if (!Number.isFinite(iterations) || iterations < 1) iterations = 1;
470
+ iterations = Math.floor(iterations);
471
+ if (this._iterationInput) this._iterationInput.value = String(iterations);
472
+ return iterations;
473
+ }
474
+
475
+ #getIterationInputValue() {
476
+ const raw = Number(this._iterationInput?.value);
477
+ if (Number.isFinite(raw) && raw >= 1) return Math.floor(raw);
478
+ const fallback = Number(this._defaultIterations);
479
+ if (Number.isFinite(fallback) && fallback >= 1) return Math.floor(fallback);
480
+ return 1;
481
+ }
482
+
483
+ #normalizeAnimateDelayValue() {
484
+ let delay = Number(this._animateDelayInput?.value ?? this._animateDelayMs);
485
+ if (!Number.isFinite(delay) || delay < 0) delay = this._animateDelayMs;
486
+ delay = Math.max(0, Math.floor(delay));
487
+ if (this._animateDelayInput) this._animateDelayInput.value = String(delay);
488
+ this._animateDelayMs = delay;
489
+ return delay;
490
+ }
491
+
492
+ #computeIterationDelay() {
493
+ const animate = this._animateCheckbox ? this._animateCheckbox.checked : this._animateEnabled;
494
+ if (!animate) return 0;
495
+ return this.#normalizeAnimateDelayValue();
496
+ }
497
+
498
+ _handleFullSolveToggleChange(checked) {
499
+ const value = !!checked;
500
+ this._fullSolveOnChange = value;
501
+ if (this._fullSolveCheckbox) {
502
+ this._fullSolveCheckbox.checked = value;
503
+ }
504
+ if (!value) {
505
+ this._pendingFullSolve = false;
506
+ return;
507
+ }
508
+ this.#scheduleFullSolveOnChange();
509
+ }
510
+
511
+ _handleAnimateCheckboxChange() {
512
+ this._animateEnabled = this._animateCheckbox?.checked !== false;
513
+ this._updateSolverUI();
514
+ }
515
+
516
+ _handleAnimateDelayChange() {
517
+ this.#normalizeAnimateDelayValue();
518
+ this._updateSolverUI();
519
+ }
520
+
521
+ #maybeRefreshLabelsDuringSolve(run) {
522
+ if (!run || run.iterationDelayMs <= 0) return;
523
+ run.labelRefreshCounter = (run.labelRefreshCounter || 0) + 1;
524
+ if (run.labelRefreshCounter < 50) return;
525
+ run.labelRefreshCounter = 0;
526
+ const entries = typeof this.history?.list === 'function' ? (this.history.list() || []) : [];
527
+ this._updateConstraintVisuals(entries);
528
+ }
529
+
530
+ _handleDebugCheckboxChange(checked) {
531
+ this._debugMode = !!checked;
532
+ this.#clearConstraintDebugArrows();
533
+ if (!this._debugMode) {
534
+ this._clearNormalArrows();
535
+ }
536
+ this.#requestViewerRender();
537
+ }
538
+
539
+ _updateSolverUI() {
540
+ const run = this._solverRun;
541
+ const running = !!run?.running && !run?.abortController?.signal?.aborted;
542
+ const stopping = !!run?.abortController?.signal?.aborted && !!run?.running;
543
+ const awaitingContinue = !!run?.awaitingContinue;
544
+
545
+ if (this._startButton) {
546
+ this._startButton.disabled = running || stopping;
547
+ }
548
+ if (this._stopButton) {
549
+ this._stopButton.disabled = !run || (!running && !stopping);
550
+ }
551
+ if (this._iterationInput) {
552
+ this._iterationInput.disabled = running || stopping;
553
+ }
554
+ if (this._animateCheckbox) {
555
+ this._animateCheckbox.disabled = running || stopping;
556
+ }
557
+ if (this._animateDelayInput) {
558
+ const animateChecked = this._animateCheckbox ? this._animateCheckbox.checked : this._animateEnabled;
559
+ this._animateDelayInput.disabled = !animateChecked || running || stopping;
560
+ }
561
+ if (this._animateDelayContainer) {
562
+ const animateChecked = this._animateCheckbox ? this._animateCheckbox.checked : this._animateEnabled;
563
+ this._animateDelayContainer.style.display = animateChecked ? '' : 'none';
564
+ }
565
+
566
+ if (this._solverStatusLabel) {
567
+ let text = 'Status: Idle';
568
+ if (stopping) text = 'Status: Stopping';
569
+ else if (awaitingContinue) text = 'Status: Paused';
570
+ else if (running) text = 'Status: Running';
571
+ else if (run) {
572
+ text = run.aborted ? 'Status: Stopped' : 'Status: Completed';
573
+ }
574
+ this._solverStatusLabel.textContent = text;
575
+ this._solverStatusLabel.dataset.state = text.split(':')[1]?.trim()?.toLowerCase() || 'idle';
576
+ }
577
+
578
+ if (this._solverLoopLabel) {
579
+ if (run) {
580
+ const max = Number.isFinite(run.maxIterations)
581
+ ? run.maxIterations
582
+ : Number.isFinite(run.iterations)
583
+ ? run.iterations
584
+ : this.#getIterationInputValue();
585
+ const current = awaitingContinue
586
+ ? run.iterationsCompleted
587
+ : Number.isFinite(run.currentIteration) ? run.currentIteration + 1 : run.iterationsCompleted;
588
+ const displayCurrent = Number.isFinite(current) && current > 0 ? current : 0;
589
+ if (Number.isFinite(max) && max > 0) {
590
+ this._solverLoopLabel.textContent = `Loop: ${Math.min(displayCurrent, max)}/${max}`;
591
+ } else if (displayCurrent > 0) {
592
+ this._solverLoopLabel.textContent = `Loop: ${displayCurrent}`;
593
+ } else {
594
+ this._solverLoopLabel.textContent = 'Loop: -';
595
+ }
596
+ } else {
597
+ this._solverLoopLabel.textContent = 'Loop: -';
598
+ }
599
+ }
600
+
601
+ if (this._solverConstraintLabel) {
602
+ if (run && run.currentConstraintID) {
603
+ this._solverConstraintLabel.textContent = `Constraint: ${run.currentConstraintID}`;
604
+ } else {
605
+ this._solverConstraintLabel.textContent = 'Constraint: -';
606
+ }
607
+ }
608
+
609
+ if (this._solverContinueButton) {
610
+ const pauseEnabled = this._shouldPauseBetweenLoops();
611
+ this._solverContinueButton.style.display = pauseEnabled ? '' : 'none';
612
+ this._solverContinueButton.disabled = !awaitingContinue;
613
+ }
614
+ }
615
+
616
+
617
+ _setConstraintGraphicsEnabled(enabled, options = {}) {
618
+ const requested = !!enabled;
619
+ if (options.recordPreference !== false) {
620
+ this._constraintGraphicsPreferred = requested;
621
+ }
622
+ const suppressed = this._pmiVisibilityLock > 0 && !options.force;
623
+ const value = suppressed ? false : requested;
624
+ this._constraintGraphicsEnabled = value;
625
+
626
+ if (this._constraintGroup) {
627
+ this._constraintGroup.visible = value;
628
+ }
629
+
630
+ if (typeof this._labelOverlay?.setVisible === 'function') {
631
+ this._labelOverlay.setVisible(value);
632
+ }
633
+
634
+ if (this._constraintGraphicsCheckbox && this._constraintGraphicsCheckbox.checked !== value) {
635
+ this._constraintGraphicsCheckbox.checked = value;
636
+ }
637
+
638
+ if (value) {
639
+ this._refreshConstraintLabels();
640
+ } else {
641
+ this.#clearActiveHoverHighlight();
642
+ }
643
+
644
+ this.#requestViewerRender();
645
+ }
646
+
647
+ onPMIModeEnter() {
648
+ this._pmiVisibilityLock = (this._pmiVisibilityLock || 0) + 1;
649
+ this._setConstraintGraphicsEnabled(this._constraintGraphicsPreferred, { recordPreference: false });
650
+ }
651
+
652
+ onPMIModeExit() {
653
+ if (!this._pmiVisibilityLock) return;
654
+ this._pmiVisibilityLock = Math.max(0, this._pmiVisibilityLock - 1);
655
+ if (this._pmiVisibilityLock > 0) return;
656
+ this._setConstraintGraphicsEnabled(this._constraintGraphicsPreferred, {
657
+ recordPreference: false,
658
+ force: true,
659
+ });
660
+ }
661
+
662
+ _clearHoverHighlights() {
663
+ restoreHighlightRecords(this._hoverHighlights);
664
+ }
665
+
666
+ _clearHighlights() {
667
+ this.#clearActiveHoverHighlight();
668
+ restoreHighlightRecords(this._highlighted);
669
+ this._clearNormalArrows();
670
+ try { SelectionFilter.clearHover?.(); } catch { /* ignore */ }
671
+ this.#requestViewerRender();
672
+ }
673
+
674
+ _applyConstraintHighlight(entry, ConstraintClass, options = {}) {
675
+ if (!entry?.inputParams) return false;
676
+
677
+ const store = options.store ?? this._highlighted;
678
+ const clearExisting = options.clearExisting !== false;
679
+ const includeNormals = options.includeNormals ?? (store === this._highlighted);
680
+ const skipSets = Array.isArray(options.skipSets) && options.skipSets.length
681
+ ? options.skipSets
682
+ : [store];
683
+ const emitWarnings = options.emitWarnings || false;
684
+
685
+ const useHoverStore = store === this._hoverHighlights;
686
+
687
+ if (useHoverStore) {
688
+ this._clearHoverHighlights();
689
+ if (clearExisting && store !== this._highlighted) {
690
+ restoreHighlightRecords(this._highlighted);
691
+ this._clearNormalArrows();
692
+ }
693
+ } else if (clearExisting) {
694
+ this._clearHighlights();
695
+ }
696
+
697
+ const schema = ConstraintClass?.inputParamsSchema || {};
698
+ const refFields = Object.entries(schema).filter(([, def]) => def?.type === 'reference_selection');
699
+ if (!refFields.length) return false;
700
+
701
+ const constraintId = resolveConstraintId(entry) || 'constraint';
702
+
703
+ let colorIndex = 0;
704
+ let foundTargets = false;
705
+ let sawFace = false;
706
+ let arrowsCreated = false;
707
+ for (const [key] of refFields) {
708
+ const color = this._highlightPalette[colorIndex % this._highlightPalette.length];
709
+ colorIndex += 1;
710
+ const targets = this._resolveReferenceObjects(entry.inputParams[key]);
711
+ if (!targets || targets.length === 0) continue;
712
+ foundTargets = true;
713
+ for (const obj of targets) {
714
+ const changed = applyHighlightMaterial(obj, color, store, skipSets);
715
+ if (changed && includeNormals && isFaceObject(obj)) {
716
+ sawFace = true;
717
+ const arrow = this._createNormalArrow(obj, color, `${constraintId}:${key}`);
718
+ if (arrow) arrowsCreated = true;
719
+ }
720
+ }
721
+ }
722
+
723
+ if (emitWarnings && !foundTargets) {
724
+ console.warn('[AssemblyConstraintsWidget] No reference objects could be highlighted for constraint:', constraintId);
725
+ }
726
+
727
+ if (emitWarnings && includeNormals && sawFace && !arrowsCreated) {
728
+ console.warn('[AssemblyConstraintsWidget] No face normals could be visualized for constraint:', constraintId);
729
+ }
730
+
731
+ this.#requestViewerRender();
732
+ return foundTargets;
733
+ }
734
+
735
+ _highlightConstraint(entry, ConstraintClass) {
736
+ this._applyConstraintHighlight(entry, ConstraintClass, {
737
+ store: this._highlighted,
738
+ clearExisting: true,
739
+ includeNormals: true,
740
+ skipSets: [this._highlighted],
741
+ emitWarnings: true,
742
+ });
743
+ }
744
+
745
+ #attachLabelHoverHandlers(element, entry, constraintID) {
746
+ if (!element) return;
747
+ const prev = element.__constraintHoverHandlers;
748
+ if (prev) {
749
+ element.removeEventListener('mouseenter', prev.enter);
750
+ element.removeEventListener('mouseleave', prev.leave);
751
+ }
752
+ if (!entry) {
753
+ element.__constraintHoverHandlers = null;
754
+ return;
755
+ }
756
+ const onEnter = () => {
757
+ try { this.#handleConstraintLabelHover(entry, constraintID); } catch { }
758
+ };
759
+ const onLeave = () => {
760
+ try { this.#handleConstraintLabelHoverEnd(constraintID); } catch { }
761
+ };
762
+ element.addEventListener('mouseenter', onEnter);
763
+ element.addEventListener('mouseleave', onLeave);
764
+ element.__constraintHoverHandlers = { enter: onEnter, leave: onLeave };
765
+ }
766
+
767
+ #handleConstraintLabelHover(entry, constraintID) {
768
+ if (!this._constraintGraphicsEnabled) return;
769
+ if (this._activeHoverConstraintId && this._activeHoverConstraintId !== constraintID) {
770
+ this.#clearActiveHoverHighlight();
771
+ }
772
+
773
+ const ConstraintClass = this._resolveConstraintClass(entry);
774
+
775
+ this._activeHoverConstraintId = constraintID;
776
+ this._applyConstraintHighlight(entry, ConstraintClass, {
777
+ store: this._hoverHighlights,
778
+ clearExisting: false,
779
+ includeNormals: false,
780
+ skipSets: [this._highlighted, this._hoverHighlights],
781
+ emitWarnings: false,
782
+ });
783
+ this.#setConstraintLineHighlight(constraintID, true);
784
+ this.#requestViewerRender();
785
+ }
786
+
787
+ #handleConstraintLabelHoverEnd(constraintID) {
788
+ if (!constraintID) return;
789
+ if (this._activeHoverConstraintId !== constraintID) {
790
+ this.#setConstraintLineHighlight(constraintID, false);
791
+ this.#requestViewerRender();
792
+ return;
793
+ }
794
+ this.#clearActiveHoverHighlight();
795
+ }
796
+
797
+ #clearActiveHoverHighlight() {
798
+ if (!this._activeHoverConstraintId) return;
799
+ const activeId = this._activeHoverConstraintId;
800
+ this._activeHoverConstraintId = null;
801
+ this._clearHoverHighlights();
802
+ this.#setConstraintLineHighlight(activeId, false);
803
+ this.#requestViewerRender();
804
+ }
805
+
806
+ #setConstraintLineHighlight(constraintID, active) {
807
+ const line = this._constraintLines.get(constraintID);
808
+ if (!line || !line.material) return;
809
+ const mat = line.material;
810
+ line.userData = line.userData || {};
811
+
812
+ if (active) {
813
+ if (!line.userData.__hoverOriginal) {
814
+ line.userData.__hoverOriginal = {
815
+ color: mat.color ? mat.color.clone() : null,
816
+ linewidth: mat.linewidth,
817
+ opacity: mat.opacity,
818
+ depthTest: mat.depthTest,
819
+ depthWrite: mat.depthWrite,
820
+ };
821
+ }
822
+ try { mat.color?.set('#ffffff'); } catch { }
823
+ try { mat.opacity = 1; } catch { }
824
+ try { mat.linewidth = 2; } catch { }
825
+ try { mat.depthTest = false; mat.depthWrite = false; } catch { }
826
+ line.renderOrder = 10050;
827
+ } else {
828
+ const original = line.userData.__hoverOriginal;
829
+ if (original) {
830
+ try {
831
+ if (original.color && mat.color) mat.color.copy(original.color);
832
+ if (original.opacity != null) mat.opacity = original.opacity;
833
+ if (original.linewidth != null) mat.linewidth = original.linewidth;
834
+ if (original.depthTest != null) mat.depthTest = original.depthTest;
835
+ if (original.depthWrite != null) mat.depthWrite = original.depthWrite;
836
+ } catch { }
837
+ }
838
+ delete line.userData.__hoverOriginal;
839
+ line.renderOrder = 9999;
840
+ }
841
+
842
+ try { mat.needsUpdate = true; } catch { }
843
+ }
844
+
845
+ _updateConstraintVisuals(entries = []) {
846
+ const scene = this.viewer?.scene || null;
847
+ if (!scene) return;
848
+
849
+ this.#clearActiveHoverHighlight();
850
+
851
+ const activeIds = new Set();
852
+ this._labelPositions.clear();
853
+ if (this._labelOverlay) {
854
+ try { this._labelOverlay.clear(); } catch { }
855
+ }
856
+
857
+ if (!entries || entries.length === 0) {
858
+ this._removeUnusedConstraintLines(activeIds);
859
+ this._refreshConstraintLabels();
860
+ return;
861
+ }
862
+
863
+ entries.forEach((entry, index) => {
864
+ if (!entry) return;
865
+ const constraintID = resolveConstraintId(entry) || `constraint-${index}`;
866
+ const constraintClass = this._resolveConstraintClass(entry);
867
+ const statusInfo = constraintStatusInfo(entry);
868
+ const color = statusInfo?.color || DEFAULT_CONSTRAINT_COLOR;
869
+ const segments = this.#constraintSegments(entry);
870
+ let labelPosition = null;
871
+
872
+ if (Array.isArray(segments) && segments.length > 0) {
873
+ this.#upsertConstraintLines(constraintID, segments, color);
874
+ const midpoint = new THREE.Vector3();
875
+ let midpointCount = 0;
876
+ for (const [start, end] of segments) {
877
+ if (!start || !end) continue;
878
+ midpoint.add(start.clone().add(end).multiplyScalar(0.5));
879
+ midpointCount += 1;
880
+ }
881
+ if (midpointCount > 0) {
882
+ midpoint.divideScalar(midpointCount);
883
+ labelPosition = midpoint;
884
+ } else {
885
+ labelPosition = this.#constraintStandalonePosition(entry, constraintClass);
886
+ }
887
+ } else {
888
+ this.#removeConstraintLine(constraintID);
889
+ labelPosition = this.#constraintStandalonePosition(entry, constraintClass);
890
+ }
891
+
892
+ if (!labelPosition) return;
893
+
894
+ const text = constraintLabelText(entry, constraintClass, this.partHistory);
895
+ const overlayData = { id: constraintID, constraintID };
896
+
897
+ if (this._constraintGraphicsEnabled) {
898
+ try { this._labelOverlay?.updateLabel(constraintID, text, labelPosition.clone(), overlayData); } catch { }
899
+ const el = this._labelOverlay?.getElement?.(constraintID);
900
+ if (el) {
901
+ try {
902
+ el.classList.add('constraint-label');
903
+ el.dataset.constraintId = constraintID;
904
+ } catch { }
905
+ this.#applyConstraintLabelColor(el, color);
906
+ this.#attachLabelHoverHandlers(el, entry, constraintID);
907
+ }
908
+ }
909
+
910
+ this._labelPositions.set(constraintID, {
911
+ position: labelPosition.clone(),
912
+ text,
913
+ data: overlayData,
914
+ entry,
915
+ color,
916
+ });
917
+ activeIds.add(constraintID);
918
+ });
919
+
920
+ this._refreshConstraintLabels();
921
+ this.#requestViewerRender();
922
+ }
923
+
924
+ _refreshConstraintLabels() {
925
+ if (!this._constraintGraphicsEnabled) return;
926
+ if (!this._labelOverlay || !this._labelPositions.size) return;
927
+
928
+ const activeIds = new Set();
929
+
930
+ for (const [constraintID, record] of this._labelPositions.entries()) {
931
+ if (!record || !record.position) continue;
932
+
933
+ activeIds.add(constraintID);
934
+
935
+ let color = record.color || DEFAULT_CONSTRAINT_COLOR;
936
+
937
+ if (record.entry) {
938
+ const statusInfo = constraintStatusInfo(record.entry);
939
+ color = statusInfo?.color || color;
940
+ record.color = color;
941
+ const constraintClass = this._resolveConstraintClass(record.entry);
942
+ record.text = constraintLabelText(record.entry, constraintClass, this.partHistory);
943
+ const segments = this.#constraintSegments(record.entry);
944
+ if (Array.isArray(segments) && segments.length > 0) {
945
+ this.#upsertConstraintLines(constraintID, segments, color);
946
+ } else {
947
+ this.#removeConstraintLine(constraintID);
948
+ }
949
+ } else {
950
+ record.color = color;
951
+ }
952
+
953
+ try {
954
+ this._labelOverlay.updateLabel(constraintID, record.text, record.position.clone(), record.data);
955
+ const el = this._labelOverlay.getElement(constraintID);
956
+ if (el) {
957
+ el.classList.add('constraint-label');
958
+ el.dataset.constraintId = constraintID;
959
+ this.#applyConstraintLabelColor(el, color);
960
+ this.#attachLabelHoverHandlers(el, record.entry, constraintID);
961
+ }
962
+ } catch { }
963
+ }
964
+
965
+ this._removeUnusedConstraintLines(activeIds);
966
+ }
967
+
968
+ #applyConstraintLabelColor(element, color) {
969
+ if (!element) return;
970
+ const appliedColor = color || '';
971
+ try { element.style.borderColor = appliedColor; } catch { }
972
+ try { element.style.color = appliedColor || ''; } catch { }
973
+ }
974
+
975
+ _clearConstraintVisuals() {
976
+ this.#clearActiveHoverHighlight();
977
+ this._labelPositions.clear();
978
+ if (this._labelOverlay) {
979
+ try { this._labelOverlay.clear(); } catch { }
980
+ }
981
+ for (const constraintID of Array.from(this._constraintLines.keys())) {
982
+ this.#removeConstraintLine(constraintID);
983
+ }
984
+ }
985
+
986
+ _removeUnusedConstraintLines(activeIds) {
987
+ for (const constraintID of Array.from(this._constraintLines.keys())) {
988
+ if (!activeIds.has(constraintID)) {
989
+ this.#removeConstraintLine(constraintID);
990
+ }
991
+ }
992
+ }
993
+
994
+ _resolveReferenceObjects(value) {
995
+ const values = Array.isArray(value) ? value : (value ? [value] : []);
996
+ const results = [];
997
+ const scene = this.viewer?.scene || null;
998
+ for (const item of values) {
999
+ const resolved = resolveSelectionObject(scene, item);
1000
+ if (resolved) results.push(resolved);
1001
+ }
1002
+ return results;
1003
+ }
1004
+
1005
+ _clearNormalArrows() {
1006
+ const scene = this.viewer?.scene || null;
1007
+ if (!scene || !this._normalArrows) return;
1008
+ for (const arrow of this._normalArrows) {
1009
+ try { arrow?.parent?.remove?.(arrow); }
1010
+ catch { }
1011
+ }
1012
+ this._normalArrows.clear();
1013
+ }
1014
+
1015
+ #clearConstraintDebugArrows() {
1016
+ const scene = this.viewer?.scene || null;
1017
+ if (!scene || typeof scene.traverse !== 'function') return;
1018
+ const prefixes = [
1019
+ 'parallel-constraint-normal-',
1020
+ 'distance-constraint-normal-',
1021
+ 'touch-align-normal-',
1022
+ ];
1023
+ const toRemove = [];
1024
+ scene.traverse((obj) => {
1025
+ if (!obj || typeof obj.name !== 'string') return;
1026
+ if (prefixes.some((prefix) => obj.name.startsWith(prefix))) {
1027
+ toRemove.push(obj);
1028
+ }
1029
+ });
1030
+ for (const obj of toRemove) {
1031
+ try { obj.parent?.remove?.(obj); }
1032
+ catch { }
1033
+ }
1034
+ }
1035
+
1036
+ #constraintSegments(entry) {
1037
+ if (!entry?.inputParams) return [];
1038
+ const refPoints = this.#collectReferenceSelectionPoints(entry, { limit: Infinity });
1039
+ if (refPoints.length < 2) return [];
1040
+
1041
+ if (refPoints.length === 2) {
1042
+ return [[refPoints[0], refPoints[1]]];
1043
+ }
1044
+
1045
+ const anchor = refPoints[0];
1046
+ const segments = [];
1047
+ for (let i = 1; i < refPoints.length; i += 1) {
1048
+ const point = refPoints[i];
1049
+ if (!point) continue;
1050
+ segments.push([anchor.clone(), point.clone()]);
1051
+ }
1052
+ return segments;
1053
+ }
1054
+
1055
+ #collectReferenceSelectionPoints(entry, { limit = Infinity } = {}) {
1056
+ const cls = this._resolveConstraintClass(entry);
1057
+ const schema = cls?.inputParamsSchema || {};
1058
+ const refKeys = Object.entries(schema)
1059
+ .filter(([, def]) => def?.type === 'reference_selection')
1060
+ .map(([key]) => key);
1061
+ if (!refKeys.length) return [];
1062
+
1063
+ const points = [];
1064
+ const pushSelection = (value) => {
1065
+ if (!value || points.length >= limit) return;
1066
+ if (Array.isArray(value)) {
1067
+ for (const item of value) {
1068
+ pushSelection(item);
1069
+ if (points.length >= limit) break;
1070
+ }
1071
+ return;
1072
+ }
1073
+ const point = this.#resolveSelectionPoint(value);
1074
+ if (point) points.push(point);
1075
+ };
1076
+
1077
+ for (const key of refKeys) {
1078
+ pushSelection(entry.inputParams[key]);
1079
+ if (points.length >= limit) break;
1080
+ }
1081
+
1082
+ return points;
1083
+ }
1084
+
1085
+ #upsertConstraintLines(constraintID, segments, color = DEFAULT_CONSTRAINT_COLOR) {
1086
+ if (!this._constraintGroup || !Array.isArray(segments) || segments.length === 0) return;
1087
+ const lineColor = color || DEFAULT_CONSTRAINT_COLOR;
1088
+ if (!this._constraintGroup.parent) {
1089
+ try { this.viewer?.scene?.add(this._constraintGroup); } catch { }
1090
+ }
1091
+ let line = this._constraintLines.get(constraintID);
1092
+ if (!line) {
1093
+ const geometry = new THREE.BufferGeometry();
1094
+ const material = new THREE.LineBasicMaterial({
1095
+ color: new THREE.Color(lineColor).getHex(),
1096
+ linewidth: 1,
1097
+ transparent: true,
1098
+ opacity: 0.85,
1099
+ depthTest: false,
1100
+ depthWrite: false,
1101
+ });
1102
+ line = new THREE.Line(geometry, material);
1103
+ line.name = `constraint-line-${constraintID}`;
1104
+ line.renderOrder = 9999;
1105
+ line.userData.excludeFromFit = true;
1106
+ try { this._constraintGroup.add(line); } catch { }
1107
+ this._constraintLines.set(constraintID, line);
1108
+ } else if (line.material) {
1109
+ try { line.material.color?.set?.(lineColor); } catch { }
1110
+ try { line.material.needsUpdate = true; } catch { }
1111
+ }
1112
+ if (line) {
1113
+ line.userData = line.userData || {};
1114
+ line.userData.baseColor = lineColor;
1115
+ }
1116
+
1117
+ const vertexCount = segments.length * 2;
1118
+ const expectedArrayLength = vertexCount * 3;
1119
+ let attr = line.geometry.getAttribute('position');
1120
+ if (!attr || attr.count !== vertexCount) {
1121
+ const positions = new Float32Array(expectedArrayLength);
1122
+ line.geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
1123
+ attr = line.geometry.getAttribute('position');
1124
+ }
1125
+
1126
+ let writeIndex = 0;
1127
+ for (const [start, end] of segments) {
1128
+ if (!start || !end) continue;
1129
+ attr.setXYZ(writeIndex, start.x, start.y, start.z);
1130
+ attr.setXYZ(writeIndex + 1, end.x, end.y, end.z);
1131
+ writeIndex += 2;
1132
+ }
1133
+
1134
+ if (writeIndex < 2) {
1135
+ this.#removeConstraintLine(constraintID);
1136
+ return;
1137
+ }
1138
+
1139
+ attr.needsUpdate = true;
1140
+ line.geometry.setDrawRange(0, writeIndex);
1141
+ line.geometry.computeBoundingSphere?.();
1142
+ }
1143
+
1144
+ #removeConstraintLine(constraintID) {
1145
+ const line = this._constraintLines.get(constraintID);
1146
+ if (!line) return;
1147
+ try { line.parent?.remove(line); } catch { }
1148
+ try { line.geometry?.dispose?.(); } catch { }
1149
+ try { line.material?.dispose?.(); } catch { }
1150
+ this._constraintLines.delete(constraintID);
1151
+ }
1152
+
1153
+ #resolveSelectionPoint(selection) {
1154
+ if (!selection) return null;
1155
+ const candidates = this._resolveReferenceObjects(selection);
1156
+ const object = candidates?.find((obj) => obj) || null;
1157
+ if (object) {
1158
+ const point = extractWorldPoint(object);
1159
+ if (point) return point;
1160
+ }
1161
+ if (Array.isArray(selection)) {
1162
+ for (const item of selection) {
1163
+ const point = this.#resolveSelectionPoint(item);
1164
+ if (point) return point;
1165
+ }
1166
+ }
1167
+ if (selection && typeof selection === 'object') {
1168
+ if (Number.isFinite(selection.x) && Number.isFinite(selection.y) && Number.isFinite(selection.z)) {
1169
+ return new THREE.Vector3(selection.x, selection.y, selection.z);
1170
+ }
1171
+ if (Array.isArray(selection) && selection.length >= 3 && selection.every((v) => Number.isFinite(v))) {
1172
+ return new THREE.Vector3(selection[0], selection[1], selection[2]);
1173
+ }
1174
+ if (selection.point && typeof selection.point === 'object') {
1175
+ const p = selection.point;
1176
+ if (Array.isArray(p) && p.length >= 3 && p.every((v) => Number.isFinite(v))) {
1177
+ return new THREE.Vector3(p[0], p[1], p[2]);
1178
+ }
1179
+ if (Number.isFinite(p.x) && Number.isFinite(p.y) && Number.isFinite(p.z)) {
1180
+ return new THREE.Vector3(p.x, p.y, p.z);
1181
+ }
1182
+ }
1183
+ if (selection.origin && Number.isFinite(selection.origin.x)) {
1184
+ return new THREE.Vector3(selection.origin.x, selection.origin.y, selection.origin.z);
1185
+ }
1186
+ }
1187
+ return null;
1188
+ }
1189
+
1190
+ #constraintStandalonePosition(entry, cls) {
1191
+ if (!entry) return null;
1192
+ const constraintClass = cls || this._resolveConstraintClass(entry);
1193
+ const typeValue = entry?.type || constraintClass?.constraintType;
1194
+ const type = typeof typeValue === 'string' ? typeValue.toLowerCase() : String(typeValue || '').toLowerCase();
1195
+ if (type === 'fixed') {
1196
+ const center = this.#componentBoundingBoxCenter(entry?.inputParams?.component);
1197
+ if (center) return center;
1198
+ }
1199
+ const [firstPoint] = this.#collectReferenceSelectionPoints(entry, { limit: 1 });
1200
+ if (firstPoint) return firstPoint;
1201
+ return null;
1202
+ }
1203
+
1204
+ #componentBoundingBoxCenter(selection) {
1205
+ const objects = this._resolveReferenceObjects(selection);
1206
+ if (!objects || objects.length === 0) return null;
1207
+
1208
+ const totalBox = new THREE.Box3();
1209
+ const tmpBox = new THREE.Box3();
1210
+ let hasBox = false;
1211
+
1212
+ for (const obj of objects) {
1213
+ if (!obj) continue;
1214
+ try { obj.updateMatrixWorld?.(true); }
1215
+ catch { }
1216
+
1217
+ tmpBox.makeEmpty();
1218
+ tmpBox.setFromObject(obj);
1219
+
1220
+ const min = tmpBox.min;
1221
+ const max = tmpBox.max;
1222
+ const valid = Number.isFinite(min.x) && Number.isFinite(min.y) && Number.isFinite(min.z)
1223
+ && Number.isFinite(max.x) && Number.isFinite(max.y) && Number.isFinite(max.z)
1224
+ && !tmpBox.isEmpty();
1225
+ if (!valid) continue;
1226
+
1227
+ if (!hasBox) {
1228
+ totalBox.copy(tmpBox);
1229
+ hasBox = true;
1230
+ } else {
1231
+ totalBox.union(tmpBox);
1232
+ }
1233
+ }
1234
+
1235
+ if (hasBox) {
1236
+ const center = totalBox.getCenter(new THREE.Vector3());
1237
+ if (center && Number.isFinite(center.x) && Number.isFinite(center.y) && Number.isFinite(center.z)) {
1238
+ return center;
1239
+ }
1240
+ }
1241
+
1242
+ for (const obj of objects) {
1243
+ const fallback = extractWorldPoint(obj);
1244
+ if (fallback) return fallback;
1245
+ }
1246
+
1247
+ return null;
1248
+ }
1249
+
1250
+ #handleLabelClick(idx, _ann, ev) {
1251
+ if (idx == null) return;
1252
+ const id = String(idx);
1253
+ if (!id) return;
1254
+ if (ev) {
1255
+ try { ev.preventDefault(); } catch { }
1256
+ try { ev.stopPropagation(); } catch { }
1257
+ }
1258
+ let changed = false;
1259
+ if (typeof this.history?.setExclusiveOpen === 'function') {
1260
+ changed = this.history.setExclusiveOpen(id);
1261
+ }
1262
+ if (!changed) {
1263
+ const entries = this.history?.list?.() || [];
1264
+ for (const entry of entries) {
1265
+ const entryId = resolveConstraintId(entry);
1266
+ const shouldOpen = entryId === id;
1267
+ const current = entry?.__open !== false;
1268
+ if (current !== shouldOpen) {
1269
+ this.history?.setOpenState(entryId, shouldOpen);
1270
+ }
1271
+ }
1272
+ this.history?.setOpenState?.(id, true);
1273
+ }
1274
+
1275
+ this._constraintList?.focusEntryById?.(id);
1276
+ }
1277
+
1278
+ _createNormalArrow(object, color, label) {
1279
+ const scene = this.viewer?.scene || null;
1280
+ if (!scene || !object) return;
1281
+
1282
+ const origin = computeFaceOrigin(object);
1283
+ const normal = computeFaceNormal(object);
1284
+ if (!origin || !normal) return;
1285
+
1286
+ const hexColor = new THREE.Color(color).getHex();
1287
+ const length = estimateArrowLength(object);
1288
+ const arrow = new THREE.ArrowHelper(normal, origin, length, hexColor, length * 0.25, length * 0.15);
1289
+ arrow.name = `selection-normal-${object.uuid}-${label || 'face'}`;
1290
+ scene.add(arrow);
1291
+ this._normalArrows.add(arrow);
1292
+ return arrow;
1293
+ }
1294
+
1295
+ _buildUpdateComponentsButton() {
1296
+ const btn = document.createElement('button');
1297
+ btn.type = 'button';
1298
+ btn.className = 'update-components-btn';
1299
+ btn.title = 'Update assembly components';
1300
+ btn.textContent = 'Update components';
1301
+ btn.addEventListener('click', async (ev) => {
1302
+ ev.stopPropagation();
1303
+ await this._handleUpdateComponents();
1304
+ });
1305
+ return btn;
1306
+ }
1307
+
1308
+ _refreshUpdateComponentsButton() {
1309
+ const btn = this._updateComponentsBtn;
1310
+ if (!btn) return;
1311
+
1312
+ let count = 0;
1313
+ try {
1314
+ if (this.partHistory && typeof this.partHistory.getOutdatedAssemblyComponentCount === 'function') {
1315
+ count = Number(this.partHistory.getOutdatedAssemblyComponentCount()) || 0;
1316
+ }
1317
+ } catch {
1318
+ count = 0;
1319
+ }
1320
+
1321
+ const busy = this._updatingComponents;
1322
+ const baseLabel = 'Update components';
1323
+ const hasOutdated = count > 0;
1324
+ btn.disabled = busy || !hasOutdated;
1325
+ btn.classList.toggle('needs-update', hasOutdated && !busy);
1326
+ btn.textContent = hasOutdated ? `${baseLabel} (${count})` : baseLabel;
1327
+ btn.setAttribute('data-outdated-count', String(hasOutdated ? count : 0));
1328
+ if (busy) {
1329
+ btn.title = 'Updating assembly components…';
1330
+ } else if (hasOutdated) {
1331
+ btn.title = 'Update assembly components to pull the latest saved versions.';
1332
+ } else {
1333
+ btn.title = 'Assembly components are up to date.';
1334
+ }
1335
+ }
1336
+
1337
+ async _handleUpdateComponents() {
1338
+ if (this._updatingComponents) return;
1339
+ if (!this.partHistory || typeof this.partHistory.updateAssemblyComponents !== 'function') return;
1340
+
1341
+ this._updatingComponents = true;
1342
+ this._refreshUpdateComponentsButton();
1343
+
1344
+ try {
1345
+ const result = await this.partHistory.updateAssemblyComponents({ rerun: true });
1346
+ if (result?.updatedCount > 0 || result?.reran) {
1347
+ this._scheduleSync();
1348
+ }
1349
+ } catch (error) {
1350
+ console.warn('[AssemblyConstraintsWidget] Failed to update components:', error);
1351
+ } finally {
1352
+ this._updatingComponents = false;
1353
+ this._refreshUpdateComponentsButton();
1354
+ }
1355
+ }
1356
+
1357
+ _resolveConstraintClass(entry) {
1358
+ if (!entry) return null;
1359
+ if (entry.constraintClass) return entry.constraintClass;
1360
+ const type = entry.type || entry.inputParams?.type;
1361
+ if (!type) return null;
1362
+ if (this.registry && typeof this.registry.getSafe === 'function') {
1363
+ const cls = this.registry.getSafe(type);
1364
+ if (cls) return cls;
1365
+ }
1366
+ if (this.registry && typeof this.registry.get === 'function') {
1367
+ try { return this.registry.get(type); }
1368
+ catch { return null; }
1369
+ }
1370
+ return null;
1371
+ }
1372
+
1373
+ }