Files
ViewDesignEngine/tests/brep/test_explode_view.cpp
T
茂之钳 a2b34733bf
CI / Build & Test (push) Failing after 36s
CI / Release Build (push) Failing after 47s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v4.0): explode view — assembly part decomposition
- explode_view() with auto/manual direction + factor control
- un_explode() for restoring original transforms
- compute_explode_displacement() for per-part displacement
- 12 tests: single part, chain, factor scaling, un-explode, subassembly
2026-07-25 03:19:35 +00:00

206 lines
6.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 <gtest/gtest.h>
#include "vde/brep/explode_view.h"
#include "vde/brep/modeling.h"
#include "vde/core/transform.h"
#include <cmath>
using namespace vde::brep;
using namespace vde::core;
// ═══════════════════════════════════════════════════════════
// Basic explosion
// ═══════════════════════════════════════════════════════════
TEST(ExplodeViewTest, SinglePart_NoExplosion) {
Assembly assy("single");
assy.root.add_part("box", make_box(2, 2, 2));
ExplodeConfig cfg;
cfg.factor = 1.0;
cfg.use_constraint_dirs = false;
auto result = explode_view(assy, cfg);
EXPECT_GE(result.steps.size(), 1u);
EXPECT_GT(result.max_displacement(), 0.0);
}
TEST(ExplodeViewTest, TwoParts_DisplaceAway) {
Assembly assy("two");
assy.root.add_part("left", make_box(2, 2, 2), translate(-3, 0, 0));
assy.root.add_part("right", make_box(2, 2, 2), translate(3, 0, 0));
ExplodeConfig cfg;
cfg.factor = 2.0;
auto result = explode_view(assy, cfg);
EXPECT_EQ(result.steps.size(), 2u);
// Both parts should move further apart
EXPECT_GT(result.max_displacement(), 3.0); // factor 2 × original offset 3
}
TEST(ExplodeViewTest, Explode_ExpandsBounds) {
Assembly assy("expand");
assy.root.add_part("a", make_box(2, 2, 2), translate(0, -3, 0));
assy.root.add_part("b", make_box(2, 2, 2), translate(0, 3, 0));
double orig_extent = assy.root.children[0]->model->bounds().extent().norm();
ExplodeConfig cfg;
cfg.factor = 2.0;
auto result = explode_view(assy, cfg);
Vector3D expl_ext = result.exploded_bounds.extent();
// Exploded bounds should be larger than any single part
EXPECT_GT(expl_ext.norm(), orig_extent);
}
TEST(ExplodeViewTest, UnExplode_RestoresTransforms) {
Assembly assy("restore");
assy.root.add_part("part", make_box(2, 2, 2), translate(5, 0, 0));
// Save original transform
Transform3D orig_tf = assy.root.children[0]->local_transform;
ExplodeConfig cfg;
cfg.factor = 3.0;
auto result = explode_view(assy, cfg);
// Transform should have changed
bool changed = assy.root.children[0]->local_transform.matrix() != orig_tf.matrix();
EXPECT_TRUE(changed);
// Un-explode
un_explode(assy, result);
// Should be back to original
bool restored = assy.root.children[0]->local_transform.matrix() == orig_tf.matrix();
EXPECT_TRUE(restored);
}
TEST(ExplodeViewTest, AutoDirection_ChoosesLongestAxis) {
Assembly assy("auto_dir");
// Create a wide but flat assembly
assy.root.add_part("wide", make_box(10, 1, 1));
ExplodeConfig cfg;
cfg.factor = 1.0;
cfg.use_constraint_dirs = false;
// direction is zero → auto
auto result = explode_view(assy, cfg);
// Should pick X axis (longest extent)
// Verify by checking that displacement is primarily along X
for (auto& step : result.steps) {
Vector3D d = step.displacement;
EXPECT_GE(std::abs(d.x()), std::abs(d.y()));
EXPECT_GE(std::abs(d.x()), std::abs(d.z()));
}
}
TEST(ExplodeViewTest, ExplicitDirection_Used) {
Assembly assy("explicit_dir");
assy.root.add_part("box", make_box(2, 2, 2), translate(0, 3, 0));
ExplodeConfig cfg;
cfg.factor = 2.0;
cfg.direction = Vector3D(0, 0, 1); // explicit +Z direction
cfg.use_constraint_dirs = false;
auto result = explode_view(assy, cfg);
for (auto& step : result.steps) {
Vector3D d = step.displacement;
// Displacement should be primarily along Z
EXPECT_GE(std::abs(d.z()), std::abs(d.x()));
EXPECT_GE(std::abs(d.z()), std::abs(d.y()));
}
}
TEST(ExplodeViewTest, FactorScalesDisplacement) {
Assembly assy("scale");
assy.root.add_part("part", make_box(2, 2, 2), translate(0, 4, 0));
ExplodeConfig cfg1;
cfg1.factor = 1.0;
auto r1 = explode_view(assy, cfg1);
// Reset
un_explode(assy, r1);
ExplodeConfig cfg2;
cfg2.factor = 3.0;
auto r2 = explode_view(assy, cfg2);
// Factor 3 should give roughly 3× the displacement
EXPECT_NEAR(r2.max_displacement() / r1.max_displacement(), 3.0, 0.5);
}
TEST(ExplodeViewTest, Subassembly_NotExplodedInternally) {
Assembly assy("parent");
auto* sub = assy.root.add_subassembly("group", translate(0, 5, 0));
sub->add_part("sub_a", make_box(1, 1, 1), translate(-1, 0, 0));
sub->add_part("sub_b", make_box(1, 1, 1), translate(1, 0, 0));
assy.root.add_part("main", make_box(3, 1, 3));
ExplodeConfig cfg;
cfg.factor = 2.0;
cfg.preserve_subassemblies = true;
auto result = explode_view(assy, cfg);
// Should still work without crash
EXPECT_GE(result.steps.size(), 1u);
}
TEST(ExplodeViewTest, MaxDisplacement_NonNegative) {
Assembly assy("max_disp");
assy.root.add_part("a", make_box(2, 2, 2));
auto result = explode_view(assy);
EXPECT_GE(result.max_displacement(), 0.0);
}
TEST(ExplodeViewTest, EmptyAssembly_NoSteps) {
Assembly assy("empty");
auto result = explode_view(assy);
EXPECT_EQ(result.steps.size(), 0u);
EXPECT_EQ(result.max_displacement(), 0.0);
}
TEST(ExplodeViewTest, ThreePartsChain_AllDisplaced) {
Assembly assy("chain");
assy.root.add_part("root", make_box(3, 1, 1));
assy.root.add_part("middle", make_box(2, 1, 1), translate(4, 0, 0));
assy.root.add_part("tip", make_box(1, 1, 1), translate(7, 0, 0));
ExplodeConfig cfg;
cfg.factor = 1.5;
auto result = explode_view(assy, cfg);
EXPECT_EQ(result.steps.size(), 3u);
// Each part should have been displaced
for (auto& step : result.steps) {
EXPECT_GT(step.displacement.norm(), 0.0);
}
}
TEST(ExplodeViewTest, ComputedDisplacement_DirectionConsistent) {
Assembly assy("consistent");
// Place parts in a line
assy.root.add_part("p0", make_box(2, 2, 2), translate(0, 5, 0));
assy.root.add_part("p1", make_box(2, 2, 2), translate(0, 10, 0));
ExplodeConfig cfg;
cfg.factor = 2.0;
auto result = explode_view(assy, cfg);
ASSERT_GE(result.steps.size(), 2u);
// All displacements should be in roughly the same direction (away from origin)
Vector3D d0 = result.steps[0].displacement.normalized();
Vector3D d1 = result.steps[1].displacement.normalized();
EXPECT_GT(d0.dot(d1), 0.5); // should point in similar direction
}