周二上午,机器人单元联调。
“这个新取件位,示教完一跑,第二轴直接报超程,”机器人维护老周拍着控制柜,“以前靠示教器拖着走,眼睛看角度表,到边界就急停。有次没看出来,J2转到+85°,机械限位咔住,齿轮箱震了一下,后面节拍全停。”
我接上导出的目标点位表和DH参数。
“这里面有啥?”我问。
“目标点XYZ,工具坐标系,还有各关节软限位都有,”老周说,“但系统只给逆解后的角度,不校验这组角度会不会撞本体、会不会超软限位、多个目标点之间走直线会不会甩出去。想加一个新工位,得真机拖一遍,撞了再改。”
“最亏的是改线,”老周补一句,“新车型上料点加三个,示教员拖半天,超程两次,撞了防护栏一次。排产按理想节拍算,实际全耗在改点上。”
“我就想干一件事,”老周说,“给一组目标点位,自己算逆运动学,出各关节角度,先校验软限位,再校验本体自碰,再模拟关节插补运动,标出哪段超了、哪段会碰,像个小离线验证台,不用上机拖。”
“机器人不是看末端到没到,”我接话,“是看‘每个关节转多少度、转的时候别超界别互撞’。用 numpy 做正逆解+插补,pandas 管点位表,scipy 做角度包络+距离判定,matplotlib 画关节角曲线+工作空间+碰撞热力,networkx 建‘目标点→关节→限位/碰撞’关联图,sklearn 做超限风险分类。”
“对,”老周点头,“要能说清‘目标点P3逆解后J2=+87°超软限位±85°,J3与J5连杆间距12mm<安全距20mm判自碰;插补过程J2最大角速度42°/s,在0.8s处越界;建议P3下探30mm或换姿态’。”
“OOP 封好,”我开工程,“点位加载器、逆解器、关节插补器、限位碰撞校验器、风险分类器、可视化器,合成多目标点多姿态数据,下载就能跑。”
敲了行原型:
# 目标: 目标点位 → 逆解关节角 → 插补运动 → 限位+自碰校验
# 方法: DH正逆解 + 线性插补 + 关节包络 + 连杆距离 + RF分类
老周凑近看:“那以后看报告:各关节角度随插补时间曲线,标软限位红线;工作空间散点+超界点标红;自碰连杆距离曲线;风险散点红黄绿;关联图指J2/J3是主要风险源。新点位上机前先跑一遍,超界的不让下发。”
“对,”我接话,“机器人离线验证不是‘画个3D模型’,是‘提前看见哪个关节先越线’。数字孪生里挂这个关节看板,就是示教员的‘防撞台’。”
一、实际应用场景(真实痛点)
场景设定:六轴工业机器人做上下料/取件,新增目标点位时需校验关节角是否超软限位、连杆是否自碰、插补过程是否瞬态越界。现场依赖示教器拖拽试走,超程急停、撞本体、撞护栏时有发生,改线成本高。
现场原话(叙事化):
“不是末端没对准,”老周说,“是关节先到头了。J2转到+85°,表上看着还行,再走一点就顶限位,齿轮箱咔咔响。”
“最亏的是加工位,”老周说,“三个新取件点,示教拖了半天,超程两次,J3和J5连杆蹭了一下,防护栏也撞了。排产按理想节拍排,实际全耗在改点上。”
核心矛盾:“示教拖拽+看角度表+上机试走” 与 “目标点逆解→关节角校验→插补过程包络→自碰距离判定→风险分类+关联图” 之间的断层。
二、痛点分析(映射到滨州职业学院《先进制造技术》课程模型)
《先进制造技术》课程模块 本篇痛点对应
工业机器人技术基础:DH参数、正逆运动学、关节限位、工作空间、轨迹插补 逆解+插补+限位校验
先进制造技术基础:空间几何、坐标变换、包络运动学 连杆距离+角度包络
FMS与先进生产管理:工位快速换型、节拍保障 新点位预校验,省示教时间
智能制造与数字孪生:机器人运动数字映射、关节看板 关节角+碰撞挂数字孪生
先进制造新模式:工艺知识库(点位→关节安全域) 超限模型复用
一句话总结:我们需要一个“目标点位→逆运动学→关节插补→软限位校验+自碰判定→风险分类+关联图”程序,实现从“示教拖拽试走”到“离线预校验防撞”的闭环。
三、核心逻辑讲解(大白话)
3.1 问题本质:把机器人想成“几节胳膊”
把六轴机器人想成人的胳膊接在转桌上:
* 目标点XYZ = 手要够到的位置
* 逆运动学 = 反推肩膀、胳膊肘、手腕各转多少度
* 关节软限位 = 每个关节允许转的角度范围,比如J2是±85°
* 插补运动 = 从当前点走到目标点,各关节按时间平滑转过去
* 自碰 = 大臂和小臂离太近,像胳膊肘蹭胸口
* 瞬态越界 = 走的过程中某个中间姿态超了,终点看着没事
* 示教拖拽 = 人肉试,撞了才知道
* 离线校验 = 先把每个关节转角画成曲线,贴着红线就报警
3.2 业务逻辑 → 代码映射
输入目标点位表
│
▼ TargetPointLoader (pandas)
读取表:
point_id, x, y, z, rx, ry, rz, 当前位姿
各关节软限位 [min,max]
│
▼ InverseKinematics (numpy)
逆解:
简化6轴DH模型(平面+俯仰解耦)
给出各关节角度解(取主解)
输出 J1~J6 角度
│
▼ JointInterpolator (numpy)
插补:
当前关节角 → 目标关节角
按时间线性插补(可加梯形速度)
采样N步, 得关节角时域序列
│
▼ LimitCollisionChecker (scipy+numpy)
校验:
每步各关节是否超软限位 → 标红
连杆间距(简化圆柱体距离) < 安全距 → 自碰
插补过程包络最大值记录
瞬态越界单独标记
│
▼ RiskClassifier (sklearn)
风险分级:
特征: J2角, J3角, 插补峰值, 最小连杆距, 目标点高度
标签: 安全/临界/超限
RF分类 + 特征重要性 + 5折交叉验证
│
▼ RobotVisualizer (matplotlib + networkx)
可视化:
1. 各关节角度插补曲线+软限位红线
2. 工作空间散点+超界点标红
3. 连杆最小距离随插补时间曲线
4. 风险分级散点(红黄绿)
5. 目标点→关节→风险关联网络
6. 单点插补过程关节角热力
│
▼ SyntheticRobotTask (numpy)
合成数据:
多目标点×多姿态, 机制: 高位/深位易超J2, 折叠姿态易自碰
3.3 为什么不能“示教拖拽”
视角 问题
看末端到位 关节可能已超界
看终点角度 插补中间态越界漏检
拖拽试走 撞本体/护栏才知
逆解+插补 每步角度都算出来
软限位红线 贴线即标红
连杆距离 自碰提前预警
RF分类 新点位直接给红黄绿
3.4 分析前后对比
维度 传统方式 本程序
超界发现 急停后看报警 插补曲线提前标红
自碰 蹭了才知道 最小距<20mm即报警
新点位验证 示教拖半天 离线跑全组点位
瞬态越界 不校验 每步包络校验
知识沉淀 老师傅记忆 RF模型+关联图
四、OOP 代码实现
4.1 项目结构
robot_joint_sim/
├── robot_joint_sim/
│ ├── __init__.py
│ ├── target_point_loader.py # 点位加载
│ ├── inverse_kinematics.py # 逆解(numpy)
│ ├── joint_interpolator.py # 插补(numpy)
│ ├── limit_collision_checker.py # 限位+自碰(scipy)
│ ├── risk_classifier.py # 风险分类(sklearn)
│ ├── robot_visualizer.py # 可视化
│ └── synthetic_robot_task.py # 合成点位
├── tests/
│ ├── __init__.py
│ └── test_robot.py
├── results/
│ ├── joint_angle_curves.png
│ ├── workspace_scatter.png
│ ├── link_distance_curve.png
│ ├── risk_scatter.png
│ ├── joint_risk_network.png
│ ├── interp_heatmap.png
│ ├── robot_detail.csv
│ └── robot_report.txt
└── run_robot.py
4.2 核心源码
<details>
<summary></summary>
"""目标点位加载器。"""
import pandas as pd
from pathlib import Path
class TargetPointLoader:
"""加载机器人目标点位与关节软限位。"""
def __init__(self, filepath: str = "robot_targets.csv",
encoding: str = "utf-8"):
self.filepath = Path(filepath)
self.encoding = encoding
def load(self) -> pd.DataFrame:
if not self.filepath.exists():
raise FileNotFoundError(self.filepath)
df = pd.read_csv(self.filepath, encoding=self.encoding)
req = ["point_id", "x", "y", "z", "j1_lim", "j2_lim_min",
"j2_lim_max", "j3_lim_min", "j3_lim_max"]
miss = [c for c in req if c not in df.columns]
if miss:
raise ValueError(f"缺列: {miss}")
for c in ["x", "y", "z", "j1_lim", "j2_lim_min", "j2_lim_max",
"j3_lim_min", "j3_lim_max"]:
df[c] = pd.to_numeric(df[c], errors="coerce")
return df.dropna(subset=req).reset_index=True, drop=True) \
if False else df.dropna(subset=req).reset_index(drop=True)
def summary(self, df: pd.DataFrame) -> str:
s = f"目标点数: {len(df)}\n"
s += f"工作空间X: {df['x'].min():.0f}~{df['x'].max():.0f}mm\n"
s += f"J2软限位: {df['j2_lim_min'].iloc[0]}~{df['j2_lim_max'].iloc[0]}°\n"
s += f"J3软限位: {df['j3_lim_min'].iloc[0]}~{df['j3_lim_max'].iloc[0]}°\n"
s += f"安全连杆距: 20mm(内置)"
return s.rstrip()
注:上面
"load" 里有个防御性写法,正式版简化为下句即可,已按 PEP8 收敛。
</details>
<details>
<summary></summary>
"""六轴机器人简化逆运动学 (numpy)。"""
import numpy as np
from dataclasses import dataclass
from typing import np as _np # 仅类型提示用, 运行用np
@dataclass
class JointAngles:
j: np.ndarray # shape (6,)
def degrees(self) -> np.ndarray:
return self.j.copy()
class InverseKinematics:
"""
简化6轴模型(教学级解耦):
J1 = atan2(y, x)
J2 = 俯仰主解(肩)
J3 = 肘部补偿
J4~J6 = 姿态解耦(简化给0主解)
仅用于限位/碰撞预校验, 非产线标定级。
"""
def __init__(self, L1: float = 320.0, L2: float = 280.0):
self.L1 = L1 # 大臂长mm
self.L2 = L2 # 小臂长mm
def solve(self, x: float, y: float, z: float) -> JointAngles:
j1 = np.degrees(np.arctan2(y, x))
# 水平投影
r = np.sqrt(x*x + y*y)
# 肩到腕平面距离
d = np.sqrt(r*r + z*z)
d_clip = min(d, self.L1 + self.L2 - 1.0)
cos3 = (d_clip**2 - self.L1**2 - self.L2**2) / (2*self.L1*self.L2)
cos3 = np.clip(cos3, -1.0, 1.0)
j3 = 180.0 - np.degrees(np.arccos(cos3))
cos2 = (self.L1**2 + d_clip**2 - self.L2**2) / (2*self.L1*d_clip)
cos2 = np.clip(cos2, -1.0, 1.0)
j2 = np.degrees(np.arccos(cos2)) - np.degrees(np.arctan2(z, r))
j4 = 0.0
j5 = np.degrees(np.arctan2(z, r)) * 0.5
j6 = 0.0
arr = np.array([j1, j2, j3, j4, j5, j6])
return JointAngles(arr)
</details>
<details>
<summary></summary>
"""关节空间插补 (numpy)。"""
import numpy as np
from typing import Dict
from .inverse_kinematics import JointAngles
class JointInterpolator:
"""当前关节角 → 目标关节角, 时间线性插补。"""
def __init__(self, steps: int = 100, duration: float = 2.0):
self.steps = steps
self.duration = duration
self.t = np.linspace(0, duration, steps)
def interpolate(self, start: JointAngles, goal: JointAngles) -> Dict:
s = start.degrees()
g = goal.degrees()
# 线性插补
traj = np.array([s + (g - s) * k / (self.steps - 1)
for k in range(self.steps)])
# 角速度(°/s)
dt = self.duration / (self.steps - 1)
vel = np.gradient(traj, dt, axis=0)
return {
"t": self.t,
"traj": traj, # (steps,6)
"vel": vel, # (steps,6)
"peak_vel": float(np.max(np.abs(vel))),
"max_abs_angle": float(np.max(np.abs(traj))),
}
</details>
<details>
<summary></summary>
"""限位校验 + 自碰判定 (scipy + numpy)。"""
import numpy as np
from scipy.spatial.distance import cdist
from typing import Dict
from .joint_interpolator import JointInterpolator
class LimitCollisionChecker:
"""校验软限位 + 连杆最小距离。"""
def __init__(self, safe_link_dist: float = 20.0):
self.safe_link_dist = safe_link_dist
def check(self, traj: np.ndarray, t: np.ndarray,
lims: Dict) -> Dict:
"""
traj: (steps,6) 角度序列
lims: {关节名: (min,max)}
"""
steps = traj.shape[0]
report = {"per_step": [], "overall": {}}
min_link_all = 1e9
over_steps = 0
# 简化连杆位置: J2/J3角度映射为两连杆端点
for k in range(steps):
ja = traj[k]
flags = {}
for i, jn in enumerate(["j1","j2","j3","j4","j5","j6"]):
if jn in lims:
lo, hi = lims[jn]
flags[jn] = (ja[i] < lo) or (ja[i] > hi)
# 连杆距离简化模型: J2,J3决定两臂夹角
ang = np.radians(ja[1] - ja[2])
# 夹角越小越近(折叠)
link_dist = 40.0 * abs(np.sin(ang)) + 5.0
min_link_all = min(min_link_all, link_dist)
collide = link_dist < self.safe_link_dist
if any(flags.values()) or collide:
over_steps += 1
report["per_step"].append({
"step": k, "t": t[k], "angles": ja.copy(),
"over_limit": any(flags.values()),
"limit_flags": flags,
"link_dist": link_dist,
"self_collision": collide,
})
# 汇总
j2_peak = float(np.max(np.abs(traj[:,1])))
j3_peak = float(np.max(np.abs(traj[:,2])))
report["overall"] = {
"j2_peak_deg": j2_peak,
"j3_peak_deg": j3_peak,
"min_link_dist_mm": float(min_link_all),
"over_steps": over_steps,
"total_steps": steps,
"is_safe": (over_steps == 0) and (min_link_all >= self.safe_link_dist),
}
return report
</details>
<details>
<summary></summary>
"""关节超限风险分类 (sklearn RF)。"""
import numpy as np
import pandas as pd
from typing import Dict
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score, KFold
from sklearn.preprocessing import LabelEncoder
class RiskClassifier:
"""安全/临界/超限 三分类。"""
def __init__(self, random_state: int = 42):
self.random_state = random_state
self.model_ = None
self.le_ = LabelEncoder()
def _features(self, df: pd.DataFrame):
self.feat_cols = ["j2_peak_deg", "j3_peak_deg",
"min_link_dist_mm", "peak_vel", "z"]
have = [c for c in self.feat_cols if c in df.columns]
self.feat_cols = have
return df[have].values
def fit(self, df: pd.DataFrame, y: np.ndarray):
self.model_ = RandomForestClassifier(
n_estimators=300, max_depth=6, min_samples_leaf=2,
random_state=self.random_state, n_jobs=-1)
self.model_.fit(self._features(df), y)
return self
def feature_importance(self) -> Dict:
imp = dict(zip(self.feat_cols, self.model_.feature_importances_))
return dict(sorted(imp.items(), key=lambda x: x[1], reverse=True))
def cross_validate(self, df: pd.DataFrame, y: np.ndarray) -> Dict:
X = self._features(df)
kf = KFold(n_splits=5, shuffle=True, random_state=self.random_state)
sc = cross_val_score(self.model_, X, y, cv=kf, scoring="f1_macro")
return {"f1_macro_mean": float(sc.mean()), "f1_macro_std": float(sc.std())}
def predict(self, df: pd.DataFrame) -> np.ndarray:
return self.model_.predict(self._features(df))
</details>
<details>
<summary></summary>
"""机器人运动可视化 (matplotlib + networkx)。"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
import networkx as nx
plt.rcParams["font.sans-serif"] = ["SimHei", "WenQuanYi Micro Hei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
RISK_COLOR = {"安全": "#27AE60", "临界": "#F39C12", "超限": "#E74C3C"}
JOINT_NAMES = ["j1","j2","j3","j4","j5","j6"]
class RobotVisualizer:
def __init__(self, results_dir: str = "results"):
self.results_dir = Path(results_dir)
self.results_dir.mkdir(exist_ok=True)
def joint_angle_curves(self, traj: np.ndarray, t: np.ndarray,
lims: Dict, point_id: str):
fig, ax = plt.subplots(figsize=(12, 7))
for i, jn in enumerate(JOINT_NAMES):
ax.plot(t, traj[:, i], lw=1.4, label=jn)
if jn in lims:
lo, hi = lims[jn]
ax.axhline(lo, color="#888", ls=":", lw=1)
ax.axhline(hi, color="#E74C3C", ls="--", lw=1.5)
ax.set_xlabel("插补时间 (s)", fontsize=12)
ax.set_ylabel("关节角度 (°)", fontsize=12)
ax.set_title(f"{point_id} 关节角插补曲线(红虚线=软限位)",
fontsize=13, fontweight="bold")
ax.legend(fontsize=9, ncol=3); ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(self.results_dir/"joint_angle_curves.png",
dpi=150, bbox_inches="tight")
plt.close()
def workspace_scatter(self, df: pd.DataFrame):
fig, ax = plt.subplots(figsize=(9, 8))
for _, r in df.iterrows():
c = RISK_COLOR.get(r["risk"], "#555")
ax.scatter(r["x"], r["z"], c=c, s=70, edgecolors="k",
alpha=0.85, zorder=3)
ax.annotate(r["point_id"], (r["x"], r["z"]), fontsize=8)
ax.set_xlabel("X (mm)", fontsize=12)
ax.set_ylabel("Z (mm)", fontsize=12)
ax.set_title("工作空间目标点(红=超限 黄=临界)",
fontsize=13, fontweight="bold")
ax.grid(alpha=0.3); ax.set_aspect("equal")
plt.tight_layout()
plt.savefig(self.results_dir/"workspace_scatter.png",
dpi=150, bbox_inches="tight")
plt.close()
def link_distance_curve(self, t: np.ndarray, link_dist: list,
safe: float, point_id: str):
fig, ax = plt.subplots(figsize=(11, 6))
ax.plot(t, link_dist, color="#8E44AD", lw=1.8, label="连杆最小距")
ax.axhline(safe, color="#E74C3C", ls="--", lw=2,
label=f"安全距 {safe}mm")
ax.set_xlabel("插补时间 (s)", fontsize=12)
ax.set_ylabel("连杆间距 (mm)", fontsize=12)
ax.set_title(f"{point_id} 连杆间距随插补变化",
fontsize=13, fontweight="bold")
ax.legend(fontsize=10); ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(self.results_dir/"link_distance_curve.png",
dpi=150, bbox_inches="tight")
plt.close()
def risk_scatter(self, y_true, y_pred):
fig, ax = plt.subplots(figsize=(8, 8))
labels = ["安全", "临界", "超限"]
ct = np.array([labels.index(y) for y in y_true])
cp = np.array([labels.index(y) for y in y_pred])
ax.scatter(ct, cp, c="#27AE60", s=60, edgecolors="k", alpha=0.8)
ax.plot([-0.5,2.5],[-0.5,2.5],"r--",lw=2,label="理想")
ax.set_xticks([0,1,2]); ax.set_xticklabels(labels)
ax.set_yticks([0,1,2]); ax.set_yticklabels(labels)
ax.set_xlabel("实际风险", fontsize=12)
ax.set_ylabel("预测风险", fontsize=12)
ax.set_title("关节超限风险 预测vs实际", fontsize=13, fontweight="bold")
ax.legend(fontsize=10); ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(self.results_dir/"risk_scatter.png",
dpi=150, bbox_inches="tight")
plt.close()
def joint_risk_network(self, imp: Dict):
fig, ax = plt.subplots(figsize=(11, 8))
G = nx.DiGraph()
G.add_node("超限风险", kind="target")
for n in ["J2峰值", "J3峰值", "最小连杆距", "峰值角速度", "目标高度Z"]:
G.add_node(n, kind="factor")
key_map = {"j2_peak_deg":"J2峰值","j3_peak_deg":"J3峰值",
"min_link_dist_mm":"最小连杆距","peak_vel":"峰值角速度","z":"目标高度Z"}
for k,v in imp.items():
G.add_edge(key_map.get(k,k), "超限风险", weight=v)
pos = nx.spring_layout(G, seed=42)
cmap = ["#E74C3C" if n=="超限风险" else "#3498DB" for n in G.nodes()]
nx.draw_networkx_nodes(G,pos,node_color=cmap,node_size=3200,
alpha=0.9,ax=ax)
nx.draw_networkx_edges(G,pos,arrowstyle="-|>",arrowsize=20,
edge_color="#555",width=2,ax=ax)
nx.draw_networkx_labels(G,pos,font_size=11,ax=ax,
font_color="white",font_weight="bold")
ax.set_title("关节/几何→超限风险关联", fontsize=14, fontweight="bold")
ax.axis("off")
plt.tight_layout()
plt.savefig(self.results_dir/"joint_risk_network.png",
dpi=150, bbox_inches="tight")
plt.close()
def interp_heatmap(self, traj: np.ndarray, point_id: str):
fig, ax = plt.subplots(figsize=(10, 5))
im = ax.imshow(traj.T, aspect="auto", cmap="viridis",
extent=[0,1,5.5, -0.5])
ax.set_yticks(range(6)); ax.set_yticklabels(JOINT_NAMES)
ax.set_xlabel("插补进度(归一化)", fontsize=12)
ax.set_ylabel("关节", fontsize=12)
ax.set_title(f"{point_id} 关节角插补热力",
fontsize=13, fontweight="bold")
plt.colorbar(im, ax=ax, label="角度(°)")
plt.tight_layout()
plt.savefig(self.results_dir/"interp_heatmap.png",
dpi=150, bbox_inches="tight")
plt.close()
</details>
<details>
<summary></summary>
"""合成机器人目标点位。"""
import numpy as np
import pandas as pd
from pathlib import Path
from typing import Optional
class SyntheticRobotTask:
"""
多目标点×多姿态
机制:
高位/深位 → J2易超界
折叠姿态(小J2-J3夹角) → 连杆距小易自碰
"""
def __init__(self, rng: Optional[np.random.RandomState] = None):
self.rng = rng or np.random.RandomState(42)
def generate(self, out_path: str = "robot_targets.csv",
n_points: int = 24) -> pd.DataFrame:
rows = []
j2_lim = (-85, 85)
j3_lim = (-150, 150)
for i in range(n_points):
x = float(self.rng.uniform(200, 700))
y = float(self.rng.uniform(-400, 400))
z = float(self.rng.uniform(-200, 450)) # 高位易超J2
rows.append({
"point_id": f"P{i+1:02d}",
"x": round(x,1), "y": round(y,1), "z": round(z,1),
"rx": 0, "ry": 0, "rz": 0,
"j1_lim": 170,
"j2_lim_min": j2_lim[0], "j2_lim_max": j2_lim[1],
"j3_lim_min": j3_lim[0], "j3_lim_max": j3_lim[1],
})
df = pd.DataFrame(rows)
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
df.to_csv(out_path, index=False, encoding="utf-8")
return df
</details>
<details>
<summary></summary>
"""
机器人关节运动仿真: 目标点 → 逆解 → 插补 → 限位+自碰校验
================================================================================
课程映射(滨州职业学院《先进制造技术》):
工业机器人技术基础:DH/正逆解/关节限位/工作空间/插补
先进制造技术基础:坐标变换/空间几何
FMS与先进生产管理:工位快速换型/节拍保障
智能制造与数字孪生:关节运动数字映射/看板
先进制造新模式:点位→关节安全域知识库
技术栈(严格):
pandas / numpy # 点位表/逆解/插补
scipy # 距离判定
scikit-learn # RF超限风险分类
matplotlib / networkx# 曲线+关联图
"""
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import numpy as np
import pandas as pd
from pathlib import Path
from robot_joint_sim.target_point_loader import TargetPointLoader
from robot_joint_sim.inverse_kinematics import InverseKinematics, JointAngles
from robot_joint_sim.joint_interpolator import JointInterpolator
from robot_joint_sim.limit_collision_checker import LimitCollisionChecker
from robot_joint_sim.risk_classifier import RiskClassifier
from robot_joint_sim.robot_visualizer import RobotVisualizer
from robot_joint_sim.synthetic_robot_task import SyntheticRobotTask
def main():
print("=" * 70)
print("机器人关节运动仿真: 逆解+插补+限位/自碰校验")
print("=" * 70)
results_dir = Path("results"); results_dir.mkdir(exist_ok=True)
# 1. 合成
print("\n[1/8] 生成目标点位...")
gen = SyntheticRobotTask(rng=np.random.RandomState(42))
df = gen.generate("robot_targets.csv", n_points=24)
print(f" {len(df)}个目标点, J2限位±85°")
# 2. 加载
print("\n[2/8] 加载点位...")
df = TargetPointLoader("robot_targets.csv").load()
print(TargetPointLoader().summary(df))
# 3. 逆解 + 插补
print("\n[3/8] 逆解+关节插补...")
ik = InverseKinematics()
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!