# Path: robel/simulation/sim_scene.py
# class SimBackend(enum.Enum):
#     """Simulation library types."""
#     MUJOCO_PY = 0
#     DM_CONTROL = 1
# 
# class SimScene(metaclass=abc.ABCMeta):
#     """Encapsulates a MuJoCo robotics simulation."""
# 
#     @staticmethod
#     def create(*args, backend: Union[SimBackend, int], **kwargs) -> 'SimScene':
#         """Creates a new simulation scene.
# 
#         Args:
#             *args: Positional arguments to pass to the simulation.
#             backend: The simulation backend to use to load the simulation.
#             **kwargs: Keyword arguments to pass to the simulation.
# 
#         Returns:
#             A SimScene object.
#         """
#         backend = SimBackend(backend)
#         if backend == SimBackend.MUJOCO_PY:
#             from robel.simulation import mjpy_sim_scene  # type: ignore
#             return mjpy_sim_scene.MjPySimScene(*args, **kwargs)
#         elif backend == SimBackend.DM_CONTROL:
#             from robel.simulation import dm_sim_scene  # type: ignore
#             return dm_sim_scene.DMSimScene(*args, **kwargs)
#         else:
#             raise NotImplementedError(backend)
# 
#     def __init__(
#             self,
#             model_handle: Any,
#             frame_skip: int = 1,
#     ):
#         """Initializes a new simulation.
# 
#         Args:
#             model_handle: The simulation model to load. This can be a XML file,
#                 or a format/object specific to the simulation backend.
#             frame_skip: The number of simulation steps per environment step.
#                 This multiplied by the timestep defined in the model file is the
#                 step duration.
#         """
#         self.frame_skip = frame_skip
# 
#         self.sim = self._load_simulation(model_handle)
#         self.model = self.sim.model
#         self.data = self.sim.data
# 
#         self.renderer = self._create_renderer(self.sim)
# 
#         # Save initial values.
#         self.init_qpos = self.data.qpos.ravel().copy()
#         self.init_qvel = self.data.qvel.ravel().copy()
# 
#     @property
#     def step_duration(self):
#         """Returns the simulation step duration in seconds."""
#         return self.model.opt.timestep * self.frame_skip
# 
#     def close(self):
#         """Cleans up any resources used by the simulation."""
#         self.renderer.close()
# 
#     def advance(self, substeps: int = None):
#         """Advances the simulation for one step."""
#         # Step the simulation `frame_skip` times.
#         if substeps is None:
#             substeps = self.frame_skip
#         for _ in range(substeps):
#             self.sim.step()
#             self.renderer.refresh_window()
# 
#     def disable_option(self,
#                        constraint_solver: bool = False,
#                        limits: bool = False,
#                        contact: bool = False,
#                        gravity: bool = False,
#                        clamp_ctrl: bool = False,
#                        actuation: bool = False):
#         """Disables option(s) in the simulation."""
#         # http://www.mujoco.org/book/APIreference.html#mjtDisableBit
#         if constraint_solver:
#             self.model.opt.disableflags |= (1 << 0)
#         if limits:
#             self.model.opt.disableflags |= (1 << 3)
#         if contact:
#             self.model.opt.disableflags |= (1 << 4)
#         if gravity:
#             self.model.opt.disableflags |= (1 << 6)
#         if clamp_ctrl:
#             self.model.opt.disableflags |= (1 << 7)
#         if actuation:
#             self.model.opt.disableflags |= (1 << 10)
# 
#     @contextlib.contextmanager
#     def disable_option_context(self, **kwargs):
#         """Disables options(s) in the simulation for the context."""
#         original_flags = self.model.opt.disableflags
#         self.disable_option(**kwargs)
#         yield
#         self.model.opt.disableflags = original_flags
# 
#     @abc.abstractmethod
#     def copy_model(self) -> Any:
#         """Returns a copy of the MjModel object."""
# 
#     @abc.abstractmethod
#     def save_binary(self, path: str) -> str:
#         """Saves the loaded model to a binary .mjb file.
# 
#         Returns:
#             The file path that the binary was saved to.
#         """
# 
#     @abc.abstractmethod
#     def upload_height_field(self, hfield_id: int):
#         """Uploads the height field to the rendering context."""
# 
#     @abc.abstractmethod
#     def get_handle(self, value: Any) -> Any:
#         """Returns a handle that can be passed to mjlib methods."""
# 
#     @abc.abstractmethod
#     def get_mjlib(self) -> Any:
#         """Returns an interface to the low-level MuJoCo API."""
# 
#     @abc.abstractmethod
#     def _load_simulation(self, model_handle: Any) -> Any:
#         """Loads the simulation from the given model handle."""
# 
#     @abc.abstractmethod
#     def _create_renderer(self, sim: Any) -> Renderer:
#         """Creates a renderer for the given simulation."""


