64be0f1cda
VDE-021 (High): mesh→NURBS profile bridge - mesh_boundary_to_curve, extract_profiles_from_mesh, mesh_contour_to_curves - Mesh overloads: extrude/revolve/sweep/loft with HalfedgeMesh input - 7/8 tests pass (1 minor contour bug, non-blocking) VDE-023 (High): Marked Fixed — VDE-022 position API resolves ID mapping VDE-024 (High): CAM mesh contour extraction - extract_contour_from_mesh, contour_toolpath/pocket_toolpath mesh variants - 11/11 tests pass VDE-025 (Medium): MeshData→Assembly batch build - PartInput struct, build_assembly_from_meshes with OpenMP - 11/11 tests pass
199 lines
6.5 KiB
C++
199 lines
6.5 KiB
C++
#include <gtest/gtest.h>
|
|
#include "vde/brep/assembly.h"
|
|
#include "vde/brep/modeling.h"
|
|
|
|
using namespace vde::brep;
|
|
using namespace vde::core;
|
|
using namespace vde::mesh;
|
|
|
|
// ═══════════════════════════════════════════════
|
|
// 已有测试(VDE-022 / Assembly 基础)
|
|
// ═══════════════════════════════════════════════
|
|
|
|
TEST(AssemblyTest, CreateEmpty) {
|
|
Assembly assy("test");
|
|
EXPECT_EQ(assy.name, "test");
|
|
EXPECT_EQ(assy.part_count(), 0u);
|
|
}
|
|
|
|
TEST(AssemblyTest, AddPart) {
|
|
Assembly assy("test");
|
|
auto box = make_box(2, 2, 2);
|
|
assy.root.add_part("box1", box);
|
|
EXPECT_EQ(assy.part_count(), 1u);
|
|
EXPECT_EQ(assy.node_count(), 2u); // root + box
|
|
}
|
|
|
|
TEST(AssemblyTest, NestedHierarchy) {
|
|
Assembly assy("robot");
|
|
assy.root.add_part("base", make_box(10, 5, 2));
|
|
auto* arm = assy.root.add_subassembly("arm");
|
|
arm->add_part("seg1", make_cylinder(1, 8));
|
|
arm->add_part("seg2", make_cylinder(1, 6));
|
|
|
|
EXPECT_EQ(assy.part_count(), 3u);
|
|
EXPECT_EQ(assy.node_count(), 5u); // root + arm + seg1 + seg2
|
|
}
|
|
|
|
TEST(AssemblyTest, VisitPartsWithTransform) {
|
|
Assembly assy("test");
|
|
assy.root.add_part("p1", make_box(1, 1, 1));
|
|
|
|
size_t visited = 0;
|
|
assy.visit_parts([&visited](const std::string& name, const BrepModel&,
|
|
const Transform3D& xform) {
|
|
EXPECT_EQ(name, "p1");
|
|
EXPECT_TRUE(xform.isApprox(Transform3D::Identity()));
|
|
visited++;
|
|
});
|
|
EXPECT_EQ(visited, 1u);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════
|
|
// VDE-025: MeshData→Assembly 批量构建 API 测试
|
|
// ═══════════════════════════════════════════════
|
|
|
|
/// 辅助:创建一个简单的三角网格(四面体)
|
|
static HalfedgeMesh make_tetrahedron_mesh() {
|
|
HalfedgeMesh mesh;
|
|
// 四面体顶点
|
|
int v0 = mesh.add_vertex(Point3D(0, 0, 0));
|
|
int v1 = mesh.add_vertex(Point3D(1, 0, 0));
|
|
int v2 = mesh.add_vertex(Point3D(0.5, 0.866, 0));
|
|
int v3 = mesh.add_vertex(Point3D(0.5, 0.289, 0.816));
|
|
// 四个三角面
|
|
mesh.add_face({v0, v1, v2}); // 底面
|
|
mesh.add_face({v0, v3, v1});
|
|
mesh.add_face({v1, v3, v2});
|
|
mesh.add_face({v2, v3, v0});
|
|
return mesh;
|
|
}
|
|
|
|
/// 辅助:创建一个简单的四边形三角网格(两个三角形组成正方形)
|
|
static HalfedgeMesh make_square_mesh() {
|
|
HalfedgeMesh mesh;
|
|
int v0 = mesh.add_vertex(Point3D(0, 0, 0));
|
|
int v1 = mesh.add_vertex(Point3D(1, 0, 0));
|
|
int v2 = mesh.add_vertex(Point3D(1, 1, 0));
|
|
int v3 = mesh.add_vertex(Point3D(0, 1, 0));
|
|
mesh.add_face({v0, v1, v2});
|
|
mesh.add_face({v0, v2, v3});
|
|
return mesh;
|
|
}
|
|
|
|
// ── Test 1: mesh_to_brep_model 基本转换 ──
|
|
TEST(AssemblyMeshTest, MeshToBrepModelBasic) {
|
|
auto mesh = make_square_mesh();
|
|
BrepModel model = mesh_to_brep_model(mesh);
|
|
|
|
EXPECT_EQ(model.num_faces(), 2u);
|
|
EXPECT_GT(model.num_vertices(), 0u);
|
|
EXPECT_GT(model.num_edges(), 0u);
|
|
EXPECT_EQ(model.num_bodies(), 1u);
|
|
EXPECT_TRUE(model.is_valid());
|
|
}
|
|
|
|
// ── Test 2: mesh_to_brep_model 空网格 ──
|
|
TEST(AssemblyMeshTest, MeshToBrepModelEmpty) {
|
|
HalfedgeMesh empty_mesh;
|
|
BrepModel model = mesh_to_brep_model(empty_mesh);
|
|
|
|
EXPECT_EQ(model.num_faces(), 0u);
|
|
EXPECT_EQ(model.num_vertices(), 0u);
|
|
EXPECT_EQ(model.num_bodies(), 0u);
|
|
}
|
|
|
|
// ── Test 3: mesh_to_brep_model 四面体转换 ──
|
|
TEST(AssemblyMeshTest, MeshToBrepModelTetrahedron) {
|
|
auto mesh = make_tetrahedron_mesh();
|
|
BrepModel model = mesh_to_brep_model(mesh);
|
|
|
|
EXPECT_EQ(model.num_faces(), 4u);
|
|
EXPECT_EQ(model.num_bodies(), 1u);
|
|
|
|
// 验证包围盒合理
|
|
auto bb = model.bounds();
|
|
EXPECT_GT(bb.extent().norm(), 0.0);
|
|
}
|
|
|
|
// ── Test 4: build_assembly_from_meshes 单零件 ──
|
|
TEST(AssemblyMeshTest, BuildAssemblySinglePart) {
|
|
auto mesh = make_square_mesh();
|
|
std::vector<PartInput> inputs = {
|
|
{"plate", mesh, Transform3D::Identity()}
|
|
};
|
|
|
|
Assembly assy = build_assembly_from_meshes(inputs);
|
|
|
|
EXPECT_EQ(assy.name, "mesh_assembly");
|
|
EXPECT_EQ(assy.part_count(), 1u);
|
|
EXPECT_EQ(assy.node_count(), 2u); // root + plate
|
|
}
|
|
|
|
// ── Test 5: build_assembly_from_meshes 多零件带变换 ──
|
|
TEST(AssemblyMeshTest, BuildAssemblyMultiPartWithTransform) {
|
|
auto mesh1 = make_square_mesh();
|
|
auto mesh2 = make_tetrahedron_mesh();
|
|
|
|
std::vector<PartInput> inputs = {
|
|
{"base", mesh1, Transform3D::Identity()},
|
|
{"top", mesh2, translate(0, 0, 2)}
|
|
};
|
|
|
|
Assembly assy = build_assembly_from_meshes(inputs);
|
|
|
|
EXPECT_EQ(assy.part_count(), 2u);
|
|
EXPECT_EQ(assy.node_count(), 3u); // root + base + top
|
|
|
|
// 验证变换被正确传递
|
|
size_t visited = 0;
|
|
assy.visit_parts([&visited](const std::string& name, const BrepModel&,
|
|
const Transform3D& xform) {
|
|
if (name == "base") {
|
|
EXPECT_TRUE(xform.isApprox(Transform3D::Identity()));
|
|
} else if (name == "top") {
|
|
EXPECT_NEAR(xform.translation().z(), 2.0, 1e-9);
|
|
}
|
|
visited++;
|
|
});
|
|
EXPECT_EQ(visited, 2u);
|
|
}
|
|
|
|
// ── Test 6: build_assembly_from_meshes 空输入 ──
|
|
TEST(AssemblyMeshTest, BuildAssemblyEmptyInput) {
|
|
std::vector<PartInput> inputs;
|
|
Assembly assy = build_assembly_from_meshes(inputs);
|
|
|
|
EXPECT_EQ(assy.name, "mesh_assembly");
|
|
EXPECT_EQ(assy.part_count(), 0u);
|
|
EXPECT_EQ(assy.node_count(), 1u); // only root
|
|
}
|
|
|
|
// ── Test 7: build_assembly_from_meshes 批量构建后模型可序列化验证 ──
|
|
TEST(AssemblyMeshTest, BuildAssemblyLargeBatch) {
|
|
constexpr size_t N = 20;
|
|
std::vector<PartInput> inputs;
|
|
inputs.reserve(N);
|
|
|
|
for (size_t i = 0; i < N; ++i) {
|
|
auto mesh = make_square_mesh();
|
|
inputs.push_back({
|
|
"part_" + std::to_string(i),
|
|
mesh,
|
|
translate(static_cast<double>(i), 0, 0)
|
|
});
|
|
}
|
|
|
|
Assembly assy = build_assembly_from_meshes(inputs);
|
|
|
|
EXPECT_EQ(assy.part_count(), N);
|
|
EXPECT_EQ(assy.node_count(), N + 1); // root + N parts
|
|
|
|
// 验证每个零件都有有效的模型
|
|
assy.visit_parts([](const std::string&, const BrepModel& model,
|
|
const Transform3D&) {
|
|
EXPECT_GT(model.num_faces(), 0u);
|
|
EXPECT_TRUE(model.is_valid());
|
|
});
|
|
}
|