Files
ViewDesignEngine/tests/curves/test_class_a_surfacing.cpp
T
茂之钳 63fba5389a
CI / Build & Test (push) Failing after 31s
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(v10): tolerant modeling + Class-A deep + hex mesh + constraint solver + drawing standards
v10.1 — Tolerant Modeling (对标 Parasolid):
- tolerant_modeling.h/.cpp: TolerantVertex/Edge, merge_within_tolerance (BFS)
- gap_bridging (free edge detection → triangle filling)
- overlap_resolution (normal+centroid+AABB detection)
- tolerant_boolean (heal→degenerate detect→boolean→heal) + BooleanDiagnostic
- import_heal_pipeline (STEP one-shot repair)
- ssi_boolean enhanced: coplanar/collinear/tangent detection, GMP on all classify paths
- step_import enhanced: broken file recovery, non-standard entity mapping, diagnostic report
- 30 tests, 4112 total lines

v10.2 — Class-A Deep + Hex Mesh (对标 CGM + ANSYS):
- class_a_surfacing enhanced: g3_blend_with_constraints, surface_energy_minimization
- curvature_continuity_optimization, reflection_line_discontinuity (+720 lines)
- fea_mesh enhanced: mapped_hex, submapped_hex, multi_block_hex (TFI), hex_quality_optimization (+522 lines)
- 22 tests

v10.3 — Constraint Solver + Drawing Standards (对标 D-Cubed + AutoCAD):
- constraint_solver enhanced: DOFAnalyzer, RedundancyDetector, ConstraintPropagator, KinematicChainSolver
- drawing_standards.h/.cpp: IsoStandard (ISO 128/129), AnsiStandard (ASME Y14.5), JisStandard (JIS B 0001)
- 12 tests

12 files, ~5000 lines, 64 tests. Target: 87% → 93%
2026-07-27 00:33:30 +08:00

