Files
ViewDesignEngine/tests/brep/test_v4_1.cpp
T
茂之钳 2e004fda6d
CI / Build & Test (push) Failing after 35s
CI / Release Build (push) Failing after 48s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v4.1): incremental update + large assembly + format robustness
- IncrementalUpdateEngine: dirty propagation, topological rebuild, cache hit/miss
- InstanceCache: shared LOD mesh for identical parts
- LargeAssemblyManager: BVH spatial index, view frustum culling, assembly stats
- format_io: auto-detect STEP/IGES, batch import with error recovery
- 31 tests: incremental (11), large assembly (12), format IO (8)
2026-07-25 03:51:57 +00:00

219 lines
7.4 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 "vde/brep/step_export.h"
#include "vde/brep/iges_export.h"
#include <gtest/gtest.h>
#include "vde/brep/large_assembly.h"
#include "vde/brep/format_io.h"
#include "vde/brep/modeling.h"
#include <cmath>
using namespace vde::brep;
using namespace vde::core;
// ═══════════════════════════════════════════════════════════
// LargeAssemblyManager
// ═══════════════════════════════════════════════════════════
TEST(LargeAssemblyTest, BuildIndex_EmptyAssembly) {
Assembly assy("empty");
LargeAssemblyManager mgr;
mgr.build_index(assy);
EXPECT_TRUE(mgr.index_built());
EXPECT_EQ(mgr.part_count(), 0);
}
TEST(LargeAssemblyTest, BuildIndex_SinglePart) {
Assembly assy("single");
assy.root.add_part("box", make_box(2, 2, 2));
LargeAssemblyManager mgr;
mgr.build_index(assy);
EXPECT_EQ(mgr.part_count(), 1);
}
TEST(LargeAssemblyTest, BuildIndex_MultipleParts) {
Assembly assy("multi");
for (int i = 0; i < 10; ++i) {
assy.root.add_part("p" + std::to_string(i), make_box(1, 1, 1),
translate(i * 3.0, 0, 0));
}
LargeAssemblyManager mgr;
mgr.build_index(assy);
EXPECT_EQ(mgr.part_count(), 10);
}
TEST(LargeAssemblyTest, QueryVisible_ReturnsIntersectingParts) {
Assembly assy("visible");
assy.root.add_part("near", make_box(2, 2, 2), translate(0, 0, 0));
assy.root.add_part("far", make_box(2, 2, 2), translate(20, 0, 0));
LargeAssemblyManager mgr;
mgr.build_index(assy);
// AABB covering origin region
AABB3D view(Point3D(-5, -5, -5), Point3D(5, 5, 5));
auto visible = mgr.query_visible(view);
EXPECT_GE(visible.size(), 1u);
// "near" should be visible
bool has_near = false;
for (auto& n : visible) if (n == "near") has_near = true;
EXPECT_TRUE(has_near);
}
TEST(LargeAssemblyTest, QueryVisible_FarRegion_ReturnsEmpty) {
Assembly assy("far_test");
assy.root.add_part("box", make_box(2, 2, 2), translate(0, 0, 0));
LargeAssemblyManager mgr;
mgr.build_index(assy);
// AABB far away
AABB3D view(Point3D(100, 100, 100), Point3D(110, 110, 110));
auto visible = mgr.query_visible(view);
EXPECT_EQ(visible.size(), 0u);
}
TEST(LargeAssemblyTest, ComputeStats_AccurateCounts) {
Assembly assy("stats");
assy.root.add_part("p1", make_box(1, 1, 1));
assy.root.add_part("p2", make_box(1, 1, 1), translate(5, 0, 0));
auto* sub = assy.root.add_subassembly("group");
sub->add_part("sub_p1", make_box(1, 1, 1));
LargeAssemblyManager mgr;
auto stats = mgr.compute_stats(assy);
EXPECT_EQ(stats.total_parts, 3);
EXPECT_GE(stats.subassemblies, 1);
EXPECT_GE(stats.max_depth, 1);
}
TEST(LargeAssemblyTest, LargeAssembly_10KParts_PerformanceTest) {
Assembly assy("10k");
int grid_size = 100; // 100×100 = 10,000 parts
for (int i = 0; i < grid_size; ++i) {
for (int j = 0; j < grid_size; ++j) {
assy.root.add_part("b_" + std::to_string(i) + "_" + std::to_string(j),
make_box(1, 1, 1), translate(i * 3.0, j * 3.0, 0));
}
}
LargeAssemblyManager mgr;
mgr.build_index(assy);
EXPECT_EQ(mgr.part_count(), grid_size * grid_size);
// Query visible in central region
AABB3D view(Point3D(140, 140, -5), Point3D(160, 160, 5));
auto visible = mgr.query_visible(view);
// Should find ~7×7 = 49 parts in this region
EXPECT_GT(visible.size(), 0u);
EXPECT_LT(visible.size(), 100u);
}
// ═══════════════════════════════════════════════════════════
// InstanceCache
// ═══════════════════════════════════════════════════════════
TEST(InstanceCacheTest, Register_NewInstance_ReturnsTrue) {
InstanceCache cache;
auto box = make_box(2, 2, 2);
EXPECT_TRUE(cache.register_instance(1, box));
EXPECT_EQ(cache.unique_count(), 1);
}
TEST(InstanceCacheTest, Register_DuplicateInstance_ReturnsFalse) {
InstanceCache cache;
auto box = make_box(2, 2, 2);
cache.register_instance(1, box);
EXPECT_FALSE(cache.register_instance(1, box));
EXPECT_EQ(cache.unique_count(), 1);
}
TEST(InstanceCacheTest, GetLod_ReturnsNonNull) {
InstanceCache cache;
cache.register_instance(1, make_box(2, 2, 2));
auto* lod = cache.get_lod(1);
EXPECT_TRUE(lod != nullptr);
}
TEST(InstanceCacheTest, GetLod_Unknown_ReturnsNull) {
InstanceCache cache;
EXPECT_TRUE(cache.get_lod(999) == nullptr);
}
TEST(InstanceCacheTest, GetBounds_ReturnsCorrectSize) {
InstanceCache cache;
cache.register_instance(1, make_box(2, 2, 2));
auto* bb = cache.get_bounds(1);
ASSERT_TRUE(bb != nullptr);
EXPECT_NEAR(bb->extent().x(), 2.0, 0.01);
}
// ═══════════════════════════════════════════════════════════
// Format detection
// ═══════════════════════════════════════════════════════════
TEST(FormatIOTest, Detect_STEP_Header) {
auto fmt = detect_format_from_data("ISO-10303-21;\nHEADER;\n...");
EXPECT_EQ(fmt, CadFormat::STEP);
}
TEST(FormatIOTest, Detect_IGES_CardFormat) {
// 80-column IGES-style data with section marker at column 73
std::string data(80, ' ');
data[72] = 'S';
auto fmt = detect_format_from_data(data);
EXPECT_EQ(fmt, CadFormat::IGES);
}
TEST(FormatIOTest, Detect_EmptyData_Unknown) {
EXPECT_EQ(detect_format_from_data(""), CadFormat::Unknown);
}
TEST(FormatIOTest, AutoImport_EmptyData_ReturnsEmpty) {
auto models = auto_import_from_string("");
EXPECT_TRUE(models.empty());
}
TEST(FormatIOTest, BatchImport_EmptyList_ReturnsZero) {
auto result = batch_import({});
EXPECT_EQ(result.total_files, 0);
EXPECT_FALSE(result.summary().empty());
}
TEST(FormatIOTest, BatchImport_InvalidFile_CountsFailure) {
auto result = batch_import({"/nonexistent/path.stp"});
EXPECT_EQ(result.total_files, 1);
EXPECT_GT(result.fail_count + result.success_count, 0);
}
// ═══════════════════════════════════════════════════════════
// STEP round-trip with format IO
// ═══════════════════════════════════════════════════════════
TEST(FormatIOTest, StepRoundTrip_AutoDetect) {
auto box = make_box(2, 2, 2);
std::string step = export_step({box});
auto fmt = detect_format_from_data(step);
EXPECT_EQ(fmt, CadFormat::STEP);
auto models = auto_import_from_string(step);
EXPECT_EQ(models.size(), 1u);
EXPECT_TRUE(models[0].is_valid());
}
TEST(FormatIOTest, IGESRoundTrip_AutoDetect) {
auto box = make_box(2, 2, 2);
std::string iges = export_iges({box});
auto fmt = detect_format_from_data(iges);
EXPECT_EQ(fmt, CadFormat::IGES);
auto models = auto_import_from_string(iges);
EXPECT_GE(models.size(), 0u);
}