Files
ViewDesignEngine/tests/brep/test_brep_drawing.cpp
T
茂之钳 fcf25e561d feat(v4.4): complete remaining v4.1-v4.4 features + precision tolerance + Euler ops
v4.1 收尾:
- IncrementalUpdateEngine: dirty flag propagation, cache invalidation
- LargeAssembly: InstanceCache, assembly instancing
- STEP import: robust/graceful parsing with skip tracking

v4.3 分析工具:
- Mass properties (volume, centroid, inertia tensor)
- Clearance analysis, wall thickness analysis
- Enhanced drawing: hidden-line removal, offset sections, BOM
- DXF import (LINE/CIRCLE/ARC/LWPOLYLINE/SPLINE → B-Rep extrusion)

v4.4 地基加固:
- ToleranceChain: RSS cumulative tolerance propagation (7 tests)
- Euler operations: MEV/KEV/MEF/KEF/KEMR/MEKR (20 tests)
- Replace hardcoded tolerances with ToleranceConfig in validate
- Fix incremental_update test API mismatch (15/15 pass on Linux)

Docs:
- v4.1-v4.4 development plans + roadmap updated
- v4.4 marked complete on Linux

30 files, +3424/-210
2026-07-26 16:49:37 +08:00

594 lines
13 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/brep/brep_drawing.h"
#include "vde/brep/dxf_import.h"
#include "vde/brep/modeling.h"
#include <cmath>
#include <fstream>
#include <cstdio>
using namespace vde::brep;
using namespace vde::core;
// ═══════════════════════════════════════════════════════════
// generate_views — 基本测试
// ═══════════════════════════════════════════════════════════
TEST(BrepDrawingTest, GenerateViews_Box_ReturnsFourViews) {
auto box = make_box(2, 3, 4);
auto views = generate_views(box);
EXPECT_EQ(views.size(), 4u);
EXPECT_EQ(views[0].name, "front");
EXPECT_EQ(views[1].name, "top");
EXPECT_EQ(views[2].name, "right");
EXPECT_EQ(views[3].name, "iso");
}
TEST(BrepDrawingTest, GenerateViews_Box_EachViewHasSegments) {
auto box = make_box(2, 3, 4);
auto views = generate_views(box);
for (const auto& v : views) {
EXPECT_GT(v.total_segments(), 0u)
<< "View " << v.name << " should have segments";
}
}
TEST(BrepDrawingTest, GenerateViews_Box_FrontViewHasExpectedContent) {
auto box = make_box(2, 2, 2);
auto views = generate_views(box);
const auto& front = views[0];
EXPECT_EQ(front.name, "front");
// Front view: XY plane projection, should show X=[-1,1], Y=[-1,1]
EXPECT_GT(front.total_segments(), 0u);
}
TEST(BrepDrawingTest, GenerateViews_Box_ViewDirections) {
auto box = make_box(1, 1, 1);
auto views = generate_views(box);
// Front: looking down -Z
EXPECT_NEAR(views[0].view_direction.z(), -1.0, 1e-6);
// Top: looking down -Y
EXPECT_NEAR(views[1].view_direction.y(), -1.0, 1e-6);
// Right: looking down -X
EXPECT_NEAR(views[2].view_direction.x(), -1.0, 1e-6);
}
TEST(BrepDrawingTest, GenerateViews_EmptyBody_ReturnsEmpty) {
BrepModel empty;
auto views = generate_views(empty);
EXPECT_TRUE(views.empty());
}
TEST(BrepDrawingTest, GenerateViews_Cylinder_ReturnsFourViews) {
auto cyl = make_cylinder(1.0, 3.0, 16);
auto views = generate_views(cyl);
EXPECT_EQ(views.size(), 4u);
for (const auto& v : views) {
EXPECT_GT(v.total_segments(), 0u);
}
}
TEST(BrepDrawingTest, GenerateViews_Sphere_ReturnsFourViews) {
auto sphere = make_sphere(2.0, 16, 8);
auto views = generate_views(sphere);
EXPECT_EQ(views.size(), 4u);
// 球所有视图的主要轮廓应类似
for (const auto& v : views) {
ASSERT_GT(v.total_segments(), 0u);
}
}
// ═══════════════════════════════════════════════════════════
// section_view — 测试
// ═══════════════════════════════════════════════════════════
TEST(BrepDrawingTest, SectionView_Box_AtCenter) {
auto box = make_box(4, 4, 4);
// 截平面 z = 0(XY 平面,法向量沿 Z)
auto view = section_view(box, Point3D(0, 0, 0), Vector3D(0, 0, 1));
EXPECT_EQ(view.name, "section");
// 截面应为矩形轮廓,至少有 4 个交点形成轮廓线段
// + hatch 线,总数应 > 4
ASSERT_GT(view.total_segments(), 4u);
}
TEST(BrepDrawingTest, SectionView_Box_HorizontalPlane) {
auto box = make_box(4, 4, 4);
// z = 1 的水平截面(穿过盒子)
auto view = section_view(box, Point3D(0, 0, 1), Vector3D(0, 0, 1));
ASSERT_GT(view.total_segments(), 4u);
}
TEST(BrepDrawingTest, SectionView_Box_PlaneOutside) {
auto box = make_box(2, 2, 2);
// 截平面在盒子之外(z = 5
auto view = section_view(box, Point3D(0, 0, 5), Vector3D(0, 0, 1));
// 交点不足 3 个 → 无法形成截面
EXPECT_LT(view.total_segments(), 3u);
}
TEST(BrepDrawingTest, SectionView_EmptyBody_ReturnsEmpty) {
BrepModel empty;
auto view = section_view(empty, Point3D(0, 0, 0), Vector3D(0, 0, 1));
EXPECT_EQ(view.name, "section");
EXPECT_EQ(view.total_segments(), 0u);
}
TEST(BrepDrawingTest, SectionView_Cylinder_Midplane) {
auto cyl = make_cylinder(2.0, 6.0, 32);
// 垂直圆柱的 z=0 截面 → 圆形截面
auto view = section_view(cyl, Point3D(0, 0, 0), Vector3D(0, 0, 1));
ASSERT_GT(view.segments.size(), 3u);
}
// ═══════════════════════════════════════════════════════════
// DXF 导出 — 测试
// ═══════════════════════════════════════════════════════════
TEST(BrepDrawingTest, ExportDxf_CreatesFile) {
auto box = make_box(2, 2, 2);
auto views = generate_views(box);
const char* filepath = "test_output.dxf";
std::remove(filepath); // 清理旧文件
bool ok = export_dxf(filepath, views);
EXPECT_TRUE(ok);
// 验证文件存在
std::ifstream in(filepath);
EXPECT_TRUE(in.is_open());
std::string content((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>());
in.close();
// 验证关键结构
EXPECT_NE(content.find("SECTION"), std::string::npos);
EXPECT_NE(content.find("ENTITIES"), std::string::npos);
EXPECT_NE(content.find("LINE"), std::string::npos);
EXPECT_NE(content.find("ENDSEC"), std::string::npos);
EXPECT_NE(content.find("EOF"), std::string::npos);
// 验证有至少一条 LINE 实体
EXPECT_NE(content.find("LINE"), std::string::npos);
std::remove(filepath);
}
TEST(BrepDrawingTest, ExportDxf_HasLayers) {
auto box = make_box(2, 2, 2);
auto views = generate_views(box);
const char* filepath = "test_layers.dxf";
std::remove(filepath);
export_dxf(filepath, views);
std::ifstream in(filepath);
std::string content((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>());
in.close();
EXPECT_NE(content.find("visible"), std::string::npos);
EXPECT_NE(content.find("DASHED"), std::string::npos);
std::remove(filepath);
}
TEST(BrepDrawingTest, ExportDxf_EmptyViews_StillWritesSkeleton) {
std::vector<ProjectionView> empty_views;
const char* filepath = "test_empty.dxf";
std::remove(filepath);
bool ok = export_dxf(filepath, empty_views);
EXPECT_TRUE(ok);
std::ifstream in(filepath);
std::string content((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>());
in.close();
EXPECT_NE(content.find("SECTION"), std::string::npos);
EXPECT_NE(content.find("EOF"), std::string::npos);
std::remove(filepath);
}
TEST(BrepDrawingTest, ExportDxf_InvalidPath_ReturnsFalse) {
auto box = make_box(1, 1, 1);
auto views = generate_views(box);
bool ok = export_dxf("/nonexistent_dir_xyz/test.dxf", views);
EXPECT_FALSE(ok);
}
// ═══════════════════════════════════════════════════════════
// 隐藏线 — 测试
// ═══════════════════════════════════════════════════════════
TEST(BrepDrawingTest, HiddenLines_Box_HasHiddenEdges) {
auto box = make_box(2, 2, 2);
auto views = generate_views(box);
// 每个标准视图均应有隐藏边
for (size_t i = 0; i < std::min(views.size(), size_t(3)); ++i) {
// 前/俯/右视图都应该有隐藏线
EXPECT_GE(views[i].hidden_lines.size() + views[i].segments.size(), 1u);
}
}
TEST(BrepDrawingTest, DrawSegment_DefaultNotHidden) {
DrawSegment seg{0, 0, 1, 1};
EXPECT_FALSE(seg.hidden);
}
TEST(BrepDrawingTest, DrawSegment_Hidden) {
DrawSegment seg{0, 0, 1, 1, true};
EXPECT_TRUE(seg.hidden);
}
TEST(BrepDrawingTest, ProjectionView_TotalSegments) {
ProjectionView v;
v.segments = {{0,0,1,0}, {1,0,1,1}};
v.hidden_lines = {{0,1,0,0, true}};
EXPECT_EQ(v.total_segments(), 3u);
}
// ═══════════════════════════════════════════════════════════
// Offset (stepped) section views
// ═══════════════════════════════════════════════════════════
TEST(OffsetSectionTest, SingleOffset) {
auto box = make_box(4, 4, 4);
auto view = offset_section_view(box, Vector3D(0, 0, 1), {0.0});
EXPECT_FALSE(view.segments.empty());
EXPECT_EQ(view.name, "offset_section");
}
TEST(OffsetSectionTest, MultipleOffsets) {
auto box = make_box(4, 4, 4);
auto view = offset_section_view(box, Vector3D(0, 0, 1), {-1.0, 0.0, 1.0});
EXPECT_GE(view.segments.size(), 3u); // section + step lines
}
TEST(OffsetSectionTest, EmptyOffsets) {
auto box = make_box(4, 4, 4);
auto view = offset_section_view(box, Vector3D(0, 0, 1), {});
EXPECT_TRUE(view.segments.empty());
}
TEST(OffsetSectionTest, CylinderSection) {
auto cyl = make_cylinder(2, 6, 32);
auto view = offset_section_view(cyl, Vector3D(0, 0, 1), {-2.0, 0.0, 2.0});
EXPECT_GE(view.total_segments(), 3u);
}
TEST(DxfImportTest, EmptyString) {
auto result = import_dxf_from_string("");
EXPECT_EQ(result.entities_parsed, 0);
EXPECT_EQ(result.contours.size(), 0u);
}
TEST(DxfImportTest, MinimalDxfWithLine) {
std::string dxf = R"(0
SECTION
2
ENTITIES
0
LINE
8
0
10
0.0
20
0.0
30
0.0
11
5.0
21
5.0
31
0.0
0
ENDSEC
0
EOF
)";
auto result = import_dxf_from_string(dxf);
EXPECT_GE(result.entities_parsed, 1);
EXPECT_GE(result.contours.size(), 1u);
}
TEST(DxfImportTest, MinimalDxfWithCircle) {
std::string dxf = R"(0
SECTION
2
ENTITIES
0
CIRCLE
8
0
10
0.0
20
0.0
30
0.0
40
3.0
0
ENDSEC
0
EOF
)";
auto result = import_dxf_from_string(dxf);
EXPECT_GE(result.entities_parsed, 1);
EXPECT_GE(result.contours.size(), 1u);
// Circle should be closed
EXPECT_TRUE(result.contours[0].closed);
}
TEST(DxfImportTest, MinimalDxfWithArc) {
std::string dxf = R"(0
SECTION
2
ENTITIES
0
ARC
8
0
10
0.0
20
0.0
30
0.0
40
2.0
50
0.0
51
180.0
0
ENDSEC
0
EOF
)";
auto result = import_dxf_from_string(dxf);
EXPECT_GE(result.entities_parsed, 1);
EXPECT_GE(result.contours.size(), 1u);
}
TEST(DxfImportTest, LwPolylineRectangle) {
std::string dxf = R"(0
SECTION
2
ENTITIES
0
LWPOLYLINE
8
walls
90
4
70
1
10
0.0
20
0.0
10
10.0
20
0.0
10
10.0
20
5.0
10
0.0
20
5.0
0
ENDSEC
0
EOF
)";
auto result = import_dxf_from_string(dxf);
EXPECT_GE(result.entities_parsed, 1);
// Find the LWPOLYLINE contour
bool found_rect = false;
for (const auto& c : result.contours) {
if (c.layer == "walls" && c.points.size() >= 4) {
found_rect = true;
EXPECT_TRUE(c.closed);
break;
}
}
EXPECT_TRUE(found_rect);
}
TEST(DxfImportTest, SplineEntity) {
std::string dxf = R"(0
SECTION
2
ENTITIES
0
SPLINE
8
curves
70
8
71
3
10
0.0
20
0.0
30
0.0
10
2.0
20
3.0
30
0.0
10
4.0
20
0.0
30
0.0
10
6.0
20
3.0
30
0.0
40
0.0
40
0.0
40
0.0
40
0.0
40
0.333
40
0.667
40
1.0
40
1.0
0
ENDSEC
0
EOF
)";
auto result = import_dxf_from_string(dxf);
EXPECT_GE(result.entities_parsed, 1);
}
TEST(DxfImportTest, UnknownEntityTypeIsSkipped) {
std::string dxf = R"(0
SECTION
2
ENTITIES
0
MYSTERY_ENTITY
8
0
0
LINE
8
0
10
0
20
0
30
0
11
1
21
1
31
0
0
ENDSEC
0
EOF
)";
auto result = import_dxf_from_string(dxf);
EXPECT_GE(result.entities_parsed, 1);
EXPECT_GE(result.entities_skipped, 1);
}
TEST(DxfImportTest, MultipleEntities) {
std::string dxf = R"(0
SECTION
2
ENTITIES
0
LINE
8
0
10
0
20
0
30
0
11
1
21
0
31
0
0
CIRCLE
8
holes
10
5
20
5
30
0
40
3.0
0
ARC
8
holes
10
0
20
0
30
0
40
2.5
50
90
51
270
0
ENDSEC
0
EOF
)";
auto result = import_dxf_from_string(dxf);
EXPECT_GE(result.entities_parsed, 3);
EXPECT_GE(result.contours.size(), 3u);
}
TEST(DxfImportTest, ExtrudeContour_ProducesValidBody) {
DxfContour c;
c.points = {Point3D(0, 0, 0), Point3D(10, 0, 0), Point3D(10, 5, 0), Point3D(0, 5, 0)};
c.closed = true;
c.layer = "profile";
auto body = extrude_dxf_contour(c, 2.0);
EXPECT_GT(body.num_faces(), 0u);
EXPECT_TRUE(body.is_valid());
}
TEST(DxfImportTest, ExtrudeEmptyContour_ReturnsEmpty) {
DxfContour c;
auto body = extrude_dxf_contour(c, 1.0);
EXPECT_EQ(body.num_faces(), 0u);
}