6edb8d39ab
在开始'设备契约层 + 目录重构 + 通止规装配'改造之前,先钉住当前可运行状态, 使后续任何删除/重构都可回退。 本基线包含: - 现有 ROS 2 工作区全部源码(arm_application / arm_control / arm_bringup 等) - 官方 flexiv_ros2 栈(含 flexiv_hardware / flexiv_controllers 等符号链接) - 设备契约层 src/arm_interfaces(新增,46 项单测通过) - devices/ 设备选型与品牌清单(新增) - scripts/gen_site_example.py 与由它生成的 config/site.example.yaml 按 .gitignore 排除:build/ / install/ / log/ / config/site.yaml / 模型权重二进制。
118 lines
4.2 KiB
Python
Executable File
118 lines
4.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Convert Flexiv Elements .traj (text) -> arm_application YAML waypoints."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import math
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
def parse_flexiv_traj(text: str) -> list[dict[str, Any]]:
|
|
"""Parse Elements Studio scatter-point .traj file."""
|
|
blocks = re.split(r'pattern_params\s*\{', text)[1:]
|
|
waypoints: list[dict[str, Any]] = []
|
|
for block in blocks:
|
|
name_m = re.search(r'pattern_name:\s*"([^"]+)"', block)
|
|
vel_m = re.search(r'velocity:\s*"([^"]+)"', block)
|
|
joints: dict[str, float] = {}
|
|
for j in range(1, 8):
|
|
m = re.search(rf'joint{j}:\s*"([^"]+)"', block)
|
|
if m:
|
|
joints[f'joint{j}'] = math.radians(float(m.group(1)))
|
|
if len(joints) != 7:
|
|
continue
|
|
positions = [joints[f'joint{i}'] for i in range(1, 8)]
|
|
waypoints.append({
|
|
'name': name_m.group(1) if name_m else f'WPT{len(waypoints)}',
|
|
'positions': positions,
|
|
'velocity': float(vel_m.group(1)) if vel_m else 0.2,
|
|
})
|
|
return waypoints
|
|
|
|
|
|
def assign_times(waypoints: list[dict[str, Any]], min_seg: float = 3.0) -> None:
|
|
"""Cumulative time_from_start from joint-space segment length."""
|
|
t = 0.0
|
|
prev = None
|
|
for wp in waypoints:
|
|
pos = wp['positions']
|
|
if prev is None:
|
|
t += min_seg
|
|
else:
|
|
delta = max(abs(a - b) for a, b in zip(pos, prev))
|
|
# ~4 s per radian max joint move, floored at min_seg
|
|
t += max(min_seg, delta * 4.0)
|
|
wp['time'] = round(t, 2)
|
|
prev = pos
|
|
|
|
|
|
def to_yaml(
|
|
waypoints: list[dict[str, Any]],
|
|
*,
|
|
joint_prefix: str = 'Rizon4-063989_',
|
|
velocity_scale: float | None = None,
|
|
) -> str:
|
|
if velocity_scale is None:
|
|
velocity_scale = waypoints[0].get('velocity', 0.2) if waypoints else 0.2
|
|
times = [wp['time'] for wp in waypoints]
|
|
flat: list[float] = []
|
|
for wp in waypoints:
|
|
flat.extend(wp['positions'])
|
|
pos_line = ', '.join(f'{v:.6f}' for v in flat)
|
|
time_line = ', '.join(str(t) for t in times)
|
|
names = ', '.join(f' # {wp.get("name", "")}' if i == 0 else '' for i, wp in enumerate(waypoints))
|
|
_ = names # names kept in comments below
|
|
lines = [
|
|
'# Auto-converted from Flexiv Elements .traj (joints: deg -> rad)',
|
|
'# ROS params: flat waypoint_positions + waypoint_times (no nested dicts)',
|
|
'# Edit velocity_scale / loop_count as needed (no rebuild).',
|
|
'run_joint_trajectory:',
|
|
' ros__parameters:',
|
|
f' joint_prefix: "{joint_prefix}"',
|
|
' joint_names: [joint1, joint2, joint3, joint4, joint5, joint6, joint7]',
|
|
f' velocity_scale: {velocity_scale}',
|
|
' loop_count: 1 # 1=once, 0=forever, N=repeat N',
|
|
' pause_between_loops: 0.5',
|
|
f' waypoint_times: [{time_line}]',
|
|
' waypoint_positions: [',
|
|
]
|
|
for i, wp in enumerate(waypoints):
|
|
pos = ', '.join(f'{v:.6f}' for v in wp['positions'])
|
|
lines.append(f' {pos}, # {wp.get("name", f"WPT{i}")}')
|
|
lines.append(' ]')
|
|
return '\n'.join(lines) + '\n'
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser(description=__doc__)
|
|
ap.add_argument('input', type=Path, help='Flexiv Elements .traj file')
|
|
ap.add_argument('-o', '--output', type=Path, help='Output YAML path')
|
|
ap.add_argument('--prefix', default='Rizon4-063989_', help='Joint name prefix (site SN + _)')
|
|
ap.add_argument('--min-seg', type=float, default=3.0, help='Min seconds per segment')
|
|
args = ap.parse_args()
|
|
|
|
text = args.input.read_text(encoding='utf-8')
|
|
wps = parse_flexiv_traj(text)
|
|
if not wps:
|
|
print('ERROR: no waypoints parsed', file=sys.stderr)
|
|
return 1
|
|
assign_times(wps, min_seg=args.min_seg)
|
|
yaml_text = to_yaml(wps, joint_prefix=args.prefix)
|
|
|
|
out = args.output
|
|
if out:
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
out.write_text(yaml_text, encoding='utf-8')
|
|
print(f'Wrote {len(wps)} waypoints -> {out}')
|
|
else:
|
|
print(yaml_text)
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|