# Copyright 2018 The dm_control Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ """Tests of the viewer.py module.""" from absl.testing import absltest from dm_control.mujoco.wrapper.mjbindings import enums from dm_control.viewer import util from dm_control.viewer import viewer import mock class ViewerTest(absltest.TestCase): def setUp(self): super().setUp() self.viewport = mock.MagicMock() self.mouse = mock.MagicMock() self.keyboard = mock.MagicMock() self.viewer = viewer.Viewer(self.viewport, self.mouse, self.keyboard) self.viewer._render_settings = mock.MagicMock() self.physics = mock.MagicMock() self.renderer = mock.MagicMock() self.renderer.priority_components = util.QuietSet() def _extract_bind_call_args(self, bind_mock): call_args = [] for calls in bind_mock.call_args_list: args = calls[0] if len(args) == 2: call_args.append(args[1]) return call_args def test_initialize_creates_components(self): with mock.patch(viewer.__name__ + '.renderer'): self.viewer.initialize(self.physics, self.renderer, touchpad=False) self.assertIsNotNone(self.viewer._camera) self.assertIsNotNone(self.viewer._manipulator) self.assertIsNotNone(self.viewer._free_camera) self.assertIsNotNone(self.viewer._camera_select) self.assertEqual(self.renderer, self.viewer._renderer) def test_initialize_creates_touchpad_specific_input_mapping(self): self.viewer._input_map = mock.MagicMock() with mock.patch(viewer.__name__ + '.renderer'): self.viewer.initialize(self.physics, self.renderer, touchpad=True) call_args = self._extract_bind_call_args(self.viewer._input_map.bind) self.assertIn(viewer._MOVE_OBJECT_VERTICAL_TOUCHPAD, call_args) self.assertIn(viewer._MOVE_OBJECT_HORIZONTAL_TOUCHPAD, call_args) self.assertIn(viewer._ROTATE_OBJECT_TOUCHPAD, call_args) self.assertIn(viewer._PAN_CAMERA_VERTICAL_TOUCHPAD, call_args) self.assertIn(viewer._PAN_CAMERA_HORIZONTAL_TOUCHPAD, call_args) def test_initialize_create_mouse_specific_input_mapping(self): self.viewer._input_map = mock.MagicMock() with mock.patch(viewer.__name__ + '.renderer'): self.viewer.initialize(self.physics, self.renderer, touchpad=False) call_args = self._extract_bind_call_args(self.viewer._input_map.bind) self.assertIn(viewer._MOVE_OBJECT_VERTICAL_MOUSE, call_args) self.assertIn(viewer._MOVE_OBJECT_HORIZONTAL_MOUSE, call_args) self.assertIn(viewer._ROTATE_OBJECT_MOUSE, call_args) self.assertIn(viewer._PAN_CAMERA_VERTICAL_MOUSE, call_args) self.assertIn(viewer._PAN_CAMERA_HORIZONTAL_MOUSE, call_args) def test_initialization_flushes_old_input_map(self): self.viewer._input_map = mock.MagicMock() with mock.patch(viewer.__name__ + '.renderer'): self.viewer.initialize(self.physics, self.renderer, touchpad=False) self.viewer._input_map.clear_bindings.assert_called_once() def test_deinitialization_deletes_components(self): self.viewer._camera = mock.MagicMock() self.viewer._manipulator = mock.MagicMock() self.viewer._free_camera = mock.MagicMock() self.viewer._camera_select = mock.MagicMock() self.viewer._renderer = mock.MagicMock() self.viewer.deinitialize() self.assertIsNone(self.viewer._camera) self.assertIsNone(self.viewer._manipulator) self.assertIsNone(self.viewer._free_camera) self.assertIsNone(self.viewer._camera_select) self.assertIsNone(self.viewer._renderer) def test_deinitialization_flushes_old_input_map(self): self.viewer._input_map = mock.MagicMock() self.viewer.deinitialize() self.viewer._input_map.clear_bindings.assert_called_once() def test_rendering_uninitialized(self): self.viewer.render() # nothing crashes def test_zoom_to_scene_uninitialized(self): self.viewer.zoom_to_scene() # nothing crashes def test_rendering(self): self.viewer._camera = mock.MagicMock() self.viewer._renderer = mock.MagicMock() self.viewer.render() self.viewer._camera.render.assert_called_once_with(self.viewer.perturbation) self.viewer._renderer.render.assert_called_once() def test_applying_render_settings_before_rendering_a_scene(self): self.viewer._camera = mock.MagicMock() self.viewer._renderer = mock.MagicMock() self.viewer.render() self.viewer._render_settings.apply_settings.assert_called_once() def test_zoom_to_scene(self): self.viewer._camera = mock.MagicMock() self.viewer.zoom_to_scene() self.viewer._camera.zoom_to_scene.assert_called_once() def test_retrieving_perturbation(self): object_perturbation = mock.MagicMock() self.viewer._manipulator = mock.MagicMock() self.viewer._manipulator.perturbation = object_perturbation self.assertEqual(object_perturbation, self.viewer.perturbation) def test_retrieving_perturbation_without_manipulator(self): self.viewer._manipulator = None self.assertEqual(self.viewer._null_perturbation, self.viewer.perturbation) def test_retrieving_perturbation_without_selected_object(self): self.viewer._manipulator = mock.MagicMock() self.viewer._manipulator.perturbation = None self.assertEqual(self.viewer._null_perturbation, self.viewer.perturbation) class CameraSelectorTest(absltest.TestCase): def setUp(self): super().setUp() self.camera = mock.MagicMock() self.model = mock.MagicMock() self.free_camera = mock.MagicMock() self.model.ncam = 2 options = { 'camera': self.camera, 'model': self.model, 'free_camera': self.free_camera } self.controller = viewer.CameraSelector(**options) def test_activating_freelook_camera_by_default(self): self.assertEqual(self.controller._free_ctrl, self.controller._active_ctrl) def test_cycling_forward_through_cameras(self): self.controller.select_next() self.assertIsNone(self.controller._active_ctrl) self.controller._free_ctrl.deactivate.assert_called_once() self.controller._free_ctrl.reset_mock() self.controller._camera.set_fixed_mode.assert_called_once_with(0) self.controller._camera.reset_mock() self.controller.select_next() self.assertIsNone(self.controller._active_ctrl) self.controller._camera.set_fixed_mode.assert_called_once_with(1) self.controller._camera.reset_mock() self.controller.select_next() self.assertEqual(self.controller._free_ctrl, self.controller._active_ctrl) self.controller._free_ctrl.activate.assert_called_once() def test_cycling_backwards_through_cameras(self): self.controller.select_previous() self.assertIsNone(self.controller._active_ctrl) self.controller._free_ctrl.deactivate.assert_called_once() self.controller._free_ctrl.reset_mock() self.controller._camera.set_fixed_mode.assert_called_once_with(1) self.controller._camera.reset_mock() self.controller.select_previous() self.assertIsNone(self.controller._active_ctrl) self.controller._camera.set_fixed_mode.assert_called_once_with(0) self.controller._camera.reset_mock() self.controller.select_previous() self.assertEqual(self.controller._free_ctrl, self.controller._active_ctrl) self.controller._free_ctrl.activate.assert_called_once() def test_controller_activation(self): old_controller = mock.MagicMock() new_controller = mock.MagicMock() self.controller._active_ctrl = old_controller self.controller._activate(new_controller) old_controller.deactivate.assert_called_once() new_controller.activate.assert_called_once() def test_controller_activation_not_repeated_for_already_active_one(self): controller = mock.MagicMock() self.controller._active_ctrl = controller self.controller._activate(controller) self.assertEqual(0, controller.deactivate.call_count) self.assertEqual(0, controller.activate.call_count) class FreeCameraControllerTest(absltest.TestCase): def setUp(self): super().setUp() self.viewport = mock.MagicMock() self.camera = mock.MagicMock() self.mouse = mock.MagicMock() self.selection_service = mock.MagicMock() options = { 'camera': self.camera, 'viewport': self.viewport, 'pointer': self.mouse, 'selection_service': self.selection_service } self.controller = viewer.FreeCameraController(**options) self.controller._action = mock.MagicMock() def test_activation_while_not_in_tracking_mode(self): self.controller._tracked_body_idx = -1 self.controller.activate() self.camera.set_freelook_mode.assert_called_once() def test_activation_while_in_tracking_mode(self): self.controller._tracked_body_idx = 1 self.controller.activate() self.camera.set_tracking_mode.assert_called_once_with(1) def test_activation_and_deactivation_flag(self): self.controller.activate() self.assertTrue(self.controller._active) self.controller.deactivate() self.assertFalse(self.controller._active) def test_vertical_panning_camera_with_active_controller(self): self.controller._active = True self.controller.set_pan_vertical_mode(True) self.controller._action.begin.assert_called_once_with( enums.mjtMouse.mjMOUSE_MOVE_V) self.controller.set_pan_vertical_mode(False) self.controller._action.end.assert_called_once_with( enums.mjtMouse.mjMOUSE_MOVE_V) def test_vertical_panning_camera_with_inactive_controller(self): self.controller._active = False self.controller.set_pan_vertical_mode(True) self.assertEqual(0, self.controller._action.begin.call_count) self.controller.set_pan_vertical_mode(False) self.assertEqual(0, self.controller._action.end.call_count) def test_horizontal_panning_camera_with_active_controller(self): self.controller._active = True self.controller.set_pan_horizontal_mode(True) self.controller._action.begin.assert_called_once_with( enums.mjtMouse.mjMOUSE_MOVE_H) self.controller.set_pan_horizontal_mode(False) self.controller._action.end.assert_called_once_with( enums.mjtMouse.mjMOUSE_MOVE_H) def test_horizontal_panning_camera_with_inactive_controller(self): self.controller._active = False self.controller.set_pan_horizontal_mode(True) self.assertEqual(0, self.controller._action.begin.call_count) self.controller.set_pan_horizontal_mode(False) self.assertEqual(0, self.controller._action.end.call_count) def test_rotating_camera_with_active_controller(self): self.controller._active = True self.controller.set_rotate_mode(True) self.controller._action.begin.assert_called_once_with( enums.mjtMouse.mjMOUSE_ROTATE_H) self.controller.set_rotate_mode(False) self.controller._action.end.assert_called_once_with( enums.mjtMouse.mjMOUSE_ROTATE_H) def test_rotating_camera_with_inactive_controller(self): self.controller._active = False self.controller.set_rotate_mode(True) self.assertEqual(0, self.controller._action.begin.call_count) self.controller.set_rotate_mode(False) self.assertEqual(0, self.controller._action.end.call_count) def test_centering_with_active_controller(self): self.controller._active = True self.camera.raycast.return_value = 1, 2 self.controller.center() self.camera.raycast.assert_called_once() def test_centering_with_inactive_controller(self): self.controller._active = False self.controller.center() self.assertEqual(0, self.camera.raycast.call_count) def test_moving_mouse_moves_camera(self): position = [100, 200] translation = [1, 0] viewport_space_translation = [2, 0] action = 1 self.viewport.screen_to_viewport.return_value = viewport_space_translation self.controller._action.in_progress = True self.controller._action.watermark = action self.controller.on_move(position, translation) self.viewport.screen_to_viewport.assert_called_once_with(translation) self.camera.move.assert_called_once_with(action, viewport_space_translation) def test_mouse_move_doesnt_work_without_an_action_selected(self): self.controller._action.in_progress = False self.controller.on_move([], []) self.assertEqual(0, self.camera.move.call_count) def test_zoom_with_active_controller(self): self.controller._active = True expected_zoom_vector = [0, -0.05] self.controller.zoom(1.) self.camera.move.assert_called_once_with( enums.mjtMouse.mjMOUSE_ZOOM, expected_zoom_vector) def test_zoom_with_inactive_controller(self): self.controller._active = False self.controller.zoom(1.) self.assertEqual(0, self.camera.move.call_count) def test_tracking_with_active_controller(self): self.controller._active = True selected_body_id = 5 self.selection_service.selected_body_id = selected_body_id self.controller._tracked_body_idx = -1 self.controller.track() self.assertEqual(self.controller._tracked_body_idx, selected_body_id) self.camera.set_tracking_mode.assert_called_once_with(selected_body_id) def test_tracking_with_inactive_controller(self): self.controller._active = False selected_body_id = 5 self.selection_service.selected_body_id = selected_body_id self.controller.track() self.assertEqual(self.controller._tracked_body_idx, -1) self.assertEqual(0, self.camera.set_tracking_mode.call_count) def test_free_look_mode_with_active_controller(self): self.controller._active = True self.controller._tracked_body_idx = 5 self.controller.free_look() self.assertEqual(self.controller._tracked_body_idx, -1) self.camera.set_freelook_mode.assert_called_once() def test_free_look_mode_with_inactive_controller(self): self.controller._active = False self.controller._tracked_body_idx = 5 self.controller.free_look() self.assertEqual(self.controller._tracked_body_idx, 5) self.assertEqual(0, self.camera.set_freelook_mode.call_count) class ManipulationControllerTest(absltest.TestCase): def setUp(self): super().setUp() self.viewport = mock.MagicMock() self.camera = mock.MagicMock() self.mouse = mock.MagicMock() options = { 'camera': self.camera, 'viewport': self.viewport, 'pointer': self.mouse, } self.controller = viewer.ManipulationController(**options) self.body_id = 1 self.click_pos_on_body = [1, 2, 3] self.camera.raycast.return_value = (self.body_id, self.click_pos_on_body) def test_selecting_a_body(self): self.camera.raycast.return_value = (self.body_id, self.click_pos_on_body) self.controller.select() self.assertIsNotNone(self.controller._perturb) def test_selecting_empty_space_cancels_selection(self): self.camera.raycast.return_value = (-1, None) self.controller.select() self.assertIsNone(self.controller._perturb) def test_vertical_movement_operation(self): self.controller._perturb = mock.MagicMock() self.controller.set_move_vertical_mode(True) self.controller._perturb.start_move.assert_called_once() self.assertEqual(enums.mjtMouse.mjMOUSE_MOVE_V, self.controller._perturb.start_move.call_args[0][0]) self.controller.set_move_vertical_mode(False) self.controller._perturb.end_move.assert_called_once() def test_horzontal_movement_operation(self): self.controller._perturb = mock.MagicMock() self.controller.set_move_horizontal_mode(True) self.controller._perturb.start_move.assert_called_once() self.assertEqual(enums.mjtMouse.mjMOUSE_MOVE_H, self.controller._perturb.start_move.call_args[0][0]) self.controller.set_move_horizontal_mode(False) self.controller._perturb.end_move.assert_called_once() def test_rotation_operation(self): self.controller._perturb = mock.MagicMock() self.controller.set_rotate_mode(True) self.controller._perturb.start_move.assert_called_once() self.assertEqual(enums.mjtMouse.mjMOUSE_ROTATE_H, self.controller._perturb.start_move.call_args[0][0]) self.controller.set_rotate_mode(False) self.controller._perturb.end_move.assert_called_once() def test_every_action_generates_a_fresh_grab_pos(self): some_action = 0 self.controller._perturb = mock.MagicMock() self.controller._update_action(some_action) self.camera.raycast.assert_called_once() def test_actions_not_started_without_object_selected(self): some_action = 0 self.controller._perturb = None self.controller._update_action(some_action) self.assertEqual(0, self.camera.raycast.call_count) def test_on_move_requires_an_action_to_be_started_first(self): self.controller._perturb = mock.MagicMock() self.controller._action = mock.MagicMock() self.controller._action.in_progress = False self.controller.on_move([], []) self.assertEqual(0, self.controller._perturb.tick_move.call_count) def test_dragging_selected_object_moves_it(self): screen_pos = [1, 2] screen_translation = [3, 4] viewport_offset = [5, 6] self.controller._perturb = mock.MagicMock() self.controller._action = mock.MagicMock() self.controller._action.in_progress = True self.viewport.screen_to_viewport.return_value = viewport_offset self.controller.on_move(screen_pos, screen_translation) self.viewport.screen_to_viewport.assert_called_once_with(screen_translation) self.controller._perturb.tick_move.assert_called_once_with(viewport_offset) def test_operations_require_object_to_be_selected(self): self.controller._perturb = None # No exceptions should be raised. self.controller.set_move_vertical_mode(True) self.controller.set_move_vertical_mode(False) self.controller.set_move_horizontal_mode(True) self.controller.set_move_horizontal_mode(False) self.controller.set_rotate_mode(True) self.controller.set_rotate_mode(False) self.controller.on_move([1, 2], [3, 4]) if __name__ == '__main__': absltest.main()