import contextlib
import tempfile
from typing import Generator
from absl.testing import absltest
from robel.simulation.sim_scene import SimBackend, SimScene
            with self.subTest('mujoco_py'):
                fn(
                    self,
                    SimScene.create(
                        test_file_path, backend=SimBackend.MUJOCO_PY))
            with self.subTest('dm_control'):
                fn(
                    self,
                    SimScene.create(
                        test_file_path, backend=SimBackend.DM_CONTROL))

    return test_fn


class SimSceneTest(absltest.TestCase):
    """Unit test class for SimScene."""

    @mjpy_and_dm
    def test_load(self, robot: SimScene):
        self.assertIsNotNone(robot.sim)
        self.assertIsNotNone(robot.model)
        self.assertIsNotNone(robot.data)

    @mjpy_and_dm
    def test_step(self, robot: SimScene):
        robot.sim.reset()
        robot.sim.forward()
        robot.sim.step()

    @mjpy_and_dm

# Path: robel/simulation/sim_scene.py
# class SimBackend(enum.Enum):
#     """Simulation library types."""
#     MUJOCO_PY = 0
#     DM_CONTROL = 1
# 
# class SimScene(metaclass=abc.ABCMeta):
#     """Encapsulates a MuJoCo robotics simulation."""
# 
#     @staticmethod
#     def create(*args, backend: Union[SimBackend, int], **kwargs) -> 'SimScene':
#         """Creates a new simulation scene.
# 
#         Args:
#             *args: Positional arguments to pass to the simulation.
#             backend: The simulation backend to use to load the simulation.
#             **kwargs: Keyword arguments to pass to the simulation.
# 
#         Returns:
#             A SimScene object.
#         """
#         backend = SimBackend(backend)
#         if backend == SimBackend.MUJOCO_PY:
#             from robel.simulation import mjpy_sim_scene  # type: ignore
#             return mjpy_sim_scene.MjPySimScene(*args, **kwargs)
#         elif backend == SimBackend.DM_CONTROL:
#             from robel.simulation import dm_sim_scene  # type: ignore
#             return dm_sim_scene.DMSimScene(*args, **kwargs)
#         else:
#             raise NotImplementedError(backend)
# 
#     def __init__(
#             self,
#             model_handle: Any,
#             frame_skip: int = 1,
#     ):
#         """Initializes a new simulation.
# 
#         Args:
#             model_handle: The simulation model to load. This can be a XML file,
#                 or a format/object specific to the simulation backend.
#             frame_skip: The number of simulation steps per environment step.
#                 This multiplied by the timestep defined in the model file is the
#                 step duration.
#         """
#         self.frame_skip = frame_skip
# 
#         self.sim = self._load_simulation(model_handle)
#         self.model = self.sim.model
#         self.data = self.sim.data
# 
#         self.renderer = self._create_renderer(self.sim)
# 
#         # Save initial values.
#         self.init_qpos = self.data.qpos.ravel().copy()
#         self.init_qvel = self.data.qvel.ravel().copy()
# 
#     @property
#     def step_duration(self):
#         """Returns the simulation step duration in seconds."""
#         return self.model.opt.timestep * self.frame_skip
# 
#     def close(self):
#         """Cleans up any resources used by the simulation."""
#         self.renderer.close()
# 
#     def advance(self, substeps: int = None):
#         """Advances the simulation for one step."""
#         # Step the simulation `frame_skip` times.
#         if substeps is None:
#             substeps = self.frame_skip
#         for _ in range(substeps):
#             self.sim.step()
#             self.renderer.refresh_window()
# 
#     def disable_option(self,
#                        constraint_solver: bool = False,
#                        limits: bool = False,
#                        contact: bool = False,
#                        gravity: bool = False,
#                        clamp_ctrl: bool = False,
#                        actuation: bool = False):
#         """Disables option(s) in the simulation."""
#         # http://www.mujoco.org/book/APIreference.html#mjtDisableBit
#         if constraint_solver:
#             self.model.opt.disableflags |= (1 << 0)
#         if limits:
#             self.model.opt.disableflags |= (1 << 3)
#         if contact:
#             self.model.opt.disableflags |= (1 << 4)
#         if gravity:
#             self.model.opt.disableflags |= (1 << 6)
#         if clamp_ctrl:
#             self.model.opt.disableflags |= (1 << 7)
#         if actuation:
#             self.model.opt.disableflags |= (1 << 10)
# 
#     @contextlib.contextmanager
#     def disable_option_context(self, **kwargs):
#         """Disables options(s) in the simulation for the context."""
#         original_flags = self.model.opt.disableflags
#         self.disable_option(**kwargs)
#         yield
#         self.model.opt.disableflags = original_flags
# 
#     @abc.abstractmethod
#     def copy_model(self) -> Any:
#         """Returns a copy of the MjModel object."""
# 
#     @abc.abstractmethod
#     def save_binary(self, path: str) -> str:
#         """Saves the loaded model to a binary .mjb file.
# 
#         Returns:
#             The file path that the binary was saved to.
#         """
# 
#     @abc.abstractmethod
#     def upload_height_field(self, hfield_id: int):
#         """Uploads the height field to the rendering context."""
# 
#     @abc.abstractmethod
#     def get_handle(self, value: Any) -> Any:
#         """Returns a handle that can be passed to mjlib methods."""
# 
#     @abc.abstractmethod
#     def get_mjlib(self) -> Any:
#         """Returns an interface to the low-level MuJoCo API."""
# 
#     @abc.abstractmethod
#     def _load_simulation(self, model_handle: Any) -> Any:
#         """Loads the simulation from the given model handle."""
# 
#     @abc.abstractmethod
#     def _create_renderer(self, sim: Any) -> Renderer:
#         """Creates a renderer for the given simulation."""


# Path: robel/simulation/sim_scene_test.py
import contextlib
import tempfile
from typing import Generator
from absl.testing import absltest
from robel.simulation.sim_scene import SimBackend, SimScene

            with self.subTest('mujoco_py'):
                fn(
                    self,
                    SimScene.create(
                        test_file_path, backend=SimBackend.MUJOCO_PY))
            with self.subTest('dm_control'):
                fn(
                    self,
                    SimScene.create(
                        test_file_path, backend=SimBackend.DM_CONTROL))

    return test_fn


class SimSceneTest(absltest.TestCase):
    """Unit test class for SimScene."""

    @mjpy_and_dm
    def test_load(self, robot: SimScene):
        self.assertIsNotNone(robot.sim)
        self.assertIsNotNone(robot.model)
        self.assertIsNotNone(robot.data)

    @mjpy_and_dm
    def test_step(self, robot: SimScene):
        robot.sim.reset()
        robot.sim.forward()
        robot.sim.step()

    @mjpy_and_dm

    def test_accessors(self, robot: SimScene):