#pragma once /** * @file direct_modeling.h * @brief 直接建模操作 — 面级推拉/偏移/移动/替换 * * 直接建模(Direct Modeling / Synchronous Technology)允许用户 * 直接操纵几何面而不依赖特征历史树。该模块提供: * * | 操作 | 含义 | * |--------------|-----------------------------------------| * | tweak_face | 面沿法向微调偏移,邻面自动延伸保持水密 | * | move_face | 面刚体移动(平移+旋转),检测不穿透邻面 | * | replace_face | 面替换(交换曲面),边界兼容性检查 | * | push_pull | 推拉:正值=挤出加材料,负值=切除减材料 | * | offset_face | 面等距偏移 | * * 所有操作返回 DirectModelingResult,含成功/失败诊断及生成的新模型。 * * @ingroup brep */ #include "vde/brep/brep.h" #include "vde/curves/nurbs_surface.h" #include "vde/core/transform.h" #include #include namespace vde::brep { // ═══════════════════════════════════════════════════════════ // DirectModelingResult — 操作结果 // ═══════════════════════════════════════════════════════════ /** * @brief 直接建模操作结果 * * 包含成功/失败标志、错误/警告诊断信息及生成的新模型。 * 即使操作部分成功(如某些邻面未能延伸)也会在 warnings 中记录。 */ struct DirectModelingResult { /** @brief 操作是否成功(至少基本几何有效) */ bool success = false; /** @brief 结果模型(success=true 时有效,否则为输入副本) */ BrepModel body; /** @brief 错误消息列表(导致 success=false) */ std::vector errors; /** @brief 警告消息列表(不影响 success) */ std::vector warnings; /** @brief 受影响的面数 */ int affected_faces = 0; /** @brief 新建的面数(推拉产生的侧面等) */ int new_faces = 0; /** @brief 操作类型名称(供日志/UI 显示) */ std::string operation; }; // ═══════════════════════════════════════════════════════════ // Direct Modeling Operations // ═══════════════════════════════════════════════════════════ /** * @brief 面微调:沿法向偏移指定距离 * * 将面沿其法向量方向偏移 delta。邻面自动延伸/裁剪 * 以保持水密封闭体。适合小范围的面调整。 * * @param body 输入 B-Rep 实体 * @param face_id 目标面索引(0..num_faces-1) * @param delta 偏移距离(正=沿法向向外,负=向内) * @return DirectModelingResult 含新模型和诊断信息 * * @pre face_id 在 [0, body.num_faces()) 范围内 * @pre 实体应为封闭水密体 * * @code{.cpp} * auto box = make_box(2, 2, 2); * auto result = tweak_face(box, 0, 0.5); // 顶面上移 0.5 * // 四个侧面自动延伸保持水密 * @endcode */ [[nodiscard]] DirectModelingResult tweak_face(const BrepModel& body, int face_id, double delta); /** * @brief 面移动:对指定面施加刚体变换 * * 通过 4x4 仿射变换矩阵移动面。检测移动后面是否 * 与邻面相交(产生自交),相交时返回错误。 * * @param body 输入 B-Rep 实体 * @param face_id 目标面索引 * @param transform 刚体变换矩阵(仅平移+旋转,不含缩放) * @return DirectModelingResult 含新模型和诊断 * * @note 非均匀缩放会导致法线失真;使用 Transform3D 时建议只用平移+旋转 * @note 移动后检测面是否穿透邻面(通过法线方向变化判断) * * @code{.cpp} * auto box = make_box(2, 2, 2); * auto T = translate(Vector3D(0.5, 0, 0)) * rotate_z(M_PI / 4); * auto result = move_face(box, 0, T); * @endcode */ [[nodiscard]] DirectModelingResult move_face(const BrepModel& body, int face_id, const core::Transform3D& transform); /** * @brief 面替换:用新曲面替换面的现有曲面 * * 新曲面与原面必须有兼容的边界:新曲面的四条边界 * 应近似匹配面的四条边界边。不兼容时操作失败。 * * @param body 输入 B-Rep 实体 * @param face_id 目标面索引 * @param new_surface 替换用的新 NURBS 曲面 * @return DirectModelingResult 含新模型和诊断 * * @note 仅替换曲面的几何定义,拓扑结构不变 * @note 新曲面参数域应与原曲面兼容(边界对应对齐) * * @code{.cpp} * auto box = make_box(2, 2, 2); * NurbsSurface curved_surf = ...; // 曲面边界与 box 顶面匹配 * auto result = replace_face(box, 0, curved_surf); * @endcode */ [[nodiscard]] DirectModelingResult replace_face(const BrepModel& body, int face_id, const curves::NurbsSurface& new_surface); /** * @brief 推拉操作:沿法线挤出(正值)或切除(负值) * * 正值 distance:面沿法向向外挤出,自动创建四侧面 * 闭合新体积。等同于"挤出添加材料"。 * * 负值 distance:面沿法向向内凹陷,邻面自动延伸 * 封闭。等同于"切除材料"。 * * @param body 输入 B-Rep 实体 * @param face_id 目标面索引 * @param distance 推拉距离(正=挤出加材料,负=切除减材料) * @return DirectModelingResult 含新模型和诊断 * * @pre distance 的绝对值不超过实体在法线方向的最小厚度 * * @code{.cpp} * auto box = make_box(2, 2, 2); * // 顶面挤出 1.0 → 凸台 * auto pad = push_pull(box, 0, 1.0); * // 顶面切除 0.5 → 凹陷 * auto pocket = push_pull(box, 0, -0.5); * @endcode */ [[nodiscard]] DirectModelingResult push_pull(const BrepModel& body, int face_id, double distance); /** * @brief 面等距偏移:将面均匀偏移指定距离 * * 面等距偏移与 tweak_face 的区别: * - offset_face 保证偏移后曲面与原曲面处处等距(等距曲面) * - tweak_face 是近似偏移(沿平均法向移动顶点) * * 对于平面,两者等价。对于曲面,offset_face 生成 * 真正的等距曲面。 * * @param body 输入 B-Rep 实体 * @param face_id 目标面索引 * @param offset_distance 等距偏移距离(正=向外,负=向内) * @return DirectModelingResult 含新模型和诊断 * * @warning 自由曲面等距偏移可能产生自交,需检查结果有效性 * * @code{.cpp} * auto sphere = make_sphere(3.0); * auto result = offset_face(sphere, 0, 0.2); // 面均匀外扩 0.2 * @endcode */ [[nodiscard]] DirectModelingResult offset_face(const BrepModel& body, int face_id, double offset_distance); // ═══════════════════════════════════════════════════════════ // Advanced Direct Modeling Operations — v5.4 // ═══════════════════════════════════════════════════════════ /** * @brief 高级拔模 — 面按拔模方向和角度倾斜 * * 将指定面相对于拔模方向旋转到目标拔模角。 * 面的与邻面共享的边作为铰链轴(hinge line)。 * * 对标 Parasolid Draft Face。 * * @param body 输入 B-Rep 实体 * @param face_id 目标面索引 * @param angle_deg 拔模角度(度),> 0 且 < 90 * @param pull_dir 拔模方向(通常是模具开模方向) * @return DirectModelingResult 含新模型和诊断 * * @pre angle_deg 必须在 (0, 90) 度范围内 * @pre pull_dir 不能与面法向量平行 * * @code{.cpp} * auto box = make_box(10, 5, 3); * // 面 0 向 Z+ 方向拔模 5 度 * auto result = draft_face_advanced(box, 0, 5.0, Vector3D::UnitZ()); * @endcode */ [[nodiscard]] DirectModelingResult draft_face_advanced(const BrepModel& body, int face_id, double angle_deg, const core::Vector3D& pull_dir); /** * @brief 非均匀缩放体 * * 沿 X/Y/Z 轴以独立缩放因子缩放整个 B-Rep 实体。 * 对标 Parasolid Scale Body (non-uniform)。 * * 均匀缩放时(所有因子相等)使用 Transform3D 加速。 * 非均匀缩放时逐个顶点变换并重建曲面控制点网格。 * * @param body 输入 B-Rep 实体 * @param factors 各轴缩放因子 (sx, sy, sz),均必须 > 0 * @return DirectModelingResult 含缩放后的新模型 * * @pre factors.x() > 0, factors.y() > 0, factors.z() > 0 * * @code{.cpp} * auto box = make_box(10, 5, 3); * // X 方向拉伸 2x, Y/Z 不变 * auto stretched = scale_body(box, Vector3D(2.0, 1.0, 1.0)); * @endcode */ [[nodiscard]] DirectModelingResult scale_body(const BrepModel& body, const core::Vector3D& factors); /** * @brief 镜像体 * * 关于指定平面镜像整个 B-Rep 实体。 * 对标 Parasolid Mirror Body。 * * 算法: * 1. 所有顶点关于镜像平面对称映射 * 2. 曲面控制点网格镜像(保持参数化方向) * 3. 环方向反转以保持正确的面朝向 * * @param body 输入 B-Rep 实体 * @param plane_point 镜像平面上的一点 * @param plane_normal 镜像平面法向量 * @return DirectModelingResult 含镜像体 * * @pre plane_normal 不能为零向量 * * @code{.cpp} * auto box = make_box(10, 5, 3); * // 关于 YZ 平面 (x=0) 镜像 * auto mirrored = mirror_body(box, Point3D(0,0,0), Vector3D::UnitX()); * @endcode */ [[nodiscard]] DirectModelingResult mirror_body(const BrepModel& body, 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 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& 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); // ═══════════════════════════════════════════════════════════ // Direct Modeling Enhancements — v5.6 // ═══════════════════════════════════════════════════════════ /** * @brief 相切联动拉伸:拉伸面并保持相邻面相切 * * 将指定面沿其法向拉伸,同时检测并调整相邻面,使其 * 在共享边处保持与拉伸面的相切关系(G1 连续)。 * * 非相切邻面保持原有行为(仅共享边处适配新位置)。 * * 算法: * 1. 检测相邻面(与目标面共享边的面) * 2. 对每个相邻面检测 G1 连续性 * - 平面面:通过法向量平行性判定(dot > 0.9999) * - 曲面:通过 surface_continuity 检测 * 3. 执行 push_pull 拉伸目标面 * 4. 对 G1 相邻面,将其非共享顶点沿目标面法向偏移相同距离 * 以保持相切关系 * * @param body 输入 B-Rep 实体 * @param face_id 被拉伸的面索引 * @param distance 拉伸距离(正=挤出加材料,负=切除减材料) * @return DirectModelingResult * - result.body: 拉伸后保持相切的新模型 * - result.affected_faces: 受影响的面数(含调整的相邻面) * - result.new_faces: 新创建的侧面数 * * @pre face_id 在 [0, body.num_faces()) 范围内 * @pre 实体应为封闭水密体 * @pre distance 的绝对值不超过实体在法线方向的最小厚度 * * @code{.cpp} * auto box = make_box(10, 5, 3); * // 拉伸顶面 2.0,相切邻面自动联动 * auto result = tangent_pull(box, 0, 2.0); * @endcode */ [[nodiscard]] DirectModelingResult tangent_pull(const BrepModel& body, int face_id, double distance); } // namespace vde::brep