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,676 @@
1
+ import * as THREE from 'three';
2
+ import { objectRepresentativePoint, getElementDirection } from '../../UI/pmi/annUtils.js';
3
+
4
+ export const ANGLE_TOLERANCE = THREE.MathUtils.degToRad(0.001);
5
+ export const MAX_ROTATION_PER_ITERATION = THREE.MathUtils.degToRad(10);
6
+ const MAX_EDGE_SAMPLE_POINTS = 256;
7
+ const DEFAULT_PARALLEL_ALIGNMENT_TOLERANCE = 1e-12;
8
+
9
+ function getWorldNormal(object) {
10
+ if (!object) return null;
11
+ object.updateMatrixWorld?.(true);
12
+
13
+ if (typeof object.getAverageNormal === 'function') {
14
+ try {
15
+ const avg = object.getAverageNormal();
16
+ if (avg && avg.lengthSq() > 1e-10) {
17
+ return avg.clone().normalize();
18
+ }
19
+ } catch {}
20
+ }
21
+
22
+ return computeNormalFromObject(object);
23
+ }
24
+
25
+ function resolveOrigin(object, component) {
26
+ let origin = null;
27
+ if (object) {
28
+ try {
29
+ origin = objectRepresentativePoint(null, object);
30
+ } catch {}
31
+ }
32
+ if (!origin && component) {
33
+ try {
34
+ origin = objectRepresentativePoint(null, component);
35
+ } catch {}
36
+ if (!origin && typeof component.getWorldPosition === 'function') {
37
+ origin = component.getWorldPosition(new THREE.Vector3());
38
+ }
39
+ }
40
+ return origin || null;
41
+ }
42
+
43
+ function computeNormalFromObject(object, depth = 0) {
44
+ if (!object || depth > 3) return null;
45
+
46
+ const geometry = object.geometry;
47
+ if (geometry && geometry.isBufferGeometry) {
48
+ const normal = computeNormalFromGeometry(object, geometry);
49
+ if (normal) return normal;
50
+ }
51
+
52
+ if (Array.isArray(object.children)) {
53
+ for (const child of object.children) {
54
+ const normal = computeNormalFromObject(child, depth + 1);
55
+ if (normal && normal.lengthSq() > 0) return normal;
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+
61
+ function computeNormalFromGeometry(object, geometry) {
62
+ if (!geometry?.isBufferGeometry) return null;
63
+ const positionAttr = geometry.getAttribute?.('position');
64
+ if (!positionAttr || positionAttr.itemSize !== 3 || positionAttr.count < 3) return null;
65
+
66
+ const indexAttr = geometry.getIndex?.();
67
+ const triangleCount = indexAttr ? Math.floor(indexAttr.count / 3) : Math.floor(positionAttr.count / 3);
68
+ if (triangleCount <= 0) return null;
69
+
70
+ object.updateMatrixWorld?.(true);
71
+
72
+ const v0 = new THREE.Vector3();
73
+ const v1 = new THREE.Vector3();
74
+ const v2 = new THREE.Vector3();
75
+ const edge1 = new THREE.Vector3();
76
+ const edge2 = new THREE.Vector3();
77
+ const normal = new THREE.Vector3();
78
+ const accum = new THREE.Vector3();
79
+
80
+ const sampleCount = Math.min(triangleCount, 60);
81
+ let count = 0;
82
+ for (let tri = 0; tri < sampleCount; tri += 1) {
83
+ let i0;
84
+ let i1;
85
+ let i2;
86
+ if (indexAttr) {
87
+ const base = tri * 3;
88
+ if (base + 2 >= indexAttr.count) break;
89
+ i0 = indexAttr.getX(base);
90
+ i1 = indexAttr.getX(base + 1);
91
+ i2 = indexAttr.getX(base + 2);
92
+ } else {
93
+ i0 = tri * 3;
94
+ i1 = i0 + 1;
95
+ i2 = i0 + 2;
96
+ if (i2 >= positionAttr.count) break;
97
+ }
98
+
99
+ v0.set(positionAttr.getX(i0), positionAttr.getY(i0), positionAttr.getZ(i0)).applyMatrix4(object.matrixWorld);
100
+ v1.set(positionAttr.getX(i1), positionAttr.getY(i1), positionAttr.getZ(i1)).applyMatrix4(object.matrixWorld);
101
+ v2.set(positionAttr.getX(i2), positionAttr.getY(i2), positionAttr.getZ(i2)).applyMatrix4(object.matrixWorld);
102
+
103
+ edge1.subVectors(v1, v0);
104
+ edge2.subVectors(v2, v0);
105
+ normal.crossVectors(edge1, edge2);
106
+ if (normal.lengthSq() > 1e-10) {
107
+ accum.add(normal);
108
+ count += 1;
109
+ }
110
+ }
111
+
112
+ if (count === 0) return null;
113
+
114
+ accum.divideScalar(count);
115
+ if (accum.lengthSq() <= 1e-10) return null;
116
+ return accum.normalize();
117
+ }
118
+
119
+ function normalizeOrNull(vec) {
120
+ if (!vec) return null;
121
+ const clone = vec instanceof THREE.Vector3
122
+ ? vec.clone()
123
+ : new THREE.Vector3(vec.x ?? 0, vec.y ?? 0, vec.z ?? 0);
124
+ if (clone.lengthSq() <= 1e-12) return null;
125
+ return clone.normalize();
126
+ }
127
+
128
+ function readAttributeVector(attr, index) {
129
+ if (!attr || typeof attr.getX !== 'function') return null;
130
+ if (index < 0 || index >= attr.count) return null;
131
+ return new THREE.Vector3(attr.getX(index), attr.getY(index), attr.getZ(index));
132
+ }
133
+
134
+ function elementDirectionFrom(target) {
135
+ if (!target) return null;
136
+ try {
137
+ const dir = getElementDirection(null, target);
138
+ if (dir && dir.lengthSq() > 1e-12) {
139
+ return dir.clone ? dir.clone() : new THREE.Vector3(dir.x, dir.y, dir.z);
140
+ }
141
+ } catch {}
142
+ return null;
143
+ }
144
+
145
+ function collectEdgeSamplePoints(target, maxSamples = MAX_EDGE_SAMPLE_POINTS) {
146
+ if (!target) return null;
147
+
148
+ const polyline = target.userData?.polylineLocal;
149
+ const closedLoop = !!target.userData?.closedLoop;
150
+ const points = [];
151
+
152
+ target.updateMatrixWorld?.(true);
153
+ const matrixWorld = target.matrixWorld;
154
+
155
+ const pushPoint = (x, y, z) => {
156
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return;
157
+ const p = new THREE.Vector3(x, y, z);
158
+ p.applyMatrix4(matrixWorld);
159
+ points.push(p);
160
+ };
161
+
162
+ if (Array.isArray(polyline) && polyline.length >= 2) {
163
+ for (const entry of polyline) {
164
+ if (!entry) continue;
165
+ if (Array.isArray(entry) && entry.length >= 3) {
166
+ pushPoint(entry[0], entry[1], entry[2]);
167
+ continue;
168
+ }
169
+ if (typeof entry === 'object') {
170
+ pushPoint(entry.x ?? entry[0] ?? 0, entry.y ?? entry[1] ?? 0, entry.z ?? entry[2] ?? 0);
171
+ }
172
+ }
173
+ return points.length >= 2 ? { points, closedLoop } : null;
174
+ }
175
+
176
+ const geom = target.geometry;
177
+ if (!geom) return null;
178
+
179
+ const sampleAttribute = (attrCount, getter) => {
180
+ if (attrCount <= 0) return;
181
+ const total = attrCount;
182
+ const sampleCount = Math.min(Math.max(total, 1), Math.max(2, Math.min(total, maxSamples)));
183
+ const denom = sampleCount > 1 ? (sampleCount - 1) : 1;
184
+ let lastIndex = -1;
185
+ for (let i = 0; i < sampleCount; i += 1) {
186
+ const t = denom === 0 ? 0 : i / denom;
187
+ const idx = Math.min(total - 1, Math.round(t * (total - 1)));
188
+ if (idx === lastIndex) continue;
189
+ lastIndex = idx;
190
+ const vec = getter(idx);
191
+ if (vec) pushPoint(vec.x, vec.y, vec.z);
192
+ }
193
+ if (lastIndex !== total - 1) {
194
+ const tail = getter(total - 1);
195
+ if (tail) pushPoint(tail.x, tail.y, tail.z);
196
+ }
197
+ };
198
+
199
+ const posAttr = geom.getAttribute?.('position');
200
+ if (posAttr && posAttr.itemSize === 3 && posAttr.count >= 1) {
201
+ sampleAttribute(posAttr.count, (idx) => readAttributeVector(posAttr, idx));
202
+ return points.length >= 2 ? { points, closedLoop } : null;
203
+ }
204
+
205
+ const arr = geom.attributes?.position?.array;
206
+ if (arr && (Array.isArray(arr) || ArrayBuffer.isView(arr))) {
207
+ const total = Math.floor(arr.length / 3);
208
+ if (total <= 0) return null;
209
+ sampleAttribute(total, (idx) => {
210
+ const base = idx * 3;
211
+ return new THREE.Vector3(arr[base], arr[base + 1], arr[base + 2]);
212
+ });
213
+ return points.length >= 2 ? { points, closedLoop } : null;
214
+ }
215
+
216
+ return null;
217
+ }
218
+
219
+ function principalAxisFromPoints(points) {
220
+ if (!Array.isArray(points) || points.length < 2) return null;
221
+ const centroid = new THREE.Vector3();
222
+ for (const p of points) centroid.add(p);
223
+ centroid.multiplyScalar(1 / points.length);
224
+
225
+ let xx = 0;
226
+ let xy = 0;
227
+ let xz = 0;
228
+ let yy = 0;
229
+ let yz = 0;
230
+ let zz = 0;
231
+ for (const p of points) {
232
+ const dx = p.x - centroid.x;
233
+ const dy = p.y - centroid.y;
234
+ const dz = p.z - centroid.z;
235
+ xx += dx * dx;
236
+ xy += dx * dy;
237
+ xz += dx * dz;
238
+ yy += dy * dy;
239
+ yz += dy * dz;
240
+ zz += dz * dz;
241
+ }
242
+
243
+ const vec = new THREE.Vector3(1, 0, 0);
244
+ if (yy > xx && yy >= zz) vec.set(0, 1, 0);
245
+ else if (zz > xx && zz > yy) vec.set(0, 0, 1);
246
+
247
+ for (let i = 0; i < 10; i += 1) {
248
+ const x = xx * vec.x + xy * vec.y + xz * vec.z;
249
+ const y = xy * vec.x + yy * vec.y + yz * vec.z;
250
+ const z = xz * vec.x + yz * vec.y + zz * vec.z;
251
+ const len = Math.hypot(x, y, z);
252
+ if (len <= 1e-9) return null;
253
+ vec.set(x / len, y / len, z / len);
254
+ }
255
+ return vec.normalize();
256
+ }
257
+
258
+ function resolveEdgeData(object) {
259
+ if (!object) return null;
260
+ const samplePayload = collectEdgeSamplePoints(object);
261
+ if (!samplePayload) return null;
262
+
263
+ const { points: samples, closedLoop } = samplePayload;
264
+ if (!samples || samples.length < 2) return null;
265
+
266
+ if (closedLoop) {
267
+ const error = new Error('ParallelConstraint: Selected edge is a closed loop and has no unique tangent direction.');
268
+ error.details = {
269
+ objectName: object?.name || null,
270
+ reason: 'edge-closed-loop',
271
+ };
272
+ throw error;
273
+ }
274
+
275
+ const first = samples[0].clone();
276
+ const last = samples[samples.length - 1].clone();
277
+ const endpoints = [first.clone(), last.clone()];
278
+
279
+ const centroid = samples.reduce((acc, p) => acc.add(p), new THREE.Vector3()).multiplyScalar(1 / samples.length);
280
+ const midpoint = first.distanceToSquared(last) > 1e-14
281
+ ? first.clone().add(last).multiplyScalar(0.5)
282
+ : centroid.clone();
283
+
284
+ const segmentAccum = new THREE.Vector3();
285
+ let totalSegmentLength = 0;
286
+ for (let i = 1; i < samples.length; i += 1) {
287
+ const seg = samples[i].clone().sub(samples[i - 1]);
288
+ const segLength = seg.length();
289
+ if (segLength > 1e-9) {
290
+ segmentAccum.add(seg);
291
+ totalSegmentLength += segLength;
292
+ }
293
+ }
294
+
295
+ const chord = last.clone().sub(first);
296
+ const chordLength = chord.length();
297
+
298
+ let direction = principalAxisFromPoints(samples);
299
+ let directionSource = direction ? 'edge-principal-axis' : null;
300
+
301
+ if (!direction && totalSegmentLength > 1e-9) {
302
+ direction = segmentAccum.clone().normalize();
303
+ directionSource = 'edge-segment-average';
304
+ }
305
+
306
+ if (!direction && chordLength > 1e-9) {
307
+ direction = chord.clone().normalize();
308
+ directionSource = 'edge-chord';
309
+ }
310
+
311
+ if (!direction || direction.lengthSq() <= 1e-12) {
312
+ return {
313
+ endpoints,
314
+ midpoint,
315
+ sampleCount: samples.length,
316
+ chord: chordLength > 1e-9 ? chord.clone() : null,
317
+ segmentAccum: totalSegmentLength > 1e-9 ? segmentAccum.clone() : null,
318
+ direction: null,
319
+ directionSource: 'edge-direction-unresolved',
320
+ totalSegmentLength,
321
+ chordLength,
322
+ };
323
+ }
324
+
325
+ if (chordLength > 1e-9 && direction.dot(chord) < 0) {
326
+ direction.negate();
327
+ }
328
+
329
+ return {
330
+ endpoints,
331
+ midpoint,
332
+ sampleCount: samples.length,
333
+ chord: chordLength > 1e-9 ? chord.clone() : null,
334
+ segmentAccum: totalSegmentLength > 1e-9 ? segmentAccum.clone() : null,
335
+ direction: direction ? direction.clone() : null,
336
+ directionSource,
337
+ totalSegmentLength,
338
+ chordLength,
339
+ closedLoop: !!closedLoop,
340
+ };
341
+ }
342
+
343
+ function resolveDirection(object, component, kind, edgeData) {
344
+ const preferEdgeTangent = kind === 'EDGE';
345
+ const elementDirCandidate = preferEdgeTangent
346
+ ? null
347
+ : normalizeOrNull(elementDirectionFrom(object) || elementDirectionFrom(component));
348
+
349
+ if (preferEdgeTangent) {
350
+ const edgeDir = normalizeOrNull(edgeData?.direction);
351
+ if (edgeDir) {
352
+ return { direction: edgeDir, source: edgeData?.directionSource || 'edge-data' };
353
+ }
354
+
355
+ return { direction: null, source: edgeData?.directionSource || 'edge-direction-unresolved' };
356
+ }
357
+
358
+ const worldNormal = normalizeOrNull(getWorldNormal(object));
359
+ if (worldNormal) {
360
+ return { direction: worldNormal, source: 'surface-normal' };
361
+ }
362
+
363
+ if (elementDirCandidate) {
364
+ const source = preferEdgeTangent ? 'edge-element-direction' : 'element-direction';
365
+ return { direction: elementDirCandidate, source };
366
+ }
367
+
368
+ const geometryNormal = normalizeOrNull(computeNormalFromObject(object));
369
+ if (geometryNormal) {
370
+ return { direction: geometryNormal, source: 'geometry-normal' };
371
+ }
372
+
373
+ return { direction: null, source: preferEdgeTangent ? 'edge-direction-unresolved' : 'unresolved' };
374
+ }
375
+
376
+ function selectionKindFrom(object, selection) {
377
+ const candidates = [];
378
+ if (selection && typeof selection.kind === 'string') candidates.push(selection.kind);
379
+ const userData = object?.userData;
380
+ if (userData?.type) candidates.push(userData.type);
381
+ if (userData?.brepType) candidates.push(userData.brepType);
382
+ if (object?.type) candidates.push(object.type);
383
+
384
+ for (const raw of candidates) {
385
+ const val = String(raw || '').toUpperCase();
386
+ if (!val) continue;
387
+ if (val.includes('FACE')) return 'FACE';
388
+ if (val.includes('EDGE')) return 'EDGE';
389
+ if (val.includes('VERTEX') || val.includes('POINT')) return 'POINT';
390
+ if (val.includes('COMPONENT')) return 'COMPONENT';
391
+ }
392
+ return 'UNKNOWN';
393
+ }
394
+
395
+ function selectionDirection(constraint, context, selection, selectionLabel) {
396
+ const object = context.resolveObject?.(selection) || null;
397
+ const component = context.resolveComponent?.(selection) || null;
398
+ const kind = selectionKindFrom(object, selection);
399
+ const preferEdgeTangent = kind === 'EDGE';
400
+ const edgeData = preferEdgeTangent ? resolveEdgeData(object) : null;
401
+ if (context.scene?.updateMatrixWorld) {
402
+ try { context.scene.updateMatrixWorld(true); } catch {}
403
+ }
404
+ component?.updateMatrixWorld?.(true);
405
+ object?.updateMatrixWorld?.(true);
406
+
407
+ let origin = resolveOrigin(object, component);
408
+ if (edgeData?.midpoint) {
409
+ origin = edgeData.midpoint.clone();
410
+ }
411
+
412
+ const resolved = resolveDirection(object, component, kind, edgeData);
413
+ const dirFromObject = resolved.direction;
414
+ if (!dirFromObject || dirFromObject.lengthSq() === 0) {
415
+ const failureDetails = {
416
+ selectionLabel,
417
+ selection,
418
+ objectName: object?.name || null,
419
+ componentName: component?.name || null,
420
+ kind,
421
+ edgeData: edgeData ? {
422
+ endpoints: edgeData.endpoints?.map?.((p) => p.toArray()) || null,
423
+ directionSource: edgeData.directionSource || null,
424
+ sampleCount: edgeData.sampleCount ?? null,
425
+ chordLength: edgeData.chordLength ?? null,
426
+ totalSegmentLength: edgeData.totalSegmentLength ?? null,
427
+ closedLoop: edgeData.closedLoop ?? null,
428
+ } : null,
429
+ reason: resolved.source,
430
+ };
431
+ const error = new Error('ParallelConstraint: Unable to resolve a surface normal for the provided selection.');
432
+ error.details = failureDetails;
433
+ console.error('[ParallelConstraint] Failed to resolve normal for selection.', failureDetails, error);
434
+ throw error;
435
+ }
436
+
437
+ return {
438
+ direction: dirFromObject.clone().normalize(),
439
+ origin,
440
+ object,
441
+ component: component || null,
442
+ directionSource: resolved.source,
443
+ kind,
444
+ edgeData,
445
+ };
446
+ }
447
+
448
+ function describeSelectionLabel(label) {
449
+ if (!label) return 'selection';
450
+ const match = /^elements\[(\d+)\]$/i.exec(String(label));
451
+ if (match) {
452
+ const index = Number(match[1]);
453
+ if (Number.isFinite(index)) return `Element ${index + 1}`;
454
+ }
455
+ const trimmed = String(label).trim();
456
+ if (!trimmed) return 'selection';
457
+ return trimmed
458
+ .replace(/_/g, ' ')
459
+ .replace(/\s+/g, ' ')
460
+ .replace(/\b([a-z])/gi, (m, ch) => ch.toUpperCase());
461
+ }
462
+
463
+ function normalizeQuaternion(quaternion) {
464
+ if (!quaternion) return null;
465
+ const q = quaternion instanceof THREE.Quaternion
466
+ ? quaternion.clone()
467
+ : new THREE.Quaternion(quaternion.x ?? 0, quaternion.y ?? 0, quaternion.z ?? 0, quaternion.w ?? 1);
468
+ if (!Number.isFinite(q.x) || !Number.isFinite(q.y) || !Number.isFinite(q.z) || !Number.isFinite(q.w)) return null;
469
+ if (Math.abs(1 - q.lengthSq()) > 1e-6) q.normalize();
470
+ return q;
471
+ }
472
+
473
+ function computeRotation(fromDir, toDir, gain = 1) {
474
+ if (!fromDir || !toDir) return null;
475
+ const a = fromDir.clone().normalize();
476
+ const b = toDir.clone().normalize();
477
+ const dot = THREE.MathUtils.clamp(a.dot(b), -1, 1);
478
+ let angle = Math.acos(dot);
479
+ if (!Number.isFinite(angle) || angle <= 1e-6) return null;
480
+ const axis = new THREE.Vector3().crossVectors(a, b);
481
+ if (axis.lengthSq() <= 1e-12) {
482
+ axis.set(1, 0, 0).cross(a);
483
+ if (axis.lengthSq() <= 1e-12) axis.set(0, 1, 0).cross(a);
484
+ }
485
+ axis.normalize();
486
+ const clampedGain = Math.max(0, Math.min(1, gain));
487
+ const intendedAngle = angle * clampedGain;
488
+ const appliedAngle = Math.min(intendedAngle, MAX_ROTATION_PER_ITERATION, angle);
489
+ if (appliedAngle <= 1e-6) return null;
490
+ return new THREE.Quaternion().setFromAxisAngle(axis, appliedAngle);
491
+ }
492
+
493
+ function attemptRotation(context, component, fromDir, toDir, gain) {
494
+ if (!component) return false;
495
+ const quat = computeRotation(fromDir, toDir, gain);
496
+ if (!quat) return false;
497
+ const normalized = normalizeQuaternion(quat);
498
+ if (!normalized) return false;
499
+ const ok = context.applyRotation?.(component, normalized);
500
+ return ok ? { component, quaternion: normalized } : false;
501
+ }
502
+
503
+ // Shared business logic for making two selections parallel; reuse in future constraints (e.g., distance).
504
+ export function solveParallelAlignment({
505
+ constraint,
506
+ context = {},
507
+ selectionA,
508
+ selectionB,
509
+ opposeNormals = false,
510
+ selectionLabelA = 'elements[0]',
511
+ selectionLabelB = 'elements[1]',
512
+ }) {
513
+ if (!constraint) throw new Error('solveParallelAlignment requires a constraint instance.');
514
+
515
+ const labelA = describeSelectionLabel(selectionLabelA);
516
+ const labelB = describeSelectionLabel(selectionLabelB);
517
+
518
+ let infoA;
519
+ let infoB;
520
+ try {
521
+ infoA = selectionDirection(constraint, context, selectionA, selectionLabelA);
522
+ } catch (error) {
523
+ return {
524
+ ok: false,
525
+ status: 'normal-resolution-failed',
526
+ satisfied: false,
527
+ applied: false,
528
+ message: `Failed to resolve a normal for ${labelA}.`,
529
+ exception: error,
530
+ infoA: null,
531
+ infoB: null,
532
+ };
533
+ }
534
+
535
+ try {
536
+ infoB = selectionDirection(constraint, context, selectionB, selectionLabelB);
537
+ } catch (error) {
538
+ return {
539
+ ok: false,
540
+ status: 'normal-resolution-failed',
541
+ satisfied: false,
542
+ applied: false,
543
+ message: `Failed to resolve a normal for ${labelB}.`,
544
+ exception: error,
545
+ infoA,
546
+ infoB: null,
547
+ };
548
+ }
549
+
550
+ if (!infoA.component || !infoB.component) {
551
+ return {
552
+ ok: false,
553
+ status: 'invalid-selection',
554
+ satisfied: false,
555
+ applied: false,
556
+ message: 'Both selections must belong to assembly components.',
557
+ infoA,
558
+ infoB,
559
+ };
560
+ }
561
+
562
+ if (infoA.component === infoB.component) {
563
+ return {
564
+ ok: false,
565
+ status: 'invalid-selection',
566
+ satisfied: false,
567
+ applied: false,
568
+ message: 'Select references from two different components.',
569
+ infoA,
570
+ infoB,
571
+ };
572
+ }
573
+
574
+ const dirA = infoA.direction;
575
+ const dirB = infoB.direction;
576
+
577
+ if (!dirA || !dirB) {
578
+ return {
579
+ ok: false,
580
+ status: 'invalid-selection',
581
+ satisfied: false,
582
+ applied: false,
583
+ message: 'Unable to resolve directions for one or both selections.',
584
+ infoA,
585
+ infoB,
586
+ };
587
+ }
588
+
589
+ const targetForB = opposeNormals ? dirA.clone().negate() : dirA.clone();
590
+ const targetForA = opposeNormals ? dirB.clone().negate() : dirB.clone();
591
+
592
+ const dot = THREE.MathUtils.clamp(dirB.dot(targetForB), -1, 1);
593
+ const angle = Math.acos(dot);
594
+ const angleDeg = THREE.MathUtils.radToDeg(angle);
595
+
596
+ const contextTolerance = Math.abs(context.tolerance ?? DEFAULT_PARALLEL_ALIGNMENT_TOLERANCE);
597
+ const angleTolerance = Math.max(ANGLE_TOLERANCE, contextTolerance * 10);
598
+
599
+ const fixedA = context.isComponentFixed?.(infoA.component);
600
+ const fixedB = context.isComponentFixed?.(infoB.component);
601
+ const rotationGain = context.rotationGain ?? 1;
602
+
603
+ if (angle <= angleTolerance) {
604
+ return {
605
+ ok: true,
606
+ status: 'satisfied',
607
+ satisfied: true,
608
+ applied: false,
609
+ angle,
610
+ angleDeg,
611
+ error: angle,
612
+ infoA,
613
+ infoB,
614
+ message: 'Reference directions are parallel within tolerance.',
615
+ };
616
+ }
617
+
618
+ if (fixedA && fixedB) {
619
+ return {
620
+ ok: false,
621
+ status: 'blocked',
622
+ satisfied: false,
623
+ applied: false,
624
+ angle,
625
+ angleDeg,
626
+ error: angle,
627
+ infoA,
628
+ infoB,
629
+ message: 'Both components are fixed; unable to rotate to satisfy constraint.',
630
+ };
631
+ }
632
+
633
+ const rotations = [];
634
+ let applied = false;
635
+
636
+ const pushRotation = (attempt) => {
637
+ if (!attempt) return false;
638
+ const { component, quaternion } = attempt;
639
+ rotations.push({ component: component.name || component.uuid, quaternion: quaternion.toArray() });
640
+ component.updateMatrixWorld?.(true);
641
+ return true;
642
+ };
643
+
644
+ if (!fixedA && !fixedB) {
645
+ applied = pushRotation(attemptRotation(context, infoA.component, dirA, targetForA, rotationGain * 0.5)) || applied;
646
+ applied = pushRotation(attemptRotation(context, infoB.component, dirB, targetForB, rotationGain * 0.5)) || applied;
647
+ } else if (fixedA && !fixedB) {
648
+ applied = pushRotation(attemptRotation(context, infoB.component, dirB, targetForB, rotationGain)) || applied;
649
+ } else if (!fixedA && fixedB) {
650
+ applied = pushRotation(attemptRotation(context, infoA.component, dirA, targetForA, rotationGain)) || applied;
651
+ }
652
+
653
+ const status = applied ? 'adjusted' : 'pending';
654
+ const message = applied
655
+ ? 'Applied rotation to improve parallelism.'
656
+ : 'Waiting for a movable component to rotate.';
657
+
658
+ return {
659
+ ok: true,
660
+ status,
661
+ satisfied: false,
662
+ applied,
663
+ angle,
664
+ angleDeg,
665
+ error: angle,
666
+ infoA,
667
+ infoB,
668
+ message,
669
+ rotations,
670
+ diagnostics: { angle, angleDeg, rotations },
671
+ };
672
+ }
673
+
674
+ export function resolveParallelSelection(constraint, context, selection, selectionLabel) {
675
+ return selectionDirection(constraint, context, selection, selectionLabel);
676
+ }