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,405 @@
1
+ import { BREP } from "../../BREP/BREP.js";
2
+ import { LineGeometry } from "three/examples/jsm/Addons.js";
3
+
4
+ const inputParamsSchema = {
5
+ id: {
6
+ type: "string",
7
+ default_value: null,
8
+ hint: "unique identifier for the helix feature",
9
+ },
10
+ placementMode: {
11
+ type: "options",
12
+ options: ["transform", "axis"],
13
+ default_value: "transform",
14
+ label: "Placement",
15
+ hint: "Use transform or align to an existing axis and start point",
16
+ },
17
+ transform: {
18
+ type: "transform",
19
+ default_value: {
20
+ position: [0, 0, 0],
21
+ rotationEuler: [0, 0, 0],
22
+ scale: [1, 1, 1],
23
+ },
24
+ hint: "Position, rotation, and scale to place the helix",
25
+ },
26
+ axis: {
27
+ type: "reference_selection",
28
+ selectionFilter: ["EDGE"],
29
+ multiple: false,
30
+ default_value: null,
31
+ label: "Axis",
32
+ hint: "Select an edge to use as the helix axis",
33
+ },
34
+ startPoint: {
35
+ type: "reference_selection",
36
+ selectionFilter: ["VERTEX"],
37
+ multiple: false,
38
+ default_value: null,
39
+ label: "Start point",
40
+ hint: "Optional start point; defaults to the axis start",
41
+ },
42
+ radius: {
43
+ type: "number",
44
+ default_value: 5,
45
+ hint: "Base radius of the helix",
46
+ },
47
+ endRadius: {
48
+ type: "number",
49
+ default_value: 5,
50
+ hint: "Optional end radius to taper the helix",
51
+ },
52
+ handedness: {
53
+ type: "options",
54
+ options: ["right", "left"],
55
+ default_value: "right",
56
+ hint: "Choose right- or left-handed helix winding",
57
+ },
58
+ resolution: {
59
+ type: "number",
60
+ default_value: 64,
61
+ hint: "Segments per turn for the helix polyline",
62
+ },
63
+ mode: {
64
+ type: "options",
65
+ options: ["turns", "pitch"],
66
+ default_value: "turns",
67
+ label: "Mode",
68
+ hint: "Control helix using turn count or pitch; height is always applied",
69
+ },
70
+ height: {
71
+ type: "number",
72
+ default_value: 15,
73
+ hint: "Total height along the axis",
74
+ },
75
+ turns: {
76
+ type: "number",
77
+ default_value: 3,
78
+ hint: "Number of turns (used in mode 'turns'; derived in mode 'pitch')",
79
+ },
80
+ pitch: {
81
+ type: "number",
82
+ default_value: 5,
83
+ step: 1,
84
+ min: 0.001,
85
+ hint: "Distance advanced per turn along the helix axis (editable in mode 'pitch'; derived otherwise)",
86
+ },
87
+ startAngle: {
88
+ type: "number",
89
+ default_value: 0,
90
+ hint: "Starting angle in degrees",
91
+ },
92
+ };
93
+
94
+ const THREE = BREP.THREE;
95
+
96
+ const toNumber = (value, fallback) => {
97
+ const n = Number(value);
98
+ return Number.isFinite(n) ? n : fallback;
99
+ };
100
+
101
+ const toVec3 = (value) => {
102
+ if (!value) return null;
103
+ if (value.isVector3) return value.clone();
104
+ if (typeof value.getWorldPosition === "function") {
105
+ const v = new THREE.Vector3();
106
+ try {
107
+ value.getWorldPosition(v);
108
+ return v;
109
+ } catch { /* ignore */ }
110
+ }
111
+ if (Array.isArray(value) && value.length >= 3) {
112
+ return new THREE.Vector3(
113
+ toNumber(value[0], 0),
114
+ toNumber(value[1], 0),
115
+ toNumber(value[2], 0)
116
+ );
117
+ }
118
+ if (value.position && typeof value.position === "object") {
119
+ const p = value.position;
120
+ return new THREE.Vector3(
121
+ toNumber(p.x, 0),
122
+ toNumber(p.y, 0),
123
+ toNumber(p.z, 0)
124
+ );
125
+ }
126
+ return null;
127
+ };
128
+
129
+ function extractEdgePolylineWorld(edgeObj) {
130
+ const pts = [];
131
+ if (!edgeObj) return pts;
132
+ const cached = edgeObj?.userData?.polylineLocal;
133
+ const isWorld = !!(edgeObj?.userData?.polylineWorld);
134
+ const v = new THREE.Vector3();
135
+ if (Array.isArray(cached) && cached.length >= 2) {
136
+ if (isWorld) return cached.map((p) => [p[0], p[1], p[2]]);
137
+ for (const p of cached) {
138
+ v.set(p[0], p[1], p[2]).applyMatrix4(edgeObj.matrixWorld);
139
+ pts.push([v.x, v.y, v.z]);
140
+ }
141
+ return pts;
142
+ }
143
+ const posAttr = edgeObj?.geometry?.getAttribute?.("position");
144
+ if (posAttr && posAttr.itemSize === 3 && posAttr.count >= 2) {
145
+ for (let i = 0; i < posAttr.count; i++) {
146
+ v.set(posAttr.getX(i), posAttr.getY(i), posAttr.getZ(i)).applyMatrix4(edgeObj.matrixWorld);
147
+ pts.push([v.x, v.y, v.z]);
148
+ }
149
+ return pts;
150
+ }
151
+ const aStart = edgeObj?.geometry?.attributes?.instanceStart;
152
+ const aEnd = edgeObj?.geometry?.attributes?.instanceEnd;
153
+ if (aStart && aEnd && aStart.itemSize === 3 && aEnd.itemSize === 3 && aStart.count === aEnd.count && aStart.count >= 1) {
154
+ v.set(aStart.getX(0), aStart.getY(0), aStart.getZ(0)).applyMatrix4(edgeObj.matrixWorld);
155
+ pts.push([v.x, v.y, v.z]);
156
+ for (let i = 0; i < aEnd.count; i++) {
157
+ v.set(aEnd.getX(i), aEnd.getY(i), aEnd.getZ(i)).applyMatrix4(edgeObj.matrixWorld);
158
+ pts.push([v.x, v.y, v.z]);
159
+ }
160
+ }
161
+ return pts;
162
+ }
163
+
164
+ function deriveAxisFromEdge(edgeObj) {
165
+ const poly = extractEdgePolylineWorld(edgeObj);
166
+ if (!Array.isArray(poly) || poly.length < 2) return null;
167
+ const start = new THREE.Vector3(poly[0][0], poly[0][1], poly[0][2]);
168
+ // Pick the farthest point from start to establish direction
169
+ let far = null;
170
+ let maxD2 = -1;
171
+ for (const p of poly) {
172
+ const v = new THREE.Vector3(p[0], p[1], p[2]);
173
+ const d2 = v.distanceToSquared(start);
174
+ if (d2 > maxD2) {
175
+ maxD2 = d2;
176
+ far = v;
177
+ }
178
+ }
179
+ if (!far || maxD2 < 1e-12) return null;
180
+ const dir = far.clone().sub(start).normalize();
181
+ // Pick a stable perpendicular for xDirection
182
+ const up = new THREE.Vector3(0, 1, 0);
183
+ let xDir = new THREE.Vector3().crossVectors(up, dir);
184
+ if (xDir.lengthSq() < 1e-12) xDir = new THREE.Vector3().crossVectors(new THREE.Vector3(1, 0, 0), dir);
185
+ if (xDir.lengthSq() < 1e-12) xDir.set(1, 0, 0);
186
+ xDir.normalize();
187
+ return {
188
+ origin: start,
189
+ direction: dir,
190
+ xDirection: xDir,
191
+ polyline: poly,
192
+ };
193
+ }
194
+
195
+ export class HelixFeature {
196
+ static shortName = "HX";
197
+ static longName = "Helix";
198
+ static inputParamsSchema = inputParamsSchema;
199
+
200
+ constructor() {
201
+ this.inputParams = {};
202
+ this.persistentData = this.persistentData || {};
203
+ }
204
+
205
+ uiFieldsTest(context) {
206
+ const params = this.inputParams || context?.params || {};
207
+ const placementMode = String(params?.placementMode || "transform").toLowerCase();
208
+ const modeRaw = params?.mode ?? params?.lengthMode;
209
+ const modeNormalized = String(modeRaw || "turns").toLowerCase();
210
+ const mode = modeNormalized === "pitch" || modeNormalized === "height" ? "pitch" : "turns";
211
+ if (!params?.mode && params?.lengthMode && this.inputParams) {
212
+ this.inputParams.mode = mode;
213
+ }
214
+
215
+ const exclude = new Set();
216
+ if (placementMode.startsWith("axis")) {
217
+ exclude.add("transform");
218
+ } else {
219
+ exclude.add("axis");
220
+ exclude.add("startPoint");
221
+ }
222
+ if (mode === "pitch") {
223
+ exclude.add("turns");
224
+ } else {
225
+ exclude.add("pitch");
226
+ }
227
+
228
+ return Array.from(exclude);
229
+ }
230
+
231
+ async run(partHistory) { // partHistory reserved for future downstream needs
232
+ const resolveRef = (val) => {
233
+ if (Array.isArray(val)) {
234
+ const obj = val.find(Boolean);
235
+ if (obj) return obj;
236
+ }
237
+ if (val && typeof val === "object") return val;
238
+ if (typeof val === "string" && partHistory?.scene) {
239
+ const found = partHistory.scene.getObjectByName(val);
240
+ if (found) return found;
241
+ }
242
+ return null;
243
+ };
244
+
245
+ const featureId = this.inputParams?.featureID
246
+ ? String(this.inputParams.featureID)
247
+ : "Helix";
248
+
249
+ const radius = Math.max(1e-6, Math.abs(toNumber(this.inputParams.radius, 5)));
250
+ const endRadiusRaw = toNumber(this.inputParams.endRadius, radius);
251
+ const endRadius = Math.max(1e-6, Math.abs(endRadiusRaw));
252
+ const pitchDefault = inputParamsSchema.pitch?.default_value ?? 5;
253
+ const rawPitch = toNumber(this.inputParams.pitch, pitchDefault);
254
+ const pitch = Math.abs(rawPitch);
255
+ const minPitch = 1e-6;
256
+ if (!Number.isFinite(pitch) || pitch < minPitch) {
257
+ throw new Error(`HelixFeature: pitch must be a non-zero number (received ${this.inputParams.pitch}).`);
258
+ }
259
+ const modeRaw = this.inputParams.mode ?? this.inputParams.lengthMode;
260
+ const normalizedMode = String(modeRaw || "turns").toLowerCase();
261
+ const mode = normalizedMode === "pitch" || normalizedMode === "height" ? "pitch" : "turns";
262
+ if (!this.inputParams.mode && this.inputParams.lengthMode) {
263
+ this.inputParams.mode = mode;
264
+ }
265
+ const turns = toNumber(this.inputParams.turns, 3);
266
+ const height = toNumber(
267
+ this.inputParams.height,
268
+ pitch * Math.max(1, toNumber(this.inputParams.turns, 3))
269
+ );
270
+ const startAngleDeg = toNumber(this.inputParams.startAngle, 0);
271
+ const resolution = Math.max(8, Math.floor(toNumber(this.inputParams.resolution, 64)));
272
+ const handedRaw = this.inputParams.handedness || (this.inputParams.clockwise ? "left" : "right");
273
+ const handedness = String(handedRaw || "right").toLowerCase() === "left" ? "left" : "right";
274
+ const placementMode = String(this.inputParams.placementMode || "transform").toLowerCase();
275
+
276
+ let placementOpts = { transform: this.inputParams.transform };
277
+ if (placementMode.startsWith("axis")) {
278
+ const axisSel = resolveRef(this.inputParams.axis);
279
+ const axisInfo = deriveAxisFromEdge(axisSel);
280
+ if (!axisInfo) {
281
+ console.warn("HelixFeature: axis placement selected but no valid edge provided.");
282
+ return { added: [], removed: [] };
283
+ }
284
+ // Flip the derived axis direction so the helix advances opposite the edge's forward direction
285
+ const axisDir = axisInfo.direction.clone().multiplyScalar(-1);
286
+ const startSel = resolveRef(this.inputParams.startPoint);
287
+ const startVec = toVec3(startSel) || axisInfo.origin;
288
+ placementOpts = {
289
+ origin: [startVec.x, startVec.y, startVec.z],
290
+ axis: [axisDir.x, axisDir.y, axisDir.z],
291
+ xDirection: [axisInfo.xDirection.x, axisInfo.xDirection.y, axisInfo.xDirection.z],
292
+ };
293
+ }
294
+
295
+ const helixData = BREP.buildHelixPolyline({
296
+ radius,
297
+ endRadius,
298
+ pitch,
299
+ turns,
300
+ height,
301
+ mode,
302
+ lengthMode: mode, // support legacy naming
303
+ startAngleDeg,
304
+ handedness,
305
+ segmentsPerTurn: resolution,
306
+ ...placementOpts,
307
+ });
308
+
309
+ if (!helixData || !Array.isArray(helixData.polyline) || helixData.polyline.length < 2) {
310
+ return { added: [], removed: [] };
311
+ }
312
+
313
+ const sceneGroup = new THREE.Group();
314
+ sceneGroup.name = featureId;
315
+ sceneGroup.type = "HELIX";
316
+ sceneGroup.onClick = () => {};
317
+
318
+ const positions = [];
319
+ for (const p of helixData.polyline) {
320
+ positions.push(p[0], p[1], p[2]);
321
+ }
322
+
323
+ const geometry = new LineGeometry();
324
+ geometry.setPositions(positions);
325
+
326
+ const edge = new BREP.Edge(geometry);
327
+ edge.name = `${featureId}:Helix`;
328
+ edge.userData = {
329
+ polylineLocal: helixData.polyline.map((p) => [p[0], p[1], p[2]]),
330
+ polylineWorld: true,
331
+ helixParams: {
332
+ radius,
333
+ endRadius,
334
+ pitch: helixData.pitch,
335
+ turns: helixData.turns,
336
+ height: helixData.height,
337
+ handedness,
338
+ clockwise: helixData.clockwise,
339
+ startAngleDeg,
340
+ mode,
341
+ lengthMode: mode,
342
+ resolution,
343
+ placementMode,
344
+ },
345
+ };
346
+ sceneGroup.add(edge);
347
+
348
+ try {
349
+ if (Array.isArray(helixData.axisLine) && helixData.axisLine.length === 2) {
350
+ const axisPositions = [
351
+ helixData.axisLine[0][0],
352
+ helixData.axisLine[0][1],
353
+ helixData.axisLine[0][2],
354
+ helixData.axisLine[1][0],
355
+ helixData.axisLine[1][1],
356
+ helixData.axisLine[1][2],
357
+ ];
358
+ const axisGeometry = new LineGeometry();
359
+ axisGeometry.setPositions(axisPositions);
360
+ const axisEdge = new BREP.Edge(axisGeometry);
361
+ axisEdge.name = `${featureId}:Axis`;
362
+ axisEdge.userData = {
363
+ polylineLocal: helixData.axisLine.map((p) => [p[0], p[1], p[2]]),
364
+ polylineWorld: true,
365
+ helixAxis: true,
366
+ centerline: true,
367
+ sourceAxisName: (Array.isArray(this.inputParams.axis) ? this.inputParams.axis.find(Boolean)?.name : this.inputParams.axis?.name) || null,
368
+ };
369
+ sceneGroup.add(axisEdge);
370
+
371
+ // Add centerline endpoints for the helix axis
372
+ try {
373
+ const vAxisStart = new BREP.Vertex(helixData.axisLine[0], { name: `${featureId}:AxisStart` });
374
+ vAxisStart.userData = { helixFeatureId: featureId, centerline: true, endpoint: "start" };
375
+ const vAxisEnd = new BREP.Vertex(helixData.axisLine[1], { name: `${featureId}:AxisEnd` });
376
+ vAxisEnd.userData = { helixFeatureId: featureId, centerline: true, endpoint: "end" };
377
+ sceneGroup.add(vAxisStart);
378
+ sceneGroup.add(vAxisEnd);
379
+ } catch {
380
+ // centerline endpoints are optional; ignore failures
381
+ }
382
+ }
383
+ } catch {
384
+ // axis visualization is optional; ignore failures
385
+ }
386
+
387
+ try {
388
+ const first = helixData.polyline[0];
389
+ const last = helixData.polyline[helixData.polyline.length - 1];
390
+ const vStart = new BREP.Vertex(first, { name: `${featureId}:Start` });
391
+ const vEnd = new BREP.Vertex(last, { name: `${featureId}:End` });
392
+ vStart.userData = { helixFeatureId: featureId, endpoint: "start" };
393
+ vEnd.userData = { helixFeatureId: featureId, endpoint: "end" };
394
+ sceneGroup.add(vStart);
395
+ sceneGroup.add(vEnd);
396
+ } catch {
397
+ // vertices are just helpers; ignore failures
398
+ }
399
+
400
+ this.persistentData = this.persistentData || {};
401
+ this.persistentData.helix = helixData;
402
+
403
+ return { added: [sceneGroup], removed: [] };
404
+ }
405
+ }