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,659 @@
1
+ import * as THREE from 'three';
2
+ import { unfoldSheetMetal, exportFlatPatternToSVG } from './sheetMetalUnfold.js';
3
+ import { SHEET_METAL_FACE_TYPES } from '../features/sheetMetal/sheetMetalFaceTypes.js';
4
+
5
+ const EPS = 1e-12;
6
+ const BEND_SURFACE_TYPES = new Set(['cylindrical', 'conical']);
7
+
8
+ const makeEdgeKey = (a, b) => (a < b ? `${a}|${b}` : `${b}|${a}`);
9
+
10
+ function readVec3(value) {
11
+ if (!Array.isArray(value) || value.length < 3) return null;
12
+ const x = Number(value[0]);
13
+ const y = Number(value[1]);
14
+ const z = Number(value[2]);
15
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return null;
16
+ return new THREE.Vector3(x, y, z);
17
+ }
18
+
19
+ function polylineLength(positions) {
20
+ if (!Array.isArray(positions) || positions.length < 2) return 0;
21
+ let length = 0;
22
+ for (let i = 1; i < positions.length; i++) {
23
+ const a = positions[i - 1];
24
+ const b = positions[i];
25
+ if (!Array.isArray(a) || !Array.isArray(b)) continue;
26
+ const dx = b[0] - a[0];
27
+ const dy = b[1] - a[1];
28
+ const dz = b[2] - a[2];
29
+ length += Math.hypot(dx, dy, dz);
30
+ }
31
+ return length;
32
+ }
33
+
34
+ function deriveAxisFromBoundary(boundary) {
35
+ const positions = boundary?.positions;
36
+ if (!Array.isArray(positions) || positions.length < 2) return null;
37
+ const a = positions[0];
38
+ const b = positions[positions.length - 1];
39
+ if (!Array.isArray(a) || !Array.isArray(b)) return null;
40
+ const dir = new THREE.Vector3(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
41
+ if (dir.lengthSq() < EPS) return null;
42
+ return dir.normalize();
43
+ }
44
+
45
+ function derivePointFromBoundary(boundary) {
46
+ const positions = boundary?.positions;
47
+ if (!Array.isArray(positions) || positions.length === 0) return null;
48
+ const a = positions[0];
49
+ if (!Array.isArray(a) || a.length < 3) return null;
50
+ return new THREE.Vector3(a[0], a[1], a[2]);
51
+ }
52
+
53
+ function signedAngleAroundAxis(from, to, axis) {
54
+ const cross = new THREE.Vector3().crossVectors(from, to);
55
+ const dot = from.dot(to);
56
+ return Math.atan2(axis.dot(cross), dot);
57
+ }
58
+
59
+ function angleToMatchNormal(from, to, axis) {
60
+ const angleSame = signedAngleAroundAxis(from, to, axis);
61
+ const angleOpp = signedAngleAroundAxis(from, to.clone().multiplyScalar(-1), axis);
62
+ const tmp = from.clone().applyAxisAngle(axis, angleSame);
63
+ const tmpOpp = from.clone().applyAxisAngle(axis, angleOpp);
64
+ return tmp.dot(to) >= tmpOpp.dot(to) ? angleSame : angleOpp;
65
+ }
66
+
67
+ function resolveSheetMetalParams(solid, opts = {}) {
68
+ const fallbackThickness = Number.isFinite(opts.thickness)
69
+ ? Number(opts.thickness)
70
+ : Number(solid?.userData?.sheetMetal?.thickness ?? solid?.userData?.sheetThickness ?? 0);
71
+ const fallbackRadius = Number.isFinite(opts.bendRadius)
72
+ ? Number(opts.bendRadius)
73
+ : Number(solid?.userData?.sheetMetal?.bendRadius ?? solid?.userData?.sheetBendRadius ?? 0);
74
+ const neutral = Number.isFinite(opts.kFactor)
75
+ ? Number(opts.kFactor)
76
+ : (Number.isFinite(opts.neutralFactor)
77
+ ? Number(opts.neutralFactor)
78
+ : Number(solid?.userData?.sheetMetal?.neutralFactor ?? solid?.userData?.sheetMetalNeutralFactor ?? 0.5));
79
+ return {
80
+ thickness: Number.isFinite(fallbackThickness) ? fallbackThickness : 0,
81
+ bendRadius: Number.isFinite(fallbackRadius) ? fallbackRadius : 0,
82
+ kFactor: Number.isFinite(neutral) ? neutral : 0.5,
83
+ };
84
+ }
85
+
86
+ function computeFaceStatsFromMesh(mesh) {
87
+ const positions = mesh?.vertProperties;
88
+ const triVerts = mesh?.triVerts;
89
+ const faceIDs = mesh?.faceID;
90
+ if (!positions || !triVerts || !faceIDs) return new Map();
91
+ const triCount = (triVerts.length / 3) | 0;
92
+ const stats = new Map();
93
+
94
+ const vA = new THREE.Vector3();
95
+ const vB = new THREE.Vector3();
96
+ const vC = new THREE.Vector3();
97
+ const vAB = new THREE.Vector3();
98
+ const vAC = new THREE.Vector3();
99
+ const n = new THREE.Vector3();
100
+ const centroid = new THREE.Vector3();
101
+
102
+ for (let t = 0; t < triCount; t++) {
103
+ const faceId = faceIDs[t];
104
+ let data = stats.get(faceId);
105
+ if (!data) {
106
+ data = { sum: new THREE.Vector3(), centroidSum: new THREE.Vector3(), area: 0, ref: null };
107
+ stats.set(faceId, data);
108
+ }
109
+ const base = t * 3;
110
+ const i0 = triVerts[base + 0];
111
+ const i1 = triVerts[base + 1];
112
+ const i2 = triVerts[base + 2];
113
+ vA.fromArray(positions, i0 * 3);
114
+ vB.fromArray(positions, i1 * 3);
115
+ vC.fromArray(positions, i2 * 3);
116
+ vAB.subVectors(vB, vA);
117
+ vAC.subVectors(vC, vA);
118
+ n.crossVectors(vAB, vAC);
119
+ const len = n.length();
120
+ if (len < EPS) continue;
121
+ const area = len * 0.5;
122
+ n.multiplyScalar(1 / len);
123
+ if (!data.ref) data.ref = n.clone();
124
+ else if (n.dot(data.ref) < 0) n.negate();
125
+ data.sum.addScaledVector(n, area);
126
+ centroid.copy(vA).add(vB).add(vC).multiplyScalar(1 / 3);
127
+ data.centroidSum.addScaledVector(centroid, area);
128
+ data.area += area;
129
+ }
130
+
131
+ const out = new Map();
132
+ for (const [faceId, data] of stats.entries()) {
133
+ const normal = data.sum.lengthSq() > EPS
134
+ ? data.sum.normalize()
135
+ : (data.ref ? data.ref.clone().normalize() : new THREE.Vector3(0, 1, 0));
136
+ const centroidFinal = data.area > EPS
137
+ ? data.centroidSum.multiplyScalar(1 / data.area)
138
+ : new THREE.Vector3();
139
+ out.set(faceId, { normal, centroid: centroidFinal, area: data.area });
140
+ }
141
+ return out;
142
+ }
143
+
144
+ function computeMeshBounds(positions) {
145
+ if (!positions || !positions.length) return new THREE.Box3();
146
+ const box = new THREE.Box3();
147
+ const v = new THREE.Vector3();
148
+ const count = (positions.length / 3) | 0;
149
+ for (let i = 0; i < count; i++) {
150
+ v.set(positions[i * 3 + 0], positions[i * 3 + 1], positions[i * 3 + 2]);
151
+ box.expandByPoint(v);
152
+ }
153
+ return box;
154
+ }
155
+
156
+ function buildBendVertexRemap({
157
+ mesh,
158
+ boundaries,
159
+ faceInfo,
160
+ planarFaceIds,
161
+ bendFaceIds,
162
+ faceNameToId,
163
+ epsilon,
164
+ }) {
165
+ if (!mesh || !mesh.vertProperties) return { positions: null, remap: null };
166
+ const positions = Array.from(mesh.vertProperties);
167
+ const remap = new Map();
168
+ const bendPointMap = new Map();
169
+ const snap = Number.isFinite(epsilon) ? epsilon : 1e-5;
170
+
171
+ for (const boundary of boundaries || []) {
172
+ const faceAName = boundary?.faceA;
173
+ const faceBName = boundary?.faceB;
174
+ if (!faceAName || !faceBName) continue;
175
+ const faceAId = faceNameToId.get(faceAName);
176
+ const faceBId = faceNameToId.get(faceBName);
177
+ if (faceAId == null || faceBId == null) continue;
178
+ const isPlanarA = planarFaceIds.has(faceAId);
179
+ const isPlanarB = planarFaceIds.has(faceBId);
180
+ const isBendA = bendFaceIds.has(faceAId);
181
+ const isBendB = bendFaceIds.has(faceBId);
182
+ if (!(isPlanarA && isBendB) && !(isPlanarB && isBendA)) continue;
183
+
184
+ const bendId = isBendA ? faceAId : faceBId;
185
+ const bendMeta = faceInfo.get(bendId)?.meta || {};
186
+ let axisDir = readVec3(bendMeta.axis);
187
+ if (!axisDir) axisDir = deriveAxisFromBoundary(boundary);
188
+ if (!axisDir || axisDir.lengthSq() < EPS) continue;
189
+ axisDir.normalize();
190
+ let axisPoint = readVec3(bendMeta.center);
191
+ if (!axisPoint) axisPoint = derivePointFromBoundary(boundary);
192
+ if (!axisPoint) axisPoint = new THREE.Vector3();
193
+
194
+ let map = bendPointMap.get(bendId);
195
+ if (!map) { map = new Map(); bendPointMap.set(bendId, map); }
196
+
197
+ const indices = Array.isArray(boundary?.indices) ? boundary.indices : [];
198
+ for (const idx of indices) {
199
+ if (!Number.isFinite(idx)) continue;
200
+ if (remap.has(idx)) continue;
201
+ const px = positions[idx * 3 + 0];
202
+ const py = positions[idx * 3 + 1];
203
+ const pz = positions[idx * 3 + 2];
204
+ const p = new THREE.Vector3(px, py, pz);
205
+ const t = axisDir.dot(p.clone().sub(axisPoint));
206
+ const tSnap = Math.round(t / snap) * snap;
207
+ const key = `${bendId}|${tSnap.toFixed(6)}`;
208
+ let newIdx = map.get(key);
209
+ if (newIdx == null) {
210
+ const proj = axisPoint.clone().addScaledVector(axisDir, tSnap);
211
+ newIdx = (positions.length / 3) | 0;
212
+ positions.push(proj.x, proj.y, proj.z);
213
+ map.set(key, newIdx);
214
+ }
215
+ remap.set(idx, newIdx);
216
+ }
217
+ }
218
+
219
+ return {
220
+ positions,
221
+ remap,
222
+ };
223
+ }
224
+
225
+ function buildPlanarGroups({ planarFaceIds, faceInfo, faceStats, boundaries, bounds, opts }) {
226
+ const faceToGroup = new Map();
227
+ const groupNormals = new Map();
228
+
229
+ const diag = bounds?.isEmpty?.() ? 0 : bounds.getSize(new THREE.Vector3()).length();
230
+ const normalTol = Number.isFinite(opts.normalTol) ? opts.normalTol : 1e-3;
231
+ const distTol = Number.isFinite(opts.planeTol) ? opts.planeTol : Math.max(1e-6, diag * 1e-5);
232
+
233
+ const adjacency = new Map();
234
+ for (const boundary of boundaries || []) {
235
+ const faceAName = boundary?.faceA;
236
+ const faceBName = boundary?.faceB;
237
+ if (!faceAName || !faceBName) continue;
238
+ const faceAId = opts.faceNameToId?.get(faceAName);
239
+ const faceBId = opts.faceNameToId?.get(faceBName);
240
+ if (faceAId == null || faceBId == null) continue;
241
+ if (!planarFaceIds.has(faceAId) || !planarFaceIds.has(faceBId)) continue;
242
+ let list = adjacency.get(faceAId);
243
+ if (!list) { list = new Set(); adjacency.set(faceAId, list); }
244
+ list.add(faceBId);
245
+ list = adjacency.get(faceBId);
246
+ if (!list) { list = new Set(); adjacency.set(faceBId, list); }
247
+ list.add(faceAId);
248
+ }
249
+
250
+ let groupId = 0;
251
+ for (const faceId of planarFaceIds) {
252
+ if (faceToGroup.has(faceId)) continue;
253
+ const stack = [faceId];
254
+ faceToGroup.set(faceId, groupId);
255
+ while (stack.length) {
256
+ const current = stack.pop();
257
+ const neighbors = adjacency.get(current);
258
+ if (!neighbors) continue;
259
+ for (const neighbor of neighbors) {
260
+ if (faceToGroup.has(neighbor)) continue;
261
+ const infoA = faceInfo.get(current);
262
+ const infoB = faceInfo.get(neighbor);
263
+ if (infoA?.sheetType && infoB?.sheetType && infoA.sheetType !== infoB.sheetType) continue;
264
+ const statsA = faceStats.get(current);
265
+ const statsB = faceStats.get(neighbor);
266
+ if (!statsA || !statsB) continue;
267
+ const dot = statsA.normal.dot(statsB.normal);
268
+ if (dot < 1 - normalTol) continue;
269
+ const distA = statsA.normal.dot(statsA.centroid);
270
+ const distB = statsB.normal.dot(statsB.centroid);
271
+ if (Math.abs(distA - distB) > distTol) continue;
272
+ faceToGroup.set(neighbor, groupId);
273
+ stack.push(neighbor);
274
+ }
275
+ }
276
+ groupId += 1;
277
+ }
278
+
279
+ for (const [faceId, gid] of faceToGroup.entries()) {
280
+ const stats = faceStats.get(faceId);
281
+ if (!stats) continue;
282
+ const area = stats.area || 0;
283
+ if (!groupNormals.has(gid)) {
284
+ groupNormals.set(gid, stats.normal.clone().multiplyScalar(area));
285
+ } else {
286
+ groupNormals.get(gid).addScaledVector(stats.normal, area);
287
+ }
288
+ }
289
+
290
+ for (const [gid, normal] of groupNormals.entries()) {
291
+ if (normal.lengthSq() > EPS) normal.normalize();
292
+ else normal.set(0, 1, 0);
293
+ }
294
+
295
+ return { faceToGroup, groupNormals };
296
+ }
297
+
298
+ function buildGeometryFromMesh(mesh, planarFaceIds, faceToGroup, vertexRemap, positionsOverride) {
299
+ if (!mesh || !mesh.vertProperties || !mesh.triVerts || !mesh.faceID) return null;
300
+ const positions = positionsOverride
301
+ ? new Float32Array(positionsOverride)
302
+ : new Float32Array(mesh.vertProperties);
303
+ const triVerts = mesh.triVerts;
304
+ const faceIDs = mesh.faceID;
305
+ const triCount = (triVerts.length / 3) | 0;
306
+
307
+ const indices = [];
308
+ const faceIdsAttr = [];
309
+ for (let t = 0; t < triCount; t++) {
310
+ const faceId = faceIDs[t];
311
+ if (!planarFaceIds.has(faceId)) continue;
312
+ const groupId = faceToGroup?.get(faceId);
313
+ if (groupId == null) continue;
314
+ const base = t * 3;
315
+ const i0 = triVerts[base + 0];
316
+ const i1 = triVerts[base + 1];
317
+ const i2 = triVerts[base + 2];
318
+ indices.push(
319
+ vertexRemap?.get(i0) ?? i0,
320
+ vertexRemap?.get(i1) ?? i1,
321
+ vertexRemap?.get(i2) ?? i2,
322
+ );
323
+ faceIdsAttr.push(groupId);
324
+ }
325
+
326
+ if (!indices.length) return null;
327
+
328
+ const geometry = new THREE.BufferGeometry();
329
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
330
+ const indexArray = (positions.length / 3) > 65535
331
+ ? new Uint32Array(indices)
332
+ : new Uint16Array(indices);
333
+ geometry.setIndex(new THREE.BufferAttribute(indexArray, 1));
334
+ geometry.setAttribute('faceId', new THREE.BufferAttribute(new Uint32Array(faceIdsAttr), 1));
335
+ return geometry;
336
+ }
337
+
338
+ function buildInputsFromSolid(solid, opts = {}) {
339
+ if (!solid || typeof solid.getMesh !== 'function') return null;
340
+
341
+ const explicitGeometry = (opts.geometry instanceof THREE.BufferGeometry)
342
+ ? opts.geometry
343
+ : (solid.userData?.flatPatternGeometry || solid.userData?.sheetMetalFlatGeometry);
344
+ const explicitBends = Array.isArray(opts.bends)
345
+ ? opts.bends
346
+ : (solid.userData?.flatPatternBends || solid.userData?.sheetMetalBends);
347
+ if (explicitGeometry instanceof THREE.BufferGeometry && Array.isArray(explicitBends)) {
348
+ return {
349
+ geometry: explicitGeometry,
350
+ bends: explicitBends,
351
+ edgeExclusions: null,
352
+ warnings: [],
353
+ };
354
+ }
355
+
356
+ const mesh = solid.getMesh();
357
+ try {
358
+ if (!mesh || !mesh.vertProperties || !mesh.triVerts) return null;
359
+ const faceIDs = mesh.faceID;
360
+ if (!faceIDs || !faceIDs.length) return null;
361
+
362
+ const idToName = solid._idToFaceName instanceof Map ? solid._idToFaceName : new Map();
363
+ const faceNameToId = solid._faceNameToID instanceof Map ? solid._faceNameToID : new Map();
364
+ const planarFaceIds = new Set();
365
+ const bendFaceIds = new Set();
366
+ const planarBySide = {
367
+ [SHEET_METAL_FACE_TYPES.A]: new Set(),
368
+ [SHEET_METAL_FACE_TYPES.B]: new Set(),
369
+ };
370
+ const planarFallback = new Set();
371
+ const faceInfo = new Map();
372
+ const faceStats = computeFaceStatsFromMesh(mesh);
373
+ let areaA = 0;
374
+ let areaB = 0;
375
+
376
+ for (let t = 0; t < faceIDs.length; t++) {
377
+ const faceId = faceIDs[t];
378
+ if (faceInfo.has(faceId)) continue;
379
+ const faceName = idToName.get(faceId) || `FACE_${faceId}`;
380
+ let meta = {};
381
+ if (typeof solid.getFaceMetadata === 'function') {
382
+ try { meta = solid.getFaceMetadata(faceName) || {}; } catch { meta = {}; }
383
+ }
384
+ const sheetType = meta.sheetMetalFaceType || null;
385
+ const surfaceType = meta.type || null;
386
+ const stats = faceStats.get(faceId);
387
+ if (sheetType === SHEET_METAL_FACE_TYPES.A || sheetType === SHEET_METAL_FACE_TYPES.B) {
388
+ planarBySide[sheetType].add(faceId);
389
+ const area = stats?.area || 0;
390
+ if (sheetType === SHEET_METAL_FACE_TYPES.A) areaA += area;
391
+ if (sheetType === SHEET_METAL_FACE_TYPES.B) areaB += area;
392
+ }
393
+ if (surfaceType && BEND_SURFACE_TYPES.has(surfaceType)) {
394
+ bendFaceIds.add(faceId);
395
+ }
396
+ if (!sheetType && !surfaceType && stats) {
397
+ planarFallback.add(faceId);
398
+ }
399
+ faceInfo.set(faceId, { faceId, faceName, meta, sheetType, surfaceType });
400
+ }
401
+
402
+ const hasA = planarBySide[SHEET_METAL_FACE_TYPES.A].size > 0;
403
+ const hasB = planarBySide[SHEET_METAL_FACE_TYPES.B].size > 0;
404
+ const forcedSide = opts.forceSide === SHEET_METAL_FACE_TYPES.A || opts.forceSide === SHEET_METAL_FACE_TYPES.B
405
+ ? opts.forceSide
406
+ : null;
407
+ let chosenSide = null;
408
+ if (forcedSide) {
409
+ chosenSide = forcedSide;
410
+ } else if (hasA || hasB) {
411
+ const prefer = (areaA >= areaB) ? SHEET_METAL_FACE_TYPES.A : SHEET_METAL_FACE_TYPES.B;
412
+ chosenSide = planarBySide[prefer].size ? prefer : (hasA ? SHEET_METAL_FACE_TYPES.A : SHEET_METAL_FACE_TYPES.B);
413
+ }
414
+ if (chosenSide) {
415
+ for (const id of planarBySide[chosenSide]) planarFaceIds.add(id);
416
+ } else {
417
+ for (const id of planarFallback) planarFaceIds.add(id);
418
+ }
419
+
420
+ const boundaries = (typeof solid.getBoundaryEdgePolylines === 'function')
421
+ ? (solid.getBoundaryEdgePolylines() || [])
422
+ : [];
423
+ const bounds = computeMeshBounds(mesh.vertProperties);
424
+ const useExplicitBends = Array.isArray(opts.bends) && opts.bends.length;
425
+ let faceToGroup;
426
+ let groupNormals;
427
+ if (useExplicitBends) {
428
+ faceToGroup = new Map();
429
+ groupNormals = new Map();
430
+ for (const faceId of planarFaceIds) {
431
+ faceToGroup.set(faceId, faceId);
432
+ const stats = faceStats.get(faceId);
433
+ if (stats?.normal) groupNormals.set(faceId, stats.normal.clone());
434
+ }
435
+ } else {
436
+ const grouped = buildPlanarGroups({
437
+ planarFaceIds,
438
+ faceInfo,
439
+ faceStats,
440
+ boundaries,
441
+ bounds,
442
+ opts: { normalTol: opts.normalTol, planeTol: opts.planeTol, faceNameToId },
443
+ });
444
+ faceToGroup = grouped.faceToGroup;
445
+ groupNormals = grouped.groupNormals;
446
+ }
447
+
448
+ let remap = null;
449
+ let positionsOverride = null;
450
+ if (!useExplicitBends) {
451
+ const remapData = buildBendVertexRemap({
452
+ mesh,
453
+ boundaries,
454
+ faceInfo,
455
+ planarFaceIds,
456
+ bendFaceIds,
457
+ faceNameToId,
458
+ epsilon: opts.epsilon,
459
+ });
460
+ remap = remapData.remap;
461
+ positionsOverride = remapData.positions;
462
+ }
463
+
464
+ const geometry = buildGeometryFromMesh(mesh, planarFaceIds, faceToGroup, remap, positionsOverride);
465
+ if (!geometry) return null;
466
+
467
+ const params = resolveSheetMetalParams(solid, opts);
468
+ const edgeExclusions = new Set();
469
+ const bendAdj = new Map();
470
+
471
+ if (boundaries && boundaries.length) {
472
+ for (const boundary of boundaries) {
473
+ const faceAName = boundary?.faceA;
474
+ const faceBName = boundary?.faceB;
475
+ if (!faceAName || !faceBName) continue;
476
+ const faceAId = faceNameToId.get(faceAName);
477
+ const faceBId = faceNameToId.get(faceBName);
478
+ if (faceAId == null || faceBId == null) continue;
479
+
480
+ const isPlanarA = planarFaceIds.has(faceAId);
481
+ const isPlanarB = planarFaceIds.has(faceBId);
482
+ const isBendA = bendFaceIds.has(faceAId);
483
+ const isBendB = bendFaceIds.has(faceBId);
484
+
485
+ if ((isBendA && isPlanarB) || (isBendB && isPlanarA)) {
486
+ const bendId = isBendA ? faceAId : faceBId;
487
+ const planarId = isPlanarA ? faceAId : faceBId;
488
+ const groupId = faceToGroup.get(planarId);
489
+ if (groupId == null) continue;
490
+ const length = polylineLength(boundary?.positions);
491
+ let byPlanar = bendAdj.get(bendId);
492
+ if (!byPlanar) { byPlanar = new Map(); bendAdj.set(bendId, byPlanar); }
493
+ const prev = byPlanar.get(groupId);
494
+ if (!prev || length > prev.length) {
495
+ byPlanar.set(groupId, { length, boundary });
496
+ }
497
+
498
+ const indices = Array.isArray(boundary?.indices) ? boundary.indices : [];
499
+ for (let i = 1; i < indices.length; i++) {
500
+ const aRaw = indices[i - 1];
501
+ const bRaw = indices[i];
502
+ const a = remap?.get(aRaw) ?? aRaw;
503
+ const b = remap?.get(bRaw) ?? bRaw;
504
+ if (Number.isFinite(a) && Number.isFinite(b) && a !== b) {
505
+ edgeExclusions.add(makeEdgeKey(a, b));
506
+ }
507
+ }
508
+ }
509
+
510
+ // Note: edges between planar faces are handled by bend metadata exclusion in unfold.
511
+ }
512
+ }
513
+
514
+ const bends = Array.isArray(explicitBends)
515
+ ? explicitBends
516
+ : [];
517
+
518
+ if (!bends.length) {
519
+ for (const [bendFaceId, neighbors] of bendAdj.entries()) {
520
+ const list = Array.from(neighbors.entries()).map(([planarId, data]) => ({
521
+ planarId,
522
+ length: data.length,
523
+ boundary: data.boundary,
524
+ })).sort((a, b) => b.length - a.length);
525
+ if (list.length < 2) continue;
526
+ const aFaceId = list[0].planarId;
527
+ const bFaceId = list[1].planarId;
528
+ if (aFaceId === bFaceId) continue;
529
+ const bendInfo = faceInfo.get(bendFaceId);
530
+ const meta = bendInfo?.meta || {};
531
+ let axisDir = readVec3(meta.axis);
532
+ if (!axisDir) axisDir = deriveAxisFromBoundary(list[0].boundary);
533
+ if (!axisDir || axisDir.lengthSq() < EPS) continue;
534
+ axisDir.normalize();
535
+ let axisPoint = readVec3(meta.center);
536
+ if (!axisPoint) axisPoint = derivePointFromBoundary(list[0].boundary);
537
+ if (!axisPoint) axisPoint = new THREE.Vector3();
538
+
539
+ const nA = groupNormals.get(aFaceId);
540
+ const nB = groupNormals.get(bFaceId);
541
+ let angleRad = 0;
542
+ if (nA && nB) {
543
+ angleRad = angleToMatchNormal(nA, nB, axisDir);
544
+ }
545
+
546
+ const radius = Number.isFinite(meta.radius) ? Number(meta.radius) : params.bendRadius;
547
+ const bend = {
548
+ bendId: bendInfo?.faceName || bendFaceId,
549
+ aFaceId,
550
+ bFaceId,
551
+ axisPoint,
552
+ axisDir,
553
+ angleRad,
554
+ radius: Number.isFinite(radius) ? radius : 0,
555
+ thickness: params.thickness,
556
+ kFactor: params.kFactor,
557
+ upDown: angleRad >= 0 ? 'up' : 'down',
558
+ };
559
+ bends.push(bend);
560
+ }
561
+ }
562
+
563
+ return {
564
+ geometry,
565
+ bends,
566
+ edgeExclusions,
567
+ warnings: [],
568
+ meta: {
569
+ chosenSide: chosenSide || null,
570
+ hasA,
571
+ hasB,
572
+ },
573
+ };
574
+ } finally {
575
+ try { if (mesh && typeof mesh.delete === 'function') mesh.delete(); } catch { }
576
+ }
577
+ }
578
+
579
+ function scoreFlatPattern(flatPattern) {
580
+ if (!flatPattern) return -1;
581
+ const outlines = Array.isArray(flatPattern.outlines) ? flatPattern.outlines.length : 0;
582
+ const holes = Array.isArray(flatPattern.holes) ? flatPattern.holes.length : 0;
583
+ const bends = Array.isArray(flatPattern.bendLines) ? flatPattern.bendLines.length : 0;
584
+ return outlines * 1000 + bends * 10 - holes;
585
+ }
586
+
587
+ function buildFlatPatternEntry(solid, opts = {}, index = 0) {
588
+ const name = solid?.name || `SHEET_${index + 1}`;
589
+ const tryBuild = (localOpts) => {
590
+ const inputs = buildInputsFromSolid(solid, localOpts);
591
+ if (!inputs || !inputs.geometry) return null;
592
+ const options = {
593
+ rootFaceId: localOpts.rootFaceId,
594
+ epsilon: localOpts.epsilon,
595
+ clipExtent: localOpts.clipExtent,
596
+ edgeExclusions: inputs.edgeExclusions,
597
+ };
598
+ const flatPattern = unfoldSheetMetal(inputs.geometry, inputs.bends || [], options);
599
+ const svg = exportFlatPatternToSVG(flatPattern, {
600
+ includeBends: localOpts.includeBends !== false,
601
+ scale: localOpts.scale,
602
+ padding: localOpts.padding,
603
+ flipY: localOpts.flipY,
604
+ });
605
+ return {
606
+ name,
607
+ flatPattern,
608
+ svg,
609
+ warnings: inputs.warnings || [],
610
+ meta: inputs.meta || null,
611
+ };
612
+ };
613
+
614
+ const primary = tryBuild(opts);
615
+ if (!primary) {
616
+ return {
617
+ name,
618
+ flatPattern: null,
619
+ svg: null,
620
+ warnings: ['No flat pattern inputs available for this solid.'],
621
+ };
622
+ }
623
+
624
+ const hasA = primary.meta?.hasA;
625
+ const hasB = primary.meta?.hasB;
626
+ if (opts.forceSide || !hasA || !hasB) return primary;
627
+
628
+ const altSide = primary.meta?.chosenSide === SHEET_METAL_FACE_TYPES.A
629
+ ? SHEET_METAL_FACE_TYPES.B
630
+ : SHEET_METAL_FACE_TYPES.A;
631
+ const alternate = tryBuild({ ...opts, forceSide: altSide });
632
+ if (!alternate) return primary;
633
+
634
+ const scorePrimary = scoreFlatPattern(primary.flatPattern);
635
+ const scoreAlternate = scoreFlatPattern(alternate.flatPattern);
636
+ return scoreAlternate > scorePrimary ? alternate : primary;
637
+ }
638
+
639
+ export function buildSheetMetalFlatPatternSolids(solids, opts = {}) {
640
+ const list = Array.isArray(solids) ? solids : [];
641
+ const entries = [];
642
+ for (let i = 0; i < list.length; i++) {
643
+ const solid = list[i];
644
+ if (!solid || typeof solid.getMesh !== 'function') continue;
645
+ const entry = buildFlatPatternEntry(solid, opts, i);
646
+ if (entry) entries.push(entry);
647
+ }
648
+ return entries;
649
+ }
650
+
651
+ export function buildSheetMetalFlatPatternSvgs(solids, opts = {}) {
652
+ const entries = buildSheetMetalFlatPatternSolids(solids, opts);
653
+ return entries.filter((entry) => !!entry?.svg).map((entry) => ({
654
+ name: entry.name,
655
+ svg: entry.svg,
656
+ flatPattern: entry.flatPattern,
657
+ warnings: entry.warnings,
658
+ }));
659
+ }