4b90438315
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
446 lines
17 KiB
C++
446 lines
17 KiB
C++
#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
|