brep-io-kernel 1.0.0-ci.10

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 (271) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +157 -0
  3. package/dist-kernel/brep-kernel.js +74699 -0
  4. package/package.json +58 -0
  5. package/src/BREP/AssemblyComponent.js +42 -0
  6. package/src/BREP/BREP.js +43 -0
  7. package/src/BREP/BetterSolid.js +805 -0
  8. package/src/BREP/Edge.js +103 -0
  9. package/src/BREP/Extrude.js +403 -0
  10. package/src/BREP/Face.js +187 -0
  11. package/src/BREP/MeshRepairer.js +634 -0
  12. package/src/BREP/OffsetShellSolid.js +614 -0
  13. package/src/BREP/PointCloudWrap.js +302 -0
  14. package/src/BREP/Revolve.js +345 -0
  15. package/src/BREP/SolidMethods/authoring.js +112 -0
  16. package/src/BREP/SolidMethods/booleanOps.js +230 -0
  17. package/src/BREP/SolidMethods/chamfer.js +122 -0
  18. package/src/BREP/SolidMethods/edgeResolution.js +25 -0
  19. package/src/BREP/SolidMethods/fillet.js +792 -0
  20. package/src/BREP/SolidMethods/index.js +72 -0
  21. package/src/BREP/SolidMethods/io.js +105 -0
  22. package/src/BREP/SolidMethods/lifecycle.js +103 -0
  23. package/src/BREP/SolidMethods/manifoldOps.js +375 -0
  24. package/src/BREP/SolidMethods/meshCleanup.js +2512 -0
  25. package/src/BREP/SolidMethods/meshQueries.js +264 -0
  26. package/src/BREP/SolidMethods/metadata.js +106 -0
  27. package/src/BREP/SolidMethods/metrics.js +51 -0
  28. package/src/BREP/SolidMethods/transforms.js +361 -0
  29. package/src/BREP/SolidMethods/visualize.js +508 -0
  30. package/src/BREP/SolidShared.js +26 -0
  31. package/src/BREP/Sweep.js +1596 -0
  32. package/src/BREP/Tube.js +857 -0
  33. package/src/BREP/Vertex.js +43 -0
  34. package/src/BREP/applyBooleanOperation.js +704 -0
  35. package/src/BREP/boundsUtils.js +48 -0
  36. package/src/BREP/chamfer.js +551 -0
  37. package/src/BREP/edgePolylineUtils.js +85 -0
  38. package/src/BREP/fillets/common.js +388 -0
  39. package/src/BREP/fillets/fillet.js +1422 -0
  40. package/src/BREP/fillets/filletGeometry.js +15 -0
  41. package/src/BREP/fillets/inset.js +389 -0
  42. package/src/BREP/fillets/offsetHelper.js +143 -0
  43. package/src/BREP/fillets/outset.js +88 -0
  44. package/src/BREP/helix.js +193 -0
  45. package/src/BREP/meshToBrep.js +234 -0
  46. package/src/BREP/primitives.js +279 -0
  47. package/src/BREP/setupManifold.js +71 -0
  48. package/src/BREP/threadGeometry.js +1120 -0
  49. package/src/BREP/triangleUtils.js +8 -0
  50. package/src/BREP/triangulate.js +608 -0
  51. package/src/FeatureRegistry.js +183 -0
  52. package/src/PartHistory.js +1132 -0
  53. package/src/UI/AccordionWidget.js +292 -0
  54. package/src/UI/CADmaterials.js +850 -0
  55. package/src/UI/EnvMonacoEditor.js +522 -0
  56. package/src/UI/FloatingWindow.js +396 -0
  57. package/src/UI/HistoryWidget.js +457 -0
  58. package/src/UI/MainToolbar.js +131 -0
  59. package/src/UI/ModelLibraryView.js +194 -0
  60. package/src/UI/OrthoCameraIdle.js +206 -0
  61. package/src/UI/PluginsWidget.js +280 -0
  62. package/src/UI/SceneListing.js +606 -0
  63. package/src/UI/SelectionFilter.js +629 -0
  64. package/src/UI/ViewCube.js +389 -0
  65. package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +329 -0
  66. package/src/UI/assembly/AssemblyConstraintControlsWidget.js +282 -0
  67. package/src/UI/assembly/AssemblyConstraintsWidget.css +292 -0
  68. package/src/UI/assembly/AssemblyConstraintsWidget.js +1373 -0
  69. package/src/UI/assembly/constraintFaceUtils.js +115 -0
  70. package/src/UI/assembly/constraintHighlightUtils.js +70 -0
  71. package/src/UI/assembly/constraintLabelUtils.js +31 -0
  72. package/src/UI/assembly/constraintPointUtils.js +64 -0
  73. package/src/UI/assembly/constraintSelectionUtils.js +185 -0
  74. package/src/UI/assembly/constraintStatusUtils.js +142 -0
  75. package/src/UI/componentSelectorModal.js +240 -0
  76. package/src/UI/controls/CombinedTransformControls.js +386 -0
  77. package/src/UI/dialogs.js +351 -0
  78. package/src/UI/expressionsManager.js +100 -0
  79. package/src/UI/featureDialogWidgets/booleanField.js +25 -0
  80. package/src/UI/featureDialogWidgets/booleanOperationField.js +97 -0
  81. package/src/UI/featureDialogWidgets/buttonField.js +45 -0
  82. package/src/UI/featureDialogWidgets/componentSelectorField.js +102 -0
  83. package/src/UI/featureDialogWidgets/defaultField.js +23 -0
  84. package/src/UI/featureDialogWidgets/fileField.js +66 -0
  85. package/src/UI/featureDialogWidgets/index.js +34 -0
  86. package/src/UI/featureDialogWidgets/numberField.js +165 -0
  87. package/src/UI/featureDialogWidgets/optionsField.js +33 -0
  88. package/src/UI/featureDialogWidgets/referenceSelectionField.js +208 -0
  89. package/src/UI/featureDialogWidgets/stringField.js +24 -0
  90. package/src/UI/featureDialogWidgets/textareaField.js +28 -0
  91. package/src/UI/featureDialogWidgets/threadDesignationField.js +160 -0
  92. package/src/UI/featureDialogWidgets/transformField.js +252 -0
  93. package/src/UI/featureDialogWidgets/utils.js +43 -0
  94. package/src/UI/featureDialogWidgets/vec3Field.js +133 -0
  95. package/src/UI/featureDialogs.js +1414 -0
  96. package/src/UI/fileManagerWidget.js +615 -0
  97. package/src/UI/history/HistoryCollectionWidget.js +1294 -0
  98. package/src/UI/history/historyCollectionWidget.css.js +257 -0
  99. package/src/UI/history/historyDisplayInfo.js +133 -0
  100. package/src/UI/mobile.js +28 -0
  101. package/src/UI/objectDump.js +442 -0
  102. package/src/UI/pmi/AnnotationCollectionWidget.js +120 -0
  103. package/src/UI/pmi/AnnotationHistory.js +353 -0
  104. package/src/UI/pmi/AnnotationRegistry.js +90 -0
  105. package/src/UI/pmi/BaseAnnotation.js +269 -0
  106. package/src/UI/pmi/LabelOverlay.css +102 -0
  107. package/src/UI/pmi/LabelOverlay.js +191 -0
  108. package/src/UI/pmi/PMIMode.js +1550 -0
  109. package/src/UI/pmi/PMIViewsWidget.js +1098 -0
  110. package/src/UI/pmi/annUtils.js +729 -0
  111. package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +647 -0
  112. package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +507 -0
  113. package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +462 -0
  114. package/src/UI/pmi/dimensions/LeaderAnnotation.js +403 -0
  115. package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +532 -0
  116. package/src/UI/pmi/dimensions/NoteAnnotation.js +110 -0
  117. package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +659 -0
  118. package/src/UI/pmi/pmiStyle.js +44 -0
  119. package/src/UI/sketcher/SketchMode3D.js +4095 -0
  120. package/src/UI/sketcher/dimensions.js +674 -0
  121. package/src/UI/sketcher/glyphs.js +236 -0
  122. package/src/UI/sketcher/highlights.js +60 -0
  123. package/src/UI/toolbarButtons/aboutButton.js +5 -0
  124. package/src/UI/toolbarButtons/exportButton.js +609 -0
  125. package/src/UI/toolbarButtons/flatPatternButton.js +307 -0
  126. package/src/UI/toolbarButtons/importButton.js +160 -0
  127. package/src/UI/toolbarButtons/inspectorToggleButton.js +12 -0
  128. package/src/UI/toolbarButtons/metadataButton.js +1063 -0
  129. package/src/UI/toolbarButtons/orientToFaceButton.js +114 -0
  130. package/src/UI/toolbarButtons/registerDefaultButtons.js +46 -0
  131. package/src/UI/toolbarButtons/saveButton.js +99 -0
  132. package/src/UI/toolbarButtons/scriptRunnerButton.js +302 -0
  133. package/src/UI/toolbarButtons/testsButton.js +26 -0
  134. package/src/UI/toolbarButtons/undoRedoButtons.js +25 -0
  135. package/src/UI/toolbarButtons/wireframeToggleButton.js +5 -0
  136. package/src/UI/toolbarButtons/zoomToFitButton.js +5 -0
  137. package/src/UI/triangleDebuggerWindow.js +945 -0
  138. package/src/UI/viewer.js +4228 -0
  139. package/src/assemblyConstraints/AssemblyConstraintHistory.js +1576 -0
  140. package/src/assemblyConstraints/AssemblyConstraintRegistry.js +120 -0
  141. package/src/assemblyConstraints/BaseAssemblyConstraint.js +66 -0
  142. package/src/assemblyConstraints/constraintExpressionUtils.js +35 -0
  143. package/src/assemblyConstraints/constraintUtils/parallelAlignment.js +676 -0
  144. package/src/assemblyConstraints/constraints/AngleConstraint.js +485 -0
  145. package/src/assemblyConstraints/constraints/CoincidentConstraint.js +194 -0
  146. package/src/assemblyConstraints/constraints/DistanceConstraint.js +616 -0
  147. package/src/assemblyConstraints/constraints/FixedConstraint.js +78 -0
  148. package/src/assemblyConstraints/constraints/ParallelConstraint.js +252 -0
  149. package/src/assemblyConstraints/constraints/TouchAlignConstraint.js +961 -0
  150. package/src/core/entities/HistoryCollectionBase.js +72 -0
  151. package/src/core/entities/ListEntityBase.js +109 -0
  152. package/src/core/entities/schemaProcesser.js +121 -0
  153. package/src/exporters/sheetMetalFlatPattern.js +659 -0
  154. package/src/exporters/sheetMetalUnfold.js +862 -0
  155. package/src/exporters/step.js +1135 -0
  156. package/src/exporters/threeMF.js +575 -0
  157. package/src/features/assemblyComponent/AssemblyComponentFeature.js +780 -0
  158. package/src/features/boolean/BooleanFeature.js +94 -0
  159. package/src/features/chamfer/ChamferFeature.js +116 -0
  160. package/src/features/datium/DatiumFeature.js +80 -0
  161. package/src/features/edgeFeatureUtils.js +41 -0
  162. package/src/features/extrude/ExtrudeFeature.js +143 -0
  163. package/src/features/fillet/FilletFeature.js +197 -0
  164. package/src/features/helix/HelixFeature.js +405 -0
  165. package/src/features/hole/HoleFeature.js +1050 -0
  166. package/src/features/hole/screwClearance.js +86 -0
  167. package/src/features/hole/threadDesignationCatalog.js +149 -0
  168. package/src/features/imageHeightSolid/ImageHeightmapSolidFeature.js +463 -0
  169. package/src/features/imageToFace/ImageToFaceFeature.js +727 -0
  170. package/src/features/imageToFace/imageEditor.js +1270 -0
  171. package/src/features/imageToFace/traceUtils.js +971 -0
  172. package/src/features/import3dModel/Import3dModelFeature.js +151 -0
  173. package/src/features/loft/LoftFeature.js +605 -0
  174. package/src/features/mirror/MirrorFeature.js +151 -0
  175. package/src/features/offsetFace/OffsetFaceFeature.js +370 -0
  176. package/src/features/offsetShell/OffsetShellFeature.js +89 -0
  177. package/src/features/overlapCleanup/OverlapCleanupFeature.js +85 -0
  178. package/src/features/pattern/PatternFeature.js +275 -0
  179. package/src/features/patternLinear/PatternLinearFeature.js +120 -0
  180. package/src/features/patternRadial/PatternRadialFeature.js +186 -0
  181. package/src/features/plane/PlaneFeature.js +154 -0
  182. package/src/features/primitiveCone/primitiveConeFeature.js +99 -0
  183. package/src/features/primitiveCube/primitiveCubeFeature.js +70 -0
  184. package/src/features/primitiveCylinder/primitiveCylinderFeature.js +91 -0
  185. package/src/features/primitivePyramid/primitivePyramidFeature.js +72 -0
  186. package/src/features/primitiveSphere/primitiveSphereFeature.js +62 -0
  187. package/src/features/primitiveTorus/primitiveTorusFeature.js +109 -0
  188. package/src/features/remesh/RemeshFeature.js +97 -0
  189. package/src/features/revolve/RevolveFeature.js +111 -0
  190. package/src/features/selectionUtils.js +118 -0
  191. package/src/features/sheetMetal/SheetMetalContourFlangeFeature.js +1656 -0
  192. package/src/features/sheetMetal/SheetMetalCutoutFeature.js +1056 -0
  193. package/src/features/sheetMetal/SheetMetalFlangeFeature.js +1568 -0
  194. package/src/features/sheetMetal/SheetMetalHemFeature.js +43 -0
  195. package/src/features/sheetMetal/SheetMetalObject.js +141 -0
  196. package/src/features/sheetMetal/SheetMetalTabFeature.js +176 -0
  197. package/src/features/sheetMetal/UNFOLD_NEUTRAL_REQUIREMENTS.md +153 -0
  198. package/src/features/sheetMetal/contour-flange-rebuild-spec.md +261 -0
  199. package/src/features/sheetMetal/profileUtils.js +25 -0
  200. package/src/features/sheetMetal/sheetMetalCleanup.js +9 -0
  201. package/src/features/sheetMetal/sheetMetalFaceTypes.js +146 -0
  202. package/src/features/sheetMetal/sheetMetalMetadata.js +165 -0
  203. package/src/features/sheetMetal/sheetMetalPipeline.js +169 -0
  204. package/src/features/sheetMetal/sheetMetalProfileUtils.js +216 -0
  205. package/src/features/sheetMetal/sheetMetalTabUtils.js +29 -0
  206. package/src/features/sheetMetal/sheetMetalTree.js +210 -0
  207. package/src/features/sketch/SketchFeature.js +955 -0
  208. package/src/features/sketch/sketchSolver2D/ConstraintEngine.js +800 -0
  209. package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +704 -0
  210. package/src/features/sketch/sketchSolver2D/mathHelpersMod.js +307 -0
  211. package/src/features/spline/SplineEditorSession.js +988 -0
  212. package/src/features/spline/SplineFeature.js +1388 -0
  213. package/src/features/spline/splineUtils.js +218 -0
  214. package/src/features/sweep/SweepFeature.js +110 -0
  215. package/src/features/transform/TransformFeature.js +152 -0
  216. package/src/features/tube/TubeFeature.js +635 -0
  217. package/src/fs.proxy.js +625 -0
  218. package/src/idbStorage.js +254 -0
  219. package/src/index.js +12 -0
  220. package/src/main.js +15 -0
  221. package/src/metadataManager.js +64 -0
  222. package/src/path.proxy.js +277 -0
  223. package/src/plugins/ghLoader.worker.js +151 -0
  224. package/src/plugins/pluginManager.js +286 -0
  225. package/src/pmi/PMIViewsManager.js +134 -0
  226. package/src/services/componentLibrary.js +198 -0
  227. package/src/tests/ConsoleCapture.js +189 -0
  228. package/src/tests/S7-diagnostics-2025-12-23T18-37-23-570Z.json +630 -0
  229. package/src/tests/browserTests.js +597 -0
  230. package/src/tests/debugBoolean.js +225 -0
  231. package/src/tests/partFiles/badBoolean.json +957 -0
  232. package/src/tests/partFiles/extrudeTest.json +88 -0
  233. package/src/tests/partFiles/filletFail.json +58 -0
  234. package/src/tests/partFiles/import_TEst.part.part.json +646 -0
  235. package/src/tests/partFiles/sheetMetalHem.BREP.json +734 -0
  236. package/src/tests/test_boolean_subtract.js +27 -0
  237. package/src/tests/test_chamfer.js +17 -0
  238. package/src/tests/test_extrudeFace.js +24 -0
  239. package/src/tests/test_fillet.js +17 -0
  240. package/src/tests/test_fillet_nonClosed.js +45 -0
  241. package/src/tests/test_filletsMoreDifficult.js +46 -0
  242. package/src/tests/test_history_features_basic.js +149 -0
  243. package/src/tests/test_hole.js +282 -0
  244. package/src/tests/test_mirror.js +16 -0
  245. package/src/tests/test_offsetShellGrouping.js +85 -0
  246. package/src/tests/test_plane.js +4 -0
  247. package/src/tests/test_primitiveCone.js +11 -0
  248. package/src/tests/test_primitiveCube.js +7 -0
  249. package/src/tests/test_primitiveCylinder.js +8 -0
  250. package/src/tests/test_primitivePyramid.js +9 -0
  251. package/src/tests/test_primitiveSphere.js +17 -0
  252. package/src/tests/test_primitiveTorus.js +21 -0
  253. package/src/tests/test_pushFace.js +126 -0
  254. package/src/tests/test_sheetMetalContourFlange.js +125 -0
  255. package/src/tests/test_sheetMetal_features.js +80 -0
  256. package/src/tests/test_sketch_openLoop.js +45 -0
  257. package/src/tests/test_solidMetrics.js +58 -0
  258. package/src/tests/test_stlLoader.js +1889 -0
  259. package/src/tests/test_sweepFace.js +55 -0
  260. package/src/tests/test_tube.js +45 -0
  261. package/src/tests/test_tube_closedLoop.js +67 -0
  262. package/src/tests/tests.js +493 -0
  263. package/src/tools/assemblyConstraintDialogCapturePage.js +56 -0
  264. package/src/tools/dialogCapturePageFactory.js +227 -0
  265. package/src/tools/featureDialogCapturePage.js +47 -0
  266. package/src/tools/pmiAnnotationDialogCapturePage.js +60 -0
  267. package/src/utils/axisHelpers.js +99 -0
  268. package/src/utils/deepClone.js +69 -0
  269. package/src/utils/geometryTolerance.js +37 -0
  270. package/src/utils/normalizeTypeString.js +8 -0
  271. package/src/utils/xformMath.js +51 -0
