feat(v5-M3): large assembly LOD + constraint solver + CAM strategies + direct modeling
CI / Build & Test (push) Failing after 33s
CI / Release Build (push) Failing after 39s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled

M3.1 — 大装配 LOD + 约束求解器 (Agent #0):
- assembly_lod.h/.cpp: 3-level LOD (Full/Simplified/BBox), view-distance auto-switch
- constraint_solver.h/.cpp: ConstraintGraph, DOF analysis, Newton-Raphson solver
- 7 constraint types, over-constraint detection, incremental solve
- 59 tests (22 LOD + 37 constraint)

M3.2 — 完整 CAM 策略 (Agent #1):
- cam_strategies.h/.cpp: roughing(Z-layer), finishing(parallel/spiral), drilling(G81/G83/G84)
- Tool/ToolLibrary, PostProcessor (Fanuc/Siemens/Heidenhain)
- material_removal_simulation with volume stats
- 27 tests, 26/27 passing

M3.3 — 直接建模 (Agent #2):
- direct_modeling.h/.cpp: tweak_face, move_face, replace_face, push_pull, offset_face
- Auto neighbor-face extension for watertightness
- DirectModelingResult with diagnostics
- 23 tests with validate() verification

12 files, ~5400 lines, 109 tests
This commit is contained in:
茂之钳
2026-07-26 21:19:38 +08:00
parent 73df04d5cb
commit 6bc9db663e
15 changed files with 4806 additions and 130 deletions
+267
View File
@@ -0,0 +1,267 @@
#pragma once
/**
* @file assembly_lod.h
* @brief 大装配 LOD(细节层次)系统
*
* 为装配体节点提供三级 LOD 支持,基于视距自动切换。
* 集成 InstanceCache 实现几何共享,减少大装配的内存占用。
*
* LOD 级别:
* - Full: 完整 B-Rep(精确几何)
* - Simplified: QEM 简化网格(快速渲染)
* - BoundingBox: AABB 包围盒(远距离/不可见)
*
* @ingroup brep
*/
#include "vde/brep/assembly.h"
#include "vde/brep/large_assembly.h"
#include "vde/core/aabb.h"
#include <functional>
#include <optional>
namespace vde::brep {
/// 将局部空间 AABB 变换到世界空间
[[nodiscard]] core::AABB3D compute_transformed_bounds(
const core::AABB3D& local_bb, const Transform3D& tf);
// ═══════════════════════════════════════════════════════════
// LODLevel
// ═══════════════════════════════════════════════════════════
/// 细节层次枚举
enum class LODLevel {
Full, ///< 完整 B-Rep 模型(精确几何)
Simplified, ///< QEM 简化后的三角网格
BoundingBox ///< 仅 AABB 包围盒(远距离/剔除测试)
};
/// LOD 级别转可读字符串
[[nodiscard]] inline const char* to_string(LODLevel level) {
switch (level) {
case LODLevel::Full: return "Full";
case LODLevel::Simplified: return "Simplified";
case LODLevel::BoundingBox: return "BoundingBox";
}
return "Unknown";
}
// ═══════════════════════════════════════════════════════════
// LODNode — per-node LOD state
// ═══════════════════════════════════════════════════════════
/// 每个装配节点的 LOD 状态
struct LODNode {
AssemblyNode* node = nullptr; ///< 对应的装配节点
core::AABB3D world_bounds; ///< 世界空间包围盒(缓存)
LODLevel current_level = LODLevel::Full; ///< 当前 LOD 级别
bool manual_override = false; ///< 是否手动设置了级别
};
// ═══════════════════════════════════════════════════════════
// LODConfig
// ═══════════════════════════════════════════════════════════
/// LOD 系统配置参数
struct LODConfig {
double near_plane = 50.0; ///< 近距离阈值:距离 < near_plane → Full
double far_plane = 500.0; ///< 远距离阈值:距离 > far_plane → BoundingBox
double screen_size_threshold = 0.02; ///< 屏幕空间尺寸比阈值
int simplify_num_levels = 3; ///< QEM 简化级别数
bool enable_auto_lod = true; ///< 是否启用自动 LOD 切换
};
// ═══════════════════════════════════════════════════════════
// AssemblyLOD
// ═══════════════════════════════════════════════════════════
/**
* @brief 大装配 LOD 管理器
*
* 管理装配体所有节点的 LOD 级别。支持:
* - 自动视距驱动 LOD 选择
* - 手动设置节点 LOD 级别
* - 按 LOD 级别遍历装配体
* - 集成 InstanceCache 共享几何
*
* @code{.cpp}
* Assembly assy("robot");
* // ... 添加零件 ...
* AssemblyLOD lod(assy);
* lod.set_config(LODConfig{ .near_plane = 100, .far_plane = 1000 });
*
* // 自动选择 LOD
* lod.compute_lod(camera_distance, screen_size);
*
* // 按 LOD 遍历
* lod.traverse_assembly([&](const LODNode& n) {
* switch (n.current_level) {
* case LODLevel::Full: render_brep(n); break;
* case LODLevel::Simplified: render_simplified(n); break;
* case LODLevel::BoundingBox: render_bbox(n); break;
* }
* });
* @endcode
*
* @ingroup brep
*/
class AssemblyLOD {
public:
/**
* @brief 从装配体构建 LOD 管理器
*
* 收集所有零件节点,预计算世界空间 AABB,
* 通过 InstanceCache 注册可共享的几何。
*
* @param assembly 装配体引用
*/
explicit AssemblyLOD(Assembly& assembly);
/// 获取配置
[[nodiscard]] const LODConfig& config() const { return config_; }
/// 设置配置
void set_config(const LODConfig& cfg);
/**
* @brief 获取所有 LOD 节点(扁平化列表)
* @return LODNode 列表引用
*/
[[nodiscard]] const std::vector<LODNode>& nodes() const { return nodes_; }
/**
* @brief 根据视距和屏幕尺寸自动选择 LOD 级别
*
* 选择规则:
* - distance > far_plane → BoundingBox
* - distance < near_plane → Full
* - near_plane ≤ distance ≤ far_plane → Simplified
*
* @param camera_distance 相机到装配体中心的距离
* @param screen_size 屏幕空间尺寸比(节点投影 / 屏幕尺寸)
*/
void compute_lod(double camera_distance, double screen_size);
/**
* @brief 自动计算单个节点的 LOD 级别
*
* @param node LOD 节点(含预计算的 world_bounds
* @param camera_distance 相机到装配体中心的距离
* @param screen_size 屏幕空间尺寸比
* @return 推荐的 LOD 级别
*/
[[nodiscard]] LODLevel compute_lod_for_node(
const LODNode& node,
double camera_distance,
double screen_size) const;
/**
* @brief 手动设置某节点的 LOD 级别
*
* 手动设置后,该节点将忽略自动 LOD 选择直到调用 clear_manual_override()。
*
* @param node LOD 节点(通过 nodes() 获取)
* @param level 要设置的 LOD 级别
*/
void set_lod_level(LODNode& node, LODLevel level);
/**
* @brief 清除手动 LOD 设置
*
* @param node 要清除手动覆盖的节点
*/
void clear_manual_override(LODNode& node);
/// 清除所有节点的手动 LOD 设置
void clear_all_manual_overrides();
/**
* @brief 按 LOD 级别遍历装配体
*
* 对每个零件节点调用 visitor,传入其 LODNode 状态。
*
* @param visitor 回调函数:void(const LODNode&)
*/
template <typename Visitor>
void traverse_assembly(Visitor&& visitor) const {
for (const auto& node : nodes_) {
visitor(node);
}
}
/**
* @brief 按 LOD 遍历(带装配层级上下文)
*
* 遍历装配体树,对每个零件节点调用 visitor。
* 传入 LODNode、父变换矩阵、深度。
*
* @param visitor 回调:bool(LODNode&, const Transform3D& parent_tf, int depth)
* 返回 false 可提前终止遍历
*/
template <typename Visitor>
bool traverse_with_context(Visitor&& visitor) const {
return traverse_impl(&assembly_.root, Transform3D::Identity(), 0,
std::forward<Visitor>(visitor));
}
/// 获取 InstanceCache
[[nodiscard]] InstanceCache& cache() { return cache_; }
[[nodiscard]] const InstanceCache& cache() const { return cache_; }
/// 零件总数
[[nodiscard]] size_t part_count() const { return nodes_.size(); }
/// 各级别统计
struct LODStats {
size_t full_count = 0;
size_t simplified_count = 0;
size_t bbox_count = 0;
size_t manual_count = 0;
};
/// 获取当前 LOD 统计信息
[[nodiscard]] LODStats statistics() const;
/// 获取装配体总包围盒
[[nodiscard]] core::AABB3D total_bounds() const { return total_bounds_; }
/// 更新所有节点的世界空间包围盒(变换改变后调用)
void update_bounds();
private:
Assembly& assembly_;
LODConfig config_;
std::vector<LODNode> nodes_;
InstanceCache cache_;
core::AABB3D total_bounds_;
/// 收集所有零件节点
void collect_nodes(AssemblyNode* node, const Transform3D& parent_tf);
/// 带上下文的遍历实现
template <typename Visitor>
bool traverse_impl(AssemblyNode* node, const Transform3D& parent_tf,
int depth, Visitor&& visitor) const {
Transform3D world = parent_tf * node->local_transform;
if (node->is_part()) {
// 查找对应的 LODNode
for (auto& ln : nodes_) {
if (ln.node == node) {
if (!visitor(ln, world, depth))
return false;
break;
}
}
}
for (const auto& child : node->children) {
if (!traverse_impl(child.get(), world, depth + 1,
std::forward<Visitor>(visitor)))
return false;
}
return true;
}
};
} // namespace vde::brep
+360 -42
View File
@@ -1,75 +1,393 @@
#pragma once
/**
* @file constraint_solver.h
* @brief 3D constraint solver for assembly
* @brief 3D 装配约束求解器 — 约束图 + Newton-Raphson + DOF 分析
*
* Iterative relaxation solver for spatial constraints between assembly nodes.
* Supports coincident, concentric, distance, angle, parallel, perpendicular,
* and tangent constraints over 3D bounding box proxies.
* 为 3D 装配提供完整的约束求解系统:
* - ConstraintGraph: 约束图(节点=零件, 边=约束)
* - Newton-Raphson 迭代求解
* - DOF 分析(每个零件 6 DOF
* - 过度约束检测(冗余约束识别 + 报告)
* - 联动求解(增量更新)
*
* @ingroup brep
*/
#include "vde/brep/assembly.h"
#include "vde/core/point.h"
#include "vde/core/transform.h"
#include "vde/core/aabb.h"
#include <vector>
#include <string>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <optional>
namespace vde::brep {
/// Constraint type for 3D assembly solving
enum class Constraint3D {
Coincident, ///< Face-face alignment (coplanar, opposite normals)
Concentric, ///< Axis-axis alignment
Distance, ///< Fixed distance between faces
Angle, ///< Fixed angle between faces
Parallel, ///< Face normals parallel
Perpendicular, ///< Face normals perpendicular
Tangent ///< Face tangent contact
// ═══════════════════════════════════════════════════════════
// ConstraintType
// ═══════════════════════════════════════════════════════════
/// 3D 约束类型
enum class ConstraintType {
Coincident, ///< 面-面贴合(共面,法向相反)
Concentric, ///< 轴-轴对齐(同轴)
Tangent, ///< 面相切接触
Distance, ///< 固定面间距
Angle, ///< 固定面夹角(弧度)
Parallel, ///< 面法线平行
Perpendicular ///< 面法线垂直
};
/// A single constraint between two assembly nodes
struct ConstraintEntry {
int node_a; ///< Index into a flattened node list (or 0-based child index)
int node_b; ///< Index of the node that moves to satisfy constraint
Constraint3D type;
double value = 0.0; ///< Distance or angle value (radians for Angle)
/// 约束类型转字符串
[[nodiscard]] inline const char* constraint_type_name(ConstraintType t) {
switch (t) {
case ConstraintType::Coincident: return "Coincident";
case ConstraintType::Concentric: return "Concentric";
case ConstraintType::Tangent: return "Tangent";
case ConstraintType::Distance: return "Distance";
case ConstraintType::Angle: return "Angle";
case ConstraintType::Parallel: return "Parallel";
case ConstraintType::Perpendicular: return "Perpendicular";
}
return "Unknown";
}
/// 每个约束消除的自由度数(估算)
[[nodiscard]] inline int constraint_dof_elimination(ConstraintType t) {
switch (t) {
case ConstraintType::Coincident: return 3; // 1 平移 + 2 旋转
case ConstraintType::Concentric: return 4; // 2 平移 + 2 旋转
case ConstraintType::Tangent: return 1; // 1 平移
case ConstraintType::Distance: return 1; // 1 平移
case ConstraintType::Angle: return 1; // 1 旋转
case ConstraintType::Parallel: return 2; // 2 旋转
case ConstraintType::Perpendicular: return 2; // 2 旋转
}
return 0;
}
// ═══════════════════════════════════════════════════════════
// Constraint — 单个约束边
// ═══════════════════════════════════════════════════════════
/// 单个约束描述
struct Constraint {
int node_a; ///< 约束的零件 A 索引
int node_b; ///< 约束的零件 B 索引
ConstraintType type; ///< 约束类型
double value = 0.0; ///< 约束值(Distance=distance, Angle=radians)
double weight = 1.0; ///< 权重(用于过约束系统的加权最小二乘)
std::string name; ///< 约束名称(可选)
Constraint() = default;
Constraint(int a, int b, ConstraintType t, double v = 0.0, double w = 1.0)
: node_a(a), node_b(b), type(t), value(v), weight(w) {}
};
/// Solve a system of 3D constraints on an assembly using iterative relaxation.
///
/// Repeatedly adjusts transforms for each constraint in round-robin order
/// until all constraints are satisfied within tolerance or max_iterations
/// is exhausted.
///
/// @param assembly Target assembly (root children are the constrained nodes)
/// @param constraints Ordered list of constraints to satisfy
/// @param max_iterations Maximum number of relaxation passes (default 100)
/// @param tolerance Convergence tolerance (default 1e-6)
/// @return true if all constraints satisfied within tolerance
// ═══════════════════════════════════════════════════════════
// DOFInfo — 每个节点的自由度信息
// ═══════════════════════════════════════════════════════════
/// 单个零件的自由度分析结果
struct DOFInfo {
int total_dof = 6; ///< 起始 DOF(空间刚体:6
int eliminated_dof = 0; ///< 已消除的 DOF
int remaining_dof = 6; ///< 剩余 DOF
bool is_fixed = false; ///< 是否完全约束
std::vector<int> constraining; ///< 约束此零件的约束索引列表
};
// ═══════════════════════════════════════════════════════════
// ConstraintGraph
// ═══════════════════════════════════════════════════════════
/**
* @brief 3D 约束图
*
* 节点 = 装配零件(AssemblyNode),边 = 约束。
* 提供图遍历、DOF 分析、过度约束检测。
*
* @ingroup brep
*/
class ConstraintGraph {
public:
/// 添加零件节点
/// @param name 零件名称
/// @param node 零件节点指针
/// @return 节点索引
int add_node(const std::string& name, AssemblyNode* node);
/// 添加约束边
/// @param c 约束描述
/// @return 约束索引
int add_constraint(const Constraint& c);
/// 删除约束
/// @param index 约束索引
void remove_constraint(int index);
/// 获取节点数
[[nodiscard]] int node_count() const { return static_cast<int>(nodes_.size()); }
/// 获取约束数
[[nodiscard]] int constraint_count() const { return static_cast<int>(constraints_.size()); }
/// 获取节点名
[[nodiscard]] const std::string& node_name(int idx) const;
/// 获取节点指针
[[nodiscard]] AssemblyNode* node_ptr(int idx) const;
/// 获取约束
[[nodiscard]] const Constraint& constraint(int idx) const;
/// 获取可修改约束引用(用于增量更新)
[[nodiscard]] Constraint& constraint_mut(int idx);
/// 获取某节点的所有相邻约束
[[nodiscard]] std::vector<int> node_constraints(int node_idx) const;
/// 获取所有节点索引
[[nodiscard]] const std::vector<int>& node_indices() const { return node_ids_; }
// ── DOF 分析 ──
/**
* @brief 执行 DOF 分析
*
* 对每个零件计算自由度状态:
* - 初始 6 DOF3 平移 + 3 旋转)
* - 每个约束消除若干 DOF
* - 返回每个节点的 DOFInfo
*
* @return 节点索引 → DOF 信息
*/
[[nodiscard]] std::vector<DOFInfo> analyze_dof() const;
/**
* @brief 计算单个节点的剩余 DOF
* @param node_idx 节点索引
* @return 剩余自由度数
*/
[[nodiscard]] int remaining_dof(int node_idx) const;
/**
* @brief 检查图是否完全约束(所有节点 fixed)
* @return true 如果所有节点 DOF=0
*/
[[nodiscard]] bool is_fully_constrained() const;
// ── 过度约束检测 ──
/**
* @brief 过度约束检测结果
*/
struct OverConstraintInfo {
int node_index; ///< 哪个节点
int eliminated_dof; ///< 已消除 DOF
bool over_constrained; ///< 是否过度约束(eliminated > 6
std::vector<int> redundant; ///< 冗余约束索引列表
std::string message; ///< 人类可读的消息
};
/**
* @brief 检测过度约束
*
* 对每个节点检查:已消除 DOF > 6 即为过度约束。
* 识别并报告冗余约束。
*
* @return 过度约束节点列表
*/
[[nodiscard]] std::vector<OverConstraintInfo> detect_over_constraints() const;
// ── 图结构查询 ──
/// 获取所有约束
[[nodiscard]] const std::vector<Constraint>& constraints() const { return constraints_; }
/// 检查两个节点之间是否已存在约束
[[nodiscard]] bool has_constraint_between(int a, int b) const;
/// 获取两个节点之间的所有约束
[[nodiscard]] std::vector<int> constraints_between(int a, int b) const;
/// 清空图
void clear();
private:
struct NodeInfo {
std::string name;
AssemblyNode* ptr = nullptr;
};
std::vector<NodeInfo> nodes_;
std::vector<int> node_ids_; // 压缩的索引映射
std::vector<Constraint> constraints_;
std::vector<bool> constraint_active_; // 约束是否激活(未删除)
/// 邻接表:node_idx → [constraint_idx...]
std::vector<std::vector<int>> adjacency_;
};
// ═══════════════════════════════════════════════════════════
// Newton-Raphson 求解器
// ═══════════════════════════════════════════════════════════
/**
* @brief Newton-Raphson 求解器配置
*/
struct NRSolverConfig {
int max_iterations = 100; ///< 最大迭代次数
double tolerance = 1e-6; ///< 收敛容差
double step_size = 0.5; ///< 步长衰减因子(0~1,防震荡)
double damping = 0.5; ///< 阻尼因子
bool check_over_constrained = true; ///< 求解前检测过度约束
};
/**
* @brief 求解器状态
*/
struct SolveResult {
bool converged = false; ///< 是否收敛
int iterations = 0; ///< 实际迭代次数
double final_error = 0.0; ///< 最终残差 L2 范数
std::vector<double> error_history; ///< 每步误差历史
std::string message; ///< 人类可读的消息
};
/**
* @brief Newton-Raphson 约束求解器
*
* 将约束表示为方程系统 f(x)=0,使用 Newton-Raphson 迭代求解。
*
* 状态向量 x = [t1_x, t1_y, t1_z, r1_x, r1_y, r1_z, t2_..., ...]
* 每个零件有 6 个参数:3 平移 + 3 旋转(轴-角表示的一部分,用增量旋转)。
*
* 每一步:
* 1. 评估约束函数 f(x) → 残差向量
* 2. 数值计算 Jacobian J = ∂f/∂x
* 3. 求解 J·Δx = -f(x)
* 4. 更新 x ← x + α·Δx(α 为步长)
*
* @ingroup brep
*/
class NewtonRaphsonSolver {
public:
/**
* @brief 求解约束图
*
* @param graph 约束图
* @param assembly 装配体(将被修改以满足约束)
* @param config 求解器配置
* @return SolveResult 求解结果
*/
[[nodiscard]] SolveResult solve(
ConstraintGraph& graph,
Assembly& assembly,
const NRSolverConfig& config = NRSolverConfig{});
/**
* @brief 增量求解:修改单个约束值后更新
*
* 在已有求解结果基础上,仅修改一个约束值,
* 从当前状态出发重新求解(热启动)。
*
* @param graph 约束图
* @param assembly 装配体
* @param constraint_idx 被修改的约束索引
* @param new_value 新的约束值
* @param config 求解器配置
* @return SolveResult
*/
[[nodiscard]] SolveResult incremental_solve(
ConstraintGraph& graph,
Assembly& assembly,
int constraint_idx,
double new_value,
const NRSolverConfig& config = NRSolverConfig{});
/// 获取最后一次求解的状态向量
[[nodiscard]] const std::vector<double>& last_state() const { return state_; }
private:
int n_parts_ = 0; ///< 零件数
std::vector<double> state_; ///< 状态向量 [tx,ty,tz,rx,ry,rz]*n
std::vector<double> last_error_; ///< 最近误差向量
/// 从 assembly 读取变换到状态向量
void read_state(const ConstraintGraph& graph, const Assembly& assembly);
/// 将状态向量写回 assembly
void write_state(const ConstraintGraph& graph, Assembly& assembly) const;
/// 评估所有约束函数,填充残差向量 f
/// @return 残差的 L2 范数
double evaluate_constraints(
const ConstraintGraph& graph,
const Assembly& assembly,
std::vector<double>& residual) const;
/// 数值计算 Jacobian(中心差分)
void compute_jacobian(
const ConstraintGraph& graph,
Assembly& assembly,
const std::vector<double>& residual,
Eigen::MatrixXd& J);
/// 单个约束的评估函数
double evaluate_single_constraint(
const Constraint& c,
const AssemblyNode& na,
const AssemblyNode& nb) const;
/// 求解线性最小二乘问题 J·Δx = -f
Eigen::VectorXd solve_linear_step(
const Eigen::MatrixXd& J,
const Eigen::VectorXd& f) const;
};
// ═══════════════════════════════════════════════════════════
// 向后兼容 API(保留旧接口)
// ═══════════════════════════════════════════════════════════
/// @deprecated 使用 ConstraintType 代替
using Constraint3D = ConstraintType;
/// @deprecated 使用 Constraint 代替
struct ConstraintEntry : public Constraint {
using Constraint::Constraint;
};
/**
* @brief 简化版求解器(向后兼容)
*
* 使用迭代松弛法求解约束系统。
*
* @param assembly 目标装配体
* @param constraints 约束列表(node_a, node_b 为 root.children 中的直接索引)
* @param max_iterations 最大迭代次数
* @param tolerance 收敛容差
* @return true 如果所有约束在容差内满足
*/
[[nodiscard]] bool solve_constraints(
Assembly& assembly,
const std::vector<ConstraintEntry>& constraints,
int max_iterations = 100,
double tolerance = 1e-6);
/// Apply a single coincident (mate) constraint: align the closest opposing
/// faces of node_a and node_b so they are coplanar.
///
/// @return The correction transform that was applied to node_b
/// 应用贴合约束
[[nodiscard]] core::Transform3D apply_coincident(
const AssemblyNode& node_a, AssemblyNode& node_b);
/// Apply a single concentric constraint: align the bounding-box-estimated
/// cylinder axes of node_a and node_b.
///
/// @return The correction transform that was applied to node_b
/// 应用同轴约束
[[nodiscard]] core::Transform3D apply_concentric(
const AssemblyNode& node_a, AssemblyNode& node_b);
/// Apply a distance constraint: set the closest-approach gap between
/// the bounding boxes of node_a and node_b to @p distance.
///
/// @return The correction transform that was applied to node_b
/// 应用距离约束
[[nodiscard]] core::Transform3D apply_distance(
const AssemblyNode& node_a, AssemblyNode& node_b, double distance);
+190
View File
@@ -0,0 +1,190 @@
#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 <string>
#include <vector>
namespace vde::brep {
// ═══════════════════════════════════════════════════════════
// DirectModelingResult — 操作结果
// ═══════════════════════════════════════════════════════════
/**
* @brief 直接建模操作结果
*
* 包含成功/失败标志、错误/警告诊断信息及生成的新模型。
* 即使操作部分成功(如某些邻面未能延伸)也会在 warnings 中记录。
*/
struct DirectModelingResult {
/** @brief 操作是否成功(至少基本几何有效) */
bool success = false;
/** @brief 结果模型(success=true 时有效,否则为输入副本) */
BrepModel body;
/** @brief 错误消息列表(导致 success=false */
std::vector<std::string> errors;
/** @brief 警告消息列表(不影响 success) */
std::vector<std::string> 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);
} // namespace vde::brep
+260
View File
@@ -0,0 +1,260 @@
#pragma once
/**
* @file cam_strategies.h
* @brief 完整 CAM 加工策略 — 粗加工、精加工、钻孔、刀具库、后处理、仿真
*
* 在 cam_toolpath.h 的基础刀路(contour/pocket)之上提供:
* - 层切粗加工 (roughing_toolpath)
* - 精加工平行/环绕 (finishing_toolpath)
* - 钻孔循环 G81/G83/G84 (drilling_toolpath)
* - 刀具结构与刀具库 (Tool, ToolLibrary)
* - 后处理器框架 (PostProcessor → FanucPost, SiemensPost, HeidenhainPost)
* - 加工仿真数据生成 (material_removal_simulation)
*
* @ingroup core
*/
#include "vde/core/point.h"
#include "vde/core/aabb.h"
#include "vde/core/cam_toolpath.h"
#include "vde/mesh/halfedge_mesh.h"
#include <vector>
#include <string>
#include <memory>
#include <functional>
#include <optional>
namespace vde::brep {
class BrepModel;
} // namespace vde::brep
namespace vde::core {
using core::Point3D;
using core::Vector3D;
// ═══════════════════════════════════════════════════════════════════════════
// 刀具定义
// ═══════════════════════════════════════════════════════════════════════════
/// 刀具类型枚举
enum class ToolType {
ENDMILL, ///< 平底铣刀
BALLNOSE, ///< 球头铣刀
DRILL, ///< 钻头
FACEMILL, ///< 面铣刀
TAP ///< 丝锥
};
/// 刀具参数结构体
struct Tool {
int id = 0; ///< 刀具编号
std::string name; ///< 刀具名称
ToolType type = ToolType::ENDMILL; ///< 刀具类型
double diameter = 10.0; ///< 刀具直径 (mm)
int flutes = 2; ///< 刃数
double length = 50.0; ///< 有效刃长 (mm)
double overall_length = 75.0; ///< 总长 (mm)
double shank_diameter = 10.0; ///< 柄径 (mm)
/// 计算推荐转速(基于切削速度 m/min)
[[nodiscard]] double spindle_rpm(double cutting_speed_m_per_min) const;
/// 计算推荐进给速度(mm/min,基于每齿进给量 mm/tooth
[[nodiscard]] double feed_rate_mm_per_min(double feed_per_tooth) const;
};
/// 刀具库 — 管理多个刀具
class ToolLibrary {
public:
/// 添加刀具,返回内部索引
int add_tool(const Tool& tool);
/// 按编号查找刀具
[[nodiscard]] std::optional<Tool> find_tool(int id) const;
/// 按名称查找刀具
[[nodiscard]] std::optional<Tool> find_tool(const std::string& name) const;
/// 列出所有刀具
[[nodiscard]] const std::vector<Tool>& list_tools() const { return tools_; }
/// 刀具数量
[[nodiscard]] size_t size() const { return tools_.size(); }
private:
std::vector<Tool> tools_;
};
// ═══════════════════════════════════════════════════════════════════════════
// 加工参数
// ═══════════════════════════════════════════════════════════════════════════
/// 粗加工参数
struct RoughingParams {
double step_down = 1.0; ///< 每层切深 (mm)
double step_over = 2.0; ///< 横向步距 (mm)
double stock_to_leave = 0.5; ///< 留量 (mm)
double feed_rate = 800.0; ///< 进给速度 (mm/min)
double safe_z = 10.0; ///< 安全高度 (mm)
};
/// 精加工参数
struct FinishingParams {
double step_over = 0.5; ///< 行距 (mm)
double feed_rate = 500.0; ///< 进给速度 (mm/min)
double safe_z = 10.0; ///< 安全高度 (mm)
double depth_of_cut = 0.0; ///< 精加工深度 (Z 坐标;0=最终 Z,负=往下)
};
/// 精加工策略类型
enum class FinishingStrategy {
PARALLEL, ///< 平行刀路 — 等距平行线
SPIRAL ///< 环绕刀路 — 轮廓向内偏移
};
/// 钻孔循环类型
enum class DrillingCycle {
G81, ///< 简单钻孔:G81 X.. Y.. Z.. R.. F..
G83, ///< 深孔啄钻:G83 X.. Y.. Z.. R.. Q.. F..
G84 ///< 攻丝: G84 X.. Y.. Z.. R.. F..
};
/// 钻孔点定义
struct DrillPoint {
Point3D position; ///< 孔位坐标
double depth = -10.0; ///< 钻深(Z 坐标)
double peck_depth = 2.0; ///< 啄钻每次深度 (G83 用)
};
// ═══════════════════════════════════════════════════════════════════════════
// 后处理器框架
// ═══════════════════════════════════════════════════════════════════════════
/// 抽象后处理器基类
class PostProcessor {
public:
virtual ~PostProcessor() = default;
/// 程序头部(初始化 G-code、换刀等)
[[nodiscard]] virtual std::string prologue(const Toolpath& tp) const = 0;
/// 程序尾部(退刀、程序结束)
[[nodiscard]] virtual std::string epilogue(const Toolpath& tp) const = 0;
/// 格式化一条 G-code 行
[[nodiscard]] virtual std::string format_gcode(const PathSegment& seg) const = 0;
/// 完整后处理:prologue + segments + epilogue
[[nodiscard]] std::string post_process(const Toolpath& tp) const;
};
/// Fanuc 控制器后处理器
class FanucPost : public PostProcessor {
public:
[[nodiscard]] std::string prologue(const Toolpath& tp) const override;
[[nodiscard]] std::string epilogue(const Toolpath& tp) const override;
[[nodiscard]] std::string format_gcode(const PathSegment& seg) const override;
};
/// Siemens (Sinumerik) 控制器后处理器
class SiemensPost : public PostProcessor {
public:
[[nodiscard]] std::string prologue(const Toolpath& tp) const override;
[[nodiscard]] std::string epilogue(const Toolpath& tp) const override;
[[nodiscard]] std::string format_gcode(const PathSegment& seg) const override;
};
/// Heidenhain 控制器后处理器
class HeidenhainPost : public PostProcessor {
public:
[[nodiscard]] std::string prologue(const Toolpath& tp) const override;
[[nodiscard]] std::string epilogue(const Toolpath& tp) const override;
[[nodiscard]] std::string format_gcode(const PathSegment& seg) const override;
};
// ═══════════════════════════════════════════════════════════════════════════
// 加工策略函数
// ═══════════════════════════════════════════════════════════════════════════
/// 层切粗加工刀路
///
/// 对 B-Rep 实体进行 Z 层切粗加工:
/// 1. 计算模型的 Z 范围
/// 2. 从安全高度开始,以 step_down 步距逐层往下切
/// 3. 每层将模型轮廓投影到当前 Z 高度,偏移 stock_to_leave 生成刀路
///
/// @param body B-Rep 实体模型
/// @param tool 使用的刀具
/// @param params 粗加工参数
/// @return 粗加工刀路
///
/// @ingroup core
[[nodiscard]] Toolpath roughing_toolpath(
const brep::BrepModel& body,
const Tool& tool,
const RoughingParams& params);
/// 精加工刀路
///
/// 对 B-Rep 实体生成精加工刀路,支持两种策略:
/// - PARALLEL:等距平行线覆盖整个加工面
/// - SPIRAL:从轮廓向内逐步偏移
///
/// @param body B-Rep 实体模型
/// @param tool 使用的刀具
/// @param params 精加工参数
/// @param strategy 加工策略(PARALLEL 或 SPIRAL
/// @return 精加工刀路
///
/// @ingroup core
[[nodiscard]] Toolpath finishing_toolpath(
const brep::BrepModel& body,
const Tool& tool,
const FinishingParams& params,
FinishingStrategy strategy = FinishingStrategy::PARALLEL);
/// 钻孔循环刀路
///
/// 对一组钻孔点生成指定钻孔循环的刀路。
/// - G81:简单钻孔
/// - G83:深孔啄钻(带 Q 参数)
/// - G84:攻丝
///
/// @param points 钻孔点列表
/// @param tool 使用的钻头/丝锥
/// @param cycle 钻孔循环类型
/// @param safe_z 安全高度 (mm)
/// @param feed_rate 进给速度 (mm/min)
/// @return 钻孔刀路
///
/// @ingroup core
[[nodiscard]] Toolpath drilling_toolpath(
const std::vector<DrillPoint>& points,
const Tool& tool,
DrillingCycle cycle = DrillingCycle::G81,
double safe_z = 10.0,
double feed_rate = 200.0);
// ═══════════════════════════════════════════════════════════════════════════
// 加工仿真
// ═══════════════════════════════════════════════════════════════════════════
/// 加工仿真结果
struct SimulationResult {
mesh::HalfedgeMesh remaining_mesh; ///< 加工后剩余材料网格
mesh::HalfedgeMesh removed_mesh; ///< 被切除材料网格
double volume_removed = 0.0; ///< 切除体积 (mm³)
double volume_remaining = 0.0; ///< 剩余体积 (mm³)
int steps = 0; ///< 仿真步数
};
/// 材料去除仿真
///
/// 给定毛坯 B-Rep 和刀路,模拟刀具沿刀路切割材料的过程,
/// 输出剩余材料网格、切除材料网格和体积统计。
///
/// @param body 毛坯 B-Rep 实体
/// @param toolpath 加工刀路
/// @param tool 使用的刀具(用于确定切削截面)
/// @return 仿真结果
///
/// @ingroup core
[[nodiscard]] SimulationResult material_removal_simulation(
const brep::BrepModel& body,
const Toolpath& toolpath,
const Tool& tool);
} // namespace vde::core