wirepod-vector-sdk-audio 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. anki_vector/__init__.py +43 -0
  2. anki_vector/animation.py +272 -0
  3. anki_vector/annotate.py +590 -0
  4. anki_vector/audio.py +212 -0
  5. anki_vector/audio_stream.py +335 -0
  6. anki_vector/behavior.py +1135 -0
  7. anki_vector/camera.py +670 -0
  8. anki_vector/camera_viewer/__init__.py +121 -0
  9. anki_vector/color.py +88 -0
  10. anki_vector/configure/__main__.py +331 -0
  11. anki_vector/connection.py +838 -0
  12. anki_vector/events.py +420 -0
  13. anki_vector/exceptions.py +185 -0
  14. anki_vector/faces.py +819 -0
  15. anki_vector/lights.py +210 -0
  16. anki_vector/mdns.py +131 -0
  17. anki_vector/messaging/__init__.py +45 -0
  18. anki_vector/messaging/alexa_pb2.py +36 -0
  19. anki_vector/messaging/alexa_pb2_grpc.py +3 -0
  20. anki_vector/messaging/behavior_pb2.py +40 -0
  21. anki_vector/messaging/behavior_pb2_grpc.py +3 -0
  22. anki_vector/messaging/client.py +33 -0
  23. anki_vector/messaging/cube_pb2.py +113 -0
  24. anki_vector/messaging/cube_pb2_grpc.py +3 -0
  25. anki_vector/messaging/extensions_pb2.py +25 -0
  26. anki_vector/messaging/extensions_pb2_grpc.py +3 -0
  27. anki_vector/messaging/external_interface_pb2.py +169 -0
  28. anki_vector/messaging/external_interface_pb2_grpc.py +1267 -0
  29. anki_vector/messaging/messages_pb2.py +431 -0
  30. anki_vector/messaging/messages_pb2_grpc.py +3 -0
  31. anki_vector/messaging/nav_map_pb2.py +33 -0
  32. anki_vector/messaging/nav_map_pb2_grpc.py +3 -0
  33. anki_vector/messaging/protocol.py +33 -0
  34. anki_vector/messaging/response_status_pb2.py +27 -0
  35. anki_vector/messaging/response_status_pb2_grpc.py +3 -0
  36. anki_vector/messaging/settings_pb2.py +72 -0
  37. anki_vector/messaging/settings_pb2_grpc.py +3 -0
  38. anki_vector/messaging/shared_pb2.py +54 -0
  39. anki_vector/messaging/shared_pb2_grpc.py +3 -0
  40. anki_vector/motors.py +127 -0
  41. anki_vector/nav_map.py +409 -0
  42. anki_vector/objects.py +1782 -0
  43. anki_vector/opengl/__init__.py +103 -0
  44. anki_vector/opengl/assets/LICENSE.txt +21 -0
  45. anki_vector/opengl/assets/cube.jpg +0 -0
  46. anki_vector/opengl/assets/cube.mtl +9 -0
  47. anki_vector/opengl/assets/cube.obj +1000 -0
  48. anki_vector/opengl/assets/vector.mtl +67 -0
  49. anki_vector/opengl/assets/vector.obj +13220 -0
  50. anki_vector/opengl/opengl.py +864 -0
  51. anki_vector/opengl/opengl_vector.py +620 -0
  52. anki_vector/opengl/opengl_viewer.py +689 -0
  53. anki_vector/photos.py +145 -0
  54. anki_vector/proximity.py +176 -0
  55. anki_vector/reserve_control/__main__.py +36 -0
  56. anki_vector/robot.py +930 -0
  57. anki_vector/screen.py +201 -0
  58. anki_vector/status.py +322 -0
  59. anki_vector/touch.py +119 -0
  60. anki_vector/user_intent.py +186 -0
  61. anki_vector/util.py +1132 -0
  62. anki_vector/version.py +15 -0
  63. anki_vector/viewer.py +403 -0
  64. anki_vector/vision.py +202 -0
  65. anki_vector/world.py +899 -0
  66. wirepod_vector_sdk_audio-0.9.0.dist-info/METADATA +80 -0
  67. wirepod_vector_sdk_audio-0.9.0.dist-info/RECORD +71 -0
  68. wirepod_vector_sdk_audio-0.9.0.dist-info/WHEEL +5 -0
  69. wirepod_vector_sdk_audio-0.9.0.dist-info/licenses/LICENSE.txt +180 -0
  70. wirepod_vector_sdk_audio-0.9.0.dist-info/top_level.txt +1 -0
  71. wirepod_vector_sdk_audio-0.9.0.dist-info/zip-safe +1 -0
