1###############################################################################
2# Copyright (c) Lawrence Livermore National Security, LLC and other Ascent
3# Project developers. See top-level LICENSE AND COPYRIGHT files for dates and
4# other details. No copyright assignment is required to contribute to Ascent.
5###############################################################################
6
7import ipywidgets as widgets
8from traitlets import Unicode, validate, Int, List, Dict
9from IPython.display import clear_output
10
11class TrackballWidget(widgets.DOMWidget):
12    _view_name = Unicode('TrackballView').tag(sync=True)
13    _view_module = Unicode('ascent_widgets').tag(sync=True)
14    _view_module_version = Unicode('0.0.0').tag(sync=True)
15
16    width = Int(800).tag(sync=True)
17    height = Int(800).tag(sync=True)
18    image = Unicode('').tag(sync=True)
19
20    camera_info = Dict({'position': [], 'look_at': [], 'up': [], 'fov': 60}).tag(sync=True)
21
22    scene_bounds = List([]).tag(sync=True)
23
24    def __init__(self, kernelUtils, *args, **kwargs):
25        widgets.DOMWidget.__init__(self, *args, **kwargs)
26
27        self.is_connected = True
28
29        self.on_msg(self._handle_msg)
30
31        self.kernelUtils = kernelUtils
32        self.kernelUtils.set_disconnect_callback(self.disconnect)
33
34        try:
35            self._update_scene_bounds()
36
37            self._update_camera_info_from_ascent()
38
39            self._update_image()
40        except KeyError:
41            clear_output(wait=True)
42            self.close()
43            self.kernelUtils.kernel.stderr("no images found, ensure jupyter_ascent has excecuted actions and re-execute the widget")
44
45    #TODO notify the user and stop trying to handle clicks
46    def disconnect(self):
47        self.is_connected = False
48
49    def _update_image(self):
50        self.image = self.kernelUtils.get_images()[0]
51
52    def _update_camera_info(self, camera_info):
53        self.camera_info = camera_info
54
55    def _update_camera_info_from_ascent(self):
56        ascent_camera_info = self.kernelUtils.get_ascent_info()['images'][0]['camera']
57        self._update_camera_info(ascent_camera_info)
58
59    def _update_scene_bounds(self):
60        self.scene_bounds = self.kernelUtils.get_ascent_info()['images'][0]['scene_bounds']
61
62    def _handle_msg(self, msg, *args, **kwargs):
63        if self.is_connected:
64            content = msg["content"]["data"]["content"]
65            if content['event'] == 'keydown' or content['event'] == 'button':
66                code = content['code']
67                if code == 87 or code == 'move_forward': #W
68                    self.kernelUtils.forward()
69                elif code == 65 or code == 'move_left': #A
70                    self.kernelUtils.left()
71                elif code == 83 or code == 'move_back': #S
72                    self.kernelUtils.back()
73                elif code == 68 or code == 'move_right': #D
74                    self.kernelUtils.right()
75                elif code == 'move_up':
76                    self.kernelUtils.up()
77                elif code == 'move_down':
78                    self.kernelUtils.down()
79                elif code == 'move_right':
80                    self.kernelUtils.right()
81                elif code == 'move_left':
82                    self.kernelUtils.left()
83                elif code == 'roll_c':
84                    self.kernelUtils.roll_c()
85                elif code == 'roll_cc':
86                    self.kernelUtils.roll_cc()
87                elif code == 'pitch_up':
88                    self.kernelUtils.pitch_up()
89                elif code == 'pitch_down':
90                    self.kernelUtils.pitch_down()
91                elif code == 'yaw_right':
92                    self.kernelUtils.yaw_right()
93                elif code == 'yaw_left':
94                    self.kernelUtils.yaw_left()
95                elif code == 'next':
96                    #TODO this can fail
97                    resp = self.kernelUtils.next_frame()
98                    self.is_connected = (resp is not None)
99
100                self._update_camera_info_from_ascent()
101
102            elif content['event'] == 'mouseup':
103                camera_info = content['camera_info']
104                self._update_camera_info(camera_info)
105                self.kernelUtils.look_at(camera_info['position'],
106                               camera_info['look_at'],
107                               camera_info['up'])
108
109            self._update_image()
110        else:
111            clear_output(wait=True)
112            self.close()
113            self.kernelUtils.kernel.stderr("disconnected - wait to reconnect or check the simulation hasn't ended\n")
114