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.
- package/LICENSE.md +32 -0
- package/README.md +157 -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,55 @@
|
|
|
1
|
+
export async function test_SweepFace(partHistory) {
|
|
2
|
+
const cone = await partHistory.newFeature("P.CO");
|
|
3
|
+
cone.inputParams.radiusTop = 3;
|
|
4
|
+
cone.inputParams.radiusBottom = .5;
|
|
5
|
+
cone.inputParams.height = 5.2;
|
|
6
|
+
cone.inputParams.resolution = 20;
|
|
7
|
+
|
|
8
|
+
// Build a simple sketch with a straight path edge of length 5 along +Z.
|
|
9
|
+
// We place the sketch on an XZ plane so the line from (0,0)→(0,5) maps to +Z in world.
|
|
10
|
+
const plane = await partHistory.newFeature("P");
|
|
11
|
+
plane.inputParams.orientation = "YZ";
|
|
12
|
+
|
|
13
|
+
const sketch = await partHistory.newFeature("S");
|
|
14
|
+
sketch.inputParams.sketchPlane = plane.inputParams.featureID;
|
|
15
|
+
// Define a minimal sketch: one path line (id:100) and a tiny closed loop so edges get added to the scene.
|
|
16
|
+
sketch.persistentData.sketch = {
|
|
17
|
+
points: [
|
|
18
|
+
{ id: 0, x: 0, y: 0, fixed: true }, // ground
|
|
19
|
+
{ id: 1, x: 8, y: 20, fixed: false }, // path end (+Z since XZ plane)
|
|
20
|
+
// tiny square to ensure a profile face is created (so edges are added to scene)
|
|
21
|
+
{ id: 10, x: -0.5, y: -0.5, fixed: false },
|
|
22
|
+
{ id: 11, x: 0.5, y: -0.5, fixed: false },
|
|
23
|
+
{ id: 12, x: 0.5, y: 0.5, fixed: false },
|
|
24
|
+
{ id: 13, x: -0.5, y: 0.5, fixed: false },
|
|
25
|
+
],
|
|
26
|
+
geometries: [
|
|
27
|
+
// Path edge geometry (name will be G100)
|
|
28
|
+
{ id: 100, type: "line", points: [0, 1], construction: false },
|
|
29
|
+
// Closed loop (small square) so the sketch emits edges and a face group
|
|
30
|
+
{ id: 200, type: "line", points: [10, 11], construction: false },
|
|
31
|
+
{ id: 201, type: "line", points: [11, 12], construction: false },
|
|
32
|
+
{ id: 202, type: "line", points: [12, 13], construction: false },
|
|
33
|
+
{ id: 203, type: "line", points: [13, 10], construction: false },
|
|
34
|
+
],
|
|
35
|
+
constraints: [
|
|
36
|
+
{ id: 0, type: "⏚", points: [0] }, // ground point 0
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Create the path-based Sweep from the cone's top face, following the sketch edge G100.
|
|
41
|
+
const sweep = await partHistory.newFeature("SW");
|
|
42
|
+
sweep.inputParams.profile = `${cone.inputParams.featureID}_T`;
|
|
43
|
+
sweep.inputParams.path = [`${sketch.inputParams.featureID}:G100`]; // resolve to the sketch edge created above
|
|
44
|
+
sweep.inputParams.orientationMode = "translate"; // default, but make explicit
|
|
45
|
+
|
|
46
|
+
// perform a boolean operation between the 2 solids.
|
|
47
|
+
const boolean = await partHistory.newFeature("B");
|
|
48
|
+
boolean.inputParams.targetSolid = cone.inputParams.featureID;
|
|
49
|
+
boolean.inputParams.boolean = {
|
|
50
|
+
targets: [sweep.inputParams.featureID],
|
|
51
|
+
operation: "UNION",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return partHistory;
|
|
55
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export async function test_tube(partHistory) {
|
|
2
|
+
const plane = await partHistory.newFeature("P");
|
|
3
|
+
plane.inputParams.orientation = "XY";
|
|
4
|
+
|
|
5
|
+
const sketch = await partHistory.newFeature("S");
|
|
6
|
+
sketch.inputParams.sketchPlane = plane.inputParams.featureID;
|
|
7
|
+
sketch.persistentData.sketch = {
|
|
8
|
+
points: [
|
|
9
|
+
{ id: 0, x: 0, y: 0, fixed: true },
|
|
10
|
+
{ id: 1, x: 0, y: 40, fixed: false },
|
|
11
|
+
{ id: 2, x: 25, y: 40, fixed: false },
|
|
12
|
+
{ id: 10, x: -2, y: -2, fixed: false },
|
|
13
|
+
{ id: 11, x: 2, y: -2, fixed: false },
|
|
14
|
+
{ id: 12, x: 2, y: 2, fixed: false },
|
|
15
|
+
{ id: 13, x: -2, y: 2, fixed: false },
|
|
16
|
+
],
|
|
17
|
+
geometries: [
|
|
18
|
+
{ id: 200, type: "line", points: [0, 1], construction: false },
|
|
19
|
+
{ id: 201, type: "line", points: [1, 2], construction: false },
|
|
20
|
+
{ id: 300, type: "line", points: [10, 11], construction: false },
|
|
21
|
+
{ id: 301, type: "line", points: [11, 12], construction: false },
|
|
22
|
+
{ id: 302, type: "line", points: [12, 13], construction: false },
|
|
23
|
+
{ id: 303, type: "line", points: [13, 10], construction: false },
|
|
24
|
+
],
|
|
25
|
+
constraints: [
|
|
26
|
+
{ id: 0, type: "⏚", points: [0] },
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const edgePrefix = `${sketch.inputParams.featureID}:`;
|
|
31
|
+
|
|
32
|
+
const solidTube = await partHistory.newFeature("TU");
|
|
33
|
+
solidTube.inputParams.path = [`${edgePrefix}G200`, `${edgePrefix}G201`];
|
|
34
|
+
solidTube.inputParams.radius = 4;
|
|
35
|
+
solidTube.inputParams.innerRadius = 0;
|
|
36
|
+
solidTube.inputParams.resolution = 32;
|
|
37
|
+
|
|
38
|
+
const hollowTube = await partHistory.newFeature("TU");
|
|
39
|
+
hollowTube.inputParams.path = [`${edgePrefix}G200`, `${edgePrefix}G201`];
|
|
40
|
+
hollowTube.inputParams.radius = 5;
|
|
41
|
+
hollowTube.inputParams.innerRadius = 2;
|
|
42
|
+
hollowTube.inputParams.resolution = 48;
|
|
43
|
+
|
|
44
|
+
return partHistory;
|
|
45
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export async function test_tube_closedLoop(partHistory) {
|
|
2
|
+
const plane = await partHistory.newFeature("P");
|
|
3
|
+
plane.inputParams.orientation = "XY";
|
|
4
|
+
|
|
5
|
+
const sketch = await partHistory.newFeature("S");
|
|
6
|
+
sketch.inputParams.sketchPlane = plane.inputParams.featureID;
|
|
7
|
+
|
|
8
|
+
// Create a closed loop path (hexagon)
|
|
9
|
+
const numSides = 6;
|
|
10
|
+
const radius = 20;
|
|
11
|
+
const points = [];
|
|
12
|
+
const geometries = [];
|
|
13
|
+
|
|
14
|
+
// Generate hexagon points
|
|
15
|
+
for (let i = 0; i < numSides; i++) {
|
|
16
|
+
const angle = (i / numSides) * 2 * Math.PI;
|
|
17
|
+
points.push({
|
|
18
|
+
id: i,
|
|
19
|
+
x: Math.cos(angle) * radius,
|
|
20
|
+
y: Math.sin(angle) * radius,
|
|
21
|
+
fixed: false
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create line segments between consecutive points
|
|
26
|
+
for (let i = 0; i < numSides; i++) {
|
|
27
|
+
const nextI = (i + 1) % numSides;
|
|
28
|
+
geometries.push({
|
|
29
|
+
id: 200 + i,
|
|
30
|
+
type: "line",
|
|
31
|
+
points: [i, nextI],
|
|
32
|
+
construction: false
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
sketch.persistentData.sketch = {
|
|
37
|
+
points: points,
|
|
38
|
+
geometries: geometries,
|
|
39
|
+
constraints: [
|
|
40
|
+
{ id: 0, type: "⏚", points: [0] }, // Fix the first point
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const edgePrefix = `${sketch.inputParams.featureID}:`;
|
|
45
|
+
|
|
46
|
+
// Create a solid tube with the closed loop path
|
|
47
|
+
const solidTube = await partHistory.newFeature("TU");
|
|
48
|
+
solidTube.inputParams.path = geometries.map(g => `${edgePrefix}G${g.id}`);
|
|
49
|
+
solidTube.inputParams.radius = 3;
|
|
50
|
+
solidTube.inputParams.innerRadius = 0;
|
|
51
|
+
solidTube.inputParams.resolution = 24;
|
|
52
|
+
|
|
53
|
+
// Create a hollow tube with the closed loop path
|
|
54
|
+
const hollowTube = await partHistory.newFeature("TU");
|
|
55
|
+
hollowTube.inputParams.path = geometries.map(g => `${edgePrefix}G${g.id}`);
|
|
56
|
+
hollowTube.inputParams.radius = 4;
|
|
57
|
+
hollowTube.inputParams.innerRadius = 1;
|
|
58
|
+
hollowTube.inputParams.resolution = 24;
|
|
59
|
+
// Offset the hollow tube vertically so we can see both
|
|
60
|
+
hollowTube.inputParams.transform = {
|
|
61
|
+
position: [0, 0, 10],
|
|
62
|
+
rotation: [0, 0, 0],
|
|
63
|
+
scale: [1, 1, 1]
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return partHistory;
|
|
67
|
+
}
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import { posix as path } from '../path.proxy.js';
|
|
2
|
+
import { fs } from '../fs.proxy.js';
|
|
3
|
+
import { PartHistory } from "../PartHistory.js";
|
|
4
|
+
import { test_primitiveCube } from './test_primitiveCube.js';
|
|
5
|
+
import { test_solidMetrics, afterRun_solidMetrics } from './test_solidMetrics.js';
|
|
6
|
+
import { test_primitiveCylinder } from './test_primitiveCylinder.js';
|
|
7
|
+
import { test_plane } from './test_plane.js';
|
|
8
|
+
import { test_primitiveCone } from './test_primitiveCone.js';
|
|
9
|
+
import { test_primitiveTorus } from './test_primitiveTorus.js';
|
|
10
|
+
import { test_boolean_subtract } from './test_boolean_subtract.js';
|
|
11
|
+
import { test_primitiveSphere } from './test_primitiveSphere.js';
|
|
12
|
+
import { test_primitivePyramid } from './test_primitivePyramid.js';
|
|
13
|
+
import { test_stlLoader } from './test_stlLoader.js';
|
|
14
|
+
import { test_SweepFace } from './test_sweepFace.js';
|
|
15
|
+
import { test_ExtrudeFace } from './test_extrudeFace.js';
|
|
16
|
+
import { test_Fillet } from './test_fillet.js';
|
|
17
|
+
import { test_Chamfer } from './test_chamfer.js';
|
|
18
|
+
import { test_mirror } from './test_mirror.js';
|
|
19
|
+
import { test_fillets_more_dificult } from './test_filletsMoreDifficult.js';
|
|
20
|
+
import { test_tube } from './test_tube.js';
|
|
21
|
+
import { test_tube_closedLoop } from './test_tube_closedLoop.js';
|
|
22
|
+
import { test_offsetShellGrouping } from './test_offsetShellGrouping.js';
|
|
23
|
+
import { test_sheetMetal_tab, test_sheetMetal_flange, test_sheetMetal_hem, test_sheetMetal_cutout } from './test_sheetMetal_features.js';
|
|
24
|
+
import {
|
|
25
|
+
test_SheetMetalContourFlange_Basic,
|
|
26
|
+
test_SheetMetalContourFlange_StraightLine,
|
|
27
|
+
afterRun_SheetMetalContourFlange_Basic,
|
|
28
|
+
afterRun_SheetMetalContourFlange_StraightLine,
|
|
29
|
+
} from './test_sheetMetalContourFlange.js';
|
|
30
|
+
import { test_pushFace, afterRun_pushFace } from './test_pushFace.js';
|
|
31
|
+
import { test_sketch_openLoop, afterRun_sketch_openLoop } from './test_sketch_openLoop.js';
|
|
32
|
+
import { test_Fillet_NonClosed, afterRun_Fillet_NonClosed } from './test_fillet_nonClosed.js';
|
|
33
|
+
import { test_history_features_basic, afterRun_history_features_basic } from './test_history_features_basic.js';
|
|
34
|
+
import { generate3MF } from '../exporters/threeMF.js';
|
|
35
|
+
import { buildSheetMetalFlatPatternSvgs } from '../exporters/sheetMetalFlatPattern.js';
|
|
36
|
+
import {
|
|
37
|
+
test_hole_through,
|
|
38
|
+
afterRun_hole_through,
|
|
39
|
+
test_hole_thread_symbolic,
|
|
40
|
+
afterRun_hole_thread_symbolic,
|
|
41
|
+
test_hole_thread_modeled,
|
|
42
|
+
afterRun_hole_thread_modeled,
|
|
43
|
+
test_hole_countersink,
|
|
44
|
+
afterRun_hole_countersink,
|
|
45
|
+
test_hole_counterbore,
|
|
46
|
+
afterRun_hole_counterbore,
|
|
47
|
+
} from './test_hole.js';
|
|
48
|
+
|
|
49
|
+
const IS_NODE_RUNTIME = typeof process !== 'undefined' && process.versions && process.versions.node && typeof window === 'undefined';
|
|
50
|
+
const TEST_LOG_PATH = path.join('tests', 'test-run.log');
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export const testFunctions = [
|
|
54
|
+
{ test: test_plane, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
55
|
+
{ test: test_primitiveCube, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
56
|
+
{ test: test_primitivePyramid, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
57
|
+
{ test: test_primitiveCylinder, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
58
|
+
{ test: test_primitiveCone, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
59
|
+
{ test: test_primitiveTorus, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
60
|
+
{ test: test_primitiveSphere, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
61
|
+
{ test: test_boolean_subtract, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
62
|
+
{ test: test_stlLoader, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
63
|
+
{ test: test_SweepFace, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
64
|
+
{ test: test_tube, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
65
|
+
{ test: test_tube_closedLoop, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
66
|
+
{ test: test_sketch_openLoop, afterRun: afterRun_sketch_openLoop, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
|
|
67
|
+
{ test: test_offsetShellGrouping, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
|
|
68
|
+
{ test: test_sheetMetal_tab, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
69
|
+
{ test: test_sheetMetal_flange, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
70
|
+
{ test: test_sheetMetal_hem, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
71
|
+
{ test: test_sheetMetal_cutout, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
72
|
+
{ test: test_SheetMetalContourFlange_Basic, afterRun: afterRun_SheetMetalContourFlange_Basic, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
73
|
+
{ test: test_SheetMetalContourFlange_StraightLine, afterRun: afterRun_SheetMetalContourFlange_StraightLine, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
74
|
+
{ test: test_ExtrudeFace, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
75
|
+
{ test: test_Fillet, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
76
|
+
{ test: test_Fillet_NonClosed, afterRun: afterRun_Fillet_NonClosed, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
77
|
+
{ test: test_fillets_more_dificult, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
78
|
+
{ test: test_Chamfer, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
79
|
+
{ test: test_hole_through, afterRun: afterRun_hole_through, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
80
|
+
{ test: test_hole_countersink, afterRun: afterRun_hole_countersink, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
81
|
+
{ test: test_hole_counterbore, afterRun: afterRun_hole_counterbore, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
82
|
+
{ test: test_hole_thread_symbolic, afterRun: afterRun_hole_thread_symbolic, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
83
|
+
{ test: test_hole_thread_modeled, afterRun: afterRun_hole_thread_modeled, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
84
|
+
{ test: test_pushFace, afterRun: afterRun_pushFace, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
85
|
+
{ test: test_mirror, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
86
|
+
{ test: test_history_features_basic, afterRun: afterRun_history_features_basic, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
87
|
+
{ test: test_solidMetrics, afterRun: afterRun_solidMetrics, printArtifacts: true, exportFaces: true, exportSolids: true, resetHistory: true },
|
|
88
|
+
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
// Dynamically register tests to import part files from src/tests/partFiles (Node only)
|
|
92
|
+
async function registerPartFileTests() {
|
|
93
|
+
if (!(typeof process !== 'undefined' && process.versions && process.versions.node)) return;
|
|
94
|
+
try {
|
|
95
|
+
const partsDir = 'src/tests/partFiles';
|
|
96
|
+
// Use async API to avoid ESM sync-fs readiness issues
|
|
97
|
+
try {
|
|
98
|
+
const entries = await fs.promises.readdir(partsDir);
|
|
99
|
+
const files = entries.filter(f => typeof f === 'string' && f.toLowerCase().endsWith('.json'));
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
const filePath = path.join(partsDir, file);
|
|
102
|
+
const baseName = String(file).replace(/\.[^.]+$/, '');
|
|
103
|
+
const safeName = baseName.replace(/[^a-zA-Z0-9._-]+/g, '_').substring(0, 100);
|
|
104
|
+
const testName = `import_part_${safeName}`;
|
|
105
|
+
const isSheetMetalHem = file.toLowerCase() === 'sheetmetalhem.brep.json';
|
|
106
|
+
|
|
107
|
+
const importTest = async function (partHistory) {
|
|
108
|
+
// Read file and load into PartHistory
|
|
109
|
+
const content = await fs.promises.readFile(filePath, 'utf8');
|
|
110
|
+
let payload = content;
|
|
111
|
+
try {
|
|
112
|
+
const obj = JSON.parse(content);
|
|
113
|
+
if (obj && typeof obj === 'object') {
|
|
114
|
+
if (Array.isArray(obj.features)) {
|
|
115
|
+
payload = JSON.stringify(obj);
|
|
116
|
+
} else if (obj.data) {
|
|
117
|
+
payload = (typeof obj.data === 'string') ? obj.data : JSON.stringify(obj.data);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch (_) {
|
|
121
|
+
// invalid JSON; let runSingleTest catch and report when fromJSON or runHistory runs
|
|
122
|
+
}
|
|
123
|
+
await partHistory.reset();
|
|
124
|
+
await partHistory.fromJSON(payload);
|
|
125
|
+
// runHistory will be called by runSingleTest()
|
|
126
|
+
};
|
|
127
|
+
try { Object.defineProperty(importTest, 'name', { value: testName, configurable: true }); } catch {}
|
|
128
|
+
|
|
129
|
+
const afterRun = isSheetMetalHem ? async function (partHistory) {
|
|
130
|
+
const solids = (partHistory.scene?.children || []).filter(o => o && o.type === 'SOLID' && typeof o.getMesh === 'function');
|
|
131
|
+
const svgEntries = buildSheetMetalFlatPatternSvgs(solids, {
|
|
132
|
+
metadataManager: partHistory?.metadataManager || null,
|
|
133
|
+
});
|
|
134
|
+
if (!svgEntries.length) {
|
|
135
|
+
console.warn(`[runTests] No flat pattern SVG produced for ${testName}`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const exportPath = `./tests/results/${testName}/`;
|
|
139
|
+
for (const entry of svgEntries) {
|
|
140
|
+
const safeEntryName = sanitizeFileName(entry.name || 'SHEET');
|
|
141
|
+
const outPath = path.join(exportPath, `${safeEntryName}_flat-pattern.svg`);
|
|
142
|
+
writeFile(outPath, entry.svg);
|
|
143
|
+
}
|
|
144
|
+
} : null;
|
|
145
|
+
|
|
146
|
+
testFunctions.push({
|
|
147
|
+
test: importTest,
|
|
148
|
+
afterRun,
|
|
149
|
+
printArtifacts: false,
|
|
150
|
+
exportFaces: true,
|
|
151
|
+
exportSolids: true,
|
|
152
|
+
resetHistory: true,
|
|
153
|
+
allowErrors: true,
|
|
154
|
+
_sourceFile: filePath,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
// Directory may not exist; ignore silently in CI
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.warn('Failed to register part file import tests:', e?.message || e);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
// call runTests automatically when executed under Node.js
|
|
168
|
+
if (IS_NODE_RUNTIME) {
|
|
169
|
+
runTests()
|
|
170
|
+
.then(() => {
|
|
171
|
+
// ensure CLI exits promptly once the suite finishes
|
|
172
|
+
process.exit(0);
|
|
173
|
+
})
|
|
174
|
+
.catch((err) => {
|
|
175
|
+
console.error(err);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
export async function runTests(partHistory = new PartHistory(), callbackToRunBetweenTests = null) {
|
|
184
|
+
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
185
|
+
//await console.clear();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (IS_NODE_RUNTIME) {
|
|
189
|
+
resetTestLog();
|
|
190
|
+
logTestEvent('Test run started');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// delete the ./tests/results directory in an asynchronous way
|
|
194
|
+
await fs.promises.rm('./tests/results', { recursive: true, force: true });
|
|
195
|
+
|
|
196
|
+
// Discover and register part-file import tests (Node only)
|
|
197
|
+
await registerPartFileTests();
|
|
198
|
+
|
|
199
|
+
for (const testFunction of testFunctions) {
|
|
200
|
+
const isLastTest = testFunction === testFunctions[testFunctions.length - 1];
|
|
201
|
+
await partHistory.reset();
|
|
202
|
+
|
|
203
|
+
if (testFunction.resetHistory) partHistory.features = [];
|
|
204
|
+
|
|
205
|
+
const testName = (testFunction?.test?.name && String(testFunction.test.name)) || 'unnamed_test';
|
|
206
|
+
if (IS_NODE_RUNTIME) logTestEvent(`Starting ${testName}`);
|
|
207
|
+
|
|
208
|
+
let handledError = null;
|
|
209
|
+
try {
|
|
210
|
+
handledError = await runSingleTest(testFunction, partHistory);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if (IS_NODE_RUNTIME) logTestEvent(`Test ${testName} failed: ${stringifyError(err)}`);
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (IS_NODE_RUNTIME) {
|
|
217
|
+
if (handledError) logTestEvent(`Test ${testName} completed with handled error: ${stringifyError(handledError)}`);
|
|
218
|
+
else logTestEvent(`Test ${testName} completed successfully`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (typeof window !== "undefined") {
|
|
222
|
+
if (typeof callbackToRunBetweenTests === 'function') {
|
|
223
|
+
await callbackToRunBetweenTests(partHistory, isLastTest);
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
// run each test and export the results to a folder ./tests/results/<testFunction name>/
|
|
227
|
+
const exportName = testFunction.test.name;
|
|
228
|
+
const exportPath = `./tests/results/${exportName}/`;
|
|
229
|
+
// create the directory if it does not exist
|
|
230
|
+
if (!fs.existsSync(exportPath)) {
|
|
231
|
+
fs.mkdirSync(exportPath, { recursive: true });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Collect SOLID nodes from the scene
|
|
235
|
+
const solids = (partHistory.scene?.children || []).filter(o => o && o.type === 'SOLID' && typeof o.toSTL === 'function');
|
|
236
|
+
|
|
237
|
+
// Export solids (triggered by either flag for convenience)
|
|
238
|
+
if (testFunction.exportSolids || testFunction.printArtifacts) {
|
|
239
|
+
solids.forEach((solid, idx) => {
|
|
240
|
+
const rawName = solid.name && String(solid.name).trim().length ? String(solid.name) : `solid_${idx}`;
|
|
241
|
+
const safeName = sanitizeFileName(rawName);
|
|
242
|
+
let stl = "";
|
|
243
|
+
try {
|
|
244
|
+
stl = solid.toSTL(safeName, 6);
|
|
245
|
+
} catch (e) {
|
|
246
|
+
console.warn(`[runTests] toSTL failed for solid ${rawName}:`, e?.message || e);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const outPath = path.join(exportPath, `${safeName}.stl`);
|
|
250
|
+
writeFile(outPath, stl);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Export faces per solid
|
|
255
|
+
if (testFunction.exportFaces) {
|
|
256
|
+
solids.forEach((solid, sidx) => {
|
|
257
|
+
const rawName = solid.name && String(solid.name).trim().length ? String(solid.name) : `solid_${sidx}`;
|
|
258
|
+
const safeSolid = sanitizeFileName(rawName);
|
|
259
|
+
let faces = [];
|
|
260
|
+
try {
|
|
261
|
+
faces = typeof solid.getFaces === 'function' ? solid.getFaces(false) : [];
|
|
262
|
+
} catch {
|
|
263
|
+
faces = [];
|
|
264
|
+
}
|
|
265
|
+
faces.forEach(({ faceName, triangles }, fIdx) => {
|
|
266
|
+
if (!triangles || triangles.length === 0) return;
|
|
267
|
+
const rawFace = faceName || `face_${fIdx}`;
|
|
268
|
+
const safeFace = sanitizeFileName(rawFace);
|
|
269
|
+
const stl = trianglesToAsciiSTL(`${safeSolid}_${safeFace}`, triangles);
|
|
270
|
+
const outPath = path.join(exportPath, `${safeSolid}_${safeFace}.stl`);
|
|
271
|
+
writeFile(outPath, stl);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Export 3MF with embedded feature history
|
|
277
|
+
await export3mfArtifact({
|
|
278
|
+
partHistory,
|
|
279
|
+
exportName,
|
|
280
|
+
exportPath,
|
|
281
|
+
solids,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (IS_NODE_RUNTIME) logTestEvent('Test run finished');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
export async function runSingleTest(testFunction, partHistory = new PartHistory()) {
|
|
300
|
+
let error = null;
|
|
301
|
+
try {
|
|
302
|
+
await testFunction.test(partHistory);
|
|
303
|
+
await partHistory.runHistory();
|
|
304
|
+
// Optional per-test post-run hook for validations/metrics
|
|
305
|
+
if (typeof testFunction.afterRun === 'function') {
|
|
306
|
+
try { await testFunction.afterRun(partHistory); } catch (e) { console.warn('afterRun failed:', e?.message || e); }
|
|
307
|
+
}
|
|
308
|
+
} catch (e) {
|
|
309
|
+
error = e;
|
|
310
|
+
if (testFunction.allowErrors) {
|
|
311
|
+
const name = (testFunction.test && testFunction.test.name) ? testFunction.test.name : 'unnamed_test';
|
|
312
|
+
const exportPath = `./tests/results/${name}/`;
|
|
313
|
+
try { if (!fs.existsSync(exportPath)) fs.mkdirSync(exportPath, { recursive: true }); } catch {}
|
|
314
|
+
const msg = `${e?.message || e}\n\n${e?.stack || ''}`;
|
|
315
|
+
try { writeFile(path.join(exportPath, 'error.txt'), msg); } catch {}
|
|
316
|
+
console.error(`Error in test ${name}:`, e);
|
|
317
|
+
} else {
|
|
318
|
+
// rethrow to fail fast for normal tests
|
|
319
|
+
throw e;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// sleep for 1 second to allow any async operations to complete
|
|
323
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
324
|
+
|
|
325
|
+
return error;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
// function to write a file. If the path dose not exist it should make the folders needed.
|
|
330
|
+
function writeFile(filePath, content) {
|
|
331
|
+
// imediatly return if running in the browser
|
|
332
|
+
if (typeof window !== "undefined") {
|
|
333
|
+
//console.warn(`writeFile is not supported in the browser.`);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const dir = path.dirname(filePath);
|
|
339
|
+
if (!fs.existsSync(dir)) {
|
|
340
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
341
|
+
}
|
|
342
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.log(`Error writing file ${filePath}:`, error);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function writeBinaryFile(filePath, content) {
|
|
349
|
+
if (typeof window !== "undefined") {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const dir = path.dirname(filePath);
|
|
354
|
+
if (!fs.existsSync(dir)) {
|
|
355
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
356
|
+
}
|
|
357
|
+
fs.writeFileSync(filePath, content);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
console.log(`Error writing file ${filePath}:`, error);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function appendToFile(filePath, content) {
|
|
364
|
+
if (!IS_NODE_RUNTIME) return;
|
|
365
|
+
try {
|
|
366
|
+
const dir = path.dirname(filePath);
|
|
367
|
+
if (!fs.existsSync(dir)) {
|
|
368
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
369
|
+
}
|
|
370
|
+
fs.appendFileSync(filePath, content, 'utf8');
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.log(`Error appending file ${filePath}:`, error);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function resetTestLog() {
|
|
377
|
+
if (!IS_NODE_RUNTIME) return;
|
|
378
|
+
writeFile(TEST_LOG_PATH, '');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function logTestEvent(message) {
|
|
382
|
+
if (!IS_NODE_RUNTIME) return;
|
|
383
|
+
const timestamp = new Date().toISOString();
|
|
384
|
+
appendToFile(TEST_LOG_PATH, `[${timestamp}] ${message}\n`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function stringifyError(err) {
|
|
388
|
+
if (!err) return 'Unknown error';
|
|
389
|
+
if (typeof err === 'string') return err;
|
|
390
|
+
const message = err?.message || err?.toString?.() || 'Unknown error';
|
|
391
|
+
return err?.stack ? `${message}\n${err.stack}` : message;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ---------------- Local helpers for artifact export (Node only) ----------------
|
|
395
|
+
|
|
396
|
+
function sanitizeFileName(name) {
|
|
397
|
+
return String(name)
|
|
398
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '_') // collapse invalid chars
|
|
399
|
+
.replace(/^_+|_+$/g, '') // trim leading/trailing underscores
|
|
400
|
+
.substring(0, 100) || 'artifact'; // cap length
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function trianglesToAsciiSTL(name, tris) {
|
|
404
|
+
const fmt = (n) => Number.isFinite(n) ? (Math.abs(n) < 1e-18 ? '0' : n.toFixed(6)) : '0';
|
|
405
|
+
const out = [];
|
|
406
|
+
out.push(`solid ${name}`);
|
|
407
|
+
for (let i = 0; i < tris.length; i++) {
|
|
408
|
+
const t = tris[i];
|
|
409
|
+
const p0 = t.p1, p1 = t.p2, p2 = t.p3;
|
|
410
|
+
const ux = p1[0] - p0[0], uy = p1[1] - p0[1], uz = p1[2] - p0[2];
|
|
411
|
+
const vx = p2[0] - p0[0], vy = p2[1] - p0[1], vz = p2[2] - p0[2];
|
|
412
|
+
let nx = uy * vz - uz * vy;
|
|
413
|
+
let ny = uz * vx - ux * vz;
|
|
414
|
+
let nz = ux * vy - uy * vx;
|
|
415
|
+
const len = Math.hypot(nx, ny, nz) || 1;
|
|
416
|
+
nx /= len; ny /= len; nz /= len;
|
|
417
|
+
out.push(` facet normal ${fmt(nx)} ${fmt(ny)} ${fmt(nz)}`);
|
|
418
|
+
out.push(` outer loop`);
|
|
419
|
+
out.push(` vertex ${fmt(p0[0])} ${fmt(p0[1])} ${fmt(p0[2])}`);
|
|
420
|
+
out.push(` vertex ${fmt(p1[0])} ${fmt(p1[1])} ${fmt(p1[2])}`);
|
|
421
|
+
out.push(` vertex ${fmt(p2[0])} ${fmt(p2[1])} ${fmt(p2[2])}`);
|
|
422
|
+
out.push(` endloop`);
|
|
423
|
+
out.push(` endfacet`);
|
|
424
|
+
}
|
|
425
|
+
out.push(`endsolid ${name}`);
|
|
426
|
+
return out.join('\n');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function export3mfArtifact({ partHistory, exportName, exportPath, solids }) {
|
|
430
|
+
let historyJson = null;
|
|
431
|
+
try {
|
|
432
|
+
const json = await partHistory?.toJSON?.();
|
|
433
|
+
if (json) historyJson = (typeof json === 'string') ? json : JSON.stringify(json);
|
|
434
|
+
} catch (e) {
|
|
435
|
+
console.warn(`[runTests] Failed to serialize feature history for ${exportName}:`, e?.message || e);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const additionalFiles = historyJson ? { 'Metadata/featureHistory.json': historyJson } : undefined;
|
|
439
|
+
const modelMetadata = historyJson ? { featureHistoryPath: '/Metadata/featureHistory.json' } : undefined;
|
|
440
|
+
const metadataManager = partHistory?.metadataManager || null;
|
|
441
|
+
|
|
442
|
+
const solidsForExport = [];
|
|
443
|
+
(solids || []).forEach((s, idx) => {
|
|
444
|
+
try {
|
|
445
|
+
const mesh = s?.getMesh?.();
|
|
446
|
+
const canExport = !!(mesh && mesh.vertProperties && mesh.triVerts);
|
|
447
|
+
if (mesh && typeof mesh.delete === 'function') {
|
|
448
|
+
try { mesh.delete(); } catch { }
|
|
449
|
+
}
|
|
450
|
+
if (canExport) {
|
|
451
|
+
solidsForExport.push(s);
|
|
452
|
+
} else {
|
|
453
|
+
const name = sanitizeFileName(s?.name || `solid_${idx}`);
|
|
454
|
+
console.warn(`[runTests] Skipping non-manifold solid for 3MF: ${name}`);
|
|
455
|
+
}
|
|
456
|
+
} catch {
|
|
457
|
+
const name = sanitizeFileName(s?.name || `solid_${idx}`);
|
|
458
|
+
console.warn(`[runTests] Skipping solid that failed to export for 3MF: ${name}`);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const safeName = sanitizeFileName(exportName || 'partHistory');
|
|
463
|
+
const outPath = path.join(exportPath, `${safeName}.3mf`);
|
|
464
|
+
try {
|
|
465
|
+
let data = null;
|
|
466
|
+
try {
|
|
467
|
+
data = await generate3MF(solidsForExport, {
|
|
468
|
+
unit: 'millimeter',
|
|
469
|
+
precision: 6,
|
|
470
|
+
scale: 1,
|
|
471
|
+
additionalFiles,
|
|
472
|
+
modelMetadata,
|
|
473
|
+
metadataManager,
|
|
474
|
+
});
|
|
475
|
+
} catch (e) {
|
|
476
|
+
data = await generate3MF([], {
|
|
477
|
+
unit: 'millimeter',
|
|
478
|
+
precision: 6,
|
|
479
|
+
scale: 1,
|
|
480
|
+
additionalFiles,
|
|
481
|
+
modelMetadata,
|
|
482
|
+
metadataManager,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (data && data.length) {
|
|
486
|
+
writeBinaryFile(outPath, data);
|
|
487
|
+
} else {
|
|
488
|
+
console.warn(`[runTests] 3MF export returned empty payload for ${exportName}`);
|
|
489
|
+
}
|
|
490
|
+
} catch (e) {
|
|
491
|
+
console.warn(`[runTests] 3MF export failed for ${exportName}:`, e?.message || e);
|
|
492
|
+
}
|
|
493
|
+
}
|