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,302 @@
1
+ import * as THREE from 'three';
2
+ import { Solid } from './BetterSolid.js';
3
+
4
+ // Simple spatial hash grid for neighbor queries
5
+ class SpatialHash {
6
+ constructor(points, cellSize) {
7
+ this.cellSize = cellSize;
8
+ this.map = new Map();
9
+ for (const p of points) {
10
+ const k = this._key(p.x, p.y, p.z);
11
+ let arr = this.map.get(k);
12
+ if (!arr) { arr = []; this.map.set(k, arr); }
13
+ arr.push(p);
14
+ }
15
+ }
16
+ _key(x, y, z) {
17
+ const cs = this.cellSize;
18
+ const ix = Math.floor(x / cs);
19
+ const iy = Math.floor(y / cs);
20
+ const iz = Math.floor(z / cs);
21
+ return ix + '|' + iy + '|' + iz;
22
+ }
23
+ queryNeighborhood(x, y, z, radius) {
24
+ const cs = this.cellSize;
25
+ const r = Math.max(radius, cs);
26
+ const ix = Math.floor(x / cs);
27
+ const iy = Math.floor(y / cs);
28
+ const iz = Math.floor(z / cs);
29
+ const dr = Math.ceil(r / cs) + 1;
30
+ const out = [];
31
+ for (let dx = -dr; dx <= dr; dx++) {
32
+ for (let dy = -dr; dy <= dr; dy++) {
33
+ for (let dz = -dr; dz <= dr; dz++) {
34
+ const k = (ix + dx) + '|' + (iy + dy) + '|' + (iz + dz);
35
+ const arr = this.map.get(k);
36
+ if (arr) out.push(...arr);
37
+ }
38
+ }
39
+ }
40
+ return out;
41
+ }
42
+ }
43
+
44
+ function computeMedianKNNDistance(points, k = 8) {
45
+ if (!points.length) return 0;
46
+ // Build spatial hash at heuristic cell size
47
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
48
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
49
+ for (const p of points) {
50
+ if (p.x < minX) minX = p.x; if (p.y < minY) minY = p.y; if (p.z < minZ) minZ = p.z;
51
+ if (p.x > maxX) maxX = p.x; if (p.y > maxY) maxY = p.y; if (p.z > maxZ) maxZ = p.z;
52
+ }
53
+ const diag = Math.hypot(maxX - minX, maxY - minY, maxZ - minZ) || 1;
54
+ const cell = diag / Math.cbrt(Math.max(8, points.length));
55
+ const sh = new SpatialHash(points, cell);
56
+
57
+ const dists = new Array(points.length).fill(0);
58
+ const tmp = new Array(k).fill(Infinity);
59
+ for (let i = 0; i < points.length; i++) {
60
+ const p = points[i];
61
+ const nbrs = sh.queryNeighborhood(p.x, p.y, p.z, cell * 2.5);
62
+ // track k nearest
63
+ for (let j = 0; j < nbrs.length; j++) {
64
+ const q = nbrs[j];
65
+ if (q === p) continue;
66
+ const dx = p.x - q.x, dy = p.y - q.y, dz = p.z - q.z;
67
+ const d = Math.hypot(dx, dy, dz);
68
+ let worstIdx = -1, worst = -1;
69
+ for (let t = 0; t < k; t++) { if (tmp[t] > worst) { worst = tmp[t]; worstIdx = t; } }
70
+ if (d < worst) tmp[worstIdx] = d;
71
+ }
72
+ // use median of tmp (ignore Infinity)
73
+ const vals = tmp.filter(Number.isFinite).sort((a,b)=>a-b);
74
+ dists[i] = vals.length ? vals[Math.floor(vals.length/2)] : cell;
75
+ for (let t = 0; t < k; t++) tmp[t] = Infinity;
76
+ }
77
+ const sorted = dists.filter(Number.isFinite).sort((a,b)=>a-b);
78
+ return sorted.length ? sorted[Math.floor(sorted.length/2)] : cell;
79
+ }
80
+
81
+ // Marching Cubes lookup tables (edgeTable, triTable)
82
+ // Tables adapted from Paul Bourke, public domain.
83
+ const edgeTable = new Int32Array([
84
+ 0x0,0x109,0x203,0x30a,0x406,0x50f,0x605,0x70c,0x80c,0x905,0xa0f,0xb06,0xc0a,0xd03,0xe09,0xf00,
85
+ 0x190,0x99,0x393,0x29a,0x596,0x49f,0x795,0x69c,0x99c,0x895,0xb9f,0xa96,0xd9a,0xc93,0xf99,0xe90,
86
+ 0x230,0x339,0x33,0x13a,0x636,0x73f,0x435,0x53c,0xa3c,0xb35,0x83f,0x936,0xe3a,0xf33,0xc39,0xd30,
87
+ 0x3a0,0x2a9,0x1a3,0xaa,0x7a6,0x6af,0x5a5,0x4ac,0xbac,0xaa5,0x9af,0x8a6,0xfaa,0xea3,0xda9,0xca0,
88
+ 0x460,0x569,0x663,0x76a,0x66,0x16f,0x265,0x36c,0xc6c,0xd65,0xe6f,0xf66,0x86a,0x963,0xa69,0xb60,
89
+ 0x5f0,0x4f9,0x7f3,0x6fa,0x1f6,0xff,0x3f5,0x2fc,0xdfc,0xcf5,0xfff,0xef6,0x9fa,0x8f3,0xbf9,0xaf0,
90
+ 0x650,0x759,0x453,0x55a,0x256,0x35f,0x55,0x15c,0xe5c,0xf55,0xc5f,0xd56,0xa5a,0xb53,0x859,0x950,
91
+ 0x7c0,0x6c9,0x5c3,0x4ca,0x3c6,0x2cf,0x1c5,0xcc,0xfcc,0xec5,0xdcf,0xcc6,0xbca,0xac3,0x9c9,0x8c0,
92
+ 0x8c0,0x9c9,0xac3,0xbca,0xcc6,0xdcf,0xec5,0xfcc,0xcc,0x1c5,0x2cf,0x3c6,0x4ca,0x5c3,0x6c9,0x7c0,
93
+ 0x950,0x859,0xb53,0xa5a,0xd56,0xc5f,0xf55,0xe5c,0x15c,0x55,0x35f,0x256,0x55a,0x453,0x759,0x650,
94
+ 0xaf0,0xbf9,0x8f3,0x9fa,0xef6,0xfff,0xcf5,0xdfc,0x2fc,0x3f5,0xff,0x1f6,0x6fa,0x7f3,0x4f9,0x5f0,
95
+ 0xb60,0xa69,0x963,0x86a,0xf66,0xe6f,0xd65,0xc6c,0x36c,0x265,0x16f,0x66,0x76a,0x663,0x569,0x460,
96
+ 0xca0,0xda9,0xea3,0xfaa,0x8a6,0x9af,0xaa5,0xbac,0x4ac,0x5a5,0x6af,0x7a6,0xaa,0x1a3,0x2a9,0x3a0,
97
+ 0xd30,0xc39,0xf33,0xe3a,0x936,0x83f,0xb35,0xa3c,0x53c,0x435,0x73f,0x636,0x13a,0x33,0x339,0x230,
98
+ 0xe90,0xf99,0xc93,0xd9a,0xa96,0xb9f,0x895,0x99c,0x69c,0x795,0x49f,0x596,0x29a,0x393,0x99,0x190,
99
+ 0xf00,0xe09,0xd03,0xc0a,0xb06,0xa0f,0x905,0x80c,0x70c,0x605,0x50f,0x406,0x30a,0x203,0x109,0x0
100
+ ]);
101
+
102
+ const triTable = [
103
+ [],[0,8,3],[0,1,9],[1,8,3,9,8,1],[1,2,10],[0,8,3,1,2,10],[9,2,10,0,2,9],[2,8,3,2,10,8,10,9,8],
104
+ [3,11,2],[0,11,2,8,11,0],[1,9,0,2,3,11],[1,11,2,1,9,11,9,8,11],[3,10,1,11,10,3],[0,10,1,0,8,10,8,11,10],[3,9,0,3,11,9,11,10,9],[9,8,10,10,8,11],
105
+ [4,7,8],[4,3,0,7,3,4],[0,1,9,8,4,7],[4,1,9,4,7,1,7,3,1],[1,2,10,8,4,7],[3,4,7,3,0,4,1,2,10],[9,2,10,9,0,2,8,4,7],[2,10,9,2,9,7,2,7,3,7,9,4],
106
+ [8,4,7,3,11,2],[11,4,7,11,2,4,2,0,4],[9,0,1,8,4,7,2,3,11],[4,7,11,9,4,11,9,11,2,9,2,1],[3,10,1,3,11,10,7,8,4],[1,11,10,1,4,11,1,0,4,7,11,4],[4,7,8,9,0,11,9,11,10,11,0,3],[4,7,11,4,11,9,9,11,10],
107
+ [9,5,4],[9,5,4,0,8,3],[0,5,4,1,5,0],[8,5,4,8,3,5,3,1,5],[1,2,10,9,5,4],[3,0,8,1,2,10,4,9,5],[5,2,10,5,4,2,4,0,2],[2,10,5,3,2,5,3,5,4,3,4,8],
108
+ [9,5,4,2,3,11],[0,11,2,0,8,11,4,9,5],[0,5,4,0,1,5,2,3,11],[2,1,5,2,5,8,2,8,11,4,8,5],[10,3,11,10,1,3,9,5,4],[4,9,5,0,8,1,8,10,1,8,11,10],[5,4,0,5,0,11,5,11,10,11,0,3],[5,4,8,5,8,10,10,8,11],
109
+ [9,7,8,5,7,9],[9,3,0,9,5,3,5,7,3],[0,7,8,0,1,7,1,5,7],[1,5,3,3,5,7],[9,5,4,10,1,2,8,3,7],[5,7,9,7,8,9,1,2,10,0,3,4,3,7,4],[8,0,2,8,2,5,8,5,7,10,5,2],[2,10,5,2,5,3,3,5,7],
110
+ [7,9,5,7,8,9,3,11,2],[9,5,7,9,7,2,9,2,0,2,7,11],[2,3,11,0,1,8,1,7,8,1,5,7],[11,2,1,11,1,7,7,1,5],[9,5,4,10,1,6,11,2,3,7,8,4],[6,10,1,6,1,7,7,1,0,7,0,8,4,9,5],[7,8,4,6,10,3,6,3,7,3,10,1,0,5,4],[6,10,5,6,5,7,4,7,5],
111
+ [10,6,5],[8,3,0,5,10,6],[0,1,9,5,10,6],[8,1,9,8,3,1,10,6,5],[1,2,10,6,5,8,6,8,11,8,5,4],[1,2,10,3,0,11,0,6,11,0,4,6,6,5,11],[4,11,6,4,6,0,0,6,2,5,10,6],[2,3,11,4,6,5,4,0,6,0,1,6,1,10,6],
112
+ [3,2,8,8,2,4,4,2,6,10,6,5],[0,4,2,2,4,6,1,9,5,10,6,5],[5,10,6,1,9,2,2,9,11,11,9,8],[6,5,10,3,11,0,0,11,6,0,6,4,4,6,8],[9,5,4,10,6,1,1,6,11,1,11,3],[11,1,0,11,0,6,6,0,4,8,9,5,8,5,6],[6,11,3,6,3,4,4,3,8,6,5,10],[5,10,6,4,6,8,8,6,11,8,11,3],
113
+ [5,9,4,6,7,11],[0,8,3,4,5,9,11,6,7],[1,5,0,5,4,0,7,11,6],[8,3,1,8,1,6,8,6,7,6,1,5],[1,2,10,9,4,5,6,7,11],[1,2,10,0,8,3,4,5,9,6,7,11],[7,11,6,5,4,10,10,4,2,2,4,0],[8,3,4,4,3,5,5,3,2,5,2,10,11,6,7],
114
+ [9,4,5,2,3,7,2,7,6,7,3,8],[0,9,4,0,4,7,0,7,3,6,7,2,2,7,1,1,7,5],[6,7,2,2,7,1,1,7,5,0,1,4,4,1,5],[6,7,2,2,7,1,1,7,5,8,3,4,4,3,5],[2,10,1,11,6,7,9,4,5],[11,6,7,1,2,10,0,8,3,5,9,4],[4,5,9,0,1,7,0,7,8,7,1,6,1,10,6],[11,6,7,2,10,3,3,10,8,8,10,5,8,5,4],
115
+ [6,7,11,10,6,5],[0,8,3,5,10,6,11,6,7],[9,0,1,5,10,6,11,6,7],[1,8,3,1,9,8,5,10,6,11,6,7],[1,6,5,2,6,1,3,0,8,11,6,7],[2,6,1,1,6,5,0,2,9,9,2,11,9,11,6,9,6,5,11,6,7],[6,5,2,2,5,3,3,5,9,3,9,8,11,6,7],[2,6,5,2,5,3,3,5,8,8,5,9,11,6,7],
116
+ [7,11,6,8,4,9,8,9,3,3,9,1,5,10,6],[5,10,6,0,1,4,4,1,8,8,1,3,11,6,7],[11,6,7,1,4,9,1,2,4,2,6,4,8,3,0,5,10,6],[11,6,7,1,2,5,5,2,6,3,0,8,9,4,1],[6,7,11,2,3,10,10,3,5,5,3,4,5,4,9],[2,0,10,10,0,5,5,0,4,11,6,7,8,3,1,1,3,6,1,6,5,3,6,7],[5,10,6,0,4,8,3,0,8,11,6,7,2,0,1],[5,10,6,2,1,11,11,1,7,7,1,4,7,4,8,11,6,7],
117
+ [9,4,5,7,11,6,10,2,1],[6,7,11,1,10,2,0,8,3,4,5,9],[7,11,6,10,2,1,8,3,4,4,3,5,5,3,1,5,1,9],[6,7,11,10,2,1,9,0,5,5,0,4],[4,5,9,2,3,10,10,3,6,6,3,7],[6,7,10,10,7,1,1,7,8,1,8,0,4,5,9],[7,6,3,3,6,0,0,6,2,4,5,9,1,10,2],[7,6,8,8,6,3,3,6,2,8,4,5,5,4,9,10,2,1],
118
+ [2,3,6,6,3,7],[0,8,2,2,8,6,6,8,7],[1,9,0,2,3,6,6,3,7],[1,8,2,1,9,8,2,8,6,6,8,7],[10,2,1,6,7,0,0,7,9,9,7,8],[10,2,1,0,6,7,0,7,8],[6,7,2,2,7,3,0,1,9],[6,7,2,2,7,3,1,9,8,1,8,3],
119
+ [7,8,3,7,3,6,6,3,2],[7,0,8,7,6,0,6,2,0],[2,7,6,2,3,7,0,1,9],[1,6,2,1,9,6,9,7,6,9,8,7,3,7,2],[9,7,8,9,6,7,9,1,6,1,2,6],[1,6,2,1,9,6,9,7,6,0,6,3,0,3,8],[0,6,3,0,1,6,1,2,6],[6,2,1,6,1,3,3,1,8,8,1,9],
120
+ [1,10,2,3,7,8,3,6,7],[10,2,1,0,8,6,0,6,7,0,7,3],[9,0,1,8,3,6,8,6,7,2,6,3,10,2,1],[6,7,8,6,8,2,2,8,0,10,2,1,9,0,1],[7,3,6,6,3,2,1,10,0,0,10,8,8,10,6],[1,10,2,3,6,7,0,3,7,0,7,8,6,7,10,0,10,8],[6,7,2,2,7,3,9,0,1],[7,2,6,7,3,2,8,9,0,8,1,9],
121
+ [10,6,5],[0,8,3,5,10,6],[9,0,1,5,10,6],[1,8,3,1,9,8,5,10,6],[1,6,5,2,6,1],[1,6,5,1,2,6,3,0,8],[9,6,5,9,0,6,0,2,6],[5,9,8,5,8,2,2,8,3,6,5,2],
122
+ [2,3,11,10,6,5],[11,0,8,11,2,0,10,6,5],[0,1,9,2,3,11,5,10,6],[5,10,6,1,9,2,2,9,11,11,9,8],[6,3,11,6,5,3,5,1,3,10,6,1],[0,8,11,0,11,5,0,5,1,5,11,6,10,6,5],[3,11,6,3,6,0,0,6,5,0,5,9,10,6,5],[6,5,9,6,9,11,11,9,8],
123
+ [5,10,6,4,7,8],[4,3,0,4,7,3,6,5,10],[1,9,0,5,10,6,8,4,7],[10,6,5,1,9,7,1,7,3,7,9,4],[6,1,2,6,5,1,4,7,8],[1,2,5,5,2,6,3,0,4,3,4,7],[8,4,7,9,0,5,5,0,6,6,0,2],[7,3,4,7,8,3,6,5,2,2,5,1],
124
+ [9,4,5,11,2,3,7,8,6,8,6,7],[2,11,0,0,11,4,4,11,6,9,4,5,10,6,5,7,8,3],[2,3,11,0,1,9,4,7,8,5,10,6],[9,1,4,4,1,7,7,1,3,6,5,10,2,11,3,3,11,7],[4,7,8,6,5,10,3,11,1,1,11,0],[1,0,9,8,4,7,11,6,5,11,5,3,3,5,0],[7,8,4,11,6,5,3,0,6,0,5,6,0,1,5],[7,8,4,3,6,5,3,5,1,6,3,2,10,6,5],
125
+ [7,2,3,7,6,2,5,10,4,4,10,9,8,4,7],[9,5,4,0,1,6,0,6,7,0,7,8,2,6,1,10,6,5],[3,2,7,7,2,6,0,1,4,4,1,5,8,4,7,10,6,5],[6,2,7,6,7,5,5,7,8,5,8,4,1,9,0,3,2,7],
126
+ [2,3,6,6,3,7,10,6,5,8,4,9,9,4,5],[5,10,6,1,9,0,7,2,6,7,3,2,8,4,7],[0,8,3,4,7,2,4,2,5,5,2,6,10,6,5,1,9,0],[6,5,10,2,1,7,7,1,4,7,4,8,3,2,7],
127
+ [9,5,4,10,6,5],[3,0,8,9,5,4,10,6,5],[5,4,0,5,0,10,10,0,2,6,5,10],[8,3,1,8,1,4,4,1,5,2,6,1,1,6,10],
128
+ [1,2,10,9,5,4,3,0,8],[4,9,5,0,10,6,0,1,10,0,6,7,0,7,8,6,5,7,2,3,6],[5,4,9,1,10,2,0,8,6,0,6,7,0,7,3],[6,7,2,2,7,0,0,7,8,1,10,2,5,4,9],
129
+ [6,5,10,2,3,11,4,9,5,8,4,7],[5,10,6,11,2,1,11,1,8,8,1,9,4,7,8,0,4,9],[3,11,2,0,1,10,0,10,6,0,6,7,4,9,5],[6,7,8,6,8,10,10,8,1,1,8,3,5,10,6,4,9,5],
130
+ [5,10,6,4,9,7,7,9,3,3,9,8,11,2,3],[6,5,10,4,9,7,7,9,8,2,1,11,11,1,0],[1,10,6,1,6,3,3,6,7,0,1,4,4,1,5,8,4,7,2,3,11],[6,7,8,6,8,10,10,8,1,4,5,9,11,2,3,0,1,0],
131
+ [6,7,11,10,6,5,8,4,9,8,9,3,3,9,1],[0,8,4,4,8,5,5,8,1,11,6,7,10,6,5],[11,6,7,1,9,5,1,5,0,0,5,4,3,1,0],[6,7,11,5,10,4,4,10,0,0,10,2,1,9,5,8,3,0],
132
+ [7,11,6,5,10,4,4,10,2,4,2,8,8,2,3,9,5,4],[5,10,6,4,9,2,4,2,0,11,6,7,8,4,3,3,4,2],[11,6,7,8,4,3,3,4,2,10,6,5,1,9,0],[7,11,6,8,4,3,10,6,5,2,1,0],
133
+ [7,11,6,8,4,3],[7,11,6]
134
+ ];
135
+
136
+ function marchingCubesSDF({ nx, ny, nz, h, origin, sample, iso = 1.0 }) {
137
+ const solid = new Solid();
138
+ // Cache of interpolated edge vertices
139
+ const interpCache = new Map();
140
+ const keyEdge = (i,j,k,e) => `${i}|${j}|${k}|${e}`;
141
+ const lerp = (p1, p2, v1, v2) => {
142
+ const t = (iso - v1) / (v2 - v1 + 1e-20);
143
+ return new THREE.Vector3(
144
+ p1.x + t * (p2.x - p1.x),
145
+ p1.y + t * (p2.y - p1.y),
146
+ p1.z + t * (p2.z - p1.z)
147
+ );
148
+ };
149
+
150
+ // Precompute scalar field per grid node
151
+ const val = new Float32Array(nx * ny * nz);
152
+ const idx3 = (i,j,k) => (k*ny + j)*nx + i;
153
+ for (let k = 0; k < nz; k++) {
154
+ for (let j = 0; j < ny; j++) {
155
+ for (let i = 0; i < nx; i++) {
156
+ const x = origin.x + i * h;
157
+ const y = origin.y + j * h;
158
+ const z = origin.z + k * h;
159
+ val[idx3(i,j,k)] = sample(x,y,z);
160
+ }
161
+ }
162
+ }
163
+
164
+ // Corner offsets
165
+ const co = [
166
+ [0,0,0],[1,0,0],[1,1,0],[0,1,0],
167
+ [0,0,1],[1,0,1],[1,1,1],[0,1,1]
168
+ ];
169
+ // Edges: pairs of corners
170
+ const eCorners = [
171
+ [0,1],[1,2],[2,3],[3,0],
172
+ [4,5],[5,6],[6,7],[7,4],
173
+ [0,4],[1,5],[2,6],[3,7]
174
+ ];
175
+
176
+ for (let k = 0; k < nz - 1; k++) {
177
+ for (let j = 0; j < ny - 1; j++) {
178
+ for (let i = 0; i < nx - 1; i++) {
179
+ // Gather cube values
180
+ const cube = new Float32Array(8);
181
+ const p = new Array(8);
182
+ for (let c = 0; c < 8; c++) {
183
+ const ii = i + co[c][0], jj = j + co[c][1], kk = k + co[c][2];
184
+ cube[c] = val[idx3(ii,jj,kk)];
185
+ p[c] = new THREE.Vector3(origin.x + ii*h, origin.y + jj*h, origin.z + kk*h);
186
+ }
187
+ // Compute cube index
188
+ let ci = 0;
189
+ for (let c = 0; c < 8; c++) if (cube[c] < iso) ci |= (1 << c);
190
+ const et = edgeTable[ci];
191
+ if (et === 0) continue;
192
+ const vertList = new Array(12);
193
+ for (let e = 0; e < 12; e++) {
194
+ if (et & (1 << e)) {
195
+ const kcache = keyEdge(i,j,k,e);
196
+ let v = interpCache.get(kcache);
197
+ if (!v) {
198
+ const a = eCorners[e][0], b = eCorners[e][1];
199
+ v = lerp(p[a], p[b], cube[a], cube[b]);
200
+ interpCache.set(kcache, v);
201
+ }
202
+ vertList[e] = v;
203
+ }
204
+ }
205
+ const tris = triTable[ci];
206
+ for (let t = 0; t < tris.length; t += 3) {
207
+ const a = vertList[tris[t+0]];
208
+ const b = vertList[tris[t+1]];
209
+ const c = vertList[tris[t+2]];
210
+ solid.addTriangle('FILLET_TOOL_WRAP', [a.x,a.y,a.z], [b.x,b.y,b.z], [c.x,c.y,c.z]);
211
+ }
212
+ }
213
+ }
214
+ }
215
+ try { solid.fixTriangleWindingsByAdjacency(); } catch {}
216
+ try { solid._weldVerticesByEpsilon(1e-8); } catch {}
217
+ try { solid.fixTriangleWindingsByAdjacency(); } catch {}
218
+ return solid;
219
+ }
220
+
221
+ /**
222
+ * Build a tight point-cloud wrap as a Solid using marching cubes over a density field.
223
+ * @param {Array<[number,number,number]>|Array<{x:number,y:number,z:number}>} rawPoints input points
224
+ * @param {object} [opts]
225
+ * @param {number} [opts.padding] Absolute padding around the point cloud (default 2% of diagonal)
226
+ * @param {number} [opts.alphaRadius] Alpha radius used to choose grid spacing (default 0.6 * median kNN)
227
+ * @returns {Solid}
228
+ */
229
+ export function buildTightPointCloudWrap(rawPoints, opts = {}) {
230
+ const points = rawPoints
231
+ .filter(p => Number.isFinite(p.x) && Number.isFinite(p.y) && Number.isFinite(p.z));
232
+ if (points.length < 4) return new Solid();
233
+ // Bounding box
234
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
235
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
236
+ for (const p of points) {
237
+ if (p.x < minX) minX = p.x; if (p.y < minY) minY = p.y; if (p.z < minZ) minZ = p.z;
238
+ if (p.x > maxX) maxX = p.x; if (p.y > maxY) maxY = p.y; if (p.z > maxZ) maxZ = p.z;
239
+ }
240
+ const pad = opts.padding != null ? opts.padding : 0.02 * Math.max(maxX - minX, maxY - minY, maxZ - minZ);
241
+ minX -= pad; minY -= pad; minZ -= pad;
242
+ maxX += pad; maxY += pad; maxZ += pad;
243
+
244
+ // Choose radius from median kNN distance for tightness
245
+ const medianNN = computeMedianKNNDistance(points, 8);
246
+ const R = Math.max(1e-6, (opts.alphaRadius || 0.6 * medianNN));
247
+
248
+ // Grid resolution
249
+ const diag = Math.hypot(maxX - minX, maxY - minY, maxZ - minZ);
250
+ const baseCells = Math.max(24, Math.min(96, Math.floor(diag / (R * 0.75))));
251
+ const sizeX = maxX - minX, sizeY = maxY - minY, sizeZ = maxZ - minZ;
252
+ const maxSize = Math.max(sizeX, sizeY, sizeZ);
253
+ const buildAtScale = (scale) => {
254
+ const cells = Math.max(24, Math.min(192, Math.floor(baseCells * scale)));
255
+ const h = maxSize / cells;
256
+ const nx = Math.max(8, Math.floor(sizeX / h) + 1);
257
+ const ny = Math.max(8, Math.floor(sizeY / h) + 1);
258
+ const nz = Math.max(8, Math.floor(sizeZ / h) + 1);
259
+ const origin = new THREE.Vector3(minX, minY, minZ);
260
+ const solid = marchingCubesSDF({ nx, ny, nz, h, origin, sample, iso: R });
261
+ try { solid.fixTriangleWindingsByAdjacency(); } catch {}
262
+ try { solid._weldVerticesByEpsilon(Math.max(1e-9, 1e-4 * R)); } catch {}
263
+ try { solid.fixTriangleWindingsByAdjacency(); } catch {}
264
+ return solid;
265
+ };
266
+
267
+ // Spatial hash for fast nearest
268
+ const sh = new SpatialHash(points, Math.max(h, R));
269
+ const sample = (x,y,z) => {
270
+ // Distance to nearest point (Euclidean)
271
+ const nbrs = sh.queryNeighborhood(x,y,z, R * 2.5);
272
+ let best2 = Infinity;
273
+ for (let i = 0; i < nbrs.length; i++) {
274
+ const q = nbrs[i];
275
+ const dx = x - q.x, dy = y - q.y, dz = z - q.z;
276
+ const d2 = dx*dx + dy*dy + dz*dz;
277
+ if (d2 < best2) best2 = d2;
278
+ }
279
+ return Math.sqrt(best2);
280
+ };
281
+
282
+ // Try increasing resolutions until the mesh manifoldizes
283
+ const scales = [1, 1.5, 2.0, 3.0];
284
+ let out = null;
285
+ for (let s = 0; s < scales.length; s++) {
286
+ out = buildAtScale(scales[s]);
287
+ try {
288
+ // Probe manifoldization
289
+ const __m = out.getMesh();
290
+ try { /* probe */ } finally { try { if (__m && typeof __m.delete === 'function') __m.delete(); } catch {} }
291
+ break; // success
292
+ } catch (e) {
293
+ if (s === scales.length - 1) {
294
+ // Give up and return the best-effort mesh
295
+ try { console.warn('[PointCloudWrap] Manifoldization failed at max refinement:', e?.message || e); } catch {}
296
+ } else {
297
+ continue;
298
+ }
299
+ }
300
+ }
301
+ return out;
302
+ }
@@ -0,0 +1,345 @@
1
+ import { Solid } from './BetterSolid.js';
2
+ import * as THREE from 'three';
3
+ import { getEdgeLineEndpointsWorld, getEdgePolylineWorld } from './edgePolylineUtils.js';
4
+
5
+ export class Revolve extends Solid {
6
+ /**
7
+ * @param {object} [opts]
8
+ * @param {import('./Face.js').Face} opts.face Face/profile to revolve
9
+ * @param {any} opts.axis Axis reference (Edge/Line object or array with entry 0) defining rotation line
10
+ * @param {number} [opts.angle=360] Sweep angle in degrees
11
+ * @param {number} [opts.resolution=64] Segment resolution along the revolution
12
+ * @param {string} [opts.name='Revolve'] Name of the resulting solid
13
+ */
14
+ constructor({ face, axis, angle = 360, resolution = 64, name = 'Revolve' } = {}) {
15
+ super();
16
+ this.params = { face, axis, angle, resolution };
17
+ this.name = name || 'Revolve';
18
+ this.generate();
19
+ }
20
+
21
+ generate() {
22
+ const { face: faceObj, axis, angle, resolution } = this.params;
23
+ if (!faceObj || !faceObj.geometry) return;
24
+
25
+ // Resolve axis edge → world-space origin+direction
26
+ const axisObj = Array.isArray(axis) ? (axis[0] || null) : (axis || null);
27
+ const A = new THREE.Vector3(0, 0, 0);
28
+ const B = new THREE.Vector3(0, 1, 0);
29
+ if (axisObj) {
30
+ const endpoints = getEdgeLineEndpointsWorld(axisObj);
31
+ if (endpoints) {
32
+ A.copy(endpoints.start);
33
+ B.copy(endpoints.end);
34
+ }
35
+ }
36
+ let axisDir = B.clone().sub(A); if (axisDir.lengthSq() < 1e-12) axisDir.set(0, 1, 0); axisDir.normalize();
37
+
38
+ // Ensure positive angles follow a consistent orientation relative to the face normal.
39
+ const faceNormal = (typeof faceObj.getAverageNormal === 'function') ? faceObj.getAverageNormal().clone() : null;
40
+ const faceCentroid = computeFaceCentroidWorld(faceObj);
41
+ if (faceNormal && faceCentroid && faceNormal.lengthSq() > 1e-12) {
42
+ faceNormal.normalize();
43
+ const radial = faceCentroid.clone().sub(A);
44
+ const projLen = radial.dot(axisDir);
45
+ radial.sub(axisDir.clone().multiplyScalar(projLen));
46
+ if (radial.lengthSq() > 1e-12) {
47
+ const orientVec = new THREE.Vector3().crossVectors(axisDir, radial);
48
+ const orient = orientVec.dot(faceNormal);
49
+ if (orient < 0) axisDir.negate();
50
+ }
51
+ }
52
+
53
+ const deg = Number.isFinite(angle) ? angle : 360;
54
+ const sweepRad = -deg * Math.PI / 180;
55
+ const rawResolution = Number(resolution);
56
+ const baseResolution = Number.isFinite(rawResolution) ? rawResolution : 64;
57
+ const revolvedResolution = Math.max(3, Math.floor(Math.abs(baseResolution) || 0));
58
+ const steps = Math.max(3, Math.ceil((Math.abs(deg) / 360) * revolvedResolution));
59
+ const dA = sweepRad / steps;
60
+ const baseName = faceObj?.name || 'Face';
61
+ const startName = `${baseName}_START`;
62
+ const endName = `${baseName}_END`;
63
+ const setFaceType = (name, faceType) => {
64
+ if (!name || !faceType) return;
65
+ try { this.setFaceMetadata(name, { faceType }); } catch { /* best effort */ }
66
+ };
67
+ const edgeSourceByName = new Map();
68
+ const registerEdgeSource = (faceName, edge) => {
69
+ if (!faceName || !edge) return;
70
+ if (!edgeSourceByName.has(faceName)) {
71
+ edgeSourceByName.set(faceName, edge?.name || 'EDGE');
72
+ }
73
+ };
74
+ const ensureSidewallMetadata = (faceName) => {
75
+ if (!faceName) return;
76
+ const sourceEdgeName = edgeSourceByName.get(faceName);
77
+ if (sourceEdgeName) {
78
+ try { this.setFaceMetadata(faceName, { sourceEdgeName }); } catch { /* best effort */ }
79
+ }
80
+ };
81
+ setFaceType(startName, 'STARTCAP');
82
+ setFaceType(endName, 'ENDCAP');
83
+
84
+ // Helper: rotate world point around axis by angle
85
+ const rotQ = new THREE.Quaternion();
86
+ const tmp = new THREE.Vector3();
87
+ const rotateP = (p, a) => {
88
+ rotQ.setFromAxisAngle(axisDir, a);
89
+ tmp.set(p.x, p.y, p.z).sub(A).applyQuaternion(rotQ).add(A);
90
+ return [tmp.x, tmp.y, tmp.z];
91
+ };
92
+
93
+ // Caps: use sketch profile triangulation if available, else face geometry
94
+ const groups = Array.isArray(faceObj?.userData?.profileGroups) ? faceObj.userData.profileGroups : null;
95
+ if (Math.abs(deg) < 360 - 1e-6) {
96
+ if (groups && groups.length) {
97
+ for (const g of groups) {
98
+ const contour2D = g.contour2D || [];
99
+ const holes2D = g.holes2D || [];
100
+ const contourW = g.contourW || [];
101
+ const holesW = g.holesW || [];
102
+ if (contour2D.length < 3 || contourW.length !== contour2D.length) continue;
103
+ const contourV2 = contour2D.map(p => new THREE.Vector2(p[0], p[1]));
104
+ const holesV2 = holes2D.map(h => h.map(p => new THREE.Vector2(p[0], p[1])));
105
+ const tris = THREE.ShapeUtils.triangulateShape(contourV2, holesV2);
106
+ const allW = contourW.concat(...holesW);
107
+ for (const t of tris) {
108
+ const p0 = allW[t[0]], p1 = allW[t[1]], p2 = allW[t[2]];
109
+ const v0 = new THREE.Vector3(p0[0], p0[1], p0[2]);
110
+ const v1 = new THREE.Vector3(p1[0], p1[1], p1[2]);
111
+ const v2 = new THREE.Vector3(p2[0], p2[1], p2[2]);
112
+ // Start cap reversed
113
+ this.addTriangle(startName, [v0.x, v0.y, v0.z], [v2.x, v2.y, v2.z], [v1.x, v1.y, v1.z]);
114
+ // End cap rotated
115
+ const q0 = rotateP(v0, sweepRad);
116
+ const q1 = rotateP(v1, sweepRad);
117
+ const q2 = rotateP(v2, sweepRad);
118
+ this.addTriangle(endName, q0, q1, q2);
119
+ }
120
+ }
121
+ } else {
122
+ // Fallback: face geometry
123
+ const baseGeom = faceObj.geometry;
124
+ const posAttr = baseGeom.getAttribute('position');
125
+ if (posAttr) {
126
+ const idx = baseGeom.getIndex();
127
+ const hasIndex = !!idx;
128
+ const v = new THREE.Vector3();
129
+ const world = new Array(posAttr.count);
130
+ for (let i = 0; i < posAttr.count; i++) {
131
+ v.set(posAttr.getX(i), posAttr.getY(i), posAttr.getZ(i)).applyMatrix4(faceObj.matrixWorld);
132
+ world[i] = [v.x, v.y, v.z];
133
+ }
134
+ const addTri = (i0, i1, i2) => {
135
+ const p0 = world[i0], p1 = world[i1], p2 = world[i2];
136
+ this.addTriangle(startName, p0, p2, p1);
137
+ const q0 = rotateP(new THREE.Vector3(...p0), sweepRad);
138
+ const q1 = rotateP(new THREE.Vector3(...p1), sweepRad);
139
+ const q2 = rotateP(new THREE.Vector3(...p2), sweepRad);
140
+ this.addTriangle(endName, q0, q1, q2);
141
+ };
142
+ if (hasIndex) {
143
+ for (let i = 0; i < idx.count; i += 3) addTri(idx.getX(i + 0) >>> 0, idx.getX(i + 1) >>> 0, idx.getX(i + 2) >>> 0);
144
+ } else {
145
+ for (let t = 0; t < (posAttr.count / 3 | 0); t++) addTri(3 * t + 0, 3 * t + 1, 3 * t + 2);
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ // Side walls using boundary loops (preferred) or edges (fallback)
152
+ const boundaryLoops = Array.isArray(faceObj?.userData?.boundaryLoopsWorld) ? faceObj.userData.boundaryLoopsWorld : null;
153
+ if (boundaryLoops && boundaryLoops.length) {
154
+ const key = (p) => `${p[0].toFixed(6)},${p[1].toFixed(6)},${p[2].toFixed(6)}`;
155
+ const edges = Array.isArray(faceObj?.edges) ? faceObj.edges : [];
156
+ const pointToEdgeNames = new Map();
157
+ for (const e of edges) {
158
+ const name = `${e?.name || 'EDGE'}_RV`;
159
+ registerEdgeSource(name, e);
160
+ const poly = getEdgePolylineWorld(e, { dedupe: false });
161
+ if (Array.isArray(poly) && poly.length >= 2) {
162
+ for (const p of poly) {
163
+ const k = key(p);
164
+ let set = pointToEdgeNames.get(k);
165
+ if (!set) { set = new Set(); pointToEdgeNames.set(k, set); }
166
+ set.add(name);
167
+ }
168
+ }
169
+ }
170
+
171
+ for (const loop of boundaryLoops) {
172
+ const pts = Array.isArray(loop?.pts) ? loop.pts : loop;
173
+ const isHole = !!(loop && loop.isHole);
174
+ const pA = pts.slice();
175
+ if (pA.length >= 2) {
176
+ const first = pA[0], last = pA[pA.length - 1];
177
+ if (!(first[0] === last[0] && first[1] === last[1] && first[2] === last[2])) pA.push([first[0], first[1], first[2]]);
178
+ }
179
+ for (let i = pA.length - 2; i >= 0; i--) {
180
+ const a = pA[i], b = pA[i + 1];
181
+ if (a[0] === b[0] && a[1] === b[1] && a[2] === b[2]) pA.splice(i + 1, 1);
182
+ }
183
+
184
+ for (let i = 0; i < pA.length - 1; i++) {
185
+ const a = pA[i];
186
+ const b = pA[i + 1];
187
+ const setA = pointToEdgeNames.get(key(a));
188
+ const setB = pointToEdgeNames.get(key(b));
189
+ let fname = `${faceObj.name || 'FACE'}_RV`;
190
+ if (setA && setB) { for (const n of setA) { if (setB.has(n)) { fname = n; break; } } }
191
+ setFaceType(fname, 'SIDEWALL');
192
+ ensureSidewallMetadata(fname);
193
+
194
+ // sweep along angular steps
195
+ let ang0 = 0;
196
+ for (let s = 0; s < steps; s++, ang0 += dA) {
197
+ const ang1 = (s === steps - 1) ? sweepRad : ang0 + dA;
198
+ const a0 = rotateP(new THREE.Vector3(a[0], a[1], a[2]), ang0);
199
+ const a1 = rotateP(new THREE.Vector3(a[0], a[1], a[2]), ang1);
200
+ const b0 = rotateP(new THREE.Vector3(b[0], b[1], b[2]), ang0);
201
+ const b1 = rotateP(new THREE.Vector3(b[0], b[1], b[2]), ang1);
202
+ if (isHole) {
203
+ this.addTriangle(fname, a0, b1, b0);
204
+ this.addTriangle(fname, a0, a1, b1);
205
+ } else {
206
+ this.addTriangle(fname, a0, b0, b1);
207
+ this.addTriangle(fname, a0, b1, a1);
208
+ }
209
+ }
210
+ }
211
+ }
212
+ } else {
213
+ // Fallback: build side walls by revolving per-edge polylines
214
+ const edges = Array.isArray(faceObj?.edges) ? faceObj.edges : [];
215
+ for (const edge of edges) {
216
+ const name = `${edge?.name || 'EDGE'}_RV`;
217
+ registerEdgeSource(name, edge);
218
+ setFaceType(name, 'SIDEWALL');
219
+ ensureSidewallMetadata(name);
220
+ const pts = [];
221
+ const w = new THREE.Vector3();
222
+
223
+ // 1) Prefer cached polyline
224
+ const cached = edge?.userData?.polylineLocal;
225
+ const isWorld = !!(edge?.userData?.polylineWorld);
226
+ if (Array.isArray(cached) && cached.length >= 2) {
227
+ if (isWorld) {
228
+ for (const p of cached) pts.push([p[0], p[1], p[2]]);
229
+ } else {
230
+ for (const p of cached) { w.set(p[0], p[1], p[2]).applyMatrix4(edge.matrixWorld); pts.push([w.x, w.y, w.z]); }
231
+ }
232
+ } else {
233
+ // 2) BufferGeometry position attribute
234
+ const posAttr = edge?.geometry?.getAttribute?.('position');
235
+ if (posAttr && posAttr.itemSize === 3 && posAttr.count >= 2) {
236
+ for (let i = 0; i < posAttr.count; i++) {
237
+ w.set(posAttr.getX(i), posAttr.getY(i), posAttr.getZ(i)).applyMatrix4(edge.matrixWorld);
238
+ pts.push([w.x, w.y, w.z]);
239
+ }
240
+ } else {
241
+ // 3) LineSegments-style fat lines
242
+ const aStart = edge?.geometry?.attributes?.instanceStart;
243
+ const aEnd = edge?.geometry?.attributes?.instanceEnd;
244
+ if (aStart && aEnd && aStart.itemSize === 3 && aEnd.itemSize === 3 && aStart.count === aEnd.count && aStart.count >= 1) {
245
+ w.set(aStart.getX(0), aStart.getY(0), aStart.getZ(0)).applyMatrix4(edge.matrixWorld);
246
+ pts.push([w.x, w.y, w.z]);
247
+ for (let i = 0; i < aEnd.count; i++) {
248
+ w.set(aEnd.getX(i), aEnd.getY(i), aEnd.getZ(i)).applyMatrix4(edge.matrixWorld);
249
+ pts.push([w.x, w.y, w.z]);
250
+ }
251
+ }
252
+ }
253
+ }
254
+
255
+ // Remove consecutive duplicates
256
+ for (let i = pts.length - 2; i >= 0; i--) {
257
+ const a = pts[i], b = pts[i + 1];
258
+ if (a[0] === b[0] && a[1] === b[1] && a[2] === b[2]) pts.splice(i + 1, 1);
259
+ }
260
+
261
+ if (pts.length < 2) continue;
262
+ const isHole = !!(edge && edge.userData && edge.userData.isHole);
263
+
264
+ // Revolve each segment by angular steps
265
+ for (let i = 0; i < pts.length - 1; i++) {
266
+ const a = pts[i];
267
+ const b = pts[i + 1];
268
+ if (a[0] === b[0] && a[1] === b[1] && a[2] === b[2]) continue;
269
+
270
+ let ang0 = 0;
271
+ for (let s = 0; s < steps; s++, ang0 += dA) {
272
+ const ang1 = (s === steps - 1) ? sweepRad : ang0 + dA;
273
+ const a0 = rotateP(new THREE.Vector3(a[0], a[1], a[2]), ang0);
274
+ const a1 = rotateP(new THREE.Vector3(a[0], a[1], a[2]), ang1);
275
+ const b0 = rotateP(new THREE.Vector3(b[0], b[1], b[2]), ang0);
276
+ const b1 = rotateP(new THREE.Vector3(b[0], b[1], b[2]), ang1);
277
+ if (isHole) {
278
+ this.addTriangle(name, a0, b1, b0);
279
+ this.addTriangle(name, a0, a1, b1);
280
+ } else {
281
+ this.addTriangle(name, a0, b0, b1);
282
+ this.addTriangle(name, a0, b1, a1);
283
+ }
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ // Attach an axis centerline that spans the revolved geometry extents.
290
+ try {
291
+ const vp = Array.isArray(this._vertProperties) ? this._vertProperties : null;
292
+ if (vp && vp.length >= 6) {
293
+ const tmp = new THREE.Vector3();
294
+ let minT = Infinity;
295
+ let maxT = -Infinity;
296
+ for (let i = 0; i < vp.length; i += 3) {
297
+ tmp.set(vp[i], vp[i + 1], vp[i + 2]);
298
+ const t = tmp.clone().sub(A).dot(axisDir);
299
+ if (t < minT) minT = t;
300
+ if (t > maxT) maxT = t;
301
+ }
302
+ if (Number.isFinite(minT) && Number.isFinite(maxT) && maxT - minT > 1e-9) {
303
+ const p0 = A.clone().add(axisDir.clone().multiplyScalar(minT));
304
+ const p1 = A.clone().add(axisDir.clone().multiplyScalar(maxT));
305
+ this.addCenterline(p0, p1, `${this.name || 'Revolve'}_AXIS`, { polylineWorld: true });
306
+ }
307
+ }
308
+ } catch { /* optional centerline add */ }
309
+
310
+ try { this.setEpsilon(1e-6); } catch { }
311
+ }
312
+ }
313
+
314
+ function computeFaceCentroidWorld(faceObj) {
315
+ try {
316
+ const geom = faceObj?.geometry;
317
+ const posAttr = geom?.getAttribute?.('position');
318
+ if (posAttr && posAttr.itemSize === 3 && posAttr.count > 0) {
319
+ const sum = new THREE.Vector3();
320
+ const tmp = new THREE.Vector3();
321
+ for (let i = 0; i < posAttr.count; i++) {
322
+ tmp.set(posAttr.getX(i), posAttr.getY(i), posAttr.getZ(i)).applyMatrix4(faceObj.matrixWorld);
323
+ sum.add(tmp);
324
+ }
325
+ return sum.multiplyScalar(1 / posAttr.count);
326
+ }
327
+ } catch { /* best effort */ }
328
+
329
+ try {
330
+ const loops = Array.isArray(faceObj?.userData?.boundaryLoopsWorld)
331
+ ? faceObj.userData.boundaryLoopsWorld
332
+ : null;
333
+ const outer = loops?.find((loop) => Array.isArray(loop?.pts) && loop.pts.length);
334
+ if (outer) {
335
+ const center = new THREE.Vector3();
336
+ let count = 0;
337
+ for (const pt of outer.pts) {
338
+ center.add(new THREE.Vector3(pt[0], pt[1], pt[2]));
339
+ count++;
340
+ }
341
+ if (count) return center.multiplyScalar(1 / count);
342
+ }
343
+ } catch { /* ignore */ }
344
+ return null;
345
+ }