Source code for embodichain_benchmarks.motion_generation.robots.dual_arm
# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------
"""Dual-arm robot provider for coordinated Atomic Skill benchmarks."""
from __future__ import annotations
from embodichain.lab.sim.cfg import RobotCfg
from .._atomic_fixtures import (
DUAL_UR5_INIT_POS,
DUAL_UR5_INIT_ROT,
create_dual_tutorial_robot_cfg,
)
from ..registry import register_robot_provider
from .base import RobotProvider
__all__ = ["DualArmProvider"]
def _optional_floats(value: object, name: str) -> list[float] | None:
"""Return an optional list of floats from a YAML value."""
if value is None:
return None
if not isinstance(value, (list, tuple)):
raise TypeError(f"robot.config.{name} must be a list of numbers.")
return [float(item) for item in value]
[docs]
class DualArmProvider(RobotProvider):
"""Build the tutorial dual-arm robot: two arms with one gripper each.
The control parts are ``left_arm``/``right_arm`` and
``left_eef``/``right_eef``. ``robot.config`` selects the arm family and
the tutorial construction parameters, so each coordinated skill suite can
reuse its tutorial's validated robot.
"""
control_part = "left_arm"
[docs]
def build_cfg(self) -> RobotCfg:
"""Build the configured dual-arm robot."""
config = dict(self.spec.config)
robot_type = str(config.get("robot_type", "ur5"))
solver = str(config.get("solver", "ur"))
if solver not in {"ur", "pytorch"}:
raise ValueError("robot.config.solver must be 'ur' or 'pytorch'.")
return create_dual_tutorial_robot_cfg(
robot_type=robot_type,
uid=str(config.get("uid", f"benchmark_dual_{robot_type}")),
urdf_name=str(config.get("urdf_name", f"benchmark_dual_{robot_type}")),
tcp_z=float(config.get("tcp_z", 0.15)),
solver=solver,
ur_ik_nearest_weight=_optional_floats(
config.get("ur_ik_nearest_weight"), "ur_ik_nearest_weight"
),
pytorch_num_samples=int(config.get("pytorch_num_samples", 30)),
init_pos=tuple(config.get("init_pos", DUAL_UR5_INIT_POS)),
init_rot=tuple(config.get("init_rot", DUAL_UR5_INIT_ROT)),
left_arm_home=_optional_floats(
config.get("left_arm_home"), "left_arm_home"
),
right_arm_home=_optional_floats(
config.get("right_arm_home"), "right_arm_home"
),
hand_stiffness=float(config.get("hand_stiffness", 1.0e3)),
hand_damping=float(config.get("hand_damping", 1.0e2)),
hand_max_effort=float(config.get("hand_max_effort", 1.0e4)),
)
register_robot_provider("dual_arm", DualArmProvider)