feat(v1.0.1): Parasolid + ACIS boolean robustness improvements
P0 — Boolean Robustness (Parasolid): - 7-class degenerate taxonomy: surface_overlap, curve_overlap, high_order, boundary_contact, vertex_contact, non_manifold_contact - Multi-point sampling: N=sqrt(area/tol²) points, >75% voting, exact_orient3d fallback on boundary cases - PrecisionTracker: accumulated loss, threshold warning P0 — Auto-Heal Pipeline (ACIS): - 5-step pipeline: Stitch→Simplify→Regularize→Orient→Check - AutoHealReport with per-step status, element counts, overall score P1 — Direct Modeling (ACIS): - fill_hole: edge loop → plane fit → NURBS insert - pull_up_to_face: push_pull to target face alignment Total: +~1500 lines across 6 files
This commit is contained in:
@@ -58,6 +58,10 @@ using core::Point3D;
|
||||
using core::Vector3D;
|
||||
using core::AABB3D;
|
||||
|
||||
// 前向声明(定义在 brep_heal.h)
|
||||
struct AutoHealReport;
|
||||
struct StepResult;
|
||||
|
||||
/**
|
||||
* @brief 边曲线类型枚举
|
||||
*
|
||||
@@ -384,6 +388,12 @@ public:
|
||||
friend int heal_orientation(BrepModel&);
|
||||
friend bool heal_topology(BrepModel&, double);
|
||||
friend int heal_step_import(BrepModel&);
|
||||
friend AutoHealReport auto_heal_pipeline(BrepModel&, double);
|
||||
friend StepResult step_stitch(BrepModel&, double);
|
||||
friend StepResult step_simplify(BrepModel&, double);
|
||||
friend StepResult step_regularize(BrepModel&, double);
|
||||
friend StepResult step_orient(BrepModel&);
|
||||
friend StepResult step_check(const BrepModel&);
|
||||
friend class EulerOp;
|
||||
|
||||
private:
|
||||
|
||||
@@ -85,6 +85,80 @@ int heal_orientation(BrepModel& body);
|
||||
*/
|
||||
bool heal_topology(BrepModel& body, double tolerance = 1e-6);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// AutoHealReport — 五步流水线修复报告
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* @brief 单步修复结果
|
||||
*/
|
||||
struct StepResult {
|
||||
bool success = false; ///< 本步是否成功执行
|
||||
std::string step_name; ///< 步骤名称
|
||||
int items_fixed = 0; ///< 修复的元素数量
|
||||
std::vector<std::string> warnings; ///< 本步产生的警告
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ACIS风格五步自动修复流水线报告
|
||||
*
|
||||
* 包含每一步的详细结果、总体评分和剩余警告。
|
||||
*/
|
||||
struct AutoHealReport {
|
||||
StepResult stitch; ///< Step 1: 缝合
|
||||
StepResult simplify; ///< Step 2: 简化
|
||||
StepResult regularize; ///< Step 3: 正则化
|
||||
StepResult orient; ///< Step 4: 定向
|
||||
StepResult check; ///< Step 5: 验证
|
||||
|
||||
/** @brief 总体评分 (0-100),基于每步成功率和修复量 */
|
||||
int overall_score = 0;
|
||||
|
||||
/** @brief 剩余警告列表(跨步骤汇总) */
|
||||
std::vector<std::string> warnings;
|
||||
|
||||
/** @brief 总修复元素数 */
|
||||
[[nodiscard]] int total_fixed() const {
|
||||
return stitch.items_fixed + simplify.items_fixed +
|
||||
regularize.items_fixed + orient.items_fixed +
|
||||
check.items_fixed;
|
||||
}
|
||||
|
||||
/** @brief 成功步数 */
|
||||
[[nodiscard]] int successful_steps() const {
|
||||
return (stitch.success ? 1 : 0) +
|
||||
(simplify.success ? 1 : 0) +
|
||||
(regularize.success ? 1 : 0) +
|
||||
(orient.success ? 1 : 0) +
|
||||
(check.success ? 1 : 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief ACIS风格五步自动修复流水线
|
||||
*
|
||||
* 按顺序执行以下五个独立步骤,前一步失败不影响后一步:
|
||||
*
|
||||
* | 步骤 | 名称 | 操作 |
|
||||
* |------|-------------|----------------------------------------------------------|
|
||||
* | 1 | Stitch | 检测距离<tol的边→缝合(合并重合边) |
|
||||
* | 2 | Simplify | 移除退化边(长度<tol)+合并共线边+合并共面小面 |
|
||||
* | 3 | Regularize | 移除孤立顶点/边、修复翻转面、确保每边恰好属于2面 |
|
||||
* | 4 | Orient | 统一面法线向外指向 |
|
||||
* | 5 | Check | 欧拉-庞加莱公式验证+边面关联验证 |
|
||||
*
|
||||
* @param body 待修复的 B-Rep 模型(原地修改)
|
||||
* @param tolerance 缝合/简化容差,默认 1e-6
|
||||
* @return AutoHealReport 包含每步详细结果、修复数量和总体评分
|
||||
*
|
||||
* @note 每一步捕获异常,失败后继续执行后续步骤。
|
||||
* 最终评分 (0-100): 基础分=每步成功20分,警告扣分。
|
||||
*/
|
||||
[[nodiscard]] AutoHealReport auto_heal_pipeline(
|
||||
BrepModel& body, double tolerance = 1e-6);
|
||||
|
||||
// ── 旧版兼容 ──
|
||||
|
||||
/**
|
||||
* @brief STEP 导入后自动修复
|
||||
*
|
||||
|
||||
@@ -270,4 +270,77 @@ struct DirectModelingResult {
|
||||
const core::Point3D& plane_point,
|
||||
const core::Vector3D& plane_normal);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Direct Modeling Enhancements — v5.5
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* @brief 填孔:用边环定义的边界填充孔洞,生成新面
|
||||
*
|
||||
* 给定一个由边 ID 组成的闭合边环,在 B-Rep 实体中创建一个
|
||||
* 新的平面面来填补孔洞。该操作对于修复导入的 STEP/IGES
|
||||
* 文件中的缺失面非常有用。
|
||||
*
|
||||
* 算法:
|
||||
* 1. 检测边环是否封闭(首尾相连)
|
||||
* 2. 采样边环上的点
|
||||
* 3. 最小二乘拟合平面
|
||||
* 4. 创建平面 NURBS 曲面
|
||||
* 5. 构建裁剪曲面并插入 B-Rep 面
|
||||
*
|
||||
* @param body 输入 B-Rep 实体(含有孔洞边界边环)
|
||||
* @param edge_loop 构成孔洞边界的边 ID 数组
|
||||
* @return DirectModelingResult
|
||||
* - result.success: 操作是否成功
|
||||
* - result.body: 含新面的模型
|
||||
* - result.affected_faces: 新增面的 face_id
|
||||
*
|
||||
* @pre edge_loop.size() >= 3
|
||||
* @pre 边环必须首尾相连形成闭合环
|
||||
*
|
||||
* @code{.cpp}
|
||||
* // 用 4 条边构成的孔洞边界填充孔
|
||||
* auto box_with_hole = ...; // 导入的有孔模型
|
||||
* std::vector<int> hole_edges = {10, 11, 12, 13};
|
||||
* auto result = fill_hole(box_with_hole, hole_edges);
|
||||
* if (result.success) {
|
||||
* int new_face = result.affected_faces;
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
[[nodiscard]] DirectModelingResult fill_hole(const BrepModel& body,
|
||||
const std::vector<int>& edge_loop);
|
||||
|
||||
/**
|
||||
* @brief 拉伸面对齐到目标面
|
||||
*
|
||||
* 将指定面沿其法向拉伸,直到与目标面贴合。
|
||||
* 内部调用 push_pull 实现推拉,并自动计算所需距离。
|
||||
*
|
||||
* 算法:
|
||||
* 1. 计算当前面到目标面的距离(沿当前面法向)
|
||||
* 2. 调用 push_pull 拉伸到目标面距离
|
||||
* 3. 检测拉伸后与目标面是否贴合(距离 < 容差)
|
||||
*
|
||||
* @param body 输入 B-Rep 实体
|
||||
* @param face_id 被拉伸的面索引
|
||||
* @param target_face_id 目标面对齐到的面索引
|
||||
* @return DirectModelingResult
|
||||
* - result.success: 是否成功贴合
|
||||
* - result.body: 拉伸后的模型
|
||||
*
|
||||
* @pre face_id 和 target_face_id 均在有效范围内
|
||||
* @pre face_id != target_face_id
|
||||
* @pre 面应为平面(非平面会产生警告)
|
||||
*
|
||||
* @code{.cpp}
|
||||
* auto box = make_box(10, 5, 3);
|
||||
* // 将面 0 拉伸到与面 1 对齐
|
||||
* auto result = pull_up_to_face(box, 0, 1);
|
||||
* @endcode
|
||||
*/
|
||||
[[nodiscard]] DirectModelingResult pull_up_to_face(const BrepModel& body,
|
||||
int face_id,
|
||||
int target_face_id);
|
||||
|
||||
} // namespace vde::brep
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*
|
||||
* 1. **SSI 计算** — 对所有面对并行计算曲面交线
|
||||
* 2. **面分割** — 用交线分割面,生成 TrimmedSurface 片段
|
||||
* 3. **分类** — 射线投射分类每个面片段为 IN/OUT/ON
|
||||
* 3. **分类** — 射线投射 + 精确谓词分类每个面片段
|
||||
* 4. **缝合** — 缝合保留的面片段,调用 heal_orientation
|
||||
*
|
||||
* ## 精确谓词
|
||||
@@ -19,6 +19,21 @@
|
||||
* 分类阶段集成 GMP 精确谓词 (exact_orient3d, exact_point_in_polyhedron),
|
||||
* 使用自适应精度:先 double,不确定时自动升级到 GMP。
|
||||
*
|
||||
* ## 退化检测(七类)
|
||||
*
|
||||
* 布尔运算前进行七类退化检测,避免因退化几何导致运算失败:
|
||||
* - detect_surface_overlap: 共面重叠检测
|
||||
* - detect_curve_overlap: 共线重叠检测
|
||||
* - detect_high_order_contact: G2+ 高阶接触检测
|
||||
* - detect_boundary_contact: 交线-面边界接触检测
|
||||
* - detect_vertex_contact: 多面汇聚接触检测
|
||||
* - detect_non_manifold_contact: 非流形边检测
|
||||
*
|
||||
* ## 精度追踪
|
||||
*
|
||||
* PrecisionTracker 累积追踪每次布尔操作的精度损失,
|
||||
* 超阈值时自动触发容差升级。
|
||||
*
|
||||
* @ingroup brep
|
||||
*/
|
||||
#include "vde/brep/brep.h"
|
||||
@@ -28,6 +43,82 @@
|
||||
|
||||
namespace vde::brep {
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 分类结果枚举
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/** @brief 点/面片段相对于实体的分类结果 */
|
||||
enum ClassResult { IN = -1, ON = 0, OUT = 1 };
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 精度追踪器
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* @brief 精度追踪器
|
||||
*
|
||||
* 累积追踪每次布尔操作的精度损失。每次 SSI、分割、分类操作
|
||||
* 后调用 record(),超阈值时触发容差升级策略。
|
||||
*
|
||||
* 使用场景:
|
||||
* - 布尔操作开始前创建 tracker,传入后通过 accumulate 累积
|
||||
* - accumulated_loss() 返回累积精度损失
|
||||
* - exceeds_threshold(t) 判断是否超过给定阈值
|
||||
*
|
||||
* @code{.cpp}
|
||||
* PrecisionTracker pt;
|
||||
* pt.record("ssi_compute", 1e-8);
|
||||
* pt.record("face_split", 2e-7);
|
||||
* if (pt.exceeds_threshold(1e-6)) {
|
||||
* // 触发容差升级
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
class PrecisionTracker {
|
||||
public:
|
||||
/// 默认构造,累积损失初始为 0
|
||||
PrecisionTracker() = default;
|
||||
|
||||
/**
|
||||
* @brief 记录一次操作的精度损失
|
||||
* @param operation 操作名称(用于诊断)
|
||||
* @param precision_loss 本次操作的精度损失(绝对值)
|
||||
*/
|
||||
void record(const std::string& operation, double precision_loss);
|
||||
|
||||
/**
|
||||
* @brief 返回累积精度损失
|
||||
*/
|
||||
[[nodiscard]] double accumulated_loss() const { return accumulated_; }
|
||||
|
||||
/**
|
||||
* @brief 判断累积损失是否超过给定阈值
|
||||
* @param threshold 精度阈值
|
||||
* @return true 如果累计损失超过阈值
|
||||
*/
|
||||
[[nodiscard]] bool exceeds_threshold(double threshold) const;
|
||||
|
||||
/**
|
||||
* @brief 重置累积损失(新一轮布尔操作前调用)
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* @brief 获取操作记录的条目数
|
||||
*/
|
||||
[[nodiscard]] size_t record_count() const { return count_; }
|
||||
|
||||
/**
|
||||
* @brief 获取最近一次记录的操作名称
|
||||
*/
|
||||
[[nodiscard]] const std::string& last_operation() const { return last_op_; }
|
||||
|
||||
private:
|
||||
double accumulated_ = 0.0;
|
||||
size_t count_ = 0;
|
||||
std::string last_op_;
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// SSI 布尔运算结果
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@@ -54,12 +145,193 @@ struct SSIBooleanResult {
|
||||
double sew_time_ms = 0.0; ///< 缝合耗时 (ms)
|
||||
std::string error_message; ///< 错误信息(空表示成功)
|
||||
|
||||
/// 精度追踪器(累积布尔操作期间的精度损失)
|
||||
PrecisionTracker precision_tracker;
|
||||
|
||||
/// 总耗时 (ms)
|
||||
[[nodiscard]] double total_time_ms() const {
|
||||
return ssi_time_ms + split_time_ms + classify_time_ms + sew_time_ms;
|
||||
}
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 退化检测数据结构
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/// 曲面重叠检测结果
|
||||
struct SurfaceOverlapResult {
|
||||
bool overlapping = false; ///< 是否存在共面重叠
|
||||
BrepModel overlap_region; ///< 重叠区域的面片段(用于2D布尔替代)
|
||||
};
|
||||
|
||||
/// 曲线重叠检测结果
|
||||
struct CurveOverlapResult {
|
||||
bool overlapping = false; ///< 是否存在共线段
|
||||
double t_start_a = 0.0; ///< 曲线A上重叠区间的起始参数
|
||||
double t_end_a = 0.0; ///< 曲线A上重叠区间的终止参数
|
||||
double t_start_b = 0.0; ///< 曲线B上重叠区间的起始参数
|
||||
double t_end_b = 0.0; ///< 曲线B上重叠区间的终止参数
|
||||
};
|
||||
|
||||
/// 高阶接触检测结果(G2+ 曲率连续接触)
|
||||
struct HighOrderContactResult {
|
||||
bool has_contact = false; ///< 是否存在高阶接触
|
||||
Point3D contact_point; ///< 接触点
|
||||
Vector3D offset_direction; ///< 需要微偏移的方向(沿法向)
|
||||
double offset_magnitude = 0.0; ///< 建议的偏移量
|
||||
};
|
||||
|
||||
/// 边界接触检测结果
|
||||
struct BoundaryContactResult {
|
||||
bool has_boundary_contact = false; ///< 是否存在边界接触
|
||||
Point3D extended_start; ///< 延伸后的起点
|
||||
Point3D extended_end; ///< 延伸后的终点
|
||||
};
|
||||
|
||||
/// 顶点接触检测结果(多面汇聚于一点)
|
||||
struct VertexContactResult {
|
||||
bool has_vertex_contact = false; ///< 是否存在顶点接触
|
||||
Point3D contact_point; ///< 汇聚点
|
||||
std::vector<int> involved_faces_a; ///< 模型A中涉及的面ID
|
||||
std::vector<int> involved_faces_b; ///< 模型B中涉及的面ID
|
||||
double sphere_radius = 0.0; ///< 球面参数化半径(用于局部球面展开)
|
||||
};
|
||||
|
||||
/// 非流形接触检测结果
|
||||
struct NonManifoldContactResult {
|
||||
bool is_non_manifold = false; ///< 是否为非流形边
|
||||
int edge_id = -1; ///< 边ID
|
||||
std::vector<int> incident_faces; ///< 与此边相邻的面ID列表
|
||||
};
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 退化检测函数
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* @brief 检测两面是否存在共面重叠
|
||||
*
|
||||
* 比较两面法向量和面方程常数项。如果共面且包围盒有重叠,
|
||||
* 返回 true 表示应优先使用 2D 布尔替代 3D SSI。
|
||||
*
|
||||
* @param a 模型A
|
||||
* @param b 模型B
|
||||
* @param face_a 模型A中的面ID
|
||||
* @param face_b 模型B中的面ID
|
||||
* @param tol 容差(角度容差弧度,距离容差)
|
||||
* @return 重叠检测结果
|
||||
*/
|
||||
[[nodiscard]] SurfaceOverlapResult detect_surface_overlap(
|
||||
const BrepModel& a, const BrepModel& b,
|
||||
int face_a, int face_b, double tol = 1e-6);
|
||||
|
||||
/**
|
||||
* @brief 检测两条交线是否共线重叠
|
||||
*
|
||||
* 比较两条曲线的方向和位置。如果共线,返回重叠参数区间,
|
||||
* 用于去重或合并。
|
||||
*
|
||||
* @param curve_a 曲线A上的采样点序列
|
||||
* @param curve_b 曲线B上的采样点序列
|
||||
* @param tol 共线容差
|
||||
* @return 重叠检测结果(含重叠区间参数)
|
||||
*/
|
||||
[[nodiscard]] CurveOverlapResult detect_curve_overlap(
|
||||
const std::vector<Point3D>& curve_a,
|
||||
const std::vector<Point3D>& curve_b,
|
||||
double tol = 1e-6);
|
||||
|
||||
/**
|
||||
* @brief 检测两面是否存在 G2+ 高阶接触
|
||||
*
|
||||
* 高阶接触(曲率连续)会导致 SSI 数值不稳定。
|
||||
* 检测到后返回微偏移方向和偏移量。
|
||||
*
|
||||
* @param a 模型A
|
||||
* @param b 模型B
|
||||
* @param face_a 模型A中的面ID
|
||||
* @param face_b 模型B中的面ID
|
||||
* @param tol 接触检测容差
|
||||
* @return 高阶接触检测结果
|
||||
*/
|
||||
[[nodiscard]] HighOrderContactResult detect_high_order_contact(
|
||||
const BrepModel& a, const BrepModel& b,
|
||||
int face_a, int face_b, double tol = 1e-6);
|
||||
|
||||
/**
|
||||
* @brief 检测交线端点是否触碰面边界
|
||||
*
|
||||
* 当交线端点落在面边界上时,布尔分类容易出错。
|
||||
* 检测到后返回延伸后的端点,使交线稍微超出面边界。
|
||||
*
|
||||
* @param intersection_points 交线上的采样点序列
|
||||
* @param a 模型A
|
||||
* @param b 模型B
|
||||
* @param face_a 模型A中的面ID
|
||||
* @param face_b 模型B中的面ID
|
||||
* @param tol 容差
|
||||
* @return 边界接触检测结果(含延伸后的端点)
|
||||
*/
|
||||
[[nodiscard]] BoundaryContactResult detect_boundary_contact(
|
||||
const std::vector<Point3D>& intersection_points,
|
||||
const BrepModel& a, const BrepModel& b,
|
||||
int face_a, int face_b, double tol = 1e-6);
|
||||
|
||||
/**
|
||||
* @brief 检测多面是否汇聚于一点
|
||||
*
|
||||
* 当 3+ 个面共享一个接触点时,需要球面参数化
|
||||
* 来处理局部拓扑。
|
||||
*
|
||||
* @param intersection_points 交线上的采样点
|
||||
* @param a 模型A
|
||||
* @param b 模型B
|
||||
* @param face_a 模型A中的面ID
|
||||
* @param face_b 模型B中的面ID
|
||||
* @param tol 容差
|
||||
* @return 顶点接触检测结果
|
||||
*/
|
||||
[[nodiscard]] VertexContactResult detect_vertex_contact(
|
||||
const std::vector<Point3D>& intersection_points,
|
||||
const BrepModel& a, const BrepModel& b,
|
||||
int face_a, int face_b, double tol = 1e-6);
|
||||
|
||||
/**
|
||||
* @brief 检测边是否为非流形边(属于 3+ 个面)
|
||||
*
|
||||
* 非流形边会导致拓扑不一致,必须先检测后
|
||||
* 使用特殊处理路径。
|
||||
*
|
||||
* @param body B-Rep 模型
|
||||
* @param edge_id 边ID
|
||||
* @return 非流形检测结果
|
||||
*/
|
||||
[[nodiscard]] NonManifoldContactResult detect_non_manifold_contact(
|
||||
const BrepModel& body, int edge_id);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 多点采样面片段分类
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* @brief 多点采样分类面片段(鲁棒性增强版本)
|
||||
*
|
||||
* 替换简单的 centroid 分类,采用多点投票策略:
|
||||
* 1. 基于面 bounding box 均匀撒 N = sqrt(面积/容差²) 个采样点(最少4个)
|
||||
* 2. 每个点独立调用 classify_point_adaptive() 分类
|
||||
* 3. 投票:若 >75% 一致则返回结果;否则撒 2N 个点重新投票
|
||||
* 4. 边界点(距离 < 容差)使用 exact_orient3d 判定
|
||||
*
|
||||
* @param body 面片段所在模型
|
||||
* @param other_body 分类参考模型(另一实体)
|
||||
* @param face_id 面片段ID
|
||||
* @param num_exact_upgrades [in/out] 升级到 GMP 精确谓词的计数
|
||||
* @return IN / ON / OUT
|
||||
*/
|
||||
[[nodiscard]] ClassResult classify_face_fragment(
|
||||
const BrepModel& body, const BrepModel& other_body,
|
||||
int face_id, int& num_exact_upgrades);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// SSI 布尔运算 API
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@@ -102,7 +374,7 @@ struct SSIBooleanResult {
|
||||
const BrepModel& a, const BrepModel& b);
|
||||
|
||||
/**
|
||||
* @brief SSI 布尔差集: A \ B
|
||||
* @brief SSI 布尔差集: A \\ B
|
||||
*
|
||||
* 从 A 中减去 B 所占据的区域。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user