diff --git a/robosuite/wrappers/domain_randomization_wrapper.py b/robosuite/wrappers/domain_randomization_wrapper.py index ce27582663..27365fd638 100644 --- a/robosuite/wrappers/domain_randomization_wrapper.py +++ b/robosuite/wrappers/domain_randomization_wrapper.py @@ -207,16 +207,17 @@ def reset(self): # normal env reset ret = super().reset() + # a hard reset frees the old sim and builds a new one, so point the + # modders at it before reading any model parameters from them + for modder in self.modders: + modder.update_sim(self.env.sim) + # save the original env parameters self.save_default_domain() # reset counter for doing domain randomization at a particular frequency self.step_counter = 0 - # update sims - for modder in self.modders: - modder.update_sim(self.env.sim) - if self.randomize_on_reset: # domain randomize + regenerate observation self.randomize_domain() diff --git a/tests/test_environments/test_domain_randomization.py b/tests/test_environments/test_domain_randomization.py new file mode 100644 index 0000000000..e64d283356 --- /dev/null +++ b/tests/test_environments/test_domain_randomization.py @@ -0,0 +1,41 @@ +""" +Test that DomainRandomizationWrapper survives a hard reset. + +When an environment uses the "mujoco" renderer, a hard reset frees the old MjSim +and builds a new one. The wrapper's modders keep a reference to the freed sim, so +saving the default domain must rebind them to the new sim first. Otherwise the +second reset raises `AttributeError: 'MjSim' object has no attribute 'model'`. + +See https://github.com/ARISE-Initiative/robosuite/issues/426. +""" +import numpy as np + +import robosuite as suite +from robosuite.wrappers import DomainRandomizationWrapper + + +def test_domain_randomization_hard_reset(): + + env = suite.make( + env_name="Lift", + robots="Panda", + has_renderer=False, + has_offscreen_renderer=False, + use_camera_obs=False, + renderer="mujoco", + ) + # randomize_color needs mujoco==3.1.1, so leave it off here + env = DomainRandomizationWrapper(env, randomize_color=False) + + # the first reset builds the initial sim; each later hard reset frees it and + # builds a new one, which is what used to break the modders + for _ in range(3): + env.reset() + env.step(np.zeros(env.action_dim)) + + env.close() + + +if __name__ == "__main__": + + test_domain_randomization_hard_reset()