Files
ViewDesignEngine/include/vde/brep/tolerant_modeling.h
T
茂之钳 4b90438315
CI / Build & Test (push) Failing after 24s
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(v1.0.1): P2 improvements — TolerantEdge + .vde format + boolean fallback
P2 — TolerantEdge (ACIS):
- TolerantEdge: tube_radius, path_tolerance, is_within_tube, intersects
- detect_tolerance_conflicts: VertexInSphere/EdgeInTube/FaceGap
- resolve_tolerance_conflicts: auto merge vertices/edges/gaps

P2 — .vde Native Format (ACIS):
- Binary format: VdeHeader(32B), 6 SerializationSections
- save_vde/load_vde: full B-Rep topology + geometry + tolerance + attributes
- save_vde_json: debuggable JSON export

P2 — Boolean Fallback Ladder (Parasolid):
- 4-level cascade: SSI → tolerant → mesh → SDF
- Level 4 never fails (SDF + MC mathematical guarantee)
- BooleanFallbackResult with per-level diagnostics

Total: +~2650 lines across 8 files
2026-07-27 21:01:33 +08:00

446 lines
17 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.
#pragma once
/**
* @file tolerant_modeling.h
* @brief 容错建模 — 对标 Parasolid 的容差感知 B-Rep 操作
*
* 提供带容差范围的拓扑元素、自适应合并、间隙桥接、重叠面检测、
* 容差感知布尔运算和 STEP 导入一站式修复流水线。
*
* ## 核心能力
*
* 1. **TolerantVertex/TolerantEdge** — 带容差范围的拓扑元素
* 2. **merge_within_tolerance** — BFS 自适应顶点合并
* 3. **gap_bridging** — 间隙检测 + 自动三角填充
* 4. **overlap_resolution** — 重叠面检测 + 移除
* 5. **tolerant_boolean** — 容差感知布尔 (heal → boolean → heal)
* 6. **import_heal_pipeline** — STEP 导入一站式修复
*
* @ingroup brep
*/
#include "vde/brep/brep.h"
#include "vde/brep/brep_heal.h"
#include "vde/brep/tolerance.h"
#include "vde/core/point.h"
#include <string>
#include <vector>
#include <map>
#include <functional>
namespace vde::brep {
using core::Point3D;
using core::Vector3D;
using core::AABB3D;
// ═══════════════════════════════════════════════════════════
// TolerantVertex / TolerantEdge
// ═══════════════════════════════════════════════════════════
/**
* @brief 带容差范围的拓扑顶点
*
* 除了基本坐标外,额外存储:
* - tolerance: 该顶点的合并容差(可独立于全局配置)
* - merged_from: 若此顶点由多个原始顶点合并而来,记录合并数
* - is_degenerate: 标记退化顶点(如切触引起的退化边端点)
*/
struct TolerantVertex {
int id; ///< 顶点 ID(对应 BrepModel 中的索引)
Point3D point; ///< 三维坐标
double tolerance = 1e-6; ///< 该顶点的独立容差
int merged_from = 1; ///< 合并来源顶点数(1 = 未合并)
bool is_degenerate = false; ///< 是否属于退化拓扑
/// 从普通 TopoVertex 构造
static TolerantVertex from_topo(const TopoVertex& tv) {
TolerantVertex res;
res.id = tv.id;
res.point = tv.point;
res.tolerance = tv.tolerance;
return res;
}
};
/**
* @brief 带容差范围的拓扑边(管状边模型)
*
* 将边建模为以其中心线为轴、tube_radius 为半径的圆柱管。
* 额外存储:
* - tolerance: 边的曲线拟合容差
* - tube_radius: 管半径(自适应=该边两端点容差的最大值)
* - path_tolerance: 路径允许最大偏移(曲线拟合误差上限)
* - is_degenerate: 退化边(两端点重合或长度<tolerance
* - gap_flag: 标记此边参与间隙桥接
*
* 管状边用于容差冲突检测:若两条边的管相交但边本身不共线,
* 则判定为 EdgeInTube 冲突。
*/
struct TolerantEdge {
int id; ///< 边 ID(即 edge_id
int v_start, v_end; ///< 端点顶点 ID
double tolerance = 1e-6; ///< 曲线拟合容差
double tube_radius = 1e-6; ///< 管半径(自适应=两端点容差的最大值)
double path_tolerance = 1e-4; ///< 路径允许最大偏移
bool is_degenerate = false; ///< 退化边(长度 < tolerance
bool gap_flag = false; ///< 参与间隙桥接
bool is_tangent_contact = false; ///< 切触边(奇异性处理)
// ── 缓存的端点坐标(用于 is_within_tube / intersects 的无 body 调用) ──
Point3D p_start; ///< 起点坐标(缓存)
Point3D p_end; ///< 终点坐标(缓存)
/// 从普通 TopoEdge 构造
static TolerantEdge from_topo(const TopoEdge& te) {
TolerantEdge res;
res.id = te.id;
res.v_start = te.v_start;
res.v_end = te.v_end;
return res;
}
/// 判断是否为退化边
[[nodiscard]] bool check_degenerate(const BrepModel& body) const {
const auto& v0 = body.vertex(v_start);
const auto& v1 = body.vertex(v_end);
return (v1.point - v0.point).norm() < tolerance;
}
/// 从 BrepModel 计算并缓存端点坐标 + 自适应管半径
void cache_from_body(const BrepModel& body) {
p_start = body.vertex(v_start).point;
p_end = body.vertex(v_end).point;
tube_radius = std::max(body.vertex(v_start).tolerance,
body.vertex(v_end).tolerance);
if (tube_radius < tolerance) tube_radius = tolerance;
}
/// 检测点是否在管状边内部(点到线段距离 < tube_radius
[[nodiscard]] bool is_within_tube(const Point3D& point) const {
Vector3D ab = p_end - p_start;
double len_sq = ab.squaredNorm();
if (len_sq < 1e-20) {
// 退化边:退化为球体
return (point - p_start).norm() < tube_radius;
}
Vector3D ap = point - p_start;
double t = ap.dot(ab) / len_sq;
t = std::max(0.0, std::min(1.0, t));
Point3D closest = p_start + ab * t;
return (point - closest).norm() < tube_radius;
}
/// 检测两条边管是否相交(线段间最小距离 < tube_radius_a + tube_radius_b
[[nodiscard]] bool intersects(const TolerantEdge& other) const {
double min_dist = segment_segment_distance(
p_start, p_end, other.p_start, other.p_end);
return min_dist < (tube_radius + other.tube_radius);
}
/// 两条线段之间的最小距离
[[nodiscard]] static double segment_segment_distance(
const Point3D& a0, const Point3D& a1,
const Point3D& b0, const Point3D& b1)
{
Vector3D d1 = a1 - a0;
Vector3D d2 = b1 - b0;
Vector3D r = a0 - b0;
double a = d1.squaredNorm();
double e = d2.squaredNorm();
double f = d2.dot(r);
// 处理退化线段
double s, t;
if (a < 1e-20 && e < 1e-20) {
// 两条线段都退化为点
return r.norm();
}
if (a < 1e-20) {
// 第一条线段退化为点
s = 0.0;
t = std::max(0.0, std::min(1.0, f / e));
} else {
double c = d1.dot(r);
if (e < 1e-20) {
// 第二条线段退化为点
t = 0.0;
s = std::max(0.0, std::min(1.0, -c / a));
} else {
double b = d1.dot(d2);
double denom = a * e - b * b;
// 并行线段特殊处理
if (std::abs(denom) < 1e-20) {
s = 0.0;
} else {
s = std::max(0.0, std::min(1.0, (b * f - c * e) / denom));
}
double tnom = b * s + f;
t = std::max(0.0, std::min(1.0, tnom / e));
}
}
Point3D closest_a = a0 + d1 * s;
Point3D closest_b = b0 + d2 * t;
return (closest_a - closest_b).norm();
}
/// 获取边的单位方向向量
[[nodiscard]] Vector3D direction() const {
Vector3D d = p_end - p_start;
double n = d.norm();
if (n < 1e-12) return Vector3D(1, 0, 0);
return d / n;
}
/// 获取边的长度
[[nodiscard]] double length() const {
return (p_end - p_start).norm();
}
};
// ═══════════════════════════════════════════════════════════
// BooleanDiagnostic — 布尔运算失败面诊断报告
// ═══════════════════════════════════════════════════════════
/**
* @brief 布尔运算失败面的诊断信息
*
* 记录每个失败面的原因、位置和容差建议。
*/
struct FailedFaceInfo {
int face_id = -1; ///< 失败的面 ID
std::string reason; ///< 失败原因描述
int related_face_id = -1; ///< 关联面 ID(如有)
double suggested_tolerance = 0.0; ///< 建议的容差增大值
Point3D location; ///< 失败位置(近似)
bool is_coplanar = false; ///< 共面退化
bool is_tangent = false; ///< 切触退化
bool is_near_miss = false; ///< 接近但未精确相交
};
/**
* @brief 容差感知布尔运算的诊断结果
*
* 包含布尔运算的完整诊断信息,包括预处理/后处理统计、
* 失败面列表和建议修复方案。
*/
struct BooleanDiagnostic {
bool success = false; ///< 布尔运算是否成功
BrepModel result; ///< 结果模型
// ── 预处理统计 ──
int pre_heal_merged_vertices = 0; ///< 预处理合并的顶点数
int pre_heal_closed_gaps = 0; ///< 预处理闭合的间隙数
int pre_heal_removed_slivers = 0; ///< 预处理移除的退化面数
// ── 布尔运算统计 ──
int degeneracy_detections = 0; ///< 检测到的退化情况数
int coplanar_pairs = 0; ///< 共面对数量
int tangent_contacts = 0; ///< 切触对数量
int exact_predicate_upgrades = 0; ///< 升级到精确谓词的次数
// ── 后处理统计 ──
int post_heal_merged_vertices = 0; ///< 后处理合并的顶点数
int post_heal_bridged_gaps = 0; ///< 后处理桥接的间隙数
int post_heal_resolved_overlaps = 0;///< 后处理解决的重叠面数
// ── 失败诊断 ──
std::vector<FailedFaceInfo> failed_faces; ///< 失败面列表
std::string error_message; ///< 错误信息
/// 诊断是否包含失败面
[[nodiscard]] bool has_failures() const {
return !failed_faces.empty() || !error_message.empty();
}
/// 生成可读的诊断报告
[[nodiscard]] std::string report() const;
};
// ═══════════════════════════════════════════════════════════
// ToleranceConflict — 容差冲突自动检测
// ═══════════════════════════════════════════════════════════
/// 容差冲突类型
enum class ConflictType {
VertexInSphere, ///< 顶点球体重叠但未合并
EdgeInTube, ///< 边管相交但未共线
FaceGap ///< 面间间隙 < 容差
};
/**
* @brief 容差冲突记录
*
* 记录检测到的容差冲突类型、涉及元素和推荐修复策略。
*/
struct ToleranceConflict {
ConflictType type; ///< 冲突类型
int element_id_a = -1; ///< 冲突元素 A 的 ID
int element_id_b = -1; ///< 冲突元素 B 的 ID
double gap_size = 0.0; ///< 冲突的几何尺寸(距离/间隙值)
std::string resolution; ///< 建议的合并/修复策略
/// 是否为顶点球体冲突
[[nodiscard]] bool is_vertex_conflict() const {
return type == ConflictType::VertexInSphere;
}
/// 是否为边管冲突
[[nodiscard]] bool is_edge_conflict() const {
return type == ConflictType::EdgeInTube;
}
/// 是否为面间隙冲突
[[nodiscard]] bool is_face_conflict() const {
return type == ConflictType::FaceGap;
}
};
/**
* @brief 检测 B-Rep 模型中的容差冲突
*
* 执行三项检测:
* 1. 顶点球体重叠:两点距离 < tolerance_a + tolerance_b 但尚未合并
* 2. 边管相交:两条边的管状区域相交但边不共线
* 3. 面间间隙:面之间存在微小间隙(自由边距离 < 容差)
*
* @param body 待检测的 B-Rep 模型
* @param vertex_tolerance 顶点合并容差,默认 1e-6
* @param edge_tolerance 边管容差倍率,默认 2.0(顶点容差的倍数)
* @param face_gap_tolerance 面间隙容差,默认 1e-4
* @return 检测到的冲突列表
*/
[[nodiscard]] std::vector<ToleranceConflict> detect_tolerance_conflicts(
const BrepModel& body,
double vertex_tolerance = 1e-6,
double edge_tolerance = 2.0,
double face_gap_tolerance = 1e-4);
/**
* @brief 解析并修复容差冲突
*
* 按冲突类型分类处理:
* - VertexInSphere → 调用 heal_gaps 合并顶点
* - EdgeInTube → 调用 heal_merge_edges 合并边
* - FaceGap → 调用 heal_gaps 闭合间隙
*
* @param body 待修复的 B-Rep 模型(原地修改)
* @param conflicts 冲突列表(来自 detect_tolerance_conflicts
* @return 修复的冲突数量
*/
int resolve_tolerance_conflicts(
BrepModel& body,
const std::vector<ToleranceConflict>& conflicts);
// ═══════════════════════════════════════════════════════════
// Public API
// ═══════════════════════════════════════════════════════════
/**
* @brief BFS 自适应顶点合并
*
* 与 heal_gaps 类似,但支持:
* - 每顶点独立容差(TolerantVertex::tolerance
* - 自适应容差:根据局部几何特征动态调整合并阈值
* - 退化顶点检测:标记切触产生的退化顶点
*
* @param body 待修复的 B-Rep 模型(原地修改)
* @param tolerance 全局合并容差,默认 1e-6
* @return 合并的顶点对数
*/
int merge_within_tolerance(BrepModel& body, double tolerance = 1e-6);
/**
* @brief 间隙检测 + 自动三角填充
*
* 检测面之间的微小间隙(距离 < max_gap),
* 自动生成三角填充面以桥接间隙。
*
* 算法:
* 1. 遍历所有边,查找自由边(仅属于一个面)
* 2. 对自由边配对:距离 < max_gap 的边视为间隙边界
* 3. 对每对自由边生成三角填充面
*
* @param body 待修复的 B-Rep 模型(原地修改)
* @param max_gap 最大间隙尺寸,默认 1e-4
* @return 填充的间隙数
*/
int gap_bridging(BrepModel& body, double max_gap = 1e-4);
/**
* @brief 重叠面检测 + 移除
*
* 检测并移除重叠/重复的面:
* 1. 计算每对面的法向量和位置相似度
* 2. 共面且重叠 → 保留面积较大的面,移除较小的面
* 3. 更新壳的面引用
*
* @param body 待修复的 B-Rep 模型(原地修改)
* @return 移除的重叠面数
*/
int overlap_resolution(BrepModel& body);
/**
* @brief 容差感知布尔运算
*
* 完整流水线:heal → boolean → heal
* 1. 预处理:对 A 和 B 分别执行 heal_topology
* 2. 退化检测:共面/共线/切触 → 记录诊断信息
* 3. 执行布尔运算(委托给 ssi_boolean_*
* 4. 后处理:gap_bridging + overlap_resolution + heal_topology
*
* @param a 第一个 B-Rep 实体
* @param b 第二个 B-Rep 实体
* @param op 布尔操作类型:0=union, 1=intersection, 2=difference
* @return BooleanDiagnostic 含结果模型和诊断信息
*/
[[nodiscard]] BooleanDiagnostic tolerant_boolean(
const BrepModel& a, const BrepModel& b, int op);
/**
* @brief STEP 导入一站式修复流水线
*
* 导入 STEP 数据后自动执行完整修复:
* 1. 导入 STEP(内部调用 import_step_from_string
* 2. 对每个 body 执行 merge_within_tolerance
* 3. 执行 gap_bridging 闭合微小间隙
* 4. 执行 overlap_resolution 移除重复面
* 5. 执行 heal_orientation 统一方向
* 6. 验证最终结果
*
* @param step_data STEP 文件内容(字符串形式)
* @return 修复后的 B-Rep 体列表
*
* @note 与直接调用 import_step_from_string() + heal_topology() 不同,
* 此函数按容错建模流水线顺序执行,包含 gap_bridging 和
* overlap_resolution 步骤。
*/
[[nodiscard]] std::vector<BrepModel> import_heal_pipeline(const std::string& step_data);
// ═══════════════════════════════════════════════════════════
// Internal utilities(跨编译单元可见)
// ═══════════════════════════════════════════════════════════
/// 检测两个面是否为退化共面对
[[nodiscard]] bool detect_coplanar_faces(
const BrepModel& body, int face_a, int face_b, double tolerance = 1e-6);
/// 检测两个面是否为退化切触对
[[nodiscard]] bool detect_tangent_contact(
const BrepModel& body, int face_a, int face_b, double tolerance = 1e-6);
/// 检测边是否为退化边(长度 < tolerance
[[nodiscard]] bool detect_degenerate_edge(
const BrepModel& body, int edge_id, double tolerance = 1e-6);
/// 构建 TolerantVertex 映射表
[[nodiscard]] std::vector<TolerantVertex> build_tolerant_vertices(
const BrepModel& body, double global_tolerance = 1e-6);
/// 构建 TolerantEdge 映射表
[[nodiscard]] std::vector<TolerantEdge> build_tolerant_edges(
const BrepModel& body, double global_tolerance = 1e-6);
} // namespace vde::brep