550 lines
17 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.
#include <gtest/gtest.h>
#include "vde/curves/class_a_surfacing.h"
#include "vde/curves/nurbs_curve.h"
#include "vde/curves/nurbs_surface.h"
#include <cmath>
using namespace vde::curves;
using namespace vde::core;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/// 创建平面 NURBS 曲面(XY 平面,[0,1]×[0,1]
static NurbsSurface plane_surface() {
std::vector<std::vector<Point3D>> grid = {
{Point3D(0,0,0), Point3D(0,1,0)},
{Point3D(1,0,0), Point3D(1,1,0)}
};
return NurbsSurface(grid, {0,0,1,1}, {0,0,1,1}, {{1,1},{1,1}}, 1, 1);
}
/// 创建二次曲面(抛物面样)
static NurbsSurface quad_surface() {
std::vector<std::vector<Point3D>> grid = {
{Point3D(0,0,0), Point3D(0,1,0.5), Point3D(0,2,0)},
{Point3D(1,0,0.5), Point3D(1,1,1), Point3D(1,2,0.5)},
{Point3D(2,0,0), Point3D(2,1,0.5), Point3D(2,2,0)}
};
return NurbsSurface(grid,
{0,0,0,1,1,1}, {0,0,0,1,1,1},
{{1,1,1},{1,1,1},{1,1,1}}, 2, 2);
}
/// 创建圆柱面
static NurbsSurface cylinder_surface() {
double R = 1.0;
std::vector<std::vector<Point3D>> grid(5, std::vector<Point3D>(3));
for (int i = 0; i < 5; ++i) {
double angle = i * M_PI / 2.0;
double x = R * std::cos(angle);
double y = R * std::sin(angle);
for (int j = 0; j < 3; ++j) {
grid[i][j] = Point3D(x, y, j * 0.5);
}
}
std::vector<std::vector<double>> w(5, std::vector<double>(3, 1.0));
return NurbsSurface(grid,
{0,0,0,0,0.5,1,1,1,1},
{0,0,0,1,1,1}, w, 3, 2);
}
/// 沿 Z 偏移的平面
static NurbsSurface offset_plane(double z) {
std::vector<std::vector<Point3D>> grid = {
{Point3D(0,0,z), Point3D(0,1,z)},
{Point3D(1,0,z), Point3D(1,1,z)}
};
return NurbsSurface(grid, {0,0,1,1}, {0,0,1,1}, {{1,1},{1,1}}, 1, 1);
}
// ---------------------------------------------------------------------------
// Test: G3 Blend — G3 连续性过渡
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, G3Blend_IdenticalPlanes) {
auto a = plane_surface();
auto b = offset_plane(0.0); // 重合平面
EdgeParams params;
params.edge_a = 3; // vmax of a
params.edge_b = 2; // vmin of b
params.blend_width = 0.2;
auto result = g3_blend(a, b, params);
// 过渡面应该非退化
auto [nu, nv] = result.blend_surface.num_control_points();
EXPECT_GT(nu, 0);
EXPECT_GT(nv, 0);
}
TEST(ClassASurfacingTest, G3Blend_ContinuityChecks) {
auto a = plane_surface();
auto b = offset_plane(0.0);
EdgeParams params;
params.blend_width = 0.15;
auto result = g3_blend(a, b, params);
// G0 位置连续
EXPECT_TRUE(result.g0_ok);
EXPECT_LT(result.max_position_error, 1e-3);
// G1 切平面连续
EXPECT_TRUE(result.g1_ok);
EXPECT_LT(result.max_tangent_error, 0.02);
}
TEST(ClassASurfacingTest, G3Blend_ResultHasBlendSurface) {
auto a = plane_surface();
auto b = offset_plane(1.0); // 间距 1
EdgeParams params;
params.edge_a = 3;
params.edge_b = 2;
params.blend_width = 0.5;
auto result = g3_blend(a, b, params);
auto [nu, nv] = result.blend_surface.num_control_points();
// 应生成有效的过渡曲面
EXPECT_GE(nu, 2);
EXPECT_EQ(nv, 4); // G3 需要 4 排控制点
}
// ---------------------------------------------------------------------------
// Test: Curvature Matching — 曲率匹配
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, CurvatureMatching_FlatPlanes) {
auto a = plane_surface();
auto b = offset_plane(0.0);
CurvatureMatchOptions opts;
opts.samples_per_edge = 10;
opts.max_iterations = 20;
auto result = curvature_matching(a, b, opts);
// 平面曲率处处为0,匹配后应保持
EXPECT_LT(result.final_rms_curvature_diff, 1.0);
EXPECT_GT(result.iterations, 0);
}
TEST(ClassASurfacingTest, CurvatureMatching_ReturnsMatchedSurface) {
auto a = quad_surface();
auto b = quad_surface();
CurvatureMatchOptions opts;
opts.max_iterations = 5;
opts.tolerance = 1e-4;
auto result = curvature_matching(a, b, opts);
auto [nu, nv] = result.matched_surface_b.num_control_points();
EXPECT_EQ(nu, 3);
EXPECT_EQ(nv, 3);
}
TEST(ClassASurfacingTest, CurvatureMatching_DisplacementVector) {
auto a = plane_surface();
auto b = quad_surface();
CurvatureMatchOptions opts;
opts.max_iterations = 3;
auto result = curvature_matching(a, b, opts);
// 应有位移向量
EXPECT_FALSE(result.displacement.empty());
EXPECT_GE(result.displacement.size(), 1u);
}
// ---------------------------------------------------------------------------
// Test: Highlight Lines — 高光线分析
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, HighlightLines_PlaneOutput) {
auto s = plane_surface();
std::vector<Vector3D> lights = {Vector3D(0,0,1)};
auto result = highlight_lines(s, lights, 10, 10, 6);
EXPECT_EQ(result.res_u, 10);
EXPECT_EQ(result.res_v, 10);
EXPECT_EQ(static_cast<int>(result.band_mask.size()), 1);
}
TEST(ClassASurfacingTest, HighlightLines_MultipleLights) {
auto s = quad_surface();
std::vector<Vector3D> lights = {
Vector3D(1,0,0), Vector3D(0,1,0), Vector3D(0,0,1)
};
auto result = highlight_lines(s, lights, 15, 15, 8);
EXPECT_EQ(static_cast<int>(result.band_mask.size()), 3);
EXPECT_EQ(static_cast<int>(result.continuity_scores.size()), 3);
EXPECT_GE(result.overall_score, 0.0);
EXPECT_LE(result.overall_score, 1.0);
}
TEST(ClassASurfacingTest, HighlightLines_FlatPlanePerfectScore) {
auto s = plane_surface();
std::vector<Vector3D> lights = {Vector3D(1,1,1)};
auto result = highlight_lines(s, lights, 20, 20, 10);
// 平面上高光线应连续
EXPECT_GT(result.continuity_scores[0], 0.9);
}
// ---------------------------------------------------------------------------
// Test: Reflection Lines — 反射线分析
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, ReflectionLines_PlaneIsFair) {
auto s = plane_surface();
auto result = reflection_lines(s,
Point3D(5, 5, 5), Point3D(-5, -5, 10), 20, 20);
EXPECT_EQ(result.res_u, 20);
EXPECT_EQ(result.res_v, 20);
// 平面反射线应判定为光顺
EXPECT_TRUE(result.is_fair);
}
TEST(ClassASurfacingTest, ReflectionLines_DistortionBounds) {
auto s = quad_surface();
auto result = reflection_lines(s,
Point3D(3, 3, 5), Point3D(-3, -3, 8), 15, 15);
EXPECT_GE(result.max_distortion, 0.0);
EXPECT_GE(result.mean_distortion, 0.0);
EXPECT_LE(result.mean_distortion, result.max_distortion + 1e-9);
}
// ---------------------------------------------------------------------------
// Test: Iso-Photes — 等照度分析
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, IsoPhotes_OutputDimensions) {
auto s = plane_surface();
auto result = iso_photes(s, 16);
EXPECT_EQ(result.res_u, 16);
EXPECT_EQ(result.res_v, 16);
EXPECT_EQ(static_cast<int>(result.illumination.size()), 17);
EXPECT_EQ(static_cast<int>(result.illumination[0].size()), 17);
}
TEST(ClassASurfacingTest, IsoPhotes_IlluminationRange) {
auto s = plane_surface();
auto result = iso_photes(s, 10);
for (size_t i = 0; i < result.illumination.size(); ++i) {
for (size_t j = 0; j < result.illumination[i].size(); ++j) {
double val = result.illumination[i][j];
EXPECT_GE(val, -1.1);
EXPECT_LE(val, 1.1);
}
}
}
TEST(ClassASurfacingTest, IsoPhotes_GradientAnalysis) {
auto s = quad_surface();
auto result = iso_photes(s, 20);
EXPECT_GE(result.grad_max, 0.0);
EXPECT_GE(result.grad_mean, 0.0);
}
// ---------------------------------------------------------------------------
// Test: Surface Diagnosis — 综合曲面诊断
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, SurfaceDiagnosis_PlaneDiagnosis) {
auto s = plane_surface();
auto report = surface_diagnosis(s);
// 平面应该达到 A 级
EXPECT_EQ(report.grade, DiagnosisGrade::A_CLASS);
EXPECT_GT(report.overall_score, 80.0);
}
TEST(ClassASurfacingTest, SurfaceDiagnosis_ReportHasAllFields) {
auto s = quad_surface();
auto report = surface_diagnosis(s);
EXPECT_FALSE(report.gaussian_range.name.empty());
EXPECT_FALSE(report.mean_range.name.empty());
EXPECT_FALSE(report.highlight_score.name.empty());
EXPECT_FALSE(report.reflection_distortion.name.empty());
EXPECT_FALSE(report.isophote_gradient.name.empty());
EXPECT_FALSE(report.normal_jump.name.empty());
EXPECT_FALSE(report.recommendation.empty());
}
// ---------------------------------------------------------------------------
// Test: Shape Modification — 保形修改
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, ShapeModification_SingleConstraint) {
auto s = plane_surface();
std::vector<ShapeConstraint> constraints;
ShapeConstraint c;
c.target_point = Point3D(0.5, 0.5, 0.5); // 提升0.5
c.u = 0.5;
c.v = 0.5;
c.weight = 1.0;
constraints.push_back(c);
auto result = shape_modification(s, constraints);
auto [nu, nv] = result.modified_surface.num_control_points();
EXPECT_EQ(nu, 2);
EXPECT_EQ(nv, 2);
// 控制点应有位移
EXPECT_GT(result.max_displacement, 0.0);
EXPECT_GE(result.energy_preserved_ratio, 0.0);
EXPECT_LE(result.energy_preserved_ratio, 1.1);
}
TEST(ClassASurfacingTest, ShapeModification_ConstraintPointMoves) {
auto s = plane_surface();
std::vector<ShapeConstraint> constraints;
ShapeConstraint c;
c.target_point = Point3D(0.3, 0.3, 2.0);
c.u = 0.3;
c.v = 0.3;
c.weight = 2.0;
constraints.push_back(c);
auto result = shape_modification(s, constraints);
// 修改后的曲面在约束点处应接近目标
auto pt = result.modified_surface.evaluate(0.3, 0.3);
EXPECT_GT(pt.z(), 0.1); // 应被提起
}
TEST(ClassASurfacingTest, ShapeModification_MultipleConstraints) {
auto s = quad_surface();
std::vector<ShapeConstraint> constraints;
for (int i = 0; i < 3; ++i) {
ShapeConstraint c;
c.u = 0.25 * (i + 1);
c.v = 0.5;
c.target_point = Point3D(c.u * 2, 1.0, 1.5 + i * 0.5);
c.weight = 1.0;
constraints.push_back(c);
}
auto result = shape_modification(s, constraints);
EXPECT_GE(result.iterations, 1);
EXPECT_GT(result.max_displacement, 0.0);
}
// ---------------------------------------------------------------------------
// Test: G3 Blend with Constraints — 带约束 G3 过渡 (新增)
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, G3BlendWithConstraints_IdenticalPlanes) {
auto a = plane_surface();
auto b = offset_plane(0.0);
G3ConstraintEdges edges;
edges.g0_edge.edge_a = 3;
edges.g0_edge.edge_b = 2;
edges.g0_edge.blend_width = 0.2;
edges.g1_edge = edges.g0_edge;
edges.g2_edge = edges.g0_edge;
edges.g3_edge = edges.g0_edge;
auto result = g3_blend_with_constraints(a, b, edges);
auto [nu, nv] = result.blend_surface.num_control_points();
EXPECT_GT(nu, 0);
EXPECT_GT(nv, 0);
}
TEST(ClassASurfacingTest, G3BlendWithConstraints_G0G1Check) {
auto a = plane_surface();
auto b = offset_plane(0.0);
G3ConstraintEdges edges;
edges.g0_edge.edge_a = 3;
edges.g0_edge.edge_b = 2;
edges.g0_edge.blend_width = 0.15;
edges.g1_edge = edges.g0_edge;
edges.g2_edge = edges.g0_edge;
edges.g3_edge = edges.g0_edge;
edges.enforce_g0 = true;
edges.enforce_g1 = true;
auto result = g3_blend_with_constraints(a, b, edges);
EXPECT_TRUE(result.g0_ok);
EXPECT_TRUE(result.g1_ok);
}
TEST(ClassASurfacingTest, G3BlendWithConstraints_OnlyG0Enforced) {
auto a = plane_surface();
auto b = offset_plane(0.5);
G3ConstraintEdges edges;
edges.g0_edge.edge_a = 3;
edges.g0_edge.edge_b = 2;
edges.g0_edge.blend_width = 0.3;
edges.g1_edge = edges.g0_edge;
edges.g2_edge = edges.g0_edge;
edges.g3_edge = edges.g0_edge;
edges.enforce_g0 = true;
edges.enforce_g1 = false;
edges.enforce_g2 = false;
edges.enforce_g3 = false;
auto result = g3_blend_with_constraints(a, b, edges);
EXPECT_TRUE(result.g0_ok);
EXPECT_FALSE(result.g1_ok); // G1未强制=检查默认失败
}
// ---------------------------------------------------------------------------
// Test: Surface Energy Minimization — 薄板能量最小化 (新增)
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, EnergyMinimization_FlatSurface) {
auto s = plane_surface();
EnergyMinimizationOptions opts;
opts.max_iterations = 20;
opts.tolerance = 1e-4;
auto result = surface_energy_minimization(s, {}, opts);
auto [nu, nv] = result.optimized_surface.num_control_points();
EXPECT_EQ(nu, 2);
EXPECT_EQ(nv, 2);
EXPECT_GE(result.final_bending_energy, 0.0);
}
TEST(ClassASurfacingTest, EnergyMinimization_WithConstraint) {
auto s = quad_surface();
std::vector<ShapeConstraint> constraints;
ShapeConstraint c;
c.target_point = Point3D(1.0, 1.0, 2.0);
c.u = 0.5;
c.v = 0.5;
c.weight = 5.0;
constraints.push_back(c);
EnergyMinimizationOptions opts;
opts.max_iterations = 30;
opts.constraint_weight = 20.0;
auto result = surface_energy_minimization(s, constraints, opts);
EXPECT_GT(result.iterations, 0);
EXPECT_GT(result.max_displacement, 0.0);
}
TEST(ClassASurfacingTest, EnergyMinimization_BendingReduction) {
auto s = quad_surface();
EnergyMinimizationOptions opts;
opts.bending_weight = 5.0;
opts.membrane_weight = 0.01;
opts.max_iterations = 50;
opts.preserve_boundary = false;
auto result = surface_energy_minimization(s, {}, opts);
EXPECT_GE(result.final_bending_energy, 0.0);
EXPECT_GE(result.iterations, 1);
}
// ---------------------------------------------------------------------------
// Test: Curvature Continuity Optimization — 曲率连续性迭代精化 (新增)
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, CurvatureContinuity_IdenticalSurfaces) {
auto a = plane_surface();
auto b = plane_surface();
CurvatureContinuityOptions opts;
opts.max_iterations = 10;
opts.boundary_samples = 15;
auto result = curvature_continuity_optimization(a, b, opts);
EXPECT_LT(result.final_g0_error, 1e-3);
EXPECT_LT(result.final_g1_error, 0.01);
}
TEST(ClassASurfacingTest, CurvatureContinuity_QuadSurfaces) {
auto a = quad_surface();
auto b = quad_surface();
CurvatureContinuityOptions opts;
opts.max_iterations = 20;
opts.boundary_samples = 20;
opts.step_size = 0.02;
auto result = curvature_continuity_optimization(a, b, opts);
auto [nu, nv] = result.optimized_b.num_control_points();
EXPECT_EQ(nu, 3);
EXPECT_EQ(nv, 3);
EXPECT_GE(result.iterations, 0);
}
// ---------------------------------------------------------------------------
// Test: Reflection Line Discontinuity — 反射线不连续检测+修复 (新增)
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, ReflectionDiscontinuity_PlaneIsSmooth) {
auto s = plane_surface();
ReflectionDiscontinuityParams params;
params.light_direction = Vector3D(1, 0, 0);
params.res_u = 20;
params.res_v = 20;
params.discontinuity_threshold = 0.05;
params.auto_fix = false;
auto result = reflection_line_discontinuity(s, params);
EXPECT_EQ(result.res_u, 20);
EXPECT_EQ(result.res_v, 20);
EXPECT_TRUE(result.is_smooth);
}
TEST(ClassASurfacingTest, ReflectionDiscontinuity_AutoFix) {
auto s = quad_surface();
ReflectionDiscontinuityParams params;
params.light_direction = Vector3D(1, 1, 1);
params.res_u = 15;
params.res_v = 15;
params.discontinuity_threshold = 0.1;
params.max_fix_iterations = 5;
params.fix_strength = 0.05;
params.auto_fix = true;
auto result = reflection_line_discontinuity(s, params);
auto [nu, nv] = result.fixed_surface.num_control_points();
EXPECT_GT(nu, 0);
EXPECT_GT(nv, 0);
EXPECT_GE(result.total_discontinuities, 0);
}