brep-io-kernel 1.0.0-ci.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +154 -0
  3. package/dist-kernel/brep-kernel.js +74699 -0
  4. package/package.json +58 -0
  5. package/src/BREP/AssemblyComponent.js +42 -0
  6. package/src/BREP/BREP.js +43 -0
  7. package/src/BREP/BetterSolid.js +805 -0
  8. package/src/BREP/Edge.js +103 -0
  9. package/src/BREP/Extrude.js +403 -0
  10. package/src/BREP/Face.js +187 -0
  11. package/src/BREP/MeshRepairer.js +634 -0
  12. package/src/BREP/OffsetShellSolid.js +614 -0
  13. package/src/BREP/PointCloudWrap.js +302 -0
  14. package/src/BREP/Revolve.js +345 -0
  15. package/src/BREP/SolidMethods/authoring.js +112 -0
  16. package/src/BREP/SolidMethods/booleanOps.js +230 -0
  17. package/src/BREP/SolidMethods/chamfer.js +122 -0
  18. package/src/BREP/SolidMethods/edgeResolution.js +25 -0
  19. package/src/BREP/SolidMethods/fillet.js +792 -0
  20. package/src/BREP/SolidMethods/index.js +72 -0
  21. package/src/BREP/SolidMethods/io.js +105 -0
  22. package/src/BREP/SolidMethods/lifecycle.js +103 -0
  23. package/src/BREP/SolidMethods/manifoldOps.js +375 -0
  24. package/src/BREP/SolidMethods/meshCleanup.js +2512 -0
  25. package/src/BREP/SolidMethods/meshQueries.js +264 -0
  26. package/src/BREP/SolidMethods/metadata.js +106 -0
  27. package/src/BREP/SolidMethods/metrics.js +51 -0
  28. package/src/BREP/SolidMethods/transforms.js +361 -0
  29. package/src/BREP/SolidMethods/visualize.js +508 -0
  30. package/src/BREP/SolidShared.js +26 -0
  31. package/src/BREP/Sweep.js +1596 -0
  32. package/src/BREP/Tube.js +857 -0
  33. package/src/BREP/Vertex.js +43 -0
  34. package/src/BREP/applyBooleanOperation.js +704 -0
  35. package/src/BREP/boundsUtils.js +48 -0
  36. package/src/BREP/chamfer.js +551 -0
  37. package/src/BREP/edgePolylineUtils.js +85 -0
  38. package/src/BREP/fillets/common.js +388 -0
  39. package/src/BREP/fillets/fillet.js +1422 -0
  40. package/src/BREP/fillets/filletGeometry.js +15 -0
  41. package/src/BREP/fillets/inset.js +389 -0
  42. package/src/BREP/fillets/offsetHelper.js +143 -0
  43. package/src/BREP/fillets/outset.js +88 -0
  44. package/src/BREP/helix.js +193 -0
  45. package/src/BREP/meshToBrep.js +234 -0
  46. package/src/BREP/primitives.js +279 -0
  47. package/src/BREP/setupManifold.js +71 -0
  48. package/src/BREP/threadGeometry.js +1120 -0
  49. package/src/BREP/triangleUtils.js +8 -0
  50. package/src/BREP/triangulate.js +608 -0
  51. package/src/FeatureRegistry.js +183 -0
  52. package/src/PartHistory.js +1132 -0
  53. package/src/UI/AccordionWidget.js +292 -0
  54. package/src/UI/CADmaterials.js +850 -0
  55. package/src/UI/EnvMonacoEditor.js +522 -0
  56. package/src/UI/FloatingWindow.js +396 -0
  57. package/src/UI/HistoryWidget.js +457 -0
  58. package/src/UI/MainToolbar.js +131 -0
  59. package/src/UI/ModelLibraryView.js +194 -0
  60. package/src/UI/OrthoCameraIdle.js +206 -0
  61. package/src/UI/PluginsWidget.js +280 -0
  62. package/src/UI/SceneListing.js +606 -0
  63. package/src/UI/SelectionFilter.js +629 -0
  64. package/src/UI/ViewCube.js +389 -0
  65. package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +329 -0
  66. package/src/UI/assembly/AssemblyConstraintControlsWidget.js +282 -0
  67. package/src/UI/assembly/AssemblyConstraintsWidget.css +292 -0
  68. package/src/UI/assembly/AssemblyConstraintsWidget.js +1373 -0
  69. package/src/UI/assembly/constraintFaceUtils.js +115 -0
  70. package/src/UI/assembly/constraintHighlightUtils.js +70 -0
  71. package/src/UI/assembly/constraintLabelUtils.js +31 -0
  72. package/src/UI/assembly/constraintPointUtils.js +64 -0
  73. package/src/UI/assembly/constraintSelectionUtils.js +185 -0
  74. package/src/UI/assembly/constraintStatusUtils.js +142 -0
  75. package/src/UI/componentSelectorModal.js +240 -0
  76. package/src/UI/controls/CombinedTransformControls.js +386 -0
  77. package/src/UI/dialogs.js +351 -0
  78. package/src/UI/expressionsManager.js +100 -0
  79. package/src/UI/featureDialogWidgets/booleanField.js +25 -0
  80. package/src/UI/featureDialogWidgets/booleanOperationField.js +97 -0
  81. package/src/UI/featureDialogWidgets/buttonField.js +45 -0
  82. package/src/UI/featureDialogWidgets/componentSelectorField.js +102 -0
  83. package/src/UI/featureDialogWidgets/defaultField.js +23 -0
  84. package/src/UI/featureDialogWidgets/fileField.js +66 -0
  85. package/src/UI/featureDialogWidgets/index.js +34 -0
  86. package/src/UI/featureDialogWidgets/numberField.js +165 -0
  87. package/src/UI/featureDialogWidgets/optionsField.js +33 -0
  88. package/src/UI/featureDialogWidgets/referenceSelectionField.js +208 -0
  89. package/src/UI/featureDialogWidgets/stringField.js +24 -0
  90. package/src/UI/featureDialogWidgets/textareaField.js +28 -0
  91. package/src/UI/featureDialogWidgets/threadDesignationField.js +160 -0
  92. package/src/UI/featureDialogWidgets/transformField.js +252 -0
  93. package/src/UI/featureDialogWidgets/utils.js +43 -0
  94. package/src/UI/featureDialogWidgets/vec3Field.js +133 -0
  95. package/src/UI/featureDialogs.js +1414 -0
  96. package/src/UI/fileManagerWidget.js +615 -0
  97. package/src/UI/history/HistoryCollectionWidget.js +1294 -0
  98. package/src/UI/history/historyCollectionWidget.css.js +257 -0
  99. package/src/UI/history/historyDisplayInfo.js +133 -0
  100. package/src/UI/mobile.js +28 -0
  101. package/src/UI/objectDump.js +442 -0
  102. package/src/UI/pmi/AnnotationCollectionWidget.js +120 -0
  103. package/src/UI/pmi/AnnotationHistory.js +353 -0
  104. package/src/UI/pmi/AnnotationRegistry.js +90 -0
  105. package/src/UI/pmi/BaseAnnotation.js +269 -0
  106. package/src/UI/pmi/LabelOverlay.css +102 -0
  107. package/src/UI/pmi/LabelOverlay.js +191 -0
  108. package/src/UI/pmi/PMIMode.js +1550 -0
  109. package/src/UI/pmi/PMIViewsWidget.js +1098 -0
  110. package/src/UI/pmi/annUtils.js +729 -0
  111. package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +647 -0
  112. package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +507 -0
  113. package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +462 -0
  114. package/src/UI/pmi/dimensions/LeaderAnnotation.js +403 -0
  115. package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +532 -0
  116. package/src/UI/pmi/dimensions/NoteAnnotation.js +110 -0
  117. package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +659 -0
  118. package/src/UI/pmi/pmiStyle.js +44 -0
  119. package/src/UI/sketcher/SketchMode3D.js +4095 -0
  120. package/src/UI/sketcher/dimensions.js +674 -0
  121. package/src/UI/sketcher/glyphs.js +236 -0
  122. package/src/UI/sketcher/highlights.js +60 -0
  123. package/src/UI/toolbarButtons/aboutButton.js +5 -0
  124. package/src/UI/toolbarButtons/exportButton.js +609 -0
  125. package/src/UI/toolbarButtons/flatPatternButton.js +307 -0
  126. package/src/UI/toolbarButtons/importButton.js +160 -0
  127. package/src/UI/toolbarButtons/inspectorToggleButton.js +12 -0
  128. package/src/UI/toolbarButtons/metadataButton.js +1063 -0
  129. package/src/UI/toolbarButtons/orientToFaceButton.js +114 -0
  130. package/src/UI/toolbarButtons/registerDefaultButtons.js +46 -0
  131. package/src/UI/toolbarButtons/saveButton.js +99 -0
  132. package/src/UI/toolbarButtons/scriptRunnerButton.js +302 -0
  133. package/src/UI/toolbarButtons/testsButton.js +26 -0
  134. package/src/UI/toolbarButtons/undoRedoButtons.js +25 -0
  135. package/src/UI/toolbarButtons/wireframeToggleButton.js +5 -0
  136. package/src/UI/toolbarButtons/zoomToFitButton.js +5 -0
  137. package/src/UI/triangleDebuggerWindow.js +945 -0
  138. package/src/UI/viewer.js +4228 -0
  139. package/src/assemblyConstraints/AssemblyConstraintHistory.js +1576 -0
  140. package/src/assemblyConstraints/AssemblyConstraintRegistry.js +120 -0
  141. package/src/assemblyConstraints/BaseAssemblyConstraint.js +66 -0
  142. package/src/assemblyConstraints/constraintExpressionUtils.js +35 -0
  143. package/src/assemblyConstraints/constraintUtils/parallelAlignment.js +676 -0
  144. package/src/assemblyConstraints/constraints/AngleConstraint.js +485 -0
  145. package/src/assemblyConstraints/constraints/CoincidentConstraint.js +194 -0
  146. package/src/assemblyConstraints/constraints/DistanceConstraint.js +616 -0
  147. package/src/assemblyConstraints/constraints/FixedConstraint.js +78 -0
  148. package/src/assemblyConstraints/constraints/ParallelConstraint.js +252 -0
  149. package/src/assemblyConstraints/constraints/TouchAlignConstraint.js +961 -0
  150. package/src/core/entities/HistoryCollectionBase.js +72 -0
  151. package/src/core/entities/ListEntityBase.js +109 -0
  152. package/src/core/entities/schemaProcesser.js +121 -0
  153. package/src/exporters/sheetMetalFlatPattern.js +659 -0
  154. package/src/exporters/sheetMetalUnfold.js +862 -0
  155. package/src/exporters/step.js +1135 -0
  156. package/src/exporters/threeMF.js +575 -0
  157. package/src/features/assemblyComponent/AssemblyComponentFeature.js +780 -0
  158. package/src/features/boolean/BooleanFeature.js +94 -0
  159. package/src/features/chamfer/ChamferFeature.js +116 -0
  160. package/src/features/datium/DatiumFeature.js +80 -0
  161. package/src/features/edgeFeatureUtils.js +41 -0
  162. package/src/features/extrude/ExtrudeFeature.js +143 -0
  163. package/src/features/fillet/FilletFeature.js +197 -0
  164. package/src/features/helix/HelixFeature.js +405 -0
  165. package/src/features/hole/HoleFeature.js +1050 -0
  166. package/src/features/hole/screwClearance.js +86 -0
  167. package/src/features/hole/threadDesignationCatalog.js +149 -0
  168. package/src/features/imageHeightSolid/ImageHeightmapSolidFeature.js +463 -0
  169. package/src/features/imageToFace/ImageToFaceFeature.js +727 -0
  170. package/src/features/imageToFace/imageEditor.js +1270 -0
  171. package/src/features/imageToFace/traceUtils.js +971 -0
  172. package/src/features/import3dModel/Import3dModelFeature.js +151 -0
  173. package/src/features/loft/LoftFeature.js +605 -0
  174. package/src/features/mirror/MirrorFeature.js +151 -0
  175. package/src/features/offsetFace/OffsetFaceFeature.js +370 -0
  176. package/src/features/offsetShell/OffsetShellFeature.js +89 -0
  177. package/src/features/overlapCleanup/OverlapCleanupFeature.js +85 -0
  178. package/src/features/pattern/PatternFeature.js +275 -0
  179. package/src/features/patternLinear/PatternLinearFeature.js +120 -0
  180. package/src/features/patternRadial/PatternRadialFeature.js +186 -0
  181. package/src/features/plane/PlaneFeature.js +154 -0
  182. package/src/features/primitiveCone/primitiveConeFeature.js +99 -0
  183. package/src/features/primitiveCube/primitiveCubeFeature.js +70 -0
  184. package/src/features/primitiveCylinder/primitiveCylinderFeature.js +91 -0
  185. package/src/features/primitivePyramid/primitivePyramidFeature.js +72 -0
  186. package/src/features/primitiveSphere/primitiveSphereFeature.js +62 -0
  187. package/src/features/primitiveTorus/primitiveTorusFeature.js +109 -0
  188. package/src/features/remesh/RemeshFeature.js +97 -0
  189. package/src/features/revolve/RevolveFeature.js +111 -0
  190. package/src/features/selectionUtils.js +118 -0
  191. package/src/features/sheetMetal/SheetMetalContourFlangeFeature.js +1656 -0
  192. package/src/features/sheetMetal/SheetMetalCutoutFeature.js +1056 -0
  193. package/src/features/sheetMetal/SheetMetalFlangeFeature.js +1568 -0
  194. package/src/features/sheetMetal/SheetMetalHemFeature.js +43 -0
  195. package/src/features/sheetMetal/SheetMetalObject.js +141 -0
  196. package/src/features/sheetMetal/SheetMetalTabFeature.js +176 -0
  197. package/src/features/sheetMetal/UNFOLD_NEUTRAL_REQUIREMENTS.md +153 -0
  198. package/src/features/sheetMetal/contour-flange-rebuild-spec.md +261 -0
  199. package/src/features/sheetMetal/profileUtils.js +25 -0
  200. package/src/features/sheetMetal/sheetMetalCleanup.js +9 -0
  201. package/src/features/sheetMetal/sheetMetalFaceTypes.js +146 -0
  202. package/src/features/sheetMetal/sheetMetalMetadata.js +165 -0
  203. package/src/features/sheetMetal/sheetMetalPipeline.js +169 -0
  204. package/src/features/sheetMetal/sheetMetalProfileUtils.js +216 -0
  205. package/src/features/sheetMetal/sheetMetalTabUtils.js +29 -0
  206. package/src/features/sheetMetal/sheetMetalTree.js +210 -0
  207. package/src/features/sketch/SketchFeature.js +955 -0
  208. package/src/features/sketch/sketchSolver2D/ConstraintEngine.js +800 -0
  209. package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +704 -0
  210. package/src/features/sketch/sketchSolver2D/mathHelpersMod.js +307 -0
  211. package/src/features/spline/SplineEditorSession.js +988 -0
  212. package/src/features/spline/SplineFeature.js +1388 -0
  213. package/src/features/spline/splineUtils.js +218 -0
  214. package/src/features/sweep/SweepFeature.js +110 -0
  215. package/src/features/transform/TransformFeature.js +152 -0
  216. package/src/features/tube/TubeFeature.js +635 -0
  217. package/src/fs.proxy.js +625 -0
  218. package/src/idbStorage.js +254 -0
  219. package/src/index.js +12 -0
  220. package/src/main.js +15 -0
  221. package/src/metadataManager.js +64 -0
  222. package/src/path.proxy.js +277 -0
  223. package/src/plugins/ghLoader.worker.js +151 -0
  224. package/src/plugins/pluginManager.js +286 -0
  225. package/src/pmi/PMIViewsManager.js +134 -0
  226. package/src/services/componentLibrary.js +198 -0
  227. package/src/tests/ConsoleCapture.js +189 -0
  228. package/src/tests/S7-diagnostics-2025-12-23T18-37-23-570Z.json +630 -0
  229. package/src/tests/browserTests.js +597 -0
  230. package/src/tests/debugBoolean.js +225 -0
  231. package/src/tests/partFiles/badBoolean.json +957 -0
  232. package/src/tests/partFiles/extrudeTest.json +88 -0
  233. package/src/tests/partFiles/filletFail.json +58 -0
  234. package/src/tests/partFiles/import_TEst.part.part.json +646 -0
  235. package/src/tests/partFiles/sheetMetalHem.BREP.json +734 -0
  236. package/src/tests/test_boolean_subtract.js +27 -0
  237. package/src/tests/test_chamfer.js +17 -0
  238. package/src/tests/test_extrudeFace.js +24 -0
  239. package/src/tests/test_fillet.js +17 -0
  240. package/src/tests/test_fillet_nonClosed.js +45 -0
  241. package/src/tests/test_filletsMoreDifficult.js +46 -0
  242. package/src/tests/test_history_features_basic.js +149 -0
  243. package/src/tests/test_hole.js +282 -0
  244. package/src/tests/test_mirror.js +16 -0
  245. package/src/tests/test_offsetShellGrouping.js +85 -0
  246. package/src/tests/test_plane.js +4 -0
  247. package/src/tests/test_primitiveCone.js +11 -0
  248. package/src/tests/test_primitiveCube.js +7 -0
  249. package/src/tests/test_primitiveCylinder.js +8 -0
  250. package/src/tests/test_primitivePyramid.js +9 -0
  251. package/src/tests/test_primitiveSphere.js +17 -0
  252. package/src/tests/test_primitiveTorus.js +21 -0
  253. package/src/tests/test_pushFace.js +126 -0
  254. package/src/tests/test_sheetMetalContourFlange.js +125 -0
  255. package/src/tests/test_sheetMetal_features.js +80 -0
  256. package/src/tests/test_sketch_openLoop.js +45 -0
  257. package/src/tests/test_solidMetrics.js +58 -0
  258. package/src/tests/test_stlLoader.js +1889 -0
  259. package/src/tests/test_sweepFace.js +55 -0
  260. package/src/tests/test_tube.js +45 -0
  261. package/src/tests/test_tube_closedLoop.js +67 -0
  262. package/src/tests/tests.js +493 -0
  263. package/src/tools/assemblyConstraintDialogCapturePage.js +56 -0
  264. package/src/tools/dialogCapturePageFactory.js +227 -0
  265. package/src/tools/featureDialogCapturePage.js +47 -0
  266. package/src/tools/pmiAnnotationDialogCapturePage.js +60 -0
  267. package/src/utils/axisHelpers.js +99 -0
  268. package/src/utils/deepClone.js +69 -0
  269. package/src/utils/geometryTolerance.js +37 -0
  270. package/src/utils/normalizeTypeString.js +8 -0
  271. package/src/utils/xformMath.js +51 -0
