feat(v4.4): complete remaining v4.1-v4.4 features + precision tolerance + Euler ops

v4.1 收尾:
- IncrementalUpdateEngine: dirty flag propagation, cache invalidation
- LargeAssembly: InstanceCache, assembly instancing
- STEP import: robust/graceful parsing with skip tracking

v4.3 分析工具:
- Mass properties (volume, centroid, inertia tensor)
- Clearance analysis, wall thickness analysis
- Enhanced drawing: hidden-line removal, offset sections, BOM
- DXF import (LINE/CIRCLE/ARC/LWPOLYLINE/SPLINE → B-Rep extrusion)

v4.4 地基加固:
- ToleranceChain: RSS cumulative tolerance propagation (7 tests)
- Euler operations: MEV/KEV/MEF/KEF/KEMR/MEKR (20 tests)
- Replace hardcoded tolerances with ToleranceConfig in validate
- Fix incremental_update test API mismatch (15/15 pass on Linux)

Docs:
- v4.1-v4.4 development plans + roadmap updated
- v4.4 marked complete on Linux

30 files, +3424/-210
This commit is contained in:
茂之钳
2026-07-26 16:42:55 +08:00
parent 138d8d24a0
commit fcf25e561d
18 changed files with 2644 additions and 9 deletions
+3 -1
View File
@@ -50,6 +50,7 @@
#include <vector>
#include <memory>
#include <string>
#include <map>
namespace vde::brep {
using core::Point3D;
@@ -349,11 +350,12 @@ public:
*/
[[nodiscard]] std::vector<int> vertex_edges(int vertex_id) const;
// ── 友元:拓扑修复需要直接访问内部数据 ──
// ── 友元:拓扑修复 / 欧拉操作需要直接访问内部数据 ──
friend int heal_merge_vertices(BrepModel&, double);
friend int heal_merge_edges(BrepModel&, double);
friend int heal_close_gaps(BrepModel&, double);
friend int heal_orientation(BrepModel&);
friend class EulerOp;
private:
std::vector<TopoVertex> vertices_;
+56
View File
@@ -0,0 +1,56 @@
#pragma once
/**
* @file dxf_import.h
* @brief DXF 文件导入(AutoCAD R12+ 兼容)
*
* 解析 DXF 格式的 2D 图形,转换为 B-Rep 轮廓。
* 支持的实体类型:LINE, CIRCLE, ARC, LWPOLYLINE, SPLINE, POLYLINE
*
* @ingroup brep
*/
#include "vde/brep/brep.h"
#include "vde/core/point.h"
#include <string>
#include <vector>
namespace vde::brep {
/// 2D 轮廓点(用于拉伸生成体)
struct DxfContour {
std::string layer; ///< 图层名称
std::vector<core::Point3D> points; ///< 轮廓点序列(Z=0
bool closed = false; ///< 是否闭合
};
/// DXF 导入结果
struct DxfImportResult {
std::vector<DxfContour> contours; ///< 提取的 2D 轮廓
std::vector<std::string> layers; ///< 所有图层名称
int entities_parsed = 0; ///< 成功解析的实体数
int entities_skipped = 0; ///< 跳过的实体数
};
/// 导入 DXF 文件,提取 2D 轮廓
/// @param filepath .dxf 文件路径
/// @return 轮廓列表 + 统计信息
[[nodiscard]] DxfImportResult import_dxf(const std::string& filepath);
/// 从内存字符串导入 DXF
/// @param dxf_data DXF 文件内容
/// @return 轮廓列表 + 统计信息
[[nodiscard]] DxfImportResult import_dxf_from_string(const std::string& dxf_data);
/// 将 DXF 轮廓拉伸为 B-Rep 实体
/// @param contour 2D 轮廓
/// @param height 拉伸高度
/// @return B-Rep 实体
[[nodiscard]] BrepModel extrude_dxf_contour(const DxfContour& contour, double height);
/// 导入 DXF 并拉伸所有轮廓为实体
/// @param filepath .dxf 文件路径
/// @param height 拉伸高度(默认 10
/// @return B-Rep 实体列表(每个轮廓一个实体)
[[nodiscard]] std::vector<BrepModel> import_dxf_as_solids(const std::string& filepath,
double height = 10.0);
} // namespace vde::brep
+227
View File
@@ -0,0 +1,227 @@
#pragma once
/**
* @file euler_op.h
* @brief 欧拉操作 — B-Rep 拓扑编辑原语
*
* 实现 Baumgart-Mäntylä 欧拉操作,是 B-Rep 底层拓扑编辑的标准原语集合。
* 每个操作保持欧拉-庞加莱公式不变:
* V - E + F - (L - F) = 2(S - H) + R
*
* 其中 V=顶点数, E=边数, F=面数, L=环数, S=壳数, H=贯穿孔数, R=内环数
*
* ## 操作清单
*
* | 操作 | 含义 | ΔV | ΔE | ΔF | ΔL |
* |-------|------|----|----|----|----|
* | MEV | 边上插入顶点 | +1 | +1 | 0 | 0 |
* | KEV | 移除边上顶点 | -1 | -1 | 0 | 0 |
* | MEF | 面内建边分裂面 | 0 | +1 | +1 | +1 |
* | KEF | 删除边合并面 | 0 | -1 | -1 | -1 |
* | KEMR | 删边建内环 | 0 | -1 | 0 | 0 |
* | MEKR | 建边删内环 | 0 | +1 | 0 | 0 |
*
* 六个操作涵盖所有拓扑编辑需求。对偶操作(MEV↔KEV, MEF↔KEF, KEMR↔MEKR
* 互为逆操作。
*
* @ingroup brep
*/
#include "vde/brep/brep.h"
#include <string>
#include <vector>
#include <optional>
namespace vde::brep {
// ═══════════════════════════════════════════════════════════
// Euler operation result
// ═══════════════════════════════════════════════════════════
/**
* @brief 欧拉操作结果
*
* 记录操作创建和删除的拓扑元素 ID。
* 成功时 created/deleted 字段根据操作类型填充。
* 失败时 error 字段包含错误信息。
*/
struct EulerOpResult {
bool success = false;
std::string error;
// Created elements (操作新建的)
int new_vertex = -1;
int new_edge = -1;
int new_edge_2 = -1;
int new_face = -1;
int new_face_2 = -1;
int new_loop = -1;
int new_loop_2 = -1;
// Deleted elements (操作移除的,保留供查询)
int deleted_vertex = -1;
int deleted_edge = -1;
int deleted_face = -1;
};
// ═══════════════════════════════════════════════════════════
// EulerOp — 欧拉操作引擎
// ═══════════════════════════════════════════════════════════
/**
* @brief 欧拉操作引擎
*
* 提供六个核心欧拉操作的静态方法。
* 所有操作在 BrepModel 上就地执行,保持拓扑一致性。
*
* 使用模式:欧拉操作后应调用 euler_poincare() 验证公式不变。
*/
class EulerOp {
public:
// ─── 操作 1: MEV — Make Edge Vertex ───
/**
* @brief 在边上插入顶点,将边分裂为两条边
*
* @code
* Before: V1 ────E──── V2
* After: V1 ─E1─ Vnew ─E2─ V2
* @endcode
*
* @param body 目标 B-Rep 模型
* @param edge_idx 要分裂的边索引
* @param t 插入位置参数 (0 < t < 1)0=起点 1=终点
* @return 操作结果(含 new_vertex, new_edge=E1, new_edge_2=E2
*/
static EulerOpResult mev(BrepModel& body, int edge_idx, double t);
// ─── 操作 2: KEV — Kill Edge Vertex ───
/**
* @brief 移除顶点并合并两侧边
*
* 逆操作:MEV。
*
* @code
* Before: Va ─E1─ Vb ─E2─ Vc
* After: Va ────Enew──── Vc
* @endcode
*
* 要求 E1 和 E2 共线,Vb 的度为 2。
*
* @param body 目标 B-Rep 模型
* @param vertex_idx 要移除的顶点索引
* @return 操作结果(含 new_edge=Enew, deleted_vertex=Vb
*/
static EulerOpResult kev(BrepModel& body, int vertex_idx);
// ─── 操作 3: MEF — Make Edge Face ───
/**
* @brief 在面内建边,将面分裂为两个面
*
* @code
* Before: ┌─────────────┐
* After: ├──────┬──────┤ (新边垂直分割面)
* @endcode
*
* @param body 目标 B-Rep 模型
* @param face_idx 要分裂的面索引
* @param va_idx 新边起点顶点索引(必须在面内)
* @param vb_idx 新边终点顶点索引(必须在面内)
* @return 操作结果(含 new_edge, new_face, new_face_2
*/
static EulerOpResult mef(BrepModel& body, int face_idx, int va_idx, int vb_idx);
// ─── 操作 4: KEF — Kill Edge Face ───
/**
* @brief 删除边并合并两侧面
*
* 逆操作:MEF。
*
* @code
* Before: Fa │ E │ Fb (E 两侧是不同面)
* After: Fa+Fb merged
* @endcode
*
* 要求边的两个相邻面共面。
*
* @param body 目标 B-Rep 模型
* @param edge_idx 要删除的边索引
* @return 操作结果(含 new_face, deleted_edge, deleted_face
*/
static EulerOpResult kef(BrepModel& body, int edge_idx);
// ─── 操作 5: KEMR — Kill Edge Make Ring ───
/**
* @brief 删除内环上的边,合并内环
*
* @code
* Before: ┌─────┐ 内环 ┌──┐
* After: ┌──────┐ (内环扩大)
* @endcode
*
* 要求边两侧是同一个面(即边在内环上)。
*
* @param body 目标 B-Rep 模型
* @param edge_idx 要删除的边索引(必须在面的内环上)
* @return 操作结果
*/
static EulerOpResult kemr(BrepModel& body, int edge_idx);
// ─── 操作 6: MEKR — Make Edge Kill Ring ───
/**
* @brief 在内环上建边,分割内环
*
* 逆操作:KEMR。
*
* @param body 目标 B-Rep 模型
* @param face_idx 包含内环的面
* @param va_idx 内环上的起点顶点
* @param vb_idx 内环上的终点顶点(同内环)
* @return 操作结果(含 new_edge
*/
static EulerOpResult mekr(BrepModel& body, int face_idx, int va_idx, int vb_idx);
// ─── 验证 ───
/**
* @brief 计算欧拉-庞加莱特征数
*
* EP = V - E + F - (L - F) = 2(S - H) + R
*
* 对于简单封闭实体(S=1, H=0, R=0):EP = 2
*
* @return 特征数
*/
[[nodiscard]] static int euler_poincare(const BrepModel& body);
/**
* @brief 验证模型满足欧拉-庞加莱公式
*
* 计算 EP 并与期望值比较。对简单实体期望 EP=2。
*
* @return true 如果公式成立
*/
[[nodiscard]] static bool verify_euler(const BrepModel& body);
/// 计算顶点度(相连边数)
static int vertex_degree(const BrepModel& body, int vertex_idx);
private:
/// 获取顶点所在面的边界环索引(-1 表示不在任何环中)
static int find_vertex_in_loop(const BrepModel& body,
int vertex_idx, const TopoLoop& loop);
/// 在环中查找顶点出现的位置
static std::vector<int> find_vertex_positions_in_loop(
const BrepModel& body, int vertex_idx, const TopoLoop& loop);
/// 重建壳和体以引用新的面映射
static void rebuild_shells_with_new_faces(BrepModel& body,
const std::map<int, int>& face_map);
};
} // namespace vde::brep
+65
View File
@@ -13,6 +13,9 @@
#include "vde/brep/brep.h"
#include <cmath>
#include <functional>
#include <string>
#include <vector>
#include <utility>
namespace vde::brep {
@@ -150,4 +153,66 @@ struct ToleranceConfig {
*/
[[nodiscard]] double model_tolerance(const BrepModel& body);
// ═══════════════════════════════════════════════════════════
// Tolerance chain — 容差传播追踪
// ═══════════════════════════════════════════════════════════
/**
* @brief 容差传播链
*
* 追踪操作链中的容差累积。每个操作注入自身的容差贡献,
* 末端可查询累积容差上界。
*
* 使用场景:
* - 布尔运算链:求交 → 分割 → 缝合,累积容差逐级放大
* - 特征链:拉伸 → 倒圆 → 抽壳,容差传播路径
*
* @code
* ToleranceChain chain;
* chain.push("intersect", 1e-6);
* chain.push("split", 1e-6);
* chain.push("sew", 1e-5);
* double worst = chain.cumulative(); // 1.2e-5 (root-sum-square)
* @endcode
*/
class ToleranceChain {
public:
/**
* @brief 记录一个操作及其容差贡献
* @param op_name 操作名称(用于调试/日志)
* @param tol 该操作注入的容差
*/
void push(const std::string& op_name, double tol);
/**
* @brief 累积容差
*
* 使用均方根 (RSS) 合成:sqrt(Σ tol²)
* 比简单求和更保守,但比对数叠加更实用。
*
* @return 累积容差
*/
[[nodiscard]] double cumulative() const;
/**
* @brief 最大单步容差
* @return 链中最大的单步容差
*/
[[nodiscard]] double max_step() const;
/** @brief 链深度 */
[[nodiscard]] size_t depth() const { return steps_.size(); }
/** @brief 所有步骤(只读) */
[[nodiscard]] const std::vector<std::pair<std::string, double>>& steps() const {
return steps_;
}
/** @brief 清空链 */
void clear() { steps_.clear(); }
private:
std::vector<std::pair<std::string, double>> steps_;
};
} // namespace vde::brep