From 36fa33b0326b96c36f2430d97dfb9f2bf82e87cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=93=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=B0=D1=80=D1=8C?= Date: Wed, 3 Jun 2026 14:03:44 +1000 Subject: [PATCH] Refactor tool management and update robot configuration - Updated iiwa7.srdf to streamline group and end effector definitions, removing unnecessary joint specifications. - Enhanced setting.yaml to include active tool configuration. - Removed deprecated gripper URDF and Xacro files, consolidating tool definitions into tools.yaml. - Introduced tool_manager.py for dynamic tool management, allowing for easy updates to active tools and collision settings. - Created tool_active.xacro to reflect the currently active tool in the robot's URDF. - Added comprehensive collision management for the new tool configurations. --- cobot-setting.yaml | 19 +- cobot/commands/robot_setup.py | 105 ++++++++++- src/iiwa_config/config/moveit/iiwa7.srdf | 44 ++--- src/iiwa_config/config/setting.yaml | 3 + src/iiwa_config/config/tools.yaml | 64 +++++++ .../urdf/gripper/___gripper.urdf | 176 ------------------ .../urdf/gripper/gripper.xacro | 13 -- .../urdf/gripper/gripper_joints.xacro | 41 ---- .../urdf/gripper/gripper_links.xacro | 67 ------- .../urdf/gripper/gripper_macros.xacro | 65 ------- .../urdf/gripper/gripper_meshes.xacro | 12 -- src/iiwa_description/urdf/iiwa7.urdf.xacro | 2 +- .../urdf/tools/tool_active.xacro | 5 + src/iiwa_utils/iiwa_utils/setting_loader.py | 13 ++ src/iiwa_utils/iiwa_utils/tool_manager.py | 157 ++++++++++++++++ 15 files changed, 364 insertions(+), 422 deletions(-) create mode 100644 src/iiwa_config/config/tools.yaml delete mode 100644 src/iiwa_description/urdf/gripper/___gripper.urdf delete mode 100644 src/iiwa_description/urdf/gripper/gripper.xacro delete mode 100644 src/iiwa_description/urdf/gripper/gripper_joints.xacro delete mode 100644 src/iiwa_description/urdf/gripper/gripper_links.xacro delete mode 100644 src/iiwa_description/urdf/gripper/gripper_macros.xacro delete mode 100644 src/iiwa_description/urdf/gripper/gripper_meshes.xacro create mode 100644 src/iiwa_description/urdf/tools/tool_active.xacro create mode 100644 src/iiwa_utils/iiwa_utils/tool_manager.py diff --git a/cobot-setting.yaml b/cobot-setting.yaml index ec55b12..a2aef58 100644 --- a/cobot-setting.yaml +++ b/cobot-setting.yaml @@ -18,7 +18,7 @@ digital_twin: controller_timer: "50" cameras: - - pkg://iiwa_config/config/cameras/d455_top.yaml + - pkg://iiwa_config/config/cameras/d455_top.yaml rviz: config: pkg://iiwa_config/config/rviz/rviz_moveit.rviz @@ -36,6 +36,9 @@ controller: moveit_cpp: pkg://iiwa_config/config/moveit/moveit_cpp.yaml +tool: + active: "patron" # Активный инструмент: none | patron | ... (из tools.yaml) + planning: pose_link: "tcp" # TCP-линк для декартовых целей planning_group: "iiwa_arm" # Группа планирования из SRDF @@ -68,12 +71,12 @@ foxglove: send_buffer_limit: 10000000 # Максимальный размер буфера отправки в байтах (защита от OOM при медленном клиенте) use_sim_time: false # Использовать симуляционное время /clock вместо системного capabilities: # Список возможностей, открытых клиенту - - clientPublish - - parameters - - parametersSubscribe - - services - - connectionGraph - - assets + - clientPublish + - parameters + - parametersSubscribe + - services + - connectionGraph + - assets include_hidden: false # Показывать клиенту скрытые топики и сервисы (начинаются с _) asset_uri_allowlist: ['^package://(?:[-\w%]+/)*[-\w%.]+\.(?:dae|fbx|glb|gltf|jpeg|jpg|mtl|obj|png|stl|tif|tiff|urdf|webp|xacro)$'] # Regex-список URI вида package://..., из которых bridge разрешает отдавать файлы-ассеты (URDF, mesh и т.п.) - ignore_unresponsive_param_nodes: true # Не падать, если нода не отвечает на запросы параметров (защита от зависания при старте) \ No newline at end of file + ignore_unresponsive_param_nodes: true # Не падать, если нода не отвечает на запросы параметров (защита от зависания при старте) diff --git a/cobot/commands/robot_setup.py b/cobot/commands/robot_setup.py index efabf3d..2e7ca6f 100644 --- a/cobot/commands/robot_setup.py +++ b/cobot/commands/robot_setup.py @@ -17,6 +17,12 @@ from cobot.tui import SCREEN_CSS, InputScreen, PickScreen _PROJECT_DIR = Path(__file__).parent.parent.parent _CONFIG_PATH = _PROJECT_DIR / "cobot-setting.yaml" +_TOOLS_YAML = _PROJECT_DIR / "src" / "iiwa_config" / "config" / "tools.yaml" +_TOOL_ACTIVE_XACRO = ( + _PROJECT_DIR / "src" / "iiwa_description" / "urdf" / "tools" / "tool_active.xacro" +) +_SRDF_PATH = _PROJECT_DIR / "src" / "iiwa_config" / "config" / "moveit" / "iiwa7.srdf" + # Use ruamel.yaml instead of PyYAML so comments and formatting in the config file are preserved. # Используем ruamel.yaml вместо PyYAML, чтобы комментарии и форматирование в конфиге сохранялись. _yaml = YAML() @@ -48,6 +54,41 @@ class _Block: fields: List[_Field] +def _load_tools_registry() -> dict: + """Читает tools.yaml и возвращает словарь инструментов.""" + if not _TOOLS_YAML.exists(): + return {} + _y = YAML() + with open(_TOOLS_YAML, encoding="utf-8") as f: + data = _y.load(f) + return dict(data.get("tools", {})) + + +def _build_tool_block() -> Optional[_Block]: + """Строит блок выбора инструмента из реестра tools.yaml. + Возвращает None если реестр недоступен.""" + registry = _load_tools_registry() + if not registry: + return None + options = list(registry.keys()) + labels = " | ".join( + f"{name}: {registry[name].get('label', '')}" for name in options + ) + return _Block( + yaml_key="tool", + title="Tool / End-effector", + fields=[ + _Field( + "active", + "Выберите активный инструмент:", + options[0], + note=labels, + options=options, + ), + ], + ) + + # All configuration blocks. Each block maps to a top-level key in cobot-setting.yaml. # Все блоки конфигурации. Каждый блок соответствует ключу верхнего уровня в cobot-setting.yaml. _BLOCKS: List[_Block] = [ @@ -75,8 +116,6 @@ _BLOCKS: List[_Block] = [ yaml_key="planning", title="MoveIt planning", fields=[ - _Field("pose_link", "TCP link name:", "tcp", - note="Link used as the end-effector for Cartesian goals (defined in URDF/SRDF)"), _Field("planning_group", "Planning group:", "iiwa_arm", note="MoveIt planning group as defined in the SRDF"), _Field("default_frame", "Default reference frame:", "base_link"), @@ -204,14 +243,15 @@ class _Wizard(App[None]): """ CSS = SCREEN_CSS - def __init__(self, data: Any): + def __init__(self, data: Any, extra_blocks: Optional[List[_Block]] = None): super().__init__() self._data = data - self._blocks = list(_BLOCKS) + self._blocks = list(extra_blocks or []) + list(_BLOCKS) self._block_idx = 0 self._field_idx = 0 self._current_block: Optional[_Block] = None self._pending_fields: List[_Field] = [] + self.did_save = False def on_mount(self) -> None: self._next_block() @@ -221,6 +261,7 @@ class _Wizard(App[None]): # All blocks done - save and show the confirmation screen. # Все блоки пройдены - сохраняем и показываем экран подтверждения. _save_config(self._data) + self.did_save = True self.push_screen(_SavedScreen(), lambda _: self.exit()) return block = self._blocks[self._block_idx] @@ -317,10 +358,60 @@ def register(subparsers: argparse._SubParsersAction) -> None: def run(args: argparse.Namespace) -> None: + from rich.console import Console + console = Console() + if not _CONFIG_PATH.exists(): - from rich.console import Console - Console().print(f"[red]Config not found:[/red] {_CONFIG_PATH}") + console.print(f"[red]Config not found:[/red] {_CONFIG_PATH}") sys.exit(1) data = _load_config() - _Wizard(data).run() + + # Убеждаемся что секция tool существует в данных (для старых конфигов) + if "tool" not in data: + data["tool"] = {"active": "patron"} + + tool_block = _build_tool_block() + extra = [tool_block] if tool_block else [] + + wizard = _Wizard(data, extra_blocks=extra) + wizard.run() + + if not wizard.did_save: + return + + # Применяем выбранный инструмент: перезаписываем tool_active.xacro и iiwa7.srdf + active_tool = str(data["tool"].get("active", "patron")) + if _TOOLS_YAML.exists(): + try: + registry = _load_tools_registry() + if active_tool not in registry: + raise ValueError( + f"Unknown tool '{active_tool}'. Available: {', '.join(registry)}" + ) + tool_cfg = dict(registry[active_tool]) + + import sys as _sys + _sys.path.insert(0, str(_PROJECT_DIR / "src" / "iiwa_utils")) + from iiwa_utils.tool_manager import apply_tool + apply_tool( + tool_cfg=tool_cfg, + xacro_out_path=_TOOL_ACTIVE_XACRO, + srdf_path=_SRDF_PATH, + ) + + # Синхронизируем planning.pose_link с tcp_link выбранного инструмента + tcp_link = tool_cfg.get("tcp_link", "link_ee") + if "planning" in data: + data["planning"]["pose_link"] = tcp_link + _save_config(data) + + console.print( + f"[green]✓[/green] Tool [bold]{active_tool}[/bold] applied: " + f"tool_active.xacro, iiwa7.srdf updated, " + f"planning.pose_link → [bold]{tcp_link}[/bold]." + ) + except Exception as exc: + console.print(f"[red]Tool apply failed:[/red] {exc}") + else: + console.print(f"[yellow]tools.yaml not found at {_TOOLS_YAML} — skipping tool apply[/yellow]") diff --git a/src/iiwa_config/config/moveit/iiwa7.srdf b/src/iiwa_config/config/moveit/iiwa7.srdf index ee69f82..5379f3c 100644 --- a/src/iiwa_config/config/moveit/iiwa7.srdf +++ b/src/iiwa_config/config/moveit/iiwa7.srdf @@ -1,32 +1,11 @@ - + - - - - - + - - + @@ -54,9 +33,9 @@ - + - + @@ -67,26 +46,27 @@ - - - - - + + + + + + @@ -119,4 +99,4 @@ - \ No newline at end of file + diff --git a/src/iiwa_config/config/setting.yaml b/src/iiwa_config/config/setting.yaml index ec55b12..2352374 100644 --- a/src/iiwa_config/config/setting.yaml +++ b/src/iiwa_config/config/setting.yaml @@ -36,6 +36,9 @@ controller: moveit_cpp: pkg://iiwa_config/config/moveit/moveit_cpp.yaml +tool: + active: "patron" # Активный инструмент: none | patron | ... (из tools.yaml) + planning: pose_link: "tcp" # TCP-линк для декартовых целей planning_group: "iiwa_arm" # Группа планирования из SRDF diff --git a/src/iiwa_config/config/tools.yaml b/src/iiwa_config/config/tools.yaml new file mode 100644 index 0000000..8f3e014 --- /dev/null +++ b/src/iiwa_config/config/tools.yaml @@ -0,0 +1,64 @@ +# Реестр инструментов / захватов для KUKA iiwa7. +# +# Поля каждого инструмента: +# label — название, отображаемое в меню robot-setup +# xacro — путь до xacro-файла инструмента (xacro $(find ...) синтаксис); +# null = без захвата (голый фланец) +# tip_link — конец кинематической цепочки в SRDF () +# tcp_link — фрейм TCP для Декартовых целей (pose_link в planning) +# collisions — пары disable_collisions специфичные для этого инструмента; +# базовые пары робота (link1..link7) добавляются автоматически + +tools: + + none: + label: "Без захвата" + xacro: null + tip_link: "link_ee" + tcp_link: "link_ee" + collisions: [] + + patron: + label: "patron" + xacro: "$(find iiwa_description)/urdf/tools/patron.xacro" + tip_link: "patron" + tcp_link: "tcp" + collisions: + - [link1, patron, Never] + - [link2, patron, Never] + - [link3, patron, Never] + - [link4, patron, Never] + - [link5, patron, Never] + - [link6, patron, Never] + - [link7, patron, Adjacent] + - [link7, camera_holder, Adjacent] + - [link6, camera_holder, Never] + - [link5, camera_holder, Never] + - [link4, camera_holder, Never] + - [link3, camera_holder, Never] + - [link2, camera_holder, Never] + - [link1, camera_holder, Never] + - [base_link, camera_holder, Never] + - [camera_holder, patron, Adjacent] + - [camera_holder, camera_corner, Adjacent] + - [camera_corner, patron, Never] + - [camera_corner, link7, Never] + - [camera_corner, link6, Never] + - [camera_corner, link5, Never] + - [camera_corner, link4, Never] + - [camera_corner, link3, Never] + - [camera_corner, link2, Never] + - [camera_corner, link1, Never] + - [camera_corner, base_link, Never] + - [camera_corner, camera_hand, Adjacent] + - [camera_hand, camera_holder, Never] + - [camera_hand, patron, Never] + - [camera_hand, link7, Never] + - [camera_hand, link6, Never] + - [camera_hand, link5, Never] + - [camera_hand, link4, Never] + - [camera_hand, link3, Never] + - [camera_hand, link2, Never] + - [camera_hand, link1, Never] + - [camera_hand, base_link, Never] + diff --git a/src/iiwa_description/urdf/gripper/___gripper.urdf b/src/iiwa_description/urdf/gripper/___gripper.urdf deleted file mode 100644 index e8d5c08..0000000 --- a/src/iiwa_description/urdf/gripper/___gripper.urdf +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/iiwa_description/urdf/gripper/gripper.xacro b/src/iiwa_description/urdf/gripper/gripper.xacro deleted file mode 100644 index 1bcfc8c..0000000 --- a/src/iiwa_description/urdf/gripper/gripper.xacro +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/iiwa_description/urdf/gripper/gripper_joints.xacro b/src/iiwa_description/urdf/gripper/gripper_joints.xacro deleted file mode 100644 index d7063de..0000000 --- a/src/iiwa_description/urdf/gripper/gripper_joints.xacro +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/iiwa_description/urdf/gripper/gripper_links.xacro b/src/iiwa_description/urdf/gripper/gripper_links.xacro deleted file mode 100644 index 630b8a6..0000000 --- a/src/iiwa_description/urdf/gripper/gripper_links.xacro +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/iiwa_description/urdf/gripper/gripper_macros.xacro b/src/iiwa_description/urdf/gripper/gripper_macros.xacro deleted file mode 100644 index 920aeb0..0000000 --- a/src/iiwa_description/urdf/gripper/gripper_macros.xacro +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/iiwa_description/urdf/gripper/gripper_meshes.xacro b/src/iiwa_description/urdf/gripper/gripper_meshes.xacro deleted file mode 100644 index 194fce7..0000000 --- a/src/iiwa_description/urdf/gripper/gripper_meshes.xacro +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/iiwa_description/urdf/iiwa7.urdf.xacro b/src/iiwa_description/urdf/iiwa7.urdf.xacro index e9f58e9..917ee7b 100644 --- a/src/iiwa_description/urdf/iiwa7.urdf.xacro +++ b/src/iiwa_description/urdf/iiwa7.urdf.xacro @@ -17,7 +17,7 @@ - + diff --git a/src/iiwa_description/urdf/tools/tool_active.xacro b/src/iiwa_description/urdf/tools/tool_active.xacro new file mode 100644 index 0000000..1bd4e22 --- /dev/null +++ b/src/iiwa_description/urdf/tools/tool_active.xacro @@ -0,0 +1,5 @@ + + + + + diff --git a/src/iiwa_utils/iiwa_utils/setting_loader.py b/src/iiwa_utils/iiwa_utils/setting_loader.py index 5e6b58d..0161ab4 100644 --- a/src/iiwa_utils/iiwa_utils/setting_loader.py +++ b/src/iiwa_utils/iiwa_utils/setting_loader.py @@ -65,6 +65,11 @@ class PlanningCfg: planning_attempts: int +@dataclass(frozen=True) +class ToolCfg: + active: str # ключ из tools.yaml: "none" | "patron" | ... + + @dataclass(frozen=True) class WebCfg: enabled: bool @@ -108,6 +113,7 @@ class Settings: digital_twin: DigitalTwinCfg controller: ControllerCfg planning: PlanningCfg + tool: ToolCfg foxglove: FoxgloveCfg web: WebCfg @@ -336,6 +342,12 @@ def build_settings(settings_path: str, check_files: bool = True) -> Settings: planning_attempts=int(planning_raw.get("planning_attempts", 3)), ) + # tool + tool_raw = raw.get("tool", {}) + tool = ToolCfg( + active=str(tool_raw.get("active", "patron")), + ) + # foxglove foxglove = _parse_foxglove(raw.get("foxglove")) @@ -347,6 +359,7 @@ def build_settings(settings_path: str, check_files: bool = True) -> Settings: digital_twin=digital_twin, controller=controller, planning=planning, + tool=tool, foxglove=foxglove, web=web, ) diff --git a/src/iiwa_utils/iiwa_utils/tool_manager.py b/src/iiwa_utils/iiwa_utils/tool_manager.py new file mode 100644 index 0000000..f70bbd9 --- /dev/null +++ b/src/iiwa_utils/iiwa_utils/tool_manager.py @@ -0,0 +1,157 @@ +""" +Управление активным инструментом робота. + +Применяет выбранный инструмент из реестра tools.yaml: + 1. Перезаписывает tool_active.xacro — URDF подхватывает его при следующем запуске. + 2. Перегенерирует iiwa7.srdf — сохраняет текущие group_state, обновляет + кинематическую цепочку, end_effector и пары disable_collisions. +""" + +from __future__ import annotations + +import xml.etree.ElementTree as ET +from pathlib import Path +from typing import Any + + +# --------------------------------------------------------------------------- +# Базовые пары столкновений робота (не зависят от инструмента). +# --------------------------------------------------------------------------- +_BASE_COLLISIONS: list[tuple[str, str, str]] = [ + ("base_link", "link1", "Adjacent"), + ("base_link", "link2", "Never"), + ("base_link", "link3", "Never"), + ("base_link", "link4", "Never"), + ("link1", "link2", "Adjacent"), + ("link1", "link3", "Never"), + ("link1", "link4", "Never"), + ("link1", "link5", "Never"), + ("link1", "link6", "Never"), + ("link1", "link7", "Never"), + ("link2", "link3", "Adjacent"), + ("link2", "link4", "Never"), + ("link2", "link5", "Never"), + ("link2", "link6", "Never"), + ("link2", "link7", "Never"), + ("link3", "link4", "Adjacent"), + ("link3", "link5", "Never"), + ("link3", "link6", "Never"), + ("link3", "link7", "Never"), + ("link4", "link5", "Adjacent"), + ("link4", "link6", "Never"), + ("link4", "link7", "Never"), + ("link5", "link6", "Adjacent"), + ("link5", "link7", "Never"), + ("link6", "link7", "Adjacent"), +] + + +def load_registry(tools_yaml_path: Path) -> dict[str, Any]: + """Загружает реестр инструментов из tools.yaml. + Поддерживает PyYAML и ruamel.yaml (что доступно в окружении).""" + try: + import yaml as _yaml + with open(tools_yaml_path, encoding="utf-8") as f: + data = _yaml.safe_load(f) + except ImportError: + from ruamel.yaml import YAML as _RYAML + _y = _RYAML() + with open(tools_yaml_path, encoding="utf-8") as f: + data = _y.load(f) + return dict(data.get("tools", {})) + + +def _read_group_states(srdf_path: Path) -> list[dict]: + """Читает group_state из существующего SRDF, чтобы сохранить их при регенерации.""" + if not srdf_path.exists(): + return [] + tree = ET.parse(srdf_path) + root = tree.getroot() + states = [] + for gs in root.findall("group_state"): + joints = {j.get("name"): j.get("value") for j in gs.findall("joint")} + states.append({ + "name": gs.get("name"), + "group": gs.get("group"), + "joints": joints, + }) + return states + + +def _write_xacro(tool_cfg: dict, output_path: Path) -> None: + """Записывает tool_active.xacro с include нужного инструмента (или пустой).""" + xacro_include = tool_cfg.get("xacro") + if xacro_include: + body = f' \n' + else: + body = " \n" + + content = ( + '\n' + '\n' + '\n' + + body + + '\n' + ) + output_path.write_text(content, encoding="utf-8") + + +def _write_srdf( + tool_cfg: dict, + group_states: list[dict], + output_path: Path, +) -> None: + """Генерирует iiwa7.srdf с учётом выбранного инструмента.""" + tip_link = tool_cfg.get("tip_link", "link_ee") + tool_collisions = [tuple(c) for c in tool_cfg.get("collisions", [])] + + lines: list[str] = [ + '', + '', + '', + '', + ' ', + f' ', + ' ', + ] + + if group_states: + lines.append('') + for gs in group_states: + lines.append(f' ') + for jname, jval in gs["joints"].items(): + lines.append(f' ') + lines.append(' ') + + if tip_link != "link_ee": + lines += [ + '', + f' ', + ] + + lines.append('') + for l1, l2, reason in _BASE_COLLISIONS: + lines.append(f' ') + + if tool_collisions: + lines.append('') + for l1, l2, reason in tool_collisions: + lines.append(f' ') + + lines += ['', ''] + output_path.write_text('\n'.join(lines), encoding="utf-8") + + +def apply_tool( + tool_cfg: dict, + xacro_out_path: Path, + srdf_path: Path, +) -> None: + """ + Применяет инструмент по его конфигу из реестра: + - перезаписывает xacro_out_path (tool_active.xacro) + - регенерирует srdf_path (iiwa7.srdf), сохраняя group_state + """ + group_states = _read_group_states(srdf_path) + _write_xacro(tool_cfg, xacro_out_path) + _write_srdf(tool_cfg, group_states, srdf_path)