#include "vde/brep/step_export.h" #include "vde/brep/iges_export.h" #include #include "vde/brep/large_assembly.h" #include "vde/brep/format_io.h" #include "vde/brep/modeling.h" #include 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); }