219 lines
7.4 KiB
C++
219 lines
7.4 KiB
C++
|
|
#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);
|
|||
|
|
}
|