diff --git a/src/software/embedded/robot_diagnostics_cli/requirements_lock.txt b/src/software/embedded/robot_diagnostics_cli/requirements_lock.txt index ac94619ecf..e69de29bb2 100644 --- a/src/software/embedded/robot_diagnostics_cli/requirements_lock.txt +++ b/src/software/embedded/robot_diagnostics_cli/requirements_lock.txt @@ -1,134 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# bazel run //software/embedded/robot_diagnostics_cli:requirements.update -# -click==8.1.7 \ - --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ - --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de - # via - # click-shell - # typer - # typer-shell -click-shell==2.1 \ - --hash=sha256:2d971a2e50eb7ad387cf0ce79ba4b844e66e0580784e2efe2df58b50a2f047f0 \ - --hash=sha256:ce0c91faae284c41a39bec966f928791ad4a45763755445f1fe2041fd091aa37 - # via typer-shell -inquirerpy==0.3.4 \ - --hash=sha256:89d2ada0111f337483cb41ae31073108b2ec1e618a49d7110b0d7ade89fc197e \ - --hash=sha256:c65fdfbac1fa00e3ee4fb10679f4d3ed7a012abf4833910e63c295827fe2a7d4 - # via -r software/embedded/robot_diagnostics_cli/requirements.in -markdown-it-py==3.0.0 \ - --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ - --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb - # via rich -mdurl==0.1.2 \ - --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ - --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba - # via markdown-it-py -pfzy==0.3.4 \ - --hash=sha256:5f50d5b2b3207fa72e7ec0ef08372ef652685470974a107d0d4999fc5a903a96 \ - --hash=sha256:717ea765dd10b63618e7298b2d98efd819e0b30cd5905c9707223dceeb94b3f1 - # via inquirerpy -prompt-toolkit==3.0.48 \ - --hash=sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90 \ - --hash=sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e - # via inquirerpy -protobuf==6.31.1 \ - --hash=sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16 \ - --hash=sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447 \ - --hash=sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6 \ - --hash=sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402 \ - --hash=sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e \ - --hash=sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9 \ - --hash=sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9 \ - --hash=sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39 \ - --hash=sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a - # via -r software/embedded/robot_diagnostics_cli/requirements.in -pygments==2.18.0 \ - --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ - --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a - # via rich -pyyaml==6.0.2 \ - --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ - --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ - --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ - --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ - --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ - --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ - --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ - --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ - --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ - --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ - --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ - --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ - --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ - --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ - --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ - --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ - --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ - --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ - --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ - --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ - --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ - --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ - --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ - --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ - --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ - --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ - --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ - --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ - --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ - --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ - --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ - --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ - --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ - --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ - --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ - --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ - --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ - --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ - --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ - --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ - --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ - --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ - --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ - --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ - --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ - --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ - --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ - --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ - --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ - --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ - --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ - --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ - --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 - # via typer-shell -rich==13.9.2 \ - --hash=sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c \ - --hash=sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1 - # via - # -r software/embedded/robot_diagnostics_cli/requirements.in - # typer - # typer-shell -shellingham==1.5.4 \ - --hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \ - --hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de - # via typer -typer==0.12.5 \ - --hash=sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b \ - --hash=sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722 - # via typer-shell -typer-shell==0.1.12 \ - --hash=sha256:63b0b6b46b24c906d027b410bc521eeabc893f72c970318a7dd7034e95186caa \ - --hash=sha256:dc7742649e6dcdd77cc3ab909cbac23438a2e3ad6cf847cadc35bb0937b4965c - # via -r software/embedded/robot_diagnostics_cli/requirements.in -typing-extensions==4.12.2 \ - --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ - --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 - # via typer -wcwidth==0.2.13 \ - --hash=sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859 \ - --hash=sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5 - # via prompt-toolkit diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index eb5bc0bb00..8b8969175f 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -264,6 +264,7 @@ class Colors: TRANSPARENT = QtGui.QColor(0, 0, 0, 0) SPEED_VECTOR_COLOR = QtGui.QColor(255, 0, 255, 100) + SELECTED_ROBOT_OUTLINE = QtGui.QColor(0, 255, 255, 200) DESIRED_ROBOT_LOCATION_OUTLINE = QtGui.QColor(255, 0, 0, 255) NAVIGATOR_PATH_COLOR = QtGui.QColor(0, 255, 0, 255) NAVIGATOR_OBSTACLE_COLOR = QtGui.QColor(255, 80, 0, 100) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 28f64a299e..469c741b9e 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -241,6 +241,8 @@ def add_layer(self, layer: GLLayer, visible: bool = True) -> None: layer_checkbox.stateChanged.connect( lambda: layer.setVisible(layer_checkbox.isChecked()) ) + if layer.needs_mouse_movement_tracking: + layer_checkbox.stateChanged.connect(self._update_mouse_tracking) def remove_layer(self, layer: GLLayer) -> None: """Remove a layer from this GLWidget @@ -304,17 +306,23 @@ def set_camera_view(self, camera_view: CameraView) -> None: pos=pg.Vector(2.5, 0, 0), distance=10, elevation=45, azimuth=0 ) + def _update_mouse_tracking(self) -> None: + """Enable detect_mouse_movement_in_scene if measure mode or any layer that + requires mouse movement tracking is active; disable it when both are off. + """ + movement_layer_active = any( + layer.needs_mouse_movement_tracking and layer.visible() + for layer in self.layers + ) + self.gl_view_widget.detect_mouse_movement_in_scene = ( + self.measure_mode_enabled or movement_layer_active + ) + def toggle_measure_mode(self) -> None: """Toggles measure mode in the 3D visualizer""" self.measure_mode_enabled = not self.measure_mode_enabled - # Enable/disable detect_mouse_movement_in_scene in ExtendedGLViewWidget - # so that the mouse_in_scene_moved_signal is emitted if measure mode is on. - # - # Normally we want to disable detect_mouse_movement_in_scene so that we - # don't do unnecessary calculations every tick to find the point in the scene - # that the mouse is pointing at. - self.gl_view_widget.detect_mouse_movement_in_scene = self.measure_mode_enabled + self._update_mouse_tracking() if self.measure_mode_enabled: self.measure_layer = GLMeasureLayer("Measure") diff --git a/src/software/thunderscope/gl/layers/gl_layer.py b/src/software/thunderscope/gl/layers/gl_layer.py index 4d6fb40415..b01d487008 100644 --- a/src/software/thunderscope/gl/layers/gl_layer.py +++ b/src/software/thunderscope/gl/layers/gl_layer.py @@ -17,6 +17,8 @@ class GLLayer(GLGraphicsItem): enabling us to group together related layers. """ + needs_mouse_movement_tracking: bool = False + def __init__(self, name: str, parent_item: GLGraphicsItem | None = None) -> None: """Initialize the GLLayer diff --git a/src/software/thunderscope/gl/layers/gl_movement_field_test_layer.py b/src/software/thunderscope/gl/layers/gl_movement_field_test_layer.py index 8fb312bae6..56b30ec5ca 100644 --- a/src/software/thunderscope/gl/layers/gl_movement_field_test_layer.py +++ b/src/software/thunderscope/gl/layers/gl_movement_field_test_layer.py @@ -5,6 +5,9 @@ from software.thunderscope.gl.layers.gl_world_layer import tbots_cpp from software.thunderscope.proto_unix_io import ProtoUnixIO from software.logger.logger import create_logger +from software.thunderscope.constants import Colors +from software.thunderscope.gl.graphics.gl_robot_outline import GLRobotOutline +from software.thunderscope.gl.graphics.gl_line_strip import GLLineStrip from software.thunderscope.gl.layers.gl_layer import GLLayer from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from typing import override @@ -14,6 +17,11 @@ class GLMovementFieldTestLayer(GLLayer): + needs_mouse_movement_tracking = True + + DEFAULT_ORIENTATION = -math.pi / 2 + ORIENTATION_LINE_LENGTH = 0.3 + def __init__( self, name: str, fullsystem_io: ProtoUnixIO, buffer_size: int = 5 ) -> None: @@ -28,21 +36,55 @@ def __init__( self.world_buffer: ThreadSafeBuffer = ThreadSafeBuffer(buffer_size, World) self.fullsystem_io: ProtoUnixIO = fullsystem_io self.selected_robot_id = 0 - self.cached_team: tbots_cpp.Team = None + self.cached_world: World | None = None self.is_selected = False + # State for drag-to-orient movement + self.is_dragging_to_orient = False + self.target_point = None + self.current_orientation = self.DEFAULT_ORIENTATION + + self.selected_robot_graphic = GLRobotOutline( + parent_item=self, outline_color=Colors.SELECTED_ROBOT_OUTLINE + ) + self.preview_robot_graphic = GLRobotOutline( + parent_item=self, outline_color=Colors.DESIRED_ROBOT_LOCATION_OUTLINE + ) + self.preview_orientation_graphic = GLLineStrip( + parent_item=self, outline_color=Colors.DESIRED_ROBOT_LOCATION_OUTLINE + ) + self.selected_robot_graphic.hide() + self.preview_robot_graphic.hide() + self.preview_orientation_graphic.hide() + + def _get_selected_robot(self, cached_world: World | None, selected_robot_id: int): + """Return the proto robot matching the selected robot ID, or None. + + :param cached_world: The world snapshot to search + :param selected_robot_id: The ID of the robot to find + """ + if cached_world is None: + return None + return next( + ( + robot + for robot in cached_world.friendly_team.team_robots + if robot.id == selected_robot_id + ), + None, + ) + def select_closest_robot(self, point): """Find the closest robot to a point :param point: the reference point to find the closest robot """ - if not self.cached_team: + team = tbots_cpp.Team(self.cached_world.friendly_team) + if not team: logger.warning("No vision data received") return - closest_robot = self.cached_team.getNearestRobot( - tbots_cpp.Point(point.x(), point.y()) - ) + closest_robot = team.getNearestRobot(tbots_cpp.Point(point.x(), point.y())) if closest_robot is None: logger.warning( "No robots found. Are you sure there is friendly robot on field?" @@ -52,11 +94,16 @@ def select_closest_robot(self, point): self.selected_robot_id = closest_robot.id() self.is_selected = True + # Set the selected robot's orientation as current orientation + robot = self._get_selected_robot(self.cached_world, self.selected_robot_id) + if robot is not None: + self.current_orientation = robot.current_state.global_orientation.radians + @override def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: - """Move to the point clicked. + """Handle mouse press events. If Shift+Alt+Control is pressed, clicking selects a robot based on the closest point in scene. - If Shift+Alt is pressed, clicking moves a robot to the point in scene. + If Shift+Alt is pressed, clicking starts a drag-to-orient movement sequence. :param event: The event """ @@ -71,13 +118,19 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: # Shift+Alt+Control is pressed self.select_closest_robot(point) else: - # Shift+Alt is pressed - self.move_to_point(point) + # Shift+Alt is pressed - start drag-to-orient sequence + if not self.is_selected: + logger.warning("No robot selected to be moved") + return + + self.target_point = point + self.is_dragging_to_orient = True - def move_to_point(self, point): - """Move to a point + def move_to_point(self, point, orientation: float = None): + """Move to a point with an optional orientation :param point: the point we are commanding the robot to move to + :param orientation: the final orientation in radians """ # whether the selected_robot_index would cause index out of range issues if not self.is_selected: @@ -90,7 +143,7 @@ def move_to_point(self, point): move_tactic = MoveTactic( destination=point, dribbler_mode=DribblerMode.OFF, - final_orientation=Angle(radians=-math.pi / 2), + final_orientation=Angle(radians=orientation), ball_collision_type=BallCollisionType.AVOID, auto_chip_or_kick=AutoChipOrKick(autokick_speed_m_per_s=0.0), max_allowed_speed_mode=MaxAllowedSpeedMode.PHYSICAL_LIMIT, @@ -102,12 +155,114 @@ def move_to_point(self, point): self.fullsystem_io.send_proto(AssignedTacticPlayControlParams, assign_tactic) + @override + def mouse_in_scene_dragged(self, event: MouseInSceneEvent) -> None: + """Handle mouse drag events to update orientation. + + :param event: The event + """ + if not self.visible(): + return + + if not self.is_dragging_to_orient or self.target_point is None: + return + + # Calculate the angle from the target point to the current mouse position + dx = event.point_in_scene.x() - self.target_point.x() + dy = event.point_in_scene.y() - self.target_point.y() + + # Calculate angle using atan2 (y, x) + self.current_orientation = math.atan2(dy, dx) + + @override + def mouse_in_scene_released(self, event: MouseInSceneEvent) -> None: + """Handle mouse release events to finalize movement with the calculated orientation. + + :param event: The event + """ + if not self.visible(): + return + + if not self.is_dragging_to_orient or self.target_point is None: + return + + # Move to the target point with the calculated orientation + self.move_to_point(self.target_point, self.current_orientation) + + # Reset drag-to-orient state + self.is_dragging_to_orient = False + self.target_point = None + + @override + def mouse_in_scene_moved(self, event: MouseInSceneEvent) -> None: + """Handle mouse moved events to update robot position preview. + + :param event: The event + """ + if not self.visible() or not self.is_selected: + self.target_point = None + return + + if self.is_dragging_to_orient: + return + + if ( + event.mouse_event.modifiers() & Qt.KeyboardModifier.AltModifier + and not event.mouse_event.modifiers() & Qt.KeyboardModifier.ControlModifier + ): + # Shift+Alt is pressed - mouse point is the current target position + self.target_point = event.point_in_scene + else: + self.target_point = None + + def _draw_selection(self): + """Renders an outline around the currently selected robot.""" + robot = ( + self._get_selected_robot(self.cached_world, self.selected_robot_id) + if self.is_selected + else None + ) + if robot is None: + self.selected_robot_graphic.hide() + return + + self.selected_robot_graphic.set_position( + robot.current_state.global_position.x_meters, + robot.current_state.global_position.y_meters, + ) + self.selected_robot_graphic.set_orientation( + math.degrees(robot.current_state.global_orientation.radians) + ) + self.selected_robot_graphic.show() + + def _draw_preview(self): + """Renders a robot outline at the target position and a line indicating orientation.""" + if self.target_point is None: + self.preview_robot_graphic.hide() + self.preview_orientation_graphic.hide() + return + + x = self.target_point.x() + y = self.target_point.y() + angle = self.current_orientation + + self.preview_robot_graphic.set_position(x, y) + self.preview_robot_graphic.set_orientation(math.degrees(angle)) + self.preview_robot_graphic.show() + + end_x = x + math.cos(angle) * self.ORIENTATION_LINE_LENGTH + end_y = y + math.sin(angle) * self.ORIENTATION_LINE_LENGTH + + self.preview_orientation_graphic.set_points([[x, y], [end_x, end_y]]) + self.preview_orientation_graphic.show() + @override def refresh_graphics(self): """Updating the world cache""" world = self.world_buffer.get(block=False, return_cached=False) - if world is None: - return + if world is not None: + self.cached_world = world - self.cached_team = tbots_cpp.Team(world.friendly_team) + self._draw_selection() + self._draw_preview()