@@ -0,0 +1,704 @@
1
+ "use strict";
2
+ import { calculateAngle, rotatePoint, distance, roundToDecimals } from "./mathHelpersMod.js";
3
+ let tolerance = 0.00001;
4
+ const constraintFunctions = [];
5
+
6
+ const normalizeAngle = (angle) => ((angle % 360) + 360) % 360;
7
+ const shortestAngleDelta = (target, current) => {
8
+ const delta = normalizeAngle(target - current);
9
+ return (delta > 180) ? delta - 360 : delta;
10
+ };
11
+
12
+
13
+ (constraintFunctions["━"] = function (solverObject, constraint, points, constraintValue) {
14
+ // Horizontal constraint
15
+ // test if the points are already on the same horizontal line with a tolerance
16
+ if (Math.abs(points[0].y - points[1].y) < tolerance) {
17
+ constraint.error = null;
18
+ } else {
19
+ constraint.error = `Horizontal constraint not satisfied
20
+ ${points[0].y} != ${points[1].y}`;
21
+ }
22
+
23
+ if (!points[0].fixed && !points[1].fixed) {
24
+ const avgY = (points[0].y + points[1].y) / 2;
25
+ points[0].y = avgY;
26
+ points[1].y = avgY;
27
+ } else if (!points[0].fixed) {
28
+ points[0].y = points[1].y;
29
+ } else if (!points[1].fixed) {
30
+ points[1].y = points[0].y;
31
+ }
32
+ }).hints = {
33
+ commandTooltip: "Horizontal Constraint",
34
+ pointsRequired: 2,
35
+ };
36
+
37
+
38
+
39
+ (constraintFunctions["│"] = function (solverObject, constraint, points, constraintValue) {
40
+ // Vertical constraint
41
+ // test if the points are already on the same vertical line with a tolerance
42
+ if (Math.abs(points[0].x - points[1].x) < tolerance * 2) {
43
+ constraint.error = null;
44
+ } else {
45
+ constraint.error = `Vertical constraint not satisfied
46
+ ${points[0].x} != ${points[1].x}`;
47
+ }
48
+
49
+ if (!points[0].fixed && !points[1].fixed) {
50
+ const avgX = (points[0].x + points[1].x) / 2;
51
+ points[0].x = avgX;
52
+ points[1].x = avgX;
53
+ } else if (!points[0].fixed) {
54
+ points[0].x = points[1].x;
55
+ } else if (!points[1].fixed) {
56
+ points[1].x = points[0].x;
57
+ }
58
+ }).hints = {
59
+ commandTooltip: "Vertical Constraint",
60
+ pointsRequired: 2,
61
+ };
62
+
63
+
64
+ (constraintFunctions["⟺"] = function (solverObject, constraint, points, constraintValue) {
65
+ // Distance constraint with movement limiting
66
+ const [pointA, pointB] = points;
67
+ let targetDistance = constraintValue;
68
+ let dx = pointB.x - pointA.x;
69
+ let dy = pointB.y - pointA.y;
70
+ let currentDistance = distance(pointA, pointB);
71
+
72
+ //console.log(constraintValue);
73
+
74
+ if (isNaN(constraintValue) | constraintValue == undefined | constraintValue == null) {
75
+ targetDistance = currentDistance;
76
+ constraint.value = currentDistance;
77
+ }
78
+
79
+
80
+
81
+ let diff = roundToDecimals(Math.abs(targetDistance) - currentDistance, 4);
82
+ //console.log(diff);
83
+ if (Math.abs(diff) === 0) {
84
+ constraint.error = null;
85
+ return;
86
+ } else {
87
+ constraint.error = `Distance constraint not satisfied
88
+ ${targetDistance} != ${currentDistance}`;
89
+
90
+ }
91
+
92
+ if (currentDistance === 0) {
93
+ currentDistance = 1; // Avoid division by zero
94
+ dx = 1;
95
+ dy = 1;
96
+ }
97
+
98
+ const ratio = diff / currentDistance;
99
+
100
+ let offsetX = dx * ratio * 0.5;
101
+ let offsetY = dy * ratio * 0.5;
102
+
103
+ const direction = targetDistance >= 0 ? 1 : -1;
104
+
105
+ // Limiting the movement
106
+ const maxMove = 1;
107
+ const moveDistance = Math.sqrt(offsetX * offsetX + offsetY * offsetY) || tolerance;
108
+ if (moveDistance > maxMove) {
109
+ const scale = maxMove / moveDistance;
110
+ offsetX *= scale;
111
+ offsetY *= scale;
112
+ }
113
+
114
+ if (!pointA.fixed && !pointB.fixed) {
115
+ pointA.x -= offsetX * direction;
116
+ pointA.y -= offsetY * direction;
117
+ pointB.x += offsetX * direction;
118
+ pointB.y += offsetY * direction;
119
+ } else if (!pointA.fixed) {
120
+ pointA.x -= offsetX * 2 * direction;
121
+ pointA.y -= offsetY * 2 * direction;
122
+ } else if (!pointB.fixed) {
123
+ pointB.x += offsetX * 2 * direction;
124
+ pointB.y += offsetY * 2 * direction;
125
+ } else {
126
+ return constraint.error = `points ${pointA.id} and ${pointB.id} are both fixed`;
127
+ }
128
+ return;
129
+ }).hints = {
130
+ commandTooltip: "Distance Constraint",
131
+ pointsRequired: 2,
132
+ };
133
+
134
+
135
+
136
+
137
+ (constraintFunctions["⇌"] = function (solverObject, constraint, points, constraintValue) {
138
+ // Equal Distance constraint
139
+ const [pointA, pointB, pointC, pointD] = points;
140
+
141
+ // check if either line has a distance constraint applied to it
142
+ // if so, then the line is not moving
143
+ let line1DistanceConstraint = solverObject.constraints.find(c => c.type === "⟺" && c.points.includes(pointA.id) && c.points.includes(pointB.id));
144
+ let line2DistanceConstraint = solverObject.constraints.find(c => c.type === "⟺" && c.points.includes(pointC.id) && c.points.includes(pointD.id));
145
+
146
+ let avgDistance = null;
147
+ let line1moving = false;
148
+ let line2moving = false;
149
+ if (!(line1DistanceConstraint) && !(line2DistanceConstraint)) {
150
+ // Calculate the current distances
151
+ const distanceAB = Math.sqrt(Math.pow(pointB.x - pointA.x, 2) + Math.pow(pointB.y - pointA.y, 2));
152
+ const distanceCD = Math.sqrt(Math.pow(pointD.x - pointC.x, 2) + Math.pow(pointD.y - pointC.y, 2));
153
+ avgDistance = (distanceAB + distanceCD) / 2;
154
+ line1moving = true;
155
+ line2moving = true;
156
+ } else if (line1DistanceConstraint && !line2DistanceConstraint) {
157
+ avgDistance = line1DistanceConstraint.value;
158
+ line2moving = true;
159
+ } else if (line2DistanceConstraint && !line1DistanceConstraint) {
160
+ avgDistance = line2DistanceConstraint.value;
161
+ line1moving = true;
162
+ } else if (line1DistanceConstraint && line2DistanceConstraint) {
163
+ //console.log(constraint, "Both lines have a distance constraint applied to them")
164
+ return constraint.error = "Both lines have a distance constraint applied to them";
165
+ }
166
+
167
+
168
+ if (line1moving) {
169
+ let result1 = constraintFunctions["⟺"](solverObject, constraint, [pointA, pointB], avgDistance);
170
+ if (result1) return result1;
171
+ }
172
+
173
+ if (line2moving) {
174
+ let result2 = constraintFunctions["⟺"](solverObject, constraint, [pointC, pointD], avgDistance);
175
+ if (result2) return result2;
176
+ }
177
+
178
+ }).hints = {
179
+ commandTooltip: "Equal Distance Constraint",
180
+ pointsRequired: 4,
181
+ };
182
+
183
+ (constraintFunctions["∥"] = function (solverObject, constraint, points, constraintValue) {
184
+ // Parallel constraint
185
+ // check if either line has a vertical or horizontal constraint applied to it
186
+ // if so simply apply the vertical or horizontal constraint to the other line
187
+ let line1VerticalConstraint = solverObject.constraints.find(c => c.type === "│" && c.points.includes(points[0].id) && c.points.includes(points[1].id));
188
+ let line1HorizontalConstraint = solverObject.constraints.find(c => c.type === "━" && c.points.includes(points[0].id) && c.points.includes(points[1].id));
189
+ let line2VerticalConstraint = solverObject.constraints.find(c => c.type === "│" && c.points.includes(points[2].id) && c.points.includes(points[3].id));
190
+ let line2HorizontalConstraint = solverObject.constraints.find(c => c.type === "━" && c.points.includes(points[2].id) && c.points.includes(points[3].id));
191
+
192
+ if (line1VerticalConstraint) {
193
+ if (line2VerticalConstraint) {
194
+ return constraint.error = "Both lines have a vertical constraint applied to them";
195
+ } else if (line2HorizontalConstraint) {
196
+ return constraint.error = "One line has a vertical constraint and the other has a horizontal constraint";
197
+ } else {
198
+ let result = constraintFunctions["│"](solverObject, constraint, [points[2], points[3]], 0);
199
+ if (result) return result;
200
+ }
201
+ } else if (line1HorizontalConstraint) {
202
+ if (line2VerticalConstraint) {
203
+ return constraint.error = "One line has a vertical constraint and the other has a horizontal constraint";
204
+ } else if (line2HorizontalConstraint) {
205
+ return constraint.error = "Both lines have a horizontal constraint applied to them";
206
+ } else {
207
+ let result = constraintFunctions["━"](solverObject, constraint, [points[2], points[3]], 0);
208
+ if (result) return result;
209
+ }
210
+ } else if (line2VerticalConstraint) {
211
+ let result = constraintFunctions["│"](solverObject, constraint, [points[0], points[1]], 0);
212
+ if (result) return result;
213
+ } else if (line2HorizontalConstraint) {
214
+ let result = constraintFunctions["━"](solverObject, constraint, [points[0], points[1]], 0);
215
+ if (result) return result;
216
+ } else {
217
+ // test angle between the lines
218
+
219
+ let line1Angle = calculateAngle(points[0], points[1]);
220
+ let line2Angle = calculateAngle(points[2], points[3]);
221
+
222
+ let angleDifference = (line1Angle - line2Angle);
223
+ angleDifference = (angleDifference + 360) % 360;
224
+
225
+
226
+
227
+ let newSetAngle = 0;
228
+ if (angleDifference > 90) newSetAngle = 180;
229
+ if (angleDifference > 180) newSetAngle = 180;
230
+ if (angleDifference > 270) newSetAngle = 360;
231
+
232
+ //console.log(angleDifference, newSetAngle);
233
+ return constraintFunctions["∠"](solverObject, constraint, points, newSetAngle)
234
+ }
235
+ }).hints = {
236
+ commandTooltip: "Parallel Constraint",
237
+ pointsRequired: 4,
238
+ };
239
+
240
+
241
+ (constraintFunctions["⟂"] = function (solverObject, constraint, points, constraintValue) {
242
+ // Perpendicular constraint
243
+ // check if either line has a vertical or horizontal constraint applied to it
244
+ // if so simply apply the vertical or horizontal constraint to the other line
245
+ let line1VerticalConstraint = solverObject.constraints.find(c => c.type === "│" && c.points.includes(points[0].id) && c.points.includes(points[1].id));
246
+ let line1HorizontalConstraint = solverObject.constraints.find(c => c.type === "━" && c.points.includes(points[0].id) && c.points.includes(points[1].id));
247
+ let line2VerticalConstraint = solverObject.constraints.find(c => c.type === "│" && c.points.includes(points[2].id) && c.points.includes(points[3].id));
248
+ let line2HorizontalConstraint = solverObject.constraints.find(c => c.type === "━" && c.points.includes(points[2].id) && c.points.includes(points[3].id));
249
+
250
+ if (line1VerticalConstraint) {
251
+ if (line2VerticalConstraint) {
252
+ return constraint.error = "Both lines have a vertical constraint applied to them";
253
+ } else if (line2HorizontalConstraint) {
254
+ return constraint.error = "One line has a vertical constraint and the other has a horizontal constraint";
255
+ } else {
256
+ let result = constraintFunctions["━"](solverObject, constraint, [points[2], points[3]], 0);
257
+ if (result) return result;
258
+ }
259
+ } else if (line1HorizontalConstraint) {
260
+ if (line2VerticalConstraint) {
261
+ return constraint.error = "One line has a vertical constraint and the other has a horizontal constraint";
262
+ } else if (line2HorizontalConstraint) {
263
+ return constraint.error = "Both lines have a horizontal constraint applied to them";
264
+ } else {
265
+ let result = constraintFunctions["│"](solverObject, constraint, [points[2], points[3]], 0);
266
+ if (result) return result;
267
+ }
268
+ } else if (line2VerticalConstraint) {
269
+ let result = constraintFunctions["━"](solverObject, constraint, [points[0], points[1]], 0);
270
+ if (result) return result;
271
+ } else if (line2HorizontalConstraint) {
272
+ let result = constraintFunctions["│"](solverObject, constraint, [points[0], points[1]], 0);
273
+ if (result) return result;
274
+ } else {
275
+
276
+ let p1, p2, p3, p4;
277
+
278
+ [p1, p2, p3, p4] = points;
279
+
280
+ let line1Angle = calculateAngle(p1, p2);
281
+ let line2Angle = calculateAngle(p3, p4);
282
+ let differenceBetweenAngles = line1Angle - line2Angle;
283
+
284
+ differenceBetweenAngles = (differenceBetweenAngles + 360) % 360;
285
+
286
+ let newTargetAngle;
287
+
288
+ if (differenceBetweenAngles <= 180) {
289
+ newTargetAngle = 90;
290
+ } else {
291
+ newTargetAngle = 270;
292
+ }
293
+
294
+ //console.log("current values", differenceBetweenAngles, newTargetAngle)
295
+
296
+ return constraintFunctions["∠"](solverObject, constraint, points, newTargetAngle);
297
+ }
298
+ }).hints = {
299
+ commandTooltip: "Perpendicular Constraint",
300
+ pointsRequired: 4,
301
+ };
302
+
303
+
304
+ (constraintFunctions["∠"] = function (solverObject, constraint, points, constraintValue) {
305
+ // Angle constraint
306
+ const [p1, p2, p3, p4] = points;
307
+
308
+ const line1Angle = calculateAngle(p1, p2);
309
+ const line2Angle = calculateAngle(p3, p4);
310
+ const differenceBetweenAngles = line1Angle - line2Angle;
311
+
312
+ if (constraint.value == null) {
313
+ // Seed with the current measured angle (normalize into [0, 360))
314
+ constraint.value = roundToDecimals(normalizeAngle(differenceBetweenAngles), 4);
315
+ // return; // Don't return, allow solving to happen immediately (e.g. if constraintValue provided)
316
+ } else if (constraint.value < 0) {
317
+ constraint.value = Math.abs(constraint.value);
318
+ constraint.points = [constraint.points[2], constraint.points[3], constraint.points[1], constraint.points[0]];
319
+ return;
320
+ } else if (constraint.value > 360) {
321
+ constraint.value = normalizeAngle(constraint.value);
322
+ return;
323
+ }
324
+
325
+ const currentAngle = normalizeAngle(differenceBetweenAngles);
326
+ let desiredAngle = Number.isFinite(constraintValue) ? constraintValue : parseFloat(constraint.value);
327
+ if (!Number.isFinite(desiredAngle)) desiredAngle = currentAngle;
328
+ const targetAngle = normalizeAngle(desiredAngle);
329
+
330
+ const deltaRaw = shortestAngleDelta(targetAngle, currentAngle);
331
+
332
+ if (Math.abs(deltaRaw) < tolerance) {
333
+ constraint.error = null;
334
+ return;
335
+ }
336
+
337
+ if (Math.abs(deltaRaw) > tolerance) {
338
+ constraint.error = `Angle constraint not satisfied
339
+ ${targetAngle} != ${currentAngle}
340
+ Diff: ${Math.abs(deltaRaw).toFixed(4)}
341
+ `;
342
+ } else {
343
+ constraint.error = null;
344
+ }
345
+
346
+ let line1Moving = !(p1.fixed && p2.fixed);
347
+ let line2Moving = !(p3.fixed && p4.fixed);
348
+
349
+ // Lines that already have horizontal/vertical constraints should stay put here.
350
+ if (participateInConstraint(solverObject, "━", [p1, p2])) line1Moving = false;
351
+ if (participateInConstraint(solverObject, "━", [p3, p4])) line2Moving = false;
352
+ if (participateInConstraint(solverObject, "│", [p1, p2])) line1Moving = false;
353
+ if (participateInConstraint(solverObject, "│", [p3, p4])) line2Moving = false;
354
+
355
+ if (!line1Moving && !line2Moving) return;
356
+
357
+ const maxStep = 1.5;
358
+ let delta = deltaRaw;
359
+ if (Math.abs(delta) > maxStep) delta = Math.sign(delta) * maxStep;
360
+
361
+ let rotationLine1 = 0;
362
+ let rotationLine2 = 0;
363
+
364
+ if (line1Moving && line2Moving) {
365
+ rotationLine1 = delta / 2;
366
+ rotationLine2 = -delta / 2;
367
+ } else if (line1Moving) {
368
+ rotationLine1 = delta;
369
+ } else if (line2Moving) {
370
+ rotationLine2 = -delta;
371
+ }
372
+
373
+ if (line1Moving && rotationLine1) {
374
+ if (p1.fixed) {
375
+ rotatePoint(p1, p2, rotationLine1);
376
+ } else if (p2.fixed) {
377
+ rotatePoint(p2, p1, rotationLine1);
378
+ } else {
379
+ // Rotate around midpoint to decouple rotation from translation/length changes
380
+ const midX = (p1.x + p2.x) / 2;
381
+ const midY = (p1.y + p2.y) / 2;
382
+ const center = { x: midX, y: midY };
383
+ rotatePoint(center, p1, rotationLine1);
384
+ rotatePoint(center, p2, rotationLine1);
385
+ }
386
+ }
387
+
388
+ if (line2Moving && rotationLine2) {
389
+ if (p3.fixed) {
390
+ rotatePoint(p3, p4, rotationLine2);
391
+ } else if (p4.fixed) {
392
+ rotatePoint(p4, p3, rotationLine2);
393
+ } else {
394
+ // Rotate around midpoint
395
+ const midX = (p3.x + p4.x) / 2;
396
+ const midY = (p3.y + p4.y) / 2;
397
+ const center = { x: midX, y: midY };
398
+ rotatePoint(center, p3, rotationLine2);
399
+ rotatePoint(center, p4, rotationLine2);
400
+ }
401
+ }
402
+
403
+ return;
404
+ }).hints = {
405
+ commandTooltip: "Angle Constraint",
406
+ pointsRequired: 4,
407
+ };
408
+
409
+
410
+ (constraintFunctions["≡"] = function (solverObject, constraint, points, constraintValue) {
411
+ // Coincident constraint
412
+ const [point1, point2] = points;
413
+
414
+
415
+ if (point1.fixed && point2.fixed) {
416
+ if (participateInConstraint(solverObject, "⏚", [points[0]]) && participateInConstraint(solverObject, "⏚", [points[1]])) {
417
+ constraint.error = "Both points are fixed";
418
+ }
419
+ return;
420
+ }
421
+
422
+ if (point1.x === point2.x && point1.y === point2.y) {
423
+ // console.log("points are coincident");
424
+ constraint.error = null;
425
+ }
426
+ else {
427
+ if (!point1.fixed && !point2.fixed) {
428
+ // If both points are not fixed, average their coordinates
429
+ const avgX = (point1.x + point2.x) / 2;
430
+ const avgY = (point1.y + point2.y) / 2;
431
+ point1.x = avgX;
432
+ point1.y = avgY;
433
+ point2.x = avgX;
434
+ point2.y = avgY;
435
+ } else if (!point1.fixed) {
436
+ point1.x = point2.x;
437
+ point1.y = point2.y;
438
+ point1.fixed = true;
439
+ } else if (!point2.fixed) {
440
+ point2.x = point1.x;
441
+ point2.y = point1.y;
442
+ point2.fixed = true;
443
+ }
444
+
445
+ }
446
+ if (point1.fixed || point2.fixed) {
447
+ point1.fixed = true;
448
+ point2.fixed = true;
449
+ }
450
+ }).hints = {
451
+ commandTooltip: "Coincident Constraint",
452
+ pointsRequired: 2,
453
+ };
454
+
455
+
456
+
457
+ (constraintFunctions["⏛"] = function (solverObject, constraint, points, constraintValue) {
458
+ const [pointA, pointB, pointC] = points; // Line AB, Point C
459
+
460
+ // Vector AB
461
+ const dx = pointB.x - pointA.x;
462
+ const dy = pointB.y - pointA.y;
463
+ const lenSq = dx * dx + dy * dy;
464
+
465
+ // Handle degenerate line case (A ~= B)
466
+ if (lenSq < tolerance) {
467
+ // Treat as coincident C to A
468
+ const dist = distance(pointA, pointC);
469
+ if (dist > tolerance) {
470
+ constraint.error = `Point on Line: Line is degenerate (points too close) and Point C is not coincident.`;
471
+ // Simple coincident push
472
+ if (!pointC.fixed) {
473
+ pointC.x = pointA.x;
474
+ pointC.y = pointA.y;
475
+ } else if (!pointA.fixed) {
476
+ pointA.x = pointC.x;
477
+ pointA.y = pointC.y;
478
+ // Sync B to A since they are "coincident" in this check
479
+ if (!pointB.fixed) {
480
+ pointB.x = pointC.x;
481
+ pointB.y = pointC.y
482
+ }
483
+ }
484
+ } else {
485
+ constraint.error = null;
486
+ }
487
+ return;
488
+ }
489
+
490
+ // Project C onto line AB
491
+ // t = Dot(AC, AB) / |AB|^2
492
+ const t = ((pointC.x - pointA.x) * dx + (pointC.y - pointA.y) * dy) / lenSq;
493
+
494
+ // Closest Point on Line
495
+ const projX = pointA.x + t * dx;
496
+ const projY = pointA.y + t * dy;
497
+
498
+ // Error Vector (C -> Proj)
499
+ // We want C to be at Proj. So Error = C - Proj.
500
+ const errX = pointC.x - projX;
501
+ const errY = pointC.y - projY;
502
+ const errDist = Math.sqrt(errX * errX + errY * errY);
503
+
504
+ if (errDist < tolerance) {
505
+ constraint.error = null;
506
+ return;
507
+ }
508
+
509
+ constraint.error = `Point on Line not satisfied. Dist: ${errDist.toFixed(4)}`;
510
+
511
+ // Gradients / Distribution
512
+ // To minimize Error^2 = (Cx - Ax - t*dx)^2 + ...
513
+ // Standard iterative geometric projection:
514
+ // Move C towards Proj.
515
+ // Move Line towards C.
516
+
517
+ // Weighting:
518
+ // If all movable, we distribute the move.
519
+ // C moves by -Error.
520
+ // A moves by +Error * (1-t).
521
+ // B moves by +Error * t.
522
+ // (This effectively rotates/moves the line based on the lever arm t)
523
+
524
+ let wA = !pointA.fixed ? 1 : 0;
525
+ let wB = !pointB.fixed ? 1 : 0;
526
+ let wC = !pointC.fixed ? 1 : 0;
527
+
528
+ // Normalize weights?
529
+ // Actually, we can just apply the delta directly with a damping/learning rate or just full Newton step geometry.
530
+ // Geometric projection is usually stable with full steps if not conflicting.
531
+ // However, since we have 3 points sharing the error correction:
532
+ // If we move C full step, error is 0. If we move A/B full step, error is 0.
533
+ // We should split it.
534
+
535
+ // Simplified: Just assume roughly equal contribution capability?
536
+ // Let's use specific "Position Based Dynamics" style constraints.
537
+ // Inverse Masses: wA, wB, wC.
538
+ // Jacobian J for C is N (normal). J for A is -(1-t)N. J for B is -tN.
539
+ // Lambda = -Constraint / sum(w * J^2).
540
+ // J^2 roughly 1 for C. (1-t)^2 for A. t^2 for B.
541
+
542
+ let denom = 0;
543
+ if (!pointC.fixed) denom += 1;
544
+ if (!pointA.fixed) denom += (1 - t) * (1 - t);
545
+ if (!pointB.fixed) denom += t * t;
546
+
547
+ if (denom === 0) return; // All fixed
548
+
549
+ // Relaxation factor (can use 1.0 for direct projection, but 0.8 helps stability)
550
+ const k = 1.0;
551
+
552
+ // Vector to correct error: (-errX, -errY)
553
+ // C contributes: 1 * deltaC = -err
554
+ // A contributes: -(1-t) * deltaA = -err
555
+ // B contributes: -t * deltaB = -err
556
+
557
+ // Common scalar lambda
558
+ // We want sum(changes) to cancel error.
559
+ // Actually, let's just use the direct formulas from PBD for Point-Segment distance.
560
+ // corrC = - (w_c / sum) * error
561
+ // corrA = + (w_a * (1-t) / sum) * error
562
+ // corrB = + (w_a * t / sum) * error
563
+
564
+ // In our case error vector E = (errX, errY) = C - Proj.
565
+ // We want to displace points so C' becomes Proj'.
566
+
567
+ const factor = k / denom;
568
+
569
+ // Parallel expansion force (from analytical gradient of distance metric)
570
+ // Helps avoid line collapse by encouraging length increase to reduce angular error.
571
+ // Magnitude ~ Error / Length.
572
+ // Only apply if we are moving the line points.
573
+ let expansionX = 0;
574
+ let expansionY = 0;
575
+ if ((!pointA.fixed || !pointB.fixed) && lenSq > tolerance) {
576
+ // Direction B-A
577
+ const len = Math.sqrt(lenSq);
578
+ const ux = dx / len;
579
+ const uy = dy / len;
580
+
581
+ // Force magnitude: errDist / len.
582
+ // We dampen it slightly to avoid over-expansion instabilities.
583
+ const expForce = (errDist / len) * 0.1 * factor;
584
+
585
+ expansionX = ux * expForce;
586
+ expansionY = uy * expForce;
587
+ }
588
+
589
+ if (!pointC.fixed) {
590
+ pointC.x -= errX * factor;
591
+ pointC.y -= errY * factor;
592
+ }
593
+
594
+ if (!pointA.fixed) {
595
+ pointA.x += errX * (1 - t) * factor;
596
+ pointA.y += errY * (1 - t) * factor;
597
+
598
+ // Push A away from B (negative dir)
599
+ pointA.x -= expansionX;
600
+ pointA.y -= expansionY;
601
+ }
602
+
603
+ if (!pointB.fixed) {
604
+ pointB.x += errX * t * factor;
605
+ pointB.y += errY * t * factor;
606
+
607
+ // Push B away from A (positive dir)
608
+ pointB.x += expansionX;
609
+ pointB.y += expansionY;
610
+ }
611
+ }).hints = {
612
+ commandTooltip: "Point on Line Constraint",
613
+ pointsRequired: 3,
614
+ };
615
+
616
+ // Midpoint constraint with bidirectional update
617
+ (constraintFunctions["⋯"] = function (solverObject, constraint, points, constraintValue) {
618
+ // Gracefully change the name of the constraint to upgrade from old files if needed
619
+ if (constraint.type === "⋱") constraint.type = "⋯";
620
+
621
+ const [pointA, pointB, pointC] = points; // C is the midpoint of A and B
622
+
623
+ // Constraint equation: 2*C - A - B = 0
624
+ // We treat this as a vector equation and project the error.
625
+
626
+ // Calculate current residual (error)
627
+ const rx = 2 * pointC.x - pointA.x - pointB.x;
628
+ const ry = 2 * pointC.y - pointA.y - pointB.y;
629
+
630
+ // Check satisfaction
631
+ if (Math.abs(rx) < tolerance && Math.abs(ry) < tolerance) {
632
+ constraint.error = null;
633
+ return;
634
+ }
635
+
636
+ constraint.error = `Midpoint constraint not satisfied. Error: ${Math.hypot(rx, ry).toFixed(4)}`;
637
+
638
+ // Gradients of internal function f = 2C - A - B
639
+ // grad(C) = 2, grad(A) = -1, grad(B) = -1
640
+ // We assume equal weights for "movability" but respect fixed status.
641
+
642
+ let denom = 0;
643
+ if (!pointA.fixed) denom += 1; // (-1)^2
644
+ if (!pointB.fixed) denom += 1; // (-1)^2
645
+ if (!pointC.fixed) denom += 4; // (2)^2
646
+
647
+ if (denom === 0) {
648
+ constraint.error = "All points fixed in Midpoint constraint";
649
+ return;
650
+ }
651
+
652
+ // Lagrange multiplier step (Newton step for linear constraint)
653
+ // alpha = - Error / sum(grad^2)
654
+ const alphaX = -rx / denom;
655
+ const alphaY = -ry / denom;
656
+
657
+ // Update points
658
+ // New Pos = Old Pos + alpha * grad
659
+ if (!pointA.fixed) {
660
+ pointA.x += alphaX * (-1);
661
+ pointA.y += alphaY * (-1);
662
+ }
663
+ if (!pointB.fixed) {
664
+ pointB.x += alphaX * (-1);
665
+ pointB.y += alphaY * (-1);
666
+ }
667
+ if (!pointC.fixed) {
668
+ pointC.x += alphaX * (2);
669
+ pointC.y += alphaY * (2);
670
+ }
671
+
672
+ }).hints = {
673
+ commandTooltip: "Midpoint Constraint",
674
+ pointsRequired: 3,
675
+ };
676
+
677
+ //gracefully change the name of the constraint
678
+ //constraintFunctions["⋱"] = constraintFunctions["⋯"];
679
+
680
+
681
+
682
+ (constraintFunctions["⏚"] = function (solverObject, constraint, points, constraintValue) {
683
+ // Fixed constraint
684
+ points[0].fixed = true;
685
+ }).hints = {
686
+ commandTooltip: "Fix Point",
687
+ pointsRequired: 1,
688
+ };
689
+
690
+
691
+ export const constraints = {
692
+ tolerance,
693
+ constraintFunctions,
694
+ }
695
+
696
+
697
+
698
+
699
+
700
+ function participateInConstraint(solverObject, constraintType, points) {
701
+ return solverObject.constraints.some(c => {
702
+ return c.type === constraintType && points.every(point => c.points.includes(point.id));
703
+ });
704
+ }