Files
ViewDesignEngine/tests/brep/test_boolean_fallback.cpp
T
茂之钳 4b90438315
CI / Build & Test (push) Failing after 24s
CI / Release Build (push) Failing after 31s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v1.0.1): P2 improvements — TolerantEdge + .vde format + boolean fallback
P2 — TolerantEdge (ACIS):
- TolerantEdge: tube_radius, path_tolerance, is_within_tube, intersects
- detect_tolerance_conflicts: VertexInSphere/EdgeInTube/FaceGap
- resolve_tolerance_conflicts: auto merge vertices/edges/gaps

P2 — .vde Native Format (ACIS):
- Binary format: VdeHeader(32B), 6 SerializationSections
- save_vde/load_vde: full B-Rep topology + geometry + tolerance + attributes
- save_vde_json: debuggable JSON export

P2 — Boolean Fallback Ladder (Parasolid):
- 4-level cascade: SSI → tolerant → mesh → SDF
- Level 4 never fails (SDF + MC mathematical guarantee)
- BooleanFallbackResult with per-level diagnostics

Total: +~2650 lines across 8 files
2026-07-27 21:01:33 +08:00

263 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file test_boolean_fallback.cpp
* @brief 布尔策略回退阶梯 单元测试
*
* 覆盖场景:
* 1. Level 1 正常成功(ssi_boolean 直接返回)
* 2. Level 2 容差回退触发
* 3. Level 3 网格回退触发
* 4. Level 4 SDF 兜底
* 5. 诊断信息验证(levels_tried, diagnostics
* 6. brep_quality 标记
* 7. 空体/退化输入
* 8. summary() 输出
* 9. 三操差异(Union/Intersection/Difference
* 10. 异常安全(不崩溃)
*/
#include <gtest/gtest.h>
#include "vde/brep/brep.h"
#include "vde/brep/modeling.h"
#include "vde/brep/boolean_fallback.h"
#include "vde/brep/ssi_boolean.h"
#include "vde/brep/tolerant_modeling.h"
using namespace vde::brep;
using namespace vde::core;
// ═══════════════════════════════════════════════════════════
// Test 1: Level 1 正常成功 — 两个简单立方体 Union
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, Level1_Success_SimpleUnion) {
auto box1 = make_box(2.0, 2.0, 2.0);
auto box2 = make_box(2.0, 2.0, 2.0);
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
EXPECT_EQ(result.final_level, 1);
EXPECT_TRUE(result.brep_quality);
EXPECT_GT(result.result_body.num_vertices(), 0u);
EXPECT_GT(result.result_body.num_faces(), 0u);
EXPECT_TRUE(result.brep_body.has_value());
EXPECT_EQ(result.levels_tried.size(), 1u);
EXPECT_EQ(result.levels_tried[0], 1);
}
// ═══════════════════════════════════════════════════════════
// Test 2: Level 1 正常成功 — Intersection
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, Level1_Success_Intersection) {
auto box1 = make_box(4.0, 4.0, 4.0);
auto box2 = make_box(4.0, 4.0, 4.0);
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Intersection);
EXPECT_EQ(result.final_level, 1);
EXPECT_TRUE(result.brep_quality);
EXPECT_GT(result.result_body.num_vertices(), 0u);
}
// ═══════════════════════════════════════════════════════════
// Test 3: Level 1 正常成功 — Difference
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, Level1_Success_Difference) {
auto big = make_box(4.0, 4.0, 4.0);
auto small = make_box(2.0, 2.0, 2.0);
auto result = brep_boolean_robust(big, small, FallbackBoolOp::Difference);
EXPECT_EQ(result.final_level, 1);
EXPECT_TRUE(result.brep_quality);
EXPECT_GT(result.result_body.num_faces(), 0u);
}
// ═══════════════════════════════════════════════════════════
// Test 4: 诊断信息验证 — levels_tried + diagnostics 数量一致
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, Diagnostics_Tracking) {
auto box1 = make_box(2.0, 2.0, 2.0);
auto box2 = make_box(2.0, 2.0, 2.0);
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
// levels_tried 和 level_diagnostics 数量必须一致
EXPECT_EQ(result.levels_tried.size(), result.level_diagnostics.size());
// Level 1 成功时,只尝试了 1 级
EXPECT_EQ(result.levels_tried.size(), 1u);
EXPECT_EQ(result.final_level, 1);
// final_level 必须在 levels_tried 中
bool found = false;
for (int lv : result.levels_tried) {
if (lv == result.final_level) { found = true; break; }
}
EXPECT_TRUE(found);
}
// ═══════════════════════════════════════════════════════════
// Test 5: brep_quality 标记 — Level 1/2 为 true
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, BRepQuality_Flag_Level1) {
auto box1 = make_box(2.0, 2.0, 2.0);
auto box2 = make_box(2.0, 2.0, 2.0);
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
if (result.final_level <= 2) {
EXPECT_TRUE(result.brep_quality);
EXPECT_TRUE(result.brep_body.has_value());
} else {
EXPECT_FALSE(result.brep_quality);
}
}
// ═══════════════════════════════════════════════════════════
// Test 6: summary() 输出非空
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, Summary_ProducesOutput) {
auto box1 = make_box(2.0, 2.0, 2.0);
auto box2 = make_box(2.0, 2.0, 2.0);
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
auto s = result.summary();
EXPECT_FALSE(s.empty());
EXPECT_NE(s.find("BooleanFallbackResult"), std::string::npos);
EXPECT_NE(s.find("final_level"), std::string::npos);
EXPECT_NE(s.find("levels_tried"), std::string::npos);
}
// ═══════════════════════════════════════════════════════════
// Test 7: 空体输入不崩溃
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, EmptyBody_DoesNotCrash) {
BrepModel empty_a;
BrepModel empty_b;
auto box = make_box(2.0, 2.0, 2.0);
// 两个空体
auto r1 = brep_boolean_robust(empty_a, empty_b, FallbackBoolOp::Union);
EXPECT_NO_THROW({ auto s = r1.summary(); (void)s; });
// 一个空体 + 正常体
auto r2 = brep_boolean_robust(empty_a, box, FallbackBoolOp::Union);
EXPECT_NO_THROW({ auto s = r2.summary(); (void)s; });
auto r3 = brep_boolean_robust(box, empty_a, FallbackBoolOp::Intersection);
EXPECT_NO_THROW({ auto s = r3.summary(); (void)s; });
}
// ═══════════════════════════════════════════════════════════
// Test 8: 每级内部函数独立可调
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, LevelFunctions_IndependentlyCallable) {
auto box1 = make_box(2.0, 2.0, 2.0);
auto box2 = make_box(3.0, 3.0, 3.0);
// Level 1: try_ssi_boolean
{
auto [diag, ok] = try_ssi_boolean(box1, box2, FallbackBoolOp::Union);
EXPECT_TRUE(ok);
EXPECT_TRUE(diag.failed_faces.empty());
}
// Level 2: try_tolerant_boolean
{
auto [diag, ok] = try_tolerant_boolean(box1, box2, FallbackBoolOp::Union);
EXPECT_TRUE(ok);
EXPECT_TRUE(diag.failed_faces.empty());
}
// Level 3: try_mesh_boolean_fallback
{
auto [mesh, ok] = try_mesh_boolean_fallback(box1, box2, FallbackBoolOp::Union);
EXPECT_TRUE(ok);
EXPECT_GT(mesh.num_vertices(), 0u);
}
// Level 4: try_sdf_boolean_fallback (永不失败)
{
auto mesh = try_sdf_boolean_fallback(box1, box2, FallbackBoolOp::Union);
EXPECT_GT(mesh.num_vertices(), 0u);
}
}
// ═══════════════════════════════════════════════════════════
// Test 9: 三操作都支持(Union / Intersection / Difference
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, AllThreeOperations_Supported) {
auto box1 = make_box(2.0, 2.0, 2.0);
auto box2 = make_box(3.0, 3.0, 3.0);
auto r_union = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
EXPECT_GT(r_union.result_body.num_vertices(), 0u);
auto r_inter = brep_boolean_robust(box1, box2, FallbackBoolOp::Intersection);
EXPECT_GT(r_inter.result_body.num_vertices(), 0u);
auto r_diff = brep_boolean_robust(box2, box1, FallbackBoolOp::Difference);
EXPECT_GT(r_diff.result_body.num_vertices(), 0u);
}
// ═══════════════════════════════════════════════════════════
// Test 10: 重叠立方体 — 各级都能产生有效结果
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, OverlappingBoxes_AllLevelsProduceValidMesh) {
auto box1 = make_box(2.0, 2.0, 2.0);
auto box2 = make_box(2.0, 2.0, 2.0);
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
// 无论哪一级成功,结果网格必须有效
EXPECT_GT(result.result_body.num_vertices(), 0u);
EXPECT_GT(result.result_body.num_faces(), 0u);
// final_level 在 1-4 范围内
EXPECT_GE(result.final_level, 1);
EXPECT_LE(result.final_level, 4);
// 诊断链非空
EXPECT_FALSE(result.level_diagnostics.empty());
}
// ═══════════════════════════════════════════════════════════
// Test 11: SDF Level 4 单独验证(盒+球并集)
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, SDF_Level4_BoxWithSphere) {
auto box = make_box(3.0, 3.0, 3.0);
auto sphere = make_sphere(1.5);
auto mesh = try_sdf_boolean_fallback(box, sphere, FallbackBoolOp::Union);
EXPECT_GT(mesh.num_vertices(), 0u);
EXPECT_GT(mesh.num_faces(), 0u);
}
// ═══════════════════════════════════════════════════════════
// Test 12: 异常安全 — 不崩溃
// ═══════════════════════════════════════════════════════════
TEST(BooleanFallbackTest, ExceptionSafety_AllOperations) {
auto box = make_box(2.0, 2.0, 2.0);
auto sphere = make_sphere(1.0);
// 重复相同操作不应该崩溃
for (int i = 0; i < 3; ++i) {
EXPECT_NO_THROW({
auto r = brep_boolean_robust(box, sphere, FallbackBoolOp::Union);
auto s = r.summary(); (void)s;
});
}
}