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,800 @@
1
+ "use strict";
2
+
3
+ import { constraints } from "./constraintDefinitions.js";
4
+ import { distance, calculateAngle } from "./mathHelpersMod.js";
5
+
6
+ // === Constraint function table ===
7
+ const constraintFunctions = constraints.constraintFunctions;
8
+
9
+ // === Engine that performs numeric solving on a sketch snapshot ===
10
+ class ConstraintEngine {
11
+ constructor(sketchJSON) {
12
+ const sketch = JSON.parse(sketchJSON);
13
+ this.points = sketch.points.map(p => new Point(p.id, p.x, p.y, p.fixed));
14
+ this.geometries = sketch.geometries || [];
15
+ this.constraints = sketch.constraints || [];
16
+ }
17
+
18
+ processConstraintsOfType(type) {
19
+ const list = (type === "all")
20
+ ? this.constraints
21
+ : this.constraints.filter(c => c.type === type);
22
+
23
+ for (const constraint of list) {
24
+ constraint.status = "";
25
+ const constraintValue = parseFloat(constraint.value);
26
+ const points = constraint.points.map(id => this.points.find(p => p.id === id));
27
+ const before = JSON.stringify(points);
28
+
29
+ if (constraint.previousPointValues !== undefined &&
30
+ constraint.previousPointValues === before &&
31
+ constraint.status === "solved") continue;
32
+
33
+ try {
34
+ constraintFunctions[constraint.type](this, constraint, points, constraintValue);
35
+ } catch (e) {
36
+ // Keep solving other constraints; record the error on this constraint
37
+ constraint.error = e?.message || String(e);
38
+ }
39
+
40
+ const after = JSON.stringify(points);
41
+ if (before === after) {
42
+ constraint.status = "solved";
43
+ constraint.previousPointValues = after;
44
+ }
45
+ }
46
+ }
47
+
48
+ async tidyDecimalsOfPoints(decimalsPlaces = 4, resetFixed = true) {
49
+ for (const p of this.points) {
50
+ if (resetFixed) p.fixed = false;
51
+
52
+ if (typeof p.x === "string") p.x = parseFloat(p.x);
53
+ if (typeof p.y === "string") p.y = parseFloat(p.y);
54
+
55
+ if (p.x === null || p.x === undefined || Number.isNaN(p.x)) p.x = 0;
56
+ if (p.y === null || p.y === undefined || Number.isNaN(p.y)) p.y = 0;
57
+
58
+ const k = Math.pow(10, decimalsPlaces);
59
+ p.x = Math.round(p.x * k) / k;
60
+ p.y = Math.round(p.y * k) / k;
61
+ }
62
+ }
63
+
64
+ solve(iterations = 100) {
65
+ const decimalsPlaces = 6;
66
+
67
+ // Implied constraints for certain geometry types (e.g., arcs, bezier splines)
68
+ let nextTempId =
69
+ Math.max(0, ...this.constraints.map(c => Number.isFinite(+c.id) ? +c.id : 0)) + 1;
70
+ const pushTemp = (type, points) => {
71
+ this.constraints.push({
72
+ id: nextTempId++,
73
+ type,
74
+ points,
75
+ temporary: true,
76
+ labelX: 0,
77
+ labelY: 0
78
+ });
79
+ };
80
+
81
+ this.geometries.forEach(g => {
82
+ if (g.type === "arc") {
83
+ // Insert a temporary equal-chord constraint between (0-1) and (0-2)
84
+ pushTemp("⇌", [g.points[0], g.points[1], g.points[0], g.points[2]]);
85
+ } else if (g.type === "bezier" && Array.isArray(g.points) && g.points.length >= 4) {
86
+ // Keep internal spline anchors colinear with their adjacent control points
87
+ const ids = g.points || [];
88
+ const segCount = Math.floor((ids.length - 1) / 3);
89
+ const lastAnchorIndex = segCount * 3;
90
+ for (let i = 3; i < lastAnchorIndex; i += 3) {
91
+ const prevHandle = ids[i - 1];
92
+ const anchor = ids[i];
93
+ const nextHandle = ids[i + 1];
94
+ if (prevHandle == null || anchor == null || nextHandle == null) continue;
95
+ if (prevHandle === anchor || nextHandle === anchor || prevHandle === nextHandle) continue;
96
+ pushTemp("⏛", [prevHandle, nextHandle, anchor]);
97
+ }
98
+ }
99
+ });
100
+
101
+ this.tidyDecimalsOfPoints(decimalsPlaces, true);
102
+
103
+ // Ground first, then everything
104
+ this.processConstraintsOfType("⏚");
105
+ this.processConstraintsOfType("all");
106
+
107
+ const order = [
108
+ "⏛", "━", "│", "⋯",
109
+ "⟺", "⇌", "∠", "⟂", "∥",
110
+ "⇌", "⟺", "⇌", "⟺", "⏛", "━", "│", // repeated passes for convergence
111
+ ];
112
+
113
+ let prev = JSON.stringify(this.points);
114
+ let converged = false;
115
+
116
+ for (let i = 0; i < iterations; i++) {
117
+ for (const t of order) {
118
+ this.processConstraintsOfType(t);
119
+ this.processConstraintsOfType("≡"); // keep coincident snapping frequently
120
+ this.processConstraintsOfType("━");
121
+ this.processConstraintsOfType("|");
122
+ this.tidyDecimalsOfPoints(decimalsPlaces, false);
123
+ this.processConstraintsOfType("━");
124
+ this.processConstraintsOfType("|");
125
+ this.tidyDecimalsOfPoints(decimalsPlaces, false);
126
+ }
127
+
128
+ const cur = JSON.stringify(this.points);
129
+ if (cur === prev) {
130
+ converged = true;
131
+ break;
132
+ }
133
+ prev = cur;
134
+
135
+ // Movement throttling
136
+ const maxMove = 0.5;
137
+ for (let j = 0; j < this.points.length; j++) {
138
+ const p = this.points[j];
139
+ const last = JSON.parse(prev)[j];
140
+ const dx = p.x - last.x;
141
+ const dy = p.y - last.y;
142
+ const d = Math.hypot(dx, dy);
143
+ if (d > maxMove) {
144
+ const s = maxMove / d;
145
+ p.x = last.x + dx * s;
146
+ p.y = last.y + dy * s;
147
+ }
148
+ }
149
+ }
150
+
151
+ // Return a new sketch object mirroring input structure
152
+ const updatedSketch = {
153
+ points: this.points.map(p => ({ id: p.id, x: p.x, y: p.y, fixed: p.fixed })),
154
+ geometries: this.geometries,
155
+ constraints: this.constraints.filter(c => !c.temporary) // drop temporaries
156
+ };
157
+
158
+ return JSON.parse(JSON.stringify(updatedSketch));
159
+ }
160
+ }
161
+
162
+ class Point {
163
+ constructor(id, x, y, fixed = false) {
164
+ this.id = id;
165
+ this.x = x;
166
+ this.y = y;
167
+ this.fixed = fixed;
168
+ }
169
+ }
170
+
171
+ // === Public API class ===
172
+ export class ConstraintSolver {
173
+ /**
174
+ * @param {Object} opts
175
+ * @param {Object} [opts.sketch] initial sketch {points, geometries, constraints}
176
+ * @param {Function} [opts.notifyUser] (message, type) => void
177
+ * @param {Function} [opts.updateCanvas] () => void
178
+ * @param {Function} [opts.getSelectionItems] () => Array<{type:"point"|"geometry", id:number}>
179
+ * @param {Object} [opts.appState] external state to mirror mode/type/requiredSelections
180
+ */
181
+ constructor(opts = {}) {
182
+ this.hooks = {
183
+ notifyUser: typeof opts.notifyUser === "function" ? opts.notifyUser : (m) => { /* no-op in headless */ },
184
+ updateCanvas: typeof opts.updateCanvas === "function" ? opts.updateCanvas : () => { },
185
+ getSelectionItems: typeof opts.getSelectionItems === "function" ? opts.getSelectionItems : () => []
186
+ };
187
+
188
+ this.appState = opts.appState || { mode: "", type: "", requiredSelections: 0 };
189
+
190
+ this.sketchObject = opts.sketch ? sanitizeSketch(opts.sketch) : {
191
+ points: [{ id: 0, x: 0, y: 0, fixed: true }],
192
+ geometries: [],
193
+ constraints: [{ id: 0, type: "⏚", points: [0] }]
194
+ };
195
+
196
+ this._paused = false;
197
+ this._pauseReason = "";
198
+ }
199
+
200
+ // ---------- Solver control ----------
201
+ pause(reason = "") {
202
+ this._paused = true;
203
+ this._pauseReason = reason || "";
204
+ }
205
+
206
+ resume() {
207
+ this._paused = false;
208
+ this._pauseReason = "";
209
+ }
210
+
211
+ isPaused() { return !!this._paused; }
212
+
213
+ // ---------- Core solve ----------
214
+ solveSketch(iterations = null) {
215
+ if (this._paused) {
216
+ return this.sketchObject;
217
+ }
218
+
219
+ const iters = iterations === "full"
220
+ ? this.fullSolve()
221
+ : (iterations == null ? this.defaultLoops() : iterations);
222
+
223
+ const engine = new ConstraintEngine(JSON.stringify(this.sketchObject));
224
+ const solved = engine.solve(iters);
225
+ console.log(`Solver completed in ${iters} iterations.`);
226
+ console.log(solved.constraints);
227
+ this.sketchObject = solved;
228
+ return this.sketchObject;
229
+ }
230
+
231
+ defaultLoops() { return 1500; }
232
+ fullSolve() { return 2000; }
233
+
234
+ // ---------- Accessors ----------
235
+ getPointById(id) {
236
+ return this.sketchObject.points.find(p => p.id === parseInt(id));
237
+ }
238
+
239
+ // ---------- Edit operations (formerly exported functions) ----------
240
+ removePointById(id) {
241
+ id = parseInt(id);
242
+ if (id === 0) return;
243
+
244
+ this.sketchObject.points = this.sketchObject.points.filter(p => p.id !== id);
245
+
246
+ // Remove geometries referencing the point
247
+ this.sketchObject.geometries = this.sketchObject.geometries.filter(g => !g.points.includes(id));
248
+
249
+ // Remove constraints referencing the point
250
+ this.sketchObject.constraints = this.sketchObject.constraints.filter(c => !c.points.includes(id));
251
+ }
252
+
253
+ removeGeometryById(id) {
254
+ id = parseInt(id);
255
+ if (id === 0) return;
256
+
257
+ this.sketchObject.geometries = this.sketchObject.geometries.filter(g => parseInt(g.id) !== id);
258
+
259
+ // If any constraint stores geometryId, prune those as well
260
+ this.sketchObject.constraints = this.sketchObject.constraints.filter(c => c.geometryId !== id);
261
+ }
262
+
263
+ removeConstraintById(id) {
264
+ id = parseInt(id);
265
+ this.sketchObject.constraints = this.sketchObject.constraints.filter(c => parseInt(c.id) !== id);
266
+ }
267
+
268
+ toggleConstruction() {
269
+ const items = this.hooks.getSelectionItems();
270
+ if (!items || items.length === 0) return;
271
+
272
+ for (const item of items) {
273
+ if (item.type === "geometry") {
274
+ const g = this.sketchObject.geometries.find(x => x.id === parseInt(item.id));
275
+ if (!g) continue;
276
+ if (g.construction === undefined) g.construction = false;
277
+ g.construction = !g.construction;
278
+ }
279
+ }
280
+ this.hooks.updateCanvas(false);
281
+ }
282
+
283
+ geometryCreateLine() {
284
+ this.appState.mode = "createGeometry";
285
+ this.appState.type = "line";
286
+ this.appState.requiredSelections = 2;
287
+ this.createGeometry("line");
288
+ }
289
+
290
+ geometryCreateCircle() {
291
+ this.appState.mode = "createGeometry";
292
+ this.appState.type = "circle";
293
+ this.appState.requiredSelections = 2;
294
+ this.createGeometry("circle");
295
+ }
296
+
297
+ geometryCreateArc() {
298
+ this.appState.mode = "createGeometry";
299
+ this.appState.type = "arc";
300
+ this.appState.requiredSelections = 3;
301
+ this.createGeometry("arc");
302
+ }
303
+
304
+ // Create rectangle from two selected points (opposite corners in UV plane)
305
+ // Produces 4 line geometries, 4 coincident constraints (one per corner), and 3 perpendicular constraints
306
+ geometryCreateRectangle() {
307
+ this.appState.mode = "createGeometry";
308
+ this.appState.type = "rectangle";
309
+ this.appState.requiredSelections = 2;
310
+
311
+ // Collect two points from selection
312
+ const items = this.hooks.getSelectionItems();
313
+ const selPoints = [];
314
+ for (const it of items || []) {
315
+ if (it.type === "point") {
316
+ const p = this.sketchObject.points.find(x => x.id === parseInt(it.id));
317
+ if (p) selPoints.push(p);
318
+ }
319
+ }
320
+ if (selPoints.length !== 2) return false;
321
+
322
+ const pA = selPoints[0]; // one corner
323
+ const pC = selPoints[1]; // opposite corner
324
+
325
+ const u1 = pA.x, v1 = pA.y;
326
+ const u2 = pC.x, v2 = pC.y;
327
+
328
+ const nextPointId = () => Math.max(0, ...this.sketchObject.points.map(p => +p.id || 0)) + 1;
329
+ const nextGeoId = () => Math.max(0, ...this.sketchObject.geometries.map(g => +g.id || 0)) + 1;
330
+ const nextConId = () => Math.max(0, ...this.sketchObject.constraints.map(c => +c.id || 0)) + 1;
331
+
332
+ // Create additional corner points at axis-aligned positions
333
+ // We intentionally duplicate endpoints per edge and tie with coincident constraints.
334
+ // Corners: C1(u1,v1)=pA vs pA_other, C2(u2,v1)=c2a,c2b, C3(u2,v2)=pC vs pC_other, C4(u1,v2)=c4a,c4b
335
+ const pA_other = { id: nextPointId(), x: u1, y: v1, fixed: false };
336
+ const c2a = { id: pA_other.id + 1, x: u2, y: v1, fixed: false };
337
+ const c2b = { id: c2a.id + 1, x: u2, y: v1, fixed: false };
338
+ const pC_other = { id: c2b.id + 1, x: u2, y: v2, fixed: false };
339
+ const c4a = { id: pC_other.id + 1, x: u1, y: v2, fixed: false };
340
+ const c4b = { id: c4a.id + 1, x: u1, y: v2, fixed: false };
341
+
342
+ this.sketchObject.points.push(pA_other, c2a, c2b, pC_other, c4a, c4b);
343
+
344
+ // Build 4 line geometries with distinct endpoints per adjacent edge
345
+ const gIds = [];
346
+ const pushLine = (a, b) => {
347
+ const gid = nextGeoId();
348
+ this.sketchObject.geometries.push({ id: gid, type: "line", points: [a, b], construction: false });
349
+ gIds.push(gid);
350
+ };
351
+
352
+ // L1: bottom (C1 -> C2): pA -> c2a
353
+ pushLine(pA.id, c2a.id);
354
+ // L2: right (C2 -> C3): c2b -> pC
355
+ pushLine(c2b.id, pC.id);
356
+ // L3: top (C3 -> C4): pC_other -> c4a
357
+ pushLine(pC_other.id, c4a.id);
358
+ // L4: left (C4 -> C1): c4b -> pA_other
359
+ pushLine(c4b.id, pA_other.id);
360
+
361
+ // Coincident constraints at all four corners
362
+ const pushCoincident = (p1, p2) => {
363
+ const cid = nextConId();
364
+ this.sketchObject.constraints.push({ id: cid, type: "≡", points: [p1, p2] });
365
+ };
366
+ // C1: pA with pA_other
367
+ pushCoincident(pA.id, pA_other.id);
368
+ // C2: c2a with c2b
369
+ pushCoincident(c2a.id, c2b.id);
370
+ // C3: pC with pC_other
371
+ pushCoincident(pC.id, pC_other.id);
372
+ // C4: c4a with c4b
373
+ pushCoincident(c4a.id, c4b.id);
374
+
375
+ // Perpendicular constraints between adjacent sides (3 of them; the 4th is implied)
376
+ const pushPerp = (a, b, c, d) => {
377
+ const cid = nextConId();
378
+ this.sketchObject.constraints.push({ id: cid, type: "⟂", points: [a, b, c, d] });
379
+ };
380
+ // Use endpoints that define each line direction
381
+ // L1(pA,c2a) ⟂ L2(c2b,pC)
382
+ pushPerp(pA.id, c2a.id, c2b.id, pC.id);
383
+ // L2(c2b,pC) ⟂ L3(pC_other,c4a)
384
+ pushPerp(c2b.id, pC.id, pC_other.id, c4a.id);
385
+ // L3(pC_other,c4a) ⟂ L4(c4b,pA_other)
386
+ pushPerp(pC_other.id, c4a.id, c4b.id, pA_other.id);
387
+
388
+ // Solve and update
389
+ this.sketchObject = this.solveSketch("full");
390
+ this.hooks.updateCanvas();
391
+ this.appState.mode = "";
392
+ this.appState.type = "";
393
+ this.appState.requiredSelections = 0;
394
+ return true;
395
+ }
396
+
397
+ createGeometry(type, points = []) {
398
+ // Use selection if not provided
399
+ if (points.length === 0) {
400
+ const items = this.hooks.getSelectionItems();
401
+ if (items && items.length > 0) {
402
+ points = [];
403
+ for (const it of items) {
404
+ if (it.type === "point") {
405
+ const p = this.sketchObject.points.find(x => x.id === parseInt(it.id));
406
+ if (p) points.push(p);
407
+ }
408
+ }
409
+ }
410
+ }
411
+
412
+ if (this.appState.requiredSelections && points.length !== this.appState.requiredSelections) {
413
+ return false;
414
+ }
415
+
416
+ let pointIds;
417
+ if (points.length > 0 && typeof points[0] === "object") {
418
+ pointIds = points.map(p => p.id);
419
+ } else {
420
+ pointIds = points;
421
+ }
422
+
423
+ if (!pointIds || pointIds.length === 0) return false;
424
+
425
+ const maxId = Math.max(0, ...this.sketchObject.geometries.map(geo => +geo.id || 0)) + 1;
426
+ const newGeometry = {
427
+ id: maxId,
428
+ type,
429
+ points: pointIds,
430
+ construction: false
431
+ };
432
+
433
+ this.sketchObject.geometries.push(newGeometry);
434
+ this.hooks.updateCanvas();
435
+ this.appState.mode = "";
436
+ this.appState.type = "";
437
+ this.appState.requiredSelections = 0;
438
+ return true;
439
+ }
440
+
441
+ createConstraint(type, currentlySelected = null) {
442
+ const selected = [];
443
+ let geometryType = null;
444
+
445
+ const items = Array.isArray(currentlySelected) ? currentlySelected : this.hooks.getSelectionItems();
446
+
447
+ for (const item of items) {
448
+ if (item.type === "point") {
449
+ const p = this.sketchObject.points.find(pp => pp.id === parseInt(item.id));
450
+ if (p) selected.push(p);
451
+ }
452
+ if (item.type === "geometry") {
453
+ const g = this.sketchObject.geometries.find(gg => gg.id === parseInt(item.id));
454
+ if (!g) continue;
455
+ for (const pid of g.points) {
456
+ const p = this.sketchObject.points.find(pp => pp.id === pid);
457
+ if (p) selected.push(p);
458
+ }
459
+ if (g.type === "arc") selected.pop(); // center + start; omit end for 3pt cases
460
+ geometryType = g.type;
461
+ }
462
+ }
463
+
464
+ if (selected.length === 0) return;
465
+
466
+ const selectedPointIds = selected.map(p => parseInt(p.id));
467
+
468
+ const newConstraint = {
469
+ id: 0,
470
+ type,
471
+ points: selectedPointIds,
472
+ labelX: 0,
473
+ labelY: 0,
474
+ displayStyle: "",
475
+ value: null,
476
+ valueNeedsSetup: true
477
+ };
478
+
479
+ if (selected.length === 1) {
480
+ if (type === "⏚") return this.createAndPushNewConstraint(newConstraint);
481
+ }
482
+
483
+ if (selected.length === 2) {
484
+ if (type === "━") return this.createAndPushNewConstraint(newConstraint);
485
+ if (type === "│") return this.createAndPushNewConstraint(newConstraint);
486
+ if (type === "≡") return this.createAndPushNewConstraint(newConstraint);
487
+
488
+ if (type === "⟺") {
489
+ if (geometryType === "arc" || geometryType === "circle") newConstraint.displayStyle = "radius";
490
+ return this.createAndPushNewConstraint(newConstraint);
491
+ }
492
+ }
493
+
494
+ if (selected.length === 3) {
495
+ if (type === "⏛") return this.createAndPushNewConstraint(newConstraint);
496
+ if (type === "⋯") {
497
+ // If selection is Point + Line (and Point was first), ensure Point is last (Midpoint)
498
+ const hasGeometry = items.some(i => i.type === "geometry");
499
+ if (hasGeometry && items[0]?.type === "point") {
500
+ newConstraint.points = selectedPointIds.slice().reverse();
501
+ }
502
+ return this.createAndPushNewConstraint(newConstraint);
503
+ }
504
+ if (type === "⇌") return this.createAndPushNewConstraint(newConstraint);
505
+ }
506
+
507
+ if (selected.length === 4 || selected.length === 5) {
508
+ if (type === "⏛") {
509
+ // Allow Point-on-Line from two selected lines: use first line as target,
510
+ // constrain both endpoints of the second line onto it (all points colinear).
511
+ const geometries = items.filter(i => i.type === "geometry");
512
+ if (geometries.length === 2) {
513
+ const g0 = this.sketchObject.geometries.find(gg => gg.id === parseInt(geometries[0].id));
514
+ const g1 = this.sketchObject.geometries.find(gg => gg.id === parseInt(geometries[1].id));
515
+ if (g0?.type === "line" && g1?.type === "line" &&
516
+ Array.isArray(g0.points) && g0.points.length >= 2 &&
517
+ Array.isArray(g1.points) && g1.points.length >= 2) {
518
+ const a = this.sketchObject.points.find(p => p.id === g0.points[0]);
519
+ const b = this.sketchObject.points.find(p => p.id === g0.points[1]);
520
+ const c0 = this.sketchObject.points.find(p => p.id === g1.points[0]);
521
+ const c1 = this.sketchObject.points.find(p => p.id === g1.points[1]);
522
+ if (a && b && c0 && c1) {
523
+ const base = {
524
+ type,
525
+ labelX: 0,
526
+ labelY: 0,
527
+ displayStyle: "",
528
+ value: null,
529
+ valueNeedsSetup: true
530
+ };
531
+ const maxId = Math.max(0, ...this.sketchObject.constraints.map(c => +c.id || 0)) + 1;
532
+ const cA = { ...base, id: maxId, points: [a.id, b.id, c0.id] };
533
+ const cB = { ...base, id: maxId + 1, points: [a.id, b.id, c1.id] };
534
+ this.sketchObject.constraints.push(cA, cB);
535
+ this.sketchObject = this.solveSketch("full");
536
+ this.hooks.updateCanvas();
537
+ this.hooks.notifyUser("Constraint added", "info");
538
+ return true;
539
+ }
540
+ }
541
+ }
542
+ }
543
+ if (type === "⟂") {
544
+ // Check if this is a tangent constraint (line + arc/circle)
545
+ const isTangentConstraint = this.#detectTangentConstraint(items);
546
+
547
+ if (isTangentConstraint) {
548
+ // Handle tangent constraint: choose closest line endpoint to circle center
549
+ const optimizedPoints = this.#optimizePointsForTangent(items, selected);
550
+ if (optimizedPoints) {
551
+ newConstraint.points = optimizedPoints;
552
+ return this.createAndPushNewConstraint(newConstraint);
553
+ }
554
+ }
555
+
556
+ // Only proceed with standard perpendicular if we have exactly 4 points (two lines)
557
+ if (selected.length !== 4) {
558
+ this.hooks.updateCanvas();
559
+ this.hooks.notifyUser(
560
+ `Invalid selection for constraint type ${type}\nwith ${selected.length} points.`,
561
+ "warning"
562
+ );
563
+ return;
564
+ }
565
+
566
+ // Standard perpendicular constraint for two lines
567
+ let line1AngleA = calculateAngle(selected[0], selected[1]);
568
+ let line1AngleB = calculateAngle(selected[1], selected[0]);
569
+ let line2Angle = calculateAngle(selected[2], selected[3]);
570
+
571
+ line1AngleA = (line1AngleA + 180) % 360 - 180;
572
+ line1AngleB = (line1AngleB + 180) % 360 - 180;
573
+ line2Angle = (line2Angle + 180) % 360 - 180;
574
+
575
+ let diffA = line1AngleA - line2Angle;
576
+ let diffB = line1AngleB - line2Angle;
577
+
578
+ // Choose orientation closer to 90°
579
+ if (Math.abs(90 - diffA) > Math.abs(90 - diffB)) {
580
+ [newConstraint.points[0], newConstraint.points[1]] = [newConstraint.points[1], newConstraint.points[0]];
581
+ }
582
+ return this.createAndPushNewConstraint(newConstraint);
583
+ }
584
+ if (type === "∥") return this.createAndPushNewConstraint(newConstraint);
585
+ if (type === "∠") {
586
+ // Do NOT set a value on creation. The solver initializes the
587
+ // constraint to the current angle on first evaluation, and the
588
+ // renderer will display the interior arc by default.
589
+ return this.createAndPushNewConstraint(newConstraint);
590
+ }
591
+ if (type === "⇌") return this.createAndPushNewConstraint(newConstraint);
592
+ }
593
+
594
+ this.hooks.updateCanvas();
595
+ this.hooks.notifyUser(
596
+ `Invalid selection for constraint type ${type}\nwith ${selected.length} points.`,
597
+ "warning"
598
+ );
599
+ }
600
+
601
+ createAndPushNewConstraint(constraint) {
602
+ const maxId = Math.max(0, ...this.sketchObject.constraints.map(c => +c.id || 0)) + 1;
603
+ constraint.id = maxId;
604
+ constraint.value = (constraint.value === null || constraint.value === undefined)
605
+ ? null
606
+ : parseFloat(Number(constraint.value).toFixed(4));
607
+
608
+ this.sketchObject.constraints.push(constraint);
609
+ this.sketchObject = this.solveSketch("full");
610
+
611
+ this.hooks.updateCanvas();
612
+ this.hooks.notifyUser("Constraint added", "info");
613
+ return true;
614
+ }
615
+
616
+ // ---------- Coincident simplification & cleanup ----------
617
+ simplifyCoincidentConstraints() {
618
+ const data = this.sketchObject;
619
+ const coincidentGroups = {};
620
+ const pointToGroup = {};
621
+
622
+ data.constraints.forEach(constraint => {
623
+ if (constraint.type === "≡") {
624
+ const [p1, p2] = constraint.points;
625
+ if (!coincidentGroups[p1]) coincidentGroups[p1] = new Set();
626
+ if (!coincidentGroups[p2]) coincidentGroups[p2] = new Set();
627
+ coincidentGroups[p1].add(p2);
628
+ coincidentGroups[p2].add(p1);
629
+ }
630
+ });
631
+
632
+ // Merge overlapping groups
633
+ for (const [point, group] of Object.entries(coincidentGroups)) {
634
+ for (const other of group) {
635
+ if (coincidentGroups[other]) {
636
+ for (const p of coincidentGroups[other]) {
637
+ group.add(p);
638
+ coincidentGroups[p] = group;
639
+ }
640
+ }
641
+ }
642
+ }
643
+
644
+ for (const [point, group] of Object.entries(coincidentGroups)) {
645
+ const minId = Math.min(...Array.from(group));
646
+ pointToGroup[point] = minId;
647
+ }
648
+
649
+ // Replace IDs in constraints and geometries
650
+ data.constraints.forEach(c => {
651
+ c.points = c.points.map(p => pointToGroup[p] || p);
652
+ });
653
+ data.geometries.forEach(g => {
654
+ g.points = g.points.map(p => pointToGroup[p] || p);
655
+ });
656
+
657
+ this.discardUnusedPoints();
658
+
659
+ // Remove degenerate coincident constraints (same point twice)
660
+ data.constraints = data.constraints.filter(c => !(c.type === "≡" && c.points[0] === c.points[1]));
661
+
662
+ return this.sketchObject;
663
+ }
664
+
665
+ discardUnusedPoints() {
666
+ const data = this.sketchObject;
667
+ const used = new Set();
668
+ data.constraints.forEach(c => c.points.forEach(pid => used.add(pid)));
669
+ data.geometries.forEach(g => g.points.forEach(pid => used.add(pid)));
670
+ data.points = data.points.filter(p => used.has(p.id));
671
+ return this.sketchObject;
672
+ }
673
+
674
+ // Helper method to detect if this is a tangent constraint (line + arc/circle)
675
+ #detectTangentConstraint(items) {
676
+ if (items.length !== 2) return false;
677
+
678
+ const geometries = items.filter(item => item.type === "geometry");
679
+ if (geometries.length !== 2) return false;
680
+
681
+ const lineGeo = geometries.find(item => {
682
+ const g = this.sketchObject.geometries.find(gg => gg.id === parseInt(item.id));
683
+ return g?.type === "line";
684
+ });
685
+
686
+ const circularGeo = geometries.find(item => {
687
+ const g = this.sketchObject.geometries.find(gg => gg.id === parseInt(item.id));
688
+ return g?.type === "arc" || g?.type === "circle";
689
+ });
690
+
691
+ return lineGeo && circularGeo;
692
+ }
693
+
694
+ // Helper method to choose optimal points for tangent constraint
695
+ #optimizePointsForTangent(items, selected) {
696
+ const geometries = items.filter(item => item.type === "geometry");
697
+ if (geometries.length !== 2) return null;
698
+
699
+ let lineGeo = null;
700
+ let circularGeo = null;
701
+ let linePoints = [];
702
+ let circularPoints = [];
703
+
704
+ // Identify line and circular geometry
705
+ for (const item of geometries) {
706
+ const g = this.sketchObject.geometries.find(gg => gg.id === parseInt(item.id));
707
+ if (!g) continue;
708
+
709
+ if (g.type === "line") {
710
+ lineGeo = g;
711
+ linePoints = g.points.map(pid => this.sketchObject.points.find(p => p.id === pid));
712
+ } else if (g.type === "arc" || g.type === "circle") {
713
+ circularGeo = g;
714
+ circularPoints = g.points.map(pid => this.sketchObject.points.find(p => p.id === pid));
715
+ }
716
+ }
717
+
718
+ if (!lineGeo || !circularGeo || linePoints.length < 2 || circularPoints.length < 1) {
719
+ return null;
720
+ }
721
+
722
+ // For circular geometry: [0] = center, [1] = start point, [2] = end point (for arc)
723
+ const center = circularPoints[0];
724
+ const lineStart = linePoints[0];
725
+ const lineEnd = linePoints[1];
726
+
727
+ // Get arc/circle points (excluding center)
728
+ const arcPoints = circularPoints.slice(1); // Remove center to get actual arc points
729
+
730
+ if (arcPoints.length === 0) {
731
+ // Fallback for circles - use center only
732
+ return [lineStart.id, lineEnd.id, center.id, center.id];
733
+ }
734
+
735
+ // Calculate distance from each arc point to the line
736
+ let closestArcPoint = arcPoints[0];
737
+ let minDistance = this.#distancePointToLine(arcPoints[0], lineStart, lineEnd);
738
+
739
+ for (let i = 1; i < arcPoints.length; i++) {
740
+ const dist = this.#distancePointToLine(arcPoints[i], lineStart, lineEnd);
741
+ if (dist < minDistance) {
742
+ minDistance = dist;
743
+ closestArcPoint = arcPoints[i];
744
+ }
745
+ }
746
+
747
+ // Create perpendicular constraint using the line and the radius to closest arc point
748
+ // Points: [line_start, line_end, center, closest_arc_point]
749
+ return [lineStart.id, lineEnd.id, center.id, closestArcPoint.id];
750
+ }
751
+
752
+ // Helper method to calculate distance from point to line
753
+ #distancePointToLine(point, lineStart, lineEnd) {
754
+ // Calculate line direction vector
755
+ let dirX = lineEnd.x - lineStart.x;
756
+ let dirY = lineEnd.y - lineStart.y;
757
+ const mag = Math.sqrt(dirX * dirX + dirY * dirY);
758
+
759
+ if (mag === 0) return distance(point, lineStart); // Degenerate line case
760
+
761
+ dirX /= mag; // Normalize
762
+ dirY /= mag;
763
+
764
+ // Vector from line start to point
765
+ const vecX = point.x - lineStart.x;
766
+ const vecY = point.y - lineStart.y;
767
+
768
+ // Project point onto line
769
+ const dot = vecX * dirX + vecY * dirY;
770
+ const projX = lineStart.x + dot * dirX;
771
+ const projY = lineStart.y + dot * dirY;
772
+
773
+ // Calculate distance from point to its projection on the line
774
+ const distX = point.x - projX;
775
+ const distY = point.y - projY;
776
+ return Math.sqrt(distX * distX + distY * distY);
777
+ }
778
+ }
779
+
780
+ // ---------- Utilities ----------
781
+ function sanitizeSketch(sketch) {
782
+ const s = {
783
+ points: Array.isArray(sketch.points) ? sketch.points.map(p => ({
784
+ id: +p.id, x: +p.x, y: +p.y, fixed: !!p.fixed
785
+ })) : [],
786
+ geometries: Array.isArray(sketch.geometries) ? sketch.geometries.slice() : [],
787
+ constraints: Array.isArray(sketch.constraints) ? sketch.constraints.slice() : []
788
+ };
789
+
790
+ // Ensure at least an origin and ground if empty
791
+ if (s.points.length === 0) s.points.push({ id: 0, x: 0, y: 0, fixed: true });
792
+ if (!s.constraints.some(c => c.type === "⏚")) {
793
+ s.constraints.push({ id: 0, type: "⏚", points: [0] });
794
+ }
795
+ return s;
796
+ }
797
+
798
+ // Named exports for convenience (optional for consumers)
799
+ // Consumers should primarily instantiate the default export (ConstraintSolver)
800
+ export { ConstraintEngine };