Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/test_teleoperation.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def main():
env = gym.make(
"gym_hil/PandaPickCubeGamepad-v0",
render_mode=args.render_mode,
viewer_type="dual",
image_obs=True,
step_size=args.step_size,
use_gamepad=not args.use_keyboard,
Expand Down
7 changes: 6 additions & 1 deletion gym_hil/wrappers/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
def wrap_env(
env: gym.Env,
use_viewer: bool = False,
viewer_type: str = "single",
use_gamepad: bool = False,
use_gripper: bool = True,
auto_reset: bool = False,
Expand All @@ -29,6 +30,7 @@ def wrap_env(
Args:
env: The base environment to wrap
use_viewer: Whether to add a passive viewer
viewer_type: Whether to use a single or dual viewer
use_gamepad: Whether to use gamepad instead of keyboard controls
use_gripper: Whether to enable gripper control
auto_reset: Whether to automatically reset the environment when episode ends
Expand All @@ -42,7 +44,7 @@ def wrap_env(
"""
# Apply wrappers in the correct order
if use_viewer:
env = PassiveViewerWrapper(env, show_left_ui=show_ui, show_right_ui=show_ui)
env = PassiveViewerWrapper(env, viewer_type=viewer_type, show_left_ui=show_ui, show_right_ui=show_ui)

if use_gripper:
env = GripperPenaltyWrapper(env, penalty=gripper_penalty)
Expand Down Expand Up @@ -70,6 +72,7 @@ def wrap_env(
def make_env(
env_id: str,
use_viewer: bool = False,
viewer_type: str = "single",
use_gamepad: bool = False,
use_gripper: bool = True,
auto_reset: bool = False,
Expand All @@ -84,6 +87,7 @@ def make_env(
Args:
env_id: The ID of the base environment to create
use_viewer: Whether to add a passive viewer
viewer_type: Whether to use a single or dual viewer
use_gamepad: Whether to use gamepad instead of keyboard controls
use_gripper: Whether to enable gripper control
auto_reset: Whether to automatically reset the environment when episode ends
Expand All @@ -105,6 +109,7 @@ def make_env(
return wrap_env(
env,
use_viewer=use_viewer,
viewer_type=viewer_type,
use_gamepad=use_gamepad,
use_gripper=use_gripper,
auto_reset=auto_reset,
Expand Down
86 changes: 62 additions & 24 deletions gym_hil/wrappers/viewer_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,43 +28,64 @@ class PassiveViewerWrapper(gym.Wrapper):
environment is created so the user no longer needs to use
``mujoco.viewer.launch_passive`` or any context–manager boiler-plate.

Args:
viewer_type: Whether to use a single or dual viewer. If dual, two
viewers will be opened. You can switch between fixed cameras using
the [, ] keys or access a free camera mode for manual adjustments using the Esc key
show_left_ui: Whether to show the left UI
show_right_ui: Whether to show the right UI

The viewer is kept in sync after every ``reset`` and ``step`` call and is
closed automatically when the environment itself is closed or deleted.
"""

def __init__(
self,
env: gym.Env,
*,
viewer_type: str = "single",
show_left_ui: bool = False,
show_right_ui: bool = False,
**kwargs,
) -> None:
super().__init__(env)

self.viewer_type = viewer_type
# Launch the interactive viewer. We expose *model* and *data* from the
# *unwrapped* environment to make sure we operate on the base MuJoCo
# objects even if other wrappers have been applied before this one.
self._viewer = mujoco.viewer.launch_passive(
env.unwrapped.model,
env.unwrapped.data,
# show_left_ui=show_left_ui,
# show_right_ui=show_right_ui,
)

if self.viewer_type == "single":
self._viewer = mujoco.viewer.launch_passive(
env.unwrapped.model,
env.unwrapped.data,
# show_left_ui=show_left_ui,
# show_right_ui=show_right_ui,
)
elif self.viewer_type == "dual":
self._viewer_1 = mujoco.viewer.launch_passive(
env.unwrapped.model,
env.unwrapped.data,
# show_left_ui=show_left_ui,
# show_right_ui=show_right_ui,
)
self._viewer_2 = mujoco.viewer.launch_passive(
env.unwrapped.model,
env.unwrapped.data,
# show_left_ui=show_left_ui,
# show_right_ui=show_right_ui,
)
# Make sure the first frame is rendered.
self._viewer.sync()
self._sync()

# ---------------------------------------------------------------------
# Gym API overrides

def reset(self, **kwargs): # type: ignore[override]
observation, info = self.env.reset(**kwargs)
self._viewer.sync()
self._sync()
return observation, info

def step(self, action): # type: ignore[override]
observation, reward, terminated, truncated, info = self.env.step(action)
self._viewer.sync()
self._sync()
return observation, reward, terminated, truncated, info

def close(self) -> None: # type: ignore[override]
Expand All @@ -88,20 +109,25 @@ def close(self) -> None: # type: ignore[override]
# 1. Tidy up the renderer managed by the wrapped environment (if any).
base_env = self.env.unwrapped # type: ignore[attr-defined]
if hasattr(base_env, "_viewer"):
viewer = base_env._viewer
if viewer is not None and hasattr(viewer, "close") and callable(viewer.close):
try: # noqa: SIM105
viewer.close()
except Exception:
# Ignore errors coming from older MuJoCo versions or
# already-freed contexts.
pass
# Prevent the underlying env from trying to close it again.
base_env._viewer = None
if self.viewer_type == "single":
viewer = base_env._viewer
if viewer is not None and hasattr(viewer, "close") and callable(viewer.close):
try: # noqa: SIM105
viewer.close()
except Exception:
# Ignore errors coming from older MuJoCo versions or
# already-freed contexts.
pass
# Prevent the underlying env from trying to close it again.
base_env._viewer = None

# 2. Close the passive viewer launched by this wrapper.
try: # noqa: SIM105
self._viewer.close()
if self.viewer_type == "single":
self._viewer.close()
elif self.viewer_type == "dual":
self._viewer_1.close()
self._viewer_2.close()
except Exception: # pragma: no cover
# Defensive: avoid propagating viewer shutdown errors.
pass
Expand All @@ -114,6 +140,18 @@ def __del__(self):
# in case.
if hasattr(self, "_viewer"):
try: # noqa: SIM105
self._viewer.close()
if self.viewer_type == "single":
self._viewer.close()
elif self.viewer_type == "dual":
self._viewer_1.close()
self._viewer_2.close()
except Exception:
pass

def _sync(self):
if self.viewer_type == "single":
self._viewer.sync()
elif self.viewer_type == "dual":
self._viewer_1.sync()
self._viewer_2.sync()

Loading