From 039543a0f98838186543c569fe0d4e70dc679e82 Mon Sep 17 00:00:00 2001
From: Melvin Laux
Date: Thu, 12 Sep 2024 15:03:24 +0200
Subject: [PATCH 01/40] Disable mujoco gui by default
---
deformable_gym/envs/mujoco/base_mjenv.py | 2 +-
deformable_gym/envs/mujoco/grasp_env.py | 2 +-
deformable_gym/envs/mujoco/mia_grasp.py | 2 +-
deformable_gym/envs/mujoco/shadow_grasp.py | 2 +-
examples/mj_mia_example.py | 4 ++--
examples/mj_shadow_example.py | 2 --
6 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/deformable_gym/envs/mujoco/base_mjenv.py b/deformable_gym/envs/mujoco/base_mjenv.py
index 4a22784..dfe0573 100644
--- a/deformable_gym/envs/mujoco/base_mjenv.py
+++ b/deformable_gym/envs/mujoco/base_mjenv.py
@@ -61,7 +61,7 @@ def __init__(
obj_name: str,
observable_object_pos: bool = True,
max_sim_time: float = 5,
- gui: bool = True,
+ gui: bool = False,
init_frame: str | None = None,
):
self.scene = AssetManager().create_scene(robot_name, obj_name)
diff --git a/deformable_gym/envs/mujoco/grasp_env.py b/deformable_gym/envs/mujoco/grasp_env.py
index 047f162..e1b29ae 100644
--- a/deformable_gym/envs/mujoco/grasp_env.py
+++ b/deformable_gym/envs/mujoco/grasp_env.py
@@ -20,7 +20,7 @@ def __init__(
obj_name: str,
observable_object_pos: bool = True,
max_sim_time: float = 5,
- gui: bool = True,
+ gui: bool = False,
init_frame: Optional[str] = None,
**kwargs,
):
diff --git a/deformable_gym/envs/mujoco/mia_grasp.py b/deformable_gym/envs/mujoco/mia_grasp.py
index 448b373..7753eed 100644
--- a/deformable_gym/envs/mujoco/mia_grasp.py
+++ b/deformable_gym/envs/mujoco/mia_grasp.py
@@ -9,7 +9,7 @@ def __init__(
obj_name: str,
observable_object_pos: bool = True,
max_sim_time: float = 1,
- gui: bool = True,
+ gui: bool = False,
init_frame: str | None = None,
**kwargs,
):
diff --git a/deformable_gym/envs/mujoco/shadow_grasp.py b/deformable_gym/envs/mujoco/shadow_grasp.py
index 72e5c53..bcd94a0 100644
--- a/deformable_gym/envs/mujoco/shadow_grasp.py
+++ b/deformable_gym/envs/mujoco/shadow_grasp.py
@@ -10,7 +10,7 @@ def __init__(
obj_name: str,
observable_object_pos: bool = True,
max_sim_time: float = 1,
- gui: bool = True,
+ gui: bool = False,
init_frame: str | None = None,
**kwargs,
):
diff --git a/examples/mj_mia_example.py b/examples/mj_mia_example.py
index 1a5d57d..619f1b3 100644
--- a/examples/mj_mia_example.py
+++ b/examples/mj_mia_example.py
@@ -7,11 +7,11 @@
episode = 0
n_episodes = 3
observation, info = env.reset()
- env.render()
+ #env.render()
while episode < n_episodes:
action = env.action_space.sample()
observation, reward, terminate, truncated, info = env.step(action)
- env.render()
+ #env.render()
if terminate:
episode += 1
print(f"Episode {episode} finished with reward {reward}")
diff --git a/examples/mj_shadow_example.py b/examples/mj_shadow_example.py
index d77fc5f..cc21c92 100644
--- a/examples/mj_shadow_example.py
+++ b/examples/mj_shadow_example.py
@@ -7,11 +7,9 @@
episode = 0
n_episodes = 3
observation, info = env.reset()
- env.render()
while episode < n_episodes:
action = env.action_space.sample()
observation, reward, terminate, truncated, info = env.step(action)
- env.render()
if terminate:
episode += 1
print(f"Episode {episode} finished with reward {reward}")
From f5079975f92ece5af6d83780839f21492ef50434 Mon Sep 17 00:00:00 2001
From: Melvin Laux
Date: Thu, 12 Sep 2024 15:37:31 +0200
Subject: [PATCH 02/40] Add separate requirements for mujoco and dev
installations
---
setup.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 85eab90..68f79ae 100644
--- a/setup.py
+++ b/setup.py
@@ -19,6 +19,11 @@
"gymnasium",
"numpy>=1.23.5,<2.0.0",
"pytransform3d",
- "mujoco==3.1.6",
],
+ extras_require={
+ "mujoco": [
+ "mujoco==3.1.6",
+ ],
+ "dev": ["pytest", "pre-commit"],
+ },
)
From 5dd7be4dcbe479267fdc57e1a98820fc103e8c60 Mon Sep 17 00:00:00 2001
From: Melvin Laux
Date: Thu, 12 Sep 2024 16:00:50 +0200
Subject: [PATCH 03/40] Add flake8 as dev dependency
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 68f79ae..ca5bf3f 100644
--- a/setup.py
+++ b/setup.py
@@ -24,6 +24,6 @@
"mujoco": [
"mujoco==3.1.6",
],
- "dev": ["pytest", "pre-commit"],
+ "dev": ["pytest", "pre-commit", "flake8"],
},
)
From 3873659ad05da9023b0633862b617d5751096c10 Mon Sep 17 00:00:00 2001
From: Melvin Laux
Date: Thu, 12 Sep 2024 16:01:37 +0200
Subject: [PATCH 04/40] Update pipline
---
.github/workflows/test.yaml | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index b304c3b..0b87bb0 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -27,8 +27,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install flake8 pytest
- pip install . --user
+ pip install .[dev,mujoco] --user
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
From f0c9defe447e44daae794033c3de6d6f73d722f8 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 10 Sep 2024 20:45:32 +0200
Subject: [PATCH 05/40] Change relative body of equality constraints
---
assets/objects/mjcf/insole_fixed.xml | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/assets/objects/mjcf/insole_fixed.xml b/assets/objects/mjcf/insole_fixed.xml
index d953b60..571a4bf 100644
--- a/assets/objects/mjcf/insole_fixed.xml
+++ b/assets/objects/mjcf/insole_fixed.xml
@@ -5,7 +5,7 @@
-
+
@@ -17,13 +17,13 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
From 452dfeda11c8bfe25b3fa9c869fce24f9c64f007 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 10 Sep 2024 20:46:02 +0200
Subject: [PATCH 06/40] Remove initial position
---
assets/objects/mjcf/pillow_fixed.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/objects/mjcf/pillow_fixed.xml b/assets/objects/mjcf/pillow_fixed.xml
index 7858dbf..2afe79d 100644
--- a/assets/objects/mjcf/pillow_fixed.xml
+++ b/assets/objects/mjcf/pillow_fixed.xml
@@ -5,7 +5,7 @@
-
+
From 702589e5eef7b75889b07b7676badf4d094af977 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 10 Sep 2024 20:47:38 +0200
Subject: [PATCH 07/40] Add default position and orientation for pose
---
deformable_gym/helpers/mj_utils.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/deformable_gym/helpers/mj_utils.py b/deformable_gym/helpers/mj_utils.py
index c0ac2f5..fb4242e 100644
--- a/deformable_gym/helpers/mj_utils.py
+++ b/deformable_gym/helpers/mj_utils.py
@@ -1,4 +1,4 @@
-from dataclasses import dataclass
+from dataclasses import dataclass, field
from typing import List, Tuple
import mujoco
@@ -38,8 +38,10 @@
# -------------------------------- DATA CLASSES --------------------------------#
@dataclass
class Pose:
- position: ArrayLike
- orientation: ArrayLike
+ position: ArrayLike = field(default_factory=lambda: np.zeros(3))
+ orientation: ArrayLike = field(
+ default_factory=lambda: np.array([1, 0, 0, 0])
+ )
def __post_init__(self):
assert len(self.position) == 3, "position should be in shape (3,)."
From 07635ca5eb2b54579021e10e938bb30a71c7184e Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 10 Sep 2024 20:50:44 +0200
Subject: [PATCH 08/40] Refactor asset manager into modules
---
deformable_gym/envs/mujoco/base_mjenv.py | 9 +-
deformable_gym/helpers/asset_manager.py | 242 +++++++++++------------
2 files changed, 116 insertions(+), 135 deletions(-)
diff --git a/deformable_gym/envs/mujoco/base_mjenv.py b/deformable_gym/envs/mujoco/base_mjenv.py
index dfe0573..cdeacae 100644
--- a/deformable_gym/envs/mujoco/base_mjenv.py
+++ b/deformable_gym/envs/mujoco/base_mjenv.py
@@ -10,8 +10,8 @@
from gymnasium import spaces
from numpy.typing import ArrayLike, NDArray
+from ...helpers import asset_manager as am
from ...helpers import mj_utils as mju
-from ...helpers.asset_manager import AssetManager
from ...objects.mj_object import ObjectFactory
from ...robots.mj_robot import RobotFactory
@@ -64,7 +64,7 @@ def __init__(
gui: bool = False,
init_frame: str | None = None,
):
- self.scene = AssetManager().create_scene(robot_name, obj_name)
+ self.scene = am.create_scene(robot_name, obj_name)
self.model, self.data = mju.load_model_from_string(self.scene)
self.robot = RobotFactory.create(robot_name)
self.object = ObjectFactory.create(obj_name)
@@ -133,7 +133,10 @@ def reset(
self.model, _ = mju.load_model_from_string(self.scene)
mujoco.mj_resetData(self.model, self.data)
self.robot.set_pose(
- self.model, self.data, self.robot.init_pose[self.object.name]
+ self.model, self.data, self.robot.init_pose.get(self.object.name)
+ )
+ self.object.set_pose(
+ self.model, self.data, self.object.init_pose.get(self.robot.name)
)
if self.gui and self.viewer is None:
self.viewer = mujoco.viewer.launch_passive(
diff --git a/deformable_gym/helpers/asset_manager.py b/deformable_gym/helpers/asset_manager.py
index 22e2f5a..75ea8fd 100644
--- a/deformable_gym/helpers/asset_manager.py
+++ b/deformable_gym/helpers/asset_manager.py
@@ -7,146 +7,124 @@
import mujoco
+ASSETS_DIR = Path(__file__).parents[2] / "assets"
+MESH_DIR = os.path.join(ASSETS_DIR, "meshes")
+ROBOT_DIR = os.path.join(ASSETS_DIR, "robots", "mjcf")
+OBJECT_DIR = os.path.join(ASSETS_DIR, "objects", "mjcf")
+
ROBOTS = {
- "shadow_hand": "shadow_hand.xml",
- "mia_hand": "mia_hand.xml",
+ "shadow_hand": os.path.join(ROBOT_DIR, "shadow_hand.xml"),
+ "mia_hand": os.path.join(ROBOT_DIR, "mia_hand.xml"),
+ "ur5_mia": os.path.join(ROBOT_DIR, "mia_hand_on_ur5.xml"),
+ "ur10_mia": os.path.join(ROBOT_DIR, "mia_hand_on_ur10.xml"),
+ "ur10ft_mia": os.path.join(ROBOT_DIR, "mia_hand_on_ur10_ft.xml"),
+ "ur10e_mia": os.path.join(ROBOT_DIR, "mia_hand_on_ur10e.xml"),
+ "ur5_shadow": os.path.join(ROBOT_DIR, "shadow_hand_on_ur5.xml"),
+ "ur10_shadow": os.path.join(ROBOT_DIR, "shadow_hand_on_ur10.xml"),
+ "ur10e_shadow": os.path.join(ROBOT_DIR, "shadow_hand_on_ur10e.xml"),
}
OBJECTS = {
- "insole_fixed": "insole_fixed.xml",
- "pillow_fixed": "pillow_fixed.xml",
+ "insole_fixed": os.path.join(OBJECT_DIR, "insole_fixed.xml"),
+ "pillow_fixed": os.path.join(OBJECT_DIR, "pillow_fixed.xml"),
}
-class AssetManager:
+def load_asset(name: str) -> mujoco.MjModel:
+ """
+ Loads a asset (robot or object mujoco Model) based on its name.
+
+ Args:
+ name (str): The name of the robot or object to be loaded. Must be a key in either
+ ROBOTS or OBJECTS.
+
+ Returns:
+ mujoco.MjModel: The loaded MuJoCo model.
+
+ Raises:
+ AssertionError: If the specified name is not found in either ROBOTS or OBJECTS.
"""
- The AssetManager class manages the loading and combining of MuJoCo models (robots and objects)
- into a single simulation scene. It provides methods to load individual robot or object models,
- create complex scenes, and save the resulting XML configuration.
+ assert (
+ name in ROBOTS or name in OBJECTS
+ ), f"Model {name} not found.\n available: {list(ROBOTS.keys()) + list(OBJECTS.keys())}"
+ model_path = ROBOTS.get(name, OBJECTS.get(name))
+ model = mujoco.MjModel.from_xml_path(model_path)
+ return model
+
+
+def create_scene(robot_name: str, obj_name: str) -> str:
"""
+ Creates an MJCF string representing a MuJoCo scene that includes a robot and an object.
+
+ Args:
+ robot_name (str): The name of the robot to include in the scene. Must be a key in ROBOTS.
+ obj_name (str): The name of the object to include in the scene. Must be a key in OBJECTS.
- def __init__(self) -> None:
- self.assets_dir = Path(__file__).parents[2] / "assets"
- self.meshdir = os.path.join(self.assets_dir, "meshes")
- self.robot_dir = os.path.join(self.assets_dir, "robots", "mjcf")
- self.object_dir = os.path.join(self.assets_dir, "objects", "mjcf")
- self.robots = {
- k: os.path.join(self.robot_dir, v) for k, v in ROBOTS.items()
- }
- self.objects = {
- k: os.path.join(self.object_dir, v) for k, v in OBJECTS.items()
- }
-
- def load_asset(self, name: str) -> mujoco.MjModel:
- """
- Loads a asset (robot or object mujoco Model) based on its name.
-
- Args:
- name (str): The name of the robot or object to be loaded. Must be a key in either
- self.robots or self.objects.
-
- Returns:
- mujoco.MjModel: The loaded MuJoCo model.
-
- Raises:
- AssertionError: If the specified name is not found in either self.robots or self.objects.
- """
- assert (
- name in self.robots or name in self.objects
- ), f"Model {name} not found.\n available: {list(self.robots.keys()) + list(self.objects.keys())}"
- model_path = self.robots.get(name, self.objects.get(name))
- model = mujoco.MjModel.from_xml_path(model_path)
- return model
-
- def create_scene(self, robot_name: str, obj_name: str) -> str:
- """
- Creates an MJCF string representing a MuJoCo scene that includes a robot and an object.
-
- Args:
- robot_name (str): The name of the robot to include in the scene. Must be a key in self.robots.
- obj_name (str): The name of the object to include in the scene. Must be a key in self.objects.
-
- Returns:
- str: A MJCF string representing the combined MuJoCo scene.
-
- Raises:
- AssertionError: If the specified robot_name is not found in self.robots.
- AssertionError: If the specified obj_name is not found in self.objects.
- """
- assert (
- robot_name in self.robots
- ), f"Robot {robot_name} not found.\n available: {list(self.robots.keys())}"
- assert (
- obj_name in self.objects
- ), f"Object {obj_name} not found.\n available: {list(self.objects.keys())}"
-
- floor_path = os.path.join(self.object_dir, "floor.xml")
- robot_path = self.robots.get(robot_name)
- obj_path = self.objects.get(obj_name)
-
- scene = self.include_mjcf(
- obj_path, [robot_path, floor_path], meshdir=self.meshdir
- )
- return scene
-
- # def create_scene(self, robot_name: str, *obj_name: str) -> str:
- # assert (
- # robot_name in self.robots
- # ), f"Robot {robot_name} not found.\n available: {list(self.robots.keys())}"
-
- # robot_path = self._get_full_path(self.robots[robot_name])
-
- # for obj in obj_name:
- # assert (
- # obj in self.objects
- # ), f"Object {obj} not found.\n available: {list(self.objects.keys())}"
- # obj_path = [self._get_full_path(self.objects[obj]) for obj in obj_name]
- # scene = self.include_mjcf(robot_path, obj_path, meshdir=self.meshdir)
- # return scene
-
- @staticmethod
- def include_mjcf(
- base_path: str,
- include_path: str | Sequence[str],
- *,
- meshdir: str | None = None,
- ) -> str:
- """
- Generates an XML string for a MuJoCo scene by including additional MJCF files within a base MJCF file.
-
- Args:
- base_path (str): The file path to the base MJCF file.
- include_path (Union[str, Sequence[str]]): A string or list of strings representing file paths
- to MJCF files to be included in the base file.
- meshdir (Optional[str]): A string representing the path to the directory containing mesh files.
- If provided, this path is added to the meshdir attribute of the compiler
- element in the MJCF XML.
-
- Returns:
- str: An XML string representing the combined MJCF file.
- """
- tree = ET.parse(base_path)
- root = tree.getroot()
- if isinstance(include_path, list) or isinstance(include_path, tuple):
- for path in include_path:
- new_elem = ET.Element("include", {"file": path})
- root.insert(0, new_elem)
+ Returns:
+ str: A MJCF string representing the combined MuJoCo scene.
+
+ Raises:
+ AssertionError: If the specified robot_name is not found in ROBOTS.
+ AssertionError: If the specified obj_name is not found in OBJECTS.
+ """
+ assert (
+ robot_name in ROBOTS
+ ), f"Robot {robot_name} not found.\n available: {list(ROBOTS.keys())}"
+ assert (
+ obj_name in OBJECTS
+ ), f"Object {obj_name} not found.\n available: {list(OBJECTS.keys())}"
+
+ floor_path = os.path.join(OBJECT_DIR, "floor.xml")
+ robot_path = ROBOTS.get(robot_name)
+ obj_path = OBJECTS.get(obj_name)
+
+ scene = include_mjcf(obj_path, [robot_path, floor_path], meshdir=MESH_DIR)
+ return scene
+
+
+def include_mjcf(
+ base_path: str,
+ include_path: str | Sequence[str],
+ *,
+ meshdir: str | None = None,
+) -> str:
+ """
+ Generates an XML string for a MuJoCo scene by including additional MJCF files within a base MJCF file.
+
+ Args:
+ base_path (str): The file path to the base MJCF file.
+ include_path (Union[str, Sequence[str]]): A string or list of strings representing file paths
+ to MJCF files to be included in the base file.
+ meshdir (Optional[str]): A string representing the path to the directory containing mesh files.
+ If provided, this path is added to the meshdir attribute of the compiler
+ element in the MJCF XML.
+
+ Returns:
+ str: An XML string representing the combined MJCF file.
+ """
+ tree = ET.parse(base_path)
+ root = tree.getroot()
+ if isinstance(include_path, list) or isinstance(include_path, tuple):
+ for path in include_path:
+ new_elem = ET.Element("include", {"file": path})
+ root.insert(0, new_elem)
+ else:
+ new_elem = ET.Element("include", {"file": include_path})
+ root.insert(0, new_elem)
+ if meshdir is not None:
+ if meshdir[-1] != "/":
+ meshdir += "/"
+ elems = root.findall("compiler")
+ if len(elems) != 0:
+ for elem in elems:
+ elem.set("meshdir", meshdir)
else:
- new_elem = ET.Element("include", {"file": include_path})
+ new_elem = ET.Element("compiler", {"meshdir": meshdir})
root.insert(0, new_elem)
- if meshdir is not None:
- if meshdir[-1] != "/":
- meshdir += "/"
- elems = root.findall("compiler")
- if len(elems) != 0:
- for elem in elems:
- elem.set("meshdir", meshdir)
- else:
- new_elem = ET.Element("compiler", {"meshdir": meshdir})
- root.insert(0, new_elem)
- new_mjcf = ET.tostring(root, encoding="utf-8").decode("utf-8")
-
- return new_mjcf
-
- def save_mjcf_string(self, mjcf: str, path: str) -> None:
- with open(path, "w") as f:
- f.write(mjcf)
+ new_mjcf = ET.tostring(root, encoding="utf-8").decode("utf-8")
+
+ return new_mjcf
+
+
+def save_mjcf_string(mjcf: str, path: str) -> None:
+ with open(path, "w") as f:
+ f.write(mjcf)
From 299e1cb4f2e16af97fec8265f48fe84904e3eedb Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 10 Sep 2024 20:51:27 +0200
Subject: [PATCH 09/40] Add new robots
---
deformable_gym/robots/mj_robot.py | 142 ++++++++++++++++++++++++++----
1 file changed, 123 insertions(+), 19 deletions(-)
diff --git a/deformable_gym/robots/mj_robot.py b/deformable_gym/robots/mj_robot.py
index 9a04af5..1c52a6e 100644
--- a/deformable_gym/robots/mj_robot.py
+++ b/deformable_gym/robots/mj_robot.py
@@ -5,8 +5,8 @@
import numpy as np
from numpy.typing import ArrayLike, NDArray
+from ..helpers import asset_manager as am
from ..helpers import mj_utils as mju
-from ..helpers.asset_manager import AssetManager
from ..helpers.mj_utils import Pose
@@ -30,17 +30,32 @@ class MJRobot(ABC):
actuators (List[str]): A list of actuator names for the robot.
"""
- init_pos = {}
+ init_pose = {}
def __init__(self, name: str) -> None:
self.name = name
- self.model = AssetManager().load_asset(self.name)
- self.n_qpos = self.model.nq
- self.dof = self.model.nv
- self.n_actuator = self.model.nu
- self.joint_range = self.model.jnt_range.copy()
- self.ctrl_range = self.model.actuator_ctrlrange.copy()
+ self.model = am.load_asset(self.name)
+
+ @property
+ def n_qpos(self) -> int:
+ return self.model.nq
+
+ @property
+ def dof(self) -> int:
+ return self.model.nv
+
+ @property
+ def n_actuator(self) -> int:
+ return self.model.nu
+
+ @property
+ def joint_range(self) -> NDArray:
+ return self.model.jnt_range.copy()
+
+ @property
+ def ctrl_range(self) -> NDArray:
+ return self.model.actuator_ctrlrange.copy()
@property
def joints(self) -> List[str]:
@@ -82,7 +97,7 @@ def set_pose(
self,
model: mujoco.MjModel,
data: mujoco.MjData,
- pose: Pose,
+ pose: Pose | None,
) -> None:
"""
Set the pose (position and orientation) of the robot's base.
@@ -95,10 +110,10 @@ def set_pose(
data (mujoco.MjData): The MuJoCo data object containing the current simulation state.
pose (Pose): A Pose object containing the desired position and orientation for the robot's base.
"""
-
- model.body(self.name).pos[:] = pose.position
- model.body(self.name).quat[:] = pose.orientation
- mujoco.mj_forward(model, data)
+ if pose is not None:
+ model.body(self.name).pos[:] = pose.position
+ model.body(self.name).quat[:] = pose.orientation
+ mujoco.mj_forward(model, data)
def set_ctrl(
self, model: mujoco.MjModel, data: mujoco.MjData, ctrl: ArrayLike
@@ -131,8 +146,38 @@ class ShadowHand(MJRobot):
"insole_fixed": Pose([-0.35, 0.00, 0.49], [0, np.pi / 2, 0]),
}
- def __init__(self) -> None:
- super().__init__("shadow_hand")
+ def __init__(self, name: str = "shadow_hand") -> None:
+ super().__init__(name)
+
+
+class Ur5Shadow(ShadowHand):
+ init_pose = {
+ "insole_fixed": Pose([0.0, 0.0, 0.0]),
+ "pillow_fixed": Pose([0.0, 0.0, 0.0]),
+ }
+
+ def __init__(self, name: str = "ur5_shadow"):
+ super().__init__(name)
+
+
+class Ur10Shadow(ShadowHand):
+ init_pose = {
+ "insole_fixed": Pose([0.0, 0.0, 0.0]),
+ "pillow_fixed": Pose([0.0, 0.0, 0.0]),
+ }
+
+ def __init__(self, name: str = "ur10_shadow"):
+ super().__init__(name)
+
+
+class Ur10eShadow(ShadowHand):
+ init_pose = {
+ "insole_fixed": Pose([0.0, 0.0, 0.0]),
+ "pillow_fixed": Pose([0.0, 0.0, 0.0]),
+ }
+
+ def __init__(self, name: str = "ur10e_shadow"):
+ super().__init__(name)
class MiaHand(MJRobot):
@@ -147,10 +192,15 @@ class MiaHand(MJRobot):
"insole_fixed": Pose([-0.1, 0, 0.49], [0, np.pi, np.pi / 2]),
}
- def __init__(self) -> None:
- super().__init__("mia_hand")
- self.n_actuator = self.model.nu - 2
+ def __init__(self, name: str = "mia_hand") -> None:
+ super().__init__(name)
+
+ @property
+ def n_actuator(self):
+ return self.model.nu - 2
+ @property
+ def ctrl_range(self):
mrl_range = self.model.actuator("j_middle_fle_A").ctrlrange
ctrl_range_wo_mrl = np.array(
[
@@ -159,7 +209,7 @@ def __init__(self) -> None:
if name not in self.mrl_actuators
]
)
- self.ctrl_range = np.vstack((ctrl_range_wo_mrl, mrl_range))
+ return np.vstack((ctrl_range_wo_mrl, mrl_range))
@property
def actuators(self):
@@ -187,13 +237,67 @@ def set_ctrl(
mju.set_actuator_ctrl(model, data, act, ctrl[i])
+class Ur5Mia(MiaHand):
+ init_pose = {
+ "insole_fixed": Pose([0.0, 0.0, 0.0]),
+ "pillow_fixed": Pose([0.0, 0.0, 0.0]),
+ }
+
+ def __init__(self, name: str = "ur5_mia"):
+ super().__init__(name)
+
+
+class Ur10Mia(MiaHand):
+ init_pose = {
+ "insole_fixed": Pose([0.0, 0.0, 0.0]),
+ "pillow_fixed": Pose([0.0, 0.0, 0.0]),
+ }
+
+ def __init__(self, name: str = "ur10_mia"):
+ super().__init__(name)
+
+
+class Ur10ftMia(MiaHand):
+ init_pose = {
+ "insole_fixed": Pose([0.0, 0.0, 0.0]),
+ "pillow_fixed": Pose([0.0, 0.0, 0.0]),
+ }
+
+ def __init__(self, name: str = "ur10ft_mia"):
+ super().__init__(name)
+
+
+class Ur10eMia(MiaHand):
+ init_pose = {
+ "insole_fixed": Pose([0.0, 0.0, 0.0]),
+ "pillow_fixed": Pose([0.0, 0.0, 0.0]),
+ }
+
+ def __init__(self, name: str = "ur10e_mia"):
+ super().__init__(name)
+
+
class RobotFactory:
@staticmethod
def create(name: str) -> MJRobot:
if name == "shadow_hand":
return ShadowHand()
+ elif name == "ur5_shadow":
+ return Ur5Shadow()
+ elif name == "ur10_shadow":
+ return Ur10Shadow()
+ elif name == "ur10e_shadow":
+ return Ur10eShadow()
elif name == "mia_hand":
return MiaHand()
+ elif name == "ur5_mia":
+ return Ur5Mia()
+ elif name == "ur10_mia":
+ return Ur10Mia()
+ elif name == "ur10ft_mia":
+ return Ur10ftMia()
+ elif name == "ur10e_mia":
+ return Ur10eMia()
else:
raise ValueError(f"Robot {name} not found.")
From 19a85e9b9f63b5ab97d2f47cde2b3090607734ee Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 17 Sep 2024 12:40:03 +0200
Subject: [PATCH 10/40] Support mocap body control
---
assets/robots/mjcf/include/asset/mia_hand.xml | 19 ++
.../robots/mjcf/include/asset/shadow_hand.xml | 20 ++
assets/robots/mjcf/include/asset/ur10.xml | 11 +
assets/robots/mjcf/include/asset/ur10e.xml | 11 +
assets/robots/mjcf/include/asset/ur10ft.xml | 13 +
assets/robots/mjcf/include/asset/ur5.xml | 11 +
.../robots/mjcf/include/default/mia_hand.xml | 16 ++
.../mjcf/include/default/shadow_hand.xml | 66 +++++
assets/robots/mjcf/include/default/ur10.xml | 21 ++
assets/robots/mjcf/include/default/ur10e.xml | 21 ++
assets/robots/mjcf/include/default/ur10ft.xml | 21 ++
assets/robots/mjcf/include/default/ur5.xml | 21 ++
.../mjcf/include/hand/mia_hand_component.xml | 60 +++++
.../mjcf/include/hand/mia_hand_misc.xml | 9 +
.../include/hand/shadow_hand_component.xml | 161 +++++++++++
.../mjcf/include/hand/shadow_hand_misc.xml | 44 +++
assets/robots/mjcf/mia_hand.xml | 90 +++----
assets/robots/mjcf/mia_hand_on_ur10.xml | 193 +++++---------
assets/robots/mjcf/mia_hand_on_ur10_ft.xml | 207 ++++++--------
assets/robots/mjcf/mia_hand_on_ur10e.xml | 195 +++++---------
assets/robots/mjcf/mia_hand_on_ur5.xml | 191 +++++--------
assets/robots/mjcf/mocap.xml | 7 +
assets/robots/mjcf/shadow_hand.xml | 252 +++++++++---------
assets/robots/mjcf/shadow_hand_on_ur10.xml | 68 +++++
assets/robots/mjcf/shadow_hand_on_ur10e.xml | 68 +++++
assets/robots/mjcf/shadow_hand_on_ur5.xml | 68 +++++
assets/robots/mjcf/ur10.xml | 57 ++++
assets/robots/mjcf/ur10e.xml | 57 ++++
assets/robots/mjcf/ur10ft.xml | 61 +++++
assets/robots/mjcf/ur5.xml | 57 ++++
30 files changed, 1411 insertions(+), 685 deletions(-)
create mode 100644 assets/robots/mjcf/include/asset/mia_hand.xml
create mode 100644 assets/robots/mjcf/include/asset/shadow_hand.xml
create mode 100644 assets/robots/mjcf/include/asset/ur10.xml
create mode 100644 assets/robots/mjcf/include/asset/ur10e.xml
create mode 100644 assets/robots/mjcf/include/asset/ur10ft.xml
create mode 100644 assets/robots/mjcf/include/asset/ur5.xml
create mode 100644 assets/robots/mjcf/include/default/mia_hand.xml
create mode 100644 assets/robots/mjcf/include/default/shadow_hand.xml
create mode 100644 assets/robots/mjcf/include/default/ur10.xml
create mode 100644 assets/robots/mjcf/include/default/ur10e.xml
create mode 100644 assets/robots/mjcf/include/default/ur10ft.xml
create mode 100644 assets/robots/mjcf/include/default/ur5.xml
create mode 100644 assets/robots/mjcf/include/hand/mia_hand_component.xml
create mode 100644 assets/robots/mjcf/include/hand/mia_hand_misc.xml
create mode 100644 assets/robots/mjcf/include/hand/shadow_hand_component.xml
create mode 100644 assets/robots/mjcf/include/hand/shadow_hand_misc.xml
create mode 100644 assets/robots/mjcf/mocap.xml
create mode 100644 assets/robots/mjcf/shadow_hand_on_ur10.xml
create mode 100644 assets/robots/mjcf/shadow_hand_on_ur10e.xml
create mode 100644 assets/robots/mjcf/shadow_hand_on_ur5.xml
create mode 100644 assets/robots/mjcf/ur10.xml
create mode 100644 assets/robots/mjcf/ur10e.xml
create mode 100644 assets/robots/mjcf/ur10ft.xml
create mode 100644 assets/robots/mjcf/ur5.xml
diff --git a/assets/robots/mjcf/include/asset/mia_hand.xml b/assets/robots/mjcf/include/asset/mia_hand.xml
new file mode 100644
index 0000000..c8f6d8a
--- /dev/null
+++ b/assets/robots/mjcf/include/asset/mia_hand.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/asset/shadow_hand.xml b/assets/robots/mjcf/include/asset/shadow_hand.xml
new file mode 100644
index 0000000..a9d1d32
--- /dev/null
+++ b/assets/robots/mjcf/include/asset/shadow_hand.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/asset/ur10.xml b/assets/robots/mjcf/include/asset/ur10.xml
new file mode 100644
index 0000000..bd70a29
--- /dev/null
+++ b/assets/robots/mjcf/include/asset/ur10.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/asset/ur10e.xml b/assets/robots/mjcf/include/asset/ur10e.xml
new file mode 100644
index 0000000..aae1808
--- /dev/null
+++ b/assets/robots/mjcf/include/asset/ur10e.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/asset/ur10ft.xml b/assets/robots/mjcf/include/asset/ur10ft.xml
new file mode 100644
index 0000000..28c0506
--- /dev/null
+++ b/assets/robots/mjcf/include/asset/ur10ft.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/asset/ur5.xml b/assets/robots/mjcf/include/asset/ur5.xml
new file mode 100644
index 0000000..6a67171
--- /dev/null
+++ b/assets/robots/mjcf/include/asset/ur5.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/default/mia_hand.xml b/assets/robots/mjcf/include/default/mia_hand.xml
new file mode 100644
index 0000000..40d292c
--- /dev/null
+++ b/assets/robots/mjcf/include/default/mia_hand.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/default/shadow_hand.xml b/assets/robots/mjcf/include/default/shadow_hand.xml
new file mode 100644
index 0000000..1dedc98
--- /dev/null
+++ b/assets/robots/mjcf/include/default/shadow_hand.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/default/ur10.xml b/assets/robots/mjcf/include/default/ur10.xml
new file mode 100644
index 0000000..23d3165
--- /dev/null
+++ b/assets/robots/mjcf/include/default/ur10.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/default/ur10e.xml b/assets/robots/mjcf/include/default/ur10e.xml
new file mode 100644
index 0000000..4367a17
--- /dev/null
+++ b/assets/robots/mjcf/include/default/ur10e.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/default/ur10ft.xml b/assets/robots/mjcf/include/default/ur10ft.xml
new file mode 100644
index 0000000..36dc77b
--- /dev/null
+++ b/assets/robots/mjcf/include/default/ur10ft.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/default/ur5.xml b/assets/robots/mjcf/include/default/ur5.xml
new file mode 100644
index 0000000..68326d5
--- /dev/null
+++ b/assets/robots/mjcf/include/default/ur5.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/hand/mia_hand_component.xml b/assets/robots/mjcf/include/hand/mia_hand_component.xml
new file mode 100644
index 0000000..b1303ce
--- /dev/null
+++ b/assets/robots/mjcf/include/hand/mia_hand_component.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/hand/mia_hand_misc.xml b/assets/robots/mjcf/include/hand/mia_hand_misc.xml
new file mode 100644
index 0000000..90b6176
--- /dev/null
+++ b/assets/robots/mjcf/include/hand/mia_hand_misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/hand/shadow_hand_component.xml b/assets/robots/mjcf/include/hand/shadow_hand_component.xml
new file mode 100644
index 0000000..5b32c32
--- /dev/null
+++ b/assets/robots/mjcf/include/hand/shadow_hand_component.xml
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/include/hand/shadow_hand_misc.xml b/assets/robots/mjcf/include/hand/shadow_hand_misc.xml
new file mode 100644
index 0000000..4e300a9
--- /dev/null
+++ b/assets/robots/mjcf/include/hand/shadow_hand_misc.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/mia_hand.xml b/assets/robots/mjcf/mia_hand.xml
index 3ecc694..1c2a020 100644
--- a/assets/robots/mjcf/mia_hand.xml
+++ b/assets/robots/mjcf/mia_hand.xml
@@ -1,65 +1,49 @@
+
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -119,17 +103,15 @@
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/mia_hand_on_ur10.xml b/assets/robots/mjcf/mia_hand_on_ur10.xml
index e94206f..515f311 100644
--- a/assets/robots/mjcf/mia_hand_on_ur10.xml
+++ b/assets/robots/mjcf/mia_hand_on_ur10.xml
@@ -1,127 +1,70 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/mia_hand_on_ur10_ft.xml b/assets/robots/mjcf/mia_hand_on_ur10_ft.xml
index 3d34afe..0674667 100644
--- a/assets/robots/mjcf/mia_hand_on_ur10_ft.xml
+++ b/assets/robots/mjcf/mia_hand_on_ur10_ft.xml
@@ -1,133 +1,76 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/mia_hand_on_ur10e.xml b/assets/robots/mjcf/mia_hand_on_ur10e.xml
index 01a0800..df1dfa2 100644
--- a/assets/robots/mjcf/mia_hand_on_ur10e.xml
+++ b/assets/robots/mjcf/mia_hand_on_ur10e.xml
@@ -1,127 +1,72 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/mia_hand_on_ur5.xml b/assets/robots/mjcf/mia_hand_on_ur5.xml
index a45bbe6..0f5d136 100644
--- a/assets/robots/mjcf/mia_hand_on_ur5.xml
+++ b/assets/robots/mjcf/mia_hand_on_ur5.xml
@@ -1,127 +1,68 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/mocap.xml b/assets/robots/mjcf/mocap.xml
new file mode 100644
index 0000000..d0ec6c4
--- /dev/null
+++ b/assets/robots/mjcf/mocap.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/shadow_hand.xml b/assets/robots/mjcf/shadow_hand.xml
index 5a5448d..3eda9f2 100644
--- a/assets/robots/mjcf/shadow_hand.xml
+++ b/assets/robots/mjcf/shadow_hand.xml
@@ -1,9 +1,9 @@
-
+
-
+
@@ -18,28 +18,27 @@
-
+
-
+
-
+
-
+
-
-
+
-
+
@@ -49,15 +48,15 @@
-
+
-
+
-
+
@@ -100,149 +99,151 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
-
-
-
+
+
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -250,28 +251,28 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -283,49 +284,52 @@
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/shadow_hand_on_ur10.xml b/assets/robots/mjcf/shadow_hand_on_ur10.xml
new file mode 100644
index 0000000..c1f0f91
--- /dev/null
+++ b/assets/robots/mjcf/shadow_hand_on_ur10.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/shadow_hand_on_ur10e.xml b/assets/robots/mjcf/shadow_hand_on_ur10e.xml
new file mode 100644
index 0000000..5ca21c4
--- /dev/null
+++ b/assets/robots/mjcf/shadow_hand_on_ur10e.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/shadow_hand_on_ur5.xml b/assets/robots/mjcf/shadow_hand_on_ur5.xml
new file mode 100644
index 0000000..6946a31
--- /dev/null
+++ b/assets/robots/mjcf/shadow_hand_on_ur5.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/ur10.xml b/assets/robots/mjcf/ur10.xml
new file mode 100644
index 0000000..7036233
--- /dev/null
+++ b/assets/robots/mjcf/ur10.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/ur10e.xml b/assets/robots/mjcf/ur10e.xml
new file mode 100644
index 0000000..9a88a2e
--- /dev/null
+++ b/assets/robots/mjcf/ur10e.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/ur10ft.xml b/assets/robots/mjcf/ur10ft.xml
new file mode 100644
index 0000000..3780e86
--- /dev/null
+++ b/assets/robots/mjcf/ur10ft.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/robots/mjcf/ur5.xml b/assets/robots/mjcf/ur5.xml
new file mode 100644
index 0000000..6feb47d
--- /dev/null
+++ b/assets/robots/mjcf/ur5.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From b8cc066e9044dab3b54f37d3be47c5838e5ccd3a Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 17 Sep 2024 13:04:46 +0200
Subject: [PATCH 11/40] Add mocap body control
---
deformable_gym/envs/mujoco/base_mjenv.py | 57 ++++++++++++++----
deformable_gym/envs/mujoco/grasp_env.py | 14 ++++-
deformable_gym/envs/mujoco/mia_grasp.py | 10 +++-
deformable_gym/envs/mujoco/shadow_grasp.py | 2 +
deformable_gym/helpers/mj_mocap_control.py | 68 ++++++++++++++++++++++
5 files changed, 134 insertions(+), 17 deletions(-)
create mode 100644 deformable_gym/helpers/mj_mocap_control.py
diff --git a/deformable_gym/envs/mujoco/base_mjenv.py b/deformable_gym/envs/mujoco/base_mjenv.py
index cdeacae..0aec660 100644
--- a/deformable_gym/envs/mujoco/base_mjenv.py
+++ b/deformable_gym/envs/mujoco/base_mjenv.py
@@ -12,9 +12,13 @@
from ...helpers import asset_manager as am
from ...helpers import mj_utils as mju
+from ...helpers.mj_mocap_control import MocapControl
from ...objects.mj_object import ObjectFactory
from ...robots.mj_robot import RobotFactory
+MOCAP_POS_CTRL_RANGE = np.array([[-0.005, 0.005]] * 3)
+MOCAP_QUAT_CTRL_RANGE = np.array([[-np.pi / 100, np.pi / 100]] * 3)
+
class BaseMJEnv(gym.Env, ABC):
"""
@@ -60,15 +64,23 @@ def __init__(
robot_name: str,
obj_name: str,
observable_object_pos: bool = True,
- max_sim_time: float = 5,
- gui: bool = False,
+ control_type: str = "mocap",
+ max_sim_time: float = 10,
+ gui: bool = True,
+ mocap_cfg: Dict[str, str] | None = None,
init_frame: str | None = None,
):
self.scene = am.create_scene(robot_name, obj_name)
self.model, self.data = mju.load_model_from_string(self.scene)
- self.robot = RobotFactory.create(robot_name)
+ self.robot = RobotFactory.create(robot_name, control_type)
self.object = ObjectFactory.create(obj_name)
self.observable_object_pos = observable_object_pos
+ self.control_type = control_type
+ if control_type == "mocap":
+ if mocap_cfg is not None:
+ self.mocap = MocapControl(**mocap_cfg)
+ else:
+ self.mocap = MocapControl()
self.init_frame = init_frame
self.max_sim_time = max_sim_time
self.gui = gui
@@ -84,13 +96,29 @@ def _get_action_space(self) -> spaces.Box:
Returns:
spaces.Box: A continuous space representing the possible actions the agent can take.
"""
-
- n_actuator = self.robot.n_actuator
- low = self.robot.ctrl_range[:, 0].copy()
- high = self.robot.ctrl_range[:, 1].copy()
- return spaces.Box(
- low=low, high=high, shape=(n_actuator,), dtype=np.float64
- )
+ if self.control_type == "mocap":
+ shape = self.robot.n_actuator + 6
+ low = np.concatenate(
+ [
+ MOCAP_POS_CTRL_RANGE[:, 0],
+ MOCAP_QUAT_CTRL_RANGE[:, 0],
+ self.robot.ctrl_range[:, 0],
+ ]
+ )
+ high = np.concatenate(
+ [
+ MOCAP_POS_CTRL_RANGE[:, 1],
+ MOCAP_QUAT_CTRL_RANGE[:, 1],
+ self.robot.ctrl_range[:, 1],
+ ]
+ )
+ elif self.control_type == "joint":
+ shape = self.robot.n_actuator
+ low = self.robot.ctrl_range[:, 0].copy()
+ high = self.robot.ctrl_range[:, 1].copy()
+ else:
+ raise ValueError(f"Unsupported control type: {self.control_type}")
+ return spaces.Box(low=low, high=high, shape=(shape,), dtype=np.float64)
def _get_observation_space(self) -> spaces.Box:
"""
@@ -105,8 +133,8 @@ def _get_observation_space(self) -> spaces.Box:
low = self.robot.joint_range[:, 0].copy()
high = self.robot.joint_range[:, 1].copy()
if self.observable_object_pos:
- low = np.concatenate([low, [-np.inf, -np.inf, -np.inf]])
- high = np.concatenate([high, [np.inf, np.inf, np.inf]])
+ low = np.concatenate([low, [-np.inf] * 3])
+ high = np.concatenate([high, [np.inf] * 3])
return spaces.Box(
low=low, high=high, shape=(n_qpos + 3,), dtype=np.float64
)
@@ -144,6 +172,11 @@ def reset(
)
if self.init_frame is not None:
self._load_keyframe(self.init_frame)
+ if self.control_type == "mocap":
+ if not self.mocap.eq_is_active(self.model, self.data):
+ self.mocap.enable_eq(self.model, self.data)
+ self.mocap.attach_mocap2weld_body(self.model, self.data)
+ self.mocap.reset_eq(self.model, self.data)
def _set_state(
self,
diff --git a/deformable_gym/envs/mujoco/grasp_env.py b/deformable_gym/envs/mujoco/grasp_env.py
index e1b29ae..8342636 100644
--- a/deformable_gym/envs/mujoco/grasp_env.py
+++ b/deformable_gym/envs/mujoco/grasp_env.py
@@ -1,4 +1,4 @@
-from typing import Dict, Optional, Tuple
+from typing import Any, Dict, Optional, Tuple
import mujoco
import mujoco.viewer
@@ -19,8 +19,10 @@ def __init__(
robot_name: str,
obj_name: str,
observable_object_pos: bool = True,
+ control_type: str = "mocap",
max_sim_time: float = 5,
- gui: bool = False,
+ gui: bool = True,
+ mocap_cfg: Dict[str, str] | None = None,
init_frame: Optional[str] = None,
**kwargs,
):
@@ -28,8 +30,10 @@ def __init__(
robot_name,
obj_name,
observable_object_pos,
+ control_type,
max_sim_time,
gui,
+ mocap_cfg,
init_frame,
**kwargs,
)
@@ -143,7 +147,11 @@ def step(self, action: ArrayLike) -> Tuple[NDArray, int, bool, bool, Dict]:
truncation flag, and an info.
"""
sim_time = self.data.time
- self.robot.set_ctrl(self.model, self.data, action)
+ if self.control_type == "mocap":
+ self.mocap.set_ctrl(self.model, self.data, action[:6])
+ self.robot.set_ctrl(self.model, self.data, action[6:])
+ else:
+ self.robot.set_ctrl(self.model, self.data, action)
mujoco.mj_step(self.model, self.data)
observation = self._get_observation()
diff --git a/deformable_gym/envs/mujoco/mia_grasp.py b/deformable_gym/envs/mujoco/mia_grasp.py
index 7753eed..6fb5a6e 100644
--- a/deformable_gym/envs/mujoco/mia_grasp.py
+++ b/deformable_gym/envs/mujoco/mia_grasp.py
@@ -1,5 +1,7 @@
from __future__ import annotations
+from typing import Dict
+
from .grasp_env import GraspEnv
@@ -8,8 +10,10 @@ def __init__(
self,
obj_name: str,
observable_object_pos: bool = True,
- max_sim_time: float = 1,
- gui: bool = False,
+ control_type: str = "mocap",
+ max_sim_time: float = 2,
+ gui: bool = True,
+ mocap_cfg: Dict[str, str] | None = None,
init_frame: str | None = None,
**kwargs,
):
@@ -17,8 +21,10 @@ def __init__(
"mia_hand",
obj_name,
observable_object_pos,
+ control_type,
max_sim_time,
gui,
+ mocap_cfg,
init_frame,
**kwargs,
)
diff --git a/deformable_gym/envs/mujoco/shadow_grasp.py b/deformable_gym/envs/mujoco/shadow_grasp.py
index bcd94a0..9443f8f 100644
--- a/deformable_gym/envs/mujoco/shadow_grasp.py
+++ b/deformable_gym/envs/mujoco/shadow_grasp.py
@@ -9,6 +9,7 @@ def __init__(
self,
obj_name: str,
observable_object_pos: bool = True,
+ control_type: str = "mocap",
max_sim_time: float = 1,
gui: bool = False,
init_frame: str | None = None,
@@ -18,6 +19,7 @@ def __init__(
"shadow_hand",
obj_name,
observable_object_pos,
+ control_type,
max_sim_time,
gui,
init_frame,
diff --git a/deformable_gym/helpers/mj_mocap_control.py b/deformable_gym/helpers/mj_mocap_control.py
new file mode 100644
index 0000000..a6c97f5
--- /dev/null
+++ b/deformable_gym/helpers/mj_mocap_control.py
@@ -0,0 +1,68 @@
+import mujoco
+import numpy as np
+from mujoco import MjData, MjModel, mjtEq
+from numpy.typing import NDArray
+
+from . import mj_utils as mju
+from .mj_utils import Pose
+
+
+class MocapControl:
+
+ def __init__(
+ self, body_name: str = "mocap", equality_name: str = "mocap"
+ ) -> None:
+ self.body_name = body_name
+ self.equality_name = equality_name
+
+ def eq_is_active(self, model: MjModel, data: MjData) -> bool:
+ eq_id = mju.name2id(model, self.equality_name, "equality")
+ return True if data.eq_active[eq_id] else False
+
+ def enable_eq(self, model: MjModel, data: MjData) -> None:
+ mju.enable_equality_constraint(model, data, self.equality_name)
+
+ def disable_eq(self, model: MjModel, data: MjData) -> None:
+ mju.disable_equality_constraint(model, data, self.equality_name)
+
+ def reset_eq(self, model: MjModel, data: MjData) -> None:
+ # the indication of eq_data is as follows:
+ # eq_data[:3]: anchor position
+ # eq_data[3:10]: relative position (3D position followed by 4D quaternion orientation)
+ # eq_data[10]: torquescale
+ # since mocap body is attached to the desired body, and no anchor is set,
+ # we need to set the relative position 0,0,0,1,0,0,0 and anchor position 0,0,0 and torquescale 1 by default
+ eq_id = mju.name2id(model, self.equality_name, "equality")
+ assert (
+ model.equality(eq_id).type == mjtEq.mjEQ_WELD
+ ), "Only weld equality is supported."
+ model.eq_data[eq_id] = np.array(
+ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
+ )
+ mujoco.mj_forward(model, data)
+
+ def get_weld_body_id(self, model: MjModel) -> int:
+ mocap_body_id = mju.name2id(model, self.body_name)
+ body1_id = model.equality(self.equality_name).obj1id
+ body2_id = model.equality(self.equality_name).obj2id
+ return body1_id if body1_id != mocap_body_id else body2_id
+
+ def attach_mocap2weld_body(self, model: MjModel, data: MjData) -> None:
+ weld_body_id = self.get_weld_body_id(model)
+ body_xpos = data.body(weld_body_id).xpos
+ body_xquat = data.body(weld_body_id).xquat
+ mju.set_body_pose(
+ model, data, self.body_name, Pose(body_xpos, body_xquat)
+ )
+ mju.set_mocap_pose(model, data, Pose(body_xpos, body_xquat))
+
+ def set_ctrl(self, model: MjModel, data: MjData, ctrl: NDArray) -> None:
+ assert len(ctrl) == 6, "mocap ctrl should be in shape (6,)."
+ assert model.nmocap == 1, "Only one mocap body is supported."
+
+ ctrl = np.array(ctrl)
+ new_mocap_pos = np.squeeze(data.mocap_pos) + ctrl[:3]
+ new_mocap_quat = mju.rotate_quat_by_euler(
+ np.squeeze(data.mocap_quat), ctrl[3:]
+ )
+ mju.set_mocap_pose(model, data, Pose(new_mocap_pos, new_mocap_quat))
From c9b287eedcf6eb80b8343edeb4d2b1afe8ed27fc Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 17 Sep 2024 13:05:44 +0200
Subject: [PATCH 12/40] Load init pose from init_pose and add pillow object
---
deformable_gym/envs/mujoco/init_pose.py | 60 +++++++++++++++++++++++++
deformable_gym/objects/mj_object.py | 57 +++++++++++++++--------
2 files changed, 98 insertions(+), 19 deletions(-)
create mode 100644 deformable_gym/envs/mujoco/init_pose.py
diff --git a/deformable_gym/envs/mujoco/init_pose.py b/deformable_gym/envs/mujoco/init_pose.py
new file mode 100644
index 0000000..8a48631
--- /dev/null
+++ b/deformable_gym/envs/mujoco/init_pose.py
@@ -0,0 +1,60 @@
+from typing import Dict
+
+import numpy as np
+
+from ...helpers.mj_utils import Pose
+
+
+class ObjectInitPose:
+ insole_fixed = {
+ "shadow_hand": Pose([0.12, -0.05, 0.456], [0, 0, 0]),
+ "ur5_shadow": Pose([1.4, -0.05, 1], [0, 0, 0]),
+ "ur10_shadow": Pose([1.55, -0.05, 1], [0, 0, 0]),
+ "ur10e_shadow": Pose([1.6, -0.05, 1], [0, 0, 0]),
+ "mia_hand": Pose([0.12, -0.05, 0.456], [0, 0, 0]),
+ "ur5_mia": Pose([1.4, -0.05, 1], [0, 0, 0]),
+ "ur10_mia": Pose([1.55, -0.05, 1], [0, 0, 0]),
+ "ur10ft_mia": Pose([1.55, -0.05, 1], [0, 0, 0]),
+ "ur10e_mia": Pose([1.6, -0.05, 1], [0, 0, 0]),
+ }
+
+ pillow_fixed = {
+ "shadow_hand": Pose([0.12, -0.05, 0.456], [0, 0, np.pi / 2]),
+ "ur5_shadow": Pose([1.4, -0.05, 1], [0, 0, np.pi / 2]),
+ "ur10_shadow": Pose([1.6, -0.05, 1], [0, 0, np.pi / 2]),
+ "ur10e_shadow": Pose([1.6, -0.05, 1], [0, 0, np.pi / 2]),
+ "mia_hand": Pose([0.12, -0.05, 0.456], [0, 0, np.pi / 2]),
+ "ur5_mia": Pose([1.15, -0.05, 1], [0, 0, np.pi / 2]),
+ "ur10_mia": Pose([1.5, -0.05, 1], [0, 0, np.pi / 2]),
+ "ur10ft_mia": Pose([1.5, -0.05, 1], [0, 0, np.pi / 2]),
+ "ur10e_mia": Pose([1.48, -0.05, 1], [0, 0, np.pi / 2]),
+ }
+
+ @staticmethod
+ def get(name: str) -> Dict[str, Pose]:
+ if name == "insole_fixed":
+ return ObjectInitPose.insole_fixed
+ elif name == "pillow_fixed":
+ return ObjectInitPose.pillow_fixed
+ else:
+ return {}
+
+
+class RobotInitPose:
+
+ shadow_hand = {
+ "insole_fixed": Pose([-0.35, 0, 0.49], [0, np.pi / 2, 0]),
+ }
+
+ mia_hand = {
+ "insole_fixed": Pose([-0.1, 0, 0.49], [0, np.pi, np.pi / 2]),
+ }
+
+ @staticmethod
+ def get(name: str) -> Dict[str, Pose]:
+ if name == "shadow_hand":
+ return RobotInitPose.shadow_hand
+ elif name == "mia_hand":
+ return RobotInitPose.mia_hand
+ else:
+ return {}
diff --git a/deformable_gym/objects/mj_object.py b/deformable_gym/objects/mj_object.py
index 5ad0d44..31c40ab 100644
--- a/deformable_gym/objects/mj_object.py
+++ b/deformable_gym/objects/mj_object.py
@@ -1,12 +1,14 @@
from __future__ import annotations
-from typing import List
+from typing import Dict, List
import mujoco
+from mujoco import MjData, MjModel
from numpy.typing import NDArray
+from ..envs.mujoco.init_pose import ObjectInitPose
+from ..helpers import asset_manager as am
from ..helpers import mj_utils as mju
-from ..helpers.asset_manager import AssetManager
from ..helpers.mj_utils import Pose
@@ -17,7 +19,7 @@ class MJObject:
Attributes:
name (str): The name of the object, corresponding to its definition in the XML model.
- model (mujoco.MjModel): The MuJoCo model object representing the object in the simulation.
+ model (MjModel): The MuJoCo model object representing the object in the simulation.
eq_constraints (List[str]): A list of equality constraints associated with this object.
These constraints are typically used to enforce specific relationships between bodies,
such as keeping them at a fixed distance or maintaining an orientation.
@@ -28,7 +30,11 @@ class MJObject:
def __init__(self, name: str) -> None:
self.name = name
- self.model = AssetManager().load_asset(name)
+ self.model = am.load_asset(name)
+
+ @property
+ def init_pose(self) -> Dict[str, Pose]:
+ return ObjectInitPose.get(self.name)
@property
def eq_constraints(self) -> List[str]:
@@ -40,9 +46,9 @@ def eq_constraints_to_disable(self) -> List[str | None]:
def set_pose(
self,
- model: mujoco.MjModel,
- data: mujoco.MjData,
- pose: Pose,
+ model: MjModel,
+ data: MjData,
+ pose: Pose | None,
) -> None:
"""
Set the pose (position and orientation) of the object in the simulation.
@@ -51,20 +57,21 @@ def set_pose(
and recalculates the simulation state.
Args:
- model (mujoco.MjModel): The MuJoCo model object containing the object's configuration.
- data (mujoco.MjData): The MuJoCo data object containing the current simulation state.
+ model (MjModel): The MuJoCo model object containing the object's configuration.
+ data (MjData): The MuJoCo data object containing the current simulation state.
pose (Pose): A Pose object containing the desired position and orientation for the object.
"""
- model.body(self.name).pos[:] = pose.position
- model.body(self.name).quat[:] = pose.orientation
- mujoco.mj_forward(model, data)
+ if pose is not None:
+ model.body(self.name).pos[:] = pose.position
+ model.body(self.name).quat[:] = pose.orientation
+ mujoco.mj_forward(model, data)
- def get_center_of_mass(self, data: mujoco.MjData) -> NDArray:
+ def get_center_of_mass(self, data: MjData) -> NDArray:
"""
Get the center of mass (COM) position of the object.
Args:
- data (mujoco.MjData): The MuJoCo data object containing the current simulation state.
+ data (MjData): The MuJoCo data object containing the current simulation state.
Returns:
NDArray: An array representing the position of the object's center of mass.
@@ -72,24 +79,36 @@ def get_center_of_mass(self, data: mujoco.MjData) -> NDArray:
return data.body(self.name).ipos
-class InsoleFixed(MJObject):
-
- def __init__(self) -> None:
- super().__init__("insole_fixed")
+class FixedObject(MJObject):
+ def __init__(self, name: str) -> None:
+ super().__init__(name)
@property
def eq_constraints_to_disable(self) -> List[str]:
return self.eq_constraints
- def get_center_of_mass(self, data: mujoco.MjData) -> NDArray:
+ def get_center_of_mass(self, data: MjData) -> NDArray:
return data.body(self.name).subtree_com
+class InsoleFixed(FixedObject):
+
+ def __init__(self, name="insole_fixed") -> None:
+ super().__init__(name)
+
+
+class PillowFixed(FixedObject):
+ def __init__(self, name="pillow_fixed") -> None:
+ super().__init__(name)
+
+
class ObjectFactory:
@staticmethod
def create(name: str) -> MJObject:
if name == "insole_fixed":
return InsoleFixed()
+ if name == "pillow_fixed":
+ return PillowFixed()
else:
raise ValueError(f"Object {name} not found")
From fb100dcf4bcfb7458886f7e2f12d711622d0a0dd Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 17 Sep 2024 13:06:26 +0200
Subject: [PATCH 13/40] Add mocap control and arm descriptions
---
deformable_gym/robots/mj_robot.py | 347 +++++++++++++++++++++---------
1 file changed, 245 insertions(+), 102 deletions(-)
diff --git a/deformable_gym/robots/mj_robot.py b/deformable_gym/robots/mj_robot.py
index 1c52a6e..6a0e8f3 100644
--- a/deformable_gym/robots/mj_robot.py
+++ b/deformable_gym/robots/mj_robot.py
@@ -3,12 +3,17 @@
import mujoco
import numpy as np
+from mujoco import mjtJoint
from numpy.typing import ArrayLike, NDArray
+from ..envs.mujoco.init_pose import RobotInitPose
from ..helpers import asset_manager as am
from ..helpers import mj_utils as mju
from ..helpers.mj_utils import Pose
+SLIDE_CTRL_RANGE = [-0.005, 0.005]
+HINGE_CTRL_RANGE = [-np.pi / 180, np.pi / 180]
+
class MJRobot(ABC):
"""
@@ -30,12 +35,21 @@ class MJRobot(ABC):
actuators (List[str]): A list of actuator names for the robot.
"""
- init_pose = {}
+ supported_ctrl_types = ["joint", "mocap"]
- def __init__(self, name: str) -> None:
+ def __init__(self, name: str, control_type="joint") -> None:
self.name = name
self.model = am.load_asset(self.name)
+ if control_type not in self.supported_ctrl_types:
+ raise ValueError(
+ f"Control type {control_type} not supported.\n Supported control types: {self.supported_ctrl_types}"
+ )
+ self.control_type = control_type
+
+ @property
+ def init_pose(self) -> dict:
+ return RobotInitPose.get(self.name)
@property
def n_qpos(self) -> int:
@@ -46,24 +60,33 @@ def dof(self) -> int:
return self.model.nv
@property
- def n_actuator(self) -> int:
- return self.model.nu
-
- @property
- def joint_range(self) -> NDArray:
- return self.model.jnt_range.copy()
+ def actuators(self) -> List[str]:
+ return mju.get_actuator_names(self.model)
@property
- def ctrl_range(self) -> NDArray:
- return self.model.actuator_ctrlrange.copy()
+ def n_actuator(self) -> int:
+ return len(self.actuators)
@property
def joints(self) -> List[str]:
return mju.get_joint_names(self.model)
@property
- def actuators(self) -> List[str]:
- return mju.get_actuator_names(self.model)
+ def joint_range(self) -> NDArray:
+ return self.model.jnt_range.copy()
+
+ @property
+ def ctrl_range(self) -> NDArray:
+ ctrl_range = []
+ for actuator in self.actuators:
+ joint_id, _ = self.model.actuator(actuator).trnid
+ if self.model.joint(joint_id).type == mjtJoint.mjJNT_HINGE:
+ ctrl_range.append(HINGE_CTRL_RANGE)
+ elif self.model.joint(joint_id).type == mjtJoint.mjJNT_SLIDE:
+ ctrl_range.append(SLIDE_CTRL_RANGE)
+ else:
+ raise ValueError(f"Joint type not supported.")
+ return np.array(ctrl_range)
def get_qpos(self, model: mujoco.MjModel, data: mujoco.MjData) -> NDArray:
"""
@@ -132,52 +155,91 @@ def set_ctrl(
Raises:
AssertionError: If the length of the control vector does not match the number of actuators.
"""
-
assert (
len(ctrl) == self.n_actuator
), f"Control vector should have length {self.n_actuator}"
for i, act in enumerate(self.actuators):
- mju.set_actuator_ctrl(model, data, act, ctrl[i])
+ actuator_id = mju.name2id(self.model, act, "actuator")
+ new_ctrl = data.ctrl[actuator_id] + ctrl[i]
+ mju.set_actuator_ctrl(model, data, act, new_ctrl)
-class ShadowHand(MJRobot):
+class Ur5(MJRobot):
+
+ def __init__(self, name: str = "ur5", control_type: str = "joint") -> None:
+ super().__init__(name, control_type)
- init_pose = {
- "insole_fixed": Pose([-0.35, 0.00, 0.49], [0, np.pi / 2, 0]),
- }
- def __init__(self, name: str = "shadow_hand") -> None:
- super().__init__(name)
+class Ur10(MJRobot):
+ def __init__(self, name: str = "ur10", control_type: str = "joint") -> None:
+ super().__init__(name, control_type)
-class Ur5Shadow(ShadowHand):
- init_pose = {
- "insole_fixed": Pose([0.0, 0.0, 0.0]),
- "pillow_fixed": Pose([0.0, 0.0, 0.0]),
- }
- def __init__(self, name: str = "ur5_shadow"):
- super().__init__(name)
+class Ur10e(MJRobot):
+
+ def __init__(
+ self, name: str = "ur10e", control_type: str = "joint"
+ ) -> None:
+ super().__init__(name, control_type)
-class Ur10Shadow(ShadowHand):
- init_pose = {
- "insole_fixed": Pose([0.0, 0.0, 0.0]),
- "pillow_fixed": Pose([0.0, 0.0, 0.0]),
- }
+class Ur10ft(MJRobot):
- def __init__(self, name: str = "ur10_shadow"):
- super().__init__(name)
+ def __init__(
+ self, name: str = "ur10ft", control_type: str = "joint"
+ ) -> None:
+ super().__init__(name, control_type)
-class Ur10eShadow(ShadowHand):
- init_pose = {
- "insole_fixed": Pose([0.0, 0.0, 0.0]),
- "pillow_fixed": Pose([0.0, 0.0, 0.0]),
- }
+class ShadowHand(MJRobot):
+ ee_actuators = [
+ "ee_A_X",
+ "ee_A_Y",
+ "ee_A_Z",
+ "ee_A_OX",
+ "ee_A_OY",
+ "ee_A_OZ",
+ ]
- def __init__(self, name: str = "ur10e_shadow"):
- super().__init__(name)
+ def __init__(
+ self, name: str = "shadow_hand", control_type: str = "mocap"
+ ) -> None:
+ super().__init__(name, control_type)
+ if self.control_type == "mocap":
+ self._excluded_actuators = self.ee_actuators
+ else:
+ self._excluded_actuators = []
+
+ @property
+ def actuators(self):
+ all_actuators = mju.get_actuator_names(self.model)
+
+ if self.control_type == "joint":
+ return all_actuators
+ else:
+ return [
+ act
+ for act in all_actuators
+ if act not in self._excluded_actuators
+ ]
+
+ @property
+ def n_actuator(self):
+ return len(self.actuators)
+
+
+class ShadowHandOnArm(ShadowHand):
+
+ def __init__(
+ self, robot_name: str, arm: str, control_type: str = "mocap"
+ ) -> None:
+ super().__init__(robot_name, control_type)
+ self.arm = RobotFactory.create(arm, control_type)
+ if self.control_type == "mocap":
+ self._excluded_actuators += self.arm.actuators
+ else:
+ self._excluded_actuators = []
class MiaHand(MJRobot):
@@ -187,42 +249,67 @@ class MiaHand(MJRobot):
"j_little_fle_A",
]
mrl_joints = ["j_middle_fle", "j_ring_fle", "j_little_fle"]
+ ee_actuators = [
+ "ee_A_X",
+ "ee_A_Y",
+ "ee_A_Z",
+ "ee_A_OX",
+ "ee_A_OY",
+ "ee_A_OZ",
+ ]
- init_pose = {
- "insole_fixed": Pose([-0.1, 0, 0.49], [0, np.pi, np.pi / 2]),
- }
+ def __init__(
+ self, name: str = "mia_hand", control_type: str = "mocap"
+ ) -> None:
+ super().__init__(name, control_type)
+ if self.control_type == "mocap":
+ self._excluded_actuators = self.mrl_actuators + self.ee_actuators
+ else:
+ self._excluded_actuators = self.mrl_actuators
- def __init__(self, name: str = "mia_hand") -> None:
- super().__init__(name)
+ @property
+ def actuators(self):
+ all_actuators = mju.get_actuator_names(self.model)
+ # exclude middle, ring, little actuators
+ acts = [
+ act for act in all_actuators if act not in self._excluded_actuators
+ ]
+ # add the combined actuator for middle, ring, little finger control
+ acts.append("j_mrl_fle_A")
+ return acts
@property
def n_actuator(self):
- return self.model.nu - 2
+ return len(self.actuators)
@property
def ctrl_range(self):
- mrl_range = self.model.actuator("j_middle_fle_A").ctrlrange
- ctrl_range_wo_mrl = np.array(
- [
- self.model.actuator(name).ctrlrange
- for name in mju.get_actuator_names(self.model)
- if name not in self.mrl_actuators
- ]
- )
- return np.vstack((ctrl_range_wo_mrl, mrl_range))
- @property
- def actuators(self):
- all_actuators = mju.get_actuator_names(self.model)
- acts = [act for act in all_actuators if act not in self.mrl_actuators]
- acts.append("j_mrl_fle_A")
- return acts
+ ctrl_range = []
+ mrl_range = HINGE_CTRL_RANGE
+ actuators = [
+ act
+ for act in mju.get_actuator_names(self.model)
+ if act not in self._excluded_actuators
+ ]
+ for actuator in actuators:
+ joint_id, _ = self.model.actuator(actuator).trnid
+ if self.model.joint(joint_id).type == mjtJoint.mjJNT_HINGE:
+ ctrl_range.append(HINGE_CTRL_RANGE)
+ elif self.model.joint(joint_id).type == mjtJoint.mjJNT_SLIDE:
+ ctrl_range.append(SLIDE_CTRL_RANGE)
+ else:
+ raise ValueError(f"Joint type not supported.")
+ ctrl_range.append(mrl_range)
+ return np.array(ctrl_range)
def _set_mrl_ctrl(
self, model: mujoco.MjModel, data: mujoco.MjData, ctrl: float
) -> None:
+ act_id = mju.name2id(self.model, "j_middle_fle_A", "actuator")
+ new_ctrl = data.ctrl[act_id] + ctrl
for act in self.mrl_actuators:
- mju.set_actuator_ctrl(model, data, act, ctrl)
+ mju.set_actuator_ctrl(model, data, act, new_ctrl)
def set_ctrl(
self, model: mujoco.MjModel, data: mujoco.MjData, ctrl: ArrayLike
@@ -230,74 +317,130 @@ def set_ctrl(
assert (
len(ctrl) == self.n_actuator
), f"Control vector should have length {self.n_actuator}, now it is {len(ctrl)}"
+ ctrl = np.array(ctrl)
for i, act in enumerate(self.actuators):
if act == "j_mrl_fle_A":
self._set_mrl_ctrl(model, data, ctrl[i])
else:
- mju.set_actuator_ctrl(model, data, act, ctrl[i])
+ act_id = mju.name2id(self.model, act, "actuator")
+ new_ctrl = data.ctrl[act_id] + ctrl[i]
+ mju.set_actuator_ctrl(model, data, act, new_ctrl)
-class Ur5Mia(MiaHand):
- init_pose = {
- "insole_fixed": Pose([0.0, 0.0, 0.0]),
- "pillow_fixed": Pose([0.0, 0.0, 0.0]),
- }
+class MiaHandOnArm(MiaHand):
- def __init__(self, name: str = "ur5_mia"):
- super().__init__(name)
+ def __init__(
+ self, robot_name: str, arm: str, control_type: str = "mocap"
+ ) -> None:
+ super().__init__(robot_name, control_type)
+ self.arm = RobotFactory.create(arm, control_type)
+ if self.control_type == "mocap":
+ self._excluded_actuators += self.arm.actuators
+ else:
+ self._excluded_actuators = self.mrl_actuators
+ @property
+ def actuators(self) -> List[str]:
+ all_actuators = mju.get_actuator_names(self.model)
-class Ur10Mia(MiaHand):
- init_pose = {
- "insole_fixed": Pose([0.0, 0.0, 0.0]),
- "pillow_fixed": Pose([0.0, 0.0, 0.0]),
- }
+ acts = [
+ act for act in all_actuators if act not in self._excluded_actuators
+ ]
+ acts.append("j_mrl_fle_A")
+ return acts
- def __init__(self, name: str = "ur10_mia"):
- super().__init__(name)
+ @property
+ def n_actuator(self) -> int:
+ return len(self.actuators)
-class Ur10ftMia(MiaHand):
- init_pose = {
- "insole_fixed": Pose([0.0, 0.0, 0.0]),
- "pillow_fixed": Pose([0.0, 0.0, 0.0]),
- }
+class Ur5Shadow(ShadowHandOnArm):
- def __init__(self, name: str = "ur10ft_mia"):
- super().__init__(name)
+ def __init__(
+ self, name: str = "ur5_shadow", control_type: str = "mocap"
+ ) -> None:
+ super().__init__(name, "ur5", control_type)
+
+
+class Ur10Shadow(ShadowHandOnArm):
+
+ def __init__(
+ self, name: str = "ur10_shadow", control_type: str = "mocap"
+ ) -> None:
+ super().__init__(name, "ur10", control_type)
+
+
+class Ur10eShadow(ShadowHandOnArm):
+
+ def __init__(
+ self, name: str = "ur10e_shadow", control_type: str = "mocap"
+ ) -> None:
+ super().__init__(name, "ur10e", control_type)
+
+
+class Ur5Mia(MiaHandOnArm):
+
+ def __init__(
+ self, name: str = "ur5_mia", control_type: str = "mocap"
+ ) -> None:
+ super().__init__(name, "ur5", control_type)
+
+
+class Ur10Mia(MiaHandOnArm):
+
+ def __init__(
+ self, name: str = "ur10_mia", control_type: str = "mocap"
+ ) -> None:
+ super().__init__(name, "ur10", control_type)
-class Ur10eMia(MiaHand):
- init_pose = {
- "insole_fixed": Pose([0.0, 0.0, 0.0]),
- "pillow_fixed": Pose([0.0, 0.0, 0.0]),
- }
+class Ur10ftMia(MiaHandOnArm):
- def __init__(self, name: str = "ur10e_mia"):
- super().__init__(name)
+ def __init__(
+ self, name: str = "ur10ft_mia", control_type: str = "mocap"
+ ) -> None:
+ super().__init__(name, "ur10ft", control_type)
+
+
+class Ur10eMia(MiaHandOnArm):
+
+ def __init__(
+ self, name: str = "ur10e_mia", control_type: str = "mocap"
+ ) -> None:
+ super().__init__(name, "ur10e", control_type)
class RobotFactory:
@staticmethod
- def create(name: str) -> MJRobot:
+ def create(name: str, control_type: str) -> MJRobot:
if name == "shadow_hand":
- return ShadowHand()
+ return ShadowHand(name, control_type)
elif name == "ur5_shadow":
- return Ur5Shadow()
+ return Ur5Shadow(name, control_type)
elif name == "ur10_shadow":
- return Ur10Shadow()
+ return Ur10Shadow(name, control_type)
elif name == "ur10e_shadow":
- return Ur10eShadow()
+ return Ur10eShadow(name, control_type)
elif name == "mia_hand":
- return MiaHand()
+ return MiaHand(name, control_type)
elif name == "ur5_mia":
- return Ur5Mia()
+ return Ur5Mia(name, control_type)
elif name == "ur10_mia":
- return Ur10Mia()
+ return Ur10Mia(name, control_type)
elif name == "ur10ft_mia":
- return Ur10ftMia()
+ return Ur10ftMia(name, control_type)
elif name == "ur10e_mia":
- return Ur10eMia()
+ return Ur10eMia(name, control_type)
+ # ----- Not used for environment Just need the description ----- #
+ elif name == "ur5":
+ return Ur5()
+ elif name == "ur10":
+ return Ur10()
+ elif name == "ur10e":
+ return Ur10e()
+ elif name == "ur10ft":
+ return Ur10ft()
+ # ----- Not used for environment Just need the description ----- #
else:
raise ValueError(f"Robot {name} not found.")
From 1d09e86b80c4cead76c7f1dfa87e876d1ed3e0f8 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 17 Sep 2024 13:06:41 +0200
Subject: [PATCH 14/40] Add new robots
---
deformable_gym/helpers/asset_manager.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/deformable_gym/helpers/asset_manager.py b/deformable_gym/helpers/asset_manager.py
index 75ea8fd..7b856d8 100644
--- a/deformable_gym/helpers/asset_manager.py
+++ b/deformable_gym/helpers/asset_manager.py
@@ -13,6 +13,10 @@
OBJECT_DIR = os.path.join(ASSETS_DIR, "objects", "mjcf")
ROBOTS = {
+ "ur5": os.path.join(ROBOT_DIR, "ur5.xml"),
+ "ur10": os.path.join(ROBOT_DIR, "ur10.xml"),
+ "ur10ft": os.path.join(ROBOT_DIR, "ur10ft.xml"),
+ "ur10e": os.path.join(ROBOT_DIR, "ur10e.xml"),
"shadow_hand": os.path.join(ROBOT_DIR, "shadow_hand.xml"),
"mia_hand": os.path.join(ROBOT_DIR, "mia_hand.xml"),
"ur5_mia": os.path.join(ROBOT_DIR, "mia_hand_on_ur5.xml"),
From fc5d275ff300719bb9f3942241abc7bec914ee59 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 17 Sep 2024 13:07:37 +0200
Subject: [PATCH 15/40] Add mocap body and rotation utils
---
deformable_gym/helpers/mj_utils.py | 149 +++++++++++++++--------------
1 file changed, 75 insertions(+), 74 deletions(-)
diff --git a/deformable_gym/helpers/mj_utils.py b/deformable_gym/helpers/mj_utils.py
index fb4242e..e079ca9 100644
--- a/deformable_gym/helpers/mj_utils.py
+++ b/deformable_gym/helpers/mj_utils.py
@@ -3,36 +3,17 @@
import mujoco
import numpy as np
-from mujoco import mjtJoint
+from mujoco import MjData, MjModel, mjtJoint
from numpy.typing import ArrayLike, NDArray
+# fmt: off
TYPE_NAMES = [
- "body",
- "xbody",
- "joint",
- "dof",
- "geom",
- "site",
- "camera",
- "light",
- "flex",
- "mesh",
- "skin",
- "hfield",
- "texture",
- "material",
- "pair",
- "exclude",
- "equality",
- "tendon",
- "actuator",
- "sensor",
- "numeric",
- "text",
- "tuple",
- "key",
- "plugin",
+ "body", "xbody", "joint", "dof", "geom", "site", "camera", "light",
+ "flex", "mesh", "skin", "hfield", "texture", "material", "pair",
+ "exclude", "equality", "tendon", "actuator", "sensor", "numeric",
+ "text", "tuple", "key", "plugin",
]
+# fmt: on
# -------------------------------- DATA CLASSES --------------------------------#
@@ -56,20 +37,20 @@ def __post_init__(self):
# -------------------------------- LOAD MODEL --------------------------------#
-def load_model_from_file(path: str) -> Tuple[mujoco.MjModel, mujoco.MjData]:
- model = mujoco.MjModel.from_xml_path(path)
- data = mujoco.MjData(model)
+def load_model_from_file(path: str) -> Tuple[MjModel, MjData]:
+ model = MjModel.from_xml_path(path)
+ data = MjData(model)
return model, data
-def load_model_from_string(xml: str) -> Tuple[mujoco.MjModel, mujoco.MjData]:
- model = mujoco.MjModel.from_xml_string(xml)
- data = mujoco.MjData(model)
+def load_model_from_string(xml: str) -> Tuple[MjModel, MjData]:
+ model = MjModel.from_xml_string(xml)
+ data = MjData(model)
return model, data
# -------------------------------- BODY UTILS --------------------------------#
-def get_body_names(model: mujoco.MjModel) -> List[str]:
+def get_body_names(model: MjModel) -> List[str]:
return [
model.body(i).name
@@ -78,12 +59,22 @@ def get_body_names(model: mujoco.MjModel) -> List[str]:
]
-def remove_body(model: mujoco.MjModel, data: mujoco.MjData, name: str) -> None:
+def set_body_pose(model: MjModel, data: MjData, name: str, pose: Pose) -> None:
+ names = get_body_names(model)
+ assert (
+ name in names
+ ), f"No body named {name} in the model.\n Names available: {names}"
+ model.body(name).pos = pose.position
+ model.body(name).quat = pose.orientation
+ mujoco.mj_forward(model, data)
+
+
+def remove_body(model: MjModel, data: MjData, name: str) -> None:
"""remove a body by setting its position to (0, 0, -1000).
Args:
- model (mujoco.MjModel): mj_Model struct
- data (mujoco.MjData): mj_Data struct
+ model (MjModel): mj_Model struct
+ data (MjData): mj_Data struct
name (str): body name
"""
names = get_body_names(model)
@@ -95,9 +86,7 @@ def remove_body(model: mujoco.MjModel, data: mujoco.MjData, name: str) -> None:
mujoco.mj_forward(model, data)
-def get_body_pose(
- model: mujoco.MjModel, data: mujoco.MjModel, name: str
-) -> Pose:
+def get_body_pose(model: MjModel, data: MjModel, name: str) -> Pose:
names = get_body_names(model)
assert (
name in names
@@ -107,7 +96,7 @@ def get_body_pose(
def get_body_center_of_mass(
- model: mujoco.MjModel, data: mujoco.MjModel, name: str
+ model: MjModel, data: MjModel, name: str
) -> NDArray:
names = get_body_names(model)
assert (
@@ -118,7 +107,7 @@ def get_body_center_of_mass(
# -------------------------------- JOINT UTILS --------------------------------#
-def get_joint_names(model: mujoco.MjModel) -> List[str]:
+def get_joint_names(model: MjModel) -> List[str]:
return [
model.joint(i).name
for i in range(model.njnt)
@@ -126,9 +115,7 @@ def get_joint_names(model: mujoco.MjModel) -> List[str]:
]
-def get_joint_qpos(
- model: mujoco.MjModel, data: mujoco.MjData, *name: str
-) -> NDArray:
+def get_joint_qpos(model: MjModel, data: MjData, *name: str) -> NDArray:
names = get_joint_names(model)
assert set(name).issubset(
names
@@ -136,9 +123,7 @@ def get_joint_qpos(
return np.concatenate([data.joint(n).qpos for n in name])
-def get_joint_qvel(
- model: mujoco.MjModel, data: mujoco.MjData, *name: str
-) -> NDArray:
+def get_joint_qvel(model: MjModel, data: MjData, *name: str) -> NDArray:
names = set(get_joint_names(model))
assert set(name).issubset(
names
@@ -146,9 +131,7 @@ def get_joint_qvel(
return np.concatenate([data.joint(n).qvel for n in name])
-def disable_joint(
- model: mujoco.MjModel, data: mujoco.MjData, *name: str
-) -> None:
+def disable_joint(model: MjModel, data: MjData, *name: str) -> None:
for n in name:
joint_type = model.joint(n).type
if all(
@@ -162,11 +145,11 @@ def disable_joint(
# -------------------------------- GEOM UTILS --------------------------------#
-def get_geom_names(model: mujoco.MjModel) -> List[str]:
+def get_geom_names(model: MjModel) -> List[str]:
"""Get all first level geom names under worldbody node in the model.
Args:
- model (mujoco.MjModel): mj_Model struct
+ model (MjModel): mj_Model struct
Returns:
List[str]: list of all geom names
@@ -178,12 +161,12 @@ def get_geom_names(model: mujoco.MjModel) -> List[str]:
]
-def remove_geom(model: mujoco.MjModel, data: mujoco.MjData, name: str) -> None:
+def remove_geom(model: MjModel, data: MjData, name: str) -> None:
"""remove a geom by setting its position to (0, 0, -1000).
Args:
- model (mujoco.MjModel): mj_Model struct
- data (mujoco.MjData): mj_Data struct
+ model (MjModel): mj_Model struct
+ data (MjData): mj_Data struct
name (str): geom name
"""
names = get_geom_names(model)
@@ -196,7 +179,7 @@ def remove_geom(model: mujoco.MjModel, data: mujoco.MjData, name: str) -> None:
# -------------------------------- SENSOR UTILS --------------------------------#
-def get_sensor_names(model: mujoco.MjModel) -> List[str]:
+def get_sensor_names(model: MjModel) -> List[str]:
return [
model.sensor(i).name
for i in range(model.nsensor)
@@ -204,9 +187,7 @@ def get_sensor_names(model: mujoco.MjModel) -> List[str]:
]
-def get_sensor_data(
- model: mujoco.MjModel, data: mujoco.MjModel, name: str
-) -> NDArray:
+def get_sensor_data(model: MjModel, data: MjModel, name: str) -> NDArray:
names = get_sensor_names(model)
assert (
name in names
@@ -215,7 +196,7 @@ def get_sensor_data(
# -------------------------------- EQUALITY CONSTRAINT UTILS --------------------------------#
-def get_equality_names(model: mujoco.MjModel) -> List[str]:
+def get_equality_names(model: MjModel) -> List[str]:
return [
model.equality(i).name
for i in range(model.neq)
@@ -224,7 +205,7 @@ def get_equality_names(model: mujoco.MjModel) -> List[str]:
def enable_equality_constraint(
- model: mujoco.MjModel, data: mujoco.MjData, *name: str
+ model: MjModel, data: MjData, *name: str
) -> None:
names = get_equality_names(model)
for n in name:
@@ -237,7 +218,7 @@ def enable_equality_constraint(
def disable_equality_constraint(
- model: mujoco.MjModel, data: mujoco.MjData, *name: str
+ model: MjModel, data: MjData, *name: str
) -> None:
names = get_equality_names(model)
for n in name:
@@ -250,7 +231,7 @@ def disable_equality_constraint(
# -------------------------------- ACTUATOR UTILS --------------------------------#
-def get_actuator_names(model: mujoco.MjModel) -> List[str]:
+def get_actuator_names(model: MjModel) -> List[str]:
return [
model.actuator(i).name
for i in range(model.nu)
@@ -259,7 +240,7 @@ def get_actuator_names(model: mujoco.MjModel) -> List[str]:
def set_actuator_ctrl(
- model: mujoco.MjModel, data: mujoco.MjData, name: str, ctrl: float
+ model: MjModel, data: MjData, name: str, ctrl: float
) -> None:
names = get_actuator_names(model)
assert (
@@ -269,8 +250,36 @@ def set_actuator_ctrl(
data.ctrl[id] = ctrl
+# -------------------------------- MOCAP UTILS --------------------------------#
+def set_mocap_pose(model: MjModel, data: MjData, pose: Pose):
+ if model.nmocap != 1:
+ raise NotImplementedError("Only one mocap is supported.")
+
+ data.mocap_pos[:] = pose.position
+ data.mocap_quat[:] = pose.orientation
+
+
+# -------------------------------- ROTATION UTILS --------------------------------#
+def euler2quat(euler: ArrayLike) -> NDArray:
+ assert len(euler) == 3, "input should be in shape (3,)."
+
+ quat = np.zeros(4, dtype=np.float64)
+ mujoco.mju_euler2Quat(quat, euler, "xyz")
+ return quat
+
+
+def rotate_quat_by_euler(quat: ArrayLike, euler: ArrayLike) -> NDArray:
+ assert len(quat) == 4, "quat should be in shape (4,)."
+ assert len(euler) == 3, "euler should be in shape (3,)."
+
+ quat_rot = euler2quat(euler)
+ result = np.zeros(4, dtype=np.float64)
+ mujoco.mju_mulQuat(result, quat_rot, quat)
+ return result
+
+
# -------------------------------- OTHER UTILS --------------------------------#
-def id2name(model: mujoco.MjModel, id: int, t: str = "body") -> str:
+def id2name(model: MjModel, id: int, t: str = "body") -> str:
assert (
t in TYPE_NAMES
), f"No type name {t} found. Available type names are: {TYPE_NAMES}"
@@ -282,7 +291,7 @@ def id2name(model: mujoco.MjModel, id: int, t: str = "body") -> str:
return name
-def name2id(model: mujoco.MjModel, name: str, t: str = "body") -> int:
+def name2id(model: MjModel, name: str, t: str = "body") -> int:
assert (
t in TYPE_NAMES
), f"No type name {t} found. Available type names are: {TYPE_NAMES}"
@@ -292,11 +301,3 @@ def name2id(model: mujoco.MjModel, name: str, t: str = "body") -> int:
if id == -1:
raise ValueError(f"No {t}-name {name} found in model")
return id
-
-
-def euler2quat(euler: ArrayLike) -> NDArray:
- assert len(euler) == 3, "input should be in shape (3,)."
-
- quat = np.zeros(4, dtype=np.float64)
- mujoco.mju_euler2Quat(quat, euler, "xyz")
- return quat
From c60c0e9209595eeaa5777fe5bb2a877e891093b6 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Thu, 19 Sep 2024 02:00:11 +0200
Subject: [PATCH 16/40] Set None as initial value of Pose
---
deformable_gym/helpers/mj_utils.py | 34 +++++++++++++++---------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/deformable_gym/helpers/mj_utils.py b/deformable_gym/helpers/mj_utils.py
index e079ca9..9546a07 100644
--- a/deformable_gym/helpers/mj_utils.py
+++ b/deformable_gym/helpers/mj_utils.py
@@ -8,10 +8,10 @@
# fmt: off
TYPE_NAMES = [
- "body", "xbody", "joint", "dof", "geom", "site", "camera", "light",
- "flex", "mesh", "skin", "hfield", "texture", "material", "pair",
- "exclude", "equality", "tendon", "actuator", "sensor", "numeric",
- "text", "tuple", "key", "plugin",
+ "body", "xbody", "joint", "dof", "geom", "site", "camera",
+ "light", "flex", "mesh", "skin", "hfield", "texture", "material",
+ "pair", "exclude", "equality", "tendon", "actuator", "sensor", "numeric",
+ "text", "tuple", "key", "plugin",
]
# fmt: on
@@ -19,21 +19,21 @@
# -------------------------------- DATA CLASSES --------------------------------#
@dataclass
class Pose:
- position: ArrayLike = field(default_factory=lambda: np.zeros(3))
- orientation: ArrayLike = field(
- default_factory=lambda: np.array([1, 0, 0, 0])
- )
+ position: ArrayLike | None = None
+ orientation: ArrayLike | None = None
def __post_init__(self):
- assert len(self.position) == 3, "position should be in shape (3,)."
- assert (
- len(self.orientation) == 3 or len(self.orientation) == 4
- ), "orientation should be in shape (3,) or (4,)."
- self.position = np.array(self.position)
- if len(self.orientation) == 3:
- self.orientation = euler2quat(self.orientation)
- else:
- self.orientation = np.array(self.orientation)
+ if self.position is not None:
+ assert len(self.position) == 3, "position should be in shape (3,)."
+ self.position = np.array(self.position)
+ if self.orientation is not None:
+ assert (
+ len(self.orientation) == 3 or len(self.orientation) == 4
+ ), "orientation should be in shape (3,) or (4,)."
+ if len(self.orientation) == 3:
+ self.orientation = euler2quat(self.orientation)
+ else:
+ self.orientation = np.array(self.orientation)
# -------------------------------- LOAD MODEL --------------------------------#
From 8500d28d305a40e774c5f72781082f007d7266c6 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Thu, 19 Sep 2024 02:02:22 +0200
Subject: [PATCH 17/40] Use pose defined in mjcf if pos or quat is not
specified in given pose
---
deformable_gym/objects/mj_object.py | 6 ++++--
deformable_gym/robots/mj_robot.py | 6 ++++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/deformable_gym/objects/mj_object.py b/deformable_gym/objects/mj_object.py
index 31c40ab..bae000e 100644
--- a/deformable_gym/objects/mj_object.py
+++ b/deformable_gym/objects/mj_object.py
@@ -62,8 +62,10 @@ def set_pose(
pose (Pose): A Pose object containing the desired position and orientation for the object.
"""
if pose is not None:
- model.body(self.name).pos[:] = pose.position
- model.body(self.name).quat[:] = pose.orientation
+ if pose.position is not None:
+ model.body(self.name).pos[:] = pose.position
+ if pose.orientation is not None:
+ model.body(self.name).quat[:] = pose.orientation
mujoco.mj_forward(model, data)
def get_center_of_mass(self, data: MjData) -> NDArray:
diff --git a/deformable_gym/robots/mj_robot.py b/deformable_gym/robots/mj_robot.py
index 6a0e8f3..117fe20 100644
--- a/deformable_gym/robots/mj_robot.py
+++ b/deformable_gym/robots/mj_robot.py
@@ -134,8 +134,10 @@ def set_pose(
pose (Pose): A Pose object containing the desired position and orientation for the robot's base.
"""
if pose is not None:
- model.body(self.name).pos[:] = pose.position
- model.body(self.name).quat[:] = pose.orientation
+ if pose.position is not None:
+ model.body(self.name).pos[:] = pose.position
+ if pose.orientation is not None:
+ model.body(self.name).quat[:] = pose.orientation
mujoco.mj_forward(model, data)
def set_ctrl(
From a69798175fd010ba2efa744b88ae0e8dc6615431 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Mon, 30 Sep 2024 14:30:32 +0200
Subject: [PATCH 18/40] Change relative weld body
---
assets/objects/mjcf/pillow_fixed.xml | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/assets/objects/mjcf/pillow_fixed.xml b/assets/objects/mjcf/pillow_fixed.xml
index 2afe79d..240a29a 100644
--- a/assets/objects/mjcf/pillow_fixed.xml
+++ b/assets/objects/mjcf/pillow_fixed.xml
@@ -1,15 +1,15 @@
-
+
-
-
+
+
-
+
@@ -17,13 +17,13 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
From 14bce137fd9f2b25d1ed4fa3270fbbf17b5404e4 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Mon, 30 Sep 2024 14:30:57 +0200
Subject: [PATCH 19/40] Register all mujoco grasp environments
---
deformable_gym/__init__.py | 72 +++++++++++++++++++++++++++++++-------
1 file changed, 60 insertions(+), 12 deletions(-)
diff --git a/deformable_gym/__init__.py b/deformable_gym/__init__.py
index 2d57719..caf6ebe 100644
--- a/deformable_gym/__init__.py
+++ b/deformable_gym/__init__.py
@@ -80,16 +80,64 @@
},
)
-register(
- id="MjShadowGraspInsole-v0",
- entry_point="deformable_gym.envs.mujoco.shadow_grasp:ShadowHandGrasp",
- disable_env_checker=True,
- kwargs={"obj_name": "insole_fixed"},
-)
-register(
- id="MjMiaGraspInsole-v0",
- entry_point="deformable_gym.envs.mujoco.mia_grasp:MiaHandGrasp",
- disable_env_checker=True,
- kwargs={"obj_name": "insole_fixed"},
-)
+def register_mj_grasp_envs(
+ version: int = 0,
+):
+ robot_name2id = {
+ "shadow_hand": "FloatingShadow",
+ "mia_hand": "FloatingMia",
+ "ur5_mia": "UR5Mia",
+ "ur10_mia": "UR10Mia",
+ "ur10ft_mia": "UR10FTMia",
+ "ur10e_mia": "UR10EMia",
+ "ur5_shadow": "UR5Shadow",
+ "ur10_shadow": "UR10Shadow",
+ "ur10e_shadow": "UR10EShadow",
+ }
+ obj_name2id = {
+ "insole_fixed": "Insole",
+ "pillow_fixed": "Pillow",
+ }
+
+ mia_hand_cam_config = {
+ "distance": 1.5,
+ "elevation": -40,
+ "azimuth": 120,
+ }
+
+ shadow_hand_cam_config = {
+ "distance": 2,
+ "elevation": -40,
+ "azimuth": 120,
+ }
+
+ hand_on_arm_cam_config = {
+ "distance": 3.5,
+ "elevation": -40,
+ "azimuth": 150,
+ }
+
+ for robot_name, robot_id in robot_name2id.items():
+ for obj_name, obj_id in obj_name2id.items():
+ kwargs = {
+ "robot_name": robot_name,
+ "obj_name": obj_name,
+ "control_type": "joint",
+ }
+ if robot_name == "shadow_hand":
+ cam_config = shadow_hand_cam_config
+ elif robot_name == "mia_hand":
+ cam_config = mia_hand_cam_config
+ else:
+ cam_config = hand_on_arm_cam_config
+ kwargs.update(default_cam_config=cam_config)
+ register(
+ id=f"Mj{robot_id}Grasp{obj_id}-v{version}",
+ entry_point="deformable_gym.envs.mujoco.grasp_env:GraspEnv",
+ disable_env_checker=True,
+ kwargs=kwargs,
+ )
+
+
+register_mj_grasp_envs(version=0)
From e8b7dfacdbad4f74a7b7efaab425d83dd89931c2 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Mon, 30 Sep 2024 14:33:13 +0200
Subject: [PATCH 20/40] Add mujoco environment scene base
---
assets/{objects/mjcf/floor.xml => mj_scene_base.xml} | 3 +--
deformable_gym/helpers/asset_manager.py | 5 +++--
2 files changed, 4 insertions(+), 4 deletions(-)
rename assets/{objects/mjcf/floor.xml => mj_scene_base.xml} (89%)
diff --git a/assets/objects/mjcf/floor.xml b/assets/mj_scene_base.xml
similarity index 89%
rename from assets/objects/mjcf/floor.xml
rename to assets/mj_scene_base.xml
index 01f22b3..76a4b4c 100644
--- a/assets/objects/mjcf/floor.xml
+++ b/assets/mj_scene_base.xml
@@ -1,9 +1,8 @@
-
+
-
diff --git a/deformable_gym/helpers/asset_manager.py b/deformable_gym/helpers/asset_manager.py
index 7b856d8..92ee910 100644
--- a/deformable_gym/helpers/asset_manager.py
+++ b/deformable_gym/helpers/asset_manager.py
@@ -32,6 +32,8 @@
"pillow_fixed": os.path.join(OBJECT_DIR, "pillow_fixed.xml"),
}
+SCENE_BASE = os.path.join(ASSETS_DIR, "mj_scene_base.xml")
+
def load_asset(name: str) -> mujoco.MjModel:
"""
@@ -77,11 +79,10 @@ def create_scene(robot_name: str, obj_name: str) -> str:
obj_name in OBJECTS
), f"Object {obj_name} not found.\n available: {list(OBJECTS.keys())}"
- floor_path = os.path.join(OBJECT_DIR, "floor.xml")
robot_path = ROBOTS.get(robot_name)
obj_path = OBJECTS.get(obj_name)
- scene = include_mjcf(obj_path, [robot_path, floor_path], meshdir=MESH_DIR)
+ scene = include_mjcf(obj_path, [robot_path, SCENE_BASE], meshdir=MESH_DIR)
return scene
From 268e668b534c0e4a517904ed57a8c5fb622828de Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Mon, 30 Sep 2024 14:33:52 +0200
Subject: [PATCH 21/40] Use gymnasium style render method
---
deformable_gym/envs/mujoco/base_mjenv.py | 31 ++++++++++--------
deformable_gym/envs/mujoco/grasp_env.py | 41 +++++++++++++++++-------
2 files changed, 47 insertions(+), 25 deletions(-)
diff --git a/deformable_gym/envs/mujoco/base_mjenv.py b/deformable_gym/envs/mujoco/base_mjenv.py
index 0aec660..02ffdcc 100644
--- a/deformable_gym/envs/mujoco/base_mjenv.py
+++ b/deformable_gym/envs/mujoco/base_mjenv.py
@@ -8,6 +8,7 @@
import mujoco.viewer
import numpy as np
from gymnasium import spaces
+from gymnasium.envs.mujoco.mujoco_rendering import MujocoRenderer
from numpy.typing import ArrayLike, NDArray
from ...helpers import asset_manager as am
@@ -63,17 +64,22 @@ def __init__(
self,
robot_name: str,
obj_name: str,
+ frame_skip: int = 5,
observable_object_pos: bool = True,
control_type: str = "mocap",
max_sim_time: float = 10,
- gui: bool = True,
+ render_mode: str | None = None,
mocap_cfg: Dict[str, str] | None = None,
init_frame: str | None = None,
+ default_cam_config: Dict[str, Any] | None = None,
+ camera_name: str | None = None,
+ camera_id: int | None = None,
):
self.scene = am.create_scene(robot_name, obj_name)
self.model, self.data = mju.load_model_from_string(self.scene)
self.robot = RobotFactory.create(robot_name, control_type)
self.object = ObjectFactory.create(obj_name)
+ self.frame_skip = frame_skip
self.observable_object_pos = observable_object_pos
self.control_type = control_type
if control_type == "mocap":
@@ -83,8 +89,12 @@ def __init__(
self.mocap = MocapControl()
self.init_frame = init_frame
self.max_sim_time = max_sim_time
- self.gui = gui
- self.viewer = None
+ self.render_mode = render_mode
+ self.camera_name = camera_name
+ self.camera_id = camera_id
+ self.renderer = MujocoRenderer(
+ self.model, self.data, default_cam_config
+ )
self.observation_space = self._get_observation_space()
self.action_space = self._get_action_space()
@@ -166,17 +176,12 @@ def reset(
self.object.set_pose(
self.model, self.data, self.object.init_pose.get(self.robot.name)
)
- if self.gui and self.viewer is None:
- self.viewer = mujoco.viewer.launch_passive(
- self.model, self.data, show_left_ui=False, show_right_ui=False
- )
if self.init_frame is not None:
self._load_keyframe(self.init_frame)
if self.control_type == "mocap":
if not self.mocap.eq_is_active(self.model, self.data):
self.mocap.enable_eq(self.model, self.data)
self.mocap.attach_mocap2weld_body(self.model, self.data)
- self.mocap.reset_eq(self.model, self.data)
def _set_state(
self,
@@ -215,13 +220,13 @@ def render(self) -> None:
"""
Render a frame from the MuJoCo simulation as specified by the render_mode.
"""
- assert self.gui, "GUI is not enabled"
- if self.viewer.is_running():
- self.viewer.sync()
+
+ return self.renderer.render(
+ self.render_mode, self.camera_id, self.camera_name
+ )
def close(self) -> None:
"""
Close the environment.
"""
- if self.viewer is not None:
- self.viewer.close()
+ self.renderer.close()
diff --git a/deformable_gym/envs/mujoco/grasp_env.py b/deformable_gym/envs/mujoco/grasp_env.py
index 8342636..bcb2cef 100644
--- a/deformable_gym/envs/mujoco/grasp_env.py
+++ b/deformable_gym/envs/mujoco/grasp_env.py
@@ -1,4 +1,4 @@
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, Dict, Tuple
import mujoco
import mujoco.viewer
@@ -14,28 +14,42 @@ class GraspEnv(BaseMJEnv):
A custom MuJoCo environment for a grasping task, where a robot attempts to grasp an object.
"""
+ meta_data = {
+ "render_mode": [
+ "human",
+ "rgb_array",
+ "depth_array",
+ ],
+ }
+
def __init__(
self,
robot_name: str,
obj_name: str,
+ frame_skip: int = 3,
observable_object_pos: bool = True,
control_type: str = "mocap",
- max_sim_time: float = 5,
- gui: bool = True,
+ max_sim_time: float = 6,
+ render_mode: str | None = "human",
mocap_cfg: Dict[str, str] | None = None,
- init_frame: Optional[str] = None,
- **kwargs,
+ init_frame: str | None = None,
+ default_cam_config: Dict[str, Any] | None = None,
+ camera_name: str | None = None,
+ camera_id: int | None = None,
):
super().__init__(
robot_name,
obj_name,
+ frame_skip,
observable_object_pos,
control_type,
max_sim_time,
- gui,
+ render_mode,
mocap_cfg,
init_frame,
- **kwargs,
+ default_cam_config,
+ camera_name,
+ camera_id,
)
self.reward_range = (-1, 1)
@@ -50,10 +64,14 @@ def _get_observation(self) -> NDArray:
"""
The observation includes the robot's joint qpos in their generalized coordinates,
and optionally, the object's position.
+ If the render mode is set to "rgb_array" or "depth_array", the observation will be an image.
Returns:
- NDArray: A numpy array containing the observation.
+ NDArray: A numpy array or image containing the observation.
"""
+ img = self.render()
+ if self.render_mode == "rgb_array" or self.render_mode == "depth_array":
+ return img
robot_qpos = self.robot.get_qpos(self.model, self.data)
if self.observable_object_pos:
obj_pos = self.object.get_center_of_mass(self.data)
@@ -80,7 +98,7 @@ def _pause_simulation(self, time: float) -> None:
start_time = self.data.time
while self.data.time - start_time < time:
mujoco.mj_step(self.model, self.data)
- if self.gui:
+ if self.render_mode == "human":
self.render()
def _get_reward(self, terminated: bool) -> int:
@@ -131,8 +149,7 @@ def _get_info(self) -> Dict:
Returns:
Dict: A dictionary containing information about the environment.
"""
- if self.gui and self.viewer is not None:
- return {"is_viewer_running": self.viewer.is_running()}
+
return {}
def step(self, action: ArrayLike) -> Tuple[NDArray, int, bool, bool, Dict]:
@@ -152,7 +169,7 @@ def step(self, action: ArrayLike) -> Tuple[NDArray, int, bool, bool, Dict]:
self.robot.set_ctrl(self.model, self.data, action[6:])
else:
self.robot.set_ctrl(self.model, self.data, action)
- mujoco.mj_step(self.model, self.data)
+ mujoco.mj_step(self.model, self.data, nstep=self.frame_skip)
observation = self._get_observation()
terminated = self._is_terminated(sim_time)
From 86f7b6e017b6a5985b89ebe550533f47108dea3e Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Mon, 30 Sep 2024 14:35:21 +0200
Subject: [PATCH 22/40] Adapt initial pose for UR5ShadowGraspPillow
---
deformable_gym/envs/mujoco/init_pose.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/deformable_gym/envs/mujoco/init_pose.py b/deformable_gym/envs/mujoco/init_pose.py
index 8a48631..e8aff82 100644
--- a/deformable_gym/envs/mujoco/init_pose.py
+++ b/deformable_gym/envs/mujoco/init_pose.py
@@ -6,6 +6,10 @@
class ObjectInitPose:
+
+ # FIXME: orientation must keep the same as the quat value defined in the mjcf file
+ # otherwise, we need to reset the eq constraints
+
insole_fixed = {
"shadow_hand": Pose([0.12, -0.05, 0.456], [0, 0, 0]),
"ur5_shadow": Pose([1.4, -0.05, 1], [0, 0, 0]),
@@ -20,7 +24,7 @@ class ObjectInitPose:
pillow_fixed = {
"shadow_hand": Pose([0.12, -0.05, 0.456], [0, 0, np.pi / 2]),
- "ur5_shadow": Pose([1.4, -0.05, 1], [0, 0, np.pi / 2]),
+ "ur5_shadow": Pose([1.3, -0.05, 1], [0, 0, np.pi / 2]),
"ur10_shadow": Pose([1.6, -0.05, 1], [0, 0, np.pi / 2]),
"ur10e_shadow": Pose([1.6, -0.05, 1], [0, 0, np.pi / 2]),
"mia_hand": Pose([0.12, -0.05, 0.456], [0, 0, np.pi / 2]),
From 11bf6e83f657d4ba660d8edee430d3f1e8029953 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Mon, 30 Sep 2024 14:36:34 +0200
Subject: [PATCH 23/40] Use only GraspEnv for all mujoco grasp environments
---
deformable_gym/envs/mujoco/mia_grasp.py | 30 ----------
deformable_gym/envs/mujoco/shadow_grasp.py | 27 ---------
examples/mj_grasp_example.py | 65 ++++++++++++++++++++++
examples/mj_mia_example.py | 19 -------
examples/mj_shadow_example.py | 17 ------
5 files changed, 65 insertions(+), 93 deletions(-)
delete mode 100644 deformable_gym/envs/mujoco/mia_grasp.py
delete mode 100644 deformable_gym/envs/mujoco/shadow_grasp.py
create mode 100644 examples/mj_grasp_example.py
delete mode 100644 examples/mj_mia_example.py
delete mode 100644 examples/mj_shadow_example.py
diff --git a/deformable_gym/envs/mujoco/mia_grasp.py b/deformable_gym/envs/mujoco/mia_grasp.py
deleted file mode 100644
index 6fb5a6e..0000000
--- a/deformable_gym/envs/mujoco/mia_grasp.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from __future__ import annotations
-
-from typing import Dict
-
-from .grasp_env import GraspEnv
-
-
-class MiaHandGrasp(GraspEnv):
- def __init__(
- self,
- obj_name: str,
- observable_object_pos: bool = True,
- control_type: str = "mocap",
- max_sim_time: float = 2,
- gui: bool = True,
- mocap_cfg: Dict[str, str] | None = None,
- init_frame: str | None = None,
- **kwargs,
- ):
- super().__init__(
- "mia_hand",
- obj_name,
- observable_object_pos,
- control_type,
- max_sim_time,
- gui,
- mocap_cfg,
- init_frame,
- **kwargs,
- )
diff --git a/deformable_gym/envs/mujoco/shadow_grasp.py b/deformable_gym/envs/mujoco/shadow_grasp.py
deleted file mode 100644
index 9443f8f..0000000
--- a/deformable_gym/envs/mujoco/shadow_grasp.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from __future__ import annotations
-
-from .grasp_env import GraspEnv
-
-
-class ShadowHandGrasp(GraspEnv):
-
- def __init__(
- self,
- obj_name: str,
- observable_object_pos: bool = True,
- control_type: str = "mocap",
- max_sim_time: float = 1,
- gui: bool = False,
- init_frame: str | None = None,
- **kwargs,
- ):
- super().__init__(
- "shadow_hand",
- obj_name,
- observable_object_pos,
- control_type,
- max_sim_time,
- gui,
- init_frame,
- **kwargs,
- )
diff --git a/examples/mj_grasp_example.py b/examples/mj_grasp_example.py
new file mode 100644
index 0000000..e4306f6
--- /dev/null
+++ b/examples/mj_grasp_example.py
@@ -0,0 +1,65 @@
+from argparse import ArgumentParser
+
+import gymnasium as gym
+
+from deformable_gym.envs.mujoco.grasp_env import GraspEnv
+
+robot_name2id = {
+ "shadow_hand": "FloatingShadow",
+ "mia_hand": "FloatingMia",
+ "ur5_mia": "UR5Mia",
+ "ur10_mia": "UR10Mia",
+ "ur10ft_mia": "UR10FTMia",
+ "ur10e_mia": "UR10EMia",
+ "ur5_shadow": "UR5Shadow",
+ "ur10_shadow": "UR10Shadow",
+ "ur10e_shadow": "UR10EShadow",
+}
+obj_name2id = {
+ "insole_fixed": "Insole",
+ "pillow_fixed": "Pillow",
+}
+
+
+def make_env():
+ parser = ArgumentParser()
+ parser.add_argument(
+ "--robot",
+ type=str,
+ default="mia_hand",
+ help=f"available robots: {list(robot_name2id.keys())}",
+ )
+ parser.add_argument(
+ "--obj",
+ type=str,
+ default="insole_fixed",
+ help=f"available objects: {list(obj_name2id.keys())}",
+ )
+ parser.add_argument(
+ "--control",
+ type=str,
+ default="joint",
+ help="available control types: ['joint', 'mocap']",
+ )
+ args = parser.parse_args()
+ robot_id = robot_name2id[args.robot]
+ obj_id = obj_name2id[args.obj]
+ env_id = f"Mj{robot_id}Grasp{obj_id}-v0"
+ control_type = args.control
+ env = gym.make(env_id, control_type=control_type)
+ return env
+
+
+if __name__ == "__main__":
+ env = make_env()
+ episode = 0
+ n_episodes = 3
+ observation, info = env.reset()
+ while episode < n_episodes:
+ action = env.action_space.sample()
+ observation, reward, terminate, truncated, info = env.step(action)
+ if terminate:
+ episode += 1
+ print(f"Episode {episode} finished with reward {reward}")
+ observation, info = env.reset()
+ env.close()
diff --git a/examples/mj_mia_example.py b/examples/mj_mia_example.py
deleted file mode 100644
index 619f1b3..0000000
--- a/examples/mj_mia_example.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import gymnasium as gym
-
-from deformable_gym.envs.mujoco.mia_grasp import MiaHandGrasp
-
-if __name__ == "__main__":
- env = gym.make("MjMiaGraspInsole-v0")
- episode = 0
- n_episodes = 3
- observation, info = env.reset()
- #env.render()
- while episode < n_episodes:
- action = env.action_space.sample()
- observation, reward, terminate, truncated, info = env.step(action)
- #env.render()
- if terminate:
- episode += 1
- print(f"Episode {episode} finished with reward {reward}")
- observation, info = env.reset()
- env.close()
diff --git a/examples/mj_shadow_example.py b/examples/mj_shadow_example.py
deleted file mode 100644
index cc21c92..0000000
--- a/examples/mj_shadow_example.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import gymnasium as gym
-
-from deformable_gym.envs.mujoco.shadow_grasp import ShadowHandGrasp
-
-if __name__ == "__main__":
- env = gym.make("MjShadowGraspInsole-v0")
- episode = 0
- n_episodes = 3
- observation, info = env.reset()
- while episode < n_episodes:
- action = env.action_space.sample()
- observation, reward, terminate, truncated, info = env.step(action)
- if terminate:
- episode += 1
- print(f"Episode {episode} finished with reward {reward}")
- observation, info = env.reset()
- env.close()
From fa91ecbd23b798964477715f33a5f4d5d5b17763 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Mon, 30 Sep 2024 14:37:21 +0200
Subject: [PATCH 24/40] Reset equality constraint after reset mocap pose
---
deformable_gym/helpers/mj_mocap_control.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/deformable_gym/helpers/mj_mocap_control.py b/deformable_gym/helpers/mj_mocap_control.py
index a6c97f5..66101b5 100644
--- a/deformable_gym/helpers/mj_mocap_control.py
+++ b/deformable_gym/helpers/mj_mocap_control.py
@@ -55,6 +55,7 @@ def attach_mocap2weld_body(self, model: MjModel, data: MjData) -> None:
model, data, self.body_name, Pose(body_xpos, body_xquat)
)
mju.set_mocap_pose(model, data, Pose(body_xpos, body_xquat))
+ self.reset_eq(model, data)
def set_ctrl(self, model: MjModel, data: MjData, ctrl: NDArray) -> None:
assert len(ctrl) == 6, "mocap ctrl should be in shape (6,)."
From dc2bc13e431b40e45aad54df18fdc1c228b9161b Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Mon, 30 Sep 2024 14:37:57 +0200
Subject: [PATCH 25/40] Add tests for mocap control
---
tests/envs/mujoco/test_mj_shadow_grasp.py | 361 +++++++++-------------
tests/objects/mujoco/test_insole_fixed.py | 4 +-
tests/robots/mujoco/test_shadow_hand.py | 261 ++++++++++------
3 files changed, 311 insertions(+), 315 deletions(-)
diff --git a/tests/envs/mujoco/test_mj_shadow_grasp.py b/tests/envs/mujoco/test_mj_shadow_grasp.py
index c3618e7..128b967 100644
--- a/tests/envs/mujoco/test_mj_shadow_grasp.py
+++ b/tests/envs/mujoco/test_mj_shadow_grasp.py
@@ -1,293 +1,224 @@
import numpy as np
import pytest
from gymnasium.spaces import Box
-from numpy.testing import assert_allclose, assert_array_equal
+from numpy.testing import (
+ assert_allclose,
+ assert_array_almost_equal,
+ assert_array_equal,
+)
-from deformable_gym.envs.mujoco.shadow_grasp import ShadowHandGrasp
+from deformable_gym.envs.mujoco.grasp_env import GraspEnv
SEED = 42
-OBS_SPACE = Box(
+# fmt: off
+OBS_SPACE_WITHOUT_OBJ_POS = Box(
low=np.array(
[
- -0.5,
- -0.5,
- -0.5,
- -1.570796,
- -1.570796,
- -1.570796,
- -0.523599,
- -0.698132,
- -0.349066,
- -0.261799,
- 0.0,
- 0.0,
- -0.349066,
- -0.261799,
- 0.0,
- 0.0,
- -0.349066,
- -0.261799,
- 0.0,
- 0.0,
- 0.0,
- -0.349066,
- -0.261799,
- 0.0,
- 0.0,
- -1.0472,
- 0.0,
- -0.20944,
- -0.698132,
- -0.261799,
+ -0.5 , -0.5 , -0.5 , -1.570796, -1.570796, -1.570796,
+ -0.523599, -0.698132, -0.349066, -0.261799, 0. , 0. ,
+ -0.349066, -0.261799, 0. , 0. , -0.349066, -0.261799,
+ 0. , 0. , 0. , -0.349066, -0.261799, 0. ,
+ 0. , -1.0472 , 0. , -0.20944 , -0.698132, -0.261799
]
),
high=np.array(
[
- 0.5,
- 0.5,
- 0.5,
- 1.570796,
- 1.570796,
- 1.570796,
- 0.174533,
- 0.488692,
- 0.349066,
- 1.5708,
- 1.5708,
- 1.5708,
- 0.349066,
- 1.5708,
- 1.5708,
- 1.5708,
- 0.349066,
- 1.5708,
- 1.5708,
- 1.5708,
- 0.785398,
- 0.349066,
- 1.5708,
- 1.5708,
- 1.5708,
- 1.0472,
- 1.22173,
- 0.20944,
- 0.698132,
- 1.5708,
+ 0.5 , 0.5 , 0.5 , 1.570796, 1.570796, 1.570796,
+ 0.174533, 0.488692, 0.349066, 1.5708 , 1.5708 , 1.5708 ,
+ 0.349066, 1.5708 , 1.5708 , 1.5708 , 0.349066, 1.5708 ,
+ 1.5708 , 1.5708 , 0.785398, 0.349066, 1.5708 , 1.5708 ,
+ 1.5708 , 1.0472 , 1.22173 , 0.20944 , 0.698132, 1.5708
]
),
shape=(30,),
dtype=np.float64,
)
-OBS_SPACE_W_OBJ_POS = Box(
+
+OBS_SPACE_WITH_OBJ_POS = Box(
low=np.array(
[
- -0.5,
- -0.5,
- -0.5,
- -1.570796,
- -1.570796,
- -1.570796,
- -0.523599,
- -0.698132,
- -0.349066,
- -0.261799,
- 0.0,
- 0.0,
- -0.349066,
- -0.261799,
- 0.0,
- 0.0,
- -0.349066,
- -0.261799,
- 0.0,
- 0.0,
- 0.0,
- -0.349066,
- -0.261799,
- 0.0,
- 0.0,
- -1.0472,
- 0.0,
- -0.20944,
- -0.698132,
- -0.261799,
- -np.inf,
- -np.inf,
- -np.inf,
+ -0.5 , -0.5 , -0.5 , -1.570796, -1.570796, -1.570796,
+ -0.523599, -0.698132, -0.349066, -0.261799, 0. , 0. ,
+ -0.349066, -0.261799, 0. , 0. , -0.349066, -0.261799,
+ 0. , 0. , 0. , -0.349066, -0.261799, 0. ,
+ 0. , -1.0472 , 0. , -0.20944 , -0.698132, -0.261799,
+ -np.inf, -np.inf, -np.inf
]
),
high=np.array(
[
- 0.5,
- 0.5,
- 0.5,
- 1.570796,
- 1.570796,
- 1.570796,
- 0.174533,
- 0.488692,
- 0.349066,
- 1.5708,
- 1.5708,
- 1.5708,
- 0.349066,
- 1.5708,
- 1.5708,
- 1.5708,
- 0.349066,
- 1.5708,
- 1.5708,
- 1.5708,
- 0.785398,
- 0.349066,
- 1.5708,
- 1.5708,
- 1.5708,
- 1.0472,
- 1.22173,
- 0.20944,
- 0.698132,
- 1.5708,
- np.inf,
- np.inf,
- np.inf,
+ 0.5 , 0.5 , 0.5 , 1.570796, 1.570796, 1.570796,
+ 0.174533, 0.488692, 0.349066, 1.5708 , 1.5708 , 1.5708 ,
+ 0.349066, 1.5708 , 1.5708 , 1.5708 , 0.349066, 1.5708 ,
+ 1.5708 , 1.5708 , 0.785398, 0.349066, 1.5708 , 1.5708 ,
+ 1.5708 , 1.0472 , 1.22173 , 0.20944 , 0.698132, 1.5708 ,
+ np.inf, np.inf, np.inf
]
),
shape=(33,),
dtype=np.float64,
)
-ACTION_SPACE = Box(
+
+ACTION_SPACE_JOINT = Box(
low=np.array(
[
- -0.523599,
- -0.698132,
- -1.0472,
- 0.0,
- -0.20944,
- -0.698132,
- -0.261799,
- -0.349066,
- -0.261799,
- 0.0,
- -0.349066,
- -0.261799,
- 0.0,
- -0.349066,
- -0.261799,
- 0.0,
- 0.0,
- -0.349066,
- -0.261799,
- 0.0,
- -1.0,
- -1.0,
- -1.0,
- -1.0,
- -1.0,
- -1.0,
+ -0.01745329, -0.01745329, -0.01745329, -0.01745329, -0.01745329,
+ -0.01745329, -0.01745329, -0.01745329, -0.01745329, -0.005 ,
+ -0.01745329, -0.01745329, -0.005 , -0.01745329, -0.01745329,
+ -0.005 , -0.01745329, -0.01745329, -0.01745329, -0.01745329,
+ -0.005 , -0.005 , -0.005 , -0.01745329, -0.01745329,
+ -0.01745329
]
),
high=np.array(
[
- 0.174533,
- 0.488692,
- 1.0472,
- 1.22173,
- 0.20944,
- 0.698132,
- 1.5708,
- 0.349066,
- 1.5708,
- 3.1415,
- 0.349066,
- 1.5708,
- 3.1415,
- 0.349066,
- 1.5708,
- 3.1415,
- 0.785398,
- 0.349066,
- 1.5708,
- 3.1415,
- 1.0,
- 1.0,
- 1.0,
- 1.0,
- 1.0,
- 1.0,
+ 0.01745329, 0.01745329, 0.01745329, 0.01745329, 0.01745329,
+ 0.01745329, 0.01745329, 0.01745329, 0.01745329, 0.005 ,
+ 0.01745329, 0.01745329, 0.005 , 0.01745329, 0.01745329,
+ 0.005 , 0.01745329, 0.01745329, 0.01745329, 0.01745329,
+ 0.005 , 0.005 , 0.005 , 0.01745329, 0.01745329,
+ 0.01745329
]
),
shape=(26,),
dtype=np.float64,
)
+ACTION_SPACE_MOCAP = Box(
+ low=np.array(
+ [
+ -0.005 , -0.005 , -0.005 , -0.03141593, -0.03141593,
+ -0.03141593, -0.01745329, -0.01745329, -0.01745329, -0.01745329,
+ -0.01745329, -0.01745329, -0.01745329, -0.01745329, -0.01745329,
+ -0.005 , -0.01745329, -0.01745329, -0.005 , -0.01745329,
+ -0.01745329, -0.005 , -0.01745329, -0.01745329, -0.01745329,
+ -0.01745329
+ ]
+ ),
+ high=np.array(
+ [
+ 0.005 , 0.005 , 0.005 , 0.03141593, 0.03141593,
+ 0.03141593, 0.01745329, 0.01745329, 0.01745329, 0.01745329,
+ 0.01745329, 0.01745329, 0.01745329, 0.01745329, 0.01745329,
+ 0.005 , 0.01745329, 0.01745329, 0.005 , 0.01745329,
+ 0.01745329, 0.005 , 0.01745329, 0.01745329, 0.01745329,
+ 0.01745329
+ ]
+ ),
+ shape=(26,),
+ dtype=np.float64,
+)
+# fmt: on
+
-@pytest.fixture
-def env():
- env = ShadowHandGrasp(
- obj_name="insole_fixed", gui=False, observable_object_pos=False
+def make_env(ctrl_type: str, observable_object_pos: bool):
+ return GraspEnv(
+ robot_name="shadow_hand",
+ obj_name="insole_fixed",
+ control_type=ctrl_type,
+ observable_object_pos=observable_object_pos,
)
+
+
+@pytest.fixture
+def env_without_obj_pos():
+ env = make_env("joint", False)
env.action_space.seed(SEED)
return env
@pytest.fixture
-def env_w_obj_pos():
- env = ShadowHandGrasp(
- obj_name="insole_fixed", gui=False, observable_object_pos=True
- )
+def env_with_obj_pos():
+ env = make_env("joint", True)
+ env.action_space.seed(SEED)
+ return env
+
+
+@pytest.fixture
+def env_joint_ctrl():
+ env = make_env("joint", False)
+ env.action_space.seed(SEED)
+ return env
+
+
+@pytest.fixture
+def env_mocap_ctrl():
+ env = make_env("mocap", False)
env.action_space.seed(SEED)
return env
-def test_observation_space(
- env: ShadowHandGrasp, env_w_obj_pos: ShadowHandGrasp
-):
- assert_array_equal(env.observation_space.low, OBS_SPACE.low)
- assert_array_equal(env.observation_space.high, OBS_SPACE.high)
- assert env.observation_space.shape[0] == OBS_SPACE.shape[0]
+def test_observation_space(env_without_obj_pos, env_with_obj_pos):
+ assert_array_equal(
+ env_without_obj_pos.observation_space.low, OBS_SPACE_WITHOUT_OBJ_POS.low
+ )
+ assert_array_equal(
+ env_without_obj_pos.observation_space.high,
+ OBS_SPACE_WITHOUT_OBJ_POS.high,
+ )
+ assert (
+ env_without_obj_pos.observation_space.shape[0]
+ == OBS_SPACE_WITHOUT_OBJ_POS.shape[0]
+ )
+ assert_array_equal(
+ env_with_obj_pos.observation_space.low, OBS_SPACE_WITH_OBJ_POS.low
+ )
assert_array_equal(
- env_w_obj_pos.observation_space.low, OBS_SPACE_W_OBJ_POS.low
+ env_with_obj_pos.observation_space.high, OBS_SPACE_WITH_OBJ_POS.high
)
assert (
- env_w_obj_pos.observation_space.shape[0] == OBS_SPACE_W_OBJ_POS.shape[0]
+ env_with_obj_pos.observation_space.shape[0]
+ == OBS_SPACE_WITH_OBJ_POS.shape[0]
)
-def test_action_space(env: ShadowHandGrasp):
- assert_array_equal(env.action_space.low, ACTION_SPACE.low)
- assert_array_equal(env.action_space.high, ACTION_SPACE.high)
- assert env.action_space.shape[0] == ACTION_SPACE.shape[0]
+def test_action_space(env_joint_ctrl, env_mocap_ctrl):
+ assert_array_almost_equal(
+ env_joint_ctrl.action_space.low, ACTION_SPACE_JOINT.low, decimal=8
+ )
+ assert_array_almost_equal(
+ env_joint_ctrl.action_space.high, ACTION_SPACE_JOINT.high, decimal=8
+ )
+ assert env_joint_ctrl.action_space.shape[0] == ACTION_SPACE_JOINT.shape[0]
+ assert_array_almost_equal(
+ env_mocap_ctrl.action_space.low, ACTION_SPACE_MOCAP.low, decimal=8
+ )
+ assert_array_almost_equal(
+ env_mocap_ctrl.action_space.high, ACTION_SPACE_MOCAP.high, decimal=8
+ )
+ assert env_mocap_ctrl.action_space.shape[0] == ACTION_SPACE_MOCAP.shape[0]
-def test_reset(env: ShadowHandGrasp, env_w_obj_pos: ShadowHandGrasp):
- obs, info = env.reset(seed=SEED)
+def test_reset(env_with_obj_pos, env_without_obj_pos):
+ obs, info = env_without_obj_pos.reset(seed=SEED)
assert obs.shape[0] == 30
- obs, info = env_w_obj_pos.reset(seed=SEED)
+ env_without_obj_pos.close()
+ obs, info = env_with_obj_pos.reset(seed=SEED)
assert obs.shape[0] == 33
+ env_with_obj_pos.close()
-def test_episode_reproducibility(env):
+def test_episode_reproducibility(env_without_obj_pos: GraspEnv):
observations = []
termination_flags = []
actions = []
-
+ # env_without_obj_pos.render_mode = "rgb_array"
for _ in range(2):
- observation, _ = env.reset(seed=SEED)
- env.action_space.seed(SEED)
-
+ observation, _ = env_without_obj_pos.reset(seed=SEED)
+ env_without_obj_pos.action_space.seed(SEED)
observations.append([observation])
terminated = False
termination_flags.append([terminated])
actions.append([])
while not terminated:
- action = env.action_space.sample()
+ action = env_without_obj_pos.action_space.sample()
actions[-1].append(action)
- observation, reward, terminated, truncated, info = env.step(action)
-
+ observation, reward, terminated, truncated, info = (
+ env_without_obj_pos.step(action)
+ )
observations[-1].append(observation)
termination_flags[-1].append(terminated)
-
+ env_without_obj_pos.close()
assert_allclose(actions[0], actions[1])
assert_allclose(observations[0], observations[1])
assert_allclose(termination_flags[0], termination_flags[1])
diff --git a/tests/objects/mujoco/test_insole_fixed.py b/tests/objects/mujoco/test_insole_fixed.py
index 2e7413d..72769fb 100644
--- a/tests/objects/mujoco/test_insole_fixed.py
+++ b/tests/objects/mujoco/test_insole_fixed.py
@@ -3,8 +3,8 @@
import pytest
from numpy.testing import assert_array_equal
+import deformable_gym.helpers.asset_manager as am
from deformable_gym.helpers import mj_utils as mju
-from deformable_gym.helpers.asset_manager import AssetManager
from deformable_gym.objects.mj_object import InsoleFixed
@@ -15,7 +15,7 @@ def obj():
@pytest.fixture
def model():
- return AssetManager().load_asset("insole_fixed")
+ return am.load_asset("insole_fixed")
@pytest.fixture
diff --git a/tests/robots/mujoco/test_shadow_hand.py b/tests/robots/mujoco/test_shadow_hand.py
index 83e127c..c7ed280 100644
--- a/tests/robots/mujoco/test_shadow_hand.py
+++ b/tests/robots/mujoco/test_shadow_hand.py
@@ -3,7 +3,7 @@
import pytest
from numpy.testing import assert_array_almost_equal, assert_array_equal
-from deformable_gym.helpers.asset_manager import AssetManager
+import deformable_gym.helpers.asset_manager as am
from deformable_gym.helpers.mj_utils import Pose
from deformable_gym.robots.mj_robot import ShadowHand
@@ -14,53 +14,53 @@
"ee_OX",
"ee_OY",
"ee_OZ",
- "lh_WRJ2",
- "lh_WRJ1",
- "lh_FFJ4",
- "lh_FFJ3",
- "lh_FFJ2",
- "lh_FFJ1",
- "lh_MFJ4",
- "lh_MFJ3",
- "lh_MFJ2",
- "lh_MFJ1",
- "lh_RFJ4",
- "lh_RFJ3",
- "lh_RFJ2",
- "lh_RFJ1",
- "lh_LFJ5",
- "lh_LFJ4",
- "lh_LFJ3",
- "lh_LFJ2",
- "lh_LFJ1",
- "lh_THJ5",
- "lh_THJ4",
- "lh_THJ3",
- "lh_THJ2",
- "lh_THJ1",
+ "rh_WRJ2",
+ "rh_WRJ1",
+ "rh_FFJ4",
+ "rh_FFJ3",
+ "rh_FFJ2",
+ "rh_FFJ1",
+ "rh_MFJ4",
+ "rh_MFJ3",
+ "rh_MFJ2",
+ "rh_MFJ1",
+ "rh_RFJ4",
+ "rh_RFJ3",
+ "rh_RFJ2",
+ "rh_RFJ1",
+ "rh_LFJ5",
+ "rh_LFJ4",
+ "rh_LFJ3",
+ "rh_LFJ2",
+ "rh_LFJ1",
+ "rh_THJ5",
+ "rh_THJ4",
+ "rh_THJ3",
+ "rh_THJ2",
+ "rh_THJ1",
]
-ACTUATORS = [
- "lh_A_WRJ2",
- "lh_A_WRJ1",
- "lh_A_THJ5",
- "lh_A_THJ4",
- "lh_A_THJ3",
- "lh_A_THJ2",
- "lh_A_THJ1",
- "lh_A_FFJ4",
- "lh_A_FFJ3",
- "lh_A_FFJ0",
- "lh_A_MFJ4",
- "lh_A_MFJ3",
- "lh_A_MFJ0",
- "lh_A_RFJ4",
- "lh_A_RFJ3",
- "lh_A_RFJ0",
- "lh_A_LFJ5",
- "lh_A_LFJ4",
- "lh_A_LFJ3",
- "lh_A_LFJ0",
+ACTUATORS_JOINT = [
+ "rh_A_WRJ2",
+ "rh_A_WRJ1",
+ "rh_A_THJ5",
+ "rh_A_THJ4",
+ "rh_A_THJ3",
+ "rh_A_THJ2",
+ "rh_A_THJ1",
+ "rh_A_FFJ4",
+ "rh_A_FFJ3",
+ "rh_A_FFJ0",
+ "rh_A_MFJ4",
+ "rh_A_MFJ3",
+ "rh_A_MFJ0",
+ "rh_A_RFJ4",
+ "rh_A_RFJ3",
+ "rh_A_RFJ0",
+ "rh_A_LFJ5",
+ "rh_A_LFJ4",
+ "rh_A_LFJ3",
+ "rh_A_LFJ0",
"ee_A_X",
"ee_A_Y",
"ee_A_Z",
@@ -69,41 +69,88 @@
"ee_A_OZ",
]
-CTRL_RANGE = np.array(
+ACTUATORS_MOCAP = [
+ "rh_A_WRJ2",
+ "rh_A_WRJ1",
+ "rh_A_THJ5",
+ "rh_A_THJ4",
+ "rh_A_THJ3",
+ "rh_A_THJ2",
+ "rh_A_THJ1",
+ "rh_A_FFJ4",
+ "rh_A_FFJ3",
+ "rh_A_FFJ0",
+ "rh_A_MFJ4",
+ "rh_A_MFJ3",
+ "rh_A_MFJ0",
+ "rh_A_RFJ4",
+ "rh_A_RFJ3",
+ "rh_A_RFJ0",
+ "rh_A_LFJ5",
+ "rh_A_LFJ4",
+ "rh_A_LFJ3",
+ "rh_A_LFJ0",
+]
+CTRL_RANGE_JOINT = np.array(
+ [
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.005, 0.005],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.005, 0.005],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.005, 0.005],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.005, 0.005],
+ [-0.005, 0.005],
+ [-0.005, 0.005],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ ]
+)
+
+CTRL_RANGE_MOCAP = np.array(
[
- [-0.523599, 0.174533],
- [-0.698132, 0.488692],
- [-1.0472, 1.0472],
- [0.0, 1.22173],
- [-0.20944, 0.20944],
- [-0.698132, 0.698132],
- [-0.261799, 1.5708],
- [-0.349066, 0.349066],
- [-0.261799, 1.5708],
- [0.0, 3.1415],
- [-0.349066, 0.349066],
- [-0.261799, 1.5708],
- [0.0, 3.1415],
- [-0.349066, 0.349066],
- [-0.261799, 1.5708],
- [0.0, 3.1415],
- [0.0, 0.785398],
- [-0.349066, 0.349066],
- [-0.261799, 1.5708],
- [0.0, 3.1415],
- [-1.0, 1.0],
- [-1.0, 1.0],
- [-1.0, 1.0],
- [-1.0, 1.0],
- [-1.0, 1.0],
- [-1.0, 1.0],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.005, 0.005],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.005, 0.005],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.005, 0.005],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
+ [-0.01745329, 0.01745329],
]
)
@pytest.fixture
def model():
- return AssetManager().load_asset("shadow_hand")
+ return am.load_asset("shadow_hand")
@pytest.fixture
@@ -112,36 +159,52 @@ def data(model):
@pytest.fixture
-def shadow_hand():
- return ShadowHand()
+def shadow_hand_joint():
+ return ShadowHand(control_type="joint")
+
+
+@pytest.fixture
+def shadow_hand_mocap():
+ return ShadowHand(control_type="mocap")
-def test_name(shadow_hand):
- assert shadow_hand.name == "shadow_hand"
+def test_name(shadow_hand_joint, shadow_hand_mocap):
+ assert shadow_hand_joint.name == "shadow_hand"
+ assert shadow_hand_mocap.name == "shadow_hand"
-def test_n_qpos(shadow_hand):
- assert shadow_hand.n_qpos == 30
+def test_n_qpos(shadow_hand_joint, shadow_hand_mocap):
+ assert shadow_hand_joint.n_qpos == 30
+ assert shadow_hand_mocap.n_qpos == 30
-def test_dof(shadow_hand):
- assert shadow_hand.dof == 30
+def test_dof(shadow_hand_joint, shadow_hand_mocap):
+ assert shadow_hand_joint.dof == 30
+ assert shadow_hand_mocap.dof == 30
-def test_n_actuator(shadow_hand):
- assert shadow_hand.n_actuator == 26
+def test_n_actuator(shadow_hand_joint, shadow_hand_mocap):
+ assert shadow_hand_joint.n_actuator == 26
+ assert shadow_hand_mocap.n_actuator == 20
-def test_ctrl_range(shadow_hand):
- assert_array_equal(shadow_hand.ctrl_range, CTRL_RANGE)
+def test_ctrl_range(shadow_hand_joint, shadow_hand_mocap):
+ assert_array_almost_equal(
+ shadow_hand_joint.ctrl_range, CTRL_RANGE_JOINT, decimal=8
+ )
+ assert_array_almost_equal(
+ shadow_hand_mocap.ctrl_range, CTRL_RANGE_MOCAP, decimal=8
+ )
-def test_joints(shadow_hand):
- assert shadow_hand.joints == JOINTS
+def test_joints(shadow_hand_joint, shadow_hand_mocap):
+ assert shadow_hand_joint.joints == JOINTS
+ assert shadow_hand_mocap.joints == JOINTS
-def test_actuators(shadow_hand):
- assert shadow_hand.actuators == ACTUATORS
+def test_actuators(shadow_hand_joint, shadow_hand_mocap):
+ assert shadow_hand_joint.actuators == ACTUATORS_JOINT
+ assert shadow_hand_mocap.actuators == ACTUATORS_MOCAP
@pytest.mark.parametrize(
@@ -151,23 +214,25 @@ def test_actuators(shadow_hand):
Pose([1, 1, 1], [np.pi, np.pi, np.pi]),
],
)
-def test_set_pose(model, data, shadow_hand, pose):
- shadow_hand.set_pose(model, data, pose)
- assert_array_equal(pose.position, model.body(shadow_hand.name).pos)
- assert_array_equal(pose.orientation, model.body(shadow_hand.name).quat)
+def test_set_pose(model, data, shadow_hand_joint, pose):
+ shadow_hand_joint.set_pose(model, data, pose)
+ assert_array_equal(pose.position, model.body(shadow_hand_joint.name).pos)
+ assert_array_equal(
+ pose.orientation, model.body(shadow_hand_joint.name).quat
+ )
-def test_get_qpos(model, data, shadow_hand):
+def test_get_qpos(model, data, shadow_hand_joint):
for _ in range(100):
data.ctrl = 1
mujoco.mj_step(model, data)
- qpos = shadow_hand.get_qpos(model, data)
+ qpos = shadow_hand_joint.get_qpos(model, data)
assert_array_almost_equal(qpos, data.qpos)
-def test_get_qvel(model, data, shadow_hand):
+def test_get_qvel(model, data, shadow_hand_joint):
for _ in range(100):
data.ctrl = 1
mujoco.mj_step(model, data)
- qvel = shadow_hand.get_qvel(model, data)
+ qvel = shadow_hand_joint.get_qvel(model, data)
assert_array_almost_equal(qvel, data.qvel)
From 971860805099ec8ca23bfcc5244fcaabaf82661d Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Mon, 30 Sep 2024 14:39:38 +0200
Subject: [PATCH 26/40] Update README
---
README.md | 61 ++++++++++++++++++++++++-------------------------------
1 file changed, 26 insertions(+), 35 deletions(-)
diff --git a/README.md b/README.md
index f678a15..4f39b6a 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,17 @@
[![Tests](https://github.com/dfki-ric/deformable_gym/actions/workflows/test.yaml/badge.svg)](https://github.com/dfki-ric/deformable_gym/actions/workflows/test.yaml)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
+
# DeformableGym
-This repository contains a collection of [gymnasium](https://github.com/Farama-Foundation/Gymnasium) environments built with [PyBullet](https://pybullet.org/) and [MuJoCo](https://github.com/google-deepmind/mujoco).
+This repository contains a collection of [gymnasium](https://github.com/Farama-Foundation/Gymnasium) environments built with [PyBullet](https://pybullet.org/) and [MuJoCo](https://github.com/google-deepmind/mujoco).
In these environments, the agent needs to learn to grasp deformable 3D objects such as shoe insoles or pillows from sparse reward signals.
-
-## Installation
+## Installation
```bash
git clone git@github.com:dfki-ric/deformable_gym.git
@@ -25,19 +25,16 @@ pip install -e .
## Available environments
-| Environment Name | PyBullet | MuJoCo |
-|---------------------------|:-------------------:|:------------------:|
-| FloatingMiaGraspInsole | :heavy_check_mark: | :heavy_check_mark: |
-| FloatingShadowGraspInsole | :heavy_check_mark: | :heavy_check_mark: |
-| FloatingMiaGraspPillow | :heavy_check_mark: | :x: |
-| FloatingShadowGraspPillow | :heavy_check_mark: | :x: |
-| URMiaGraspInsole | :heavy_check_mark: | :x: |
-| URShadowGraspInsole | :heavy_check_mark: | :x: |
-| URMiaGraspPillow | :heavy_check_mark: | :x: |
-| URShadowGraspPillow | :heavy_check_mark: | :x: |
-
-
-
+| Environment Name | PyBullet | MuJoCo |
+| ------------------------- | :----------------: | :----------------: |
+| FloatingMiaGraspInsole | :heavy_check_mark: | :heavy_check_mark: |
+| FloatingShadowGraspInsole | :heavy_check_mark: | :heavy_check_mark: |
+| FloatingMiaGraspPillow | :heavy_check_mark: | :heavy_check_mark: |
+| FloatingShadowGraspPillow | :heavy_check_mark: | :heavy_check_mark: |
+| URMiaGraspInsole | :heavy_check_mark: | :heavy_check_mark: |
+| URShadowGraspInsole | :heavy_check_mark: | :heavy_check_mark: |
+| URMiaGraspPillow | :heavy_check_mark: | :heavy_check_mark: |
+| URShadowGraspPillow | :heavy_check_mark: | :heavy_check_mark: |
### Known Issues
@@ -72,8 +69,8 @@ import gymnasium
Floating Mia Example
=========
-This is an example of how to use the FloatingMiaGraspEnv. A random policy is then
-used to generate ten episodes.
+This is an example of how to use the FloatingMiaGraspEnv. A random policy is then
+used to generate ten episodes.
"""
@@ -93,19 +90,20 @@ while num_episodes <= 10:
if terminated or truncated:
print(f"Episode finished with return {episode_return}!")
num_episodes += 1
-
+
env.reset()
```
-
## Documentation
+
The documentation can be found in the directory doc. To build the documentation, run e.g. (on linux):
```bash
cd doc
make html
```
+
The HTML documentation is now located at doc/build/html/index.html. You need the following packages to build the documentation:
```bash
@@ -114,14 +112,15 @@ pip install numpydoc sphinx sphinx-gallery sphinx-bootstrap-theme
## Contributing
-If you wish to report bugs, please use the [issue tracker](https://github.com/dfki-ric/deformable_gym/issues). If you would like to contribute to DeformableGym, just open an issue or a
-[pull request](https://github.com/dfki-ric/deformable_gym/pulls). The target branch for
-merge requests is the development branch. The development branch will be merged to main for new releases. If you have
+If you wish to report bugs, please use the [issue tracker](https://github.com/dfki-ric/deformable_gym/issues). If you would like to contribute to DeformableGym, just open an issue or a
+[pull request](https://github.com/dfki-ric/deformable_gym/pulls). The target branch for
+merge requests is the development branch. The development branch will be merged to main for new releases. If you have
questions about the software, you should ask them in the discussion section.
The recommended workflow to add a new feature, add documentation, or fix a bug is the following:
+
- Push your changes to a branch (e.g. feature/x, doc/y, or fix/z) of your fork of the deformable_gym repository.
-- Open a pull request to the latest development branch. There is usually an open merge request from the latest development branch to the main branch.
+- Open a pull request to the latest development branch. There is usually an open merge request from the latest development branch to the main branch.
- When the latest development branch is merged to the main branch, a new release will be made.
Note that there is a checklist for new features.
@@ -143,8 +142,6 @@ url = {https://deformable-workshop.github.io/icra2023/},
}
```
-
-
## Releases
### Semantic Versioning
@@ -155,13 +152,12 @@ version will be incremented when new functionality is added in a backwards
compatible manner, and the patch version is incremented for bugfixes,
documentation, etc.
-
## Funding
This library has been developed initially at the
-[Robotics Innovation Center](http://robotik.dfki-bremen.de/en/startpage.html) of the
-[German Research Center for Artificial Intelligence (DFKI)](http://www.dfki.de) in Bremen together with the
-[Robotics Group](https://robotik.dfki-bremen.de/en/about-us/university-of-bremen-robotics-group.html) of the
+[Robotics Innovation Center](http://robotik.dfki-bremen.de/en/startpage.html) of the
+[German Research Center for Artificial Intelligence (DFKI)](http://www.dfki.de) in Bremen together with the
+[Robotics Group](https://robotik.dfki-bremen.de/en/about-us/university-of-bremen-robotics-group.html) of the
[University of Bremen](http://www.uni-bremen.de/en.html). At this phase, the work was supported through a grant from the European
Commission (870142).
@@ -169,8 +165,3 @@ Commission (870142).
-
-
-
-
-
From 2eb34e23eb3f99f8f3d379a31bb6890a23c85142 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Tue, 1 Oct 2024 16:46:07 +0200
Subject: [PATCH 27/40] Add mjSpec utils
---
deformable_gym/helpers/mj_utils.py | 38 +++++++++++++++++++++++++++---
1 file changed, 35 insertions(+), 3 deletions(-)
diff --git a/deformable_gym/helpers/mj_utils.py b/deformable_gym/helpers/mj_utils.py
index 9546a07..f627339 100644
--- a/deformable_gym/helpers/mj_utils.py
+++ b/deformable_gym/helpers/mj_utils.py
@@ -1,9 +1,9 @@
-from dataclasses import dataclass, field
-from typing import List, Tuple
+from dataclasses import dataclass
+from typing import Any, Dict, List, Tuple
import mujoco
import numpy as np
-from mujoco import MjData, MjModel, mjtJoint
+from mujoco import MjData, MjModel, MjSpec, mjtJoint
from numpy.typing import ArrayLike, NDArray
# fmt: off
@@ -278,6 +278,38 @@ def rotate_quat_by_euler(quat: ArrayLike, euler: ArrayLike) -> NDArray:
return result
+# -------------------------------- SPEC UTILS --------------------------------#
+def make_spec_from_file(path: str) -> MjSpec:
+ spec = mujoco.MjSpec()
+ spec.from_file(path)
+ return spec
+
+
+def make_spec_from_string(xml: str) -> MjSpec:
+ spec = mujoco.MjSpec()
+ spec.from_string(xml)
+ return spec
+
+
+def spec2model(spec: MjSpec) -> MjModel:
+ return spec.compile()
+
+
+def add_geom2body(
+ spec: MjSpec, body_name: str, geom_attr: Dict[str, Any]
+) -> None:
+ body = spec.find_body(body_name)
+ geom = body.add_geom()
+ for attr_name, value in geom_attr.items():
+ setattr(geom, attr_name, value)
+
+
+def add_flex(spec: MjSpec, flex_attr: Dict[str, Any]) -> None:
+ flex = spec.add_flex()
+ for attr_name, value in flex_attr.items():
+ setattr(flex, attr_name, value)
+
+
# -------------------------------- OTHER UTILS --------------------------------#
def id2name(model: MjModel, id: int, t: str = "body") -> str:
assert (
From 65fc70e62ce2af2819bd5fa84833f28b0d6c0357 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Fri, 4 Oct 2024 16:13:29 +0200
Subject: [PATCH 28/40] Use native mujoco viewer for "human" render mode
---
deformable_gym/envs/mujoco/base_mjenv.py | 43 +++++++++++++++++++-----
1 file changed, 35 insertions(+), 8 deletions(-)
diff --git a/deformable_gym/envs/mujoco/base_mjenv.py b/deformable_gym/envs/mujoco/base_mjenv.py
index 02ffdcc..469922f 100644
--- a/deformable_gym/envs/mujoco/base_mjenv.py
+++ b/deformable_gym/envs/mujoco/base_mjenv.py
@@ -60,6 +60,8 @@ class BaseMJEnv(gym.Env, ABC):
The space representing possible actions that can be taken by the agent.
"""
+ metadata = {"render_modes": ["human", "rgb_array", "depth_array"]}
+
def __init__(
self,
robot_name: str,
@@ -89,16 +91,38 @@ def __init__(
self.mocap = MocapControl()
self.init_frame = init_frame
self.max_sim_time = max_sim_time
+ if render_mode is not None:
+ assert render_mode in self.metadata["render_modes"]
self.render_mode = render_mode
self.camera_name = camera_name
self.camera_id = camera_id
- self.renderer = MujocoRenderer(
- self.model, self.data, default_cam_config
- )
+ self.renderer = self._get_renderer(default_cam_config)
self.observation_space = self._get_observation_space()
self.action_space = self._get_action_space()
+ def _get_renderer(self, default_cam_config: Dict[str, Any] | None):
+ """
+ Returns the renderer object for the environment.
+
+ Returns:
+ MujocoRenderer or mujoco.viewer: The renderer object for the environment.
+ """
+ if self.render_mode == "human":
+ renderer = mujoco.viewer.launch_passive(
+ self.model, self.data, show_left_ui=False, show_right_ui=False
+ )
+ if default_cam_config is not None:
+ for attr, value in default_cam_config.items():
+ setattr(renderer.cam, attr, value)
+ elif (
+ self.render_mode == "rgb_array" or self.render_mode == "depth_array"
+ ):
+ renderer = MujocoRenderer(self.model, self.data, default_cam_config)
+ else:
+ renderer = None
+ return renderer
+
def _get_action_space(self) -> spaces.Box:
"""
Defines the action space for the environment based on the robot's control range.
@@ -220,13 +244,16 @@ def render(self) -> None:
"""
Render a frame from the MuJoCo simulation as specified by the render_mode.
"""
-
- return self.renderer.render(
- self.render_mode, self.camera_id, self.camera_name
- )
+ if self.render_mode == "human":
+ self.renderer.sync()
+ elif (
+ self.render_mode == "rgb_array" or self.render_mode == "depth_array"
+ ):
+ return self.renderer.render(self.render_mode)
def close(self) -> None:
"""
Close the environment.
"""
- self.renderer.close()
+ if self.renderer is not None:
+ self.renderer.close()
From a5a02efbd450c73eb3ff0fc44d5c2204b0e2fca2 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Fri, 4 Oct 2024 16:13:57 +0200
Subject: [PATCH 29/40] Update dependencies
---
requirements.txt | 4 ++--
setup.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index a15c77d..73ac318 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
-gymnasium>=0.29.1
+gymnasium>=1.0.0a2
numpy>=1.23.5,<2.0.0
pybullet>=3.2.5
pytransform3d>=2.0.0
scipy>=1.10.0
setuptools>=65.5.1
-mujoco >= 3.1.6
+mujoco >= 3.2.0
diff --git a/setup.py b/setup.py
index ca5bf3f..c753885 100644
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@
packages=["deformable_gym"],
install_requires=[
"pybullet",
- "gymnasium",
+ "gymnasium>=1.0.0a2",
"numpy>=1.23.5,<2.0.0",
"pytransform3d",
],
From 49f50123d6d48398f132ea2fce7be24ceefea5de Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Fri, 4 Oct 2024 16:14:38 +0200
Subject: [PATCH 30/40] Fix annotation issue
---
deformable_gym/envs/mujoco/grasp_env.py | 12 +++---------
deformable_gym/helpers/mj_utils.py | 2 ++
deformable_gym/robots/mj_robot.py | 2 ++
3 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/deformable_gym/envs/mujoco/grasp_env.py b/deformable_gym/envs/mujoco/grasp_env.py
index bcb2cef..d665991 100644
--- a/deformable_gym/envs/mujoco/grasp_env.py
+++ b/deformable_gym/envs/mujoco/grasp_env.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from typing import Any, Dict, Tuple
import mujoco
@@ -14,14 +16,6 @@ class GraspEnv(BaseMJEnv):
A custom MuJoCo environment for a grasping task, where a robot attempts to grasp an object.
"""
- meta_data = {
- "render_mode": [
- "human",
- "rgb_array",
- "depth_array",
- ],
- }
-
def __init__(
self,
robot_name: str,
@@ -30,7 +24,7 @@ def __init__(
observable_object_pos: bool = True,
control_type: str = "mocap",
max_sim_time: float = 6,
- render_mode: str | None = "human",
+ render_mode: str | None = None,
mocap_cfg: Dict[str, str] | None = None,
init_frame: str | None = None,
default_cam_config: Dict[str, Any] | None = None,
diff --git a/deformable_gym/helpers/mj_utils.py b/deformable_gym/helpers/mj_utils.py
index f627339..da191b7 100644
--- a/deformable_gym/helpers/mj_utils.py
+++ b/deformable_gym/helpers/mj_utils.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from dataclasses import dataclass
from typing import Any, Dict, List, Tuple
diff --git a/deformable_gym/robots/mj_robot.py b/deformable_gym/robots/mj_robot.py
index 117fe20..50f0089 100644
--- a/deformable_gym/robots/mj_robot.py
+++ b/deformable_gym/robots/mj_robot.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from abc import ABC
from typing import List
From b15f926be1f5d1ad34cecd56c9c81978a5cff468 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Fri, 4 Oct 2024 16:18:21 +0200
Subject: [PATCH 31/40] Update dependencies
---
requirements.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/requirements.txt b/requirements.txt
index 73ac318..b1f1036 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,3 +5,4 @@ pytransform3d>=2.0.0
scipy>=1.10.0
setuptools>=65.5.1
mujoco >= 3.2.0
+imageio >= 2.34.1
From 24a2cb0d05561f13eb2ac31f21786475272e494a Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Fri, 4 Oct 2024 16:31:42 +0200
Subject: [PATCH 32/40] Set render mode to "human"
---
examples/mj_grasp_example.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/mj_grasp_example.py b/examples/mj_grasp_example.py
index e4306f6..f5c7610 100644
--- a/examples/mj_grasp_example.py
+++ b/examples/mj_grasp_example.py
@@ -46,7 +46,7 @@ def make_env():
obj_id = obj_name2id[args.obj]
env_id = f"Mj{robot_id}Grasp{obj_id}-v0"
control_type = args.control
- env = gym.make(env_id, control_type=control_type)
+ env = gym.make(env_id, control_type=control_type, render_mode="human")
return env
From 54e96fc89032e5aa87eceb12840fe22c0a8256a3 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Fri, 4 Oct 2024 16:31:54 +0200
Subject: [PATCH 33/40] Update README
---
README.md | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4f39b6a..bb310c9 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,17 @@ libGL error: failed to load driver: iris
libGL error: MESA-LOADER: failed to open swrast: /usr/lib/dri/swrast_dri.so: cannot open shared object file: No such file or directory (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri, suffix _dri)
libGL error: failed to load driver: swrast
```
-
+```
+libGL error: MESA-LOADER: failed to open iris: /usr/lib/dri/iris_dri.so: cannot open shared object file: No such file or directory (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri, suffix _dri)
+libGL error: failed to load driver: iris
+libGL error: MESA-LOADER: failed to open iris: /usr/lib/dri/iris_dri.so: cannot open shared object file: No such file or directory (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri, suffix _dri)
+libGL error: failed to load driver: iris
+libGL error: MESA-LOADER: failed to open swrast: /usr/lib/dri/swrast_dri.so: cannot open shared object file: No such file or directory (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri, suffix _dri)
+libGL error: failed to load driver: swrast
+/home/kaixing/miniconda3/envs/test/lib/python3.10/site-packages/glfw/__init__.py:914: GLFWError: (65543) b'GLX: Failed to create context: BadValue (integer parameter out of range for operation)'
+ warnings.warn(message, GLFWError)
+ERROR: could not create window
+```
In this case, install the following dependency via conda-forge:
```bash
From e994b4a90d33d6f69a834358171ee3929b2dcf85 Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Fri, 4 Oct 2024 16:35:57 +0200
Subject: [PATCH 34/40] Update docstring
---
deformable_gym/envs/mujoco/base_mjenv.py | 32 +++++++++++-------------
1 file changed, 15 insertions(+), 17 deletions(-)
diff --git a/deformable_gym/envs/mujoco/base_mjenv.py b/deformable_gym/envs/mujoco/base_mjenv.py
index 469922f..bc8b7f0 100644
--- a/deformable_gym/envs/mujoco/base_mjenv.py
+++ b/deformable_gym/envs/mujoco/base_mjenv.py
@@ -33,31 +33,29 @@ class BaseMJEnv(gym.Env, ABC):
Attributes:
-----------
scene : str
- The XML string representing the MuJoCo scene, created by the `AssetManager` using
- the specified robot and object names.
+ The XML string representing the MuJoCo scene created by the `AssetManager`.
model : mujoco.MjModel
- The compiled MuJoCo model used for simulation.
+ The MuJoCo model used in the simulation.
data : mujoco.MjData
- The MuJoCo data structure containing the state of the simulation.
+ MuJoCo data structure holding the simulation state.
robot : MjRobot
- An instance of the `MjRobot` class representing the configuration of robot in the simulation.
+ The robot entity within the environment.
object : MjObject
- An instance of the `MjObject` class representing the configuration of object in the simulation.
+ The object entity within the environment.
observable_object_pos : bool
- Indicates whether the position of the object should be observable in the
- observation space.
+ Whether the object's position is part of the observation space.
init_frame : str or None
- The name of an optional keyframe to load for initializing the environment's state.
+ Keyframe for initial state setup.
max_sim_time : float
- The maximum time duration for each simulation episode.
- gui : bool
- Indicates whether a GUI viewer for the simulation should be launched.
- viewer : mujoco.viewer or None
- The GUI viewer for the simulation, if `gui` is enabled.
+ Maximum simulation time per episode.
+ render_mode : str or None
+ The rendering mode (e.g., 'human', 'rgb_array').
+ renderer : MujocoRenderer or mujoco.viewer
+ Renderer used for visualization, if applicable.
observation_space : gym.spaces.Box
- The space representing possible observations that can be returned by the environment.
+ Observation space defining the limits of sensor readings or state data available to the agent.
action_space : gym.spaces.Box
- The space representing possible actions that can be taken by the agent.
+ Action space defining the limits of control actions available to the agent.
"""
metadata = {"render_modes": ["human", "rgb_array", "depth_array"]}
@@ -106,7 +104,7 @@ def _get_renderer(self, default_cam_config: Dict[str, Any] | None):
Returns the renderer object for the environment.
Returns:
- MujocoRenderer or mujoco.viewer: The renderer object for the environment.
+ MujocoRenderer or mujoco native viewer or None: The renderer object for the environment.
"""
if self.render_mode == "human":
renderer = mujoco.viewer.launch_passive(
From 73c42484593a1812af68e0b42fb589b6bb908dec Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Fri, 4 Oct 2024 16:42:31 +0200
Subject: [PATCH 35/40] Add docstring
---
deformable_gym/helpers/mj_mocap_control.py | 63 +++++++++++++++++++---
1 file changed, 57 insertions(+), 6 deletions(-)
diff --git a/deformable_gym/helpers/mj_mocap_control.py b/deformable_gym/helpers/mj_mocap_control.py
index 66101b5..67c6148 100644
--- a/deformable_gym/helpers/mj_mocap_control.py
+++ b/deformable_gym/helpers/mj_mocap_control.py
@@ -8,6 +8,24 @@
class MocapControl:
+ """
+ A class to control the motion capture (mocap) object in a MuJoCo simulation. It handles attaching
+ the mocap body to a simulated object and managing equality constraints such as welds, allowing
+ for precise manipulation and control of the object in the environment.
+
+ Parameters:
+ -----------
+ body_name : str, optional
+ The name of the mocap body in the simulation, default is "mocap".
+ equality_name : str, optional
+ The name of the equality constraint (mjtEq.mjEQ_WELD) used to attach the mocap to another body. Default is "mocap".
+
+ Notes:
+ ------
+ - This class assumes that only one mocap body is present in the simulation (`model.nmocap == 1`).
+ - The control vector `ctrl` should be of shape (6,) with the first 3 elements controlling position
+ and the last 3 controlling orientation as Euler angles.
+ """
def __init__(
self, body_name: str = "mocap", equality_name: str = "mocap"
@@ -16,22 +34,45 @@ def __init__(
self.equality_name = equality_name
def eq_is_active(self, model: MjModel, data: MjData) -> bool:
+ """
+ Checks if the equality constraint for the mocap body is currently active.
+ """
eq_id = mju.name2id(model, self.equality_name, "equality")
return True if data.eq_active[eq_id] else False
def enable_eq(self, model: MjModel, data: MjData) -> None:
+ """
+ Enables the equality constraint (weld) for the mocap body, effectively linking it to another body.
+ """
mju.enable_equality_constraint(model, data, self.equality_name)
def disable_eq(self, model: MjModel, data: MjData) -> None:
+ """
+ Disables the equality constraint for the mocap body.
+
+ """
mju.disable_equality_constraint(model, data, self.equality_name)
def reset_eq(self, model: MjModel, data: MjData) -> None:
- # the indication of eq_data is as follows:
- # eq_data[:3]: anchor position
- # eq_data[3:10]: relative position (3D position followed by 4D quaternion orientation)
- # eq_data[10]: torquescale
- # since mocap body is attached to the desired body, and no anchor is set,
- # we need to set the relative position 0,0,0,1,0,0,0 and anchor position 0,0,0 and torquescale 1 by default
+ """
+ Resets the equality constraint data, setting the relative position to zero and the quaternion
+ to identity. This ensures that the mocap body is aligned with the welded body without any offsets.
+
+ Raises:
+ -------
+ AssertionError
+ If the equality type is not 'weld', which is the only supported type.
+
+ Note:
+ -----
+ The indication of eq_data is as follows:
+ - eq_data[:3]: anchor position
+ - eq_data[3:10]: relative position (3D position followed by 4D quaternion orientation)
+ - eq_data[10]: torquescale
+ since mocap body is attached to the desired body, and no anchor is set,
+ we need to set the relative position 0,0,0,1,0,0,0 and anchor position 0,0,0 and torquescale 1 by default
+ """
+
eq_id = mju.name2id(model, self.equality_name, "equality")
assert (
model.equality(eq_id).type == mjtEq.mjEQ_WELD
@@ -42,12 +83,18 @@ def reset_eq(self, model: MjModel, data: MjData) -> None:
mujoco.mj_forward(model, data)
def get_weld_body_id(self, model: MjModel) -> int:
+ """
+ Returns the ID of the body that is welded to the mocap body.
+ """
mocap_body_id = mju.name2id(model, self.body_name)
body1_id = model.equality(self.equality_name).obj1id
body2_id = model.equality(self.equality_name).obj2id
return body1_id if body1_id != mocap_body_id else body2_id
def attach_mocap2weld_body(self, model: MjModel, data: MjData) -> None:
+ """
+ Attaches the mocap body to the welded body by aligning their positions and orientations, and resetting the equality constraint.
+ """
weld_body_id = self.get_weld_body_id(model)
body_xpos = data.body(weld_body_id).xpos
body_xquat = data.body(weld_body_id).xquat
@@ -58,6 +105,10 @@ def attach_mocap2weld_body(self, model: MjModel, data: MjData) -> None:
self.reset_eq(model, data)
def set_ctrl(self, model: MjModel, data: MjData, ctrl: NDArray) -> None:
+ """
+ Sets the control inputs for the mocap body. The control vector is expected to have 6 elements:
+ the first 3 control the position, and the last 3 control the orientation (Euler angles).
+ """
assert len(ctrl) == 6, "mocap ctrl should be in shape (6,)."
assert model.nmocap == 1, "Only one mocap body is supported."
From 65f2a02aa18d2c5ed0ac21b675a6f277a2534e4f Mon Sep 17 00:00:00 2001
From: Kaixing Xiao
Date: Fri, 4 Oct 2024 18:23:46 +0200
Subject: [PATCH 36/40] Update setup
---
setup.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/setup.py b/setup.py
index c753885..0fb14ba 100644
--- a/setup.py
+++ b/setup.py
@@ -21,9 +21,7 @@
"pytransform3d",
],
extras_require={
- "mujoco": [
- "mujoco==3.1.6",
- ],
+ "mujoco": ["mujoco>= 3.2.0", "imageio >=2.34.1"],
"dev": ["pytest", "pre-commit", "flake8"],
},
)
From 0b3520840abdb51e3ca51e306af99f97988a8e4a Mon Sep 17 00:00:00 2001
From: Melvin Laux
Date: Tue, 3 Dec 2024 16:49:49 +0100
Subject: [PATCH 37/40] Ignore mujoco log files
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 916acb1..1e937a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ logs
*.mp4
*.json
.coverage
+*MUJOCO_LOG.TXT
From 9d1c02b4ad6c5af3076b4127c325298758b2b701 Mon Sep 17 00:00:00 2001
From: Melvin Laux
Date: Tue, 3 Dec 2024 17:16:10 +0100
Subject: [PATCH 38/40] Require specific mujoco version
---
requirements.txt | 2 +-
setup.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index b1f1036..61be206 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,5 +4,5 @@ pybullet>=3.2.5
pytransform3d>=2.0.0
scipy>=1.10.0
setuptools>=65.5.1
-mujoco >= 3.2.0
+mujoco == 3.2.3
imageio >= 2.34.1
diff --git a/setup.py b/setup.py
index 0fb14ba..651e886 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@
"pytransform3d",
],
extras_require={
- "mujoco": ["mujoco>= 3.2.0", "imageio >=2.34.1"],
+ "mujoco": ["mujoco== 3.2.3", "imageio >=2.34.1"],
"dev": ["pytest", "pre-commit", "flake8"],
},
)
From 2412b69adbd959685dcc52df0d9851ce4d0b7cdb Mon Sep 17 00:00:00 2001
From: Melvin Laux
Date: Tue, 3 Dec 2024 17:31:51 +0100
Subject: [PATCH 39/40] Fix urls in contribution guide
---
CONTRIBUTING.md | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6641a00..822adb4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -5,7 +5,7 @@ Everyone is welcome to contribute.
There are several ways to contribute to DeformableGym: you could
* send a bug report to the
- [bug tracker](https://git.hb.dfki.de/april/learning/deformable_gym)
+ [issue tracker](https://www.github.com/dfki-ric/deformable_gym)
* work on one of the reported issues
* write documentation
* add a new feature
@@ -18,17 +18,17 @@ This text is shamelessly copied from
[scikit-learn's](https://scikit-learn.org/stable/developers/contributing.html)
contribution guidelines.
-The preferred way to contribute to DefromableGym is to fork the
-[repository](https://git.hb.dfki.de/april/learning/deformable_gym) on GitLab,
-then submit a "merge request" (MR):
+The preferred way to contribute to DefromableGym is to fork this
+[repository](https://www.github.com/dfki-ric/deformable_gym),
+then submit a "pull request" (PR):
-1. Fork the [project repository](https://git.hb.dfki.de/april/learning/deformable_gym):
+1. Fork the [project repository](https://www.github.com/dfki-ric/deformable_gym):
click on the 'Fork' button near the top of the page. This creates a copy of
the code under your account on the GitHub server.
2. Clone this copy to your local disk:
- $ git clone --recurse-submodules git@git.hb.dfki.de:april/learning/deformable_gym.git
+ $ git clone git@github.com:dfki-ric/deformable_gym.git
3. Create a branch to hold your changes:
@@ -47,8 +47,8 @@ then submit a "merge request" (MR):
$ git push -u origin my-feature
Finally, go to the web page of your fork of the deformable_gym repository,
-and click 'Merge request' to send your changes to the maintainer for review.
-Make sure that your target branch is 'development'.
+and click 'Pull request' to send your changes to the maintainer for review.
+Make sure that your target branch is `development`.
In the above setup, your `origin` remote repository points to
YourLogin/deformable_gym.git. If you wish to fetch/merge from the main
@@ -56,7 +56,7 @@ repository instead of your forked one, you will need to add another remote
to use instead of `origin`. If we choose the name `upstream` for it, the
command will be:
- $ git remote add upstream https://git.hb.dfki.de/april/learning/deformable_gym
+ $ git remote add upstream https://www.github.com/dfki-ric/deformable_gym
(If any of the above seems like magic to you, then look up the
[Git documentation](http://git-scm.com/documentation) on the web.)
@@ -89,4 +89,4 @@ be discussed and announced in advance with deprecation warnings.
Semantic versioning is used, that is, the major version number will be
incremented when the API changes in a backwards incompatible way, the
minor version will be incremented when new functionality is added in a
-backwards compatible manner.
\ No newline at end of file
+backwards compatible manner.
From bb6c9f333cdb20efc9e26f475dba518373762701 Mon Sep 17 00:00:00 2001
From: Melvin Laux
Date: Tue, 3 Dec 2024 17:54:02 +0100
Subject: [PATCH 40/40] Bump to version 0.4.2
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index 651e886..753e2a5 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
setup(
name="deformable_gym",
- version="0.4.1",
+ version="0.4.2",
maintainer="Melvin Laux",
maintainer_email="melvin.laux@uni-bremen.de",
description="Gym environments for grasping deformable objects",