@@ -0,0 +1,704 @@
1
+ // applyBooleanOperation.js
2
+ // Helper to apply a boolean operation between a newly created base solid and
3
+ // a list of scene solids referenced by name via the boolean param widget.
4
+
5
+ import * as THREE from 'three';
6
+ import { Manifold, ManifoldMesh } from "./SolidShared.js";
7
+ import { Solid } from "./BetterSolid.js";
8
+ import { MeshRepairer } from "./MeshRepairer.js";
9
+ import { computeBoundsFromVertices } from "./boundsUtils.js";
10
+
11
+ const __booleanDebugConfig = (() => {
12
+ try {
13
+ if (typeof process === 'undefined' || !process?.env) return null;
14
+ const raw = process.env.DEBUG_BOOLEAN;
15
+ if (!raw) return null;
16
+ const tokens = String(raw)
17
+ .split(/[,;|]+|\s+/g)
18
+ .map(t => t.trim())
19
+ .filter(Boolean);
20
+ if (!tokens.length) return null;
21
+ const cfg = {
22
+ all: false,
23
+ ids: new Set(),
24
+ names: [],
25
+ ops: new Set(),
26
+ };
27
+ for (const tokenRaw of tokens) {
28
+ const token = tokenRaw.trim();
29
+ if (!token) continue;
30
+ const upper = token.toUpperCase();
31
+ if (upper === '*' || upper === 'ALL' || upper === 'TRUE' || upper === '1') {
32
+ cfg.all = true;
33
+ continue;
34
+ }
35
+ if (upper.startsWith('NAME:')) {
36
+ const idx = token.indexOf(':');
37
+ const namePart = idx >= 0 ? token.slice(idx + 1).trim().toLowerCase() : '';
38
+ if (namePart) cfg.names.push(namePart);
39
+ continue;
40
+ }
41
+ if (upper.startsWith('OP:')) {
42
+ const opPart = upper.slice(3).trim();
43
+ if (opPart) cfg.ops.add(opPart);
44
+ continue;
45
+ }
46
+ cfg.ids.add(token);
47
+ }
48
+ return cfg;
49
+ } catch {
50
+ return null;
51
+ }
52
+ })();
53
+
54
+ function __booleanDebugSummarizeSolid(solid) {
55
+ if (!solid || typeof solid !== 'object') return { name: '(null)' };
56
+ const summary = {
57
+ name: solid.name || solid.owningFeatureID || solid.id || solid.uuid || '(unnamed)',
58
+ };
59
+ if (solid.owningFeatureID && solid.owningFeatureID !== summary.name) {
60
+ summary.owningFeatureID = solid.owningFeatureID;
61
+ }
62
+ try {
63
+ const vp = solid._vertProperties;
64
+ if (Array.isArray(vp)) summary.vertexCount = Math.floor(vp.length / 3);
65
+ } catch { }
66
+ try {
67
+ const tris = solid._triVerts || solid._triangles;
68
+ if (Array.isArray(tris)) summary.triangleCount = Math.floor(tris.length / 3);
69
+ } catch { }
70
+ return summary;
71
+ }
72
+
73
+ function __booleanDebugMatch(featureID, op, baseSolid, tools) {
74
+ const cfg = __booleanDebugConfig;
75
+ if (!cfg) return false;
76
+ if (cfg.all) return true;
77
+
78
+ const ids = cfg.ids;
79
+ const names = cfg.names;
80
+ const ops = cfg.ops;
81
+
82
+ const normalizeName = (obj) => {
83
+ if (!obj || typeof obj !== 'object') return '';
84
+ const raw = obj.name || obj.owningFeatureID || obj.id || obj.uuid || '';
85
+ return String(raw || '').trim();
86
+ };
87
+
88
+ const matchesNamePattern = (value) => {
89
+ if (!names || names.length === 0) return false;
90
+ const lower = String(value || '').toLowerCase();
91
+ if (!lower) return false;
92
+ for (const pat of names) {
93
+ if (lower.includes(pat)) return true;
94
+ }
95
+ return false;
96
+ };
97
+
98
+ if (featureID != null && ids.has(String(featureID))) return true;
99
+
100
+ const opUpper = String(op || '').toUpperCase();
101
+ if (opUpper && ops.has(opUpper)) return true;
102
+
103
+ const baseName = normalizeName(baseSolid);
104
+ if (baseName) {
105
+ if (ids.has(baseName)) return true;
106
+ if (matchesNamePattern(baseName)) return true;
107
+ }
108
+
109
+ for (const tool of tools || []) {
110
+ const toolName = normalizeName(tool);
111
+ if (!toolName) continue;
112
+ if (ids.has(toolName)) return true;
113
+ if (matchesNamePattern(toolName)) return true;
114
+ }
115
+
116
+ return false;
117
+ }
118
+
119
+ function __booleanDebugLogger(featureID, op, baseSolid, tools) {
120
+ const shouldLog = __booleanDebugMatch(featureID, op, baseSolid, tools);
121
+ if (!shouldLog) return () => {};
122
+ const tag = (featureID != null) ? `[BooleanDebug ${featureID}]` : '[BooleanDebug]';
123
+ return (...args) => {
124
+ try { console.log(tag, ...args); } catch { }
125
+ };
126
+ }
127
+
128
+ function __booleanSolidToGeometry(solid) {
129
+ try {
130
+ if (!solid || typeof solid !== 'object') return null;
131
+ const vp = solid._vertProperties;
132
+ const tv = solid._triVerts;
133
+ const ids = solid._triIDs;
134
+ if (!Array.isArray(vp) || vp.length < 9) return null;
135
+ if (!Array.isArray(tv) || tv.length < 3) return null;
136
+ if (!Array.isArray(ids) || ids.length !== (tv.length / 3)) return null;
137
+
138
+ const vertArray = Float32Array.from(vp);
139
+ const triArray = Uint32Array.from(tv);
140
+ const geom = new THREE.BufferGeometry();
141
+ geom.setAttribute('position', new THREE.BufferAttribute(vertArray, 3));
142
+ geom.setIndex(new THREE.BufferAttribute(triArray, 1));
143
+
144
+ const bounds = computeBoundsFromVertices(vertArray);
145
+ const size = bounds ? bounds.size : [0, 0, 0];
146
+ const diag = bounds ? bounds.diag : 0;
147
+ const dx = size[0], dy = size[1], dz = size[2];
148
+ const scale = Math.max(diag || 0, Math.abs(dx), Math.abs(dy), Math.abs(dz), 1);
149
+
150
+ const idToFaceName = solid._idToFaceName instanceof Map ? solid._idToFaceName : new Map();
151
+ const solidLabel = solid.name || solid.owningFeatureID || 'SOLID';
152
+ const triangles = [];
153
+
154
+ for (let t = 0; t < triArray.length; t += 3) {
155
+ const triIndex = t / 3;
156
+ const i0 = triArray[t];
157
+ const i1 = triArray[t + 1];
158
+ const i2 = triArray[t + 2];
159
+ const ax = vertArray[i0 * 3], ay = vertArray[i0 * 3 + 1], az = vertArray[i0 * 3 + 2];
160
+ const bx = vertArray[i1 * 3], by = vertArray[i1 * 3 + 1], bz = vertArray[i1 * 3 + 2];
161
+ const cx = vertArray[i2 * 3], cy = vertArray[i2 * 3 + 1], cz = vertArray[i2 * 3 + 2];
162
+
163
+ const centerX = (ax + bx + cx) / 3;
164
+ const centerY = (ay + by + cy) / 3;
165
+ const centerZ = (az + bz + cz) / 3;
166
+
167
+ const ux = bx - ax;
168
+ const uy = by - ay;
169
+ const uz = bz - az;
170
+ const vx = cx - ax;
171
+ const vy = cy - ay;
172
+ const vz = cz - az;
173
+ let nx = uy * vz - uz * vy;
174
+ let ny = uz * vx - ux * vz;
175
+ let nz = ux * vy - uy * vx;
176
+ const len = Math.hypot(nx, ny, nz);
177
+ if (len > 0) {
178
+ nx /= len; ny /= len; nz /= len;
179
+ } else {
180
+ nx = 0; ny = 0; nz = 1;
181
+ }
182
+
183
+ const rawName = idToFaceName.get(ids[triIndex]);
184
+ const faceName = rawName ? String(rawName) : `${solidLabel}_FACE_${ids[triIndex] ?? triIndex}`;
185
+ triangles.push({
186
+ center: [centerX, centerY, centerZ],
187
+ normal: [nx, ny, nz],
188
+ faceName,
189
+ });
190
+ }
191
+
192
+ return {
193
+ geometry: geom,
194
+ triangles,
195
+ scale,
196
+ fallbackPrefix: `${solidLabel}_REPAIR`,
197
+ };
198
+ } catch {
199
+ return null;
200
+ }
201
+ }
202
+
203
+ function __booleanAssignFaceData(geometry, sourceMeta, debugLog) {
204
+ try {
205
+ if (!geometry) return null;
206
+ const indexAttr = geometry.getIndex();
207
+ const posAttr = geometry.getAttribute('position');
208
+ if (!indexAttr || !posAttr) return null;
209
+ const idx = indexAttr.array;
210
+ const pos = posAttr.array;
211
+ if (!idx || !pos) return null;
212
+ const triCount = idx.length / 3;
213
+ if (!(triCount > 0)) return null;
214
+
215
+ const triangles = Array.isArray(sourceMeta?.triangles) ? sourceMeta.triangles : [];
216
+ if (!triangles.length) return null;
217
+
218
+ const scale = Math.max(1, Number(sourceMeta?.scale) || 1);
219
+ const distLimit = Math.max(1e-9, Math.pow(scale * 5e-3, 2));
220
+ const scoreLimit = Math.max(distLimit * 16, distLimit * 2, 1e-6);
221
+ const fallbackPrefix = sourceMeta?.fallbackPrefix || 'REPAIRED';
222
+
223
+ const faceIDs = new Uint32Array(triCount);
224
+ const nameToID = new Map();
225
+ const idToName = new Map();
226
+ let nextID = 1;
227
+ let fallbackCount = 0;
228
+
229
+ for (let t = 0; t < triCount; t++) {
230
+ const i0 = idx[t * 3];
231
+ const i1 = idx[t * 3 + 1];
232
+ const i2 = idx[t * 3 + 2];
233
+ const ax = pos[i0 * 3], ay = pos[i0 * 3 + 1], az = pos[i0 * 3 + 2];
234
+ const bx = pos[i1 * 3], by = pos[i1 * 3 + 1], bz = pos[i1 * 3 + 2];
235
+ const cx = pos[i2 * 3], cy = pos[i2 * 3 + 1], cz = pos[i2 * 3 + 2];
236
+
237
+ const centerX = (ax + bx + cx) / 3;
238
+ const centerY = (ay + by + cy) / 3;
239
+ const centerZ = (az + bz + cz) / 3;
240
+
241
+ const ux = bx - ax, uy = by - ay, uz = bz - az;
242
+ const vx = cx - ax, vy = cy - ay, vz = cz - az;
243
+ let nx = uy * vz - uz * vy;
244
+ let ny = uz * vx - ux * vz;
245
+ let nz = ux * vy - uy * vx;
246
+ const len = Math.hypot(nx, ny, nz);
247
+ if (len > 0) { nx /= len; ny /= len; nz /= len; } else { nx = 0; ny = 0; nz = 1; }
248
+
249
+ let bestName = null;
250
+ let bestScore = Infinity;
251
+
252
+ for (const tri of triangles) {
253
+ const dx = centerX - tri.center[0];
254
+ const dy = centerY - tri.center[1];
255
+ const dz = centerZ - tri.center[2];
256
+ const dist2 = dx * dx + dy * dy + dz * dz;
257
+
258
+ const tn = tri.normal;
259
+ const dot = Math.max(0, Math.min(1, Math.abs(nx * tn[0] + ny * tn[1] + nz * tn[2])));
260
+ const normalPenalty = 1 - dot;
261
+
262
+ const score = dist2 + normalPenalty * distLimit;
263
+ if (score < bestScore) {
264
+ bestScore = score;
265
+ bestName = tri.faceName;
266
+ }
267
+ }
268
+
269
+ let faceName = bestName;
270
+ if (!faceName || !(bestScore <= scoreLimit)) {
271
+ fallbackCount += 1;
272
+ faceName = `${fallbackPrefix}_${fallbackCount}`;
273
+ }
274
+
275
+ let id = nameToID.get(faceName);
276
+ if (!id) {
277
+ id = nextID;
278
+ nextID += 1;
279
+ nameToID.set(faceName, id);
280
+ idToName.set(id, faceName);
281
+ }
282
+ faceIDs[t] = id;
283
+ }
284
+
285
+ return { faceIDs, idToFaceName: idToName };
286
+ } catch (err) {
287
+ debugLog?.('Face reassignment failed', { message: err?.message || err });
288
+ return null;
289
+ }
290
+ }
291
+
292
+ function __booleanMakeSolidFromGeometry(geometry, faceIDs, idToFaceName, debugLog) {
293
+ try {
294
+ if (!geometry || !faceIDs || !idToFaceName) return null;
295
+ const indexAttr = geometry.getIndex();
296
+ const posAttr = geometry.getAttribute('position');
297
+ if (!indexAttr || !posAttr) return null;
298
+ const triArray = Uint32Array.from(indexAttr.array);
299
+ const posArray = Float32Array.from(posAttr.array);
300
+ const faceIDArray = faceIDs instanceof Uint32Array ? faceIDs : Uint32Array.from(faceIDs);
301
+ const meshGL = new ManifoldMesh({
302
+ numProp: 3,
303
+ vertProperties: posArray,
304
+ triVerts: triArray,
305
+ faceID: faceIDArray,
306
+ });
307
+ meshGL.merge();
308
+ const manifoldObj = new Manifold(meshGL);
309
+ return Solid._fromManifold(manifoldObj, idToFaceName);
310
+ } catch (err) {
311
+ debugLog?.('Solid rebuild failed', { message: err?.message || err });
312
+ return null;
313
+ }
314
+ }
315
+
316
+ function __booleanAttemptRepairSolid(solid, eps, debugLog) {
317
+ const source = __booleanSolidToGeometry(solid);
318
+ if (!source) return null;
319
+
320
+ const baseGeom = source.geometry;
321
+ const repairer = new MeshRepairer();
322
+ const baseWeld = Math.max(1e-5, Math.abs(eps || 0) * 10);
323
+ const attemptScales = [1, 4, 16];
324
+
325
+ try {
326
+ for (const scale of attemptScales) {
327
+ const weld = baseWeld * scale;
328
+ const line = Math.max(1e-5, weld);
329
+ const grid = Math.max(1e-4, weld * 2);
330
+
331
+ const workingGeom = baseGeom.clone();
332
+ let repairedGeom;
333
+ try {
334
+ repairedGeom = repairer.repairAll(workingGeom, { weldEps: weld, lineEps: line, gridCell: grid }) || workingGeom;
335
+ } catch (err) {
336
+ debugLog?.('Repair attempt failed', {
337
+ attemptScale: scale,
338
+ message: err?.message || err,
339
+ });
340
+ try { workingGeom.dispose(); } catch { }
341
+ continue;
342
+ }
343
+
344
+ const faceData = __booleanAssignFaceData(repairedGeom, source, debugLog);
345
+ if (!faceData) {
346
+ try { repairedGeom.dispose(); } catch { }
347
+ continue;
348
+ }
349
+
350
+ const rebuilt = __booleanMakeSolidFromGeometry(repairedGeom, faceData.faceIDs, faceData.idToFaceName, debugLog);
351
+ try { repairedGeom.dispose(); } catch { }
352
+ if (rebuilt) {
353
+ try {
354
+ rebuilt.name = solid.name || rebuilt.name;
355
+ rebuilt.owningFeatureID = solid.owningFeatureID || rebuilt.owningFeatureID;
356
+ } catch { }
357
+ return rebuilt;
358
+ }
359
+ }
360
+ } finally {
361
+ try { baseGeom.dispose(); } catch { }
362
+ }
363
+
364
+ return null;
365
+ }
366
+
367
+ function __booleanMeshMergeUnion(baseSolid, toolSolid, eps, debugLog) {
368
+ const srcA = __booleanSolidToGeometry(baseSolid);
369
+ const srcB = __booleanSolidToGeometry(toolSolid);
370
+ if (!srcA || !srcB) {
371
+ try { srcA?.geometry?.dispose?.(); } catch { }
372
+ try { srcB?.geometry?.dispose?.(); } catch { }
373
+ return null;
374
+ }
375
+
376
+ const geomA = srcA.geometry;
377
+ const geomB = srcB.geometry;
378
+
379
+ const posA = geomA.getAttribute('position')?.array;
380
+ const posB = geomB.getAttribute('position')?.array;
381
+ const idxA = geomA.getIndex()?.array;
382
+ const idxB = geomB.getIndex()?.array;
383
+ if (!posA || !posB || !idxA || !idxB) {
384
+ try { geomA.dispose(); } catch { }
385
+ try { geomB.dispose(); } catch { }
386
+ return null;
387
+ }
388
+
389
+ const mergedPosBase = new Float32Array(posA.length + posB.length);
390
+ mergedPosBase.set(posA, 0);
391
+ mergedPosBase.set(posB, posA.length);
392
+
393
+ const mergedIdxBase = new Uint32Array(idxA.length + idxB.length);
394
+ mergedIdxBase.set(idxA, 0);
395
+ const offset = (posA.length / 3) >>> 0;
396
+ for (let i = 0; i < idxB.length; i++) {
397
+ mergedIdxBase[idxA.length + i] = idxB[i] + offset;
398
+ }
399
+
400
+ try { geomA.dispose(); } catch { }
401
+ try { geomB.dispose(); } catch { }
402
+
403
+ const sourceMeta = {
404
+ triangles: [...srcA.triangles, ...srcB.triangles],
405
+ scale: Math.max(srcA.scale || 1, srcB.scale || 1),
406
+ fallbackPrefix: `${baseSolid?.name || toolSolid?.name || 'UNION'}_REPAIR`,
407
+ };
408
+
409
+ const repairer = new MeshRepairer();
410
+ const baseWeld = Math.max(1e-5, Math.abs(eps || 0) * 10);
411
+ const attemptScales = [1, 4, 16];
412
+
413
+ const buildGeometry = () => {
414
+ const g = new THREE.BufferGeometry();
415
+ g.setAttribute('position', new THREE.BufferAttribute(mergedPosBase.slice(), 3));
416
+ g.setIndex(new THREE.BufferAttribute(mergedIdxBase.slice(), 1));
417
+ return g;
418
+ };
419
+
420
+ for (const scale of attemptScales) {
421
+ const weld = baseWeld * scale;
422
+ const line = Math.max(1e-5, weld);
423
+ const grid = Math.max(1e-4, weld * 2);
424
+
425
+ const workingGeom = buildGeometry();
426
+ let repairedGeom;
427
+ try {
428
+ repairedGeom = repairer.repairAll(workingGeom, { weldEps: weld, lineEps: line, gridCell: grid }) || workingGeom;
429
+ } catch (err) {
430
+ debugLog?.('Mesh-merge repair attempt failed', {
431
+ attemptScale: scale,
432
+ message: err?.message || err,
433
+ });
434
+ try { workingGeom.dispose(); } catch { }
435
+ continue;
436
+ }
437
+
438
+ const faceData = __booleanAssignFaceData(repairedGeom, sourceMeta, debugLog);
439
+ if (!faceData) {
440
+ try { repairedGeom.dispose(); } catch { }
441
+ continue;
442
+ }
443
+
444
+ const rebuilt = __booleanMakeSolidFromGeometry(repairedGeom, faceData.faceIDs, faceData.idToFaceName, debugLog);
445
+ try { repairedGeom.dispose(); } catch { }
446
+ if (rebuilt) {
447
+ try {
448
+ rebuilt.name = baseSolid?.name || rebuilt.name;
449
+ rebuilt.owningFeatureID = baseSolid?.owningFeatureID || rebuilt.owningFeatureID;
450
+ } catch { }
451
+ return rebuilt;
452
+ }
453
+ }
454
+
455
+ return null;
456
+ }
457
+
458
+ export async function applyBooleanOperation(partHistory, baseSolid, booleanParam, featureID) {
459
+ try {
460
+ if (!booleanParam || typeof booleanParam !== 'object') return { added: [baseSolid], removed: [] };
461
+ // Read canonical operation only
462
+ const opRaw = booleanParam.operation;
463
+ const op = String(opRaw || 'NONE').toUpperCase();
464
+ const tgt = Array.isArray(booleanParam.targets) ? booleanParam.targets.filter(Boolean) : [];
465
+
466
+ if (op === 'NONE' || tgt.length === 0) {
467
+ return { added: [baseSolid], removed: [] };
468
+ }
469
+
470
+ const scene = partHistory && partHistory.scene ? partHistory.scene : null;
471
+ if (!scene) return { added: [baseSolid], removed: [] };
472
+
473
+ // Collect unique tool solids: support either objects or names for back-compat
474
+ const seen = new Set();
475
+ const tools = [];
476
+ for (const entry of tgt) {
477
+ if (!entry) continue;
478
+ if (typeof entry === 'object') {
479
+ const obj = entry;
480
+ const key = obj.uuid || obj.id || obj.name || `${Date.now()}_${tools.length}`;
481
+ if (seen.has(key)) continue;
482
+ seen.add(key);
483
+ tools.push(obj);
484
+ } else {
485
+ const key = String(entry);
486
+ if (seen.has(key)) continue;
487
+ seen.add(key);
488
+ const obj = await scene.getObjectByName(key);
489
+ if (obj) tools.push(obj);
490
+ }
491
+ }
492
+
493
+ if (tools.length === 0) return { added: [baseSolid], removed: [] };
494
+
495
+ const debugLog = __booleanDebugLogger(featureID, op, baseSolid, tools);
496
+ debugLog('Starting boolean', {
497
+ featureID,
498
+ operation: op,
499
+ base: __booleanDebugSummarizeSolid(baseSolid),
500
+ tools: tools.map(__booleanDebugSummarizeSolid),
501
+ targetCount: tools.length,
502
+ });
503
+
504
+ // Apply selected boolean
505
+ if (op === 'SUBTRACT') {
506
+ // Inverted semantics for subtract: subtract the new baseSolid (tool)
507
+ // FROM each selected target solid. Add robust fallbacks similar to UNION.
508
+ const results = [];
509
+ let idx = 0;
510
+ // Local helpers (avoid depending on later declarations)
511
+ const approxScaleLocal = (solid) => {
512
+ try {
513
+ const vp = solid && solid._vertProperties;
514
+ if (!Array.isArray(vp) || vp.length < 3) return 1;
515
+ let minX = +Infinity, minY = +Infinity, minZ = +Infinity;
516
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
517
+ for (let i = 0; i < vp.length; i += 3) {
518
+ const x = vp[i], y = vp[i + 1], z = vp[i + 2];
519
+ if (x < minX) minX = x; if (x > maxX) maxX = x;
520
+ if (y < minY) minY = y; if (y > maxY) maxY = y;
521
+ if (z < minZ) minZ = z; if (z > maxZ) maxZ = z;
522
+ }
523
+ const dx = maxX - minX, dy = maxY - minY, dz = maxZ - minZ;
524
+ const diag = Math.hypot(dx, dy, dz);
525
+ return (diag > 0) ? diag : Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz), 1);
526
+ } catch { return 1; }
527
+ };
528
+ const preCleanLocal = (solid, eps) => {
529
+ try { if (typeof solid.setEpsilon === 'function') solid.setEpsilon(eps); } catch {}
530
+ try { if (typeof solid.fixTriangleWindingsByAdjacency === 'function') solid.fixTriangleWindingsByAdjacency(); } catch {}
531
+ };
532
+
533
+ const addResult = (solid, target) => {
534
+ solid.visualize();
535
+ const inheritedName = target?.name || target?.uuid || null;
536
+ const finalName = inheritedName || (featureID ? `${featureID}_${++idx}` : solid.name || 'RESULT');
537
+ try { solid.name = finalName; } catch (_) { }
538
+ try {
539
+ if (target?.owningFeatureID) solid.owningFeatureID = target.owningFeatureID;
540
+ } catch (_) { }
541
+ results.push(solid);
542
+ };
543
+
544
+ for (const target of tools) {
545
+ let success = false;
546
+ try {
547
+ const out = target.subtract(baseSolid);
548
+ addResult(out, target);
549
+ success = true;
550
+ } catch (e1) {
551
+ debugLog('Primary subtract failed; attempting welded fallback', {
552
+ message: e1?.message || e1,
553
+ target: __booleanDebugSummarizeSolid(target),
554
+ tool: __booleanDebugSummarizeSolid(baseSolid),
555
+ });
556
+ }
557
+ if (success) continue;
558
+
559
+ try {
560
+ const a = typeof target.clone === 'function' ? target.clone() : target;
561
+ const b = typeof baseSolid.clone === 'function' ? baseSolid.clone() : baseSolid;
562
+ const scale = Math.max(1, approxScaleLocal(a));
563
+ const eps = Math.max(1e-9, 1e-6 * scale);
564
+ preCleanLocal(a, eps);
565
+ preCleanLocal(b, eps);
566
+ const out = a.subtract(b);
567
+ addResult(out, target);
568
+ success = true;
569
+ } catch (e2) {
570
+ debugLog('Welded subtract fallback failed; passing target through', {
571
+ message: e2?.message || e2,
572
+ target: __booleanDebugSummarizeSolid(target),
573
+ });
574
+ }
575
+ if (!success) results.push(target);
576
+ }
577
+ // In SUBTRACT: removed = [all targets, baseSolid]
578
+ const removed = [...tools];
579
+ if (baseSolid) removed.push(baseSolid);
580
+ debugLog('Subtract boolean finished', {
581
+ results: results.map(__booleanDebugSummarizeSolid),
582
+ removed: removed.map(__booleanDebugSummarizeSolid),
583
+ });
584
+
585
+
586
+ return { added: results.length ? results : [baseSolid], removed };
587
+ }
588
+
589
+ // Helper: approximate scale from authoring arrays (avoids manifold build)
590
+ const approxScale = (solid) => {
591
+ try {
592
+ const vp = solid && solid._vertProperties;
593
+ if (!Array.isArray(vp) || vp.length < 3) return 1;
594
+ let minX = +Infinity, minY = +Infinity, minZ = +Infinity;
595
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
596
+ for (let i = 0; i < vp.length; i += 3) {
597
+ const x = vp[i], y = vp[i + 1], z = vp[i + 2];
598
+ if (x < minX) minX = x; if (x > maxX) maxX = x;
599
+ if (y < minY) minY = y; if (y > maxY) maxY = y;
600
+ if (z < minZ) minZ = z; if (z > maxZ) maxZ = z;
601
+ }
602
+ const dx = maxX - minX, dy = maxY - minY, dz = maxZ - minZ;
603
+ const diag = Math.hypot(dx, dy, dz);
604
+ return (diag > 0) ? diag : Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz), 1);
605
+ } catch { return 1; }
606
+ };
607
+
608
+ // Helper: light pre-clean in authoring space (no manifold build)
609
+ const preClean = (solid, eps) => {
610
+ try { if (typeof solid.fixTriangleWindingsByAdjacency === 'function') solid.fixTriangleWindingsByAdjacency(); } catch {}
611
+ try { if (typeof solid.setEpsilon === 'function') solid.setEpsilon(eps); } catch (e) {console.warn(e, solid)}
612
+ };
613
+
614
+ // UNION / INTERSECT: fold tools into the new baseSolid and replace base
615
+ let result = baseSolid;
616
+ for (const tool of tools) {
617
+ if (op !== 'UNION' && op !== 'INTERSECT') {
618
+ // Unknown op → pass through
619
+ return { added: [baseSolid], removed: [] };
620
+ }
621
+
622
+ const scale = Math.max(1, approxScale(result));
623
+ const eps = Math.max(1e-9, 1e-6 * scale);
624
+
625
+ try {
626
+ // Attempt the boolean directly; repair fallback handles welding if needed.
627
+ result = (op === 'UNION') ? result.union(tool) : result.intersect(tool);
628
+ } catch (e1) {
629
+ debugLog('Primary union/intersect failed; attempting welded fallback', {
630
+ message: e1?.message || e1,
631
+ tool: __booleanDebugSummarizeSolid(tool),
632
+ epsilon: eps,
633
+ });
634
+ let repaired = false;
635
+ try {
636
+ const repairedBase = __booleanAttemptRepairSolid(result, eps, debugLog);
637
+ const repairedTool = __booleanAttemptRepairSolid(tool, eps, debugLog);
638
+ if (repairedBase || repairedTool) {
639
+ debugLog('Attempting repair fallback', {
640
+ repairedBase: !!repairedBase,
641
+ repairedTool: !!repairedTool,
642
+ });
643
+ const baseOperand = repairedBase || (typeof result.clone === 'function' ? result.clone() : result);
644
+ const toolOperand = repairedTool || (typeof tool.clone === 'function' ? tool.clone() : tool);
645
+ preClean(baseOperand, eps);
646
+ preClean(toolOperand, eps);
647
+ result = (op === 'UNION') ? baseOperand.union(toolOperand) : baseOperand.intersect(toolOperand);
648
+ repaired = true;
649
+ }
650
+ } catch (repairErr) {
651
+ debugLog('Repair fallback failed', { message: repairErr?.message || repairErr });
652
+ }
653
+ if (repaired) continue;
654
+ // Fallback A: try on welded clones with tiny epsilon
655
+ try {
656
+ const a = typeof result.clone === 'function' ? result.clone() : result;
657
+ const b = typeof tool.clone === 'function' ? tool.clone() : tool;
658
+ preClean(a, eps);
659
+ preClean(b, eps);
660
+ result = (op === 'UNION') ? a.union(b) : a.intersect(b);
661
+ } catch (e2) {
662
+ let meshRecovered = false;
663
+ if (op === 'UNION') {
664
+ try {
665
+ const merged = __booleanMeshMergeUnion(result, tool, eps, debugLog);
666
+ if (merged) {
667
+ debugLog('Mesh-merge fallback succeeded');
668
+ const repairedMerged = __booleanAttemptRepairSolid(merged, eps, debugLog) || merged;
669
+ if (repairedMerged !== merged) {
670
+ debugLog('Mesh-merge result repaired');
671
+ }
672
+ result = repairedMerged;
673
+ meshRecovered = true;
674
+ }
675
+ } catch (meshErr) {
676
+ debugLog('Mesh-merge fallback failed', { message: meshErr?.message || meshErr });
677
+ }
678
+ }
679
+ if (meshRecovered) continue;
680
+ throw e2;
681
+ }
682
+ }
683
+ }
684
+ result.visualize();
685
+ debugLog('Boolean successful', {
686
+ result: __booleanDebugSummarizeSolid(result),
687
+ removedCount: tools.length + (baseSolid ? 1 : 0),
688
+ });
689
+ try { result.name = featureID || result.name || 'RESULT'; } catch (_) { }
690
+ // UNION/INTERSECT: remove tools and the base solid (replace base with result)
691
+ const removed = tools.slice();
692
+ if (baseSolid) removed.push(baseSolid);
693
+ return { added: [result], removed };
694
+ } catch (err) {
695
+ // On failure, pass through original to avoid breaking the pipeline
696
+ console.warn('[applyBooleanOperation] failed:', err?.message || err);
697
+ const debugLog = __booleanDebugLogger(featureID, booleanParam?.operation, baseSolid, []);
698
+ debugLog('applyBooleanOperation threw; returning base solid', {
699
+ error: err?.message || err,
700
+ stack: err?.stack || null,
701
+ });
702
+ return { added: [baseSolid], removed: [] };
703
+ }
704
+ }