Files
ViewDesignEngine/tests/brep/test_tolerant_modeling.cpp
T

430 lines
15 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include "vde/brep/brep.h"
#include "vde/brep/modeling.h"
#include "vde/brep/brep_validate.h"
#include "vde/brep/brep_heal.h"
#include "vde/brep/tolerant_modeling.h"
#include "vde/brep/tolerance.h"
#include "vde/brep/ssi_boolean.h"
#include "vde/brep/step_import.h"
#include "vde/curves/nurbs_surface.h"
#include "vde/core/point.h"
using namespace vde::brep;
using namespace vde::core;
using namespace vde::curves;
namespace {
/// Create a simple planar NURBS surface on the XY plane
NurbsSurface make_plane_surface() {
std::vector<std::vector<Point3D>> grid = {
{Point3D(-1, -1, 0), Point3D(-1, 1, 0)},
{Point3D( 1, -1, 0), Point3D( 1, 1, 0)}
};
return NurbsSurface(grid, {0,0,1,1}, {0,0,1,1}, {}, 1, 1);
}
/// Create a simple triangular BrepModel
BrepModel make_triangle(const Point3D& a, const Point3D& b, const Point3D& c) {
BrepModel body;
int v0 = body.add_vertex(a);
int v1 = body.add_vertex(b);
int v2 = body.add_vertex(c);
int e0 = body.add_edge(v0, v1);
int e1 = body.add_edge(v1, v2);
int e2 = body.add_edge(v2, v0);
int loop_id = body.add_loop({e0, e1, e2});
auto surf = make_plane_surface();
int sid = body.add_surface(surf);
int fid = body.add_face(sid, {loop_id});
int shell = body.add_shell({fid}, false);
body.add_body({shell}, "triangle");
return body;
}
/// Create a simple box using modeling API
BrepModel make_test_box() {
return make_box(2.0, 2.0, 2.0);
}
} // namespace
// ═══════════════════════════════════════════════════════════
// 1. TolerantVertex / TolerantEdge 基本构造
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, TolerantVertex_DefaultConstruction) {
TolerantVertex tv;
tv.id = 0;
tv.point = Point3D(1, 2, 3);
tv.tolerance = 1e-5;
EXPECT_EQ(tv.id, 0);
EXPECT_DOUBLE_EQ(tv.point.x(), 1.0);
EXPECT_DOUBLE_EQ(tv.point.y(), 2.0);
EXPECT_DOUBLE_EQ(tv.point.z(), 3.0);
EXPECT_DOUBLE_EQ(tv.tolerance, 1e-5);
EXPECT_EQ(tv.merged_from, 1);
EXPECT_FALSE(tv.is_degenerate);
}
TEST(TolerantModelingTest, TolerantVertex_FromTopo) {
TopoVertex topo;
topo.id = 5;
topo.point = Point3D(10, 20, 30);
topo.tolerance = 1e-7;
TolerantVertex tv = TolerantVertex::from_topo(topo);
EXPECT_EQ(tv.id, 5);
EXPECT_DOUBLE_EQ(tv.point.x(), 10.0);
EXPECT_DOUBLE_EQ(tv.tolerance, 1e-7);
}
TEST(TolerantModelingTest, TolerantEdge_DefaultConstruction) {
TolerantEdge te;
te.id = 3;
te.v_start = 1;
te.v_end = 2;
te.tolerance = 1e-6;
te.is_tangent_contact = true;
EXPECT_EQ(te.id, 3);
EXPECT_EQ(te.v_start, 1);
EXPECT_EQ(te.v_end, 2);
EXPECT_FALSE(te.is_degenerate);
EXPECT_FALSE(te.gap_flag);
EXPECT_TRUE(te.is_tangent_contact);
}
TEST(TolerantModelingTest, TolerantEdge_FromTopo) {
TopoEdge topo;
topo.id = 7;
topo.v_start = 0;
topo.v_end = 1;
TolerantEdge te = TolerantEdge::from_topo(topo);
EXPECT_EQ(te.id, 7);
EXPECT_EQ(te.v_start, 0);
EXPECT_EQ(te.v_end, 1);
}
TEST(TolerantModelingTest, TolerantEdge_CheckDegenerate_True) {
BrepModel body;
int v0 = body.add_vertex(Point3D(0, 0, 0));
int v1 = body.add_vertex(Point3D(1e-8, 1e-8, 0)); // extremely short edge
int e0 = body.add_edge(v0, v1);
TolerantEdge te = TolerantEdge::from_topo(body.edge(e0));
te.tolerance = 1e-6;
EXPECT_TRUE(te.check_degenerate(body));
}
TEST(TolerantModelingTest, TolerantEdge_CheckDegenerate_False) {
BrepModel body;
int v0 = body.add_vertex(Point3D(0, 0, 0));
int v1 = body.add_vertex(Point3D(1, 0, 0));
int e0 = body.add_edge(v0, v1);
TolerantEdge te = TolerantEdge::from_topo(body.edge(e0));
te.tolerance = 1e-6;
EXPECT_FALSE(te.check_degenerate(body));
}
// ═══════════════════════════════════════════════════════════
// 2. build_tolerant_vertices / build_tolerant_edges
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, BuildTolerantVertices) {
BrepModel body;
body.add_vertex(Point3D(0, 0, 0));
body.add_vertex(Point3D(1, 0, 0));
body.add_vertex(Point3D(0, 1, 0));
auto tvs = build_tolerant_vertices(body, 1e-5);
EXPECT_EQ(tvs.size(), 3u);
for (const auto& tv : tvs) {
EXPECT_DOUBLE_EQ(tv.tolerance, 1e-5);
EXPECT_EQ(tv.merged_from, 1);
}
}
TEST(TolerantModelingTest, BuildTolerantEdges) {
BrepModel body;
int v0 = body.add_vertex(Point3D(0, 0, 0));
int v1 = body.add_vertex(Point3D(1, 0, 0));
int v2 = body.add_vertex(Point3D(0, 1, 0));
body.add_edge(v0, v1);
body.add_edge(v1, v2);
body.add_edge(v2, v0);
auto tes = build_tolerant_edges(body);
EXPECT_EQ(tes.size(), 3u);
EXPECT_FALSE(tes[0].is_degenerate); // 1.0 length
}
// ═══════════════════════════════════════════════════════════
// 3. merge_within_tolerance
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, MergeWithinTolerance_NearCoincident) {
BrepModel body;
int v0 = body.add_vertex(Point3D(0, 0, 0));
int v1 = body.add_vertex(Point3D(1, 0, 0));
int v2 = body.add_vertex(Point3D(0, 1, 0));
int v_near = body.add_vertex(Point3D(1e-7, 1e-7, 1e-7));
int e0 = body.add_edge(v0, v1);
int e1 = body.add_edge(v1, v2);
int e2 = body.add_edge(v2, v_near);
int loop_id = body.add_loop({e0, e1, e2});
auto surf = make_plane_surface();
int sid = body.add_surface(surf);
body.add_face(sid, {loop_id});
size_t before = body.num_vertices();
int merged = merge_within_tolerance(body, 1e-5);
EXPECT_GE(merged, 1);
EXPECT_LT(body.num_vertices(), before);
}
TEST(TolerantModelingTest, MergeWithinTolerance_Empty) {
BrepModel body;
EXPECT_EQ(merge_within_tolerance(body), 0);
}
TEST(TolerantModelingTest, MergeWithinTolerance_SingleVertex) {
BrepModel body;
body.add_vertex(Point3D(0, 0, 0));
EXPECT_EQ(merge_within_tolerance(body), 0);
}
// ═══════════════════════════════════════════════════════════
// 4. gap_bridging
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, GapBridging_Empty) {
BrepModel body;
EXPECT_EQ(gap_bridging(body), 0);
}
TEST(TolerantModelingTest, GapBridging_SingleTriangle_NoGaps) {
auto body = make_triangle(Point3D(0,0,0), Point3D(1,0,0), Point3D(0,1,0));
// A closed triangle has no free edges
int bridged = gap_bridging(body, 1e-4);
EXPECT_GE(bridged, 0);
}
// ═══════════════════════════════════════════════════════════
// 5. overlap_resolution
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, OverlapResolution_Empty) {
BrepModel body;
EXPECT_EQ(overlap_resolution(body), 0);
}
TEST(TolerantModelingTest, OverlapResolution_SingleFace) {
auto body = make_triangle(Point3D(0,0,0), Point3D(1,0,0), Point3D(0,1,0));
EXPECT_EQ(overlap_resolution(body), 0);
}
// ═══════════════════════════════════════════════════════════
// 6. tolerant_boolean
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, TolerantBoolean_EmptyBodies) {
BrepModel a, b;
auto diag = tolerant_boolean(a, b, 0); // union
EXPECT_TRUE(diag.success);
EXPECT_EQ(diag.result.num_faces(), 0u);
}
TEST(TolerantModelingTest, TolerantBoolean_UnionOfDisjoint) {
auto box1 = make_box(1, 1, 1);
auto box2 = make_box(1, 1, 1);
auto diag = tolerant_boolean(box1, box2, 0); // union
// May succeed or have failures — check that diagnostic is populated
EXPECT_FALSE(diag.report().empty());
}
TEST(TolerantModelingTest, TolerantBoolean_InvalidOp) {
auto box1 = make_box(1, 1, 1);
auto box2 = make_box(1, 1, 1);
auto diag = tolerant_boolean(box1, box2, 99);
EXPECT_FALSE(diag.success);
EXPECT_FALSE(diag.error_message.empty());
}
// ═══════════════════════════════════════════════════════════
// 7. BooleanDiagnostic
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, BooleanDiagnostic_Default) {
BooleanDiagnostic diag;
EXPECT_FALSE(diag.success);
EXPECT_FALSE(diag.has_failures());
EXPECT_FALSE(diag.report().empty());
}
TEST(TolerantModelingTest, BooleanDiagnostic_HasFailures) {
BooleanDiagnostic diag;
EXPECT_FALSE(diag.has_failures());
FailedFaceInfo ffi;
ffi.face_id = 3;
ffi.reason = "Test failure";
diag.failed_faces.push_back(ffi);
EXPECT_TRUE(diag.has_failures());
}
TEST(TolerantModelingTest, BooleanDiagnostic_FailedFaceInfo) {
FailedFaceInfo ffi;
ffi.face_id = 42;
ffi.reason = "Coplanar degeneracy";
ffi.related_face_id = 17;
ffi.is_coplanar = true;
ffi.suggested_tolerance = 1e-3;
EXPECT_EQ(ffi.face_id, 42);
EXPECT_EQ(ffi.related_face_id, 17);
EXPECT_TRUE(ffi.is_coplanar);
EXPECT_DOUBLE_EQ(ffi.suggested_tolerance, 1e-3);
}
// ═══════════════════════════════════════════════════════════
// 8. detect_coplanar_faces / detect_tangent_contact / detect_degenerate_edge
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, DetectCoplanarFaces_SameBody) {
auto body = make_triangle(Point3D(0,0,0), Point3D(1,0,0), Point3D(0,1,0));
// Single face body → cannot check pair
EXPECT_FALSE(detect_coplanar_faces(body, 0, 0));
}
TEST(TolerantModelingTest, DetectDegenerateEdge_Valid) {
BrepModel body;
int v0 = body.add_vertex(Point3D(0, 0, 0));
int v1 = body.add_vertex(Point3D(1, 0, 0));
int e0 = body.add_edge(v0, v1);
EXPECT_FALSE(detect_degenerate_edge(body, e0));
}
TEST(TolerantModelingTest, DetectDegenerateEdge_Degenerate) {
BrepModel body;
int v0 = body.add_vertex(Point3D(0, 0, 0));
int v1 = body.add_vertex(Point3D(1e-8, 0, 0));
int e0 = body.add_edge(v0, v1);
EXPECT_TRUE(detect_degenerate_edge(body, e0, 1e-5));
}
// ═══════════════════════════════════════════════════════════
// 9. import_heal_pipeline
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, ImportHealPipeline_Empty) {
auto bodies = import_heal_pipeline("");
EXPECT_TRUE(bodies.empty());
}
TEST(TolerantModelingTest, ImportHealPipeline_InvalidStep) {
auto bodies = import_heal_pipeline("garbage data");
EXPECT_TRUE(bodies.empty());
}
TEST(TolerantModelingTest, ImportHealPipeline_ValidStepData) {
// 测试最小合法 STEP 数据
std::string step_data = R"(
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Test'),'2;1');
FILE_SCHEMA(('CONFIG_CONTROL_DESIGN'));
ENDSEC;
DATA;
#1=CARTESIAN_POINT('',(0.,0.,0.));
#2=CARTESIAN_POINT('',(1.,0.,0.));
#3=CARTESIAN_POINT('',(1.,1.,0.));
#4=CARTESIAN_POINT('',(0.,1.,0.));
ENDSEC;
END-ISO-10303-21;
)";
auto bodies = import_heal_pipeline(step_data);
// 此数据没有完整拓扑结构,预期为空或部分结果
// 主要验证不崩溃
EXPECT_TRUE(bodies.empty() || !bodies.empty());
}
// ═══════════════════════════════════════════════════════════
// 10. STEP import diagnostic
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, StepImportDiagnostic_AfterImport) {
std::string step_data = R"(
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Test'),'2;1');
FILE_SCHEMA(('CONFIG_CONTROL_DESIGN'));
ENDSEC;
DATA;
#1=CARTESIAN_POINT('',(0.,0.,0.));
ENDSEC;
END-ISO-10303-21;
)";
import_step_from_string(step_data);
auto report = step_import_diagnostic();
// 应至少解析到 1 个实体
EXPECT_GE(report.total_entities, 1);
}
TEST(TolerantModelingTest, StepImportDiagnostic_ReportFormat) {
std::string step_data = R"(
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Test'),'2;1');
FILE_SCHEMA(('CONFIG_CONTROL_DESIGN'));
ENDSEC;
DATA;
ENDSEC;
END-ISO-10303-21;
)";
import_step_from_string(step_data);
auto report = step_import_diagnostic();
auto str = report.report();
EXPECT_FALSE(str.empty());
EXPECT_TRUE(str.find("STEP Import Diagnostic") != std::string::npos);
}
// ═══════════════════════════════════════════════════════════
// 11. Damaged STEP recovery
// ═══════════════════════════════════════════════════════════
TEST(TolerantModelingTest, StepImport_DamagedEntityRecovery) {
// 包含损坏实体的 STEP 数据:不完整的实体定义
std::string step_data = R"(
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Damaged'),'2;1');
FILE_SCHEMA(('CONFIG_CONTROL_DESIGN'));
ENDSEC;
DATA;
#10=INCOMPLETE_ENTITY
#1=CARTESIAN_POINT('',(0.,0.,0.));
#2=CARTESIAN_POINT('',(1.,0.,0.));
ENDSEC;
END-ISO-10303-21;
)";
auto bodies = import_step_from_string(step_data);
// 即使有损坏实体,仍应能解析 CARTESIAN_POINT
auto report = step_import_diagnostic();
EXPECT_GE(report.skipped_damaged, 1);
}