feat: 添加双臂 MuJoCo 运动学模型
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from xr_rm_mujoco.dual_arm_simulator import (
|
||||
ARM_JOINT_NAMES,
|
||||
DualArmKinematicModel,
|
||||
)
|
||||
|
||||
|
||||
SRC_DIR = Path(__file__).resolve().parents[2]
|
||||
URDF_PATH = (
|
||||
SRC_DIR / "xr_rm_teleop" / "models" / "dual_rm75" / "Dual_arm.urdf"
|
||||
)
|
||||
DUAL_CONFIG_PATH = SRC_DIR / "xr_rm_bringup" / "config" / "dual_arm_rm75.yaml"
|
||||
|
||||
|
||||
def test_dual_urdf_loads_with_expected_joint_mapping() -> None:
|
||||
simulation = DualArmKinematicModel(str(URDF_PATH))
|
||||
|
||||
assert simulation.model.nq == 14
|
||||
assert simulation.model.nv == 14
|
||||
assert ARM_JOINT_NAMES == {
|
||||
"left": tuple(f"scissor_joint_{index}" for index in range(1, 8)),
|
||||
"right": tuple(f"omnipic_joint_{index}" for index in range(1, 8)),
|
||||
}
|
||||
assert not simulation.ready
|
||||
|
||||
|
||||
def test_joint_messages_are_mapped_by_name_not_array_order() -> None:
|
||||
simulation = DualArmKinematicModel(str(URDF_PATH))
|
||||
names = list(reversed(ARM_JOINT_NAMES["left"]))
|
||||
values = [float(index) / 10.0 for index in range(7)]
|
||||
|
||||
simulation.apply_arm_state("left", names, values)
|
||||
|
||||
by_name = dict(zip(names, values))
|
||||
assert simulation.joint_positions("left") == pytest.approx(
|
||||
[by_name[name] for name in ARM_JOINT_NAMES["left"]]
|
||||
)
|
||||
assert not simulation.ready
|
||||
|
||||
|
||||
def test_yaml_initial_poses_populate_both_arms() -> None:
|
||||
simulation = DualArmKinematicModel(str(URDF_PATH))
|
||||
with DUAL_CONFIG_PATH.open(encoding="utf-8") as stream:
|
||||
config = yaml.safe_load(stream)
|
||||
|
||||
for arm, node_name in (
|
||||
("left", "left_arm_teleop"),
|
||||
("right", "right_arm_teleop"),
|
||||
):
|
||||
degrees = config[node_name]["ros__parameters"]["initial_joint_pose"]
|
||||
radians = [math.radians(value) for value in degrees]
|
||||
simulation.apply_arm_state(arm, ARM_JOINT_NAMES[arm], radians)
|
||||
assert simulation.joint_positions(arm) == pytest.approx(radians)
|
||||
|
||||
assert simulation.ready
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("names", "positions", "match"),
|
||||
[
|
||||
(list(ARM_JOINT_NAMES["left"][:-1]), [0.0] * 6, "expected"),
|
||||
(list(ARM_JOINT_NAMES["left"]), [0.0] * 6, "same length"),
|
||||
(
|
||||
[ARM_JOINT_NAMES["left"][0]] * 7,
|
||||
[0.0] * 7,
|
||||
"unique",
|
||||
),
|
||||
(
|
||||
list(ARM_JOINT_NAMES["left"]),
|
||||
[0.0] * 6 + [math.nan],
|
||||
"finite",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_invalid_joint_state_is_rejected_without_partial_update(
|
||||
names: list[str],
|
||||
positions: list[float],
|
||||
match: str,
|
||||
) -> None:
|
||||
simulation = DualArmKinematicModel(str(URDF_PATH))
|
||||
valid = [0.1] * 7
|
||||
simulation.apply_arm_state("left", ARM_JOINT_NAMES["left"], valid)
|
||||
|
||||
with pytest.raises(ValueError, match=match):
|
||||
simulation.apply_arm_state("left", names, positions)
|
||||
|
||||
assert simulation.joint_positions("left") == pytest.approx(valid)
|
||||
Reference in New Issue
Block a user