Files
ViewDesignEngine/tests/brep/test_assembly_patterns.cpp
T
茂之钳 2ecad1543f
CI / Build & Test (push) Failing after 1m31s
CI / Release Build (push) Failing after 31s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v8): ultimate performance + CAM full optimization + visualization/IGA/quality
v8.1 — 极致性能 (SIMD + LockFree + Transaction + NUMA):
- simd_vector.h: Vec4d/Vec4f SSE/AVX/NEON auto-detect, batch AABB, SoA transpose
- concurrent_data: LockFreeQueue (MPMC CAS), LockFreeStack (Treiber), ConcurrentHashMap (64-segment sharded)
- transaction: Command pattern, UndoManager (infinite undo/redo), crash-recovery journal
- performance_tuning: NUMA-aware, cache_line aligned, prefetch, hot/cold separation
- 20 tests (concurrent + transaction), ~2600 lines

v8.2 — CAM 全面优化 + 装配模式:
- cam_optimization: chip_thinning, HSM, constant_engagement, trochoidal_turn_milling
- tool_life_management, probing_cycle, thread_milling
- cam_advanced enhanced: Mazak/Okuma/Haas/DMG post-processors (8 total)
- assembly_patterns: Circular/Rectangular/Mirror/PatternDriven/fill arrays
- assembly_feature enhanced: assembly-level PMI propagation, batch interference check
- 28 tests, compiled 0 errors (~2800 lines)

v8.3 — 可视化+压缩+IGA+质量闭环:
- visualization_quality: ambient_occlusion, edge_highlighting, wireframe, normals
- topology_compression: Brep compression, Edgebreaker, vertex quantization
- iga_prep: knot_insertion, degree_elevation, Bezier extraction for IGA analysis
- quality_feedback: design_rule_check, manufacturability, cost_estimation, quality_score (0-100)
- 28 tests, ~2349 lines

27 files, ~7750 lines, 76 tests
2026-07-26 23:13:22 +08:00

