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.
- package/LICENSE.md +32 -0
- package/README.md +154 -0
- package/dist-kernel/brep-kernel.js +74699 -0
- package/package.json +58 -0
- package/src/BREP/AssemblyComponent.js +42 -0
- package/src/BREP/BREP.js +43 -0
- package/src/BREP/BetterSolid.js +805 -0
- package/src/BREP/Edge.js +103 -0
- package/src/BREP/Extrude.js +403 -0
- package/src/BREP/Face.js +187 -0
- package/src/BREP/MeshRepairer.js +634 -0
- package/src/BREP/OffsetShellSolid.js +614 -0
- package/src/BREP/PointCloudWrap.js +302 -0
- package/src/BREP/Revolve.js +345 -0
- package/src/BREP/SolidMethods/authoring.js +112 -0
- package/src/BREP/SolidMethods/booleanOps.js +230 -0
- package/src/BREP/SolidMethods/chamfer.js +122 -0
- package/src/BREP/SolidMethods/edgeResolution.js +25 -0
- package/src/BREP/SolidMethods/fillet.js +792 -0
- package/src/BREP/SolidMethods/index.js +72 -0
- package/src/BREP/SolidMethods/io.js +105 -0
- package/src/BREP/SolidMethods/lifecycle.js +103 -0
- package/src/BREP/SolidMethods/manifoldOps.js +375 -0
- package/src/BREP/SolidMethods/meshCleanup.js +2512 -0
- package/src/BREP/SolidMethods/meshQueries.js +264 -0
- package/src/BREP/SolidMethods/metadata.js +106 -0
- package/src/BREP/SolidMethods/metrics.js +51 -0
- package/src/BREP/SolidMethods/transforms.js +361 -0
- package/src/BREP/SolidMethods/visualize.js +508 -0
- package/src/BREP/SolidShared.js +26 -0
- package/src/BREP/Sweep.js +1596 -0
- package/src/BREP/Tube.js +857 -0
- package/src/BREP/Vertex.js +43 -0
- package/src/BREP/applyBooleanOperation.js +704 -0
- package/src/BREP/boundsUtils.js +48 -0
- package/src/BREP/chamfer.js +551 -0
- package/src/BREP/edgePolylineUtils.js +85 -0
- package/src/BREP/fillets/common.js +388 -0
- package/src/BREP/fillets/fillet.js +1422 -0
- package/src/BREP/fillets/filletGeometry.js +15 -0
- package/src/BREP/fillets/inset.js +389 -0
- package/src/BREP/fillets/offsetHelper.js +143 -0
- package/src/BREP/fillets/outset.js +88 -0
- package/src/BREP/helix.js +193 -0
- package/src/BREP/meshToBrep.js +234 -0
- package/src/BREP/primitives.js +279 -0
- package/src/BREP/setupManifold.js +71 -0
- package/src/BREP/threadGeometry.js +1120 -0
- package/src/BREP/triangleUtils.js +8 -0
- package/src/BREP/triangulate.js +608 -0
- package/src/FeatureRegistry.js +183 -0
- package/src/PartHistory.js +1132 -0
- package/src/UI/AccordionWidget.js +292 -0
- package/src/UI/CADmaterials.js +850 -0
- package/src/UI/EnvMonacoEditor.js +522 -0
- package/src/UI/FloatingWindow.js +396 -0
- package/src/UI/HistoryWidget.js +457 -0
- package/src/UI/MainToolbar.js +131 -0
- package/src/UI/ModelLibraryView.js +194 -0
- package/src/UI/OrthoCameraIdle.js +206 -0
- package/src/UI/PluginsWidget.js +280 -0
- package/src/UI/SceneListing.js +606 -0
- package/src/UI/SelectionFilter.js +629 -0
- package/src/UI/ViewCube.js +389 -0
- package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +329 -0
- package/src/UI/assembly/AssemblyConstraintControlsWidget.js +282 -0
- package/src/UI/assembly/AssemblyConstraintsWidget.css +292 -0
- package/src/UI/assembly/AssemblyConstraintsWidget.js +1373 -0
- package/src/UI/assembly/constraintFaceUtils.js +115 -0
- package/src/UI/assembly/constraintHighlightUtils.js +70 -0
- package/src/UI/assembly/constraintLabelUtils.js +31 -0
- package/src/UI/assembly/constraintPointUtils.js +64 -0
- package/src/UI/assembly/constraintSelectionUtils.js +185 -0
- package/src/UI/assembly/constraintStatusUtils.js +142 -0
- package/src/UI/componentSelectorModal.js +240 -0
- package/src/UI/controls/CombinedTransformControls.js +386 -0
- package/src/UI/dialogs.js +351 -0
- package/src/UI/expressionsManager.js +100 -0
- package/src/UI/featureDialogWidgets/booleanField.js +25 -0
- package/src/UI/featureDialogWidgets/booleanOperationField.js +97 -0
- package/src/UI/featureDialogWidgets/buttonField.js +45 -0
- package/src/UI/featureDialogWidgets/componentSelectorField.js +102 -0
- package/src/UI/featureDialogWidgets/defaultField.js +23 -0
- package/src/UI/featureDialogWidgets/fileField.js +66 -0
- package/src/UI/featureDialogWidgets/index.js +34 -0
- package/src/UI/featureDialogWidgets/numberField.js +165 -0
- package/src/UI/featureDialogWidgets/optionsField.js +33 -0
- package/src/UI/featureDialogWidgets/referenceSelectionField.js +208 -0
- package/src/UI/featureDialogWidgets/stringField.js +24 -0
- package/src/UI/featureDialogWidgets/textareaField.js +28 -0
- package/src/UI/featureDialogWidgets/threadDesignationField.js +160 -0
- package/src/UI/featureDialogWidgets/transformField.js +252 -0
- package/src/UI/featureDialogWidgets/utils.js +43 -0
- package/src/UI/featureDialogWidgets/vec3Field.js +133 -0
- package/src/UI/featureDialogs.js +1414 -0
- package/src/UI/fileManagerWidget.js +615 -0
- package/src/UI/history/HistoryCollectionWidget.js +1294 -0
- package/src/UI/history/historyCollectionWidget.css.js +257 -0
- package/src/UI/history/historyDisplayInfo.js +133 -0
- package/src/UI/mobile.js +28 -0
- package/src/UI/objectDump.js +442 -0
- package/src/UI/pmi/AnnotationCollectionWidget.js +120 -0
- package/src/UI/pmi/AnnotationHistory.js +353 -0
- package/src/UI/pmi/AnnotationRegistry.js +90 -0
- package/src/UI/pmi/BaseAnnotation.js +269 -0
- package/src/UI/pmi/LabelOverlay.css +102 -0
- package/src/UI/pmi/LabelOverlay.js +191 -0
- package/src/UI/pmi/PMIMode.js +1550 -0
- package/src/UI/pmi/PMIViewsWidget.js +1098 -0
- package/src/UI/pmi/annUtils.js +729 -0
- package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +647 -0
- package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +507 -0
- package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +462 -0
- package/src/UI/pmi/dimensions/LeaderAnnotation.js +403 -0
- package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +532 -0
- package/src/UI/pmi/dimensions/NoteAnnotation.js +110 -0
- package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +659 -0
- package/src/UI/pmi/pmiStyle.js +44 -0
- package/src/UI/sketcher/SketchMode3D.js +4095 -0
- package/src/UI/sketcher/dimensions.js +674 -0
- package/src/UI/sketcher/glyphs.js +236 -0
- package/src/UI/sketcher/highlights.js +60 -0
- package/src/UI/toolbarButtons/aboutButton.js +5 -0
- package/src/UI/toolbarButtons/exportButton.js +609 -0
- package/src/UI/toolbarButtons/flatPatternButton.js +307 -0
- package/src/UI/toolbarButtons/importButton.js +160 -0
- package/src/UI/toolbarButtons/inspectorToggleButton.js +12 -0
- package/src/UI/toolbarButtons/metadataButton.js +1063 -0
- package/src/UI/toolbarButtons/orientToFaceButton.js +114 -0
- package/src/UI/toolbarButtons/registerDefaultButtons.js +46 -0
- package/src/UI/toolbarButtons/saveButton.js +99 -0
- package/src/UI/toolbarButtons/scriptRunnerButton.js +302 -0
- package/src/UI/toolbarButtons/testsButton.js +26 -0
- package/src/UI/toolbarButtons/undoRedoButtons.js +25 -0
- package/src/UI/toolbarButtons/wireframeToggleButton.js +5 -0
- package/src/UI/toolbarButtons/zoomToFitButton.js +5 -0
- package/src/UI/triangleDebuggerWindow.js +945 -0
- package/src/UI/viewer.js +4228 -0
- package/src/assemblyConstraints/AssemblyConstraintHistory.js +1576 -0
- package/src/assemblyConstraints/AssemblyConstraintRegistry.js +120 -0
- package/src/assemblyConstraints/BaseAssemblyConstraint.js +66 -0
- package/src/assemblyConstraints/constraintExpressionUtils.js +35 -0
- package/src/assemblyConstraints/constraintUtils/parallelAlignment.js +676 -0
- package/src/assemblyConstraints/constraints/AngleConstraint.js +485 -0
- package/src/assemblyConstraints/constraints/CoincidentConstraint.js +194 -0
- package/src/assemblyConstraints/constraints/DistanceConstraint.js +616 -0
- package/src/assemblyConstraints/constraints/FixedConstraint.js +78 -0
- package/src/assemblyConstraints/constraints/ParallelConstraint.js +252 -0
- package/src/assemblyConstraints/constraints/TouchAlignConstraint.js +961 -0
- package/src/core/entities/HistoryCollectionBase.js +72 -0
- package/src/core/entities/ListEntityBase.js +109 -0
- package/src/core/entities/schemaProcesser.js +121 -0
- package/src/exporters/sheetMetalFlatPattern.js +659 -0
- package/src/exporters/sheetMetalUnfold.js +862 -0
- package/src/exporters/step.js +1135 -0
- package/src/exporters/threeMF.js +575 -0
- package/src/features/assemblyComponent/AssemblyComponentFeature.js +780 -0
- package/src/features/boolean/BooleanFeature.js +94 -0
- package/src/features/chamfer/ChamferFeature.js +116 -0
- package/src/features/datium/DatiumFeature.js +80 -0
- package/src/features/edgeFeatureUtils.js +41 -0
- package/src/features/extrude/ExtrudeFeature.js +143 -0
- package/src/features/fillet/FilletFeature.js +197 -0
- package/src/features/helix/HelixFeature.js +405 -0
- package/src/features/hole/HoleFeature.js +1050 -0
- package/src/features/hole/screwClearance.js +86 -0
- package/src/features/hole/threadDesignationCatalog.js +149 -0
- package/src/features/imageHeightSolid/ImageHeightmapSolidFeature.js +463 -0
- package/src/features/imageToFace/ImageToFaceFeature.js +727 -0
- package/src/features/imageToFace/imageEditor.js +1270 -0
- package/src/features/imageToFace/traceUtils.js +971 -0
- package/src/features/import3dModel/Import3dModelFeature.js +151 -0
- package/src/features/loft/LoftFeature.js +605 -0
- package/src/features/mirror/MirrorFeature.js +151 -0
- package/src/features/offsetFace/OffsetFaceFeature.js +370 -0
- package/src/features/offsetShell/OffsetShellFeature.js +89 -0
- package/src/features/overlapCleanup/OverlapCleanupFeature.js +85 -0
- package/src/features/pattern/PatternFeature.js +275 -0
- package/src/features/patternLinear/PatternLinearFeature.js +120 -0
- package/src/features/patternRadial/PatternRadialFeature.js +186 -0
- package/src/features/plane/PlaneFeature.js +154 -0
- package/src/features/primitiveCone/primitiveConeFeature.js +99 -0
- package/src/features/primitiveCube/primitiveCubeFeature.js +70 -0
- package/src/features/primitiveCylinder/primitiveCylinderFeature.js +91 -0
- package/src/features/primitivePyramid/primitivePyramidFeature.js +72 -0
- package/src/features/primitiveSphere/primitiveSphereFeature.js +62 -0
- package/src/features/primitiveTorus/primitiveTorusFeature.js +109 -0
- package/src/features/remesh/RemeshFeature.js +97 -0
- package/src/features/revolve/RevolveFeature.js +111 -0
- package/src/features/selectionUtils.js +118 -0
- package/src/features/sheetMetal/SheetMetalContourFlangeFeature.js +1656 -0
- package/src/features/sheetMetal/SheetMetalCutoutFeature.js +1056 -0
- package/src/features/sheetMetal/SheetMetalFlangeFeature.js +1568 -0
- package/src/features/sheetMetal/SheetMetalHemFeature.js +43 -0
- package/src/features/sheetMetal/SheetMetalObject.js +141 -0
- package/src/features/sheetMetal/SheetMetalTabFeature.js +176 -0
- package/src/features/sheetMetal/UNFOLD_NEUTRAL_REQUIREMENTS.md +153 -0
- package/src/features/sheetMetal/contour-flange-rebuild-spec.md +261 -0
- package/src/features/sheetMetal/profileUtils.js +25 -0
- package/src/features/sheetMetal/sheetMetalCleanup.js +9 -0
- package/src/features/sheetMetal/sheetMetalFaceTypes.js +146 -0
- package/src/features/sheetMetal/sheetMetalMetadata.js +165 -0
- package/src/features/sheetMetal/sheetMetalPipeline.js +169 -0
- package/src/features/sheetMetal/sheetMetalProfileUtils.js +216 -0
- package/src/features/sheetMetal/sheetMetalTabUtils.js +29 -0
- package/src/features/sheetMetal/sheetMetalTree.js +210 -0
- package/src/features/sketch/SketchFeature.js +955 -0
- package/src/features/sketch/sketchSolver2D/ConstraintEngine.js +800 -0
- package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +704 -0
- package/src/features/sketch/sketchSolver2D/mathHelpersMod.js +307 -0
- package/src/features/spline/SplineEditorSession.js +988 -0
- package/src/features/spline/SplineFeature.js +1388 -0
- package/src/features/spline/splineUtils.js +218 -0
- package/src/features/sweep/SweepFeature.js +110 -0
- package/src/features/transform/TransformFeature.js +152 -0
- package/src/features/tube/TubeFeature.js +635 -0
- package/src/fs.proxy.js +625 -0
- package/src/idbStorage.js +254 -0
- package/src/index.js +12 -0
- package/src/main.js +15 -0
- package/src/metadataManager.js +64 -0
- package/src/path.proxy.js +277 -0
- package/src/plugins/ghLoader.worker.js +151 -0
- package/src/plugins/pluginManager.js +286 -0
- package/src/pmi/PMIViewsManager.js +134 -0
- package/src/services/componentLibrary.js +198 -0
- package/src/tests/ConsoleCapture.js +189 -0
- package/src/tests/S7-diagnostics-2025-12-23T18-37-23-570Z.json +630 -0
- package/src/tests/browserTests.js +597 -0
- package/src/tests/debugBoolean.js +225 -0
- package/src/tests/partFiles/badBoolean.json +957 -0
- package/src/tests/partFiles/extrudeTest.json +88 -0
- package/src/tests/partFiles/filletFail.json +58 -0
- package/src/tests/partFiles/import_TEst.part.part.json +646 -0
- package/src/tests/partFiles/sheetMetalHem.BREP.json +734 -0
- package/src/tests/test_boolean_subtract.js +27 -0
- package/src/tests/test_chamfer.js +17 -0
- package/src/tests/test_extrudeFace.js +24 -0
- package/src/tests/test_fillet.js +17 -0
- package/src/tests/test_fillet_nonClosed.js +45 -0
- package/src/tests/test_filletsMoreDifficult.js +46 -0
- package/src/tests/test_history_features_basic.js +149 -0
- package/src/tests/test_hole.js +282 -0
- package/src/tests/test_mirror.js +16 -0
- package/src/tests/test_offsetShellGrouping.js +85 -0
- package/src/tests/test_plane.js +4 -0
- package/src/tests/test_primitiveCone.js +11 -0
- package/src/tests/test_primitiveCube.js +7 -0
- package/src/tests/test_primitiveCylinder.js +8 -0
- package/src/tests/test_primitivePyramid.js +9 -0
- package/src/tests/test_primitiveSphere.js +17 -0
- package/src/tests/test_primitiveTorus.js +21 -0
- package/src/tests/test_pushFace.js +126 -0
- package/src/tests/test_sheetMetalContourFlange.js +125 -0
- package/src/tests/test_sheetMetal_features.js +80 -0
- package/src/tests/test_sketch_openLoop.js +45 -0
- package/src/tests/test_solidMetrics.js +58 -0
- package/src/tests/test_stlLoader.js +1889 -0
- package/src/tests/test_sweepFace.js +55 -0
- package/src/tests/test_tube.js +45 -0
- package/src/tests/test_tube_closedLoop.js +67 -0
- package/src/tests/tests.js +493 -0
- package/src/tools/assemblyConstraintDialogCapturePage.js +56 -0
- package/src/tools/dialogCapturePageFactory.js +227 -0
- package/src/tools/featureDialogCapturePage.js +47 -0
- package/src/tools/pmiAnnotationDialogCapturePage.js +60 -0
- package/src/utils/axisHelpers.js +99 -0
- package/src/utils/deepClone.js +69 -0
- package/src/utils/geometryTolerance.js +37 -0
- package/src/utils/normalizeTypeString.js +8 -0
- package/src/utils/xformMath.js +51 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export { constructorImpl, clone, free } from "./lifecycle.js";
|
|
2
|
+
export {
|
|
3
|
+
_key,
|
|
4
|
+
_getPointIndex,
|
|
5
|
+
_getOrCreateID,
|
|
6
|
+
addTriangle,
|
|
7
|
+
addAuxEdge,
|
|
8
|
+
addCenterline,
|
|
9
|
+
} from "./authoring.js";
|
|
10
|
+
export {
|
|
11
|
+
setFaceMetadata,
|
|
12
|
+
getFaceMetadata,
|
|
13
|
+
getFaceNames,
|
|
14
|
+
renameFace,
|
|
15
|
+
_combineFaceMetadata,
|
|
16
|
+
setEdgeMetadata,
|
|
17
|
+
getEdgeMetadata,
|
|
18
|
+
_combineEdgeMetadata,
|
|
19
|
+
} from "./metadata.js";
|
|
20
|
+
export {
|
|
21
|
+
bakeTransform,
|
|
22
|
+
bakeTRS,
|
|
23
|
+
offsetFace,
|
|
24
|
+
mirrorAcrossPlane,
|
|
25
|
+
pushFace,
|
|
26
|
+
} from "./transforms.js";
|
|
27
|
+
export {
|
|
28
|
+
_manifoldize,
|
|
29
|
+
setEpsilon,
|
|
30
|
+
_weldVerticesByEpsilon,
|
|
31
|
+
fixTriangleWindingsByAdjacency,
|
|
32
|
+
_isCoherentlyOrientedManifold,
|
|
33
|
+
invertNormals,
|
|
34
|
+
} from "./manifoldOps.js";
|
|
35
|
+
export {
|
|
36
|
+
removeSmallIslands,
|
|
37
|
+
removeSmallInternalIslands,
|
|
38
|
+
removeOppositeSingleEdgeFaces,
|
|
39
|
+
removeTinyBoundaryTriangles,
|
|
40
|
+
collapseTinyTriangles,
|
|
41
|
+
cleanupTinyFaceIslands,
|
|
42
|
+
remesh,
|
|
43
|
+
splitSelfIntersectingTriangles,
|
|
44
|
+
removeDegenerateTriangles,
|
|
45
|
+
removeInternalTriangles,
|
|
46
|
+
removeInternalTrianglesByRaycast,
|
|
47
|
+
removeInternalTrianglesByWinding,
|
|
48
|
+
mergeTinyFaces,
|
|
49
|
+
} from "./meshCleanup.js";
|
|
50
|
+
export {
|
|
51
|
+
getMesh,
|
|
52
|
+
_ensureFaceIndex,
|
|
53
|
+
getFace,
|
|
54
|
+
getFaces,
|
|
55
|
+
getBoundaryEdgePolylines,
|
|
56
|
+
} from "./meshQueries.js";
|
|
57
|
+
export {
|
|
58
|
+
union,
|
|
59
|
+
subtract,
|
|
60
|
+
intersect,
|
|
61
|
+
difference,
|
|
62
|
+
_combineIdMaps,
|
|
63
|
+
_expandTriIDsFromMesh as _expandTriIDsFromMeshStatic,
|
|
64
|
+
_fromManifold as _fromManifoldStatic,
|
|
65
|
+
setTolerance,
|
|
66
|
+
simplify,
|
|
67
|
+
} from "./booleanOps.js";
|
|
68
|
+
export { toSTL, writeSTL, toSTEP, writeSTEP } from "./io.js";
|
|
69
|
+
export { volume, surfaceArea, getTriangleCount } from "./metrics.js";
|
|
70
|
+
export { visualize } from "./visualize.js";
|
|
71
|
+
export { fillet } from "./fillet.js";
|
|
72
|
+
export { chamfer } from "./chamfer.js";
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { generateSTEP } from '../../exporters/step.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Export helpers (STL + STEP output).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function toSTL(name = "solid", precision = 6) {
|
|
8
|
+
const mesh = this.getMesh();
|
|
9
|
+
const { vertProperties, triVerts } = mesh;
|
|
10
|
+
|
|
11
|
+
const fmt = (n) => Number.isFinite(n) ? n.toFixed(precision) : "0";
|
|
12
|
+
const parts = [];
|
|
13
|
+
parts.push(`solid ${name}`);
|
|
14
|
+
|
|
15
|
+
const triCount = (triVerts.length / 3) | 0;
|
|
16
|
+
for (let t = 0; t < triCount; t++) {
|
|
17
|
+
const i0 = triVerts[t * 3 + 0];
|
|
18
|
+
const i1 = triVerts[t * 3 + 1];
|
|
19
|
+
const i2 = triVerts[t * 3 + 2];
|
|
20
|
+
|
|
21
|
+
const p0 = [
|
|
22
|
+
vertProperties[i0 * 3 + 0],
|
|
23
|
+
vertProperties[i0 * 3 + 1],
|
|
24
|
+
vertProperties[i0 * 3 + 2],
|
|
25
|
+
];
|
|
26
|
+
const p1 = [
|
|
27
|
+
vertProperties[i1 * 3 + 0],
|
|
28
|
+
vertProperties[i1 * 3 + 1],
|
|
29
|
+
vertProperties[i1 * 3 + 2],
|
|
30
|
+
];
|
|
31
|
+
const p2 = [
|
|
32
|
+
vertProperties[i2 * 3 + 0],
|
|
33
|
+
vertProperties[i2 * 3 + 1],
|
|
34
|
+
vertProperties[i2 * 3 + 2],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const ux = p1[0] - p0[0];
|
|
38
|
+
const uy = p1[1] - p0[1];
|
|
39
|
+
const uz = p1[2] - p0[2];
|
|
40
|
+
const vx = p2[0] - p0[0];
|
|
41
|
+
const vy = p2[1] - p0[1];
|
|
42
|
+
const vz = p2[2] - p0[2];
|
|
43
|
+
let nx = uy * vz - uz * vy;
|
|
44
|
+
let ny = uz * vx - ux * vz;
|
|
45
|
+
let nz = ux * vy - uy * vx;
|
|
46
|
+
const nl = Math.hypot(nx, ny, nz) || 1;
|
|
47
|
+
nx /= nl; ny /= nl; nz /= nl;
|
|
48
|
+
|
|
49
|
+
parts.push(` facet normal ${fmt(nx)} ${fmt(ny)} ${fmt(nz)}`);
|
|
50
|
+
parts.push(` outer loop`);
|
|
51
|
+
parts.push(` vertex ${fmt(p0[0])} ${fmt(p0[1])} ${fmt(p0[2])}`);
|
|
52
|
+
parts.push(` vertex ${fmt(p1[0])} ${fmt(p1[1])} ${fmt(p1[2])}`);
|
|
53
|
+
parts.push(` vertex ${fmt(p2[0])} ${fmt(p2[1])} ${fmt(p2[2])}`);
|
|
54
|
+
parts.push(` endloop`);
|
|
55
|
+
parts.push(` endfacet`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
parts.push(`endsolid ${name}`);
|
|
59
|
+
try { return parts.join("\n"); } finally { try { if (mesh && typeof mesh.delete === 'function') mesh.delete(); } catch { } }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function writeSTL(filePath, name = "solid", precision = 6) {
|
|
63
|
+
if (typeof window !== "undefined") {
|
|
64
|
+
throw new Error("writeSTL is only available in Node.js environments");
|
|
65
|
+
}
|
|
66
|
+
const { writeFile } = await import('node:fs/promises');
|
|
67
|
+
const stl = this.toSTL(name, precision);
|
|
68
|
+
await writeFile(filePath, stl, 'utf8');
|
|
69
|
+
return filePath;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate a triangulated STEP (faceted BREP) string for this solid.
|
|
74
|
+
* @param {string} [name=this.name||'part']
|
|
75
|
+
* @param {{unit?: string, precision?: number, scale?: number, applyWorldTransform?: boolean, useTessellatedFaces?: boolean}} [options]
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
export function toSTEP(name = undefined, options = {}) {
|
|
79
|
+
const opts = (options && typeof options === 'object') ? options : {};
|
|
80
|
+
const unit = opts.unit || 'millimeter';
|
|
81
|
+
const precision = Number.isFinite(opts.precision) ? opts.precision : 6;
|
|
82
|
+
const scale = Number.isFinite(opts.scale) ? opts.scale : 1;
|
|
83
|
+
const applyWorldTransform = opts.applyWorldTransform !== false;
|
|
84
|
+
const baseName = name || this?.name || 'part';
|
|
85
|
+
const stepOpts = { ...opts, name: baseName, unit, precision, scale, applyWorldTransform };
|
|
86
|
+
const { data } = generateSTEP([this], stepOpts);
|
|
87
|
+
return data;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Write a triangulated STEP file to disk (Node.js only).
|
|
92
|
+
* @param {string} filePath
|
|
93
|
+
* @param {string} [name=this.name||'part']
|
|
94
|
+
* @param {{unit?: string, precision?: number, scale?: number, applyWorldTransform?: boolean, useTessellatedFaces?: boolean}} [options]
|
|
95
|
+
* @returns {Promise<string>} resolves with file path
|
|
96
|
+
*/
|
|
97
|
+
export async function writeSTEP(filePath, name = undefined, options = {}) {
|
|
98
|
+
if (typeof window !== "undefined") {
|
|
99
|
+
throw new Error("writeSTEP is only available in Node.js environments");
|
|
100
|
+
}
|
|
101
|
+
const { writeFile } = await import('node:fs/promises');
|
|
102
|
+
const step = this.toSTEP(name, options);
|
|
103
|
+
await writeFile(filePath, step, 'utf8');
|
|
104
|
+
return filePath;
|
|
105
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solid lifecycle helpers: constructor, cloning, resource cleanup.
|
|
3
|
+
*/
|
|
4
|
+
export function constructorImpl() {
|
|
5
|
+
// Geometry data (MeshGL layout, but we build incrementally in JS arrays)
|
|
6
|
+
this._numProp = 3; // x,y,z
|
|
7
|
+
this._vertProperties = []; // flat [x0,y0,z0, x1,y1,z1, ...]
|
|
8
|
+
this._triVerts = []; // flat [i0,i1,i2, i3,i4,i5, ...]
|
|
9
|
+
this._triIDs = []; // per-triangle Manifold ID (mapped from faceName)
|
|
10
|
+
|
|
11
|
+
// Vertex uniquing
|
|
12
|
+
this._vertKeyToIndex = new Map(); // "x,y,z" -> index
|
|
13
|
+
|
|
14
|
+
// Face name <-> Manifold ID
|
|
15
|
+
this._faceNameToID = new Map();
|
|
16
|
+
this._idToFaceName = new Map();
|
|
17
|
+
|
|
18
|
+
// Face and edge metadata storage
|
|
19
|
+
this._faceMetadata = new Map(); // faceName -> metadata object
|
|
20
|
+
this._edgeMetadata = new Map(); // edgeName -> metadata object
|
|
21
|
+
|
|
22
|
+
// Laziness & caching
|
|
23
|
+
this._dirty = true; // arrays changed and manifold needs rebuild
|
|
24
|
+
this._manifold = null; // cached Manifold object built from arrays
|
|
25
|
+
this._faceIndex = null; // lazy cache: id -> [triIndices]
|
|
26
|
+
this._epsilon = 0; // optional vertex weld tolerance (off by default)
|
|
27
|
+
this._freeTimer = null; // handle for scheduled wasm cleanup
|
|
28
|
+
|
|
29
|
+
this.type = 'SOLID';
|
|
30
|
+
this.renderOrder = 1;
|
|
31
|
+
// Custom auxiliary edges (e.g., centerlines) to visualize with this solid
|
|
32
|
+
// Each item: { name?:string, points:[[x,y,z],...], closedLoop?:boolean, polylineWorld?:boolean, materialKey?:'OVERLAY'|'BASE', centerline?:boolean }
|
|
33
|
+
this._auxEdges = [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a lightweight clone of this Solid that copies geometry arrays
|
|
38
|
+
* and face maps, but not children or any THREE resources.
|
|
39
|
+
*/
|
|
40
|
+
export function clone() {
|
|
41
|
+
const Solid = this.constructor;
|
|
42
|
+
const s = new Solid();
|
|
43
|
+
s._numProp = this._numProp;
|
|
44
|
+
s._vertProperties = this._vertProperties.slice();
|
|
45
|
+
s._triVerts = this._triVerts.slice();
|
|
46
|
+
s._triIDs = this._triIDs.slice();
|
|
47
|
+
s._vertKeyToIndex = new Map();
|
|
48
|
+
for (let i = 0; i < s._vertProperties.length; i += 3) {
|
|
49
|
+
const x = s._vertProperties[i];
|
|
50
|
+
const y = s._vertProperties[i + 1];
|
|
51
|
+
const z = s._vertProperties[i + 2];
|
|
52
|
+
s._vertKeyToIndex.set(`${x},${y},${z}`, (i / 3) | 0);
|
|
53
|
+
}
|
|
54
|
+
// Copy face name maps
|
|
55
|
+
try {
|
|
56
|
+
s._idToFaceName = new Map(this._idToFaceName);
|
|
57
|
+
s._faceNameToID = new Map(this._faceNameToID);
|
|
58
|
+
} catch (_) { /* ignore */ }
|
|
59
|
+
try { s._faceMetadata = new Map(this._faceMetadata); } catch (_) { s._faceMetadata = new Map(); }
|
|
60
|
+
try { s._edgeMetadata = new Map(this._edgeMetadata); } catch (_) { s._edgeMetadata = new Map(); }
|
|
61
|
+
// Copy auxiliary edges (deep copy points)
|
|
62
|
+
try {
|
|
63
|
+
s._auxEdges = Array.isArray(this._auxEdges)
|
|
64
|
+
? this._auxEdges.map(e => ({
|
|
65
|
+
name: e?.name,
|
|
66
|
+
closedLoop: !!e?.closedLoop,
|
|
67
|
+
polylineWorld: !!e?.polylineWorld,
|
|
68
|
+
materialKey: e?.materialKey,
|
|
69
|
+
centerline: !!e?.centerline,
|
|
70
|
+
points: Array.isArray(e?.points) ? e.points.map(p => Array.isArray(p) ? [p[0], p[1], p[2]] : p) : [],
|
|
71
|
+
}))
|
|
72
|
+
: [];
|
|
73
|
+
} catch { s._auxEdges = []; }
|
|
74
|
+
s._dirty = true;
|
|
75
|
+
s._manifold = null;
|
|
76
|
+
s._faceIndex = null;
|
|
77
|
+
s.type = 'SOLID';
|
|
78
|
+
s.renderOrder = this.renderOrder;
|
|
79
|
+
return s;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Free wasm resources associated with this Solid.
|
|
84
|
+
*
|
|
85
|
+
* Disposes the underlying Manifold instance (if any) to prevent
|
|
86
|
+
* accumulating wasm memory across rebuilds. After calling free(),
|
|
87
|
+
* the Solid remains usable - any subsequent call that needs the
|
|
88
|
+
* manifold will trigger a fresh _manifoldize().
|
|
89
|
+
*/
|
|
90
|
+
export function free() {
|
|
91
|
+
try {
|
|
92
|
+
// Clear any pending auto-free timer first
|
|
93
|
+
try { if (this._freeTimer) { clearTimeout(this._freeTimer); } } catch (_) { }
|
|
94
|
+
this._freeTimer = null;
|
|
95
|
+
if (this._manifold) {
|
|
96
|
+
try { if (typeof this._manifold.delete === 'function') this._manifold.delete(); } catch (_) { }
|
|
97
|
+
this._manifold = null;
|
|
98
|
+
}
|
|
99
|
+
this._dirty = true;
|
|
100
|
+
this._faceIndex = null;
|
|
101
|
+
} catch (_) { /* noop */ }
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { Manifold, ManifoldMesh, debugMode } from "../SolidShared.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Manifold lifecycle helpers: rebuild, welding, orientation fixes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build (or rebuild) the Manifold from our MeshGL arrays.
|
|
9
|
+
* Uses faceID per triangle so face names survive CSG operations.
|
|
10
|
+
*/
|
|
11
|
+
export function _manifoldize() {
|
|
12
|
+
// Measure timing for manifoldization (cache hits vs rebuilds)
|
|
13
|
+
const nowMs = () => (typeof performance !== 'undefined' && performance?.now ? performance.now() : Date.now());
|
|
14
|
+
const __t0 = nowMs();
|
|
15
|
+
// Reset the auto-free timer: always schedule cleanup 60s after last use
|
|
16
|
+
try { if (this._freeTimer) { clearTimeout(this._freeTimer); } } catch { }
|
|
17
|
+
try {
|
|
18
|
+
this._freeTimer = setTimeout(() => {
|
|
19
|
+
try { this.free(); } catch { }
|
|
20
|
+
}, 60 * 1000);
|
|
21
|
+
} catch { }
|
|
22
|
+
if (!this._dirty && this._manifold) {
|
|
23
|
+
const __t1 = nowMs();
|
|
24
|
+
try { if (debugMode) console.log(`[Solid] _manifoldize cache-hit in ${Math.round(__t1 - __t0)} ms`); } catch { }
|
|
25
|
+
return this._manifold;
|
|
26
|
+
}
|
|
27
|
+
let __logged = false;
|
|
28
|
+
const __logDone = (ok = true) => {
|
|
29
|
+
if (__logged) return; __logged = true;
|
|
30
|
+
const __t1 = nowMs();
|
|
31
|
+
const triCountDbg = (this?._triVerts?.length || 0) / 3 | 0;
|
|
32
|
+
const vertCountDbg = (this?._vertProperties?.length || 0) / 3 | 0;
|
|
33
|
+
try {
|
|
34
|
+
if (debugMode) console.log(`[Solid] _manifoldize ${ok ? 'built' : 'failed'} in ${Math.round(__t1 - __t0)} ms (tris=${triCountDbg}, verts=${vertCountDbg})`);
|
|
35
|
+
} catch { }
|
|
36
|
+
};
|
|
37
|
+
try {
|
|
38
|
+
// Ensure consistent orientation before building a Manifold
|
|
39
|
+
this.fixTriangleWindingsByAdjacency();
|
|
40
|
+
// Ensure outward orientation (positive signed volume). If negative, flip all tris.
|
|
41
|
+
const signedVolume = (() => {
|
|
42
|
+
const vp = this._vertProperties;
|
|
43
|
+
let vol6 = 0; // 6 * volume
|
|
44
|
+
for (let t = 0; t < this._triVerts.length; t += 3) {
|
|
45
|
+
const i0 = this._triVerts[t], i1 = this._triVerts[t + 1], i2 = this._triVerts[t + 2];
|
|
46
|
+
const x0 = vp[i0 * 3], y0 = vp[i0 * 3 + 1], z0 = vp[i0 * 3 + 2];
|
|
47
|
+
const x1 = vp[i1 * 3], y1 = vp[i1 * 3 + 1], z1 = vp[i1 * 3 + 2];
|
|
48
|
+
const x2 = vp[i2 * 3], y2 = vp[i2 * 3 + 1], z2 = vp[i2 * 3 + 2];
|
|
49
|
+
// triple product p0 · (p1 × p2)
|
|
50
|
+
vol6 += x0 * (y1 * z2 - z1 * y2) - y0 * (x1 * z2 - z1 * x2) + z0 * (x1 * y2 - y1 * x2);
|
|
51
|
+
}
|
|
52
|
+
return vol6 / 6.0;
|
|
53
|
+
})();
|
|
54
|
+
if (signedVolume < 0) {
|
|
55
|
+
for (let t = 0; t < this._triVerts.length; t += 3) {
|
|
56
|
+
// swap indices 1 and 2 to flip triangle
|
|
57
|
+
const tmp = this._triVerts[t + 1];
|
|
58
|
+
this._triVerts[t + 1] = this._triVerts[t + 2];
|
|
59
|
+
this._triVerts[t + 2] = tmp;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const triCount = (this._triVerts.length / 3) | 0;
|
|
64
|
+
const triVerts = new Uint32Array(this._triVerts);
|
|
65
|
+
const faceID = new Uint32Array(triCount);
|
|
66
|
+
for (let t = 0; t < triCount; t++) faceID[t] = this._triIDs[t];
|
|
67
|
+
|
|
68
|
+
const mesh = new ManifoldMesh({
|
|
69
|
+
numProp: this._numProp,
|
|
70
|
+
vertProperties: new Float32Array(this._vertProperties),
|
|
71
|
+
triVerts,
|
|
72
|
+
faceID,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Fill mergeFromVert/mergeToVert; positions and indices stay intact.
|
|
76
|
+
mesh.merge();
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
this._manifold = new Manifold(mesh);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
// If this Solid is a FilletSolid (identified by presence of edgeToFillet),
|
|
82
|
+
// emit a structured JSON log with diagnostic context for debugging.
|
|
83
|
+
try {
|
|
84
|
+
if (this && Object.prototype.hasOwnProperty.call(this, 'edgeToFillet')) {
|
|
85
|
+
const triCountInfo = (this._triVerts?.length || 0) / 3 | 0;
|
|
86
|
+
const vertCountInfo = (this._vertProperties?.length || 0) / 3 | 0;
|
|
87
|
+
const faces = [];
|
|
88
|
+
try {
|
|
89
|
+
if (this.edgeToFillet && Array.isArray(this.edgeToFillet.faces)) {
|
|
90
|
+
for (const f of this.edgeToFillet.faces) if (f && f.name) faces.push(f.name);
|
|
91
|
+
}
|
|
92
|
+
} catch { }
|
|
93
|
+
const failure = {
|
|
94
|
+
type: 'FilletSolidManifoldFailure',
|
|
95
|
+
message: (err && (err.message || String(err))) || 'unknown',
|
|
96
|
+
params: {
|
|
97
|
+
radius: this.radius,
|
|
98
|
+
arcSegments: this.arcSegments,
|
|
99
|
+
sampleCount: this.sampleCount,
|
|
100
|
+
sideMode: this.sideMode,
|
|
101
|
+
inflate: this.inflate,
|
|
102
|
+
sideStripSubdiv: this.sideStripSubdiv,
|
|
103
|
+
seamInsetScale: this.seamInsetScale,
|
|
104
|
+
projectStripsOpenEdges: this.projectStripsOpenEdges,
|
|
105
|
+
forceSeamInset: this.forceSeamInset,
|
|
106
|
+
},
|
|
107
|
+
edge: {
|
|
108
|
+
name: this.edgeToFillet?.name || null,
|
|
109
|
+
closedLoop: !!(this.edgeToFillet?.closedLoop || this.edgeToFillet?.userData?.closedLoop),
|
|
110
|
+
faces,
|
|
111
|
+
},
|
|
112
|
+
counts: {
|
|
113
|
+
vertices: vertCountInfo,
|
|
114
|
+
triangles: triCountInfo,
|
|
115
|
+
faceLabels: (this._faceNameToID && typeof this._faceNameToID.size === 'number') ? this._faceNameToID.size : undefined,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
try { console.error(JSON.stringify(failure)); } catch { console.error('[FilletSolidManifoldFailure]', failure.message); }
|
|
119
|
+
}
|
|
120
|
+
} catch { }
|
|
121
|
+
__logDone(false);
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
try { if (mesh && typeof mesh.delete === 'function') mesh.delete(); } catch { }
|
|
126
|
+
}
|
|
127
|
+
this._dirty = false;
|
|
128
|
+
this._faceIndex = null; // will rebuild on demand
|
|
129
|
+
__logDone(true);
|
|
130
|
+
return this._manifold;
|
|
131
|
+
} finally {
|
|
132
|
+
// In case of unexpected control flow, ensure we log once with best-effort status.
|
|
133
|
+
const ok = !!(this && this._manifold) && this._dirty === false;
|
|
134
|
+
__logDone(ok);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Set vertex weld epsilon and optionally weld existing vertices and
|
|
140
|
+
* remove degenerate triangles. Epsilon <= 0 disables welding.
|
|
141
|
+
*/
|
|
142
|
+
export function setEpsilon(epsilon = 0) {
|
|
143
|
+
this._epsilon = Number(epsilon) || 0;
|
|
144
|
+
if (this._epsilon > 0) {
|
|
145
|
+
this._weldVerticesByEpsilon(this._epsilon);
|
|
146
|
+
}
|
|
147
|
+
// After adjusting vertices, attempt to correct triangle winding.
|
|
148
|
+
this.fixTriangleWindingsByAdjacency();
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function _weldVerticesByEpsilon(eps) {
|
|
153
|
+
const vp = this._vertProperties;
|
|
154
|
+
const nv = (vp.length / 3) | 0;
|
|
155
|
+
if (nv === 0) return;
|
|
156
|
+
|
|
157
|
+
const toCell = (x) => Math.round(x / eps);
|
|
158
|
+
const cellMap = new Map(); // cellKey -> representative vert index
|
|
159
|
+
const repOf = new Uint32Array(nv);
|
|
160
|
+
for (let i = 0; i < nv; i++) repOf[i] = i;
|
|
161
|
+
|
|
162
|
+
// Find representative for each vertex by grid hashing
|
|
163
|
+
for (let i = 0; i < nv; i++) {
|
|
164
|
+
const x = vp[i * 3 + 0];
|
|
165
|
+
const y = vp[i * 3 + 1];
|
|
166
|
+
const z = vp[i * 3 + 2];
|
|
167
|
+
const cx = toCell(x), cy = toCell(y), cz = toCell(z);
|
|
168
|
+
const key = `${cx},${cy},${cz}`;
|
|
169
|
+
const rep = cellMap.get(key);
|
|
170
|
+
if (rep === undefined) {
|
|
171
|
+
cellMap.set(key, i);
|
|
172
|
+
repOf[i] = i;
|
|
173
|
+
} else {
|
|
174
|
+
repOf[i] = rep;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Remap triangles to representative indices and drop degenerate/zero-area
|
|
179
|
+
const newTriVerts = [];
|
|
180
|
+
const newTriIDs = [];
|
|
181
|
+
const used = new Uint8Array(nv); // mark used reps
|
|
182
|
+
const area2Thresh = 0; // strict degenerate check
|
|
183
|
+
for (let t = 0; t < this._triVerts.length; t += 3) {
|
|
184
|
+
const a = repOf[this._triVerts[t + 0]];
|
|
185
|
+
const b = repOf[this._triVerts[t + 1]];
|
|
186
|
+
const c = repOf[this._triVerts[t + 2]];
|
|
187
|
+
if (a === b || b === c || c === a) continue; // collapsed
|
|
188
|
+
// Compute area^2 to filter near-degenerates
|
|
189
|
+
const ax = vp[a * 3], ay = vp[a * 3 + 1], az = vp[a * 3 + 2];
|
|
190
|
+
const bx = vp[b * 3], by = vp[b * 3 + 1], bz = vp[b * 3 + 2];
|
|
191
|
+
const cx = vp[c * 3], cy = vp[c * 3 + 1], cz = vp[c * 3 + 2];
|
|
192
|
+
const ux = bx - ax, uy = by - ay, uz = bz - az;
|
|
193
|
+
const vx = cx - ax, vy = cy - ay, vz = cz - az;
|
|
194
|
+
const nx = uy * vz - uz * vy;
|
|
195
|
+
const ny = uz * vx - ux * vz;
|
|
196
|
+
const nz = ux * vy - uy * vx;
|
|
197
|
+
const area2 = nx * nx + ny * ny + nz * nz;
|
|
198
|
+
if (area2 <= area2Thresh) continue;
|
|
199
|
+
const triIdx = (t / 3) | 0;
|
|
200
|
+
newTriVerts.push(a, b, c);
|
|
201
|
+
newTriIDs.push(this._triIDs[triIdx]);
|
|
202
|
+
used[a] = 1; used[b] = 1; used[c] = 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// If nothing changed, bail
|
|
206
|
+
if (newTriVerts.length === this._triVerts.length && newTriIDs.length === this._triIDs.length) return;
|
|
207
|
+
|
|
208
|
+
// Build compacted vertex buffer and remap indices
|
|
209
|
+
const oldToNew = new Int32Array(nv);
|
|
210
|
+
for (let i = 0; i < nv; i++) oldToNew[i] = -1;
|
|
211
|
+
const newVerts = [];
|
|
212
|
+
let write = 0;
|
|
213
|
+
for (let i = 0; i < nv; i++) {
|
|
214
|
+
if (!used[i]) continue;
|
|
215
|
+
oldToNew[i] = write++;
|
|
216
|
+
newVerts.push(vp[i * 3 + 0], vp[i * 3 + 1], vp[i * 3 + 2]);
|
|
217
|
+
}
|
|
218
|
+
for (let k = 0; k < newTriVerts.length; k++) {
|
|
219
|
+
newTriVerts[k] = oldToNew[newTriVerts[k]];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Commit
|
|
223
|
+
this._vertProperties = newVerts;
|
|
224
|
+
this._triVerts = newTriVerts;
|
|
225
|
+
this._triIDs = newTriIDs;
|
|
226
|
+
this._vertKeyToIndex = new Map();
|
|
227
|
+
for (let i = 0; i < this._vertProperties.length; i += 3) {
|
|
228
|
+
const x = this._vertProperties[i], y = this._vertProperties[i + 1], z = this._vertProperties[i + 2];
|
|
229
|
+
this._vertKeyToIndex.set(`${x},${y},${z}`, (i / 3) | 0);
|
|
230
|
+
}
|
|
231
|
+
this._dirty = true;
|
|
232
|
+
this._faceIndex = null;
|
|
233
|
+
this._manifoldize();
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Ensures all triangles have consistent winding by making sure
|
|
239
|
+
* shared edges are oriented oppositely between adjacent triangles.
|
|
240
|
+
*/
|
|
241
|
+
export function fixTriangleWindingsByAdjacency() {
|
|
242
|
+
if (this._isCoherentlyOrientedManifold()) return;
|
|
243
|
+
const triCount = (this._triVerts.length / 3) | 0;
|
|
244
|
+
if (triCount === 0) return;
|
|
245
|
+
|
|
246
|
+
const tris = new Array(triCount);
|
|
247
|
+
for (let t = 0; t < triCount; t++) {
|
|
248
|
+
const base = t * 3;
|
|
249
|
+
tris[t] = [
|
|
250
|
+
this._triVerts[base + 0],
|
|
251
|
+
this._triVerts[base + 1],
|
|
252
|
+
this._triVerts[base + 2],
|
|
253
|
+
];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const undirected = new Map();
|
|
257
|
+
const numVerts = (this._vertProperties.length / 3) | 0;
|
|
258
|
+
const NV = BigInt(numVerts);
|
|
259
|
+
const ukey = (a, b) => {
|
|
260
|
+
const A = BigInt(a);
|
|
261
|
+
const B = BigInt(b);
|
|
262
|
+
return A < B ? A * NV + B : B * NV + A;
|
|
263
|
+
};
|
|
264
|
+
for (let ti = 0; ti < tris.length; ti++) {
|
|
265
|
+
const tri = tris[ti];
|
|
266
|
+
for (let e = 0; e < 3; e++) {
|
|
267
|
+
const a = tri[e];
|
|
268
|
+
const b = tri[(e + 1) % 3];
|
|
269
|
+
const k = ukey(a, b);
|
|
270
|
+
let arr = undirected.get(k);
|
|
271
|
+
if (!arr) {
|
|
272
|
+
arr = [];
|
|
273
|
+
undirected.set(k, arr);
|
|
274
|
+
}
|
|
275
|
+
arr.push({ tri: ti, a, b }); // oriented edge as appears in the triangle
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const visited = new Array(triCount).fill(false);
|
|
280
|
+
const stack = [];
|
|
281
|
+
|
|
282
|
+
for (let seed = 0; seed < triCount; seed++) {
|
|
283
|
+
if (visited[seed]) continue;
|
|
284
|
+
visited[seed] = true;
|
|
285
|
+
stack.push(seed);
|
|
286
|
+
|
|
287
|
+
while (stack.length) {
|
|
288
|
+
const t = stack.pop();
|
|
289
|
+
const tri = tris[t];
|
|
290
|
+
for (let e = 0; e < 3; e++) {
|
|
291
|
+
const a = tri[e];
|
|
292
|
+
const b = tri[(e + 1) % 3];
|
|
293
|
+
const k = ukey(a, b);
|
|
294
|
+
const adj = undirected.get(k);
|
|
295
|
+
if (!adj || adj.length < 2) continue; // boundary or non-manifold; skip
|
|
296
|
+
|
|
297
|
+
for (const entry of adj) {
|
|
298
|
+
const n = entry.tri;
|
|
299
|
+
if (n === t || visited[n]) continue;
|
|
300
|
+
|
|
301
|
+
const nTri = tris[n];
|
|
302
|
+
if (entry.a === a && entry.b === b) {
|
|
303
|
+
[nTri[1], nTri[2]] = [nTri[2], nTri[1]];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
visited[n] = true;
|
|
307
|
+
stack.push(n);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
this._triVerts.length = 0;
|
|
314
|
+
for (const tri of tris) {
|
|
315
|
+
this._triVerts.push(tri[0], tri[1], tri[2]);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
this._dirty = true;
|
|
319
|
+
this._faceIndex = null;
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Return true if every undirected edge is shared by exactly 2 triangles
|
|
324
|
+
// and their directed usages are opposite.
|
|
325
|
+
export function _isCoherentlyOrientedManifold() {
|
|
326
|
+
const triCount = (this._triVerts.length / 3) | 0;
|
|
327
|
+
if (triCount === 0) return false;
|
|
328
|
+
const numVerts = (this._vertProperties.length / 3) | 0;
|
|
329
|
+
const NV = BigInt(numVerts);
|
|
330
|
+
const ukey = (a, b) => {
|
|
331
|
+
const A = BigInt(a);
|
|
332
|
+
const B = BigInt(b);
|
|
333
|
+
return A < B ? A * NV + B : B * NV + A;
|
|
334
|
+
};
|
|
335
|
+
const edgeMap = new Map();
|
|
336
|
+
for (let t = 0; t < triCount; t++) {
|
|
337
|
+
const b = t * 3;
|
|
338
|
+
const i0 = this._triVerts[b + 0];
|
|
339
|
+
const i1 = this._triVerts[b + 1];
|
|
340
|
+
const i2 = this._triVerts[b + 2];
|
|
341
|
+
const e = [
|
|
342
|
+
[i0, i1],
|
|
343
|
+
[i1, i2],
|
|
344
|
+
[i2, i0],
|
|
345
|
+
];
|
|
346
|
+
for (let k = 0; k < 3; k++) {
|
|
347
|
+
const a = e[k][0];
|
|
348
|
+
const b2 = e[k][1];
|
|
349
|
+
const key = ukey(a, b2);
|
|
350
|
+
let arr = edgeMap.get(key);
|
|
351
|
+
if (!arr) { arr = []; edgeMap.set(key, arr); }
|
|
352
|
+
arr.push({ a, b: b2 });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
for (const arr of edgeMap.values()) {
|
|
356
|
+
if (arr.length !== 2) return false; // boundary or non-manifold
|
|
357
|
+
const e0 = arr[0], e1 = arr[1];
|
|
358
|
+
if (!(e0.a === e1.b && e0.b === e1.a)) return false; // not opposite orientation
|
|
359
|
+
}
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function invertNormals() {
|
|
364
|
+
for (let t = 0; t < this._triVerts.length; t += 3) {
|
|
365
|
+
const tmp = this._triVerts[t + 1];
|
|
366
|
+
this._triVerts[t + 1] = this._triVerts[t + 2];
|
|
367
|
+
this._triVerts[t + 2] = tmp;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
this._dirty = true;
|
|
371
|
+
this._faceIndex = null;
|
|
372
|
+
this._manifoldize();
|
|
373
|
+
return this;
|
|
374
|
+
}
|
|
375
|
+
|