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,614 @@
1
+ import * as THREE from 'three';
2
+ import { MeshBVH } from 'three-mesh-bvh';
3
+ import { Manifold } from './SolidShared.js';
4
+ import { Solid } from './BetterSolid.js';
5
+
6
+ // Manifold imported directly as named export
7
+
8
+ export class OffsetShellSolid extends Solid {
9
+ /**
10
+ * @param {Solid} sourceSolid The solid to offset.
11
+ */
12
+ constructor(sourceSolid) {
13
+ super();
14
+ if (!sourceSolid || typeof sourceSolid._manifoldize !== 'function') {
15
+ throw new Error('OffsetShellSolid requires a valid Solid instance.');
16
+ }
17
+ this.sourceSolid = sourceSolid;
18
+ }
19
+
20
+ /**
21
+ * Run the offset operation against the provided source solid.
22
+ * @param {number} distance Signed offset distance.
23
+ * @returns {Solid} New solid representing the offset shell.
24
+ */
25
+ run(distance) {
26
+ return OffsetShellSolid.generate(this.sourceSolid, distance);
27
+ }
28
+
29
+ /**
30
+ * Static convenience to perform the offset without instantiating the helper.
31
+ * @param {Solid} sourceSolid Solid to offset.
32
+ * @param {number} distance Signed offset distance.
33
+ * @param {object} [options]
34
+ * @param {string} [options.newSolidName] Optional name for the result solid
35
+ * @param {string} [options.featureId='OffsetShell'] Feature identifier used in naming/debug
36
+ * @returns {Solid} New solid representing the offset shell.
37
+ */
38
+ static generate(sourceSolid, distance, options = {}) {
39
+ if (!sourceSolid || typeof sourceSolid._manifoldize !== 'function') {
40
+ throw new Error('OffsetShellSolid.generate requires a valid Solid.');
41
+ }
42
+
43
+ const dist = Number(distance);
44
+ if (!Number.isFinite(dist) || dist === 0) return sourceSolid.clone();
45
+
46
+ const {
47
+ newSolidName = `${sourceSolid.name || 'Solid'}_${Math.abs(dist)}`,
48
+ featureId = 'OffsetShell',
49
+ } = options;
50
+
51
+ const positionsRaw = Array.isArray(sourceSolid._vertProperties)
52
+ ? sourceSolid._vertProperties
53
+ : (sourceSolid._vertProperties ? Array.from(sourceSolid._vertProperties) : []);
54
+ const indicesRaw = Array.isArray(sourceSolid._triVerts)
55
+ ? sourceSolid._triVerts
56
+ : (sourceSolid._triVerts ? Array.from(sourceSolid._triVerts) : []);
57
+
58
+ if (positionsRaw.length === 0 || indicesRaw.length === 0) {
59
+ return sourceSolid.clone();
60
+ }
61
+
62
+ const triIDsRaw = Array.isArray(sourceSolid._triIDs)
63
+ ? sourceSolid._triIDs
64
+ : (sourceSolid._triIDs ? Array.from(sourceSolid._triIDs) : []);
65
+ const idToFaceName = sourceSolid._idToFaceName instanceof Map
66
+ ? sourceSolid._idToFaceName
67
+ : new Map();
68
+
69
+ let geometry = null;
70
+ let bvh = null;
71
+ try {
72
+ const positions = new Float32Array(positionsRaw);
73
+ const triVerts = new Uint32Array(indicesRaw);
74
+ const triVertsOriginal = triVerts.slice();
75
+
76
+ geometry = new THREE.BufferGeometry();
77
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
78
+ geometry.setIndex(new THREE.BufferAttribute(triVerts, 1));
79
+ geometry.computeBoundingBox();
80
+
81
+ const bbox = geometry.boundingBox;
82
+ const diag = bbox ? bbox.max.clone().sub(bbox.min).length() : 1;
83
+ const basePad = Math.max(diag * 0.05, 1e-3);
84
+ const bounds = { min: [0, 0, 0], max: [0, 0, 0] };
85
+ const bboxMin = [
86
+ bbox?.min.x ?? 0,
87
+ bbox?.min.y ?? 0,
88
+ bbox?.min.z ?? 0,
89
+ ];
90
+ const bboxMax = [
91
+ bbox?.max.x ?? 0,
92
+ bbox?.max.y ?? 0,
93
+ bbox?.max.z ?? 0,
94
+ ];
95
+
96
+ for (let i = 0; i < 3; i++) {
97
+ if (dist >= 0) {
98
+ const grow = Math.abs(dist) + basePad;
99
+ bounds.min[i] = bboxMin[i] - grow;
100
+ bounds.max[i] = bboxMax[i] + grow;
101
+ } else {
102
+ const inset = Math.abs(dist);
103
+ const pad = Math.max(1e-4, Math.min(basePad * 0.1, inset * 0.05));
104
+ bounds.min[i] = bboxMin[i] + inset - pad;
105
+ bounds.max[i] = bboxMax[i] - inset + pad;
106
+ if (bounds.min[i] > bounds.max[i]) {
107
+ const mid = (bboxMin[i] + bboxMax[i]) * 0.5;
108
+ bounds.min[i] = mid - pad;
109
+ bounds.max[i] = mid + pad;
110
+ }
111
+ }
112
+ }
113
+
114
+ const faceCount = triVerts.length / 3;
115
+ let faceNormals = new Float32Array(faceCount * 3);
116
+ const vA = new THREE.Vector3();
117
+ const vB = new THREE.Vector3();
118
+ const vC = new THREE.Vector3();
119
+ const tmp = new THREE.Vector3();
120
+ for (let f = 0; f < faceCount; f++) {
121
+ const ia = triVertsOriginal[f * 3] * 3;
122
+ const ib = triVertsOriginal[f * 3 + 1] * 3;
123
+ const ic = triVertsOriginal[f * 3 + 2] * 3;
124
+ vA.set(positions[ia], positions[ia + 1], positions[ia + 2]);
125
+ vB.set(positions[ib], positions[ib + 1], positions[ib + 2]);
126
+ vC.set(positions[ic], positions[ic + 1], positions[ic + 2]);
127
+ tmp.subVectors(vB, vA).cross(vC.clone().sub(vA));
128
+ if (tmp.lengthSq() === 0) {
129
+ faceNormals[f * 3 + 0] = 0;
130
+ faceNormals[f * 3 + 1] = 0;
131
+ faceNormals[f * 3 + 2] = 0;
132
+ } else {
133
+ tmp.normalize();
134
+ faceNormals[f * 3 + 0] = tmp.x;
135
+ faceNormals[f * 3 + 1] = tmp.y;
136
+ faceNormals[f * 3 + 2] = tmp.z;
137
+ }
138
+ }
139
+
140
+ bvh = new MeshBVH(geometry, { lazyGeneration: false });
141
+ const query = new THREE.Vector3();
142
+ const normal = new THREE.Vector3();
143
+ const ray = new THREE.Ray();
144
+ const rayDir = new THREE.Vector3(1, 0.372, 0.529).normalize();
145
+ const rayTmp = new THREE.Vector3();
146
+ const triangle = new THREE.Triangle();
147
+
148
+ let triFaceNames = new Array(faceCount);
149
+ for (let t = 0; t < faceCount; t++) {
150
+ const id = triIDsRaw[t] ?? 0;
151
+ const faceName = idToFaceName.get(id) || `${sourceSolid.name || 'Solid'}_FACE_${id}`;
152
+ triFaceNames[t] = faceName;
153
+ }
154
+ try {
155
+ const indexAttr = geometry.getIndex();
156
+ const mutatedIndex = indexAttr?.array;
157
+ if (mutatedIndex && mutatedIndex !== triVertsOriginal) {
158
+ const keyFor = (a, b, c) => {
159
+ const arr = [a, b, c];
160
+ arr.sort((x, y) => x - y);
161
+ return `${arr[0]}/${arr[1]}/${arr[2]}`;
162
+ };
163
+ const originalKeyToIndex = new Map();
164
+ for (let t = 0; t < faceCount; t++) {
165
+ const i0 = triVertsOriginal[t * 3 + 0];
166
+ const i1 = triVertsOriginal[t * 3 + 1];
167
+ const i2 = triVertsOriginal[t * 3 + 2];
168
+ originalKeyToIndex.set(keyFor(i0, i1, i2), t);
169
+ }
170
+ const remap = new Uint32Array(faceCount);
171
+ let remapNeeded = false;
172
+ for (let t = 0; t < faceCount; t++) {
173
+ const i0 = mutatedIndex[t * 3 + 0];
174
+ const i1 = mutatedIndex[t * 3 + 1];
175
+ const i2 = mutatedIndex[t * 3 + 2];
176
+ const key = keyFor(i0, i1, i2);
177
+ const orig = originalKeyToIndex.get(key);
178
+ remap[t] = (orig != null) ? orig : t;
179
+ if (remap[t] !== t) remapNeeded = true;
180
+ }
181
+ if (remapNeeded) {
182
+ const remappedNormals = new Float32Array(faceCount * 3);
183
+ const remappedNames = new Array(faceCount);
184
+ for (let t = 0; t < faceCount; t++) {
185
+ const src = remap[t];
186
+ remappedNormals[t * 3 + 0] = faceNormals[src * 3 + 0];
187
+ remappedNormals[t * 3 + 1] = faceNormals[src * 3 + 1];
188
+ remappedNormals[t * 3 + 2] = faceNormals[src * 3 + 2];
189
+ remappedNames[t] = triFaceNames[src];
190
+ }
191
+ faceNormals = remappedNormals;
192
+ triFaceNames = remappedNames;
193
+ }
194
+ }
195
+ } catch (_) { /* remap best-effort */ }
196
+
197
+ const tupleToXYZ = (vec) => {
198
+ if (vec && typeof vec === 'object') {
199
+ if (Array.isArray(vec)) return [vec[0] ?? 0, vec[1] ?? 0, vec[2] ?? 0];
200
+ return [vec.x ?? 0, vec.y ?? 0, vec.z ?? 0];
201
+ }
202
+ return [0, 0, 0];
203
+ };
204
+
205
+ const pointInside = (point) => {
206
+ let hits = 0;
207
+ ray.origin.copy(point).addScaledVector(rayDir, 1e-6);
208
+ ray.direction.copy(rayDir);
209
+ bvh.shapecast({
210
+ intersectsBounds: (box) => ray.intersectsBox(box),
211
+ intersectsTriangle: (tri) => {
212
+ triangle.a.copy(tri.a);
213
+ triangle.b.copy(tri.b);
214
+ triangle.c.copy(tri.c);
215
+ const hit = ray.intersectTriangle(triangle.a, triangle.b, triangle.c, false, rayTmp);
216
+ if (hit) hits++;
217
+ return false;
218
+ },
219
+ });
220
+ return (hits & 1) === 1;
221
+ };
222
+
223
+ const signedDistance = (vec) => {
224
+ const [x, y, z] = tupleToXYZ(vec);
225
+ query.set(x, y, z);
226
+ const closest = bvh.closestPointToPoint(query);
227
+ if (!closest) return Number.NEGATIVE_INFINITY;
228
+ const fi = closest.faceIndex ?? -1;
229
+ if (fi >= 0) {
230
+ normal.set(
231
+ faceNormals[fi * 3],
232
+ faceNormals[fi * 3 + 1],
233
+ faceNormals[fi * 3 + 2]
234
+ );
235
+ } else {
236
+ normal.set(0, 0, 0);
237
+ }
238
+ if (normal.lengthSq() === 0) {
239
+ const idx = (fi >= 0 ? fi : 0) * 3;
240
+ const ia = triVerts[idx] * 3;
241
+ const ib = triVerts[idx + 1] * 3;
242
+ const ic = triVerts[idx + 2] * 3;
243
+ vA.set(positions[ia], positions[ia + 1], positions[ia + 2]);
244
+ vB.set(positions[ib], positions[ib + 1], positions[ib + 2]);
245
+ vC.set(positions[ic], positions[ic + 1], positions[ic + 2]);
246
+ normal.subVectors(vB, vA).cross(vC.clone().sub(vA)).normalize();
247
+ }
248
+ const d = closest.distance ?? 0;
249
+ if (d < 1e-9) return dist >= 0 ? -d : d;
250
+ const inside = pointInside(query);
251
+ return inside ? d : -d;
252
+ };
253
+
254
+ const edgeLength = Math.max(
255
+ Math.abs(dist) / 2,
256
+ diag / 120,
257
+ 1e-3
258
+ );
259
+ let target = Manifold.levelSet(
260
+ (vec) => signedDistance(vec),
261
+ bounds,
262
+ edgeLength,
263
+ -dist
264
+ );
265
+
266
+ const targetMesh = target.getMesh();
267
+ const out = new Solid();
268
+ out.name = newSolidName;
269
+
270
+ const tPositions = targetMesh.vertProperties;
271
+ const tTriVerts = targetMesh.triVerts;
272
+ const triOutCount = (tTriVerts.length / 3) | 0;
273
+ const vert = new THREE.Vector3();
274
+ const centroid = new THREE.Vector3();
275
+
276
+ const closestInfo = { point: new THREE.Vector3(), faceIndex: -1, distance: 0 };
277
+ const faceNormalTmp = new THREE.Vector3();
278
+ const offsetTmp = new THREE.Vector3();
279
+
280
+ const getFaceInfoForPoint = (point) => {
281
+ const closest = bvh.closestPointToPoint(point, closestInfo);
282
+ if (!closest || closest.faceIndex == null || closest.faceIndex < 0) return null;
283
+ const faceIndex = closest.faceIndex;
284
+ const name = triFaceNames[faceIndex] || null;
285
+ if (!name) return null;
286
+
287
+ const idx = faceIndex * 3;
288
+ faceNormalTmp.set(
289
+ faceNormals[idx],
290
+ faceNormals[idx + 1],
291
+ faceNormals[idx + 2]
292
+ );
293
+ if (faceNormalTmp.lengthSq() === 0) {
294
+ const ia = triVerts[idx] * 3;
295
+ const ib = triVerts[idx + 1] * 3;
296
+ const ic = triVerts[idx + 2] * 3;
297
+ vA.set(positions[ia], positions[ia + 1], positions[ia + 2]);
298
+ vB.set(positions[ib], positions[ib + 1], positions[ib + 2]);
299
+ vC.set(positions[ic], positions[ic + 1], positions[ic + 2]);
300
+ faceNormalTmp.subVectors(vB, vA).cross(vC.clone().sub(vA));
301
+ }
302
+ if (faceNormalTmp.lengthSq() > 0) faceNormalTmp.normalize();
303
+
304
+ let offsetAlignment = 0;
305
+ if (closest.point) {
306
+ offsetTmp.copy(point).sub(closest.point);
307
+ const len = offsetTmp.length();
308
+ if (len > 1e-9) {
309
+ offsetAlignment = offsetTmp.multiplyScalar(1 / len).dot(faceNormalTmp);
310
+ }
311
+ }
312
+
313
+ return {
314
+ name,
315
+ distance: closest.distance ?? 0,
316
+ faceIndex,
317
+ offsetAlignment,
318
+ };
319
+ };
320
+
321
+ const faceBuckets = new Map();
322
+ const getFaceKey = (names) => names.join('+');
323
+
324
+ const triNormal = new THREE.Vector3();
325
+ const triNormalUnit = new THREE.Vector3();
326
+ const closestNormal = new THREE.Vector3();
327
+ const probePoint = new THREE.Vector3();
328
+
329
+ for (let t = 0; t < triOutCount; t++) {
330
+ const i0 = tTriVerts[t * 3 + 0] * 3;
331
+ const i1 = tTriVerts[t * 3 + 1] * 3;
332
+ const i2 = tTriVerts[t * 3 + 2] * 3;
333
+
334
+ const p0 = [tPositions[i0], tPositions[i0 + 1], tPositions[i0 + 2]];
335
+ const p1 = [tPositions[i1], tPositions[i1 + 1], tPositions[i1 + 2]];
336
+ const p2 = [tPositions[i2], tPositions[i2 + 1], tPositions[i2 + 2]];
337
+
338
+ centroid.set(
339
+ (p0[0] + p1[0] + p2[0]) / 3,
340
+ (p0[1] + p1[1] + p2[1]) / 3,
341
+ (p0[2] + p1[2] + p2[2]) / 3
342
+ );
343
+
344
+ triNormal.set(
345
+ (p1[1] - p0[1]) * (p2[2] - p0[2]) - (p1[2] - p0[2]) * (p2[1] - p0[1]),
346
+ (p1[2] - p0[2]) * (p2[0] - p0[0]) - (p1[0] - p0[0]) * (p2[2] - p0[2]),
347
+ (p1[0] - p0[0]) * (p2[1] - p0[1]) - (p1[1] - p0[1]) * (p2[0] - p0[0])
348
+ );
349
+ const triNormalLenSq = triNormal.lengthSq();
350
+ const triNormalHasDirection = triNormalLenSq > 1e-18;
351
+ if (triNormalHasDirection) {
352
+ const triNormalLen = Math.sqrt(triNormalLenSq);
353
+ if (triNormalLen > 0) {
354
+ triNormalUnit.copy(triNormal).multiplyScalar(1 / triNormalLen);
355
+ } else {
356
+ triNormalUnit.set(0, 0, 0);
357
+ }
358
+ } else {
359
+ triNormalUnit.set(0, 0, 0);
360
+ }
361
+
362
+ const contributions = [];
363
+ let centroidInfo = null;
364
+ const addContribution = (info) => {
365
+ if (!info || !info.name) return;
366
+ contributions.push(info);
367
+ };
368
+
369
+ vert.set(p0[0], p0[1], p0[2]); addContribution(getFaceInfoForPoint(vert));
370
+ vert.set(p1[0], p1[1], p1[2]); addContribution(getFaceInfoForPoint(vert));
371
+ vert.set(p2[0], p2[1], p2[2]); addContribution(getFaceInfoForPoint(vert));
372
+ centroidInfo = getFaceInfoForPoint(centroid);
373
+ addContribution(centroidInfo);
374
+
375
+ const counts = new Map();
376
+ for (const info of contributions) {
377
+ if (!info || !info.name) continue;
378
+ const entry = counts.get(info.name) || {
379
+ count: 0,
380
+ minDist: Infinity,
381
+ faceIndex: null,
382
+ offsetAlignSum: 0,
383
+ offsetAlignSamples: 0
384
+ };
385
+ entry.count += 1;
386
+ const distance = info.distance ?? Infinity;
387
+ if (distance < entry.minDist) {
388
+ entry.minDist = distance;
389
+ if (typeof info.faceIndex === 'number') entry.faceIndex = info.faceIndex;
390
+ } else if (entry.faceIndex == null && typeof info.faceIndex === 'number') {
391
+ entry.faceIndex = info.faceIndex;
392
+ }
393
+ if (Number.isFinite(info.offsetAlignment)) {
394
+ entry.offsetAlignSum += info.offsetAlignment;
395
+ entry.offsetAlignSamples += 1;
396
+ }
397
+ counts.set(info.name, entry);
398
+ }
399
+
400
+ let entries = Array.from(counts.entries()).map(([name, entry]) => ({
401
+ name,
402
+ count: entry.count,
403
+ minDist: entry.minDist,
404
+ faceIndex: entry.faceIndex,
405
+ triAlign: (() => {
406
+ if (!triNormalHasDirection || entry.faceIndex == null || entry.faceIndex < 0) return 0;
407
+ const idx = entry.faceIndex * 3;
408
+ closestNormal.set(
409
+ faceNormals[idx],
410
+ faceNormals[idx + 1],
411
+ faceNormals[idx + 2]
412
+ );
413
+ if (closestNormal.lengthSq() === 0) return 0;
414
+ closestNormal.normalize();
415
+ return triNormalUnit.dot(closestNormal);
416
+ })(),
417
+ offsetAlign: (() => {
418
+ if (!entry || !entry.offsetAlignSamples) return 0;
419
+ return entry.offsetAlignSum / entry.offsetAlignSamples;
420
+ })(),
421
+ }));
422
+
423
+ if (entries.length === 0) entries = [{
424
+ name: 'OFFSET',
425
+ count: 1,
426
+ minDist: 0,
427
+ faceIndex: null,
428
+ triAlign: 0,
429
+ offsetAlign: 0
430
+ }];
431
+
432
+ const distSign = dist >= 0 ? 1 : -1;
433
+ for (const entry of entries) {
434
+ entry.triAlignScore = entry.triAlign;
435
+ entry.triAlignAbs = Math.abs(entry.triAlign);
436
+ entry.offsetAlignScore = entry.offsetAlign * distSign;
437
+ }
438
+
439
+ let selected = [];
440
+ let raySelected = null;
441
+ if (triNormalHasDirection) {
442
+ probePoint.copy(centroid);
443
+ const rayOrigin = probePoint;
444
+ const rayLength = Math.abs(dist) + diag * 0.1;
445
+ const hitFaces = [];
446
+ const raycast = ray.clone();
447
+ raycast.origin.copy(rayOrigin);
448
+ raycast.direction.copy(triNormalUnit).negate();
449
+ bvh.shapecast({
450
+ intersectsBounds: (box) => (box && box.min && box.max) ? raycast.intersectsBox(box) : true,
451
+ intersectsTriangle: (tri) => {
452
+ const hitPoint = raycast.intersectTriangle(tri.a, tri.b, tri.c, true, rayTmp);
453
+ if (!hitPoint) return false;
454
+ const hitDist = hitPoint.distanceTo(rayOrigin);
455
+ if (hitDist > rayLength) return false;
456
+ const idx = tri.faceIndex ?? tri.face;
457
+ const fname = (idx != null && idx >= 0) ? triFaceNames[idx] : null;
458
+ if (!fname) return false;
459
+ let alignScore = -Infinity;
460
+ if (idx != null && idx >= 0) {
461
+ const nIdx = idx * 3;
462
+ closestNormal.set(
463
+ faceNormals[nIdx],
464
+ faceNormals[nIdx + 1],
465
+ faceNormals[nIdx + 2]
466
+ );
467
+ if (closestNormal.lengthSq() === 0) {
468
+ closestNormal.subVectors(tri.b, tri.a).cross(tri.c.clone().sub(tri.a));
469
+ }
470
+ if (closestNormal.lengthSq() > 0) {
471
+ closestNormal.normalize();
472
+ alignScore = triNormalUnit.dot(closestNormal);
473
+ }
474
+ }
475
+ hitFaces.push({ name: fname, distance: hitDist, alignScore });
476
+ return true;
477
+ }
478
+ });
479
+ if (hitFaces.length) {
480
+ hitFaces.sort((a, b) => {
481
+ const distDelta = Math.abs(a.distance - Math.abs(dist)) - Math.abs(b.distance - Math.abs(dist));
482
+ if (Math.abs(distDelta) > 1e-6) return distDelta;
483
+ return (b.alignScore ?? -Infinity) - (a.alignScore ?? -Infinity);
484
+ });
485
+ const bestHit = hitFaces.find((hit) => (hit.alignScore ?? -Infinity) > 0.05) || hitFaces[0];
486
+ if (bestHit && (bestHit.alignScore ?? 0) > -0.2) {
487
+ raySelected = bestHit.name;
488
+ selected = [bestHit.name];
489
+ }
490
+ }
491
+ }
492
+
493
+ if (selected.length) {
494
+ const matching = entries.find((entry) => entry.name === selected[0]);
495
+ if (matching && matching.triAlignScore < -0.15) {
496
+ selected.length = 0;
497
+ }
498
+ }
499
+
500
+ const entriesByName = new Map(entries.map((entry) => [entry.name, entry]));
501
+ if (centroidInfo && centroidInfo.name) {
502
+ const centroidEntry = entriesByName.get(centroidInfo.name);
503
+ if (centroidEntry) {
504
+ selected = [centroidEntry.name];
505
+ }
506
+ }
507
+
508
+ if (selected.length === 0 && entries.length) {
509
+ entries.sort((a, b) => {
510
+ if (b.count !== a.count) return b.count - a.count;
511
+ if (b.triAlignScore !== a.triAlignScore) return b.triAlignScore - a.triAlignScore;
512
+ if (b.triAlignAbs !== a.triAlignAbs) return b.triAlignAbs - a.triAlignAbs;
513
+ if (b.offsetAlignScore !== a.offsetAlignScore) return b.offsetAlignScore - a.offsetAlignScore;
514
+ if (a.minDist !== b.minDist) return a.minDist - b.minDist;
515
+ return a.name.localeCompare(b.name);
516
+ });
517
+
518
+ const primary = entries[0];
519
+ const alignmentThreshold = 0.35;
520
+ if (primary && primary.triAlignScore >= alignmentThreshold) {
521
+ selected.push(primary.name);
522
+ } else {
523
+ for (const entry of entries) {
524
+ if (!entry || !entry.name) continue;
525
+ if (selected.includes(entry.name)) continue;
526
+ if (entry.triAlignScore < -0.2) continue;
527
+ selected.push(entry.name);
528
+ if (selected.length >= 2) break;
529
+ }
530
+ if (!selected.length && primary && primary.triAlignAbs >= 0.25) {
531
+ selected.push(primary.name);
532
+ }
533
+ }
534
+ }
535
+
536
+ if (!selected.length) selected.push('OFFSET');
537
+
538
+ const uniqueSelected = [];
539
+ for (const name of selected) {
540
+ if (!name) continue;
541
+ if (!uniqueSelected.includes(name)) uniqueSelected.push(name);
542
+ }
543
+
544
+ const hasNamedFace = uniqueSelected.some((name) => name && name !== 'OFFSET');
545
+ if (hasNamedFace) {
546
+ const offsetIndex = uniqueSelected.indexOf('OFFSET');
547
+ if (offsetIndex >= 0) uniqueSelected.splice(offsetIndex, 1);
548
+ }
549
+
550
+ if (uniqueSelected.length > 2) {
551
+ const ranked = uniqueSelected
552
+ .map((name) => entries.find((e) => e.name === name) || {
553
+ name,
554
+ count: 0,
555
+ triAlignScore: -Infinity,
556
+ triAlignAbs: 0,
557
+ offsetAlignScore: -Infinity,
558
+ minDist: Infinity
559
+ })
560
+ .sort((a, b) => {
561
+ if (b.count !== a.count) return b.count - a.count;
562
+ if (b.triAlignScore !== a.triAlignScore) return b.triAlignScore - a.triAlignScore;
563
+ if (b.triAlignAbs !== a.triAlignAbs) return b.triAlignAbs - a.triAlignAbs;
564
+ if (b.offsetAlignScore !== a.offsetAlignScore) return b.offsetAlignScore - a.offsetAlignScore;
565
+ if (a.minDist !== b.minDist) return a.minDist - b.minDist;
566
+ return a.name.localeCompare(b.name);
567
+ });
568
+ uniqueSelected.length = 0;
569
+ for (let i = 0; i < ranked.length && uniqueSelected.length < 2; i++) {
570
+ uniqueSelected.push(ranked[i].name);
571
+ }
572
+ }
573
+
574
+ const sortedFaces = (uniqueSelected.length ? uniqueSelected : ['OFFSET']).sort();
575
+ const key = getFaceKey(sortedFaces.length ? sortedFaces : ['OFFSET']);
576
+ let bucket = faceBuckets.get(key);
577
+ if (!bucket) {
578
+ bucket = { name: `${newSolidName}_${key}`, tris: [] };
579
+ faceBuckets.set(key, bucket);
580
+ }
581
+ bucket.tris.push([p0, p1, p2]);
582
+ }
583
+
584
+ for (const bucket of faceBuckets.values()) {
585
+ for (const tri of bucket.tris) {
586
+ out.addTriangle(bucket.name, tri[0], tri[1], tri[2]);
587
+ }
588
+ }
589
+
590
+ // Cull tiny disconnected islands created by grid artifacts
591
+ const triOutTotal = (out._triVerts.length / 3) | 0;
592
+ if (triOutTotal > 0) {
593
+ const threshold = Math.max(8, Math.round(triOutTotal * 0.01));
594
+ try {
595
+ out.removeSmallIslands({
596
+ maxTriangles: threshold,
597
+ removeInternal: true,
598
+ removeExternal: true,
599
+ });
600
+ } catch (_) { /* best effort */ }
601
+ }
602
+
603
+ out._faceMetadata = new Map(sourceSolid._faceMetadata);
604
+ out._auxEdges = Array.isArray(sourceSolid._auxEdges) ? [...sourceSolid._auxEdges] : [];
605
+
606
+ try { if (targetMesh && typeof targetMesh.delete === 'function') targetMesh.delete(); } catch { }
607
+ try { if (typeof target.delete === 'function') target.delete(); } catch { }
608
+ return out;
609
+ } finally {
610
+ try { geometry?.dispose?.(); } catch { }
611
+ try { if (bvh && typeof bvh.dispose === 'function') bvh.dispose(); } catch { }
612
+ }
613
+ }
614
+ }