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,616 @@
1
+ import * as THREE from 'three';
2
+ import { BaseAssemblyConstraint } from '../BaseAssemblyConstraint.js';
3
+ import { solveParallelAlignment, resolveParallelSelection } from '../constraintUtils/parallelAlignment.js';
4
+ import { objectRepresentativePoint, getElementDirection } from '../../UI/pmi/annUtils.js';
5
+
6
+ const DEFAULT_DISTANCE_TOLERANCE = 1e-6;
7
+
8
+ const inputParamsSchema = {
9
+ id: {
10
+ type: 'string',
11
+ default_value: null,
12
+ hint: 'Unique identifier for the constraint.',
13
+ },
14
+ elements: {
15
+ type: 'reference_selection',
16
+ label: 'Elements',
17
+ hint: 'Select two references (point, edge, face, or component).',
18
+ selectionFilter: ['FACE', 'VERTEX', 'EDGE'],
19
+ multiple: true,
20
+ minSelections: 2,
21
+ maxSelections: 2,
22
+ },
23
+ distance: {
24
+ type: 'number',
25
+ label: 'Distance',
26
+ default_value: 0,
27
+ hint: 'Desired separation between references.',
28
+ },
29
+ opposeNormals: {
30
+ type: 'boolean',
31
+ label: 'Oppose Normals',
32
+ default_value: false,
33
+ hint: 'For face-to-face mode, flip Element B normal before alignment.',
34
+ },
35
+ };
36
+ export class DistanceConstraint extends BaseAssemblyConstraint {
37
+ static shortName = '⟺';
38
+ static longName = '⟺ Distance Constraint';
39
+ static constraintType = 'distance';
40
+ static aliases = ['distance', 'offset', 'gap', 'DIST'];
41
+ static inputParamsSchema = inputParamsSchema;
42
+
43
+ constructor(partHistory) {
44
+ super(partHistory);
45
+ this._debugHelpers = [];
46
+ }
47
+
48
+ async solve(context = {}) {
49
+ const pd = this.persistentData = this.persistentData || {};
50
+ const [selA, selB] = selectionPair(this.inputParams);
51
+ const targetDistanceRaw = Number(this.inputParams.distance ?? 0);
52
+ const targetDistance = Number.isFinite(targetDistanceRaw) ? Math.max(0, targetDistanceRaw) : 0;
53
+
54
+ if ((context.iteration ?? 0) === 0) {
55
+ this.#clearDebug(context.scene || null);
56
+ }
57
+
58
+ if (!selA || !selB) {
59
+ pd.status = 'incomplete';
60
+ pd.message = 'Select two references to define the constraint.';
61
+ pd.satisfied = false;
62
+ pd.error = null;
63
+ pd.lastAppliedMoves = [];
64
+ pd.lastAppliedRotations = [];
65
+ return { ok: false, status: 'incomplete', satisfied: false, applied: false, message: pd.message };
66
+ }
67
+
68
+ const tolerance = Math.max(Math.abs(context.tolerance ?? DEFAULT_DISTANCE_TOLERANCE), 1e-8);
69
+ const translationGain = Math.max(0, Math.min(1, context.translationGain ?? 1));
70
+
71
+ const faceFace = this.#isFaceFace(context, selA, selB);
72
+ let infoA;
73
+ let infoB;
74
+ let parallelResult = null;
75
+
76
+ if (faceFace) {
77
+ parallelResult = solveParallelAlignment({
78
+ constraint: this,
79
+ context,
80
+ selectionA: selA,
81
+ selectionB: selB,
82
+ opposeNormals: !!this.inputParams.opposeNormals,
83
+ selectionLabelA: 'elements[0]',
84
+ selectionLabelB: 'elements[1]',
85
+ });
86
+
87
+ infoA = parallelResult.infoA || null;
88
+ infoB = parallelResult.infoB || null;
89
+
90
+ pd.lastAppliedRotations = Array.isArray(parallelResult.rotations) ? parallelResult.rotations : [];
91
+
92
+ if (context.debugMode && infoA && infoB) {
93
+ this.#updateNormalDebug(context, infoA, infoB);
94
+ }
95
+
96
+ if (!parallelResult.ok) {
97
+ pd.status = parallelResult.status;
98
+ pd.message = parallelResult.message || '';
99
+ pd.satisfied = false;
100
+ pd.error = parallelResult.error ?? null;
101
+ pd.errorDeg = parallelResult.angleDeg ?? null;
102
+ pd.exception = parallelResult.exception || null;
103
+ pd.lastAppliedMoves = [];
104
+ return { ...parallelResult, stage: 'orientation' };
105
+ }
106
+
107
+ if (!parallelResult.satisfied) {
108
+ pd.status = parallelResult.status;
109
+ pd.message = parallelResult.message || 'Aligning surfaces…';
110
+ pd.satisfied = false;
111
+ pd.error = parallelResult.angle ?? null;
112
+ pd.errorDeg = parallelResult.angleDeg ?? null;
113
+ pd.lastAppliedMoves = [];
114
+ if (pd.exception) delete pd.exception;
115
+ return { ...parallelResult, stage: 'orientation' };
116
+ }
117
+ } else {
118
+ infoA = this.#resolveSelectionInfo(context, selA, 'elements[0]');
119
+ infoB = this.#resolveSelectionInfo(context, selB, 'elements[1]');
120
+ }
121
+
122
+ if (!infoA || !infoB || !infoA.origin || !infoB.origin) {
123
+ const message = 'Unable to resolve world-space references for the selection.';
124
+ pd.status = 'invalid-selection';
125
+ pd.message = message;
126
+ pd.satisfied = false;
127
+ pd.error = null;
128
+ pd.lastAppliedMoves = [];
129
+ return { ok: false, status: 'invalid-selection', satisfied: false, applied: false, message };
130
+ }
131
+
132
+ const measurement = this.#measureDistance(infoA, infoB, targetDistance, tolerance);
133
+ if (!measurement || !Number.isFinite(measurement.distance)) {
134
+ const message = 'Unable to measure distance between selections.';
135
+ pd.status = 'invalid-selection';
136
+ pd.message = message;
137
+ pd.satisfied = false;
138
+ pd.error = null;
139
+ pd.lastAppliedMoves = [];
140
+ return { ok: false, status: 'invalid-selection', satisfied: false, applied: false, message };
141
+ }
142
+
143
+ const distance = measurement.distance;
144
+ const error = distance - targetDistance;
145
+
146
+ const fixedA = context.isComponentFixed?.(infoA.component);
147
+ const fixedB = context.isComponentFixed?.(infoB.component);
148
+
149
+ pd.error = Math.abs(error);
150
+ pd.errorDeg = null;
151
+
152
+ if (Math.abs(error) <= tolerance) {
153
+ const message = 'Distance satisfied within tolerance.';
154
+ pd.status = 'satisfied';
155
+ pd.message = message;
156
+ pd.satisfied = true;
157
+ pd.lastAppliedMoves = [];
158
+ if (pd.exception) delete pd.exception;
159
+ return {
160
+ ok: true,
161
+ status: 'satisfied',
162
+ satisfied: true,
163
+ applied: false,
164
+ error: Math.abs(error),
165
+ message,
166
+ infoA,
167
+ infoB,
168
+ diagnostics: { distance, targetDistance, error, stage: faceFace ? 'offset' : 'general' },
169
+ };
170
+ }
171
+
172
+ if (fixedA && fixedB) {
173
+ const message = 'Both components are fixed; unable to adjust distance.';
174
+ pd.status = 'blocked';
175
+ pd.message = message;
176
+ pd.satisfied = false;
177
+ pd.lastAppliedMoves = [];
178
+ if (pd.exception) delete pd.exception;
179
+ return {
180
+ ok: false,
181
+ status: 'blocked',
182
+ satisfied: false,
183
+ applied: false,
184
+ error: Math.abs(error),
185
+ message,
186
+ infoA,
187
+ infoB,
188
+ diagnostics: { distance, targetDistance, error, stage: faceFace ? 'offset' : 'general' },
189
+ };
190
+ }
191
+
192
+ const dirs = measurement.directions;
193
+ const moves = [];
194
+ let applied = false;
195
+
196
+ const applyCorrection = (info, dir, share) => {
197
+ if (!info?.component || !dir || dir.lengthSq() === 0 || share === 0) return false;
198
+ const move = dir.clone().normalize().multiplyScalar(share * translationGain * (targetDistance - distance));
199
+ if (move.lengthSq() === 0) return false;
200
+ const ok = context.applyTranslation?.(info.component, move);
201
+ if (ok) {
202
+ moves.push({ component: info.component.name || info.component.uuid, move: vectorToArray(move) });
203
+ }
204
+ return ok;
205
+ };
206
+
207
+ const movableA = !fixedA && infoA.component;
208
+ const movableB = !fixedB && infoB.component;
209
+
210
+ if (movableA && movableB) {
211
+ applied = applyCorrection(infoA, dirs.increaseA, 0.5) || applied;
212
+ applied = applyCorrection(infoB, dirs.increaseB, 0.5) || applied;
213
+ } else if (movableA && !movableB) {
214
+ applied = applyCorrection(infoA, dirs.increaseA, 1) || applied;
215
+ } else if (!movableA && movableB) {
216
+ applied = applyCorrection(infoB, dirs.increaseB, 1) || applied;
217
+ }
218
+
219
+ const status = applied ? 'adjusted' : 'pending';
220
+ const message = applied
221
+ ? 'Applied translation to move toward target distance.'
222
+ : 'Waiting for a movable component to translate.';
223
+
224
+ pd.status = status;
225
+ pd.message = message;
226
+ pd.satisfied = false;
227
+ pd.lastAppliedMoves = moves;
228
+ if (pd.exception) delete pd.exception;
229
+
230
+ return {
231
+ ok: true,
232
+ status,
233
+ satisfied: false,
234
+ applied,
235
+ error: Math.abs(error),
236
+ message,
237
+ infoA,
238
+ infoB,
239
+ diagnostics: {
240
+ distance,
241
+ targetDistance,
242
+ error,
243
+ moves,
244
+ stage: faceFace ? 'offset' : 'general',
245
+ },
246
+ };
247
+ }
248
+
249
+ async run(context = {}) {
250
+ return this.solve(context);
251
+ }
252
+
253
+ #isFaceFace(context, selA, selB) {
254
+ const objA = context.resolveObject?.(selA) || null;
255
+ const objB = context.resolveObject?.(selB) || null;
256
+ const kindA = selectionKindFrom(objA, selA);
257
+ const kindB = selectionKindFrom(objB, selB);
258
+ return kindA === 'FACE' && kindB === 'FACE';
259
+ }
260
+
261
+ #resolveSelectionInfo(context, selection, label) {
262
+ const object = context.resolveObject?.(selection) || null;
263
+ const component = context.resolveComponent?.(selection) || null;
264
+ const kind = selectionKindFrom(object, selection);
265
+
266
+ if (kind === 'FACE') {
267
+ return resolveParallelSelection(this, context, selection, label);
268
+ }
269
+
270
+ const origin = this.#resolveOrigin(object, component) || new THREE.Vector3();
271
+ let direction = null;
272
+
273
+ if (kind === 'EDGE') {
274
+ direction = this.#resolveEdgeDirection(object, component);
275
+ }
276
+
277
+ return {
278
+ selection,
279
+ object,
280
+ component: component || null,
281
+ origin,
282
+ direction,
283
+ kind,
284
+ };
285
+ }
286
+
287
+ #resolveOrigin(object, component) {
288
+ if (object) {
289
+ try {
290
+ const rep = objectRepresentativePoint(null, object);
291
+ if (rep && typeof rep.clone === 'function') return rep.clone();
292
+ } catch { }
293
+ if (typeof object.getWorldPosition === 'function') {
294
+ return object.getWorldPosition(new THREE.Vector3());
295
+ }
296
+ if (object.isVector3) return object.clone();
297
+ }
298
+ if (component) {
299
+ component.updateMatrixWorld?.(true);
300
+ if (typeof component.getWorldPosition === 'function') {
301
+ return component.getWorldPosition(new THREE.Vector3());
302
+ }
303
+ if (component.position) {
304
+ const pos = component.position.clone();
305
+ component.parent?.updateMatrixWorld?.(true);
306
+ if (component.parent?.matrixWorld) {
307
+ return pos.applyMatrix4(component.parent.matrixWorld.clone());
308
+ }
309
+ return pos;
310
+ }
311
+ }
312
+ return null;
313
+ }
314
+
315
+ #resolveEdgeDirection(object, component) {
316
+ const dir = getElementDirection(null, object);
317
+ if (dir && dir.lengthSq() > 0) return dir.clone().normalize();
318
+ if (component) {
319
+ const compDir = getElementDirection(null, component);
320
+ if (compDir && compDir.lengthSq() > 0) return compDir.clone().normalize();
321
+ }
322
+ const geom = object?.geometry;
323
+ if (geom?.getAttribute) {
324
+ const pos = geom.getAttribute('position');
325
+ if (pos && pos.count >= 2) {
326
+ const a = new THREE.Vector3(pos.getX(0), pos.getY(0), pos.getZ(0));
327
+ const b = new THREE.Vector3(pos.getX(1), pos.getY(1), pos.getZ(1));
328
+ object.updateMatrixWorld?.(true);
329
+ a.applyMatrix4(object.matrixWorld);
330
+ b.applyMatrix4(object.matrixWorld);
331
+ return b.sub(a).normalize();
332
+ }
333
+ }
334
+ return null;
335
+ }
336
+
337
+ #measureDistance(infoA, infoB, targetDistance, tolerance) {
338
+ const kindA = (infoA.kind || selectionKindFrom(infoA.object, infoA.selection)).toUpperCase?.()
339
+ || selectionKindFrom(infoA.object, infoA.selection);
340
+ const kindB = (infoB.kind || selectionKindFrom(infoB.object, infoB.selection)).toUpperCase?.()
341
+ || selectionKindFrom(infoB.object, infoB.selection);
342
+
343
+ if (kindA === 'FACE' && kindB === 'FACE') {
344
+ return this.#faceFaceDistance(infoA, infoB);
345
+ }
346
+ if (kindA === 'FACE' && kindB !== 'FACE') {
347
+ return this.#facePointLikeDistance(infoA, infoB);
348
+ }
349
+ if (kindB === 'FACE' && kindA !== 'FACE') {
350
+ const swap = this.#facePointLikeDistance(infoB, infoA);
351
+ if (!swap) return null;
352
+ return {
353
+ distance: swap.distance,
354
+ directions: {
355
+ increaseA: swap.directions.increaseB,
356
+ increaseB: swap.directions.increaseA,
357
+ },
358
+ };
359
+ }
360
+ if (kindA === 'EDGE' && kindB === 'POINT') {
361
+ return this.#pointEdgeDistance(infoB, infoA, true);
362
+ }
363
+ if (kindA === 'POINT' && kindB === 'EDGE') {
364
+ return this.#pointEdgeDistance(infoA, infoB, false);
365
+ }
366
+ if (kindA === 'EDGE' && kindB === 'EDGE') {
367
+ return this.#edgeEdgeDistance(infoA, infoB);
368
+ }
369
+ return this.#pointPointDistance(infoA, infoB);
370
+ }
371
+
372
+ #pointPointDistance(infoA, infoB) {
373
+ const delta = new THREE.Vector3().subVectors(infoB.origin, infoA.origin);
374
+ const dist = delta.length();
375
+ const dir = dist > 1e-9 ? delta.clone().divideScalar(dist) : new THREE.Vector3(1, 0, 0);
376
+ return {
377
+ distance: dist,
378
+ directions: {
379
+ increaseA: dir.clone().negate(),
380
+ increaseB: dir,
381
+ },
382
+ };
383
+ }
384
+
385
+ #pointEdgeDistance(pointInfo, edgeInfo, swapped) {
386
+ const edgeDir = normalizeOrNull(edgeInfo.direction) || new THREE.Vector3(1, 0, 0);
387
+ const linePoint = edgeInfo.origin.clone();
388
+ const point = pointInfo.origin.clone();
389
+
390
+ const delta = point.clone().sub(linePoint);
391
+ const proj = delta.dot(edgeDir);
392
+ const closest = linePoint.clone().add(edgeDir.clone().multiplyScalar(proj));
393
+ const sep = point.clone().sub(closest);
394
+ let dist = sep.length();
395
+ let normal = dist > 1e-9 ? sep.clone().divideScalar(dist) : arbitraryPerpendicular(edgeDir);
396
+ dist = Math.abs(dist);
397
+
398
+ return swapped
399
+ ? {
400
+ distance: dist,
401
+ directions: {
402
+ increaseA: normal.clone().negate(),
403
+ increaseB: normal.clone(),
404
+ },
405
+ }
406
+ : {
407
+ distance: dist,
408
+ directions: {
409
+ increaseA: normal.clone(),
410
+ increaseB: normal.clone().negate(),
411
+ },
412
+ };
413
+ }
414
+
415
+ #edgeEdgeDistance(infoA, infoB) {
416
+ const dirA = normalizeOrNull(infoA.direction);
417
+ const dirB = normalizeOrNull(infoB.direction);
418
+ if (!dirA || !dirB) return null;
419
+
420
+ const p1 = infoA.origin.clone();
421
+ const p2 = infoB.origin.clone();
422
+ const r = dirA.clone();
423
+ const s = dirB.clone();
424
+ const w0 = p1.clone().sub(p2);
425
+
426
+ const a = r.dot(r);
427
+ const b = r.dot(s);
428
+ const c = s.dot(s);
429
+ const d = r.dot(w0);
430
+ const e = s.dot(w0);
431
+ const denom = a * c - b * b;
432
+
433
+ let sc;
434
+ let tc;
435
+ if (Math.abs(denom) < 1e-8) {
436
+ sc = 0;
437
+ tc = (b > c ? d / b : e / c);
438
+ } else {
439
+ sc = (b * e - c * d) / denom;
440
+ tc = (a * e - b * d) / denom;
441
+ }
442
+
443
+ const closestA = p1.clone().add(r.clone().multiplyScalar(sc));
444
+ const closestB = p2.clone().add(s.clone().multiplyScalar(tc));
445
+ const sep = closestB.clone().sub(closestA);
446
+ const dist = sep.length();
447
+ const dir = dist > 1e-9 ? sep.clone().divideScalar(dist) : arbitraryPerpendicular(r);
448
+
449
+ return {
450
+ distance: dist,
451
+ directions: {
452
+ increaseA: dir.clone().negate(),
453
+ increaseB: dir,
454
+ },
455
+ };
456
+ }
457
+
458
+ #faceFaceDistance(infoA, infoB) {
459
+ const normalA = normalizeOrNull(infoA.direction);
460
+ const originA = infoA.origin.clone();
461
+ const originB = infoB.origin.clone();
462
+ if (!normalA) return null;
463
+
464
+ const delta = originB.clone().sub(originA);
465
+ let separation = delta.dot(normalA);
466
+ if (!Number.isFinite(separation)) separation = 0;
467
+
468
+ const dir = separation >= 0 ? normalA.clone() : normalA.clone().negate();
469
+ const distance = Math.abs(separation);
470
+
471
+ return {
472
+ distance,
473
+ directions: {
474
+ increaseA: dir.clone().negate(),
475
+ increaseB: dir,
476
+ },
477
+ };
478
+ }
479
+
480
+ #facePointLikeDistance(faceInfo, otherInfo) {
481
+ const normal = normalizeOrNull(faceInfo.direction);
482
+ if (!normal) return null;
483
+ const originFace = faceInfo.origin.clone();
484
+ const originOther = otherInfo.origin.clone();
485
+
486
+ const delta = originOther.clone().sub(originFace);
487
+ const separation = delta.dot(normal);
488
+ const distance = Math.abs(separation);
489
+ const dir = separation >= 0 ? normal.clone() : normal.clone().negate();
490
+
491
+ return {
492
+ distance,
493
+ directions: {
494
+ increaseA: dir.clone().negate(),
495
+ increaseB: dir,
496
+ },
497
+ };
498
+ }
499
+
500
+ #updateNormalDebug(context, infoA, infoB) {
501
+ if (!context?.debugMode) return;
502
+ const scene = context.scene || null;
503
+ if (!scene) return;
504
+
505
+ const iteration = context.iteration ?? 0;
506
+ const entries = [
507
+ { info: infoA, color: 0xffa64d, label: 'A' },
508
+ { info: infoB, color: 0x4dc3ff, label: 'B' },
509
+ ];
510
+
511
+ for (const { info, color, label } of entries) {
512
+ if (!info?.direction || !info.origin) continue;
513
+ const dir = info.direction.clone().normalize();
514
+ if (dir.lengthSq() === 0) continue;
515
+
516
+ const origin = info.origin.clone();
517
+ const length = Math.max(this.#estimateHelperLength(info), 10);
518
+ const arrow = new THREE.ArrowHelper(dir, origin, length, color, length * 0.25, length * 0.15);
519
+ const constraintId = this.inputParams?.id ?? this.inputParams?.constraintID ?? 'unknown';
520
+ arrow.name = `distance-constraint-normal-${constraintId}-${label}-iter${iteration}`;
521
+ scene.add(arrow);
522
+ this._debugHelpers.push(arrow);
523
+ }
524
+ }
525
+
526
+ #clearDebug(scene) {
527
+ if (!this._debugHelpers) return;
528
+ for (const helper of this._debugHelpers) {
529
+ if (!helper) continue;
530
+ if (scene && helper.parent === scene) {
531
+ scene.remove(helper);
532
+ } else if (helper.parent) {
533
+ helper.parent.remove(helper);
534
+ }
535
+ }
536
+ this._debugHelpers.length = 0;
537
+ }
538
+
539
+ #estimateHelperLength(info) {
540
+ const candidates = [];
541
+ const pushBound = (obj) => {
542
+ if (!obj) return;
543
+ if (obj.geometry?.computeBoundingSphere && !obj.geometry.boundingSphere) {
544
+ try { obj.geometry.computeBoundingSphere(); } catch { }
545
+ }
546
+ const sphere = obj.geometry?.boundingSphere;
547
+ if (sphere?.radius) candidates.push(Math.abs(sphere.radius));
548
+ if (obj.geometry?.computeBoundingBox && !obj.geometry.boundingBox) {
549
+ try { obj.geometry.computeBoundingBox(); } catch { }
550
+ }
551
+ const box = obj.geometry?.boundingBox;
552
+ if (box) candidates.push(box.getSize(new THREE.Vector3()).length() / 2);
553
+ if (typeof obj.getWorldScale === 'function') {
554
+ const scale = obj.getWorldScale(new THREE.Vector3());
555
+ candidates.push(scale.length() * 5);
556
+ }
557
+ };
558
+
559
+ pushBound(info.object);
560
+ if (Array.isArray(info.component?.children)) {
561
+ for (const child of info.component.children) {
562
+ pushBound(child);
563
+ }
564
+ }
565
+
566
+ candidates.push(info.component?.userData?.boundingRadius || 0);
567
+
568
+ const max = candidates.reduce((acc, val) => (Number.isFinite(val) ? Math.max(acc, val) : acc), 0);
569
+ return Number.isFinite(max) && max > 0 ? max : 0;
570
+ }
571
+ }
572
+
573
+
574
+ function selectionPair(params) {
575
+ if (!params || typeof params !== 'object') return [null, null];
576
+ const raw = Array.isArray(params.elements) ? params.elements : [];
577
+ const picks = raw.filter((item) => item != null).slice(0, 2);
578
+ params.elements = picks;
579
+ if (picks.length === 2) return picks;
580
+ if (picks.length === 1) return [picks[0], null];
581
+ return [null, null];
582
+ }
583
+
584
+ function vectorToArray(vec) {
585
+ if (!vec) return [0, 0, 0];
586
+ return [vec.x, vec.y, vec.z];
587
+ }
588
+
589
+ function normalizeOrNull(vec) {
590
+ if (!vec) return null;
591
+ if (vec.lengthSq() === 0) return null;
592
+ return vec.normalize();
593
+ }
594
+
595
+ function arbitraryPerpendicular(dir) {
596
+ if (!dir || dir.lengthSq() === 0) return new THREE.Vector3(0, 0, 1);
597
+ const axis = Math.abs(dir.dot(new THREE.Vector3(0, 0, 1))) < 0.9
598
+ ? new THREE.Vector3(0, 0, 1)
599
+ : new THREE.Vector3(0, 1, 0);
600
+ const perp = new THREE.Vector3().crossVectors(dir, axis);
601
+ if (perp.lengthSq() === 0) {
602
+ perp.crossVectors(dir, new THREE.Vector3(1, 0, 0));
603
+ }
604
+ return perp.lengthSq() === 0 ? new THREE.Vector3(1, 0, 0) : perp.normalize();
605
+ }
606
+
607
+ function selectionKindFrom(object, selection) {
608
+ const val = (selection && typeof selection.kind === 'string') ? selection.kind : null;
609
+ const raw = (object?.userData?.type || object?.userData?.brepType || object?.type || val || '').toString().toUpperCase();
610
+ if (!raw) return 'UNKNOWN';
611
+ if (raw.includes('FACE')) return 'FACE';
612
+ if (raw.includes('EDGE')) return 'EDGE';
613
+ if (raw.includes('VERTEX') || raw.includes('POINT')) return 'POINT';
614
+ if (raw.includes('COMPONENT')) return 'COMPONENT';
615
+ return raw;
616
+ }
@@ -0,0 +1,78 @@
1
+ import { BaseAssemblyConstraint } from '../BaseAssemblyConstraint.js';
2
+
3
+ const inputParamsSchema = {
4
+ id: {
5
+ type: 'string',
6
+ default_value: null,
7
+ hint: 'Unique identifier for the constraint.',
8
+ },
9
+ component: {
10
+ type: 'reference_selection',
11
+ label: 'Component',
12
+ hint: 'Select the component that should remain fixed in the assembly.',
13
+ selectionFilter: ['COMPONENT'],
14
+ },
15
+ };
16
+
17
+
18
+ export class FixedConstraint extends BaseAssemblyConstraint {
19
+ static shortName = '⏚';
20
+ static longName = '⏚ Fixed Constraint';
21
+ static constraintType = 'fixed';
22
+ static aliases = ['fix', 'fixed constraint', 'fixed', 'FIXD'];
23
+ static inputParamsSchema = inputParamsSchema;
24
+
25
+ async solve(context = {}) {
26
+ const pd = this.persistentData = this.persistentData || {};
27
+ const selection = firstSelection(this.inputParams.component);
28
+ const component = context.resolveComponent?.(selection) || context.resolveObject?.(selection);
29
+
30
+ if (!component || !component.isAssemblyComponent) {
31
+ pd.status = 'incomplete';
32
+ pd.message = 'Select an assembly component to fix in place.';
33
+ pd.satisfied = false;
34
+ return { ok: false, status: 'incomplete', satisfied: false, applied: false, message: pd.message };
35
+ }
36
+
37
+ const wasFixed = context.isComponentFixed ? context.isComponentFixed(component) : !!component.fixed;
38
+
39
+ component.fixed = true;
40
+ component.userData = component.userData || {};
41
+ component.userData.fixedByConstraint = true;
42
+
43
+ const feature = context.getFeatureForComponent?.(component);
44
+ if (feature) {
45
+ feature.inputParams = feature.inputParams || {};
46
+ feature.inputParams.isFixed = true;
47
+ }
48
+
49
+ const message = wasFixed
50
+ ? 'Component already marked as fixed.'
51
+ : 'Component locked in place by constraint.';
52
+
53
+ pd.status = 'satisfied';
54
+ pd.message = message;
55
+ pd.satisfied = true;
56
+ pd.error = 0;
57
+ pd.componentName = component.name || component.owningFeatureID || null;
58
+
59
+ return {
60
+ ok: true,
61
+ status: 'satisfied',
62
+ satisfied: true,
63
+ applied: !wasFixed,
64
+ error: 0,
65
+ message,
66
+ };
67
+ }
68
+
69
+ async run(context = {}) {
70
+ return this.solve(context);
71
+ }
72
+ }
73
+
74
+
75
+ function firstSelection(value) {
76
+ if (!value) return null;
77
+ return Array.isArray(value) ? value.find((item) => item != null) ?? null : value;
78
+ }