feat(v11): CI/CD + regression tests + fuzzing + benchmarks + code quality
CI / Build & Test (push) Failing after 41s
CI / Release Build (push) Failing after 30s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled

v11.1 — Test Infrastructure (14 files):
- .github/workflows/ci.yml: Ubuntu 22.04 + GCC 11, auto ctest on push
- tests/regression/: degenerate geometry, extreme coords, thin wall, large models
- tests/fuzz/: SDF random CSG, random booleans, format round-trip, continuous mode
- tests/benchmark/: boolean perf, MC perf, IO perf benchmarks
- docs/benchmark-report.md: benchmark template

v11.2 — Code Quality + Docs:
- .clang-tidy: 15 check categories, 22 exclusions
- .github/workflows/static-analysis.yml: clang-tidy CI scan
- CODEOWNERS, SECURITY.md
- docs: API overview, testing guide, contributing guide
- README: module capability overview table

Pending: binary formats + curve projection (retrying)
This commit is contained in:
茂之钳
2026-07-27 01:10:58 +08:00
parent 97ee97057b
commit 7b76689ea1
30 changed files with 4318 additions and 577 deletions
+4
View File
@@ -0,0 +1,4 @@
add_vde_test(regression_degenerate)
add_vde_test(regression_extreme)
add_vde_test(regression_thin_wall)
add_vde_test(regression_large_model)
+214
View File
@@ -0,0 +1,214 @@
/// @file regression_degenerate.cpp
/// @brief 退化几何回归测试集
///
/// 覆盖场景:
/// 1. 零长度边(zero-length edge
/// 2. 零面积面(zero-area face
/// 3. 重合顶点(coincident vertices
/// 4. 自交体(self-intersecting body
///
/// 每个测试:构造退化体 → 布尔/修复/验证 → 断言不崩溃
#include <gtest/gtest.h>
#include "vde/brep/brep.h"
#include "vde/brep/modeling.h"
#include "vde/brep/brep_boolean.h"
#include "vde/brep/brep_validate.h"
#include "vde/brep/brep_heal.h"
#include "vde/core/point.h"
#include <cmath>
#include <limits>
using namespace vde::brep;
using namespace vde::core;
// ── 辅助函数 ──
/// 断言 BrepModel 至少不会崩溃(尝试布尔、验证、修复都不会抛异常)
void assert_no_crash(const BrepModel& body) {
// 1. 基本验证:能调用 is_valid()
EXPECT_NO_THROW({ body.is_valid(); });
// 2. 验证:能调用 validate()
EXPECT_NO_THROW({ auto result = validate(body); (void)result; });
// 3. 布尔自交:AA, A∩A, A\\A
EXPECT_NO_THROW({ auto u = brep_union(body, body); (void)u; });
EXPECT_NO_THROW({ auto i = brep_intersection(body, body); (void)i; });
EXPECT_NO_THROW({ auto d = brep_difference(body, body); (void)d; });
// 4. 修复:heal_topology 不崩溃
{
BrepModel copy = body;
EXPECT_NO_THROW({ heal_topology(copy, 1e-6); });
}
// 5. 网格导出:to_mesh 不崩溃
EXPECT_NO_THROW({ auto mesh = body.to_mesh(); (void)mesh; });
}
// ═══════════════════════════════════════════════
// 零长度边测试
// ═══════════════════════════════════════════════
TEST(RegressionDegenerate, ZeroLengthEdge_Box) {
// 通过 make_box(0, 1, 1) 构造含退化边的体
// 宽度为 0 → 侧面退化为零面积面
EXPECT_NO_THROW({
auto body = make_box(0.0, 1.0, 1.0);
assert_no_crash(body);
});
}
TEST(RegressionDegenerate, ZeroLengthEdge_ExtremeSmall) {
// 极窄长方体(宽 ≤ 1e-12)→ 近乎零长度边
for (double w : {1e-12, 1e-10, 1e-8}) {
EXPECT_NO_THROW({
auto body = make_box(w, 1.0, 1.0);
assert_no_crash(body);
});
}
}
// ═══════════════════════════════════════════════
// 零面积面测试
// ═══════════════════════════════════════════════
TEST(RegressionDegenerate, ZeroAreaFace_FlatBox) {
// 一个维度为 0 → 退化成一个平面
EXPECT_NO_THROW({
auto box0 = make_box(0.0, 5.0, 5.0);
assert_no_crash(box0);
// 与正常 box 做布尔
auto box = make_box(3.0, 3.0, 3.0);
auto u = brep_union(box0, box);
(void)u.is_valid();
auto i = brep_intersection(box0, box);
(void)i.is_valid();
auto d = brep_difference(box, box0);
(void)d.is_valid();
});
}
TEST(RegressionDegenerate, ZeroAreaFace_Sliver) {
// 一个极其扁平的体(高度极其小)
for (double h : {1e-12, 1e-10, 1e-9}) {
EXPECT_NO_THROW({
auto thin = make_box(1.0, h, 1.0);
assert_no_crash(thin);
// 瘦短板与正常盒子的布尔
auto box = make_box(2.0, 2.0, 2.0);
auto u = brep_union(thin, box);
(void)u.is_valid();
});
}
}
// ═══════════════════════════════════════════════
// 重合顶点测试
// ═══════════════════════════════════════════════
TEST(RegressionDegenerate, CoincidentVertices_SelfUnion) {
// 两个完全相同的盒子做布尔 → 所有面/边/顶点重合
auto box = make_box(2.0, 2.0, 2.0);
EXPECT_NO_THROW({
auto u = brep_union(box, box);
EXPECT_TRUE(u.is_valid());
auto result = validate(u);
(void)result;
});
}
TEST(RegressionDegenerate, CoincidentVertices_SelfIntersection) {
auto box = make_box(2.0, 2.0, 2.0);
EXPECT_NO_THROW({
auto i = brep_intersection(box, box);
EXPECT_TRUE(i.is_valid());
});
}
TEST(RegressionDegenerate, CoincidentVertices_SelfDifference) {
auto box = make_box(2.0, 2.0, 2.0);
EXPECT_NO_THROW({
auto d = brep_difference(box, box);
EXPECT_TRUE(d.is_valid());
});
}
// ═══════════════════════════════════════════════
// 自交体测试(通过治疗修复验证)
// ═══════════════════════════════════════════════
TEST(RegressionDegenerate, SelfIntersecting_Heal) {
// 使用极小偏移的重复体模拟自交场景
auto box = make_box(2.0, 2.0, 2.0);
// 重复布尔运算产生复杂拓扑,至少不应该崩溃
for (int i = 0; i < 5; ++i) {
EXPECT_NO_THROW({
auto u = brep_union(box, box);
auto result = validate(u);
(void)result;
BrepModel copy = u;
heal_topology(copy, 1e-4);
});
}
// 使用不同尺寸的盒子布尔链式操作
auto small = make_box(0.5, 0.5, 0.5);
EXPECT_NO_THROW({
auto diff = brep_difference(box, small);
auto result = validate(diff);
(void)result;
heal_topology(diff, 1e-4);
});
}
// ═══════════════════════════════════════════════
// 退化圆柱测试
// ═══════════════════════════════════════════════
TEST(RegressionDegenerate, DegenerateCylinder_ZeroRadius) {
// 半径为 0 的圆柱(退化为线)
EXPECT_NO_THROW({
auto cyl = make_cylinder(0.0, 1.0, 8);
assert_no_crash(cyl);
});
}
TEST(RegressionDegenerate, DegenerateCylinder_ZeroHeight) {
// 高度为 0 的圆柱(退化为圆盘)
EXPECT_NO_THROW({
auto cyl = make_cylinder(1.0, 0.0, 16);
assert_no_crash(cyl);
});
}
TEST(RegressionDegenerate, DegenerateSphere_ZeroRadius) {
// 半径为 0 的球(退化为点)
EXPECT_NO_THROW({
auto sph = make_sphere(0.0, 8, 4);
assert_no_crash(sph);
});
}
// ═══════════════════════════════════════════════
// 退化修复流水线测试
// ═══════════════════════════════════════════════
TEST(RegressionDegenerate, HealDegenerateModel) {
// 从退化体开始,通过修复流水线处理
auto thin = make_box(1.0, 1e-12, 1.0);
EXPECT_NO_THROW({
int n_gaps = heal_gaps(thin, 1e-6);
(void)n_gaps;
int n_slivers = heal_slivers(thin);
(void)n_slivers;
int n_oriented = heal_orientation(thin);
(void)n_oriented;
bool ok = heal_topology(thin, 1e-4);
(void)ok;
});
}
+225
View File
@@ -0,0 +1,225 @@
/// @file regression_extreme.cpp
/// @brief 极端坐标回归测试
///
/// 覆盖场景:
/// 1. 极小坐标(1e-15
/// 2. 极大坐标(1e15
/// 3. NaN 输入
/// 4. Inf 输入
///
/// 每个测试:构造极端体 → 布尔/修复/验证 → 断言不崩溃
#include <gtest/gtest.h>
#include "vde/brep/brep.h"
#include "vde/brep/modeling.h"
#include "vde/brep/brep_boolean.h"
#include "vde/brep/brep_validate.h"
#include "vde/brep/brep_heal.h"
#include "vde/core/point.h"
#include <cmath>
#include <limits>
using namespace vde::brep;
using namespace vde::core;
// ── 极小坐标测试 ──
TEST(RegressionExtreme, TinyCoordinates_1eMinus15) {
// 极小的盒子:尺寸为 1e-15
double s = 1e-15;
EXPECT_NO_THROW({
auto tiny = make_box(s, s, s);
// 验证合法性
EXPECT_TRUE(tiny.is_valid());
// 布尔运算:与正常盒子
auto box = make_box(1.0, 1.0, 1.0);
auto u = brep_union(tiny, box);
EXPECT_TRUE(u.is_valid());
auto i = brep_intersection(tiny, box);
EXPECT_TRUE(i.is_valid());
// 导出网格
auto mesh = tiny.to_mesh();
(void)mesh;
});
}
TEST(RegressionExtreme, TinyCoordinates_1eMinus10) {
double s = 1e-10;
EXPECT_NO_THROW({
auto tiny = make_box(s, s, s);
EXPECT_TRUE(tiny.is_valid());
auto mesh = tiny.to_mesh();
(void)mesh;
// 修复流水线
BrepModel copy = tiny;
heal_topology(copy, s * 0.1);
});
}
TEST(RegressionExtreme, TinyCoordinates_Mixed) {
// 一个维度极小,其他正常
EXPECT_NO_THROW({
auto thin = make_box(1e-15, 1.0, 1.0);
EXPECT_NO_THROW({ thin.is_valid(); });
auto result = validate(thin);
(void)result;
auto mesh = thin.to_mesh();
(void)mesh;
});
}
// ── 极大坐标测试 ──
TEST(RegressionExtreme, HugeCoordinates_1e15) {
double s = 1e15;
EXPECT_NO_THROW({
auto huge = make_box(s, s, s);
// 基本验证不崩溃
EXPECT_TRUE(huge.is_valid());
// 包围盒计算
auto bb = huge.bounds();
EXPECT_TRUE(std::isfinite(bb.min().x()));
EXPECT_TRUE(std::isfinite(bb.max().x()));
// 布尔自交
auto u = brep_union(huge, huge);
EXPECT_TRUE(u.is_valid());
// 网格导出(大尺寸模型)
auto mesh = huge.to_mesh(0.01 * s);
(void)mesh;
});
}
TEST(RegressionExtreme, HugeVsTiny) {
// 巨大体与极小体的布尔
EXPECT_NO_THROW({
auto huge = make_box(1e12, 1e12, 1e12);
auto tiny = make_box(1e-12, 1e-12, 1e-12);
auto u = brep_union(huge, tiny);
EXPECT_TRUE(u.is_valid());
auto i = brep_intersection(huge, tiny);
EXPECT_TRUE(i.is_valid());
});
}
// ── NaN 输入测试 ──
TEST(RegressionExtreme, NaN_Handling) {
double nan = std::numeric_limits<double>::quiet_NaN();
// make_box 应对 NaN 表现得优雅(返回无效体或抛出,但绝不能崩溃)
EXPECT_NO_THROW({
auto body = make_box(nan, 1.0, 1.0);
(void)body;
});
EXPECT_NO_THROW({
auto body = make_box(1.0, nan, 1.0);
(void)body;
});
EXPECT_NO_THROW({
auto body = make_box(1.0, 1.0, nan);
(void)body;
});
EXPECT_NO_THROW({
auto body = make_box(nan, nan, nan);
(void)body;
});
}
TEST(RegressionExtreme, NaN_BooleanResilience) {
// 若 NaN 产生了有效的 body,检查布尔运算不崩溃
auto good = make_box(2.0, 2.0, 2.0);
// 将所有尝试包裹在 EXPECT_NO_THROW 中
EXPECT_NO_THROW({
try {
auto bad = make_box(std::numeric_limits<double>::quiet_NaN(), 1.0, 1.0);
auto u = brep_union(good, bad);
(void)u;
} catch (...) {
// 允许抛异常但不应崩溃
}
});
}
// ── Inf 输入测试 ──
TEST(RegressionExtreme, Inf_Handling) {
double inf = std::numeric_limits<double>::infinity();
EXPECT_NO_THROW({
auto body = make_box(inf, 1.0, 1.0);
(void)body;
});
EXPECT_NO_THROW({
auto body = make_box(1.0, inf, 1.0);
(void)body;
});
EXPECT_NO_THROW({
auto body = make_box(1.0, 1.0, inf);
(void)body;
});
}
// ── 极大值 vs 极小值布尔 ──
TEST(RegressionExtreme, MaxDouble_Boolean) {
double big = 1e100;
double small = 1e-100;
EXPECT_NO_THROW({
auto a = make_box(big, 1.0, 1.0);
auto b = make_box(small, 1.0, 1.0);
auto u = brep_union(a, b);
(void)u.is_valid();
});
}
// ── 边界值批处理 ──
TEST(RegressionExtreme, BoundaryValuesBatch) {
// 一系列边界值,确保每个都不会崩溃
double values[] = {
0.0,
std::numeric_limits<double>::min(),
std::numeric_limits<double>::denorm_min(),
std::numeric_limits<double>::epsilon(),
1e-15, 1e-10, 1e-5,
1.0, 10.0, 100.0,
1e5, 1e10, 1e15,
std::numeric_limits<double>::max() * 0.5,
};
for (double v : values) {
if (!std::isfinite(v)) continue;
EXPECT_NO_THROW({
auto body = make_box(v, v, v);
body.is_valid();
auto mesh = body.to_mesh();
(void)mesh;
}) << "failed at v=" << v;
}
// 混合边界值布尔
auto a = make_box(values[0], 1.0, 1.0);
for (int i = 1; i < 10; ++i) {
if (!std::isfinite(values[i])) continue;
EXPECT_NO_THROW({
auto b = make_box(values[i], 1.0, 1.0);
auto u = brep_union(a, b);
(void)u.is_valid();
}) << "failed at i=" << i;
}
}
+188
View File
@@ -0,0 +1,188 @@
/// @file regression_large_model.cpp
/// @brief 大面数回归测试(1000+ 面模型)
///
/// 覆盖场景:
/// 1. 通过高细分球体/圆柱产生 1000+ 面的模型
/// 2. 大面数模型的布尔运算
/// 3. 大面数模型的验证(validate
/// 4. 大面数模型的修复
///
/// 每个测试:构造大面数体 → 布尔/修复/验证 → 断言不崩溃
#include <gtest/gtest.h>
#include "vde/brep/brep.h"
#include "vde/brep/modeling.h"
#include "vde/brep/brep_boolean.h"
#include "vde/brep/brep_validate.h"
#include "vde/brep/brep_heal.h"
#include "vde/core/point.h"
#include <cmath>
using namespace vde::brep;
using namespace vde::core;
/// 生成一个高面数的球体
/// seg_u × seg_v 段 → 大约 2 * seg_u * seg_v 个面
/// 例如 seg_u=50, seg_v=50 → 约 5000 面
static BrepModel make_highres_sphere(double radius, int seg_u, int seg_v) {
return make_sphere(radius, seg_u, seg_v);
}
/// 生成一个高面数的圆柱体
/// segments 段 → 侧面有 segments * 1 个面,加上顶/底面
static BrepModel make_highres_cylinder(double radius, double height, int segments) {
return make_cylinder(radius, height, segments);
}
// ═══════════════════════════════════════════════
// 1000+ 面模型 - 球体
// ═══════════════════════════════════════════════
TEST(RegressionLargeModel, Sphere_1000PlusFaces) {
// seg_u=25, seg_v=20 → 2*25*20 = 1000 faces
EXPECT_NO_THROW({
auto sphere = make_sphere(10.0, 25, 20);
EXPECT_TRUE(sphere.is_valid());
EXPECT_GE(sphere.num_faces(), 800u); // 至少 800 面
// 验证
auto result = validate(sphere);
(void)result;
// 导出网格
auto mesh = sphere.to_mesh(0.05);
(void)mesh;
});
}
TEST(RegressionLargeModel, Sphere_5000PlusFaces) {
// seg_u=55, seg_v=50 → 2*55*50 = 5500 faces
EXPECT_NO_THROW({
auto sphere = make_sphere(5.0, 55, 50);
EXPECT_TRUE(sphere.is_valid());
EXPECT_GE(sphere.num_faces(), 4000u);
auto result = validate(sphere);
(void)result;
auto mesh = sphere.to_mesh(0.01);
(void)mesh;
});
}
// ═══════════════════════════════════════════════
// 1000+ 面模型 - 圆柱体
// ═══════════════════════════════════════════════
TEST(RegressionLargeModel, Cylinder_1000PlusFaces) {
// segments=1000 → 侧面有很多面
EXPECT_NO_THROW({
auto cyl = make_cylinder(5.0, 10.0, 1000);
EXPECT_TRUE(cyl.is_valid());
// seg=1000 时侧面有 1000 个面 + 上下各 1(或更多)面
EXPECT_GE(cyl.num_faces(), 900u);
auto result = validate(cyl);
(void)result;
auto mesh = cyl.to_mesh();
(void)mesh;
});
}
// ═══════════════════════════════════════════════
// 大面数模型布尔运算
// ═══════════════════════════════════════════════
TEST(RegressionLargeModel, Boolean_LargeSphereUnion) {
auto s1 = make_sphere(5.0, 30, 24); // ~1440 faces
auto s2 = make_sphere(3.0, 20, 16); // ~640 faces
EXPECT_NO_THROW({
auto u = brep_union(s1, s2);
EXPECT_TRUE(u.is_valid());
auto result = validate(u);
(void)result;
});
}
TEST(RegressionLargeModel, Boolean_LargeSphereIntersection) {
auto s1 = make_sphere(5.0, 30, 24);
auto s2 = make_sphere(3.0, 24, 20);
EXPECT_NO_THROW({
auto i = brep_intersection(s1, s2);
EXPECT_TRUE(i.is_valid());
});
}
TEST(RegressionLargeModel, Boolean_LargeCylinders) {
// 高分辨率圆柱布尔运算
auto c1 = make_cylinder(3.0, 10.0, 500);
auto c2 = make_cylinder(2.0, 8.0, 300);
EXPECT_NO_THROW({
auto u = brep_union(c1, c2);
EXPECT_TRUE(u.is_valid());
auto d = brep_difference(c1, c2);
EXPECT_TRUE(d.is_valid());
});
}
// ═══════════════════════════════════════════════
// 大面数模型修复
// ═══════════════════════════════════════════════
TEST(RegressionLargeModel, Heal_LargeModel) {
auto sphere = make_sphere(10.0, 30, 24);
EXPECT_GE(sphere.num_faces(), 1000u);
EXPECT_NO_THROW({
// 修复大面数模型
heal_gaps(sphere, 1e-6);
heal_slivers(sphere);
bool ok = heal_topology(sphere, 1e-4);
(void)ok;
});
}
// ═══════════════════════════════════════════════
// 多体布尔链式操作(压力测试)
// ═══════════════════════════════════════════════
TEST(RegressionLargeModel, Stress_ChainedBooleans) {
auto box = make_box(10.0, 10.0, 10.0);
auto cyl = make_cylinder(0.5, 15.0, 128);
// 连续执行多次布尔运算,最终模型可能包含大量面
EXPECT_NO_THROW({
auto u = brep_union(box, cyl);
for (int i = 0; i < 3; ++i) {
auto d = brep_difference(u, make_sphere(0.3 + i * 0.1, 16, 8));
u = d; // chain
}
EXPECT_TRUE(u.is_valid());
auto result = validate(u);
(void)result;
auto mesh = u.to_mesh(0.05);
(void)mesh;
});
}
// ═══════════════════════════════════════════════
// 大面数模型导出
// ═══════════════════════════════════════════════
TEST(RegressionLargeModel, Export_LargeMesh) {
auto sphere = make_sphere(10.0, 40, 32); // ~2560 faces
EXPECT_GE(sphere.num_faces(), 2000u);
EXPECT_NO_THROW({
// 导出为精细网格
auto mesh = sphere.to_mesh(0.001);
(void)mesh.num_vertices();
(void)mesh.num_faces();
});
}
+187
View File
@@ -0,0 +1,187 @@
/// @file regression_thin_wall.cpp
/// @brief 薄壁件回归测试(壁厚 0.001mm)
///
/// 覆盖场景:
/// 1. 极薄壁盒子
/// 2. 薄壁壳的布尔运算
/// 3. 薄壁圆柱和球体
/// 4. 抽壳后的薄壁验证
///
/// 每个测试:构造薄壁体 → 布尔/修复/验证 → 断言不崩溃
#include <gtest/gtest.h>
#include "vde/brep/brep.h"
#include "vde/brep/modeling.h"
#include "vde/brep/brep_boolean.h"
#include "vde/brep/brep_validate.h"
#include "vde/brep/brep_heal.h"
#include "vde/core/point.h"
#include <cmath>
using namespace vde::brep;
using namespace vde::core;
// 行业标准薄壁厚度:0.001mm = 1e-6 米(假设模型单位为米)
static constexpr double THIN_WALL = 1e-6;
// ── 极薄壁盒子测试 ──
TEST(RegressionThinWall, Box_ThinWall1um) {
// 1 μm 壁厚的盒子
EXPECT_NO_THROW({
auto body = make_box(THIN_WALL, 1.0, 1.0);
EXPECT_TRUE(body.is_valid());
// 验证
auto result = validate(body);
(void)result;
// 导出网格不应崩溃
auto mesh = body.to_mesh();
(void)mesh;
});
}
TEST(RegressionThinWall, Box_ThinWallVarious) {
// 多种薄壁厚度
for (double t : {1e-3, 1e-4, 1e-5, 1e-6, 1e-7}) {
EXPECT_NO_THROW({
auto body = make_box(t, 1.0, 1.0);
body.is_valid();
auto mesh = body.to_mesh();
(void)mesh;
}) << "failed at thickness=" << t;
}
}
TEST(RegressionThinWall, Box_AllDimensionsThin) {
// 三个维度都极薄 → 几乎是一个点
EXPECT_NO_THROW({
auto body = make_box(THIN_WALL, THIN_WALL, THIN_WALL);
body.is_valid();
auto mesh = body.to_mesh();
(void)mesh;
});
}
// ── 薄壁布尔运算 ──
TEST(RegressionThinWall, Boolean_ThinWallUnion) {
auto thin = make_box(THIN_WALL, 1.0, 1.0);
auto normal = make_box(2.0, 2.0, 2.0);
EXPECT_NO_THROW({
auto u = brep_union(thin, normal);
EXPECT_TRUE(u.is_valid());
auto i = brep_intersection(thin, normal);
EXPECT_TRUE(i.is_valid());
auto d = brep_difference(normal, thin);
EXPECT_TRUE(d.is_valid());
});
}
TEST(RegressionThinWall, Boolean_TwoThinWalls) {
// 两个薄壁体之间的布尔运算
auto a = make_box(THIN_WALL, 2.0, 2.0);
auto b = make_box(2.0, THIN_WALL, 2.0);
EXPECT_NO_THROW({
auto u = brep_union(a, b);
EXPECT_TRUE(u.is_valid());
auto i = brep_intersection(a, b);
EXPECT_TRUE(i.is_valid());
});
}
// ── 薄壁圆柱 ──
TEST(RegressionThinWall, Cylinder_ThinRadius) {
// 极薄半径的圆柱(近似线)
for (double r : {1e-3, 1e-4, 1e-5, 1e-6}) {
EXPECT_NO_THROW({
auto cyl = make_cylinder(r, 2.0, 16);
EXPECT_TRUE(cyl.is_valid());
auto mesh = cyl.to_mesh();
(void)mesh;
}) << "failed at radius=" << r;
}
}
TEST(RegressionThinWall, Cylinder_ThinHeight) {
// 极薄高度的圆柱(近似圆盘)
for (double h : {1e-3, 1e-4, 1e-5, 1e-6}) {
EXPECT_NO_THROW({
auto cyl = make_cylinder(1.0, h, 16);
EXPECT_TRUE(cyl.is_valid());
auto mesh = cyl.to_mesh();
(void)mesh;
}) << "failed at height=" << h;
}
}
// ── 薄壁球体 ──
TEST(RegressionThinWall, Sphere_ThinRadius) {
for (double r : {1e-3, 1e-4, 1e-5, 1e-6}) {
EXPECT_NO_THROW({
auto sph = make_sphere(r, 16, 8);
EXPECT_TRUE(sph.is_valid());
auto mesh = sph.to_mesh();
(void)mesh;
}) << "failed at radius=" << r;
}
}
// ── 抽壳后的薄壁体 ──
TEST(RegressionThinWall, Shell_ThinThickness) {
// shell() 生成一个挖空的体 → 然后对其做薄壁操作
auto box = make_box(10.0, 10.0, 10.0);
EXPECT_NO_THROW({
auto shelled = shell(box, 0, THIN_WALL);
EXPECT_TRUE(shelled.is_valid());
auto result = validate(shelled);
(void)result;
// 壳上的布尔运算
auto cyl = make_cylinder(1.0, 20.0, 32);
auto u = brep_union(shelled, cyl);
(void)u.is_valid();
});
}
// ── 薄壁修复 ──
TEST(RegressionThinWall, Heal_ThinWall) {
auto thin = make_box(THIN_WALL, 1.0, 1.0);
EXPECT_NO_THROW({
// 使用容差修复(容差远大于壁厚的情况)
heal_gaps(thin, 1e-6);
heal_slivers(thin);
bool ok = heal_topology(thin, 1e-4);
(void)ok;
auto result = validate(thin);
(void)result;
});
}
// ── 压力测试:薄壁链式布尔 ──
TEST(RegressionThinWall, Stress_ChainedBoolean) {
auto base = make_box(5.0, 5.0, 5.0);
// 连续对薄壁体做布尔运算
for (int i = 0; i < 5; ++i) {
double t = THIN_WALL * (i + 1) * 10;
EXPECT_NO_THROW({
auto thin = make_box(t, 0.5, 0.5);
auto u = brep_union(base, thin);
(void)u.is_valid();
}) << "failed at iteration " << i;
}
}