337 lines
12 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/assembly_patterns.h"
#include "vde/brep/assembly.h"
#include "vde/brep/modeling.h"
#include "vde/core/transform.h"
#include <cmath>
using namespace vde::brep;
using namespace vde::core;
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
// ===========================================================================
// Helpers
// ===========================================================================
static Transform3D make_test_transform(double x = 0, double y = 0, double z = 0) {
return translate(x, y, z);
}
static Assembly make_test_assembly(const std::string& name = "TestAssembly") {
Assembly assembly(name);
auto box = make_box(10, 10, 5);
assembly.root.add_part("SourcePart", std::move(box),
make_test_transform(0, 0, 0));
return assembly;
}
// ===========================================================================
// circular_pattern 测试
// ===========================================================================
TEST(AssemblyPatternsTest, CircularPattern_FullCircle) {
Transform3D src = make_test_transform(30, 0, 0); // 在 X=30 处
CircularPatternParams params;
params.center = Point3D(0, 0, 0);
params.axis = Vector3D::UnitZ();
params.count = 8;
params.total_angle_deg = 360.0;
auto result = circular_pattern(src, "TestPart", params);
EXPECT_EQ(result.instances.size(), 8u);
EXPECT_EQ(result.total_count, 8u);
EXPECT_EQ(result.skipped_count, 0u);
// 所有实例应距离圆心 30mm
for (const auto& inst : result.instances) {
Point3D pos(inst.transform.translation().x(),
inst.transform.translation().y(),
inst.transform.translation().z());
double dist = (pos - params.center).norm();
EXPECT_NEAR(dist, 30.0, 1e-6);
}
}
TEST(AssemblyPatternsTest, CircularPattern_PartialArc) {
Transform3D src = make_test_transform(20, 0, 0);
CircularPatternParams params;
params.center = Point3D(0, 0, 0);
params.axis = Vector3D::UnitZ();
params.count = 4;
params.total_angle_deg = 180.0;
auto result = circular_pattern(src, "ArcPart", params);
EXPECT_EQ(result.instances.size(), 4u);
}
TEST(AssemblyPatternsTest, CircularPattern_SkipInstances) {
Transform3D src = make_test_transform(25, 0, 0);
CircularPatternParams params;
params.center = Point3D(0, 0, 0);
params.axis = Vector3D::UnitZ();
params.count = 6;
params.skip_instances = {1, 3, 5};
auto result = circular_pattern(src, "SkipPart", params);
EXPECT_EQ(result.instances.size(), 3u);
EXPECT_EQ(result.skipped_count, 3u);
}
// ===========================================================================
// rectangular_pattern 测试
// ===========================================================================
TEST(AssemblyPatternsTest, RectangularPattern_BasicGrid) {
Transform3D src = make_test_transform(0, 0, 0);
RectangularPatternParams params;
params.direction1 = Vector3D::UnitX();
params.direction2 = Vector3D::UnitY();
params.spacing1 = 15.0;
params.spacing2 = 12.0;
params.count1 = 3;
params.count2 = 4;
auto result = rectangular_pattern(src, "GridPart", params);
EXPECT_EQ(result.instances.size(), 12u); // 3 × 4
EXPECT_EQ(result.total_count, 12u);
// 第一个实例应为原点
Point3D p0(result.instances[0].transform.translation().x(),
result.instances[0].transform.translation().y(),
result.instances[0].transform.translation().z());
EXPECT_NEAR(p0.x(), 0.0, 1e-9);
EXPECT_NEAR(p0.y(), 0.0, 1e-9);
}
TEST(AssemblyPatternsTest, RectangularPattern_Staggered) {
Transform3D src = make_test_transform(0, 0, 0);
RectangularPatternParams params;
params.direction1 = Vector3D::UnitX();
params.direction2 = Vector3D::UnitY();
params.spacing1 = 20.0;
params.spacing2 = 20.0;
params.count1 = 3;
params.count2 = 3;
params.staggered = true;
params.stagger_offset = 0.5;
auto result = rectangular_pattern(src, "StaggerPart", params);
EXPECT_EQ(result.instances.size(), 9u); // 3 × 3
// 检查奇数行(索引 i=1 即第二行)有偏移
// 第 4 个实例 (i=1, j=0): index=3
Point3D p3(result.instances[3].transform.translation().x(),
result.instances[3].transform.translation().y(),
result.instances[3].transform.translation().z());
EXPECT_NEAR(p3.x(), 20.0, 1e-9); // spacing1 * 1
EXPECT_NEAR(p3.y(), 10.0, 1e-9); // stagger offset = 20 * 0.5
}
// ===========================================================================
// mirror_pattern 测试
// ===========================================================================
TEST(AssemblyPatternsTest, MirrorPattern_WithOriginal) {
Transform3D src = make_test_transform(10, 0, 0); // X=10
MirrorPatternParams params;
params.plane_origin = Point3D(0, 0, 0);
params.plane_normal = Vector3D::UnitY(); // YZ 平面镜像
params.copy_original = true;
auto result = mirror_pattern(src, "MirrorPart", params);
EXPECT_EQ(result.instances.size(), 2u); // 源 + 镜像
EXPECT_EQ(result.total_count, 2u);
// 源应在 X=10
Point3D p0(result.instances[0].transform.translation().x(),
result.instances[0].transform.translation().y(),
result.instances[0].transform.translation().z());
EXPECT_NEAR(p0.x(), 10.0, 1e-9);
EXPECT_NEAR(p0.y(), 0.0, 1e-9);
// 镜像应在 X=10, Y 不变(YZ平面镜像 → X不变, Y不变, Z不变?不,是平面法向=Y
// 实际上:镜像面是 XZ 平面 (法向 Y),所以 Y 坐标取反
Point3D p1(result.instances[1].transform.translation().x(),
result.instances[1].transform.translation().y(),
result.instances[1].transform.translation().z());
EXPECT_NEAR(p1.y(), 0.0, 1e-9); // 源在 XZ 平面上(Y=0),所以不变
}
TEST(AssemblyPatternsTest, MirrorPattern_NoOriginal) {
Transform3D src = make_test_transform(0, 5, 0);
MirrorPatternParams params;
params.plane_origin = Point3D(0, 0, 0);
params.plane_normal = Vector3D::UnitX(); // YZ 平面镜像
params.copy_original = false;
auto result = mirror_pattern(src, "MirrorOnly", params);
EXPECT_EQ(result.instances.size(), 1u); // 仅镜像
EXPECT_EQ(result.total_count, 1u);
Point3D p0(result.instances[0].transform.translation().x(),
result.instances[0].transform.translation().y(),
result.instances[0].transform.translation().z());
EXPECT_NEAR(p0.x(), 0.0, 1e-9); // 源在 YZ 平面上(X=0) → 镜像仍在 X=0
}
// ===========================================================================
// pattern_driven 测试
// ===========================================================================
TEST(AssemblyPatternsTest, PatternDriven_WithoutSourceAssembly) {
Transform3D src = make_test_transform(0, 0, 0);
PatternDrivenParams params;
params.feature_type = PatternDrivenParams::FeatureType::Hole;
auto result = pattern_driven(src, "DrivenPart", params);
// 无源装配体时,使用模拟特征位置
EXPECT_GT(result.instances.size(), 0u);
}
TEST(AssemblyPatternsTest, PatternDriven_MaxInstancesLimit) {
Transform3D src = make_test_transform(0, 0, 0);
PatternDrivenParams params;
params.max_instances = 3;
auto result = pattern_driven(src, "LimitedPart", params);
EXPECT_LE(result.instances.size(), static_cast<size_t>(params.max_instances));
}
// ===========================================================================
// fill_pattern 测试
// ===========================================================================
TEST(AssemblyPatternsTest, FillPattern_SquareGrid) {
Transform3D src = make_test_transform(0, 0, 0);
FillPatternParams params;
params.fill_min = Point3D(0, 0, 0);
params.fill_max = Point3D(50, 50, 0);
params.spacing = 10.0;
params.margin = 5.0;
params.hexagonal = false;
auto result = fill_pattern(src, "FillPart", params);
// 50x50 区域,间距 10mm,边距 5mm → 约 4x4=16 个实例
EXPECT_GT(result.instances.size(), 10u);
EXPECT_LT(result.instances.size(), 30u);
}
TEST(AssemblyPatternsTest, FillPattern_HexagonalGrid) {
Transform3D src = make_test_transform(0, 0, 0);
FillPatternParams params;
params.fill_min = Point3D(0, 0, 0);
params.fill_max = Point3D(40, 40, 0);
params.spacing = 10.0;
params.margin = 5.0;
params.hexagonal = true;
auto result = fill_pattern(src, "HexFillPart", params);
EXPECT_GT(result.instances.size(), 5u);
}
TEST(AssemblyPatternsTest, FillPattern_ExcludeRegions) {
Transform3D src = make_test_transform(0, 0, 0);
FillPatternParams params;
params.fill_min = Point3D(0, 0, 0);
params.fill_max = Point3D(50, 50, 0);
params.spacing = 10.0;
params.margin = 2.0;
// 排除中心区域
params.exclude_regions = {
{Point3D(20, 20, -1), Point3D(30, 30, 1)}
};
auto result_no_exclude = fill_pattern(src, "NoExclude", FillPatternParams{});
auto result_with_exclude = fill_pattern(src, "WithExclude", params);
// 带排除区的应少于不带排除区的
EXPECT_LT(result_with_exclude.instances.size(),
result_no_exclude.instances.size());
EXPECT_GT(result_with_exclude.skipped_count, 0u);
}
// ===========================================================================
// apply_pattern_to_assembly 测试
// ===========================================================================
TEST(AssemblyPatternsTest, ApplyPatternToAssembly_AddsNodes) {
Assembly assembly("PatternAssembly");
Transform3D src = make_test_transform(0, 0, 0);
CircularPatternParams params;
params.center = Point3D(0, 0, 0);
params.axis = Vector3D::UnitZ();
params.count = 4;
params.total_angle_deg = 360.0;
auto result = circular_pattern(src, "RingPart", params);
size_t before = assembly.node_count();
size_t applied = apply_pattern_to_assembly(assembly, result, "RingPart");
size_t after = assembly.node_count();
EXPECT_EQ(applied, 4u);
EXPECT_EQ(after, before + applied);
}
// ===========================================================================
// assembly_feature 增强功能测试 (PMI + 干涉检查)
// ===========================================================================
TEST(AssemblyPatternsTest, PMIPropagation_ResolvesAnnotations) {
Assembly assembly("PMITest");
auto box = make_box(10, 10, 5);
assembly.root.add_part("Bracket", std::move(box),
make_test_transform(10, 0, 0));
std::vector<PMIAnnotation> annotations;
PMIAnnotation anno;
anno.id = "PMI_001";
anno.type = PMIType::Dimension;
anno.label = "Hole spacing: 25mm";
anno.target_feature_id = "Bracket";
anno.position = Point3D(5, 5, 5); // 零件坐标系
annotations.push_back(anno);
auto result = propagate_pmi_to_assembly(assembly, annotations);
EXPECT_EQ(result.total_source_count, 1u);
EXPECT_EQ(result.propagated_count, 1u);
EXPECT_EQ(result.unresolved_count, 0u);
// 应该变换到世界坐标
EXPECT_GT(result.propagated_annotations[0].position.x(), 5.0);
}
TEST(AssemblyPatternsTest, InterferenceCheckBatch_NoInterference) {
Assembly assembly("ClearanceAssembly");
auto box1 = make_box(5, 5, 3);
auto box2 = make_box(5, 5, 3);
assembly.root.add_part("Part_A", std::move(box1),
make_test_transform(0, 0, 0));
assembly.root.add_part("Part_B", std::move(box2),
make_test_transform(20, 0, 0)); // 远离
auto result = interference_check_batch(assembly);
EXPECT_GT(result.total_pairs_checked, 0u);
EXPECT_EQ(result.interference_count, 0u)
<< "Well-separated parts should not interfere";
EXPECT_FALSE(result.summary_report.empty());
}