@@ -0,0 +1,689 @@
1
+ # Copyright (c) 2018 Anki, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License in the file LICENSE.txt or at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """This module provides a 3D visualizer for Vector's world state and a 2D camera window.
16
+
17
+ It uses PyOpenGL, a Python OpenGL 3D graphics library which is available on most
18
+ platforms. It also depends on the Pillow library for image processing.
19
+
20
+ Example:
21
+ .. testcode::
22
+
23
+ import time
24
+
25
+ import anki_vector
26
+
27
+ with anki_vector.Robot(show_viewer=True,
28
+ show_3d_viewer=True,
29
+ enable_face_detection=True,
30
+ enable_custom_object_detection=True,
31
+ enable_nav_map_feed=True) as robot:
32
+ time.sleep(10)
33
+
34
+ Warning:
35
+ This package requires Python to have the PyOpenGL package installed, along
36
+ with an implementation of GLUT (OpenGL Utility Toolkit).
37
+
38
+ To install the Python packages on Mac and Linux do ``python3 -m pip install --user "wirepod_vector_sdk[3dviewer]"``
39
+
40
+ To install the Python packages on Windows do ``py -3 -m pip install --user "wirepod_vector_sdk[3dviewer]"``
41
+
42
+ On Windows and Linux you must also install freeglut (macOS / OSX has one
43
+ preinstalled).
44
+
45
+ On Linux: ``sudo apt-get install freeglut3``
46
+
47
+ On Windows: Go to http://freeglut.sourceforge.net/ to get a ``freeglut.dll``
48
+ file. It's included in any of the `Windows binaries` downloads. Place the DLL
49
+ next to your Python script, or install it somewhere in your PATH to allow any
50
+ script to use it."
51
+ """
52
+
53
+ # __all__ should order by constants, event classes, other classes, functions.
54
+ __all__ = ['OpenGLViewer']
55
+
56
+ import math
57
+ import multiprocessing as mp
58
+ import sys
59
+ from typing import List
60
+
61
+ from anki_vector import nav_map, util
62
+ from . import opengl, opengl_vector
63
+
64
+
65
+ try:
66
+ from OpenGL.GL import (GL_FILL,
67
+ GL_FRONT_AND_BACK,
68
+ GL_LIGHTING, GL_NORMALIZE,
69
+ GL_TEXTURE_2D,
70
+ glBindTexture, glColor3f, glDisable, glEnable,
71
+ glMultMatrixf, glPolygonMode, glPopMatrix, glPushMatrix,
72
+ glScalef, glWindowPos2f)
73
+ from OpenGL.GLUT import (ctypes,
74
+ GLUT_ACTIVE_ALT, GLUT_ACTIVE_CTRL, GLUT_ACTIVE_SHIFT, GLUT_BITMAP_9_BY_15,
75
+ GLUT_DOWN, GLUT_LEFT_BUTTON, GLUT_RIGHT_BUTTON, GLUT_VISIBLE,
76
+ glutBitmapCharacter, glutCheckLoop, glutGetModifiers, glutIdleFunc,
77
+ glutKeyboardFunc, glutKeyboardUpFunc, glutMainLoop, glutMouseFunc, glutMotionFunc, glutPassiveMotionFunc,
78
+ glutPostRedisplay, glutSpecialFunc, glutSpecialUpFunc, glutVisibilityFunc)
79
+ from OpenGL.error import NullFunctionError
80
+
81
+ except ImportError as import_exc:
82
+ opengl.raise_opengl_or_pillow_import_error(import_exc)
83
+
84
+
85
+ # Constants
86
+
87
+
88
+ class _RobotControlIntents(): # pylint: disable=too-few-public-methods
89
+ """Input intents for controlling the robot.
90
+
91
+ These are sent from the OpenGL process, and consumed by the main process for
92
+ issuing movement commands on Vector (to provide a remote-control interface).
93
+ """
94
+
95
+ def __init__(self, left_wheel_speed=0.0, right_wheel_speed=0.0,
96
+ lift_speed=0.0, head_speed=0.0, connect_to_light_block=False):
97
+ self.left_wheel_speed = left_wheel_speed
98
+ self.right_wheel_speed = right_wheel_speed
99
+ self.lift_speed = lift_speed
100
+ self.head_speed = head_speed
101
+ self.connect_to_light_block = connect_to_light_block
102
+
103
+
104
+ def _draw_text(font, input_str, x, y, line_height=16, r=1.0, g=1.0, b=1.0):
105
+ """Render text based on window position. The origin is in the bottom-left."""
106
+ glColor3f(r, g, b)
107
+ glWindowPos2f(x, y)
108
+ input_list = input_str.split('\n')
109
+ y = y + (line_height * (len(input_list) - 1))
110
+ for line in input_list:
111
+ glWindowPos2f(x, y)
112
+ y -= line_height
113
+ for ch in line:
114
+ glutBitmapCharacter(font, ctypes.c_int(ord(ch)))
115
+
116
+
117
+ def _glut_install_instructions():
118
+ if sys.platform.startswith('linux'):
119
+ return "Install freeglut: `sudo apt-get install freeglut3`"
120
+ if sys.platform.startswith('darwin'):
121
+ return "GLUT should already be installed by default on macOS!"
122
+ if sys.platform in ('win32', 'cygwin'):
123
+ return "Install freeglut: You can download it from http://freeglut.sourceforge.net/ \n"\
124
+ "You just need the `freeglut.dll` file, from any of the 'Windows binaries' downloads. "\
125
+ "Place the DLL next to your Python script, or install it somewhere in your PATH "\
126
+ "to allow any script to use it."
127
+ return "(Instructions unknown for platform %s)" % sys.platform
128
+
129
+
130
+ class _OpenGLViewController():
131
+ """Controller that registers for keyboard and mouse input through GLUT, and uses them to update
132
+ the camera and listen for a shutdown cue.
133
+
134
+ :param shutdown_delegate: Function to call when we want to exit the host OpenGLViewer.
135
+ :param camera: The camera object for the controller to mutate.
136
+ :param input_intent_queue: Sends key commands from the 3D viewer process to the main process.
137
+ :type input_intent_queue: multiprocessing.Queue
138
+ :param viewer: A reference to the owning OpenGLViewer.
139
+ :type viewer: OpenGLViewer
140
+ """
141
+
142
+ def __init__(self, shutdown_delegate: callable, camera: opengl.Camera, input_intent_queue: mp.Queue, viewer):
143
+
144
+ self._logger = util.get_class_logger(__name__, self)
145
+ self._input_intent_queue = input_intent_queue
146
+ self._last_robot_control_intents = _RobotControlIntents()
147
+ self._is_keyboard_control_enabled = False
148
+
149
+ # Keyboard
150
+ self._is_key_pressed = {}
151
+ self._is_alt_down = False
152
+ self._is_ctrl_down = False
153
+ self._is_shift_down = False
154
+
155
+ # Mouse
156
+ self._is_mouse_down = {}
157
+ self._mouse_pos = None # type: util.Vector2
158
+
159
+ self._shutdown_delegate = shutdown_delegate
160
+
161
+ self._last_robot_position = None
162
+
163
+ self._camera = camera
164
+
165
+ self._opengl_viewer = viewer
166
+
167
+ #### Public Properties ####
168
+
169
+ @property
170
+ def last_robot_position(self):
171
+ return self._last_robot_position
172
+
173
+ @last_robot_position.setter
174
+ def last_robot_position(self, last_robot_position):
175
+ self._last_robot_position = last_robot_position
176
+
177
+ #### Public Methods ####
178
+
179
+ def initialize(self):
180
+ """Sets up the OpenGL window and binds input callbacks to it
181
+ """
182
+
183
+ glutKeyboardFunc(self._on_key_down)
184
+ glutSpecialFunc(self._on_special_key_down)
185
+
186
+ # [Keyboard/Special]Up methods aren't supported on some old GLUT implementations
187
+ has_keyboard_up = False
188
+ has_special_up = False
189
+ try:
190
+ if bool(glutKeyboardUpFunc):
191
+ glutKeyboardUpFunc(self._on_key_up)
192
+ has_keyboard_up = True
193
+ if bool(glutSpecialUpFunc):
194
+ glutSpecialUpFunc(self._on_special_key_up)
195
+ has_special_up = True
196
+ except NullFunctionError:
197
+ # Methods aren't available on this GLUT version
198
+ pass
199
+
200
+ if not has_keyboard_up or not has_special_up:
201
+ # Warn on old GLUT implementations that don't implement much of the interface.
202
+ self._logger.warning("Warning: Old GLUT implementation detected - keyboard remote control of Vector disabled."
203
+ "We recommend installing freeglut. %s", _glut_install_instructions())
204
+ self._is_keyboard_control_enabled = False
205
+ else:
206
+ self._is_keyboard_control_enabled = True
207
+
208
+ try:
209
+ GLUT_BITMAP_9_BY_15
210
+ except NameError:
211
+ self._logger.warning("Warning: GLUT font not detected. Help message will be unavailable.")
212
+
213
+ glutMouseFunc(self._on_mouse_button)
214
+ glutMotionFunc(self._on_mouse_move)
215
+ glutPassiveMotionFunc(self._on_mouse_move)
216
+
217
+ glutIdleFunc(self._idle)
218
+ glutVisibilityFunc(self._visible)
219
+
220
+ #### Private Methods ####
221
+
222
+ def _update_modifier_keys(self):
223
+ """Updates alt, ctrl, and shift states.
224
+ """
225
+ modifiers = glutGetModifiers()
226
+ self._is_alt_down = (modifiers & GLUT_ACTIVE_ALT != 0)
227
+ self._is_ctrl_down = (modifiers & GLUT_ACTIVE_CTRL != 0)
228
+ self._is_shift_down = (modifiers & GLUT_ACTIVE_SHIFT != 0)
229
+
230
+ def _key_byte_to_lower(self, key): # pylint: disable=no-self-use
231
+ """Convert bytes-object (representing keyboard character) to lowercase equivalent.
232
+ """
233
+ if b'A' <= key <= b'Z':
234
+ lowercase_key = ord(key) - ord(b'A') + ord(b'a')
235
+ lowercase_key = bytes([lowercase_key])
236
+ return lowercase_key
237
+ return key
238
+
239
+ def _on_key_up(self, key, x, y): # pylint: disable=unused-argument
240
+ """Called by GLUT when a standard keyboard key is released.
241
+
242
+ :param key: which key was released.
243
+ :param x: the x coordinate of the mouse cursor.
244
+ :param y: the y coordinate of the mouse cursor.
245
+ """
246
+ key = self._key_byte_to_lower(key)
247
+ self._update_modifier_keys()
248
+ self._is_key_pressed[key] = False
249
+
250
+ def _on_key_down(self, key, x, y): # pylint: disable=unused-argument
251
+ """Called by GLUT when a standard keyboard key is pressed.
252
+
253
+ :param key: which key was released.
254
+ :param x: the x coordinate of the mouse cursor.
255
+ :param y: the y coordinate of the mouse cursor.
256
+ """
257
+ key = self._key_byte_to_lower(key)
258
+ self._update_modifier_keys()
259
+ self._is_key_pressed[key] = True
260
+
261
+ if ord(key) == 9: # Tab
262
+ # Set Look-At point to current robot position
263
+ if self._last_robot_position is not None:
264
+ self._camera.look_at = self._last_robot_position
265
+ elif ord(key) == 27: # Escape key
266
+ self._shutdown_delegate()
267
+ elif ord(key) == 72 or ord(key) == 104: # H key
268
+ self._opengl_viewer.show_controls = not self._opengl_viewer.show_controls
269
+
270
+ def _on_special_key_up(self, key, x, y): # pylint: disable=unused-argument
271
+ """Called by GLUT when a special key is released.
272
+
273
+ :param key: which key was released.
274
+ :param x: the x coordinate of the mouse cursor.
275
+ :param y: the y coordinate of the mouse cursor.
276
+ """
277
+ self._update_modifier_keys()
278
+
279
+ def _on_special_key_down(self, key, x, y): # pylint: disable=unused-argument
280
+ """Called by GLUT when a special key is pressed.
281
+
282
+ :param key: which key was pressed.
283
+ :param x: the x coordinate of the mouse cursor.
284
+ :param y: the y coordinate of the mouse cursor.
285
+ """
286
+ self._update_modifier_keys()
287
+
288
+ def _on_mouse_button(self, button, state, x, y):
289
+ """Called by GLUT when a mouse button is pressed.
290
+
291
+ :param button: which button was pressed.
292
+ :param state: the current state of the button.
293
+ :param x: the x coordinate of the mouse cursor.
294
+ :param y: the y coordinate of the mouse cursor.
295
+ """
296
+ # Don't update modifier keys- reading modifier keys is unreliable
297
+ # from _on_mouse_button (for LMB down/up), only SHIFT key seems to read there
298
+ # self._update_modifier_keys()
299
+ is_down = (state == GLUT_DOWN)
300
+ self._is_mouse_down[button] = is_down
301
+ self._mouse_pos = util.Vector2(x, y)
302
+
303
+ def _on_mouse_move(self, x, y):
304
+ """Handles mouse movement.
305
+
306
+ :param x: the x coordinate of the mouse cursor.
307
+ :param y: the y coordinate of the mouse cursor.
308
+ """
309
+
310
+ # is_active is True if this is not passive (i.e. a mouse button was down)
311
+ last_mouse_pos = self._mouse_pos
312
+ self._mouse_pos = util.Vector2(x, y)
313
+ if last_mouse_pos is None:
314
+ # First mouse update - ignore (we need a delta of mouse positions)
315
+ return
316
+
317
+ left_button = self._is_mouse_down.get(GLUT_LEFT_BUTTON, False)
318
+ # For laptop and other 1-button mouse users, treat 'x' key as a right mouse button too
319
+ right_button = (self._is_mouse_down.get(GLUT_RIGHT_BUTTON, False)
320
+ or self._is_key_pressed.get(b'x', False))
321
+
322
+ MOUSE_SPEED_SCALAR = 1.0 # general scalar for all mouse movement sensitivity
323
+ MOUSE_ROTATE_SCALAR = 0.025 # additional scalar for rotation sensitivity
324
+ mouse_delta = (self._mouse_pos - last_mouse_pos) * MOUSE_SPEED_SCALAR
325
+
326
+ if left_button and right_button:
327
+ # Move up/down
328
+ self._camera.move(up_amount=-mouse_delta.y)
329
+ elif right_button:
330
+ # Move forward/back and left/right
331
+ self._camera.move(forward_amount=mouse_delta.y, right_amount=mouse_delta.x)
332
+ elif left_button:
333
+ if self._is_key_pressed.get(b'z', False):
334
+ # Zoom in/out
335
+ self._camera.zoom(mouse_delta.y)
336
+ else:
337
+ self._camera.turn(mouse_delta.x * MOUSE_ROTATE_SCALAR, mouse_delta.y * MOUSE_ROTATE_SCALAR)
338
+
339
+ def _update_intents_for_robot(self):
340
+ # Update driving intents based on current input, and pass to SDK thread
341
+ # so that it can pass the input onto the robot.
342
+ def get_intent_direction(key1, key2):
343
+ # Helper for keyboard inputs that have 1 positive and 1 negative input
344
+ pos_key = self._is_key_pressed.get(key1, False)
345
+ neg_key = self._is_key_pressed.get(key2, False)
346
+ return pos_key - neg_key
347
+
348
+ drive_dir = get_intent_direction(b'w', b's')
349
+ turn_dir = get_intent_direction(b'd', b'a')
350
+ lift_dir = get_intent_direction(b'r', b'f')
351
+ head_dir = get_intent_direction(b't', b'g')
352
+ if drive_dir < 0:
353
+ # It feels more natural to turn the opposite way when reversing
354
+ turn_dir = -turn_dir
355
+
356
+ # Scale drive speeds with SHIFT (faster) and ALT (slower)
357
+ if self._is_shift_down:
358
+ speed_scalar = 2.0
359
+ elif self._is_alt_down:
360
+ speed_scalar = 0.5
361
+ else:
362
+ speed_scalar = 1.0
363
+
364
+ drive_speed = 75.0 * speed_scalar
365
+ turn_speed = 100.0 * speed_scalar
366
+
367
+ left_wheel_speed = (drive_dir * drive_speed) + (turn_speed * turn_dir)
368
+ right_wheel_speed = (drive_dir * drive_speed) - (turn_speed * turn_dir)
369
+ lift_speed = 4.0 * lift_dir * speed_scalar
370
+ head_speed = head_dir * speed_scalar
371
+
372
+ connect_block = self._is_key_pressed.get(b'c', False)
373
+
374
+ control_intents = _RobotControlIntents(left_wheel_speed, right_wheel_speed,
375
+ lift_speed, head_speed, connect_block)
376
+ self._input_intent_queue.put(control_intents, True)
377
+
378
+ def _idle(self):
379
+ if self._is_keyboard_control_enabled:
380
+ self._update_intents_for_robot()
381
+ glutPostRedisplay()
382
+
383
+ def _visible(self, vis):
384
+ # Called from OpenGL when visibility changes (windows are either visible
385
+ # or completely invisible/hidden)
386
+ if vis == GLUT_VISIBLE:
387
+ glutIdleFunc(self._idle)
388
+ else:
389
+ glutIdleFunc(None)
390
+
391
+
392
+ #: A default window resolution provided for OpenGL Vector programs
393
+ #: 800x600 is large enough to see detail, while fitting on the smaller
394
+ #: end of modern monitors.
395
+ default_resolution = [800, 600]
396
+
397
+ #: A default projector configurate provided for OpenGL Vector programs
398
+ #: A Field of View of 45 degrees is common for 3d applications,
399
+ #: and a viewable distance range of 1.0 to 1000.0 will provide a
400
+ #: visible space comparable with most physical Vector environments.
401
+ default_projector = opengl.Projector(
402
+ fov=45.0,
403
+ near_clip_plane=1.0,
404
+ far_clip_plane=1000.0)
405
+
406
+ #: A default camera object provided for OpenGL Vector programs.
407
+ #: Starts close to and looking at the charger.
408
+ default_camera = opengl.Camera(
409
+ look_at=util.Vector3(100.0, -25.0, 0.0),
410
+ up=util.Vector3(0.0, 0.0, 1.0),
411
+ distance=500.0,
412
+ pitch=math.radians(40),
413
+ yaw=math.radians(270))
414
+
415
+ #: A default light group provided for OpenGL Vector programs.
416
+ #: Contains one light near the origin.
417
+ default_lights = [opengl.Light(
418
+ ambient_color=[1.0, 1.0, 1.0, 1.0],
419
+ diffuse_color=[1.0, 1.0, 1.0, 1.0],
420
+ specular_color=[1.0, 1.0, 1.0, 1.0],
421
+ position=util.Vector3(0, 32, 20))]
422
+
423
+ # Global viewer instance. Stored to make sure multiple viewers are not
424
+ # instantiated simultaneously.
425
+ opengl_viewer = None # type: OpenGLViewer
426
+
427
+
428
+ class OpenGLViewer():
429
+ """OpenGL-based 3D Viewer.
430
+
431
+ Handles rendering of a 3D world view including navigation map.
432
+
433
+ :param close_event: Used to notify each process when done rendering.
434
+ :type close_event: multiprocessing.Event
435
+ :param input_intent_queue: Sends key commands from the 3D viewer process to the main process.
436
+ :type input_intent_queue: multiprocessing.Queue
437
+ :param nav_map_queue: Updates the 3D viewer process with the latest navigation map.
438
+ :type nav_map_queue: multiprocessing.Queue
439
+ :param world_frame_queue: Provides the 3D viewer with details about the world.
440
+ :type world_frame_queue: multiprocessing.Queue
441
+ :param extra_render_function_queue: Functions to be executed in the 3D viewer process.
442
+ :type extra_render_function_queue: multiprocessing.Queue
443
+ :param user_data_queue: A queue that may be used outside the SDK to pass information to the viewer process.
444
+ May be used by ``extra_render_function_queue`` functions.
445
+ :type user_data_queue: multiprocessing.Queue
446
+ :param resolution: Specifies whether to draw controls on the view.
447
+ :param projector: Specifies whether to draw controls on the view.
448
+ :param camera: Specifies whether to draw controls on the view.
449
+ :param lights: Specifies whether to draw controls on the view.
450
+ :param show_viewer_controls: Specifies whether to draw controls on the view.
451
+ """
452
+
453
+ def __init__(self,
454
+ close_event: mp.Event,
455
+ input_intent_queue: mp.Queue,
456
+ nav_map_queue: mp.Queue,
457
+ world_frame_queue: mp.Queue,
458
+ extra_render_function_queue: mp.Queue,
459
+ user_data_queue: mp.Queue,
460
+ resolution: List[int] = None,
461
+ projector: opengl.Projector = None,
462
+ camera: opengl.Camera = None,
463
+ lights: List[opengl.Light] = None,
464
+ show_viewer_controls: bool = True):
465
+ if resolution is None:
466
+ resolution = default_resolution
467
+ if projector is None:
468
+ projector = default_projector
469
+ if camera is None:
470
+ camera = default_camera
471
+ if lights is None:
472
+ lights = default_lights
473
+
474
+ self._close_event = close_event
475
+ self._input_intent_queue = input_intent_queue
476
+ self._nav_map_queue = nav_map_queue
477
+ self._world_frame_queue = world_frame_queue
478
+ self._extra_render_function_queue = extra_render_function_queue
479
+ self._user_data_queue = user_data_queue
480
+
481
+ self._logger = util.get_class_logger(__name__, self)
482
+ self._extra_render_calls = []
483
+
484
+ self._internal_function_finished = False
485
+
486
+ # Controls
487
+ self.show_controls = show_viewer_controls
488
+ self._instructions = '\n'.join(['W, S: Move forward, backward',
489
+ 'A, D: Turn left, right',
490
+ 'R, F: Lift up, down',
491
+ 'T, G: Head up, down',
492
+ '',
493
+ 'C: Connect to LightCube',
494
+ '',
495
+ 'LMB: Rotate camera',
496
+ 'RMB: Move camera',
497
+ 'LMB + RMB: Move camera up/down',
498
+ 'LMB + Z: Zoom camera',
499
+ 'X: same as RMB',
500
+ 'TAB: center view on robot',
501
+ '',
502
+ 'H: Toggle help'])
503
+
504
+ self._vector_view_manifest = opengl_vector.VectorViewManifest()
505
+ self._main_window = opengl.OpenGLWindow(0, 0, resolution[0], resolution[1], b"Vector 3D Visualizer")
506
+
507
+ # Create a 3d projector configuration class.
508
+ self._projector = projector
509
+ self._camera = camera
510
+ self._lights = lights
511
+
512
+ self._view_controller = _OpenGLViewController(self.close, self._camera, self._input_intent_queue, self)
513
+
514
+ self._latest_world_frame: opengl_vector.WorldRenderFrame = None
515
+
516
+ def _render_world_frame(self, world_frame: opengl_vector.WorldRenderFrame):
517
+ """Render the world to the current OpenGL context
518
+
519
+ :param world_frame: frame to render
520
+ """
521
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
522
+ glEnable(GL_NORMALIZE) # to re-scale scaled normals
523
+
524
+ light_cube_view = self._vector_view_manifest.light_cube_view
525
+ unit_cube_view = self._vector_view_manifest.unit_cube_view
526
+ robot_view = self._vector_view_manifest.robot_view
527
+ nav_map_view = self._vector_view_manifest.nav_map_view
528
+
529
+ robot_frame = world_frame.robot_frame
530
+ robot_pose = robot_frame.pose
531
+
532
+ try:
533
+ glDisable(GL_LIGHTING)
534
+ nav_map_view.display()
535
+
536
+ glEnable(GL_LIGHTING)
537
+ # Render the cube
538
+ for obj in world_frame.cube_frames:
539
+ cube_pose = obj.pose
540
+ if cube_pose is not None and cube_pose.is_comparable(robot_pose):
541
+ light_cube_view.display(cube_pose)
542
+
543
+ # Render the custom objects
544
+ for obj in world_frame.custom_object_frames:
545
+ obj_pose = obj.pose
546
+ if obj_pose is not None and obj_pose.is_comparable(robot_pose):
547
+ glPushMatrix()
548
+ obj_matrix = obj_pose.to_matrix()
549
+ glMultMatrixf(obj_matrix.in_row_order)
550
+
551
+ glScalef(obj.x_size_mm * 0.5,
552
+ obj.y_size_mm * 0.5,
553
+ obj.z_size_mm * 0.5)
554
+
555
+ # Only draw solid object for observable custom objects
556
+
557
+ if obj.is_fixed:
558
+ # fixed objects are drawn as transparent outlined boxes to make
559
+ # it clearer that they have no effect on vision.
560
+ FIXED_OBJECT_COLOR = [1.0, 0.7, 0.0, 1.0]
561
+ unit_cube_view.display(FIXED_OBJECT_COLOR, False)
562
+ else:
563
+ CUSTOM_OBJECT_COLOR = [1.0, 0.3, 0.3, 1.0]
564
+ unit_cube_view.display(CUSTOM_OBJECT_COLOR, True)
565
+
566
+ glPopMatrix()
567
+
568
+ glBindTexture(GL_TEXTURE_2D, 0)
569
+
570
+ for face in world_frame.face_frames:
571
+ face_pose = face.pose
572
+ if face_pose is not None and face_pose.is_comparable(robot_pose):
573
+ glPushMatrix()
574
+ face_matrix = face_pose.to_matrix()
575
+ glMultMatrixf(face_matrix.in_row_order)
576
+
577
+ # Approximate size of a head
578
+ glScalef(100, 25, 100)
579
+
580
+ FACE_OBJECT_COLOR = [0.5, 0.5, 0.5, 1.0]
581
+ draw_solid = face.time_since_last_seen < 30
582
+ unit_cube_view.display(FACE_OBJECT_COLOR, draw_solid)
583
+
584
+ glPopMatrix()
585
+ except BaseException as e:
586
+ self._logger.error('rendering error: {0}'.format(e))
587
+
588
+ glDisable(GL_LIGHTING)
589
+
590
+ # Draw the Vector robot to the screen
591
+ robot_view.display(robot_frame.pose, robot_frame.head_angle, robot_frame.lift_position)
592
+
593
+ if self.show_controls:
594
+ self._draw_controls(world_frame.cube_connected(), world_frame.cube_connecting())
595
+
596
+ def _draw_controls(self, cube_connected, cube_connecting):
597
+ try:
598
+ GLUT_BITMAP_9_BY_15
599
+ except NameError:
600
+ pass
601
+ else:
602
+ _draw_text(GLUT_BITMAP_9_BY_15, self._instructions, x=10, y=10)
603
+ if cube_connecting:
604
+ _draw_text(GLUT_BITMAP_9_BY_15, "<connecting...>", x=600, y=10, r=0.75, g=0.5, b=0.0)
605
+ elif cube_connected:
606
+ _draw_text(GLUT_BITMAP_9_BY_15, "<cube connected>", x=600, y=10, r=0.0, g=0.85, b=0.0)
607
+ else:
608
+ _draw_text(GLUT_BITMAP_9_BY_15, "<no cube connected>", x=600, y=10, r=0.75, g=0.75, b=0.75)
609
+
610
+ def _render_3d_view(self, window: opengl.OpenGLWindow):
611
+ """Renders 3d objects to an openGL window
612
+
613
+ :param window: OpenGL window to render to
614
+ """
615
+ window.prepare_for_rendering(self._projector, self._camera, self._lights)
616
+
617
+ try:
618
+ extra_render_call = self._extra_render_function_queue.get(False)
619
+ self._extra_render_calls.append(extra_render_call)
620
+ except mp.queues.Empty:
621
+ pass
622
+
623
+ # Update the latest world frame if there is a new one available
624
+ try:
625
+ world_frame = self._world_frame_queue.get(False) # type: WorldRenderFrame
626
+ if world_frame is not None:
627
+ self._view_controller.last_robot_position = world_frame.robot_frame.pose.position
628
+ self._latest_world_frame = world_frame
629
+ except mp.queues.Empty:
630
+ world_frame = self._latest_world_frame
631
+
632
+ try:
633
+ new_nav_map = self._nav_map_queue.get(False)
634
+ if new_nav_map is not None:
635
+ new_nav_map = nav_map.NavMapGrid(new_nav_map, self._logger)
636
+ self._vector_view_manifest.nav_map_view.build_from_nav_map(new_nav_map)
637
+ except mp.queues.Empty:
638
+ # no new nav map - queue is empty
639
+ pass
640
+
641
+ if world_frame is not None:
642
+ self._render_world_frame(world_frame)
643
+
644
+ for render_call in self._extra_render_calls:
645
+ # Protecting the external calls with pushMatrix so internal transform
646
+ # state changes will not alter other calls
647
+ glPushMatrix()
648
+ try:
649
+ render_call.invoke(self._user_data_queue)
650
+ finally:
651
+ glPopMatrix()
652
+
653
+ window.display_rendered_content()
654
+
655
+ def _on_window_update(self):
656
+ """Top level display call.
657
+ """
658
+ try:
659
+ self._render_3d_view(self._main_window)
660
+
661
+ except KeyboardInterrupt:
662
+ self._logger.info("_display caught KeyboardInterrupt - exitting")
663
+ self._close_event.set()
664
+
665
+ def run(self):
666
+ """Turns control of the current thread over to the OpenGL viewer
667
+ """
668
+ self._main_window.initialize(self._on_window_update)
669
+ self._view_controller.initialize()
670
+
671
+ self._vector_view_manifest.load_assets()
672
+
673
+ # use a non-blocking update loop if possible to make exit conditions
674
+ # easier (not supported on all GLUT versions).
675
+ if bool(glutCheckLoop):
676
+ while not self._close_event.is_set():
677
+ glutCheckLoop()
678
+ else:
679
+ # This blocks until quit
680
+ glutMainLoop()
681
+
682
+ if not self._close_event.is_set():
683
+ # Pass the keyboard interrupt on to SDK so that it can close cleanly
684
+ raise KeyboardInterrupt
685
+
686
+ def close(self):
687
+ """Called from the SDK when the program is complete and it's time to exit."""
688
+ if not self._close_event.is_set():
689
+ self._close_event.set()