feat(v3.2): assembly tree + benchmark report + CI/Python/3MF
This commit is contained in:
+39
-53
@@ -1,64 +1,50 @@
|
|||||||
# Performance Benchmark Report — M3 里程碑
|
# ViewDesignEngine 性能基准报告
|
||||||
|
|
||||||
**Date**: 2026-07-24
|
> 生成: 2026-07-24 | 环境: ECS 4核 2.5GHz, Ubuntu 22.04, GCC 11.4
|
||||||
**Hardware**: 4× 2500 MHz CPU, 1 GB RAM (Docker container)
|
|
||||||
**Compiler**: GCC 11.4, C++17, -O2
|
|
||||||
|
|
||||||
---
|
## BVH (包围盒层次)
|
||||||
|
|
||||||
## Results
|
| 指标 | 规模 | 耗时 | 吞吐 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| BVH 构建 | 500 tris | 73.9 µs | 6.8M tris/s |
|
||||||
|
| BVH 构建 | 1000 tris | 253 µs | 4.0M tris/s |
|
||||||
|
| BVH 构建 | 2000 tris | 656 µs | 3.1M tris/s |
|
||||||
|
| 射线查询 | 500 tris | 1.38 ms | 726k queries/s |
|
||||||
|
| 射线查询 | 1000 tris | 2.06 ms | 488k queries/s |
|
||||||
|
| 射线查询 | 2000 tris | 3.21 ms | 313k queries/s |
|
||||||
|
|
||||||
### BVH (Bounding Volume Hierarchy)
|
## Delaunay 三角剖分
|
||||||
|
|
||||||
| Benchmark | Size | Time | Throughput |
|
| 指标 | 规模 | 耗时 | 吞吐 |
|
||||||
|-----------|------|------|------------|
|
|------|------|------|------|
|
||||||
| Build | 500 tris | 72 µs | 6.9M tris/s |
|
| Delaunay 2D | 100 pts | 64.8 µs | 1.55M pts/s |
|
||||||
| Build | 1000 tris | 245 µs | 4.1M tris/s |
|
| Delaunay 2D | 1000 pts | 3.97 ms | 253k pts/s |
|
||||||
| Build | 2000 tris | 650 µs | 3.1M tris/s |
|
| Delaunay 2D | 10000 pts | 659 ms | 15.2k pts/s |
|
||||||
| Ray Query | 500 tris × 1000 rays | 1.39 ms | 721K rays/s |
|
|
||||||
| Ray Query | 2000 tris × 1000 rays | 3.19 ms | 315K rays/s |
|
|
||||||
|
|
||||||
### 2D Delaunay Triangulation
|
## 曲线求值
|
||||||
|
|
||||||
| Benchmark | Size | Time | Throughput |
|
| 指标 | 规模 | 耗时 | 吞吐 |
|
||||||
|-----------|------|------|------------|
|
|------|------|------|------|
|
||||||
| Bowyer-Watson | 100 pts | 67 µs | 1.5M pts/s |
|
| Bézier 求值 | 10K evals | 277 µs | 36M evals/s |
|
||||||
| Bowyer-Watson | 1000 pts | 3.96 ms | 254K pts/s |
|
| Bézier 求值 | 100K evals | 2.64 ms | 38M evals/s |
|
||||||
| Bowyer-Watson | 10000 pts | 671 ms | 15K pts/s |
|
| B-Spline 求值 | 10K evals | 720 µs | 14M evals/s |
|
||||||
|
| B-Spline 求值 | 100K evals | 7.14 ms | 14M evals/s |
|
||||||
|
|
||||||
### Curve Evaluation
|
## 空间索引
|
||||||
|
|
||||||
| Benchmark | Size | Time | Throughput |
|
| 指标 | 规模 | 耗时 | 吞吐 |
|
||||||
|-----------|------|------|------------|
|
|------|------|------|------|
|
||||||
| Bezier (cubic) | 10000 | 263 µs | 38.5M evals/s |
|
| R-Tree 构建 | 500 items | 19.2 µs | 26.1M items/s |
|
||||||
| Bezier (cubic) | 100000 | 2.66 ms | 38.1M evals/s |
|
| R-Tree 构建 | 1000 items | 35.7 µs | 28.2M items/s |
|
||||||
| BSpline (cubic) | 10000 | 744 µs | 13.6M evals/s |
|
| R-Tree 构建 | 2000 items | 151 µs | 13.3M items/s |
|
||||||
| BSpline (cubic) | 100000 | 7.33 ms | 13.8M evals/s |
|
| R-Tree kNN | 500 items | 23.9 µs | 842k queries/s |
|
||||||
|
| R-Tree kNN | 1000 items | 27.5 µs | 731k queries/s |
|
||||||
|
|
||||||
### Spatial Index (KD-Tree)
|
## 网格布尔运算
|
||||||
|
|
||||||
| Benchmark | Size | Time | Throughput |
|
| 指标 | 规模 | 耗时 | 吞吐 |
|
||||||
|-----------|------|------|------------|
|
|------|------|------|------|
|
||||||
| Build | 500 pts | 71 µs | 7.0M pts/s |
|
| Union | ~100 faces | 2.00 ms | 501 ops/s |
|
||||||
| Build | 2000 pts | 537 µs | 3.8M pts/s |
|
| Union | ~500 faces | 9.69 ms | 104 ops/s |
|
||||||
| kNN (k=10) | 500 pts | 64 µs | 316K q/s |
|
| Intersection | ~100 faces | 1.85 ms | 544 ops/s |
|
||||||
| kNN (k=10) | 2000 pts | 92 µs | 218K q/s |
|
| Intersection | ~500 faces | 8.32 ms | 121 ops/s |
|
||||||
|
|
||||||
### 3D Mesh Boolean
|
|
||||||
|
|
||||||
| Benchmark | Size | Time | Throughput |
|
|
||||||
|-----------|------|------|------------|
|
|
||||||
| Union | ~100 faces | 2.05 ms | 495 ops/s |
|
|
||||||
| Union | ~500 faces | 9.10 ms | 112 ops/s |
|
|
||||||
| Intersection | ~100 faces | 1.94 ms | 520 ops/s |
|
|
||||||
| Intersection | ~500 faces | 8.53 ms | 118 ops/s |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
All 5 benchmark modules operational (143/143 tests pass). Performance scales reasonably for a C++17 geometry engine without SIMD optimization. Key areas for future improvement:
|
|
||||||
|
|
||||||
- BVH ray query: implement packet tracing / wider BVH
|
|
||||||
- Delaunay 2D: O(n²) in current Bowyer-Watson
|
|
||||||
- Boolean: clip-by-point-test is brute-force; needs exact mesh clipping
|
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "vde/brep/brep.h"
|
||||||
|
#include "vde/core/transform.h"
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace vde::brep {
|
||||||
|
|
||||||
|
using core::Point3D;
|
||||||
|
using core::Vector3D;
|
||||||
|
using core::Transform3D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 装配体节点
|
||||||
|
*
|
||||||
|
* 表示装配体树中的一个节点,可以是零件(含 B-Rep 模型)或子装配体。
|
||||||
|
* 每个节点有局部变换矩阵和名称。
|
||||||
|
*
|
||||||
|
* @ingroup brep
|
||||||
|
*/
|
||||||
|
struct AssemblyNode {
|
||||||
|
/// 节点名称
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
/// 局部(相对父节点)变换矩阵
|
||||||
|
Transform3D local_transform = Transform3D::Identity();
|
||||||
|
|
||||||
|
/// 此节点的 B-Rep 模型(零件),可选
|
||||||
|
std::optional<BrepModel> model;
|
||||||
|
|
||||||
|
/// 子节点列表
|
||||||
|
std::vector<std::unique_ptr<AssemblyNode>> children;
|
||||||
|
|
||||||
|
/// 是否是零件节点(有 model)还是子装配体(有 children)
|
||||||
|
[[nodiscard]] bool is_part() const { return model.has_value() && children.empty(); }
|
||||||
|
|
||||||
|
/// 添加子节点
|
||||||
|
AssemblyNode* add_child(std::unique_ptr<AssemblyNode> child) {
|
||||||
|
auto* ptr = child.get();
|
||||||
|
children.push_back(std::move(child));
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 添加 B-Rep 零件
|
||||||
|
AssemblyNode* add_part(const std::string& part_name, BrepModel part_model,
|
||||||
|
const Transform3D& transform = Transform3D::Identity()) {
|
||||||
|
auto node = std::make_unique<AssemblyNode>();
|
||||||
|
node->name = part_name;
|
||||||
|
node->model = std::move(part_model);
|
||||||
|
node->local_transform = transform;
|
||||||
|
return add_child(std::move(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 添加子装配体
|
||||||
|
AssemblyNode* add_subassembly(const std::string& sub_name,
|
||||||
|
const Transform3D& transform = Transform3D::Identity()) {
|
||||||
|
auto node = std::make_unique<AssemblyNode>();
|
||||||
|
node->name = sub_name;
|
||||||
|
node->local_transform = transform;
|
||||||
|
return add_child(std::move(node));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 装配体
|
||||||
|
*
|
||||||
|
* 装配体树的根节点。管理零件和子装配体的层级结构。
|
||||||
|
*
|
||||||
|
* @code{.cpp}
|
||||||
|
* Assembly assembly("robot_arm");
|
||||||
|
* auto* base = assembly.root().add_part("base", make_box(10,10,2));
|
||||||
|
* auto* arm = assembly.root().add_subassembly("arm",
|
||||||
|
* Transform3D::Translation(0,0,2));
|
||||||
|
* arm->add_part("segment1", make_cylinder(0.5, 8));
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @ingroup brep
|
||||||
|
*/
|
||||||
|
struct Assembly {
|
||||||
|
/// 装配体名称
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
/// 版本号
|
||||||
|
std::string version = "1.0";
|
||||||
|
|
||||||
|
/// 根节点
|
||||||
|
AssemblyNode root;
|
||||||
|
|
||||||
|
explicit Assembly(const std::string& n) : name(n) { root.name = n; }
|
||||||
|
|
||||||
|
/// 遍历所有零件节点
|
||||||
|
template <typename Visitor>
|
||||||
|
void visit_parts(Visitor&& visitor) const {
|
||||||
|
visit_parts_impl(&root, Transform3D::Identity(),
|
||||||
|
std::forward<Visitor>(visitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 统计零件总数
|
||||||
|
[[nodiscard]] size_t part_count() const {
|
||||||
|
size_t count = 0;
|
||||||
|
visit_parts([&count](const std::string&, const BrepModel&, const Transform3D&) {
|
||||||
|
++count;
|
||||||
|
});
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 统计节点总数(含子装配体)
|
||||||
|
[[nodiscard]] size_t node_count() const {
|
||||||
|
return node_count_impl(&root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename Visitor>
|
||||||
|
void visit_parts_impl(const AssemblyNode* node, const Transform3D& parent_xform,
|
||||||
|
Visitor&& visitor) const {
|
||||||
|
Transform3D world = parent_xform * node->local_transform;
|
||||||
|
if (node->model.has_value()) {
|
||||||
|
visitor(node->name, *node->model, world);
|
||||||
|
}
|
||||||
|
for (const auto& child : node->children) {
|
||||||
|
visit_parts_impl(child.get(), world, std::forward<Visitor>(visitor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t node_count_impl(const AssemblyNode* node) const {
|
||||||
|
size_t n = 1; // count this node
|
||||||
|
for (const auto& child : node->children)
|
||||||
|
n += node_count_impl(child.get());
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace vde::brep
|
||||||
@@ -6,3 +6,4 @@ add_vde_test(test_brep_boolean)
|
|||||||
add_vde_test(test_brep_validate)
|
add_vde_test(test_brep_validate)
|
||||||
add_vde_test(test_iges_import)
|
add_vde_test(test_iges_import)
|
||||||
add_vde_test(test_iges_export)
|
add_vde_test(test_iges_export)
|
||||||
|
add_vde_test(test_assembly)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "vde/brep/assembly.h"
|
||||||
|
#include "vde/brep/modeling.h"
|
||||||
|
|
||||||
|
using namespace vde::brep;
|
||||||
|
using namespace vde::core;
|
||||||
|
|
||||||
|
TEST(AssemblyTest, CreateEmpty) {
|
||||||
|
Assembly assy("test");
|
||||||
|
EXPECT_EQ(assy.name, "test");
|
||||||
|
EXPECT_EQ(assy.part_count(), 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AssemblyTest, AddPart) {
|
||||||
|
Assembly assy("test");
|
||||||
|
auto box = make_box(2, 2, 2);
|
||||||
|
assy.root.add_part("box1", box);
|
||||||
|
EXPECT_EQ(assy.part_count(), 1u);
|
||||||
|
EXPECT_EQ(assy.node_count(), 2u); // root + box
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AssemblyTest, NestedHierarchy) {
|
||||||
|
Assembly assy("robot");
|
||||||
|
auto* base = assy.root.add_part("base", make_box(10, 5, 2));
|
||||||
|
auto* arm = assy.root.add_subassembly("arm");
|
||||||
|
arm->add_part("seg1", make_cylinder(1, 8));
|
||||||
|
arm->add_part("seg2", make_cylinder(1, 6));
|
||||||
|
|
||||||
|
EXPECT_EQ(assy.part_count(), 3u);
|
||||||
|
EXPECT_EQ(assy.node_count(), 4u); // root + arm + seg1 + seg2
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AssemblyTest, VisitPartsWithTransform) {
|
||||||
|
Assembly assy("test");
|
||||||
|
assy.root.add_part("p1", make_box(1, 1, 1));
|
||||||
|
|
||||||
|
size_t visited = 0;
|
||||||
|
assy.visit_parts([&visited](const std::string& name, const BrepModel&,
|
||||||
|
const Transform3D& xform) {
|
||||||
|
EXPECT_EQ(name, "p1");
|
||||||
|
EXPECT_TRUE(xform.isApprox(Transform3D::Identity()));
|
||||||
|
visited++;
|
||||||
|
});
|
||||||
|
EXPECT_EQ(visited, 1u);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user