brep-io-kernel 1.0.0-ci.9

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 +154 -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,193 @@
1
+ import * as THREE from 'three';
2
+
3
+ const EPS = 1e-9;
4
+
5
+ const finiteOr = (value, fallback) => {
6
+ const n = Number(value);
7
+ return Number.isFinite(n) ? n : fallback;
8
+ };
9
+
10
+ const toVec3 = (value, fallback = [0, 0, 0]) => {
11
+ if (value && typeof value.x === 'number') {
12
+ return new THREE.Vector3(
13
+ Number(value.x) || 0,
14
+ Number(value.y) || 0,
15
+ Number(value.z) || 0,
16
+ );
17
+ }
18
+ if (Array.isArray(value)) {
19
+ return new THREE.Vector3(
20
+ Number(value[0]) || 0,
21
+ Number(value[1]) || 0,
22
+ Number(value[2]) || 0,
23
+ );
24
+ }
25
+ return new THREE.Vector3(
26
+ Number(fallback?.[0]) || 0,
27
+ Number(fallback?.[1]) || 0,
28
+ Number(fallback?.[2]) || 0,
29
+ );
30
+ };
31
+
32
+ const normalizedOr = (vec, fallback = new THREE.Vector3(0, 0, 1)) => {
33
+ const out = vec.clone();
34
+ if (out.lengthSq() < EPS) return fallback.clone().normalize();
35
+ return out.normalize();
36
+ };
37
+
38
+ const matrixFromTransform = (transform) => {
39
+ if (!transform) return null;
40
+ if (transform.isMatrix4) return transform.clone();
41
+
42
+ const pos = Array.isArray(transform.position) ? transform.position : [0, 0, 0];
43
+ const rot = Array.isArray(transform.rotationEuler) ? transform.rotationEuler : [0, 0, 0];
44
+ const scl = Array.isArray(transform.scale) ? transform.scale : [1, 1, 1];
45
+
46
+ const position = new THREE.Vector3(
47
+ Number(pos[0]) || 0,
48
+ Number(pos[1]) || 0,
49
+ Number(pos[2]) || 0,
50
+ );
51
+ const euler = new THREE.Euler(
52
+ THREE.MathUtils.degToRad(Number(rot[0]) || 0),
53
+ THREE.MathUtils.degToRad(Number(rot[1]) || 0),
54
+ THREE.MathUtils.degToRad(Number(rot[2]) || 0),
55
+ 'XYZ',
56
+ );
57
+ const quat = new THREE.Quaternion().setFromEuler(euler);
58
+ const scale = new THREE.Vector3(
59
+ Math.abs(Number(scl[0]) || 1),
60
+ Math.abs(Number(scl[1]) || 1),
61
+ Math.abs(Number(scl[2]) || 1),
62
+ );
63
+
64
+ return new THREE.Matrix4().compose(position, quat, scale);
65
+ };
66
+
67
+ const buildPlacementMatrix = (opts = {}) => {
68
+ const fromTransform = matrixFromTransform(opts.transform);
69
+ if (fromTransform) return fromTransform;
70
+
71
+ const origin = toVec3(opts.origin, [0, 0, 0]);
72
+ const axisDir = normalizedOr(toVec3(opts.axis, [0, 0, 1]), new THREE.Vector3(0, 0, 1));
73
+
74
+ let xDir = opts.xDirection ? toVec3(opts.xDirection) : null;
75
+ if (!xDir || xDir.lengthSq() < EPS) {
76
+ const fallback = Math.abs(axisDir.y) < 0.9 ? new THREE.Vector3(0, 1, 0) : new THREE.Vector3(1, 0, 0);
77
+ xDir = new THREE.Vector3().crossVectors(fallback, axisDir);
78
+ } else {
79
+ // Remove any component along the axis so we remain perpendicular
80
+ xDir = xDir.addScaledVector(axisDir, -xDir.dot(axisDir));
81
+ }
82
+ if (xDir.lengthSq() < EPS) {
83
+ const fallback = Math.abs(axisDir.z) < 0.9 ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(0, 1, 0);
84
+ xDir = new THREE.Vector3().crossVectors(axisDir, fallback);
85
+ }
86
+ xDir.normalize();
87
+ const yDir = new THREE.Vector3().crossVectors(axisDir, xDir).normalize();
88
+
89
+ const m = new THREE.Matrix4().makeBasis(xDir, yDir, axisDir);
90
+ m.setPosition(origin);
91
+ return m;
92
+ };
93
+
94
+ /**
95
+ * Build a helix polyline in 3D.
96
+ * - Generates points for a right/left-handed helix with optional taper.
97
+ * - Orientation is controlled by a transform matrix, or by origin/axis/xDirection.
98
+ * - Returns world-space polyline points for reuse by features (threads, sweep paths, etc.).
99
+ * Options: radius/endRadius, height + pitch with mode (turns|pitch) controlling which value is derived, startAngleDeg/startAngle, clockwise,
100
+ * segmentsPerTurn (or resolution), and placement via `transform` or `{ origin, axis, xDirection }`.
101
+ */
102
+ export function buildHelixPolyline(opts = {}) {
103
+ const r0 = Math.max(1e-6, Math.abs(finiteOr(opts.radius, 5)));
104
+ const hasEndRadius = opts.endRadius != null && Number.isFinite(Number(opts.endRadius));
105
+ const r1 = hasEndRadius ? Math.max(1e-6, Math.abs(finiteOr(opts.endRadius, r0))) : r0;
106
+
107
+ const minPitch = 1e-6;
108
+ const rawPitch = finiteOr(opts.pitch, finiteOr(opts.pitchDefault, NaN));
109
+ if (!Number.isFinite(rawPitch) || Math.abs(rawPitch) < minPitch) {
110
+ throw new Error('[buildHelixPolyline] pitch must be a finite, non-zero number.');
111
+ }
112
+ let pitch = Math.abs(rawPitch);
113
+ const modeRaw = String(opts.lengthMode || opts.mode || 'turns').toLowerCase();
114
+ const lengthMode = modeRaw === 'pitch' || modeRaw === 'height' ? 'pitch' : 'turns';
115
+ let turns = finiteOr(opts.turns, NaN);
116
+ let height = finiteOr(opts.height, NaN);
117
+
118
+ // Allow negative height to flip the axis direction while keeping a positive magnitude
119
+ let axisSign = 1;
120
+ if (Number.isFinite(height) && height < 0) {
121
+ axisSign = -1;
122
+ height = Math.abs(height);
123
+ }
124
+
125
+ if (lengthMode === 'pitch' && Number.isFinite(height) && height > EPS) {
126
+ turns = height / pitch;
127
+ } else {
128
+ if (!Number.isFinite(turns) || turns <= 0) {
129
+ if (Number.isFinite(height) && height > EPS) turns = height / pitch;
130
+ else turns = 1;
131
+ }
132
+ height = Number.isFinite(height) && height > EPS ? height : pitch * turns;
133
+ }
134
+
135
+ // Keep pitch consistent with the resolved height/turns
136
+ if (Number.isFinite(turns) && turns > EPS) pitch = height / turns;
137
+ else {
138
+ turns = 1;
139
+ height = pitch;
140
+ }
141
+
142
+ const segsPerTurn = Math.max(8, Math.floor(finiteOr(opts.segmentsPerTurn, finiteOr(opts.resolution, 64))));
143
+ const totalSeg = Math.max(1, Math.round(segsPerTurn * Math.max(turns, EPS)));
144
+
145
+ const startAngleDeg = finiteOr(opts.startAngleDeg, NaN);
146
+ const startAngleRad = Number.isFinite(startAngleDeg)
147
+ ? THREE.MathUtils.degToRad(startAngleDeg)
148
+ : finiteOr(opts.startAngle, 0);
149
+ const handedRaw = String(opts.handedness || (opts.clockwise ? 'left' : 'right')).toLowerCase();
150
+ const clockwise = handedRaw === 'left' || opts.clockwise === true;
151
+ const angleSign = clockwise ? -1 : 1;
152
+
153
+ const placement = buildPlacementMatrix(opts);
154
+ const normalMat = new THREE.Matrix3();
155
+ normalMat.getNormalMatrix(placement);
156
+
157
+ const origin = new THREE.Vector3().setFromMatrixPosition(placement);
158
+ const axisDir = new THREE.Vector3(0, 0, axisSign).applyMatrix3(normalMat).normalize();
159
+
160
+ const polyline = [];
161
+ // Keep winding direction stable even if the helix height is negative by folding the axis sign into angular direction
162
+ const angularSign = angleSign * axisSign;
163
+ const tmp = new THREE.Vector3();
164
+ for (let i = 0; i <= totalSeg; i++) {
165
+ const t = i / totalSeg;
166
+ const theta = startAngleRad + angularSign * (turns * t * Math.PI * 2);
167
+ const radius = r0 + (r1 - r0) * t;
168
+ tmp.set(Math.cos(theta) * radius, Math.sin(theta) * radius, height * t * axisSign);
169
+ tmp.applyMatrix4(placement);
170
+ polyline.push([tmp.x, tmp.y, tmp.z]);
171
+ }
172
+
173
+ const axisEnd = origin.clone().addScaledVector(axisDir, height);
174
+
175
+ return {
176
+ polyline,
177
+ closedLoop: false,
178
+ pitch,
179
+ turns,
180
+ height,
181
+ radiusStart: r0,
182
+ radiusEnd: r1,
183
+ clockwise,
184
+ handedness: clockwise ? 'left' : 'right',
185
+ startAngleRad,
186
+ origin: [origin.x, origin.y, origin.z],
187
+ axisDirection: [axisDir.x, axisDir.y, axisDir.z],
188
+ axisLine: [
189
+ [origin.x, origin.y, origin.z],
190
+ [axisEnd.x, axisEnd.y, axisEnd.z],
191
+ ],
192
+ };
193
+ }
@@ -0,0 +1,234 @@
1
+ import * as THREE from "three";
2
+ import { Solid } from "./BetterSolid.js";
3
+
4
+ /**
5
+ * MeshToBrep: Builds a Solid from a triangle mesh by grouping triangles
6
+ * into face labels based on the deflection angle between neighboring
7
+ * triangle normals.
8
+ *
9
+ * Usage
10
+ * const solid = new MeshToBrep(geometryOrMesh, 15 deg );
11
+ * solid.name = "ImportedSTL";
12
+ * solid.visualize();
13
+ *
14
+ * Notes
15
+ * - Accepts a THREE.BufferGeometry or a THREE.Mesh (uses its geometry).
16
+ * - Triangles are welded to a grid (weldTolerance) so shared edges use
17
+ * identical vertex positions for manifoldization.
18
+ * - If the input geometry has a `normal` attribute (STLLoader does),
19
+ * those normals are used for deflection. Otherwise, per-triangle
20
+ * normals are computed from positions.
21
+ */
22
+ export class MeshToBrep extends Solid {
23
+ /**
24
+ * @param {THREE.BufferGeometry|THREE.Mesh} geometryOrMesh
25
+ * @param {number} faceDeflectionAngle Degrees; neighbors within this angle join a face
26
+ * @param {number} weldTolerance Vertex welding tolerance (units). Default 1e-5.
27
+ */
28
+ constructor(geometryOrMesh, faceDeflectionAngle = 30, weldTolerance = 1e-5) {
29
+ super();
30
+ const geom = (geometryOrMesh && geometryOrMesh.isMesh)
31
+ ? geometryOrMesh.geometry
32
+ : geometryOrMesh;
33
+ if (!geom || !geom.isBufferGeometry) {
34
+ throw new Error("MeshToBrep requires a THREE.BufferGeometry or THREE.Mesh");
35
+ }
36
+
37
+ this.faceDeflectionAngle = Number(faceDeflectionAngle) || 0;
38
+ this.weldTolerance = Number(weldTolerance) || 0;
39
+
40
+ // Build internal mesh arrays and face labels
41
+ this._buildFromGeometry(geom);
42
+ }
43
+
44
+ toBrep() { return this; }
45
+
46
+ _buildFromGeometry(geometry) {
47
+ // Ensure we have positions
48
+ const posAttr = geometry.getAttribute('position');
49
+ if (!posAttr) throw new Error("Geometry has no 'position' attribute");
50
+
51
+ const idxAttr = geometry.getIndex();
52
+ const norAttr = geometry.getAttribute('normal');
53
+
54
+ // Accessor helpers
55
+ const getPos = (i, out) => {
56
+ out.x = posAttr.getX(i);
57
+ out.y = posAttr.getY(i);
58
+ out.z = posAttr.getZ(i);
59
+ return out;
60
+ };
61
+ const getTri = (t) => {
62
+ if (idxAttr) {
63
+ const i0 = idxAttr.getX(3 * t + 0) >>> 0;
64
+ const i1 = idxAttr.getX(3 * t + 1) >>> 0;
65
+ const i2 = idxAttr.getX(3 * t + 2) >>> 0;
66
+ return [i0, i1, i2];
67
+ } else {
68
+ const base = 3 * t;
69
+ return [base + 0, base + 1, base + 2];
70
+ }
71
+ };
72
+
73
+ const triCount = idxAttr ? ((idxAttr.count / 3) | 0) : ((posAttr.count / 3) | 0);
74
+ if (triCount <= 0) return;
75
+
76
+ // Build canonical vertices using a weld grid so that shared edges truly share indices
77
+ const q = Math.max(0, this.weldTolerance) || 0;
78
+ const gridKey = (x, y, z) => {
79
+ if (q <= 0) return `${x},${y},${z}`; // exact
80
+ const rx = Math.round(x / q);
81
+ const ry = Math.round(y / q);
82
+ const rz = Math.round(z / q);
83
+ return `${rx},${ry},${rz}`;
84
+ };
85
+ const tmpA = new THREE.Vector3();
86
+ const tmpB = new THREE.Vector3();
87
+ const tmpC = new THREE.Vector3();
88
+
89
+ // Vertex dictionary and arrays
90
+ const keyToIndex = new Map();
91
+ const indexToPos = []; // [ [x,y,z], ... ] matching canonical vertices
92
+
93
+ // Triangle data arrays
94
+ const triVerts = new Array(triCount);
95
+ const triNormals = new Array(triCount);
96
+
97
+ // Grab per-vertex normals if available (STL facet normals replicated per-vertex)
98
+ const getTriNormal = (t) => {
99
+ if (norAttr && norAttr.count === posAttr.count) {
100
+ const [i0] = getTri(t);
101
+ // normals are constant per tri for STL; use any vertex's normal
102
+ const nx = norAttr.getX(i0);
103
+ const ny = norAttr.getY(i0);
104
+ const nz = norAttr.getZ(i0);
105
+ const n = new THREE.Vector3(nx, ny, nz);
106
+ if (n.lengthSq() > 0) return n.normalize();
107
+ }
108
+ // Fallback: compute from positions
109
+ const [a, b, c] = getTri(t);
110
+ getPos(a, tmpA); getPos(b, tmpB); getPos(c, tmpC);
111
+ tmpB.sub(tmpA); tmpC.sub(tmpA);
112
+ const n = tmpB.clone().cross(tmpC);
113
+ if (n.lengthSq() > 0) return n.normalize();
114
+ return new THREE.Vector3(0, 0, 1);
115
+ };
116
+
117
+ // Build canonical vertices and triangle index triplets
118
+ for (let t = 0; t < triCount; t++) {
119
+ const [ia, ib, ic] = getTri(t);
120
+ const a = getPos(ia, tmpA.clone());
121
+ const b = getPos(ib, tmpB.clone());
122
+ const c = getPos(ic, tmpC.clone());
123
+
124
+ const keyA = gridKey(a.x, a.y, a.z);
125
+ const keyB = gridKey(b.x, b.y, b.z);
126
+ const keyC = gridKey(c.x, c.y, c.z);
127
+
128
+ let ai = keyToIndex.get(keyA);
129
+ if (ai === undefined) {
130
+ ai = indexToPos.length;
131
+ keyToIndex.set(keyA, ai);
132
+ const [xr, yr, zr] = (q <= 0) ? [a.x, a.y, a.z] : [Math.round(a.x / q) * q, Math.round(a.y / q) * q, Math.round(a.z / q) * q];
133
+ indexToPos.push([xr, yr, zr]);
134
+ }
135
+ let bi = keyToIndex.get(keyB);
136
+ if (bi === undefined) {
137
+ bi = indexToPos.length;
138
+ keyToIndex.set(keyB, bi);
139
+ const [xr, yr, zr] = (q <= 0) ? [b.x, b.y, b.z] : [Math.round(b.x / q) * q, Math.round(b.y / q) * q, Math.round(b.z / q) * q];
140
+ indexToPos.push([xr, yr, zr]);
141
+ }
142
+ let ci = keyToIndex.get(keyC);
143
+ if (ci === undefined) {
144
+ ci = indexToPos.length;
145
+ keyToIndex.set(keyC, ci);
146
+ const [xr, yr, zr] = (q <= 0) ? [c.x, c.y, c.z] : [Math.round(c.x / q) * q, Math.round(c.y / q) * q, Math.round(c.z / q) * q];
147
+ indexToPos.push([xr, yr, zr]);
148
+ }
149
+
150
+ triVerts[t] = [ai, bi, ci];
151
+ triNormals[t] = getTriNormal(t);
152
+ }
153
+
154
+ // Build adjacency via undirected edge -> list of triangle indices
155
+ const ek = (u, v) => (u < v ? `${u},${v}` : `${v},${u}`);
156
+ const edgeToTris = new Map();
157
+ for (let t = 0; t < triCount; t++) {
158
+ const [a, b, c] = triVerts[t];
159
+ const edges = [[a, b], [b, c], [c, a]];
160
+ for (const [u, v] of edges) {
161
+ const key = ek(u, v);
162
+ let list = edgeToTris.get(key);
163
+ if (!list) { list = []; edgeToTris.set(key, list); }
164
+ list.push(t);
165
+ }
166
+ }
167
+
168
+ // Convert to per-triangle neighbor lists
169
+ const neighbors = new Array(triCount);
170
+ for (let i = 0; i < triCount; i++) neighbors[i] = [];
171
+ for (const list of edgeToTris.values()) {
172
+ if (list.length < 2) continue;
173
+ // each pair of triangles sharing this edge are neighbors
174
+ for (let i = 0; i < list.length; i++) {
175
+ for (let j = i + 1; j < list.length; j++) {
176
+ const a = list[i], b = list[j];
177
+ neighbors[a].push(b);
178
+ neighbors[b].push(a);
179
+ }
180
+ }
181
+ }
182
+
183
+ // Region grow faces by deflection angle between neighboring triangle normals
184
+ const maxAngleRad = Math.max(0, this.faceDeflectionAngle) * Math.PI / 180.0;
185
+ const cosThresh = Math.cos(maxAngleRad);
186
+ const visited = new Uint8Array(triCount);
187
+ let faceCounter = 0;
188
+ const triFaceName = new Array(triCount);
189
+
190
+ const dot = (a, b) => {
191
+ const d = a.x * b.x + a.y * b.y + a.z * b.z;
192
+ const la = Math.hypot(a.x, a.y, a.z);
193
+ const lb = Math.hypot(b.x, b.y, b.z);
194
+ if (la === 0 || lb === 0) return 1; // treat degenerate as same
195
+ return d / (la * lb);
196
+ };
197
+
198
+ for (let seed = 0; seed < triCount; seed++) {
199
+ if (visited[seed]) continue;
200
+ const faceName = `STL_FACE_${++faceCounter}`;
201
+ // BFS using pairwise deflection with the parent triangle
202
+ const queue = [seed];
203
+ visited[seed] = 1;
204
+ triFaceName[seed] = faceName;
205
+ while (queue.length) {
206
+ const t = queue.shift();
207
+ const nrmT = triNormals[t];
208
+ for (const nb of neighbors[t]) {
209
+ if (visited[nb]) continue;
210
+ const nrmN = triNormals[nb];
211
+ // If normals are close (angle <= threshold), grow region
212
+ if (dot(nrmT, nrmN) >= cosThresh) {
213
+ visited[nb] = 1;
214
+ triFaceName[nb] = faceName;
215
+ queue.push(nb);
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ // Author triangles into this Solid with their face labels
222
+ for (let t = 0; t < triCount; t++) {
223
+ const name = triFaceName[t] || `STL_FACE_${faceCounter + 1}`;
224
+ const [a, b, c] = triVerts[t];
225
+ const pa = indexToPos[a];
226
+ const pb = indexToPos[b];
227
+ const pc = indexToPos[c];
228
+ this.addTriangle(name, pa, pb, pc);
229
+ }
230
+
231
+ // Let downstream visualization build per-face meshes and edges
232
+ // We'll leave winding correction/orientation to _manifoldize()
233
+ }
234
+ }