#include #include "vde/mesh/fea_mesh.h" #include "vde/brep/modeling.h" #include "vde/mesh/mesh_quality.h" #include #include #include using namespace vde::mesh; using namespace vde::brep; using namespace vde::core; // ═══════════════════════════════════════════════════════════ // 1. FEAMesh 数据结构测试 // ═══════════════════════════════════════════════════════════ TEST(FEAMeshTest, DefaultConstruction) { FEAMesh mesh; EXPECT_EQ(mesh.num_vertices(), 0u); EXPECT_EQ(mesh.num_elements(), 0u); EXPECT_EQ(mesh.num_boundary_faces(), 0u); EXPECT_EQ(mesh.element_type, FEAElementType::Tet4); EXPECT_EQ(mesh.npe(), 4); } TEST(FEAMeshTest, ElementTypeNPE) { EXPECT_EQ(nodes_per_element(FEAElementType::Tet4), 4); EXPECT_EQ(nodes_per_element(FEAElementType::Tet10), 10); EXPECT_EQ(nodes_per_element(FEAElementType::Hex8), 8); EXPECT_EQ(nodes_per_element(FEAElementType::Hex20), 20); EXPECT_EQ(nodes_per_element(FEAElementType::Wedge6), 6); EXPECT_EQ(nodes_per_element(FEAElementType::Wedge15), 15); } TEST(FEAMeshTest, ElementTypeName) { EXPECT_STREQ(element_type_name(FEAElementType::Tet4), "Tet4"); EXPECT_STREQ(element_type_name(FEAElementType::Hex8), "Hex8"); EXPECT_STREQ(element_type_name(FEAElementType::Wedge6), "Wedge6"); } // ═══════════════════════════════════════════════════════════ // 2. tetrahedral_mesh 测试 // ═══════════════════════════════════════════════════════════ TEST(TetrahedralMeshTest, BoxMesh_NonEmpty) { auto box = make_box(1.0, 1.0, 1.0); TetMeshParams params; params.max_size = 0.3; params.quality_iterations = 1; auto mesh = tetrahedral_mesh(box, params); EXPECT_GT(mesh.num_vertices(), 0u); EXPECT_GT(mesh.num_elements(), 0u); EXPECT_EQ(mesh.element_type, FEAElementType::Tet4); // 四面体网格应该有边界面 EXPECT_GT(mesh.num_boundary_faces(), 0u); } TEST(TetrahedralMeshTest, BoxMesh_ValidConnectivity) { auto box = make_box(1.0, 1.0, 1.0); TetMeshParams params; params.max_size = 0.5; auto mesh = tetrahedral_mesh(box, params); // 所有单元的顶点索引应在合法范围内 for (size_t ei = 0; ei < mesh.num_elements(); ++ei) { auto& e = mesh.elements[ei]; EXPECT_EQ(e.size(), 4u); for (int vi : e) { EXPECT_GE(vi, 0); EXPECT_LT(static_cast(vi), mesh.num_vertices()); } } } TEST(TetrahedralMeshTest, SphereMesh_NonTrivial) { auto sphere = make_sphere(1.0); TetMeshParams params; params.max_size = 0.5; params.quality_iterations = 1; auto mesh = tetrahedral_mesh(sphere, params); EXPECT_GT(mesh.num_vertices(), 0u); EXPECT_GT(mesh.num_elements(), 0u); } // ═══════════════════════════════════════════════════════════ // 3. boundary_layer_mesh 测试 // ═══════════════════════════════════════════════════════════ TEST(BoundaryLayerTest, BoxBoundaryLayer_Prisms) { auto box = make_box(1.0, 1.0, 1.0); BLPParams params; params.first_cell_height = 0.01; params.growth_rate = 1.2; params.num_layers = 3; auto mesh = boundary_layer_mesh(box, params); EXPECT_EQ(mesh.element_type, FEAElementType::Wedge6); EXPECT_GT(mesh.num_elements(), 0u); EXPECT_GT(mesh.num_vertices(), 0u); // 顶点数 = 表面顶点 × (layers+1) EXPECT_GT(mesh.num_vertices(), 0u); } TEST(BoundaryLayerTest, BoxBoundaryLayer_ValidConnectivity) { auto box = make_box(1.0, 1.0, 1.0); BLPParams params; params.num_layers = 2; auto mesh = boundary_layer_mesh(box, params); for (size_t ei = 0; ei < mesh.num_elements(); ++ei) { auto& e = mesh.elements[ei]; EXPECT_EQ(e.size(), 6u); // Wedge6 for (int vi : e) { EXPECT_GE(vi, 0); EXPECT_LT(static_cast(vi), mesh.num_vertices()); } } } // ═══════════════════════════════════════════════════════════ // 4. hexahedral_mesh 测试 // ═══════════════════════════════════════════════════════════ TEST(HexahedralMeshTest, BoxSweep_HexMesh) { auto box = make_box(1.0, 1.0, 1.0); HexMeshParams params; params.sweep_layers = 2; auto mesh = hexahedral_mesh(box, params); EXPECT_EQ(mesh.element_type, FEAElementType::Hex8); EXPECT_GE(mesh.num_elements(), 0u); EXPECT_GT(mesh.num_vertices(), 0u); } TEST(HexahedralMeshTest, BoxSweep_ValidConnectivity) { auto box = make_box(1.0, 1.0, 1.0); HexMeshParams params; params.sweep_layers = 2; auto mesh = hexahedral_mesh(box, params); for (size_t ei = 0; ei < mesh.num_elements(); ++ei) { auto& e = mesh.elements[ei]; EXPECT_EQ(e.size(), 8u); // Hex8 for (int vi : e) { EXPECT_GE(vi, 0); EXPECT_LT(static_cast(vi), mesh.num_vertices()); } } } // ═══════════════════════════════════════════════════════════ // 5. 单元素质量指标测试 // ═══════════════════════════════════════════════════════════ TEST(ElementQualityTest, Tet4_Regular_GoodQuality) { // 正四面体 (边长为 sqrt(2) 的四个点) std::vector verts = { {1, 1, 1}, {1, -1, -1}, {-1, 1, -1}, {-1, -1, 1} }; double sj = element_scaled_jacobian(verts, FEAElementType::Tet4); EXPECT_GT(sj, 0.5); double skew = element_skewness(verts, FEAElementType::Tet4); EXPECT_LT(skew, 0.5); double ortho = element_orthogonality(verts, FEAElementType::Tet4); EXPECT_GT(ortho, 0.0); EXPECT_LE(ortho, 1.0); } TEST(ElementQualityTest, Tet4_Degenerate_ZeroJacobian) { // 退化四面体(四点共面) std::vector verts = { {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0.5, 0.5, 0} // 在同一平面上 }; double sj = element_scaled_jacobian(verts, FEAElementType::Tet4); EXPECT_NEAR(sj, 0.0, 1e-6); } TEST(ElementQualityTest, AspectRatio_IsotropicElement) { // 边长为 1 的四面体 std::vector verts = { {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1} }; double ar = element_aspect_ratio(verts, FEAElementType::Tet4); EXPECT_GE(ar, 1.0); } TEST(ElementQualityTest, Hex8_QualityFinite) { std::vector verts = { {0,0,0},{1,0,0},{1,1,0},{0,1,0}, {0,0,1},{1,0,1},{1,1,1},{0,1,1} }; double sj = element_scaled_jacobian(verts, FEAElementType::Hex8); EXPECT_GT(sj, 0.0); EXPECT_LE(sj, 1.0); double skew = element_skewness(verts, FEAElementType::Hex8); EXPECT_LE(skew, 1.0); } // ═══════════════════════════════════════════════════════════ // 6. fea_quality_report 测试 // ═══════════════════════════════════════════════════════════ TEST(FEAQualityReportTest, EmptyMesh_AllZero) { FEAMesh mesh; auto report = fea_quality_report(mesh); EXPECT_EQ(report.total_elements, 0u); EXPECT_EQ(report.degenerate_elements, 0u); } TEST(FEAQualityReportTest, TetrahedralMesh_ReportValid) { auto box = make_box(1.0, 1.0, 1.0); TetMeshParams params; params.max_size = 0.4; auto mesh = tetrahedral_mesh(box, params); auto report = fea_quality_report(mesh); EXPECT_EQ(report.total_elements, mesh.num_elements()); EXPECT_GT(report.total_elements, 0u); // 质量报告应有有效值 EXPECT_GE(report.avg_jacobian, 0.0); EXPECT_LE(report.avg_jacobian, 1.0); EXPECT_GE(report.avg_skewness, 0.0); EXPECT_LE(report.avg_skewness, 1.0); } // ═══════════════════════════════════════════════════════════ // 7. adaptive_refinement 测试 // ═══════════════════════════════════════════════════════════ TEST(AdaptiveRefinementTest, NoRefinement_ReturnsSame) { auto box = make_box(1.0, 1.0, 1.0); auto mesh = tetrahedral_mesh(box, TetMeshParams{}); size_t original_elems = mesh.num_elements(); // 误差为 0 → 不细化 auto zero_estimator = [](int, const FEAMesh&) -> double { return 0.0; }; auto refined = adaptive_refinement(mesh, zero_estimator); // 不细化时单元数应不变(但边中点缓存可能导致微小差异) // 只验证不崩溃且仍有单元 EXPECT_GT(refined.num_elements(), 0u); } TEST(AdaptiveRefinementTest, HighError_Refines) { auto box = make_box(1.0, 1.0, 1.0); TetMeshParams params; params.max_size = 0.5; auto mesh = tetrahedral_mesh(box, params); size_t original_elems = mesh.num_elements(); // 所有单元高误差 → 细化 auto high_estimator = [](int, const FEAMesh&) -> double { return 1.0; }; RefinementParams rp; rp.error_threshold = 0.1; auto refined = adaptive_refinement(mesh, high_estimator, rp); // 细化后单元数应增加 EXPECT_GT(refined.num_elements(), original_elems); } // ═══════════════════════════════════════════════════════════ // 8. CAE 导出测试 // ═══════════════════════════════════════════════════════════ TEST(CAEExportTest, AbaqusExport_FileCreated) { auto box = make_box(1.0, 1.0, 1.0); auto mesh = tetrahedral_mesh(box, TetMeshParams{}); std::string path = "/tmp/test_abaqus.inp"; bool ok = export_abaqus(mesh, path); EXPECT_TRUE(ok); // 检查文件存在且非空 std::ifstream f(path); EXPECT_TRUE(f.good()); std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); EXPECT_GT(content.size(), 0u); // 应包含关键关键字 EXPECT_NE(content.find("*NODE"), std::string::npos); EXPECT_NE(content.find("*ELEMENT"), std::string::npos); std::remove(path.c_str()); } TEST(CAEExportTest, AnsysExport_FileCreated) { auto box = make_box(1.0, 1.0, 1.0); auto mesh = tetrahedral_mesh(box, TetMeshParams{}); std::string path = "/tmp/test_ansys.cdb"; bool ok = export_ansys(mesh, path); EXPECT_TRUE(ok); std::ifstream f(path); EXPECT_TRUE(f.good()); std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); EXPECT_GT(content.size(), 0u); EXPECT_NE(content.find("NBLOCK"), std::string::npos); EXPECT_NE(content.find("EBLOCK"), std::string::npos); std::remove(path.c_str()); } TEST(CAEExportTest, NastranExport_FileCreated) { auto box = make_box(1.0, 1.0, 1.0); auto mesh = tetrahedral_mesh(box, TetMeshParams{}); std::string path = "/tmp/test_nastran.bdf"; bool ok = export_nastran(mesh, path); EXPECT_TRUE(ok); std::ifstream f(path); EXPECT_TRUE(f.good()); std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); EXPECT_GT(content.size(), 0u); EXPECT_NE(content.find("GRID"), std::string::npos); EXPECT_NE(content.find("CTETRA"), std::string::npos); std::remove(path.c_str()); } TEST(CAEExportTest, Export_InvalidPath) { FEAMesh mesh; bool ok = export_abaqus(mesh, "/nonexistent_dir/should_fail.inp"); EXPECT_FALSE(ok); } TEST(CAEExportTest, HexMeshNastranExport) { auto box = make_box(2.0, 1.0, 1.0); HexMeshParams params; params.sweep_layers = 2; auto mesh = hexahedral_mesh(box, params); std::string path = "/tmp/test_hex_nastran.bdf"; bool ok = export_nastran(mesh, path); EXPECT_TRUE(ok); std::ifstream f(path); std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); EXPECT_NE(content.find("CHEXA"), std::string::npos); std::remove(path.c_str()); }