Files
ViewDesignEngine/include/vde/brep/ssi_boolean.h
T
茂之钳 1aa753ba50
CI / Build & Test (push) Failing after 29s
CI / Release Build (push) Failing after 36s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
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
2026-07-27 20:14:52 +08:00

389 lines
15 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 ssi_boolean.h
* @brief 基于曲面-曲面求交 (SSI) 的 B-Rep 布尔运算
*
* 这是 B-Rep 布尔运算的完整 SSI 管线实现,与 brep_boolean.h
* 中基于平面切割的旧实现并存。SSI 方法使用精确的曲面交线来
* 分割和分类面,从而获得更高质量的布尔结果。
*
* ## SSI 管线 (4 步)
*
* 1. **SSI 计算** — 对所有面对并行计算曲面交线
* 2. **面分割** — 用交线分割面,生成 TrimmedSurface 片段
* 3. **分类** — 射线投射 + 精确谓词分类每个面片段
* 4. **缝合** — 缝合保留的面片段,调用 heal_orientation
*
* ## 精确谓词
*
* 分类阶段集成 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"
#include <chrono>
#include <string>
#include <vector>
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 布尔运算结果
// ═══════════════════════════════════════════════════════════
/**
* @brief SSI 布尔运算的诊断结果
*
* 包含运算过程中的统计信息和诊断数据。
*/
struct SSIBooleanResult {
BrepModel result; ///< 运算结果模型
// ── 诊断信息 ──
int num_ssi_curves = 0; ///< 计算的曲面交线总数
int num_fragments = 0; ///< 生成的面片段总数
int num_classified_in = 0; ///< 分类为 IN 的片段数
int num_classified_out = 0; ///< 分类为 OUT 的片段数
int num_classified_on = 0; ///< 分类为 ON 的片段数
int num_kept = 0; ///< 最终保留的片段数
int num_exact_upgrades = 0; ///< 升级到 GMP 精确谓词的次数
double ssi_time_ms = 0.0; ///< SSI 计算耗时 (ms)
double split_time_ms = 0.0; ///< 面分割耗时 (ms)
double classify_time_ms = 0.0; ///< 分类耗时 (ms)
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
// ═══════════════════════════════════════════════════════════
/**
* @brief SSI 布尔并集: A B
*
* 合并两个 B-Rep 实体为一个。完整的 SSI 管线:
* 1. 并行计算所有面对之间的曲面交线
* 2. 用交线分割面生成面片段
* 3. 射线投射 + 精确谓词分类每个面片段
* 4. 缝合保留的面片段并统一方向
*
* @param a 第一个 B-Rep 实体
* @param b 第二个 B-Rep 实体
* @return SSIBooleanResult 含结果模型和诊断信息
*
* @code{.cpp}
* auto box = make_box(2, 2, 2);
* auto sphere = make_sphere(1.5);
* auto r = ssi_boolean_union(box, sphere);
* if (r.error_message.empty()) {
* // r.result 是有效的 B-Rep 模型
* }
* @endcode
*/
[[nodiscard]] SSIBooleanResult ssi_boolean_union(
const BrepModel& a, const BrepModel& b);
/**
* @brief SSI 布尔交集: A ∩ B
*
* 保留两个实体共有的区域。
*
* @param a 第一个 B-Rep 实体
* @param b 第二个 B-Rep 实体
* @return SSIBooleanResult 含结果模型和诊断信息
*/
[[nodiscard]] SSIBooleanResult ssi_boolean_intersection(
const BrepModel& a, const BrepModel& b);
/**
* @brief SSI 布尔差集: A \\ B
*
* 从 A 中减去 B 所占据的区域。
*
* @param a 被减实体 A
* @param b 减去实体 B
* @return SSIBooleanResult 含结果模型和诊断信息
*/
[[nodiscard]] SSIBooleanResult ssi_boolean_difference(
const BrepModel& a, const BrepModel& b);
} // namespace vde::brep