Files
ViewDesignEngine/include/vde/brep/constraint_solver.h
T

637 lines
23 KiB
C++
Raw Normal View History

#pragma once
/**
* @file constraint_solver.h
* @brief 3D 装配约束求解器 — 约束图 + Newton-Raphson + DOF 分析
*
* 为 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>
#include <tuple>
namespace vde::brep {
// ═══════════════════════════════════════════════════════════
// ConstraintType
// ═══════════════════════════════════════════════════════════
/// 3D 约束类型
enum class ConstraintType {
Coincident, ///< 面-面贴合(共面,法向相反)
Concentric, ///< 轴-轴对齐(同轴)
Tangent, ///< 面相切接触
Distance, ///< 固定面间距
Angle, ///< 固定面夹角(弧度)
Parallel, ///< 面法线平行
Perpendicular ///< 面法线垂直
};
/// 约束类型转字符串
[[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) {}
};
// ═══════════════════════════════════════════════════════════
// 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;
};
// ═══════════════════════════════════════════════════════════
// DOFAnalyzer — 详细自由度分析器
// ═══════════════════════════════════════════════════════════
/// DOF 分解方向描述
struct DOFDirection {
/// 平移方向:x, y, z 是否被约束
bool tx_free = true, ty_free = true, tz_free = true;
/// 旋转方向:绕 x, y, z 是否被约束
bool rx_free = true, ry_free = true, rz_free = true;
};
/// DOF 分析详细结果
struct DOFDetailedResult {
int node_index = -1;
std::string node_name;
int translational_dof_remaining = 3; ///< 剩余平移自由度
int rotational_dof_remaining = 3; ///< 剩余旋转自由度
DOFDirection free_directions; ///< 哪些方向未约束
std::vector<int> constraints_used; ///< 涉及约束索引
bool is_fully_constrained = false; ///< 完全约束
bool is_over_constrained = false; ///< 过度约束
std::string summary; ///< 可读摘要
};
/**
* @brief 零件自由度分析器
*
* 将每个零件的 6 个 DOF 分解为平移(3)和旋转(3),
* 根据约束类型计算每个方向上的自由/约束状态。
*
* @ingroup brep
*/
class DOFAnalyzer {
public:
/// 执行全图 DOF 分析
[[nodiscard]] std::vector<DOFDetailedResult> analyze(const ConstraintGraph& graph) const;
/// 获取单个零件的自由方向
[[nodiscard]] DOFDirection free_directions(const ConstraintGraph& graph, int node_idx) const;
/// 列出零件剩余的自由运动方向描述
[[nodiscard]] std::vector<std::string> free_direction_names(
const ConstraintGraph& graph, int node_idx) const;
/// 分析所有约束后的全局 DOF 状态
[[nodiscard]] std::string global_dof_summary(const ConstraintGraph& graph) const;
private:
/// 根据约束类型,返回对 node_a 的平移/旋转约束影响
static void constraint_impact(ConstraintType type, bool is_node_a,
int& trans_count, int& rot_count,
DOFDirection& dir);
};
// ═══════════════════════════════════════════════════════════
// RedundancyDetector — 冗余约束检测器
// ═══════════════════════════════════════════════════════════
/// 冗余约束建议
struct RedundancySuggestion {
int constraint_index = -1; ///< 冗余约束索引
int node_index = -1; ///< 受影响的节点
std::string reason; ///< 原因描述
int dof_impact = 0; ///< 移除后释放的 DOF 数
std::string action; ///< 建议操作(删除/修改/保留)
};
/// 约束冲突信息
struct ConstraintConflict {
int constraint_a = -1;
int constraint_b = -1;
int shared_node = -1; ///< 冲突所在的公共节点
std::string description;
};
/**
* @brief 冗余约束检测器
*
* 检测约束图中的冗余约束和约束冲突:
* - 类型级冗余:同节点对之间存在多个相同类型的约束
* - 拓扑级冗余:约束消除的 DOF 总量超出 6
* - 语义冲突:约束类型相互矛盾(如 Coincident + Distance ≠ 0
*
* @ingroup brep
*/
class RedundancyDetector {
public:
/// 全图检测冗余
[[nodiscard]] std::vector<RedundancySuggestion> detect(const ConstraintGraph& graph) const;
/// 针对特定节点检测
[[nodiscard]] std::vector<RedundancySuggestion> detect_for_node(
const ConstraintGraph& graph, int node_idx) const;
/// 检测约束冲突
[[nodiscard]] std::vector<ConstraintConflict> detect_conflicts(
const ConstraintGraph& graph) const;
/// 生成可读报告
[[nodiscard]] std::string report(const ConstraintGraph& graph) const;
private:
/// 判断两个约束是否冲突
static bool are_conflicting(ConstraintType a, ConstraintType b);
};
// ═══════════════════════════════════════════════════════════
// ConstraintPropagator — 约束传播器
// ═══════════════════════════════════════════════════════════
/// 传播结果
struct PropagateResult {
bool success = false;
std::vector<int> affected_nodes; ///< 受影响的节点索引
std::vector<int> affected_constraints; ///< 受影响的约束索引
std::vector<std::string> changes; ///< 变更描述
std::string message; ///< 可读消息
};
/**
* @brief 约束传播器
*
* 修改一个约束后,沿依赖图传播更新:
* 1. 计算图拓扑排序(BFS 从固定节点出发)
* 2. 标记受影响的下游节点
* 3. 增量重新求解受影响的部分
*
* @ingroup brep
*/
class ConstraintPropagator {
public:
/**
* @brief 修改约束并传播
*
* @param graph 约束图
* @param assembly 装配体
* @param constraint_idx 被修改约束索引
* @param new_value 新值
* @param new_type 新类型(可选:不修改则设为相同值)
* @param solver 求解器(可选:用于增量求解)
* @return 传播结果
*/
[[nodiscard]] PropagateResult propagate(
ConstraintGraph& graph,
Assembly& assembly,
int constraint_idx,
double new_value,
ConstraintType new_type,
NewtonRaphsonSolver* solver = nullptr);
/// 计算依赖传播顺序(BFS
[[nodiscard]] std::vector<int> compute_dependency_order(
const ConstraintGraph& graph, int start_node) const;
/// 获取约束之间的依赖深度
[[nodiscard]] int dependency_depth(
const ConstraintGraph& graph, int a_idx, int b_idx) const;
};
// ═══════════════════════════════════════════════════════════
// KinematicChainSolver — 闭环机构求解器
// ═══════════════════════════════════════════════════════════
/// 运动学链的连杆描述
struct ChainLink {
int node_index = -1; ///< 对应的约束图节点
double length = 1.0; ///< 连杆长度(相对于前一节点)
double twist_angle = 0.0; ///< 扭转角(弧度)
double offset = 0.0; ///< 偏距
std::string label; ///< 标签
};
/// 闭环条件
struct LoopClosure {
int start_node = -1; ///< 环路起始节点
int end_node = -1; ///< 环路终止节点
ConstraintType closure_type = ConstraintType::Coincident;
double closure_value = 0.0; ///< 目标间距/角度
double tolerance = 1e-6; ///< 容差
};
/// 运动学求解结果
struct KinematicResult {
bool solved = false;
int iterations = 0;
double final_error = 0.0;
std::vector<double> joint_angles; ///< 求解出的关节角度
std::vector<core::Transform3D> poses; ///< 求解出的位姿
std::string message;
};
/**
* @brief 闭环机构求解器
*
* 求解闭环运动学链的关节角度:
* - 将闭环约束表示为非线性方程组
* - 使用 Newton-Raphson 迭代求解
* - 支持任意长度的串行链 + 末端闭合条件
*
* @ingroup brep
*/
class KinematicChainSolver {
public:
/** @brief DH 参数法:单个连杆变换(公开,供测试使用) */
static core::Transform3D dh_transform(double theta, double d,
double a, double alpha);
/** @brief 求解闭环运动学链 */
[[nodiscard]] KinematicResult solve_closed_chain(
ConstraintGraph& graph,
Assembly& assembly,
const std::vector<ChainLink>& links,
const LoopClosure& closure);
/** @brief 前向运动学:给定关节角 → 位姿 */
[[nodiscard]] std::vector<core::Transform3D> forward_kinematics(
const Assembly& assembly,
const std::vector<ChainLink>& links,
const std::vector<double>& joint_angles) const;
/** @brief 逆向运动学:给定目标位姿 → 关节角 */
[[nodiscard]] std::vector<double> inverse_kinematics(
const Assembly& assembly,
const std::vector<ChainLink>& links,
const core::Transform3D& target_pose) const;
/** @brief 检查链是否可达 */
[[nodiscard]] bool is_reachable(
const std::vector<ChainLink>& links,
const core::Point3D& target) const;
private:
/// 评估闭环残差
static double evaluate_closure(
const Assembly& assembly,
const std::vector<ChainLink>& links,
const std::vector<double>& angles,
const LoopClosure& closure);
};
// ═══════════════════════════════════════════════════════════
// 向后兼容 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);
/// 应用贴合约束
[[nodiscard]] core::Transform3D apply_coincident(
const AssemblyNode& node_a, AssemblyNode& node_b);
/// 应用同轴约束
[[nodiscard]] core::Transform3D apply_concentric(
const AssemblyNode& node_a, AssemblyNode& node_b);
/// 应用距离约束
[[nodiscard]] core::Transform3D apply_distance(
const AssemblyNode& node_a, AssemblyNode& node_b, double distance);
} // namespace vde::brep