From be2f42a0c6bbabb552380b698eaa09d9cd911351 Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Sat, 16 May 2026 23:47:17 -0700 Subject: [PATCH 01/18] feat: drag-to-orient --- .../gl/layers/gl_movement_field_test_layer.py | 70 ++++++++++++++++--- 1 file changed, 62 insertions(+), 8 deletions(-) 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..4a44f0a6e2 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 @@ -31,6 +31,11 @@ def __init__( self.cached_team: tbots_cpp.Team = None self.is_selected = False + # State for drag-to-orient movement + self.is_dragging_to_orient = False + self.target_point = None + self.current_orientation = -math.pi / 2 + def select_closest_robot(self, point): """Find the closest robot to a point @@ -54,9 +59,9 @@ def select_closest_robot(self, point): @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 +76,20 @@ 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.current_orientation = -math.pi / 2 + 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 (defaults to -π/2) """ # whether the selected_robot_index would cause index out of range issues if not self.is_selected: @@ -86,11 +98,14 @@ def move_to_point(self, point): robot_id = self.selected_robot_id + if orientation is None: + orientation = -math.pi / 2 + point = Point(x_meters=point.x(), y_meters=point.y()) 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,6 +117,45 @@ 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 + self.current_orientation = -math.pi / 2 + @override def refresh_graphics(self): """Updating the world cache""" @@ -110,4 +164,4 @@ def refresh_graphics(self): if world is None: return - self.cached_team = tbots_cpp.Team(world.friendly_team) + self.cached_team = tbots_cpp.Team(world.friendly_team) \ No newline at end of file From be09d04fbdc14d18a2d83d31ea6969618fa14769 Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Sun, 17 May 2026 00:25:41 -0700 Subject: [PATCH 02/18] refactor: default orientation constant --- .../thunderscope/gl/layers/gl_movement_field_test_layer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 4a44f0a6e2..a43d2927bf 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 @@ -11,6 +11,7 @@ logger = create_logger(__name__) +DEFAULT_ORIENTATION = -math.pi / 2 class GLMovementFieldTestLayer(GLLayer): @@ -34,7 +35,7 @@ def __init__( # State for drag-to-orient movement self.is_dragging_to_orient = False self.target_point = None - self.current_orientation = -math.pi / 2 + self.current_orientation = DEFAULT_ORIENTATION def select_closest_robot(self, point): """Find the closest robot to a point @@ -82,7 +83,7 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: return self.target_point = point - self.current_orientation = -math.pi / 2 + self.current_orientation = DEFAULT_ORIENTATION self.is_dragging_to_orient = True def move_to_point(self, point, orientation: float = None): @@ -99,7 +100,7 @@ def move_to_point(self, point, orientation: float = None): robot_id = self.selected_robot_id if orientation is None: - orientation = -math.pi / 2 + orientation = DEFAULT_ORIENTATION point = Point(x_meters=point.x(), y_meters=point.y()) move_tactic = MoveTactic( From a512f388a5625ebf0f258d0c7121111bd150e893 Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Mon, 18 May 2026 19:43:43 -0700 Subject: [PATCH 03/18] feat: robot position preview on hover --- src/software/thunderscope/gl/gl_widget.py | 15 ++-- .../gl/helpers/extended_gl_view_widget.py | 2 +- .../gl/layers/gl_movement_field_test_layer.py | 75 +++++++++++++++++-- 3 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 28f64a299e..42152f2234 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -132,6 +132,13 @@ def __init__( self.set_camera_view(CameraView.LANDSCAPE_HIGH_ANGLE) self.proto_unix_io = proto_unix_io + def set_scene_mouse_tracking(self, enabled: bool) -> None: + """Enable or disable scene-space mouse movement tracking. + + :param enabled: Whether to emit mouse_in_scene_moved_signal on mouse moves + """ + self.gl_view_widget.detect_mouse_movement_in_scene = enabled + def get_sim_control_toolbar(self): """Returns the simulation control toolbar""" return self.simulation_control_toolbar @@ -308,14 +315,6 @@ 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 - if self.measure_mode_enabled: self.measure_layer = GLMeasureLayer("Measure") self.add_layer(self.measure_layer) diff --git a/src/software/thunderscope/gl/helpers/extended_gl_view_widget.py b/src/software/thunderscope/gl/helpers/extended_gl_view_widget.py index 2ba19d533e..ee48e8067a 100644 --- a/src/software/thunderscope/gl/helpers/extended_gl_view_widget.py +++ b/src/software/thunderscope/gl/helpers/extended_gl_view_widget.py @@ -61,7 +61,7 @@ def __init__(self) -> None: self.point_picked = False # This must be enabled for the mouse_moved_in_scene_signal to be emitted - self.detect_mouse_movement_in_scene = False + self.detect_mouse_movement_in_scene = True @override def mousePressEvent(self, event: QtGui.QMouseEvent) -> None: 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 a43d2927bf..0d56b320fa 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,10 @@ 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.helpers.observable_list import ObservableList from software.thunderscope.gl.layers.gl_layer import GLLayer from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from typing import override @@ -16,15 +20,21 @@ class GLMovementFieldTestLayer(GLLayer): def __init__( - self, name: str, fullsystem_io: ProtoUnixIO, buffer_size: int = 5 + self, + name: str, + fullsystem_io: ProtoUnixIO, + buffer_size: int = 5, + on_visibility_changed=None, ) -> None: """Initialize the GLMovementFieldTestLayer :param name: The displayed name of the layer :param buffer_size: The buffer size we have :param fullsystem_io: The fullsystem protounix io + :param on_visibility_changed: Optional callback invoked with the new visibility state """ super().__init__(name) + self._on_visibility_changed = on_visibility_changed self.world_buffer: ThreadSafeBuffer = ThreadSafeBuffer(buffer_size, World) self.fullsystem_io: ProtoUnixIO = fullsystem_io @@ -37,6 +47,15 @@ def __init__( self.target_point = None self.current_orientation = DEFAULT_ORIENTATION + self.preview_robot_graphics = ObservableList(self._graphics_changed) + self.preview_orientation_graphics = ObservableList(self._graphics_changed) + + @override + def setVisible(self, visible: bool) -> None: + super().setVisible(visible) + if self._on_visibility_changed: + self._on_visibility_changed(visible) + def select_closest_robot(self, point): """Find the closest robot to a point @@ -83,7 +102,6 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: return self.target_point = point - self.current_orientation = DEFAULT_ORIENTATION self.is_dragging_to_orient = True def move_to_point(self, point, orientation: float = None): @@ -155,14 +173,59 @@ def mouse_in_scene_released(self, event: MouseInSceneEvent) -> None: # Reset drag-to-orient state self.is_dragging_to_orient = False self.target_point = None - self.current_orientation = -math.pi / 2 + + @override + def mouse_in_scene_moved(self, event: MouseInSceneEvent) -> None: + """Handle mouse moved events to update 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 + ): + self.target_point = event.point_in_scene + else: + self.target_point = None + + def draw_preview(self): + if self.target_point is None: + self.preview_robot_graphics.resize(0, None) + self.preview_orientation_graphics.resize(0, None) + return + + x = self.target_point.x() + y = self.target_point.y() + angle = self.current_orientation + + self.preview_robot_graphics.resize( + 1, lambda: GLRobotOutline(outline_color=Colors.DESIRED_ROBOT_LOCATION_OUTLINE) + ) + self.preview_robot_graphics[0].set_position(x, y) + self.preview_robot_graphics[0].set_orientation(math.degrees(angle)) + + line_length = 0.3 + end_x = x + math.cos(angle) * line_length + end_y = y + math.sin(angle) * line_length + + self.preview_orientation_graphics.resize( + 1, lambda: GLLineStrip(outline_color=Colors.DESIRED_ROBOT_LOCATION_OUTLINE) + ) + self.preview_orientation_graphics[0].set_points([[x, y], [end_x, end_y]]) @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_team = tbots_cpp.Team(world.friendly_team) - self.cached_team = tbots_cpp.Team(world.friendly_team) \ No newline at end of file + self.draw_preview() \ No newline at end of file From 83cf4d141765fac1c0abe87bd5c1c6c178926713 Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Mon, 18 May 2026 22:14:38 -0700 Subject: [PATCH 04/18] feat: identifier for selected robots --- src/software/thunderscope/constants.py | 1 + .../gl/layers/gl_movement_field_test_layer.py | 38 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) 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/layers/gl_movement_field_test_layer.py b/src/software/thunderscope/gl/layers/gl_movement_field_test_layer.py index 0d56b320fa..962f138aa5 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 @@ -39,7 +39,7 @@ 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 = None self.is_selected = False # State for drag-to-orient movement @@ -47,6 +47,7 @@ def __init__( self.target_point = None self.current_orientation = DEFAULT_ORIENTATION + self.selected_robot_graphics = ObservableList(self._graphics_changed) self.preview_robot_graphics = ObservableList(self._graphics_changed) self.preview_orientation_graphics = ObservableList(self._graphics_changed) @@ -61,11 +62,13 @@ def select_closest_robot(self, 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( + closest_robot = team.getNearestRobot( tbots_cpp.Point(point.x(), point.y()) ) if closest_robot is None: @@ -195,6 +198,32 @@ def mouse_in_scene_moved(self, event: MouseInSceneEvent) -> None: else: self.target_point = None + def _draw_selection(self): + if not self.is_selected or self.cached_world is None: + self.selected_robot_graphics.resize(0, None) + return + + # Get selected robot + robot = next( + (r for r in self.cached_world.friendly_team.team_robots if r.id == self.selected_robot_id), + None, + ) + if robot is None: + self.selected_robot_graphics.resize(0, None) + self.is_selected = False + return + + self.selected_robot_graphics.resize( + 1, lambda: GLRobotOutline(outline_color=Colors.SELECTED_ROBOT_OUTLINE) + ) + self.selected_robot_graphics[0].set_position( + robot.current_state.global_position.x_meters, + robot.current_state.global_position.y_meters, + ) + self.selected_robot_graphics[0].set_orientation( + math.degrees(robot.current_state.global_orientation.radians) + ) + def draw_preview(self): if self.target_point is None: self.preview_robot_graphics.resize(0, None) @@ -226,6 +255,7 @@ def refresh_graphics(self): world = self.world_buffer.get(block=False, return_cached=False) if world is not None: - self.cached_team = tbots_cpp.Team(world.friendly_team) + self.cached_world = world + self._draw_selection() self.draw_preview() \ No newline at end of file From ac59cd09bc0f90ecd4307d472bb4288cea457ff1 Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Mon, 18 May 2026 22:30:15 -0700 Subject: [PATCH 05/18] refactor --- .../thunderscope/gl/layers/gl_movement_field_test_layer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 962f138aa5..b29f5fbfd3 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 @@ -198,7 +198,8 @@ def mouse_in_scene_moved(self, event: MouseInSceneEvent) -> None: else: self.target_point = None - def _draw_selection(self): + def draw_selection(self): + """Renders an outline around the currently selected robot.""" if not self.is_selected or self.cached_world is None: self.selected_robot_graphics.resize(0, None) return @@ -225,6 +226,7 @@ def _draw_selection(self): ) 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_graphics.resize(0, None) self.preview_orientation_graphics.resize(0, None) @@ -257,5 +259,5 @@ def refresh_graphics(self): if world is not None: self.cached_world = world - self._draw_selection() + self.draw_selection() self.draw_preview() \ No newline at end of file From f4fae8b83f1744ca2f348e54fae0cf3b91826d20 Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Wed, 20 May 2026 22:52:02 -0700 Subject: [PATCH 06/18] refactor --- .../gl/layers/gl_movement_field_test_layer.py | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) 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 b29f5fbfd3..6cbff8dbd9 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 @@ -51,6 +51,15 @@ def __init__( self.preview_robot_graphics = ObservableList(self._graphics_changed) self.preview_orientation_graphics = ObservableList(self._graphics_changed) + def _get_selected_robot(self): + """Return the proto robot matching selected_robot_id, or None.""" + if self.cached_world is None: + return None + return next( + (r for r in self.cached_world.friendly_team.team_robots if r.id == self.selected_robot_id), + None, + ) + @override def setVisible(self, visible: bool) -> None: super().setVisible(visible) @@ -80,6 +89,10 @@ def select_closest_robot(self, point): self.selected_robot_id = closest_robot.id() self.is_selected = True + robot = self._get_selected_robot() + if robot is not None: + self.current_orientation = robot.current_state.global_orientation.radians + @override def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: """Handle mouse press events. @@ -198,17 +211,13 @@ def mouse_in_scene_moved(self, event: MouseInSceneEvent) -> None: else: self.target_point = None - def draw_selection(self): + def _draw_selection(self): """Renders an outline around the currently selected robot.""" - if not self.is_selected or self.cached_world is None: + if not self.is_selected: self.selected_robot_graphics.resize(0, None) return - # Get selected robot - robot = next( - (r for r in self.cached_world.friendly_team.team_robots if r.id == self.selected_robot_id), - None, - ) + robot = self._get_selected_robot() if robot is None: self.selected_robot_graphics.resize(0, None) self.is_selected = False @@ -225,7 +234,7 @@ def draw_selection(self): math.degrees(robot.current_state.global_orientation.radians) ) - def draw_preview(self): + 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_graphics.resize(0, None) @@ -259,5 +268,5 @@ def refresh_graphics(self): if world is not None: self.cached_world = world - self.draw_selection() - self.draw_preview() \ No newline at end of file + self._draw_selection() + self._draw_preview() \ No newline at end of file From 71f66b99c38643a82b34283472bb36793395eaf6 Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Wed, 20 May 2026 23:42:13 -0700 Subject: [PATCH 07/18] cleanup --- .../gl/layers/gl_movement_field_test_layer.py | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) 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 6cbff8dbd9..3af4589ef2 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 @@ -20,22 +20,15 @@ class GLMovementFieldTestLayer(GLLayer): def __init__( - self, - name: str, - fullsystem_io: ProtoUnixIO, - buffer_size: int = 5, - on_visibility_changed=None, + self, name: str, fullsystem_io: ProtoUnixIO, buffer_size: int = 5, ) -> None: """Initialize the GLMovementFieldTestLayer :param name: The displayed name of the layer :param buffer_size: The buffer size we have :param fullsystem_io: The fullsystem protounix io - :param on_visibility_changed: Optional callback invoked with the new visibility state """ super().__init__(name) - self._on_visibility_changed = on_visibility_changed - self.world_buffer: ThreadSafeBuffer = ThreadSafeBuffer(buffer_size, World) self.fullsystem_io: ProtoUnixIO = fullsystem_io self.selected_robot_id = 0 @@ -52,7 +45,7 @@ def __init__( self.preview_orientation_graphics = ObservableList(self._graphics_changed) def _get_selected_robot(self): - """Return the proto robot matching selected_robot_id, or None.""" + """Return the proto robot matching the selected robot ID, or None.""" if self.cached_world is None: return None return next( @@ -60,19 +53,12 @@ def _get_selected_robot(self): None, ) - @override - def setVisible(self, visible: bool) -> None: - super().setVisible(visible) - if self._on_visibility_changed: - self._on_visibility_changed(visible) - def select_closest_robot(self, point): """Find the closest robot to a point :param point: the reference point to find the closest robot """ team = tbots_cpp.Team(self.cached_world.friendly_team) - if not team: logger.warning("No vision data received") return @@ -89,6 +75,7 @@ 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() if robot is not None: self.current_orientation = robot.current_state.global_orientation.radians @@ -133,9 +120,6 @@ def move_to_point(self, point, orientation: float = None): robot_id = self.selected_robot_id - if orientation is None: - orientation = DEFAULT_ORIENTATION - point = Point(x_meters=point.x(), y_meters=point.y()) move_tactic = MoveTactic( destination=point, @@ -189,10 +173,11 @@ def mouse_in_scene_released(self, event: MouseInSceneEvent) -> None: # Reset drag-to-orient state self.is_dragging_to_orient = False self.target_point = None + self.current_orientation = DEFAULT_ORIENTATION @override def mouse_in_scene_moved(self, event: MouseInSceneEvent) -> None: - """Handle mouse moved events to update preview. + """Handle mouse moved events to update robot position preview. :param event: The event """ @@ -207,6 +192,7 @@ def mouse_in_scene_moved(self, event: MouseInSceneEvent) -> None: 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 From bb2b856a302072b01c2cbacd3ced9492031497d0 Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Wed, 20 May 2026 23:47:35 -0700 Subject: [PATCH 08/18] cleanup --- src/software/thunderscope/gl/gl_widget.py | 7 ------- .../thunderscope/gl/layers/gl_movement_field_test_layer.py | 5 +++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 42152f2234..86c30bbe14 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -132,13 +132,6 @@ def __init__( self.set_camera_view(CameraView.LANDSCAPE_HIGH_ANGLE) self.proto_unix_io = proto_unix_io - def set_scene_mouse_tracking(self, enabled: bool) -> None: - """Enable or disable scene-space mouse movement tracking. - - :param enabled: Whether to emit mouse_in_scene_moved_signal on mouse moves - """ - self.gl_view_widget.detect_mouse_movement_in_scene = enabled - def get_sim_control_toolbar(self): """Returns the simulation control toolbar""" return self.simulation_control_toolbar 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 3af4589ef2..c2bb55e3ad 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 @@ -20,7 +20,7 @@ class GLMovementFieldTestLayer(GLLayer): def __init__( - self, name: str, fullsystem_io: ProtoUnixIO, buffer_size: int = 5, + self, name: str, fullsystem_io: ProtoUnixIO, buffer_size: int = 5 ) -> None: """Initialize the GLMovementFieldTestLayer @@ -29,6 +29,7 @@ def __init__( :param fullsystem_io: The fullsystem protounix io """ super().__init__(name) + self.world_buffer: ThreadSafeBuffer = ThreadSafeBuffer(buffer_size, World) self.fullsystem_io: ProtoUnixIO = fullsystem_io self.selected_robot_id = 0 @@ -111,7 +112,7 @@ 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 (defaults to -π/2) + :param orientation: the final orientation in radians """ # whether the selected_robot_index would cause index out of range issues if not self.is_selected: From 951a0fd686a798e17647b636ac7b69c22daa6b24 Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Wed, 20 May 2026 23:50:25 -0700 Subject: [PATCH 09/18] fix: persist orientation --- .../thunderscope/gl/layers/gl_movement_field_test_layer.py | 1 - 1 file changed, 1 deletion(-) 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 c2bb55e3ad..aecad41996 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 @@ -174,7 +174,6 @@ def mouse_in_scene_released(self, event: MouseInSceneEvent) -> None: # Reset drag-to-orient state self.is_dragging_to_orient = False self.target_point = None - self.current_orientation = DEFAULT_ORIENTATION @override def mouse_in_scene_moved(self, event: MouseInSceneEvent) -> None: From cd7fcb7e499e9fc82cd50abe3ffc1500b3d2b699 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 06:59:23 +0000 Subject: [PATCH 10/18] [pre-commit.ci lite] apply automatic fixes --- .../gl/layers/gl_movement_field_test_layer.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) 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 aecad41996..e9d5bfd4d8 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 @@ -50,7 +50,11 @@ def _get_selected_robot(self): if self.cached_world is None: return None return next( - (r for r in self.cached_world.friendly_team.team_robots if r.id == self.selected_robot_id), + ( + r + for r in self.cached_world.friendly_team.team_robots + if r.id == self.selected_robot_id + ), None, ) @@ -64,9 +68,7 @@ def select_closest_robot(self, point): logger.warning("No vision data received") return - closest_robot = 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?" @@ -232,7 +234,8 @@ def _draw_preview(self): angle = self.current_orientation self.preview_robot_graphics.resize( - 1, lambda: GLRobotOutline(outline_color=Colors.DESIRED_ROBOT_LOCATION_OUTLINE) + 1, + lambda: GLRobotOutline(outline_color=Colors.DESIRED_ROBOT_LOCATION_OUTLINE), ) self.preview_robot_graphics[0].set_position(x, y) self.preview_robot_graphics[0].set_orientation(math.degrees(angle)) @@ -255,4 +258,4 @@ def refresh_graphics(self): self.cached_world = world self._draw_selection() - self._draw_preview() \ No newline at end of file + self._draw_preview() From ced1c3b3dfa82ac22d0892fcb9a7ee2a5a130d1d Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Thu, 21 May 2026 00:03:38 -0700 Subject: [PATCH 11/18] cleanup --- .../thunderscope/gl/layers/gl_movement_field_test_layer.py | 1 - 1 file changed, 1 deletion(-) 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 aecad41996..9dbd8edf3b 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 @@ -206,7 +206,6 @@ def _draw_selection(self): robot = self._get_selected_robot() if robot is None: self.selected_robot_graphics.resize(0, None) - self.is_selected = False return self.selected_robot_graphics.resize( From 377be27d54d07ccda67faa06ee06ff26d89841c2 Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Sat, 30 May 2026 10:39:48 -0700 Subject: [PATCH 12/18] refactor: address comments --- .../gl/layers/gl_movement_field_test_layer.py | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) 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 400bb35e9b..725dabc68c 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 @@ -8,17 +8,18 @@ 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.helpers.observable_list import ObservableList from software.thunderscope.gl.layers.gl_layer import GLLayer from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from typing import override logger = create_logger(__name__) -DEFAULT_ORIENTATION = -math.pi / 2 class GLMovementFieldTestLayer(GLLayer): + DEFAULT_ORIENTATION = -math.pi / 2 + LINE_LENGTH = 0.3 + def __init__( self, name: str, fullsystem_io: ProtoUnixIO, buffer_size: int = 5 ) -> None: @@ -33,27 +34,40 @@ def __init__( self.world_buffer: ThreadSafeBuffer = ThreadSafeBuffer(buffer_size, World) self.fullsystem_io: ProtoUnixIO = fullsystem_io self.selected_robot_id = 0 - self.cached_world = 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 = DEFAULT_ORIENTATION + 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() - self.selected_robot_graphics = ObservableList(self._graphics_changed) - self.preview_robot_graphics = ObservableList(self._graphics_changed) - self.preview_orientation_graphics = ObservableList(self._graphics_changed) + def _get_selected_robot(self, cached_world: World | None, selected_robot_id: int): + """Return the proto robot matching the selected robot ID, or None. - def _get_selected_robot(self): - """Return the proto robot matching the selected robot ID, or None.""" - if self.cached_world is 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( ( - r - for r in self.cached_world.friendly_team.team_robots - if r.id == self.selected_robot_id + robot + for robot in cached_world.friendly_team.team_robots + if robot.id == selected_robot_id ), None, ) @@ -79,7 +93,7 @@ def select_closest_robot(self, point): self.is_selected = True # Set the selected robot's orientation as current orientation - robot = self._get_selected_robot() + 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 @@ -201,52 +215,40 @@ def mouse_in_scene_moved(self, event: MouseInSceneEvent) -> None: def _draw_selection(self): """Renders an outline around the currently selected robot.""" - if not self.is_selected: - self.selected_robot_graphics.resize(0, None) - return - - robot = self._get_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_graphics.resize(0, None) + self.selected_robot_graphic.hide() return - self.selected_robot_graphics.resize( - 1, lambda: GLRobotOutline(outline_color=Colors.SELECTED_ROBOT_OUTLINE) - ) - self.selected_robot_graphics[0].set_position( + self.selected_robot_graphic.set_position( robot.current_state.global_position.x_meters, robot.current_state.global_position.y_meters, ) - self.selected_robot_graphics[0].set_orientation( + 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_graphics.resize(0, None) - self.preview_orientation_graphics.resize(0, 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_graphics.resize( - 1, - lambda: GLRobotOutline(outline_color=Colors.DESIRED_ROBOT_LOCATION_OUTLINE), - ) - self.preview_robot_graphics[0].set_position(x, y) - self.preview_robot_graphics[0].set_orientation(math.degrees(angle)) + self.preview_robot_graphic.set_position(x, y) + self.preview_robot_graphic.set_orientation(math.degrees(angle)) + self.preview_robot_graphic.show() - line_length = 0.3 - end_x = x + math.cos(angle) * line_length - end_y = y + math.sin(angle) * line_length + end_x = x + math.cos(angle) * self.LINE_LENGTH + end_y = y + math.sin(angle) * self.LINE_LENGTH - self.preview_orientation_graphics.resize( - 1, lambda: GLLineStrip(outline_color=Colors.DESIRED_ROBOT_LOCATION_OUTLINE) - ) - self.preview_orientation_graphics[0].set_points([[x, y], [end_x, end_y]]) + self.preview_orientation_graphic.set_points([[x, y], [end_x, end_y]]) + self.preview_orientation_graphic.show() @override def refresh_graphics(self): From 2da0bff450bc2c559b06c76be34a42c428ed0b3d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 30 May 2026 17:46:04 +0000 Subject: [PATCH 13/18] [pre-commit.ci lite] apply automatic fixes --- .../thunderscope/gl/layers/gl_movement_field_test_layer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 725dabc68c..bbe3004034 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 @@ -215,7 +215,11 @@ def mouse_in_scene_moved(self, event: MouseInSceneEvent) -> 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 + 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 From 669dad2f8920f3ac91fccf3326cf4f93d2e8bfec Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Sat, 30 May 2026 12:16:46 -0700 Subject: [PATCH 14/18] only detect mouse movements when needed --- src/software/thunderscope/gl/gl_widget.py | 15 +++++++++++++++ .../gl/helpers/extended_gl_view_widget.py | 2 +- src/software/thunderscope/gl/layers/gl_layer.py | 2 ++ .../gl/layers/gl_movement_field_test_layer.py | 1 + 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 86c30bbe14..dbc9c06046 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,10 +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 + self._update_mouse_tracking() + if self.measure_mode_enabled: self.measure_layer = GLMeasureLayer("Measure") self.add_layer(self.measure_layer) diff --git a/src/software/thunderscope/gl/helpers/extended_gl_view_widget.py b/src/software/thunderscope/gl/helpers/extended_gl_view_widget.py index ee48e8067a..2ba19d533e 100644 --- a/src/software/thunderscope/gl/helpers/extended_gl_view_widget.py +++ b/src/software/thunderscope/gl/helpers/extended_gl_view_widget.py @@ -61,7 +61,7 @@ def __init__(self) -> None: self.point_picked = False # This must be enabled for the mouse_moved_in_scene_signal to be emitted - self.detect_mouse_movement_in_scene = True + self.detect_mouse_movement_in_scene = False @override def mousePressEvent(self, event: QtGui.QMouseEvent) -> None: diff --git a/src/software/thunderscope/gl/layers/gl_layer.py b/src/software/thunderscope/gl/layers/gl_layer.py index 4d6fb40415..36af3e2b44 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 725dabc68c..851bdb1dfb 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 @@ -19,6 +19,7 @@ class GLMovementFieldTestLayer(GLLayer): DEFAULT_ORIENTATION = -math.pi / 2 LINE_LENGTH = 0.3 + NEEDS_MOUSE_MOVEMENT_TRACKING = True def __init__( self, name: str, fullsystem_io: ProtoUnixIO, buffer_size: int = 5 From f4782a067a24517808daf5529d9482fd58891aef Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 30 May 2026 19:23:13 +0000 Subject: [PATCH 15/18] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/gl/gl_widget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index dbc9c06046..3892df3a58 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -308,7 +308,8 @@ def set_camera_view(self, camera_view: CameraView) -> None: 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.""" + 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 From cd83ef91bafdfdd55f7d80989607c8765325188b Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Sat, 30 May 2026 19:31:58 -0700 Subject: [PATCH 16/18] rename flag --- src/software/thunderscope/gl/gl_widget.py | 4 ++-- src/software/thunderscope/gl/layers/gl_layer.py | 2 +- .../thunderscope/gl/layers/gl_movement_field_test_layer.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index dbc9c06046..a9ea5cb5e9 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -241,7 +241,7 @@ 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: + if layer.needs_mouse_movement_tracking: layer_checkbox.stateChanged.connect(self._update_mouse_tracking) def remove_layer(self, layer: GLLayer) -> None: @@ -310,7 +310,7 @@ 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() + layer.needs_mouse_movement_tracking and layer.visible() for layer in self.layers ) self.gl_view_widget.detect_mouse_movement_in_scene = ( diff --git a/src/software/thunderscope/gl/layers/gl_layer.py b/src/software/thunderscope/gl/layers/gl_layer.py index 36af3e2b44..b01d487008 100644 --- a/src/software/thunderscope/gl/layers/gl_layer.py +++ b/src/software/thunderscope/gl/layers/gl_layer.py @@ -17,7 +17,7 @@ class GLLayer(GLGraphicsItem): enabling us to group together related layers. """ - NEEDS_MOUSE_MOVEMENT_TRACKING: bool = False + 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 b971a9fbbd..e6898a9e70 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 @@ -17,9 +17,10 @@ class GLMovementFieldTestLayer(GLLayer): + needs_mouse_movement_tracking = True + DEFAULT_ORIENTATION = -math.pi / 2 LINE_LENGTH = 0.3 - NEEDS_MOUSE_MOVEMENT_TRACKING = True def __init__( self, name: str, fullsystem_io: ProtoUnixIO, buffer_size: int = 5 From c02de0b7fd8c6752c54318751506213ca1bed60f Mon Sep 17 00:00:00 2001 From: Alice Peng Date: Sat, 30 May 2026 19:42:15 -0700 Subject: [PATCH 17/18] rename constant --- .../thunderscope/gl/layers/gl_movement_field_test_layer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 e6898a9e70..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 @@ -20,7 +20,7 @@ class GLMovementFieldTestLayer(GLLayer): needs_mouse_movement_tracking = True DEFAULT_ORIENTATION = -math.pi / 2 - LINE_LENGTH = 0.3 + ORIENTATION_LINE_LENGTH = 0.3 def __init__( self, name: str, fullsystem_io: ProtoUnixIO, buffer_size: int = 5 @@ -250,8 +250,8 @@ def _draw_preview(self): self.preview_robot_graphic.set_orientation(math.degrees(angle)) self.preview_robot_graphic.show() - end_x = x + math.cos(angle) * self.LINE_LENGTH - end_y = y + math.sin(angle) * self.LINE_LENGTH + 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() From 2f9ee5526779182210d71ceee7c169bf3aa20980 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 31 May 2026 02:48:37 +0000 Subject: [PATCH 18/18] [pre-commit.ci lite] apply automatic fixes --- .../requirements_lock.txt | 134 ------------------ 1 file changed, 134 deletions(-) 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