From 6f3e6353e2e6ca9237d6577b8b394623e8aa3d45 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Mon, 11 Nov 2024 17:53:43 -0800 Subject: [PATCH 01/48] added feature to draw virtual obstacles --- src/proto/visualization.proto | 5 + .../ai/hl/stp/tactic/move_primitive.cpp | 19 ++- src/software/backend/backend.cpp | 7 + src/software/backend/backend.h | 2 + .../backend/unix_simulator_backend.cpp | 15 +- src/software/backend/unix_simulator_backend.h | 2 + src/software/constants.h | 1 + src/software/py_constants.cpp | 19 ++- src/software/sensor_fusion/sensor_fusion.cpp | 6 + src/software/sensor_fusion/sensor_fusion.h | 8 + .../sensor_fusion/threaded_sensor_fusion.cpp | 5 + .../sensor_fusion/threaded_sensor_fusion.h | 5 +- src/software/thunderscope/BUILD | 1 + .../binary_context_managers/full_system.py | 2 + src/software/thunderscope/gl/layers/BUILD | 10 ++ .../gl/layers/gl_draw_polygon_obstacle.py | 147 ++++++++++++++++++ .../thunderscope/thunderscope_config.py | 4 + .../thunderscope/widget_setup_functions.py | 12 ++ src/software/unix_full_system_main.cpp | 13 +- src/software/world/world.cpp | 17 +- src/software/world/world.h | 7 + 21 files changed, 284 insertions(+), 23 deletions(-) create mode 100644 src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py diff --git a/src/proto/visualization.proto b/src/proto/visualization.proto index 2dc1165ac2..ea5c3d6419 100644 --- a/src/proto/visualization.proto +++ b/src/proto/visualization.proto @@ -63,6 +63,11 @@ message ObstacleList repeated Obstacle obstacles = 1; } +message VirtualObstacles +{ + repeated Obstacle obstacles = 1; +} + message Shape { oneof shape diff --git a/src/software/ai/hl/stp/tactic/move_primitive.cpp b/src/software/ai/hl/stp/tactic/move_primitive.cpp index 3d2eabfd7a..0bf2b467a3 100644 --- a/src/software/ai/hl/stp/tactic/move_primitive.cpp +++ b/src/software/ai/hl/stp/tactic/move_primitive.cpp @@ -1,5 +1,6 @@ #include "software/ai/hl/stp/tactic/move_primitive.h" +#include "proto/message_translation/tbots_geometry.h" #include "proto/message_translation/tbots_protobuf.h" #include "proto/primitive/primitive_msg_factory.h" #include "software/ai/navigator/trajectory/bang_bang_trajectory_1d_angular.h" @@ -185,7 +186,23 @@ void MovePrimitive::updateObstacles( field_obstacles = obstacle_factory.createObstaclesFromMotionConstraints(motion_constraints, world); - obstacles = field_obstacles; + // adding virtual obstalces + auto virtual_obstacles = world.getVirtualObstacles().obstacles(); + for (TbotsProto::Obstacle &obstacle : virtual_obstacles) + { + if (!obstacle.has_polygon()) + { + LOG(WARNING) + << "Virtual Obstacles contain obstalce that is not a polygon. Shape igonred"; + + continue; + } + + Polygon obstacle_polygon = createPolygon(obstacle.polygon()); + ObstaclePtr current_obstacle = obstacle_factory.createFromShape(obstacle_polygon); + obstacles.push_back(current_obstacle); + } + for (const Robot &enemy : world.enemyTeam().getAllRobots()) { diff --git a/src/software/backend/backend.cpp b/src/software/backend/backend.cpp index f79c5816ba..e3ceb9a8b7 100644 --- a/src/software/backend/backend.cpp +++ b/src/software/backend/backend.cpp @@ -2,6 +2,8 @@ #include "proto/message_translation/tbots_protobuf.h" #include "proto/sensor_msg.pb.h" +#include "software/multithreading/subject.hpp" + void Backend::receiveRobotStatus(TbotsProto::RobotStatus msg) { @@ -31,3 +33,8 @@ void Backend::receiveSensorProto(SensorProto sensor_msg) { Subject::sendValueToObservers(sensor_msg); } + +void Backend::receiveObstacleList(TbotsProto::VirtualObstacles new_obstacle_list) +{ + Subject::sendValueToObservers(new_obstacle_list); +} diff --git a/src/software/backend/backend.h b/src/software/backend/backend.h index 8cc2a6c0aa..f9a43998af 100644 --- a/src/software/backend/backend.h +++ b/src/software/backend/backend.h @@ -15,6 +15,7 @@ * "Subject". Please see the implementation of those classes for details. */ class Backend : public Subject, + public Subject, public FirstInFirstOutThreadedObserver, public FirstInFirstOutThreadedObserver { @@ -33,4 +34,5 @@ class Backend : public Subject, void receiveSSLWrapperPacket(SSLProto::SSL_WrapperPacket msg); void receiveSSLReferee(SSLProto::Referee msg); void receiveSensorProto(SensorProto sensor_msg); + void receiveObstacleList(TbotsProto::VirtualObstacles new_obstacle_list); }; diff --git a/src/software/backend/unix_simulator_backend.cpp b/src/software/backend/unix_simulator_backend.cpp index 977350dc26..966bb58dca 100644 --- a/src/software/backend/unix_simulator_backend.cpp +++ b/src/software/backend/unix_simulator_backend.cpp @@ -1,6 +1,7 @@ #include "software/backend/unix_simulator_backend.h" #include "proto/message_translation/ssl_wrapper.h" +#include "proto/message_translation/tbots_geometry.h" #include "proto/message_translation/tbots_protobuf.h" #include "proto/parameters.pb.h" #include "proto/robot_log_msg.pb.h" @@ -8,6 +9,7 @@ #include "shared/constants.h" #include "software/constants.h" #include "software/logger/logger.h" +#include "software/multithreading/subject.hpp" #include "software/util/generic_factory/generic_factory.h" UnixSimulatorBackend::UnixSimulatorBackend( @@ -20,9 +22,8 @@ UnixSimulatorBackend::UnixSimulatorBackend( [&](TbotsProto::RobotStatus& msg) { receiveRobotStatus(msg); }, proto_logger)); ssl_wrapper_input.reset(new ThreadedProtoUnixListener( - runtime_dir + SSL_WRAPPER_PATH, - [&](SSLProto::SSL_WrapperPacket& msg) { receiveSSLWrapperPacket(msg); }, - proto_logger)); + runtime_dir + SSL_WRAPPER_PATH, [&](SSLProto::SSL_WrapperPacket& msg) + { receiveSSLWrapperPacket(msg); }, proto_logger)); ssl_referee_input.reset(new ThreadedProtoUnixListener( runtime_dir + SSL_REFEREE_PATH, @@ -38,6 +39,14 @@ UnixSimulatorBackend::UnixSimulatorBackend( [&](TbotsProto::ThunderbotsConfig& msg) { receiveThunderbotsConfig(msg); }, proto_logger)); + // external obstacles for bang bang trajectory planner + std::cout << runtime_dir + VIRTUAL_OBSTACLES_UNIX_PATH << std::endl; + external_obstacles_list_.reset( + new ThreadedProtoUnixListener( + runtime_dir + VIRTUAL_OBSTACLES_UNIX_PATH, + [&](TbotsProto::VirtualObstacles& msg) { receiveObstacleList(msg); }, + proto_logger)); + // The following listeners have an empty callback since their values are // only used by proto_logger for replay purposes. validation_proto_set_listener.reset( diff --git a/src/software/backend/unix_simulator_backend.h b/src/software/backend/unix_simulator_backend.h index c9781a1cfb..3a28b913c0 100644 --- a/src/software/backend/unix_simulator_backend.h +++ b/src/software/backend/unix_simulator_backend.h @@ -37,6 +37,8 @@ class UnixSimulatorBackend : public Backend, public Subject> + external_obstacles_list_; std::unique_ptr> robot_status_input; std::unique_ptr> diff --git a/src/software/constants.h b/src/software/constants.h index 0300c836d1..4a30d11ef9 100644 --- a/src/software/constants.h +++ b/src/software/constants.h @@ -35,6 +35,7 @@ const std::string ROBOT_CRASH_PATH = "/robot_crash"; const std::string DYNAMIC_PARAMETER_UPDATE_REQUEST_PATH = "/dynamic_parameter_request"; const std::string DYNAMIC_PARAMETER_UPDATE_RESPONSE_PATH = "/dynamic_parameter_response"; const std::string WORLD_STATE_RECEIVED_TRIGGER_PATH = "/world_state_received_trigger"; +const std::string VIRTUAL_OBSTACLES_UNIX_PATH = "/virtual_obstacles"; const unsigned UNIX_BUFFER_SIZE = 20000; diff --git a/src/software/py_constants.cpp b/src/software/py_constants.cpp index 4a7d3dfa28..5ec65d92b7 100644 --- a/src/software/py_constants.cpp +++ b/src/software/py_constants.cpp @@ -8,6 +8,7 @@ PYBIND11_MODULE(py_constants, m) { m.attr("BALL_MAX_SPEED_METERS_PER_SECOND") = BALL_MAX_SPEED_METERS_PER_SECOND; m.attr("ROBOT_MAX_HEIGHT_METERS") = ROBOT_MAX_HEIGHT_METERS; + m.attr("VIRTUAL_OBSTACLES_UNIX_PATH") = VIRTUAL_OBSTACLES_UNIX_PATH; m.attr("ROBOT_MAX_RADIUS_METERS") = ROBOT_MAX_RADIUS_METERS; m.attr("ROBOT_MAX_HEIGHT_MILLIMETERS") = ROBOT_MAX_HEIGHT_METERS * MILLIMETERS_PER_METER; @@ -66,14 +67,16 @@ PYBIND11_MODULE(py_constants, m) m.attr("WORLD_STATE_RECEIVED_TRIGGER_PATH") = WORLD_STATE_RECEIVED_TRIGGER_PATH; // Multicast Channels - m.def("getRobotMulticastChannel", [](py::args& args) { - if (args.size() != 1) - { - throw std::runtime_error("must provide channel number only"); - } - - return ROBOT_MULTICAST_CHANNELS.at(args[0].cast()); - }); + m.def("getRobotMulticastChannel", + [](py::args& args) + { + if (args.size() != 1) + { + throw std::runtime_error("must provide channel number only"); + } + + return ROBOT_MULTICAST_CHANNELS.at(args[0].cast()); + }); // Ports m.attr("PRIMITIVE_PORT") = PRIMITIVE_PORT; diff --git a/src/software/sensor_fusion/sensor_fusion.cpp b/src/software/sensor_fusion/sensor_fusion.cpp index f7bf9cd62f..af1e412cc8 100644 --- a/src/software/sensor_fusion/sensor_fusion.cpp +++ b/src/software/sensor_fusion/sensor_fusion.cpp @@ -38,6 +38,7 @@ std::optional SensorFusion::getWorld() const new_world.updateRefereeStage(*referee_stage); } + new_world.setVirtualObstacles(virtual_obstacles_); return new_world; } else @@ -437,3 +438,8 @@ void SensorFusion::resetWorldComponents() enemy_team_filter = RobotTeamFilter(); possession = TeamPossession::FRIENDLY_TEAM; } + +void SensorFusion::setVirtualObstacles(TbotsProto::VirtualObstacles &virtual_obstacles) +{ + virtual_obstacles_ = virtual_obstacles; +} diff --git a/src/software/sensor_fusion/sensor_fusion.h b/src/software/sensor_fusion/sensor_fusion.h index 773aa76cba..67659e4bb8 100644 --- a/src/software/sensor_fusion/sensor_fusion.h +++ b/src/software/sensor_fusion/sensor_fusion.h @@ -48,6 +48,12 @@ class SensorFusion */ std::optional getWorld() const; + /** + * Set the virtual obstacles in the world + * + */ + void setVirtualObstacles(TbotsProto::VirtualObstacles &virtual_obstacles); + // Number of vision packets to indicate that the vision client most likely reset, // determined experimentally with the simulator static constexpr unsigned int VISION_PACKET_RESET_COUNT_THRESHOLD = 5; @@ -182,4 +188,6 @@ class SensorFusion // The timestamp, in seconds, of the most recently received vision packet double last_t_capture; + + TbotsProto::VirtualObstacles virtual_obstacles_; }; diff --git a/src/software/sensor_fusion/threaded_sensor_fusion.cpp b/src/software/sensor_fusion/threaded_sensor_fusion.cpp index 535d862b1e..81a88dc445 100644 --- a/src/software/sensor_fusion/threaded_sensor_fusion.cpp +++ b/src/software/sensor_fusion/threaded_sensor_fusion.cpp @@ -39,3 +39,8 @@ void ThreadedSensorFusion::onValueReceived(SensorProto sensor_msg) } } } + +void ThreadedSensorFusion::onValueReceived(TbotsProto::VirtualObstacles list) +{ + sensor_fusion.setVirtualObstacles(list); +} diff --git a/src/software/sensor_fusion/threaded_sensor_fusion.h b/src/software/sensor_fusion/threaded_sensor_fusion.h index 27b501ff72..e416767262 100644 --- a/src/software/sensor_fusion/threaded_sensor_fusion.h +++ b/src/software/sensor_fusion/threaded_sensor_fusion.h @@ -10,7 +10,9 @@ class ThreadedSensorFusion : public Subject, public FirstInFirstOutThreadedObserver, - public FirstInFirstOutThreadedObserver + public FirstInFirstOutThreadedObserver, + public FirstInFirstOutThreadedObserver + { public: explicit ThreadedSensorFusion(TbotsProto::SensorFusionConfig config); @@ -19,6 +21,7 @@ class ThreadedSensorFusion private: void onValueReceived(SensorProto sensor_msg) override; void onValueReceived(TbotsProto::ThunderbotsConfig config) override; + void onValueReceived(TbotsProto::VirtualObstacles list) override; SensorFusion sensor_fusion; TbotsProto::SensorFusionConfig sensor_fusion_config; diff --git a/src/software/thunderscope/BUILD b/src/software/thunderscope/BUILD index 1af288edc5..298b87f9c4 100644 --- a/src/software/thunderscope/BUILD +++ b/src/software/thunderscope/BUILD @@ -95,6 +95,7 @@ py_library( "//software/thunderscope/gl/layers:gl_trail_layer", "//software/thunderscope/gl/layers:gl_validation_layer", "//software/thunderscope/gl/layers:gl_world_layer", + "//software/thunderscope/gl/layers:gl_draw_polygon_obstacle", "//software/thunderscope/log:g3log_checkboxes", "//software/thunderscope/log:g3log_widget", "//software/thunderscope/play:playinfo_widget", diff --git a/src/software/thunderscope/binary_context_managers/full_system.py b/src/software/thunderscope/binary_context_managers/full_system.py index 240a06354a..9a1cd1ac7b 100644 --- a/src/software/thunderscope/binary_context_managers/full_system.py +++ b/src/software/thunderscope/binary_context_managers/full_system.py @@ -6,6 +6,7 @@ import threading from subprocess import Popen, TimeoutExpired +from software.thunderscope.gl.layers.gl_obstacle_layer import ObstacleList from software.thunderscope.proto_unix_io import ProtoUnixIO from software.python_bindings import * from proto.import_all_protos import * @@ -215,5 +216,6 @@ def setup_proto_unix_io(self, proto_unix_io: ProtoUnixIO) -> None: (VALIDATION_PROTO_SET_PATH, ValidationProtoSet), (ROBOT_LOG_PATH, RobotLog), (ROBOT_CRASH_PATH, RobotCrash), + (VIRTUAL_OBSTACLES_UNIX_PATH, VirtualObstacles), ]: proto_unix_io.attach_unix_sender(self.full_system_runtime_dir, *arg) diff --git a/src/software/thunderscope/gl/layers/BUILD b/src/software/thunderscope/gl/layers/BUILD index 33d1da8093..8034474c87 100644 --- a/src/software/thunderscope/gl/layers/BUILD +++ b/src/software/thunderscope/gl/layers/BUILD @@ -156,3 +156,13 @@ py_library( requirement("pyqtgraph"), ], ) + +py_library( + name = "gl_draw_polygon_obstacle", + srcs = ["gl_draw_polygon_obstacle.py"], + deps = [ + ":gl_layer", + "//software/thunderscope/gl/graphics:gl_polygon", + requirement("pyqtgraph"), + ], +) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py new file mode 100644 index 0000000000..6169a62e87 --- /dev/null +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -0,0 +1,147 @@ +from software.thunderscope.binary_context_managers.full_system import ProtoUnixIO +from software.thunderscope.gl.graphics.gl_polygon import GLPolygon +from pyqtgraph.Qt import QtGui +from pyqtgraph.Qt.QtCore import Qt +from pyqtgraph.opengl import * + +from proto.import_all_protos import * +from software.py_constants import * +from software.thunderscope.proto_unix_io import ProtoUnixIO + +from software.thunderscope.gl.layers.gl_layer import GLLayer +from software.thunderscope.gl.helpers.extended_gl_view_widget import MouseInSceneEvent + + +class GLDrawPolygonObstacleLayer(GLLayer): + """A layer used to draw polygons that are going to represent obstacles for the trajectroy planner + to avoid. + """ + + def __init__( + self, name, blue_fs_io: ProtoUnixIO, yellow_fs_io: ProtoUnixIO + ) -> None: + """Initial this layer + + :param blue_fs_io: the blue full system Protounix io + :param yellow_fs_io: the yellow full system Protounix io + """ + super().__init__(name) + + self.blue_fu_io: ProtoUnixIO = blue_fs_io + self.yellow_fs_io: ProtoUnixIO = yellow_fs_io + + self.current_polygon: GLPolygon = GLPolygon(parent_item=self, line_width=2) + # Tuple[float, float] represents a point (x,y) + self.points: List[Tuple[float, float]] = [] + + self.obstacles: List[Obstacle] = [] + + # used for keeping track and rendering multiple polygons + self.rendering_polygons: List[GLPolygon] = [] + + def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: + """responding to key events that are going to push obstacles to the stack or add point + + :param event: a qt key event indicating the button that are pressed + """ + if event.key() == Qt.Key.Key_Q: + self.push_polygon_to_list() + + if event.key() == Qt.Key.Key_W: + self.clear_polygons() + + def clear_polygons(self): + """clearing the obstacles""" + + self.points.clear() + self.obstacles.clear() + + for polygon in self.rendering_polygons: + polygon.hide() + self.rendering_polygons.clear() + self.current_polygon.hide() + self.current_polygon = GLPolygon(parent_item=self, line_width=2) + + self._send_to_fs() + + def push_polygon_to_list(self): + """pushing the fully drawn polygon to the stack""" + + points = [ + Point(x_meters=point[0], y_meters=point[1]) for point in self.points[:-1] + ] + + if len(points) <= 2: + print("Cannot push polygon to stack as there as less that points two.") + return + + polygon = Polygon(points=points) + obstacle = Obstacle(polygon=polygon) + self.obstacles.append(obstacle) + self.points.clear() + + self.rendering_polygons.append(self.current_polygon) + self.current_polygon = GLPolygon(parent_item=self, line_width=2) + self._send_to_fs() + + def _add_one_point(self, point: tuple[float, float]): + """adding one points to a polygon + + :param point: represent the point (x,y) that is added to the polygon + """ + # trying to create a line + if len(self.points) < 2: + self.points.append(point) + self.current_polygon.set_points(self.points) + return + + # creating a triangle + if len(self.points) == 2: + start_point = self.points[0] + + self.points.append(point) + self.points.append(start_point) + self.current_polygon.set_points(self.points) + + # creating a general polygon + start_point = self.points[0] + self.points.pop() # removing the start point since the last point is always the start point + + self.points.append(point) + self.points.append(start_point) + self.current_polygon.set_points(self.points) + self._send_to_fs() + + def _send_to_fs(self): + """sending a list of virtual obstacles to full system""" + obstacles = self.obstacles.copy() + + points = [ + Point(x_meters=point[0], y_meters=point[1]) for point in self.points[:-1] + ] + + # only send to full system when the points form a valid polygon + if len(points) >= 3: + polygon = Polygon(points=points) + obstacle = Obstacle(polygon=polygon) + obstacles.append(obstacle) + + self.blue_fu_io.send_proto( + VirtualObstacles, ObstacleListTwo(obstacles=obstacles) + ) + self.yellow_fs_io.send_proto( + VirtualObstacles, ObstacleListTwo(obstacles=obstacles) + ) + + def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: + """adding the point in scene + + :param event: the mouse event + """ + point = event.point_in_scene + self._add_one_point((point.x(), point.y())) + + return super().mouse_in_scene_pressed(event) + + def refresh_graphics(self) -> None: + return super().refresh_graphics() diff --git a/src/software/thunderscope/thunderscope_config.py b/src/software/thunderscope/thunderscope_config.py index f7d893b5ef..d87fedfa15 100644 --- a/src/software/thunderscope/thunderscope_config.py +++ b/src/software/thunderscope/thunderscope_config.py @@ -114,6 +114,7 @@ def configure_estop(proto_unix_io): def configure_base_fullsystem( full_system_proto_unix_io: ProtoUnixIO, + enemy_proto_unix_io: ProtoUnixIO, sim_proto_unix_io: ProtoUnixIO, friendly_colour_yellow: bool, sandbox_mode: bool = False, @@ -153,6 +154,7 @@ def configure_base_fullsystem( "sim_proto_unix_io": sim_proto_unix_io, "friendly_colour_yellow": friendly_colour_yellow, "visualization_buffer_size": visualization_buffer_size, + "enemy_team_io": enemy_proto_unix_io, "frame_swap_counter": frame_swap_counter, } ), @@ -311,6 +313,7 @@ def configure_two_ai_gamecontroller_view( name="Blue FullSystem", widgets=configure_base_fullsystem( full_system_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.BLUE], + enemy_proto_unix_io = proto_unix_io_map[ProtoUnixIOTypes.YELLOW], sim_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.SIM], friendly_colour_yellow=False, visualization_buffer_size=visualization_buffer_size, @@ -327,6 +330,7 @@ def configure_two_ai_gamecontroller_view( full_system_proto_unix_io=proto_unix_io_map[ ProtoUnixIOTypes.YELLOW ], + enemy_proto_unix_io = proto_unix_io_map[ProtoUnixIOTypes.BLUE], sim_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.SIM], friendly_colour_yellow=True, visualization_buffer_size=visualization_buffer_size, diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index d7d77055c9..45614fb984 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -7,6 +7,9 @@ from software.thunderscope.common.fps_widget import FPSWidget from software.thunderscope.common.frametime_counter import FrameTimeCounter from software.thunderscope.common.proto_plotter import ProtoPlotter +from software.thunderscope.gl.layers.gl_draw_polygon_obstacle import ( + GLDrawPolygonObstacleLayer, +) from software.thunderscope.proto_unix_io import ProtoUnixIO from proto.robot_log_msg_pb2 import RobotLog from extlibs.er_force_sim.src.protobuf.world_pb2 import * @@ -57,6 +60,7 @@ def setup_gl_widget( sim_proto_unix_io: ProtoUnixIO, full_system_proto_unix_io: ProtoUnixIO, + enemy_team_io: ProtoUnixIO, friendly_colour_yellow: bool, visualization_buffer_size: int, sandbox_mode: bool = False, @@ -94,6 +98,7 @@ def setup_gl_widget( "Validation", visualization_buffer_size ) path_layer = gl_path_layer.GLPathLayer("Paths", visualization_buffer_size) + obstacle_layer = gl_obstacle_layer.GLObstacleLayer( "Obstacles", visualization_buffer_size ) @@ -130,6 +135,13 @@ def setup_gl_widget( tactic_layer = gl_tactic_layer.GLTacticLayer("Tactics", visualization_buffer_size) trail_layer = gl_trail_layer.GLTrailLayer("Trail", visualization_buffer_size) + draw_obstacle_layer = GLDrawPolygonObstacleLayer( + "Draw Obstalce Layer", + full_system_proto_unix_io if not friendly_colour_yellow else enemy_team_io, # blue + full_system_proto_unix_io if friendly_colour_yellow else enemy_team_io, # yellow + ) + + gl_widget.add_layer(draw_obstacle_layer, True) gl_widget.add_layer(world_layer) gl_widget.add_layer(simulator_layer, False) gl_widget.add_layer(path_layer) diff --git a/src/software/unix_full_system_main.cpp b/src/software/unix_full_system_main.cpp index 6c59bde203..b005518d0b 100644 --- a/src/software/unix_full_system_main.cpp +++ b/src/software/unix_full_system_main.cpp @@ -95,7 +95,8 @@ int main(int argc, char** argv) if (!args.ci) { // Return the current time since epoch in seconds - time_provider = []() { + time_provider = []() + { return std::chrono::duration( std::chrono::system_clock::now().time_since_epoch()) .count(); @@ -121,8 +122,8 @@ int main(int argc, char** argv) if (args.ci) { // Update the time provider for ProtoLogger - proto_logger->updateTimeProvider( - [&backend]() { return backend->getLastWorldTimeSec(); }); + proto_logger->updateTimeProvider([&backend]() + { return backend->getLastWorldTimeSec(); }); } auto sensor_fusion = @@ -133,9 +134,8 @@ int main(int argc, char** argv) auto tactic_override_listener = ThreadedProtoUnixListener( args.runtime_dir + TACTIC_OVERRIDE_PATH, - [&ai](TbotsProto::AssignedTacticPlayControlParams input) { - ai->overrideTactics(input); - }); + [&ai](TbotsProto::AssignedTacticPlayControlParams input) + { ai->overrideTactics(input); }); auto play_override_listener = ThreadedProtoUnixListener( args.runtime_dir + PLAY_OVERRIDE_PATH, @@ -148,6 +148,7 @@ int main(int argc, char** argv) backend->Subject::registerObserver(sensor_fusion); backend->Subject::registerObserver(ai); backend->Subject::registerObserver(sensor_fusion); + backend->Subject::registerObserver(sensor_fusion); // Handle some of the signals that we manually send when we want to shut down full // system cleanly. SIGTERM is sent by Thunderscope to stop full system diff --git a/src/software/world/world.cpp b/src/software/world/world.cpp index da75f31325..0af8d7f99b 100644 --- a/src/software/world/world.cpp +++ b/src/software/world/world.cpp @@ -14,7 +14,8 @@ World::World(const Field &field, const Ball &ball, const Team &friendly_team, // Store a small buffer of previous referee commands so we can filter out noise referee_command_history_(REFEREE_COMMAND_BUFFER_SIZE), referee_stage_history_(REFEREE_COMMAND_BUFFER_SIZE), - team_with_possession_(TeamPossession::FRIENDLY_TEAM) + team_with_possession_(TeamPossession::FRIENDLY_TEAM), + virtual_obstacles_() { updateTimestamp(getMostRecentTimestampFromMembers()); } @@ -83,9 +84,8 @@ void World::updateRefereeCommand(const RefereeCommand &command) // Take the consensus of the previous referee messages if (!referee_command_history_.empty() && std::all_of(referee_command_history_.begin(), referee_command_history_.end(), - [&](auto game_state) { - return game_state == referee_command_history_.front(); - })) + [&](auto game_state) + { return game_state == referee_command_history_.front(); })) { current_game_state_.updateRefereeCommand(command); } @@ -173,3 +173,12 @@ TeamPossession World::getTeamWithPossession() const { return team_with_possession_; } + +void World::setVirtualObstacles(const TbotsProto::VirtualObstacles &virtual_obstacles) +{ + virtual_obstacles_ = virtual_obstacles; +} +TbotsProto::VirtualObstacles World::getVirtualObstacles() const +{ + return virtual_obstacles_; +} diff --git a/src/software/world/world.h b/src/software/world/world.h index 89cebc2747..9b95311325 100644 --- a/src/software/world/world.h +++ b/src/software/world/world.h @@ -3,6 +3,7 @@ #include +#include "proto/visualization.pb.h" #include "software/world/ball.h" #include "software/world/field.h" #include "software/world/game_state.h" @@ -196,6 +197,9 @@ class World final // The size of the referee history buffers to filter out noise with static constexpr unsigned int REFEREE_COMMAND_BUFFER_SIZE = 3; + void setVirtualObstacles(const TbotsProto::VirtualObstacles& virtual_obstacles); + TbotsProto::VirtualObstacles getVirtualObstacles() const; + private: /** * Searches all member objects of world for the most recent Timestamp value @@ -216,6 +220,9 @@ class World final boost::circular_buffer referee_stage_history_; // which team has possession of the ball TeamPossession team_with_possession_; + + // Virtual Obstacles for the Trajectory Planner + TbotsProto::VirtualObstacles virtual_obstacles_; }; using WorldPtr = std::shared_ptr; From 7e33f08fe1967835e65b72e0da5e3757b2443660 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:01:58 -0800 Subject: [PATCH 02/48] fixed a refactor --- .../thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 6169a62e87..461a247480 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -127,10 +127,10 @@ def _send_to_fs(self): obstacles.append(obstacle) self.blue_fu_io.send_proto( - VirtualObstacles, ObstacleListTwo(obstacles=obstacles) + VirtualObstacles, VirtualObstacles(obstacles=obstacles) ) self.yellow_fs_io.send_proto( - VirtualObstacles, ObstacleListTwo(obstacles=obstacles) + VirtualObstacles, VirtualObstacles(obstacles=obstacles) ) def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: From 079670b5f7d347e0008c5d3d5433578a0f9e1366 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:10:11 -0800 Subject: [PATCH 03/48] removed debugging comments --- src/software/backend/unix_simulator_backend.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/software/backend/unix_simulator_backend.cpp b/src/software/backend/unix_simulator_backend.cpp index 966bb58dca..18255b4123 100644 --- a/src/software/backend/unix_simulator_backend.cpp +++ b/src/software/backend/unix_simulator_backend.cpp @@ -40,7 +40,6 @@ UnixSimulatorBackend::UnixSimulatorBackend( proto_logger)); // external obstacles for bang bang trajectory planner - std::cout << runtime_dir + VIRTUAL_OBSTACLES_UNIX_PATH << std::endl; external_obstacles_list_.reset( new ThreadedProtoUnixListener( runtime_dir + VIRTUAL_OBSTACLES_UNIX_PATH, From ec869bcbc676af3ebded956eda5aca8a43978608 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 02:21:06 +0000 Subject: [PATCH 04/48] [pre-commit.ci lite] apply automatic fixes --- .../backend/unix_simulator_backend.cpp | 5 +++-- src/software/py_constants.cpp | 18 ++++++++---------- src/software/thunderscope/BUILD | 2 +- .../gl/layers/gl_draw_polygon_obstacle.py | 14 ++++++-------- .../thunderscope/thunderscope_config.py | 4 ++-- .../thunderscope/widget_setup_functions.py | 8 ++++++-- src/software/unix_full_system_main.cpp | 12 ++++++------ src/software/world/world.cpp | 5 +++-- 8 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/software/backend/unix_simulator_backend.cpp b/src/software/backend/unix_simulator_backend.cpp index 18255b4123..ed3f379e43 100644 --- a/src/software/backend/unix_simulator_backend.cpp +++ b/src/software/backend/unix_simulator_backend.cpp @@ -22,8 +22,9 @@ UnixSimulatorBackend::UnixSimulatorBackend( [&](TbotsProto::RobotStatus& msg) { receiveRobotStatus(msg); }, proto_logger)); ssl_wrapper_input.reset(new ThreadedProtoUnixListener( - runtime_dir + SSL_WRAPPER_PATH, [&](SSLProto::SSL_WrapperPacket& msg) - { receiveSSLWrapperPacket(msg); }, proto_logger)); + runtime_dir + SSL_WRAPPER_PATH, + [&](SSLProto::SSL_WrapperPacket& msg) { receiveSSLWrapperPacket(msg); }, + proto_logger)); ssl_referee_input.reset(new ThreadedProtoUnixListener( runtime_dir + SSL_REFEREE_PATH, diff --git a/src/software/py_constants.cpp b/src/software/py_constants.cpp index 5ec65d92b7..a0160fa68a 100644 --- a/src/software/py_constants.cpp +++ b/src/software/py_constants.cpp @@ -67,16 +67,14 @@ PYBIND11_MODULE(py_constants, m) m.attr("WORLD_STATE_RECEIVED_TRIGGER_PATH") = WORLD_STATE_RECEIVED_TRIGGER_PATH; // Multicast Channels - m.def("getRobotMulticastChannel", - [](py::args& args) - { - if (args.size() != 1) - { - throw std::runtime_error("must provide channel number only"); - } - - return ROBOT_MULTICAST_CHANNELS.at(args[0].cast()); - }); + m.def("getRobotMulticastChannel", [](py::args& args) { + if (args.size() != 1) + { + throw std::runtime_error("must provide channel number only"); + } + + return ROBOT_MULTICAST_CHANNELS.at(args[0].cast()); + }); // Ports m.attr("PRIMITIVE_PORT") = PRIMITIVE_PORT; diff --git a/src/software/thunderscope/BUILD b/src/software/thunderscope/BUILD index 298b87f9c4..f0ce6c795c 100644 --- a/src/software/thunderscope/BUILD +++ b/src/software/thunderscope/BUILD @@ -86,6 +86,7 @@ py_library( "//software/thunderscope/gl/layers:gl_attacker_layer", "//software/thunderscope/gl/layers:gl_cost_vis_layer", "//software/thunderscope/gl/layers:gl_debug_shapes_layer", + "//software/thunderscope/gl/layers:gl_draw_polygon_obstacle", "//software/thunderscope/gl/layers:gl_obstacle_layer", "//software/thunderscope/gl/layers:gl_passing_layer", "//software/thunderscope/gl/layers:gl_path_layer", @@ -95,7 +96,6 @@ py_library( "//software/thunderscope/gl/layers:gl_trail_layer", "//software/thunderscope/gl/layers:gl_validation_layer", "//software/thunderscope/gl/layers:gl_world_layer", - "//software/thunderscope/gl/layers:gl_draw_polygon_obstacle", "//software/thunderscope/log:g3log_checkboxes", "//software/thunderscope/log:g3log_widget", "//software/thunderscope/play:playinfo_widget", diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 461a247480..b9384b48fc 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -40,7 +40,7 @@ def __init__( self.rendering_polygons: List[GLPolygon] = [] def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: - """responding to key events that are going to push obstacles to the stack or add point + """Responding to key events that are going to push obstacles to the stack or add point :param event: a qt key event indicating the button that are pressed """ @@ -51,8 +51,7 @@ def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: self.clear_polygons() def clear_polygons(self): - """clearing the obstacles""" - + """Clearing the obstacles""" self.points.clear() self.obstacles.clear() @@ -65,8 +64,7 @@ def clear_polygons(self): self._send_to_fs() def push_polygon_to_list(self): - """pushing the fully drawn polygon to the stack""" - + """Pushing the fully drawn polygon to the stack""" points = [ Point(x_meters=point[0], y_meters=point[1]) for point in self.points[:-1] ] @@ -85,7 +83,7 @@ def push_polygon_to_list(self): self._send_to_fs() def _add_one_point(self, point: tuple[float, float]): - """adding one points to a polygon + """Adding one points to a polygon :param point: represent the point (x,y) that is added to the polygon """ @@ -113,7 +111,7 @@ def _add_one_point(self, point: tuple[float, float]): self._send_to_fs() def _send_to_fs(self): - """sending a list of virtual obstacles to full system""" + """Sending a list of virtual obstacles to full system""" obstacles = self.obstacles.copy() points = [ @@ -134,7 +132,7 @@ def _send_to_fs(self): ) def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: - """adding the point in scene + """Adding the point in scene :param event: the mouse event """ diff --git a/src/software/thunderscope/thunderscope_config.py b/src/software/thunderscope/thunderscope_config.py index d87fedfa15..87051099a8 100644 --- a/src/software/thunderscope/thunderscope_config.py +++ b/src/software/thunderscope/thunderscope_config.py @@ -313,7 +313,7 @@ def configure_two_ai_gamecontroller_view( name="Blue FullSystem", widgets=configure_base_fullsystem( full_system_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.BLUE], - enemy_proto_unix_io = proto_unix_io_map[ProtoUnixIOTypes.YELLOW], + enemy_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.YELLOW], sim_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.SIM], friendly_colour_yellow=False, visualization_buffer_size=visualization_buffer_size, @@ -330,7 +330,7 @@ def configure_two_ai_gamecontroller_view( full_system_proto_unix_io=proto_unix_io_map[ ProtoUnixIOTypes.YELLOW ], - enemy_proto_unix_io = proto_unix_io_map[ProtoUnixIOTypes.BLUE], + enemy_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.BLUE], sim_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.SIM], friendly_colour_yellow=True, visualization_buffer_size=visualization_buffer_size, diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 45614fb984..07cc278e65 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -137,8 +137,12 @@ def setup_gl_widget( draw_obstacle_layer = GLDrawPolygonObstacleLayer( "Draw Obstalce Layer", - full_system_proto_unix_io if not friendly_colour_yellow else enemy_team_io, # blue - full_system_proto_unix_io if friendly_colour_yellow else enemy_team_io, # yellow + full_system_proto_unix_io + if not friendly_colour_yellow + else enemy_team_io, # blue + full_system_proto_unix_io + if friendly_colour_yellow + else enemy_team_io, # yellow ) gl_widget.add_layer(draw_obstacle_layer, True) diff --git a/src/software/unix_full_system_main.cpp b/src/software/unix_full_system_main.cpp index b005518d0b..a8427aeb83 100644 --- a/src/software/unix_full_system_main.cpp +++ b/src/software/unix_full_system_main.cpp @@ -95,8 +95,7 @@ int main(int argc, char** argv) if (!args.ci) { // Return the current time since epoch in seconds - time_provider = []() - { + time_provider = []() { return std::chrono::duration( std::chrono::system_clock::now().time_since_epoch()) .count(); @@ -122,8 +121,8 @@ int main(int argc, char** argv) if (args.ci) { // Update the time provider for ProtoLogger - proto_logger->updateTimeProvider([&backend]() - { return backend->getLastWorldTimeSec(); }); + proto_logger->updateTimeProvider( + [&backend]() { return backend->getLastWorldTimeSec(); }); } auto sensor_fusion = @@ -134,8 +133,9 @@ int main(int argc, char** argv) auto tactic_override_listener = ThreadedProtoUnixListener( args.runtime_dir + TACTIC_OVERRIDE_PATH, - [&ai](TbotsProto::AssignedTacticPlayControlParams input) - { ai->overrideTactics(input); }); + [&ai](TbotsProto::AssignedTacticPlayControlParams input) { + ai->overrideTactics(input); + }); auto play_override_listener = ThreadedProtoUnixListener( args.runtime_dir + PLAY_OVERRIDE_PATH, diff --git a/src/software/world/world.cpp b/src/software/world/world.cpp index 0af8d7f99b..77af6bf10c 100644 --- a/src/software/world/world.cpp +++ b/src/software/world/world.cpp @@ -84,8 +84,9 @@ void World::updateRefereeCommand(const RefereeCommand &command) // Take the consensus of the previous referee messages if (!referee_command_history_.empty() && std::all_of(referee_command_history_.begin(), referee_command_history_.end(), - [&](auto game_state) - { return game_state == referee_command_history_.front(); })) + [&](auto game_state) { + return game_state == referee_command_history_.front(); + })) { current_game_state_.updateRefereeCommand(command); } From c4e365dc1aba276a468121062718984b5813dd1e Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:01:57 -0800 Subject: [PATCH 05/48] following conventions after reading on a few pr --- .../thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index b9384b48fc..0f703a9de8 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -6,6 +6,7 @@ from proto.import_all_protos import * from software.py_constants import * +from software.thunderscope.gl.helpers.observable_list import ObservableList from software.thunderscope.proto_unix_io import ProtoUnixIO from software.thunderscope.gl.layers.gl_layer import GLLayer @@ -37,7 +38,7 @@ def __init__( self.obstacles: List[Obstacle] = [] # used for keeping track and rendering multiple polygons - self.rendering_polygons: List[GLPolygon] = [] + self.rendering_polygons: ObservableList = ObservableList(self._graphics_changed) def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: """Responding to key events that are going to push obstacles to the stack or add point @@ -57,7 +58,7 @@ def clear_polygons(self): for polygon in self.rendering_polygons: polygon.hide() - self.rendering_polygons.clear() + self.rendering_polygons.resize(0, lambda: {}) self.current_polygon.hide() self.current_polygon = GLPolygon(parent_item=self, line_width=2) From 042098d92998f2db65a00a0f034f6fe57324f936 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 03:11:51 +0000 Subject: [PATCH 06/48] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 0f703a9de8..b9d15c3348 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -38,7 +38,7 @@ def __init__( self.obstacles: List[Obstacle] = [] # used for keeping track and rendering multiple polygons - self.rendering_polygons: ObservableList = ObservableList(self._graphics_changed) + self.rendering_polygons: ObservableList = ObservableList(self._graphics_changed) def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: """Responding to key events that are going to push obstacles to the stack or add point From 193e45d0ddfb69cedfe025352c83b4dd5dd16446 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Tue, 12 Nov 2024 09:56:59 -0800 Subject: [PATCH 07/48] fixed tests? --- src/software/ai/hl/stp/tactic/move_primitive.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/software/ai/hl/stp/tactic/move_primitive.cpp b/src/software/ai/hl/stp/tactic/move_primitive.cpp index 0bf2b467a3..c30e469668 100644 --- a/src/software/ai/hl/stp/tactic/move_primitive.cpp +++ b/src/software/ai/hl/stp/tactic/move_primitive.cpp @@ -185,6 +185,7 @@ void MovePrimitive::updateObstacles( // Separately store the non-robot + non-ball obstacles field_obstacles = obstacle_factory.createObstaclesFromMotionConstraints(motion_constraints, world); + obstacles = field_obstacles; // adding virtual obstalces auto virtual_obstacles = world.getVirtualObstacles().obstacles(); From bb75af24187da0f6d0f626f09f2e8b41217378e0 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:38:40 -0800 Subject: [PATCH 08/48] fixed minor bugs --- src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index b9d15c3348..eec4b19306 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -101,6 +101,8 @@ def _add_one_point(self, point: tuple[float, float]): self.points.append(point) self.points.append(start_point) self.current_polygon.set_points(self.points) + self._send_to_fs() + return # creating a general polygon start_point = self.points[0] From 9d82de23e0ad87e3e7ee92549fe0707fd0e47b57 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, 23 Nov 2024 02:48:56 +0000 Subject: [PATCH 09/48] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index eec4b19306..d962692d0c 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -102,7 +102,7 @@ def _add_one_point(self, point: tuple[float, float]): self.points.append(start_point) self.current_polygon.set_points(self.points) self._send_to_fs() - return + return # creating a general polygon start_point = self.points[0] From fd9452bb23bd43fd56a10bee926a24d0e734ac20 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 23 Nov 2024 04:32:31 -0800 Subject: [PATCH 10/48] made some changes --- src/software/backend/backend.h | 7 ++++++ src/software/sensor_fusion/sensor_fusion.cpp | 2 +- src/software/sensor_fusion/sensor_fusion.h | 3 ++- .../sensor_fusion/threaded_sensor_fusion.cpp | 5 ++-- .../sensor_fusion/threaded_sensor_fusion.h | 2 +- .../gl/layers/gl_draw_polygon_obstacle.py | 25 +++++++++---------- .../thunderscope/thunderscope_config.py | 4 --- .../thunderscope/widget_setup_functions.py | 8 +----- src/software/world/world.cpp | 5 ++-- src/software/world/world.h | 11 ++++++++ 10 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/software/backend/backend.h b/src/software/backend/backend.h index f9a43998af..46d33dd684 100644 --- a/src/software/backend/backend.h +++ b/src/software/backend/backend.h @@ -34,5 +34,12 @@ class Backend : public Subject, void receiveSSLWrapperPacket(SSLProto::SSL_WrapperPacket msg); void receiveSSLReferee(SSLProto::Referee msg); void receiveSensorProto(SensorProto sensor_msg); + + /** + * Callback function that receive a list of new virtual obstacles from + * Subject + * + * @param new_obstacles_list a new obstacles list we are having + */ void receiveObstacleList(TbotsProto::VirtualObstacles new_obstacle_list); }; diff --git a/src/software/sensor_fusion/sensor_fusion.cpp b/src/software/sensor_fusion/sensor_fusion.cpp index af1e412cc8..ac57d68d9b 100644 --- a/src/software/sensor_fusion/sensor_fusion.cpp +++ b/src/software/sensor_fusion/sensor_fusion.cpp @@ -439,7 +439,7 @@ void SensorFusion::resetWorldComponents() possession = TeamPossession::FRIENDLY_TEAM; } -void SensorFusion::setVirtualObstacles(TbotsProto::VirtualObstacles &virtual_obstacles) +void SensorFusion::setVirtualObstacles(TbotsProto::VirtualObstacles virtual_obstacles) { virtual_obstacles_ = virtual_obstacles; } diff --git a/src/software/sensor_fusion/sensor_fusion.h b/src/software/sensor_fusion/sensor_fusion.h index 67659e4bb8..4580310324 100644 --- a/src/software/sensor_fusion/sensor_fusion.h +++ b/src/software/sensor_fusion/sensor_fusion.h @@ -51,8 +51,9 @@ class SensorFusion /** * Set the virtual obstacles in the world * + * @param virtual_obstacles a list of virtual obstacles */ - void setVirtualObstacles(TbotsProto::VirtualObstacles &virtual_obstacles); + void setVirtualObstacles(TbotsProto::VirtualObstacles virtual_obstacles); // Number of vision packets to indicate that the vision client most likely reset, // determined experimentally with the simulator diff --git a/src/software/sensor_fusion/threaded_sensor_fusion.cpp b/src/software/sensor_fusion/threaded_sensor_fusion.cpp index 81a88dc445..6cf181b91c 100644 --- a/src/software/sensor_fusion/threaded_sensor_fusion.cpp +++ b/src/software/sensor_fusion/threaded_sensor_fusion.cpp @@ -40,7 +40,8 @@ void ThreadedSensorFusion::onValueReceived(SensorProto sensor_msg) } } -void ThreadedSensorFusion::onValueReceived(TbotsProto::VirtualObstacles list) +void ThreadedSensorFusion::onValueReceived( + TbotsProto::VirtualObstacles virtual_obstacle) { - sensor_fusion.setVirtualObstacles(list); + sensor_fusion.setVirtualObstacles(virtual_obstacle); } diff --git a/src/software/sensor_fusion/threaded_sensor_fusion.h b/src/software/sensor_fusion/threaded_sensor_fusion.h index e416767262..5b6f18014f 100644 --- a/src/software/sensor_fusion/threaded_sensor_fusion.h +++ b/src/software/sensor_fusion/threaded_sensor_fusion.h @@ -21,7 +21,7 @@ class ThreadedSensorFusion private: void onValueReceived(SensorProto sensor_msg) override; void onValueReceived(TbotsProto::ThunderbotsConfig config) override; - void onValueReceived(TbotsProto::VirtualObstacles list) override; + void onValueReceived(TbotsProto::VirtualObstacles virtual_obstacle) override; SensorFusion sensor_fusion; TbotsProto::SensorFusionConfig sensor_fusion_config; diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index eec4b19306..76b2318c31 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -19,17 +19,16 @@ class GLDrawPolygonObstacleLayer(GLLayer): """ def __init__( - self, name, blue_fs_io: ProtoUnixIO, yellow_fs_io: ProtoUnixIO + self, name: str, friendly_io: ProtoUnixIO ) -> None: - """Initial this layer + """Initialize this layer - :param blue_fs_io: the blue full system Protounix io - :param yellow_fs_io: the yellow full system Protounix io + :param name: the name of this layer + :param friendly_io: the friendly_io """ super().__init__(name) - self.blue_fu_io: ProtoUnixIO = blue_fs_io - self.yellow_fs_io: ProtoUnixIO = yellow_fs_io + self.friendly_io : ProtoUnixIO = friendly_io self.current_polygon: GLPolygon = GLPolygon(parent_item=self, line_width=2) # Tuple[float, float] represents a point (x,y) @@ -45,6 +44,9 @@ def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: :param event: a qt key event indicating the button that are pressed """ + if not self.visible(): + return + if event.key() == Qt.Key.Key_Q: self.push_polygon_to_list() @@ -127,10 +129,7 @@ def _send_to_fs(self): obstacle = Obstacle(polygon=polygon) obstacles.append(obstacle) - self.blue_fu_io.send_proto( - VirtualObstacles, VirtualObstacles(obstacles=obstacles) - ) - self.yellow_fs_io.send_proto( + self.friendly_io.send_proto( VirtualObstacles, VirtualObstacles(obstacles=obstacles) ) @@ -139,10 +138,10 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: :param event: the mouse event """ + if not self.visible(): + return + point = event.point_in_scene self._add_one_point((point.x(), point.y())) return super().mouse_in_scene_pressed(event) - - def refresh_graphics(self) -> None: - return super().refresh_graphics() diff --git a/src/software/thunderscope/thunderscope_config.py b/src/software/thunderscope/thunderscope_config.py index 87051099a8..f7d893b5ef 100644 --- a/src/software/thunderscope/thunderscope_config.py +++ b/src/software/thunderscope/thunderscope_config.py @@ -114,7 +114,6 @@ def configure_estop(proto_unix_io): def configure_base_fullsystem( full_system_proto_unix_io: ProtoUnixIO, - enemy_proto_unix_io: ProtoUnixIO, sim_proto_unix_io: ProtoUnixIO, friendly_colour_yellow: bool, sandbox_mode: bool = False, @@ -154,7 +153,6 @@ def configure_base_fullsystem( "sim_proto_unix_io": sim_proto_unix_io, "friendly_colour_yellow": friendly_colour_yellow, "visualization_buffer_size": visualization_buffer_size, - "enemy_team_io": enemy_proto_unix_io, "frame_swap_counter": frame_swap_counter, } ), @@ -313,7 +311,6 @@ def configure_two_ai_gamecontroller_view( name="Blue FullSystem", widgets=configure_base_fullsystem( full_system_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.BLUE], - enemy_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.YELLOW], sim_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.SIM], friendly_colour_yellow=False, visualization_buffer_size=visualization_buffer_size, @@ -330,7 +327,6 @@ def configure_two_ai_gamecontroller_view( full_system_proto_unix_io=proto_unix_io_map[ ProtoUnixIOTypes.YELLOW ], - enemy_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.BLUE], sim_proto_unix_io=proto_unix_io_map[ProtoUnixIOTypes.SIM], friendly_colour_yellow=True, visualization_buffer_size=visualization_buffer_size, diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 07cc278e65..50163e9679 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -60,7 +60,6 @@ def setup_gl_widget( sim_proto_unix_io: ProtoUnixIO, full_system_proto_unix_io: ProtoUnixIO, - enemy_team_io: ProtoUnixIO, friendly_colour_yellow: bool, visualization_buffer_size: int, sandbox_mode: bool = False, @@ -138,14 +137,9 @@ def setup_gl_widget( draw_obstacle_layer = GLDrawPolygonObstacleLayer( "Draw Obstalce Layer", full_system_proto_unix_io - if not friendly_colour_yellow - else enemy_team_io, # blue - full_system_proto_unix_io - if friendly_colour_yellow - else enemy_team_io, # yellow ) - gl_widget.add_layer(draw_obstacle_layer, True) + gl_widget.add_layer(draw_obstacle_layer, False) gl_widget.add_layer(world_layer) gl_widget.add_layer(simulator_layer, False) gl_widget.add_layer(path_layer) diff --git a/src/software/world/world.cpp b/src/software/world/world.cpp index 77af6bf10c..0af8d7f99b 100644 --- a/src/software/world/world.cpp +++ b/src/software/world/world.cpp @@ -84,9 +84,8 @@ void World::updateRefereeCommand(const RefereeCommand &command) // Take the consensus of the previous referee messages if (!referee_command_history_.empty() && std::all_of(referee_command_history_.begin(), referee_command_history_.end(), - [&](auto game_state) { - return game_state == referee_command_history_.front(); - })) + [&](auto game_state) + { return game_state == referee_command_history_.front(); })) { current_game_state_.updateRefereeCommand(command); } diff --git a/src/software/world/world.h b/src/software/world/world.h index 9b95311325..7e48de3a0f 100644 --- a/src/software/world/world.h +++ b/src/software/world/world.h @@ -197,7 +197,18 @@ class World final // The size of the referee history buffers to filter out noise with static constexpr unsigned int REFEREE_COMMAND_BUFFER_SIZE = 3; + /** + * A a list of virtual obstacles + * + * @param virtual_obstacles a list of the virtual_obstacles + */ void setVirtualObstacles(const TbotsProto::VirtualObstacles& virtual_obstacles); + + /** + * Get a list of virtual obstacles + * + * @return a list of virtual obstacles + */ TbotsProto::VirtualObstacles getVirtualObstacles() const; private: From 4f3a0c21fb0b8c1f14bf8ffd2c37d4914e1140cc 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, 23 Nov 2024 20:42:09 +0000 Subject: [PATCH 11/48] [pre-commit.ci lite] apply automatic fixes --- src/software/sensor_fusion/threaded_sensor_fusion.cpp | 3 +-- .../thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 10 ++++------ src/software/thunderscope/widget_setup_functions.py | 3 +-- src/software/world/world.cpp | 5 +++-- src/software/world/world.h | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/software/sensor_fusion/threaded_sensor_fusion.cpp b/src/software/sensor_fusion/threaded_sensor_fusion.cpp index 6cf181b91c..dc65abd04c 100644 --- a/src/software/sensor_fusion/threaded_sensor_fusion.cpp +++ b/src/software/sensor_fusion/threaded_sensor_fusion.cpp @@ -40,8 +40,7 @@ void ThreadedSensorFusion::onValueReceived(SensorProto sensor_msg) } } -void ThreadedSensorFusion::onValueReceived( - TbotsProto::VirtualObstacles virtual_obstacle) +void ThreadedSensorFusion::onValueReceived(TbotsProto::VirtualObstacles virtual_obstacle) { sensor_fusion.setVirtualObstacles(virtual_obstacle); } diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index e4b9ba45c5..da8b45a84d 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -18,17 +18,15 @@ class GLDrawPolygonObstacleLayer(GLLayer): to avoid. """ - def __init__( - self, name: str, friendly_io: ProtoUnixIO - ) -> None: + def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: """Initialize this layer - :param name: the name of this layer + :param name: the name of this layer :param friendly_io: the friendly_io """ super().__init__(name) - self.friendly_io : ProtoUnixIO = friendly_io + self.friendly_io: ProtoUnixIO = friendly_io self.current_polygon: GLPolygon = GLPolygon(parent_item=self, line_width=2) # Tuple[float, float] represents a point (x,y) @@ -45,7 +43,7 @@ def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: :param event: a qt key event indicating the button that are pressed """ if not self.visible(): - return + return if event.key() == Qt.Key.Key_Q: self.push_polygon_to_list() diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 50163e9679..08c226b1a9 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -135,8 +135,7 @@ def setup_gl_widget( trail_layer = gl_trail_layer.GLTrailLayer("Trail", visualization_buffer_size) draw_obstacle_layer = GLDrawPolygonObstacleLayer( - "Draw Obstalce Layer", - full_system_proto_unix_io + "Draw Obstalce Layer", full_system_proto_unix_io ) gl_widget.add_layer(draw_obstacle_layer, False) diff --git a/src/software/world/world.cpp b/src/software/world/world.cpp index 0af8d7f99b..77af6bf10c 100644 --- a/src/software/world/world.cpp +++ b/src/software/world/world.cpp @@ -84,8 +84,9 @@ void World::updateRefereeCommand(const RefereeCommand &command) // Take the consensus of the previous referee messages if (!referee_command_history_.empty() && std::all_of(referee_command_history_.begin(), referee_command_history_.end(), - [&](auto game_state) - { return game_state == referee_command_history_.front(); })) + [&](auto game_state) { + return game_state == referee_command_history_.front(); + })) { current_game_state_.updateRefereeCommand(command); } diff --git a/src/software/world/world.h b/src/software/world/world.h index 7e48de3a0f..f69d81aaee 100644 --- a/src/software/world/world.h +++ b/src/software/world/world.h @@ -207,7 +207,7 @@ class World final /** * Get a list of virtual obstacles * - * @return a list of virtual obstacles + * @return a list of virtual obstacles */ TbotsProto::VirtualObstacles getVirtualObstacles() const; From c1895615650c19c51b3848380a2b16e46ad40180 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 23 Nov 2024 06:34:54 -0800 Subject: [PATCH 12/48] should work --- src/software/thunderscope/constants.py | 4 ++++ .../thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 40f5e34aef..bb3278fb98 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -170,6 +170,10 @@ class EstopMode(IntEnum): Ctrl + R: Remove the current layout file and reset the layout

Layout file (on save) is located at {SAVED_LAYOUT_PATH}
+ p Push the current obstacle to the obstacle stack. GLDrawPolygonObstalce layer must be enabled.

+ c Clear the virutal obstacle stack GLDrawPolygonObstalce layer must be enabled.

+ + """ ) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index da8b45a84d..07dc108d2c 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -45,10 +45,10 @@ def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: if not self.visible(): return - if event.key() == Qt.Key.Key_Q: + if event.key() == Qt.Key.Key_P: self.push_polygon_to_list() - if event.key() == Qt.Key.Key_W: + if event.key() == Qt.Key.Key_C: self.clear_polygons() def clear_polygons(self): From a0ed76f8a0bd58ec13a66cb1cf18713404a33c65 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, 23 Nov 2024 22:44:26 +0000 Subject: [PATCH 13/48] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index bb3278fb98..3e03cac56e 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -171,7 +171,7 @@ class EstopMode(IntEnum): Layout file (on save) is located at {SAVED_LAYOUT_PATH}
p Push the current obstacle to the obstacle stack. GLDrawPolygonObstalce layer must be enabled.

- c Clear the virutal obstacle stack GLDrawPolygonObstalce layer must be enabled.

+ c Clear the virtual obstacle stack GLDrawPolygonObstalce layer must be enabled.

""" From 5ee0a072b38fb5619fc4be18a00fe8f7411c5f4d Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 21 Dec 2024 13:45:33 -0800 Subject: [PATCH 14/48] fixed typo --- src/software/ai/hl/stp/tactic/move_primitive.cpp | 2 +- src/software/sensor_fusion/threaded_sensor_fusion.cpp | 4 ++-- src/software/sensor_fusion/threaded_sensor_fusion.h | 2 +- .../thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 4 ++-- src/software/thunderscope/widget_setup_functions.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/move_primitive.cpp b/src/software/ai/hl/stp/tactic/move_primitive.cpp index c30e469668..9140896023 100644 --- a/src/software/ai/hl/stp/tactic/move_primitive.cpp +++ b/src/software/ai/hl/stp/tactic/move_primitive.cpp @@ -194,7 +194,7 @@ void MovePrimitive::updateObstacles( if (!obstacle.has_polygon()) { LOG(WARNING) - << "Virtual Obstacles contain obstalce that is not a polygon. Shape igonred"; + << "Virtual Obstacles contain obstacle that is not a polygon. Shape ignored"; continue; } diff --git a/src/software/sensor_fusion/threaded_sensor_fusion.cpp b/src/software/sensor_fusion/threaded_sensor_fusion.cpp index dc65abd04c..cc7d1b2428 100644 --- a/src/software/sensor_fusion/threaded_sensor_fusion.cpp +++ b/src/software/sensor_fusion/threaded_sensor_fusion.cpp @@ -40,7 +40,7 @@ void ThreadedSensorFusion::onValueReceived(SensorProto sensor_msg) } } -void ThreadedSensorFusion::onValueReceived(TbotsProto::VirtualObstacles virtual_obstacle) +void ThreadedSensorFusion::onValueReceived(TbotsProto::VirtualObstacles virtual_obstacles) { - sensor_fusion.setVirtualObstacles(virtual_obstacle); + sensor_fusion.setVirtualObstacles(virtual_obstacles); } diff --git a/src/software/sensor_fusion/threaded_sensor_fusion.h b/src/software/sensor_fusion/threaded_sensor_fusion.h index 5b6f18014f..bbfbf13f55 100644 --- a/src/software/sensor_fusion/threaded_sensor_fusion.h +++ b/src/software/sensor_fusion/threaded_sensor_fusion.h @@ -21,7 +21,7 @@ class ThreadedSensorFusion private: void onValueReceived(SensorProto sensor_msg) override; void onValueReceived(TbotsProto::ThunderbotsConfig config) override; - void onValueReceived(TbotsProto::VirtualObstacles virtual_obstacle) override; + void onValueReceived(TbotsProto::VirtualObstacles virtual_obstacles) override; SensorFusion sensor_fusion; TbotsProto::SensorFusionConfig sensor_fusion_config; diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 07dc108d2c..41903ee40b 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -14,7 +14,7 @@ class GLDrawPolygonObstacleLayer(GLLayer): - """A layer used to draw polygons that are going to represent obstacles for the trajectroy planner + """A layer used to draw polygons that are going to represent obstacles for the trajectory planner to avoid. """ @@ -71,7 +71,7 @@ def push_polygon_to_list(self): ] if len(points) <= 2: - print("Cannot push polygon to stack as there as less that points two.") + print("Cannot push polygon to stack as there are less than two points.") return polygon = Polygon(points=points) diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 08c226b1a9..49fca472ae 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -135,7 +135,7 @@ def setup_gl_widget( trail_layer = gl_trail_layer.GLTrailLayer("Trail", visualization_buffer_size) draw_obstacle_layer = GLDrawPolygonObstacleLayer( - "Draw Obstalce Layer", full_system_proto_unix_io + "Freehand Obstalce Layer", full_system_proto_unix_io ) gl_widget.add_layer(draw_obstacle_layer, False) From 91ee676a883f2d048e8b679b177f498615b208af Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 21 Dec 2024 14:54:58 -0800 Subject: [PATCH 15/48] made some simple changes --- src/software/thunderscope/gl/gl_widget.py | 8 ++- .../gl/widgets/gl_field_toolbar.py | 70 ++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 34c06fb0c9..ff55909516 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -14,7 +14,7 @@ from software.thunderscope.proto_unix_io import ProtoUnixIO from software.thunderscope.gl.layers.gl_layer import GLLayer from software.thunderscope.gl.layers.gl_measure_layer import GLMeasureLayer -from software.thunderscope.gl.widgets.gl_field_toolbar import GLFieldToolbar +from software.thunderscope.gl.widgets.gl_field_toolbar import GLFieldToolbar, MultiLayerToolbar from software.thunderscope.replay.proto_player import ProtoPlayer from software.thunderscope.replay.replay_controls import ReplayControls from software.thunderscope.gl.helpers.extended_gl_view_widget import * @@ -89,6 +89,8 @@ def __init__( self.layers_menu = QMenu() self.toolbars_menu = QMenu() self.layers_menu_actions = {} + + self.simulation_control_toolbar = GLFieldToolbar( parent=self.gl_view_widget, on_camera_view_change=self.set_camera_view, @@ -98,6 +100,10 @@ def __init__( sandbox_mode=sandbox_mode, ) + # + layers = [self.simulation_control_toolbar] + self.multilayer_toolbar = MultiLayerToolbar(self.gl_view_widget, layers) + # Setup gamecontroller toolbar self.gamecontroller_toolbar = GLGamecontrollerToolbar( parent=self.gl_view_widget, diff --git a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py index 7c844242fe..90932f94ff 100644 --- a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py @@ -1,4 +1,8 @@ -from typing import Callable +from typing import Callable, List +from PyQt6.QtCore import Qt +from numpy import who +from pyqtgraph.Qt import QtCore +from PyQt6.QtGui import QShortcut from pyqtgraph.Qt import QtGui from pyqtgraph.Qt.QtWidgets import * from proto.import_all_protos import * @@ -218,3 +222,67 @@ def set_speed_callback(self, callback: Callable[[float], None]) -> None: :param callback: the callback function to update the simulation speed """ self.speed_callback = callback + + +class RandomToolbar(GLToolbar): + def __init__(self, parent): + super(RandomToolbar, self).__init__(parent=parent) + + self.pushbutton = QPushButton("Change button behavior") + self.menu = QMenu() + + action_one = QtGui.QAction("click behavior one", self) + action_one.triggered.connect(self.callback) + + action_two = QtGui.QAction("clik behavior two", self) + action_two.triggered.connect(self.callback) + + self.menu.addAction(action_one) + self.menu.addAction(action_two) + self.pushbutton.setStyleSheet(self.get_button_style()) + self.pushbutton.setMenu(self.menu) + + self.layout().addWidget(self.pushbutton) + + def callback(self): + print("I just did something") + + +# TODO: move this into a different layer in the future. +""" +This would be mvoed into a different class later on! +""" +class MultiLayerToolbar(QWidget): + def __init__(self, parent, layers) -> None: + super(MultiLayerToolbar, self).__init__(parent=parent) + + self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + self.setStyleSheet("background-color: rgba(0,0,0,0);" "padding: 0px;") + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_StyledBackground) + self.layout = QHBoxLayout() + + self.layers : List[GLToolbar] = layers + self.layers.append(RandomToolbar(parent)) + + self.shortcut = QShortcut(Qt.Key.Key_F1, self) + self.shortcut.activated.connect(self.switch_layer) + + QMenu() + self.shortcut = QShortcut(Qt.Key.Key_F2, self) + self.shortcut.activated.connect(self.switch_layer_two) + + self.setLayout(self.layout) + + self.layout.addWidget(self.layers[0]) + self.layout.addWidget(self.layers[1]) + + self.layers[1].hide() + + def switch_layer(self): + self.layers[1].hide() + self.layers[0].show() + + def switch_layer_two(self): + self.layers[0].hide() + self.layers[1].show() + From 7b70bca048767bea5fa3a3fe94f66888d72109ef Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Wed, 25 Dec 2024 15:00:48 -0500 Subject: [PATCH 16/48] made some changes to the logic --- .../gl/layers/gl_draw_polygon_obstacle.py | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 41903ee40b..9f06e124b5 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -1,3 +1,4 @@ +from textwrap import wrap from software.thunderscope.binary_context_managers.full_system import ProtoUnixIO from software.thunderscope.gl.graphics.gl_polygon import GLPolygon from pyqtgraph.Qt import QtGui @@ -81,35 +82,27 @@ def push_polygon_to_list(self): self.rendering_polygons.append(self.current_polygon) self.current_polygon = GLPolygon(parent_item=self, line_width=2) - self._send_to_fs() def _add_one_point(self, point: tuple[float, float]): """Adding one points to a polygon :param point: represent the point (x,y) that is added to the polygon """ - # trying to create a line if len(self.points) < 2: + # creating a line segment self.points.append(point) - self.current_polygon.set_points(self.points) - return - - # creating a triangle - if len(self.points) == 2: + elif len(self.points) == 2: + # creating a triangle start_point = self.points[0] - self.points.append(point) self.points.append(start_point) - self.current_polygon.set_points(self.points) - self._send_to_fs() - return - - # creating a general polygon - start_point = self.points[0] - self.points.pop() # removing the start point since the last point is always the start point + else: + # creating a general polygon + start_point = self.points[0] + self.points.pop() # removing the start point since the last point is always the start point - self.points.append(point) - self.points.append(start_point) + self.points.append(point) + self.points.append(start_point) self.current_polygon.set_points(self.points) self._send_to_fs() From 7115cf03b9f234caa345b7988501cc44c0223e79 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 28 Dec 2024 11:39:41 -0500 Subject: [PATCH 17/48] remove unnecessary import --- src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 9f06e124b5..66fae921b3 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -1,4 +1,3 @@ -from textwrap import wrap from software.thunderscope.binary_context_managers.full_system import ProtoUnixIO from software.thunderscope.gl.graphics.gl_polygon import GLPolygon from pyqtgraph.Qt import QtGui From 9002178d2136b260cf64e47a1997c1dea771af91 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, 28 Dec 2024 16:49:29 +0000 Subject: [PATCH 18/48] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 66fae921b3..172bb1c418 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -95,7 +95,7 @@ def _add_one_point(self, point: tuple[float, float]): start_point = self.points[0] self.points.append(point) self.points.append(start_point) - else: + else: # creating a general polygon start_point = self.points[0] self.points.pop() # removing the start point since the last point is always the start point From 8b60160b927a7cc489f9b83ed0fd45791f17f815 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:06:50 -0500 Subject: [PATCH 19/48] kind of implemented double click --- .../gl/layers/gl_draw_polygon_obstacle.py | 22 +++++++++++++++---- .../gl/layers/gl_sandbox_world_layer.py | 2 ++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 66fae921b3..388a48982f 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -1,7 +1,7 @@ from software.thunderscope.binary_context_managers.full_system import ProtoUnixIO from software.thunderscope.gl.graphics.gl_polygon import GLPolygon from pyqtgraph.Qt import QtGui -from pyqtgraph.Qt.QtCore import Qt +from pyqtgraph.Qt.QtCore import QTimer, Qt from pyqtgraph.opengl import * from proto.import_all_protos import * @@ -26,6 +26,8 @@ def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: """ super().__init__(name) + import inspect + print(inspect.getmro(GLDrawPolygonObstacleLayer)) self.friendly_io: ProtoUnixIO = friendly_io self.current_polygon: GLPolygon = GLPolygon(parent_item=self, line_width=2) @@ -37,6 +39,9 @@ def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: # used for keeping track and rendering multiple polygons self.rendering_polygons: ObservableList = ObservableList(self._graphics_changed) + self.is_double_click: bool = True + + def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: """Responding to key events that are going to push obstacles to the stack or add point @@ -45,12 +50,12 @@ def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: if not self.visible(): return - if event.key() == Qt.Key.Key_P: - self.push_polygon_to_list() - if event.key() == Qt.Key.Key_C: self.clear_polygons() + def _reset_double_click(self): + self.is_double_click = False + def clear_polygons(self): """Clearing the obstacles""" self.points.clear() @@ -131,6 +136,15 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: if not self.visible(): return + # handle double click + if self.is_double_click: + self.push_polygon_to_list() + self.is_double_click = False + return + else: + self.is_double_click = True + QTimer.singleShot(500, self._reset_double_click) + point = event.point_in_scene self._add_one_point((point.x(), point.y())) diff --git a/src/software/thunderscope/gl/layers/gl_sandbox_world_layer.py b/src/software/thunderscope/gl/layers/gl_sandbox_world_layer.py index 4ffe6934ea..9999ec2f87 100644 --- a/src/software/thunderscope/gl/layers/gl_sandbox_world_layer.py +++ b/src/software/thunderscope/gl/layers/gl_sandbox_world_layer.py @@ -365,6 +365,7 @@ def __handle_new_robot_event(self, event: MouseInSceneEvent) -> None: self.robot_add_double_click and self.robot_add_double_click == event.point_in_scene ): + print("double click have been called?") # add an undo operation to remove the robot that is being added self.__add_undo_operation( RobotOperation(self.next_id, event.point_in_scene, None, self.next_id) @@ -377,6 +378,7 @@ def __handle_new_robot_event(self, event: MouseInSceneEvent) -> None: self.next_id = self.__get_next_robot_id(self.next_id) self.__toggle_robot_add_double_click() else: + print("i have been called?") # start a double click self.robot_add_double_click = event.point_in_scene QTimer.singleShot(500, self.__toggle_robot_add_double_click) From 9e8e6fa3def9816c990235aa07d2b046d3cca505 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:19:26 -0500 Subject: [PATCH 20/48] better double click and now handles single click --- .../gl/layers/gl_draw_polygon_obstacle.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 388a48982f..08f8c8dafe 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -1,3 +1,4 @@ +from numpy import who from software.thunderscope.binary_context_managers.full_system import ProtoUnixIO from software.thunderscope.gl.graphics.gl_polygon import GLPolygon from pyqtgraph.Qt import QtGui @@ -53,9 +54,6 @@ def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: if event.key() == Qt.Key.Key_C: self.clear_polygons() - def _reset_double_click(self): - self.is_double_click = False - def clear_polygons(self): """Clearing the obstacles""" self.points.clear() @@ -128,6 +126,16 @@ def _send_to_fs(self): VirtualObstacles, VirtualObstacles(obstacles=obstacles) ) + def create_single_click_callback(self, event: MouseInSceneEvent): + def _handle_single_click(): + if self.is_double_click: + point = event.point_in_scene + self._add_one_point((point.x(), point.y())) + + self.is_double_click = False + + return _handle_single_click + def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: """Adding the point in scene @@ -143,9 +151,7 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: return else: self.is_double_click = True - QTimer.singleShot(500, self._reset_double_click) + QTimer.singleShot(200, self.create_single_click_callback(event)) - point = event.point_in_scene - self._add_one_point((point.x(), point.y())) return super().mouse_in_scene_pressed(event) From ce99d4c0cb9e82657155479b52ed13e1eacfa445 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:25:42 -0500 Subject: [PATCH 21/48] added more docs --- .../gl/layers/gl_draw_polygon_obstacle.py | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 08f8c8dafe..95a15e0c99 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -40,7 +40,7 @@ def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: # used for keeping track and rendering multiple polygons self.rendering_polygons: ObservableList = ObservableList(self._graphics_changed) - self.is_double_click: bool = True + self.can_double_click: bool = True def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: @@ -127,13 +127,20 @@ def _send_to_fs(self): ) def create_single_click_callback(self, event: MouseInSceneEvent): + """ creating a single shot callback to handle single click + + :param event: The mouse event when a scene is pressed + """ def _handle_single_click(): - if self.is_double_click: + # This logic is somewhat non trivial. If we `can_double_click`, it indicates that + # a double-click hasn't occurred within the 200 ms time window. + # In other words, the user hasn't double-clicked, + # so we will now interpret the action as a single click. + if self.can_double_click: point = event.point_in_scene self._add_one_point((point.x(), point.y())) - self.is_double_click = False - + self.can_double_click = False return _handle_single_click def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: @@ -143,15 +150,14 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: """ if not self.visible(): return + super().mouse_in_scene_pressed(event) # handle double click - if self.is_double_click: + if self.can_double_click: self.push_polygon_to_list() - self.is_double_click = False + self.can_double_click = False return else: - self.is_double_click = True + self.can_double_click = True + # handle single click QTimer.singleShot(200, self.create_single_click_callback(event)) - - - return super().mouse_in_scene_pressed(event) From 49a8cf55ffd50bd387a2aba70a91d54b640f76f5 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:25:59 -0500 Subject: [PATCH 22/48] removed commentsq --- src/software/thunderscope/gl/layers/gl_sandbox_world_layer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_sandbox_world_layer.py b/src/software/thunderscope/gl/layers/gl_sandbox_world_layer.py index 9999ec2f87..4ffe6934ea 100644 --- a/src/software/thunderscope/gl/layers/gl_sandbox_world_layer.py +++ b/src/software/thunderscope/gl/layers/gl_sandbox_world_layer.py @@ -365,7 +365,6 @@ def __handle_new_robot_event(self, event: MouseInSceneEvent) -> None: self.robot_add_double_click and self.robot_add_double_click == event.point_in_scene ): - print("double click have been called?") # add an undo operation to remove the robot that is being added self.__add_undo_operation( RobotOperation(self.next_id, event.point_in_scene, None, self.next_id) @@ -378,7 +377,6 @@ def __handle_new_robot_event(self, event: MouseInSceneEvent) -> None: self.next_id = self.__get_next_robot_id(self.next_id) self.__toggle_robot_add_double_click() else: - print("i have been called?") # start a double click self.robot_add_double_click = event.point_in_scene QTimer.singleShot(500, self.__toggle_robot_add_double_click) From faae2667b983ab146725b82e469f0cc480d5da83 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 28 Dec 2024 23:34:12 -0500 Subject: [PATCH 23/48] added menu --- src/software/thunderscope/gl/gl_widget.py | 15 +++- .../thunderscope/gl/layers/gl_world_layer.py | 36 +++++--- .../gl/widgets/gl_field_toolbar.py | 87 ++++++++++++++++++- 3 files changed, 121 insertions(+), 17 deletions(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 34c06fb0c9..39f049ec9d 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -14,7 +14,7 @@ from software.thunderscope.proto_unix_io import ProtoUnixIO from software.thunderscope.gl.layers.gl_layer import GLLayer from software.thunderscope.gl.layers.gl_measure_layer import GLMeasureLayer -from software.thunderscope.gl.widgets.gl_field_toolbar import GLFieldToolbar +from software.thunderscope.gl.widgets.gl_field_toolbar import GLFieldToolbar, MultiToolbarLayer, ShiftButtonToolbar from software.thunderscope.replay.proto_player import ProtoPlayer from software.thunderscope.replay.replay_controls import ReplayControls from software.thunderscope.gl.helpers.extended_gl_view_widget import * @@ -83,6 +83,15 @@ def __init__( self.setLayout(self.layout) self.layout.addWidget(self.gl_view_widget) + # setup multi layer toolbar + self.multi_layer_toolbar = MultiToolbarLayer( + parent = self.gl_view_widget, + toolbars = [] + ) + + shift_button_toolbar = ShiftButtonToolbar(parent=self.multi_layer_toolbar) + self.multi_layer_toolbar.add_toolbar(shift_button_toolbar) + # Setup toolbar self.measure_mode_enabled = False self.measure_layer = None @@ -90,7 +99,7 @@ def __init__( self.toolbars_menu = QMenu() self.layers_menu_actions = {} self.simulation_control_toolbar = GLFieldToolbar( - parent=self.gl_view_widget, + parent=self.multi_layer_toolbar, on_camera_view_change=self.set_camera_view, on_measure_mode=self.toggle_measure_mode, layers_menu=self.layers_menu, @@ -98,6 +107,8 @@ def __init__( sandbox_mode=sandbox_mode, ) + self.multi_layer_toolbar.add_toolbar(self.simulation_control_toolbar) + # Setup gamecontroller toolbar self.gamecontroller_toolbar = GLGamecontrollerToolbar( parent=self.gl_view_widget, diff --git a/src/software/thunderscope/gl/layers/gl_world_layer.py b/src/software/thunderscope/gl/layers/gl_world_layer.py index 7afb244e93..de0d67ab0e 100644 --- a/src/software/thunderscope/gl/layers/gl_world_layer.py +++ b/src/software/thunderscope/gl/layers/gl_world_layer.py @@ -138,6 +138,17 @@ def __init__( self.auto_chip_graphics = ObservableList(self._graphics_changed) self.speed_line_graphics = ObservableList(self._graphics_changed) + + self.should_move_ball = False + + def should_move_ball_slot(self, should_move_ball): + """ Set the ball movement behavior + + :param should_move_ball: whether or not shift click would move the ball + """ + + self.should_move_ball = should_move_ball + def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: """Detect when a key has been pressed @@ -224,21 +235,22 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: if not event.mouse_event.modifiers() == Qt.KeyboardModifier.ShiftModifier: return - self.point_in_scene_picked = self._invert_position_if_defending_negative_half( - event.point_in_scene - ) + if self.should_move_ball: + self.point_in_scene_picked = self._invert_position_if_defending_negative_half( + event.point_in_scene + ) - # Send a command to the simulator to move the ball to the picked point - world_state = WorldState() - world_state.ball_state.CopyFrom( - BallState( - global_position=Point( - x_meters=self.point_in_scene_picked.x(), - y_meters=self.point_in_scene_picked.y(), + # Send a command to the simulator to move the ball to the picked point + world_state = WorldState() + world_state.ball_state.CopyFrom( + BallState( + global_position=Point( + x_meters=self.point_in_scene_picked.x(), + y_meters=self.point_in_scene_picked.y(), + ) ) ) - ) - self.simulator_io.send_proto(WorldState, world_state) + self.simulator_io.send_proto(WorldState, world_state) def mouse_in_scene_dragged(self, event: MouseInSceneEvent) -> None: """Detect that the mouse was dragged within the 3D scene diff --git a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py index 7c844242fe..056f2262e9 100644 --- a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py @@ -1,4 +1,5 @@ -from typing import Callable +import textwrap +from typing import Callable, List from pyqtgraph.Qt import QtGui from pyqtgraph.Qt.QtWidgets import * from proto.import_all_protos import * @@ -48,9 +49,11 @@ def __init__( """ super(GLFieldToolbar, self).__init__(parent=parent) + import pudb + pudb.set_trace() + print(f"correct parent is: {parent}") # Setup Layers button for toggling visibility of layers - self.layers_button = QPushButton() - self.layers_button.setText("Layers") + self.layers_button = QPushButton("layers") self.layers_button.setStyleSheet(self.get_button_style()) self.layers_button.setMenu(layers_menu) @@ -218,3 +221,81 @@ def set_speed_callback(self, callback: Callable[[float], None]) -> None: :param callback: the callback function to update the simulation speed """ self.speed_callback = callback + + +class ShiftButtonToolbar(QWidget): + def __init__(self, parent) -> None: + print(f"The parent is {parent}") + super().__init__(parent) + self.layout = QHBoxLayout() + self.menu = QMenu() + self.push_button = QPushButton() + self.push_button.setText("Change Shift Button Behavior") + button_style = textwrap.dedent( + f""" + QPushButton {{ + color: #969696; + background-color: transparent; + border-color: transparent; + icon-size: 22px; + border-width: 4px; + border-radius: 4px; + height: 16px; + }} + QPushButton:hover {{ + background-color: {"#363636"}; + border-color: {"#363636"}; + }} + """ + ) + self.push_button.setStyleSheet(button_style) + self.push_button.setMenu(self.menu) + + self.actions = [ + QtGui.QAction("[1] Disable Shift Click Ball Move"), + QtGui.QAction("[2] Enable Shift Click Ball Move"), + ] + + self.actions[0].triggered.connect(self.disable_ball_movement) + self.actions[1].triggered.connect(self.enable_ball_movement) + + for action in self.actions: + self.menu.addAction(action) + + + self.setLayout(self.layout) + self.layout.addWidget(self.push_button) + + def disable_ball_movement(self): + print("I have been disable") + + def enable_ball_movement(self): + print("I have been enabled") + + +class MultiToolbarLayer(QWidget): + def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): + super().__init__(parent) + self.setLayout(QVBoxLayout()) + + self.toolbars: List[GLToolbar] = toolbars + + for toolbar in toolbars: + self.layout().addWidget(toolbar) + toolbar.hide() + + self.show_toolbar(0) + + def add_toolbar(self, toolbar: GLToolbar): + self.toolbars.append(toolbar) + self.layout().addWidget(toolbar) + toolbar.hide() + + self.show_toolbar(0) + + def show_toolbar(self, num): + # cannot show toolbar, since index is out of range + if num >= len(self.toolbars): + return + + self.toolbars[num].show() From 13b52fcbfcb7d1e4357a423034fe990779de783b Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 28 Dec 2024 23:50:41 -0500 Subject: [PATCH 24/48] fixed sizing --- src/software/thunderscope/gl/gl_widget.py | 3 +- .../gl/widgets/gl_field_toolbar.py | 45 +++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 39f049ec9d..2426d1cb92 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -90,7 +90,6 @@ def __init__( ) shift_button_toolbar = ShiftButtonToolbar(parent=self.multi_layer_toolbar) - self.multi_layer_toolbar.add_toolbar(shift_button_toolbar) # Setup toolbar self.measure_mode_enabled = False @@ -107,7 +106,9 @@ def __init__( sandbox_mode=sandbox_mode, ) + # adding layer to the multitool bar layer self.multi_layer_toolbar.add_toolbar(self.simulation_control_toolbar) + self.multi_layer_toolbar.add_toolbar(shift_button_toolbar) # Setup gamecontroller toolbar self.gamecontroller_toolbar = GLGamecontrollerToolbar( diff --git a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py index 056f2262e9..4f145535d5 100644 --- a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py @@ -1,8 +1,12 @@ import textwrap from typing import Callable, List +from PyQt6.QtGui import QKeySequence +from numpy import who +from pyqtgraph import QtCore from pyqtgraph.Qt import QtGui from pyqtgraph.Qt.QtWidgets import * from proto.import_all_protos import * +from PyQt6.QtCore import Qt from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from software.thunderscope.constants import ( CameraView, @@ -49,9 +53,6 @@ def __init__( """ super(GLFieldToolbar, self).__init__(parent=parent) - import pudb - pudb.set_trace() - print(f"correct parent is: {parent}") # Setup Layers button for toggling visibility of layers self.layers_button = QPushButton("layers") self.layers_button.setStyleSheet(self.get_button_style()) @@ -223,11 +224,9 @@ def set_speed_callback(self, callback: Callable[[float], None]) -> None: self.speed_callback = callback -class ShiftButtonToolbar(QWidget): +class ShiftButtonToolbar(GLToolbar): def __init__(self, parent) -> None: - print(f"The parent is {parent}") super().__init__(parent) - self.layout = QHBoxLayout() self.menu = QMenu() self.push_button = QPushButton() self.push_button.setText("Change Shift Button Behavior") @@ -259,24 +258,26 @@ def __init__(self, parent) -> None: self.actions[0].triggered.connect(self.disable_ball_movement) self.actions[1].triggered.connect(self.enable_ball_movement) - for action in self.actions: + for action in self.actions: self.menu.addAction(action) + self.layout().addWidget(self.push_button) - self.setLayout(self.layout) - self.layout.addWidget(self.push_button) + def disable_ball_movement(self): + print("I have been disable") - def disable_ball_movement(self): - print("I have been disable") - - def enable_ball_movement(self): - print("I have been enabled") + def enable_ball_movement(self): + print("I have been enabled") class MultiToolbarLayer(QWidget): def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): super().__init__(parent) - self.setLayout(QVBoxLayout()) + + self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + self.setStyleSheet("background-color: rgba(0,0,0,0);" "padding: 0px;") + self.setAttribute(QtCore.Qt.WidgetAttribute.WA_StyledBackground) + self.setLayout(QHBoxLayout()) self.toolbars: List[GLToolbar] = toolbars @@ -284,7 +285,12 @@ def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): self.layout().addWidget(toolbar) toolbar.hide() - self.show_toolbar(0) + # Create a shortcut for the F1 key + self.shortcut_f1 = QtGui.QShortcut(QKeySequence("F1"), self) + self.shortcut_f1.activated.connect(lambda: self.show_toolbar(0)) + + self.shortcut_f1 = QtGui.QShortcut(QKeySequence("F2"), self) + self.shortcut_f1.activated.connect(lambda: self.show_toolbar(1)) def add_toolbar(self, toolbar: GLToolbar): self.toolbars.append(toolbar) @@ -295,7 +301,10 @@ def add_toolbar(self, toolbar: GLToolbar): def show_toolbar(self, num): # cannot show toolbar, since index is out of range - if num >= len(self.toolbars): - return + if num >= len(self.toolbars): + return + + for toolbar in self.toolbars: + toolbar.hide() self.toolbars[num].show() From 9c50790ff30366625f6fbff6f4fe97697f7d8286 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sun, 29 Dec 2024 00:00:42 -0500 Subject: [PATCH 25/48] basically done, need to do cleanup --- src/software/thunderscope/gl/gl_widget.py | 7 +++++-- src/software/thunderscope/gl/widgets/gl_field_toolbar.py | 9 +++++---- src/software/thunderscope/widget_setup_functions.py | 3 +++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 2426d1cb92..222faddb14 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -89,7 +89,7 @@ def __init__( toolbars = [] ) - shift_button_toolbar = ShiftButtonToolbar(parent=self.multi_layer_toolbar) + self.shift_button_toolbar = ShiftButtonToolbar(parent=self.multi_layer_toolbar) # Setup toolbar self.measure_mode_enabled = False @@ -108,7 +108,7 @@ def __init__( # adding layer to the multitool bar layer self.multi_layer_toolbar.add_toolbar(self.simulation_control_toolbar) - self.multi_layer_toolbar.add_toolbar(shift_button_toolbar) + self.multi_layer_toolbar.add_toolbar(self.shift_button_toolbar) # Setup gamecontroller toolbar self.gamecontroller_toolbar = GLGamecontrollerToolbar( @@ -135,6 +135,9 @@ def __init__( self.set_camera_view(CameraView.LANDSCAPE_HIGH_ANGLE) + def get_shift_button_toolbar(self): + return self.shift_button_toolbar + def get_sim_control_toolbar(self): """Returns the simulation control toolbar""" return self.simulation_control_toolbar diff --git a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py index 4f145535d5..669ac39c11 100644 --- a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py @@ -4,6 +4,7 @@ from numpy import who from pyqtgraph import QtCore from pyqtgraph.Qt import QtGui +from pyqtgraph.Qt.QtCore import pyqtSignal from pyqtgraph.Qt.QtWidgets import * from proto.import_all_protos import * from PyQt6.QtCore import Qt @@ -225,6 +226,7 @@ def set_speed_callback(self, callback: Callable[[float], None]) -> None: class ShiftButtonToolbar(GLToolbar): + disable_ball_movement_signal = pyqtSignal(bool) def __init__(self, parent) -> None: super().__init__(parent) self.menu = QMenu() @@ -265,18 +267,16 @@ def __init__(self, parent) -> None: def disable_ball_movement(self): print("I have been disable") + self.disable_ball_movement_signal.emit(True) def enable_ball_movement(self): print("I have been enabled") - + self.disable_ball_movement_signal.emit(False) class MultiToolbarLayer(QWidget): def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): super().__init__(parent) - self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) - self.setStyleSheet("background-color: rgba(0,0,0,0);" "padding: 0px;") - self.setAttribute(QtCore.Qt.WidgetAttribute.WA_StyledBackground) self.setLayout(QHBoxLayout()) self.toolbars: List[GLToolbar] = toolbars @@ -308,3 +308,4 @@ def show_toolbar(self, num): toolbar.hide() self.toolbars[num].show() + diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 49fca472ae..89f79d330b 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -154,6 +154,9 @@ def setup_gl_widget( simulation_control_toolbar = gl_widget.get_sim_control_toolbar() simulation_control_toolbar.set_speed_callback(world_layer.set_simulation_speed) + shift_button_toolbar = gl_widget.get_shift_button_toolbar() + shift_button_toolbar.disable_ball_movement_signal.connect(world_layer.should_move_ball_slot) + # connect all sandbox controls if using sandbox mode if sandbox_mode: simulation_control_toolbar.undo_button.clicked.connect(world_layer.undo) From b4c941c8ff78ca0cba6f7e014f52f6df462ce8a2 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, 29 Dec 2024 05:10:33 +0000 Subject: [PATCH 26/48] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/gl/gl_widget.py | 15 ++++++----- .../gl/layers/gl_draw_polygon_obstacle.py | 27 ++++++++++--------- .../thunderscope/gl/layers/gl_world_layer.py | 12 ++++----- .../gl/widgets/gl_field_toolbar.py | 10 +++---- .../thunderscope/widget_setup_functions.py | 6 +++-- 5 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 222faddb14..6f8488bd2b 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -14,7 +14,11 @@ from software.thunderscope.proto_unix_io import ProtoUnixIO from software.thunderscope.gl.layers.gl_layer import GLLayer from software.thunderscope.gl.layers.gl_measure_layer import GLMeasureLayer -from software.thunderscope.gl.widgets.gl_field_toolbar import GLFieldToolbar, MultiToolbarLayer, ShiftButtonToolbar +from software.thunderscope.gl.widgets.gl_field_toolbar import ( + GLFieldToolbar, + MultiToolbarLayer, + ShiftButtonToolbar, +) from software.thunderscope.replay.proto_player import ProtoPlayer from software.thunderscope.replay.replay_controls import ReplayControls from software.thunderscope.gl.helpers.extended_gl_view_widget import * @@ -83,10 +87,9 @@ def __init__( self.setLayout(self.layout) self.layout.addWidget(self.gl_view_widget) - # setup multi layer toolbar + # setup multi layer toolbar self.multi_layer_toolbar = MultiToolbarLayer( - parent = self.gl_view_widget, - toolbars = [] + parent=self.gl_view_widget, toolbars=[] ) self.shift_button_toolbar = ShiftButtonToolbar(parent=self.multi_layer_toolbar) @@ -135,8 +138,8 @@ def __init__( self.set_camera_view(CameraView.LANDSCAPE_HIGH_ANGLE) - def get_shift_button_toolbar(self): - return self.shift_button_toolbar + def get_shift_button_toolbar(self): + return self.shift_button_toolbar def get_sim_control_toolbar(self): """Returns the simulation control toolbar""" diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index f3f83a6921..7aa1eea7fc 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -1,4 +1,3 @@ -from numpy import who from software.thunderscope.binary_context_managers.full_system import ProtoUnixIO from software.thunderscope.gl.graphics.gl_polygon import GLPolygon from pyqtgraph.Qt import QtGui @@ -27,7 +26,8 @@ def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: """ super().__init__(name) - import inspect + import inspect + print(inspect.getmro(GLDrawPolygonObstacleLayer)) self.friendly_io: ProtoUnixIO = friendly_io @@ -42,7 +42,6 @@ def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: self.can_double_click: bool = True - def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: """Responding to key events that are going to push obstacles to the stack or add point @@ -127,22 +126,24 @@ def _send_to_fs(self): ) def create_single_click_callback(self, event: MouseInSceneEvent): - """ creating a single shot callback to handle single click + """Creating a single shot callback to handle single click :param event: The mouse event when a scene is pressed """ - def _handle_single_click(): - # This logic is somewhat non trivial. If we `can_double_click`, it indicates that - # a double-click hasn't occurred within the 200 ms time window. - # In other words, the user hasn't double-clicked, + + def _handle_single_click(): + # This logic is somewhat non trivial. If we `can_double_click`, it indicates that + # a double-click hasn't occurred within the 200 ms time window. + # In other words, the user hasn't double-clicked, # so we will now interpret the action as a single click. if self.can_double_click: point = event.point_in_scene self._add_one_point((point.x(), point.y())) self.can_double_click = False + return _handle_single_click - + def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: """Adding the point in scene @@ -155,9 +156,9 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: # handle double click if self.can_double_click: self.push_polygon_to_list() - self.can_double_click = False - return - else: - self.can_double_click = True + self.can_double_click = False + return + else: + self.can_double_click = True # handle single click QTimer.singleShot(200, self.create_single_click_callback(event)) diff --git a/src/software/thunderscope/gl/layers/gl_world_layer.py b/src/software/thunderscope/gl/layers/gl_world_layer.py index de0d67ab0e..19d19c9222 100644 --- a/src/software/thunderscope/gl/layers/gl_world_layer.py +++ b/src/software/thunderscope/gl/layers/gl_world_layer.py @@ -138,15 +138,13 @@ def __init__( self.auto_chip_graphics = ObservableList(self._graphics_changed) self.speed_line_graphics = ObservableList(self._graphics_changed) + self.should_move_ball = False - self.should_move_ball = False - - def should_move_ball_slot(self, should_move_ball): - """ Set the ball movement behavior + def should_move_ball_slot(self, should_move_ball): + """Set the ball movement behavior :param should_move_ball: whether or not shift click would move the ball """ - self.should_move_ball = should_move_ball def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: @@ -236,8 +234,8 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: return if self.should_move_ball: - self.point_in_scene_picked = self._invert_position_if_defending_negative_half( - event.point_in_scene + self.point_in_scene_picked = ( + self._invert_position_if_defending_negative_half(event.point_in_scene) ) # Send a command to the simulator to move the ball to the picked point diff --git a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py index 669ac39c11..98822f08f2 100644 --- a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py @@ -1,13 +1,10 @@ import textwrap from typing import Callable, List from PyQt6.QtGui import QKeySequence -from numpy import who -from pyqtgraph import QtCore from pyqtgraph.Qt import QtGui from pyqtgraph.Qt.QtCore import pyqtSignal from pyqtgraph.Qt.QtWidgets import * from proto.import_all_protos import * -from PyQt6.QtCore import Qt from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from software.thunderscope.constants import ( CameraView, @@ -227,6 +224,7 @@ def set_speed_callback(self, callback: Callable[[float], None]) -> None: class ShiftButtonToolbar(GLToolbar): disable_ball_movement_signal = pyqtSignal(bool) + def __init__(self, parent) -> None: super().__init__(parent) self.menu = QMenu() @@ -273,6 +271,7 @@ def enable_ball_movement(self): print("I have been enabled") self.disable_ball_movement_signal.emit(False) + class MultiToolbarLayer(QWidget): def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): super().__init__(parent) @@ -285,7 +284,7 @@ def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): self.layout().addWidget(toolbar) toolbar.hide() - # Create a shortcut for the F1 key + # Create a shortcut for the F1 key self.shortcut_f1 = QtGui.QShortcut(QKeySequence("F1"), self) self.shortcut_f1.activated.connect(lambda: self.show_toolbar(0)) @@ -304,8 +303,7 @@ def show_toolbar(self, num): if num >= len(self.toolbars): return - for toolbar in self.toolbars: + for toolbar in self.toolbars: toolbar.hide() self.toolbars[num].show() - diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 89f79d330b..2ceb1eeb0c 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -154,8 +154,10 @@ def setup_gl_widget( simulation_control_toolbar = gl_widget.get_sim_control_toolbar() simulation_control_toolbar.set_speed_callback(world_layer.set_simulation_speed) - shift_button_toolbar = gl_widget.get_shift_button_toolbar() - shift_button_toolbar.disable_ball_movement_signal.connect(world_layer.should_move_ball_slot) + shift_button_toolbar = gl_widget.get_shift_button_toolbar() + shift_button_toolbar.disable_ball_movement_signal.connect( + world_layer.should_move_ball_slot + ) # connect all sandbox controls if using sandbox mode if sandbox_mode: From 91c9f48c270a221abc748862fd994f8a96a07352 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sun, 29 Dec 2024 10:45:09 -0500 Subject: [PATCH 27/48] updated documentation --- src/software/thunderscope/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 3e03cac56e..6fd75fd533 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -170,7 +170,7 @@ class EstopMode(IntEnum): Ctrl + R: Remove the current layout file and reset the layout

Layout file (on save) is located at {SAVED_LAYOUT_PATH}
- p Push the current obstacle to the obstacle stack. GLDrawPolygonObstalce layer must be enabled.

+ Shift+Double Click Push the current obstacle to the obstacle stack. GLDrawPolygonObstalce layer must be enabled.

c Clear the virtual obstacle stack GLDrawPolygonObstalce layer must be enabled.

From 6ce5090d6bf3cce0071628aa3d8b8e722b07678a Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sun, 29 Dec 2024 12:45:31 -0500 Subject: [PATCH 28/48] moved stuff into different file --- src/software/thunderscope/gl/BUILD | 2 + src/software/thunderscope/gl/gl_widget.py | 6 +- src/software/thunderscope/gl/widgets/BUILD | 18 ++++ .../gl/widgets/gl_field_toolbar.py | 95 +------------------ .../gl/widgets/gl_multilayer_toolbar.py | 42 ++++++++ .../gl/widgets/gl_shift_toolbar.py | 60 ++++++++++++ .../thunderscope/widget_setup_functions.py | 2 +- 7 files changed, 129 insertions(+), 96 deletions(-) create mode 100644 src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py create mode 100644 src/software/thunderscope/gl/widgets/gl_shift_toolbar.py diff --git a/src/software/thunderscope/gl/BUILD b/src/software/thunderscope/gl/BUILD index 1e6813f9dd..f4c41849ef 100644 --- a/src/software/thunderscope/gl/BUILD +++ b/src/software/thunderscope/gl/BUILD @@ -11,6 +11,8 @@ py_library( "//software/thunderscope/gl/layers:gl_layer", "//software/thunderscope/gl/layers:gl_measure_layer", "//software/thunderscope/gl/widgets:gl_field_toolbar", + "//software/thunderscope/gl/widgets:gl_multilayer_toolbar", + "//software/thunderscope/gl/widgets:gl_shift_toolbar", "//software/thunderscope/gl/widgets:gl_gamecontroller_toolbar", "//software/thunderscope/replay:replay_controls", requirement("pyqtgraph"), diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 6f8488bd2b..8ba185c365 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -11,13 +11,13 @@ from software.thunderscope.constants import * +from software.thunderscope.gl.widgets.gl_multilayer_toolbar import MultilayerToolbar +from software.thunderscope.gl.widgets.gl_shift_toolbar import ShiftButtonToolbar from software.thunderscope.proto_unix_io import ProtoUnixIO from software.thunderscope.gl.layers.gl_layer import GLLayer from software.thunderscope.gl.layers.gl_measure_layer import GLMeasureLayer from software.thunderscope.gl.widgets.gl_field_toolbar import ( GLFieldToolbar, - MultiToolbarLayer, - ShiftButtonToolbar, ) from software.thunderscope.replay.proto_player import ProtoPlayer from software.thunderscope.replay.replay_controls import ReplayControls @@ -88,7 +88,7 @@ def __init__( self.layout.addWidget(self.gl_view_widget) # setup multi layer toolbar - self.multi_layer_toolbar = MultiToolbarLayer( + self.multi_layer_toolbar = MultilayerToolbar( parent=self.gl_view_widget, toolbars=[] ) diff --git a/src/software/thunderscope/gl/widgets/BUILD b/src/software/thunderscope/gl/widgets/BUILD index 4c4d720119..50218d4e24 100644 --- a/src/software/thunderscope/gl/widgets/BUILD +++ b/src/software/thunderscope/gl/widgets/BUILD @@ -42,3 +42,21 @@ py_library( requirement("pyqtgraph"), ], ) + +py_library( + name = "gl_multilayer_toolbar", + srcs = ["gl_multilayer_toolbar.py"], + deps = [ + ":gl_toolbar", + requirement("pyqtgraph"), + ], +) + +py_library( + name = "gl_shift_toolbar", + srcs = ["gl_shift_toolbar.py"], + deps = [ + ":gl_toolbar", + requirement("pyqtgraph"), + ], +) diff --git a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py index 98822f08f2..7c844242fe 100644 --- a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py @@ -1,8 +1,5 @@ -import textwrap -from typing import Callable, List -from PyQt6.QtGui import QKeySequence +from typing import Callable from pyqtgraph.Qt import QtGui -from pyqtgraph.Qt.QtCore import pyqtSignal from pyqtgraph.Qt.QtWidgets import * from proto.import_all_protos import * from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer @@ -52,7 +49,8 @@ def __init__( super(GLFieldToolbar, self).__init__(parent=parent) # Setup Layers button for toggling visibility of layers - self.layers_button = QPushButton("layers") + self.layers_button = QPushButton() + self.layers_button.setText("Layers") self.layers_button.setStyleSheet(self.get_button_style()) self.layers_button.setMenu(layers_menu) @@ -220,90 +218,3 @@ def set_speed_callback(self, callback: Callable[[float], None]) -> None: :param callback: the callback function to update the simulation speed """ self.speed_callback = callback - - -class ShiftButtonToolbar(GLToolbar): - disable_ball_movement_signal = pyqtSignal(bool) - - def __init__(self, parent) -> None: - super().__init__(parent) - self.menu = QMenu() - self.push_button = QPushButton() - self.push_button.setText("Change Shift Button Behavior") - button_style = textwrap.dedent( - f""" - QPushButton {{ - color: #969696; - background-color: transparent; - border-color: transparent; - icon-size: 22px; - border-width: 4px; - border-radius: 4px; - height: 16px; - }} - QPushButton:hover {{ - background-color: {"#363636"}; - border-color: {"#363636"}; - }} - """ - ) - self.push_button.setStyleSheet(button_style) - self.push_button.setMenu(self.menu) - - self.actions = [ - QtGui.QAction("[1] Disable Shift Click Ball Move"), - QtGui.QAction("[2] Enable Shift Click Ball Move"), - ] - - self.actions[0].triggered.connect(self.disable_ball_movement) - self.actions[1].triggered.connect(self.enable_ball_movement) - - for action in self.actions: - self.menu.addAction(action) - - self.layout().addWidget(self.push_button) - - def disable_ball_movement(self): - print("I have been disable") - self.disable_ball_movement_signal.emit(True) - - def enable_ball_movement(self): - print("I have been enabled") - self.disable_ball_movement_signal.emit(False) - - -class MultiToolbarLayer(QWidget): - def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): - super().__init__(parent) - - self.setLayout(QHBoxLayout()) - - self.toolbars: List[GLToolbar] = toolbars - - for toolbar in toolbars: - self.layout().addWidget(toolbar) - toolbar.hide() - - # Create a shortcut for the F1 key - self.shortcut_f1 = QtGui.QShortcut(QKeySequence("F1"), self) - self.shortcut_f1.activated.connect(lambda: self.show_toolbar(0)) - - self.shortcut_f1 = QtGui.QShortcut(QKeySequence("F2"), self) - self.shortcut_f1.activated.connect(lambda: self.show_toolbar(1)) - - def add_toolbar(self, toolbar: GLToolbar): - self.toolbars.append(toolbar) - self.layout().addWidget(toolbar) - toolbar.hide() - - self.show_toolbar(0) - - def show_toolbar(self, num): - # cannot show toolbar, since index is out of range - if num >= len(self.toolbars): - return - - for toolbar in self.toolbars: - toolbar.hide() - - self.toolbars[num].show() diff --git a/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py b/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py new file mode 100644 index 0000000000..2206da65bd --- /dev/null +++ b/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py @@ -0,0 +1,42 @@ +from typing import List +from PyQt6.QtGui import QKeySequence +from pyqtgraph.Qt import QtGui +from pyqtgraph.Qt.QtWidgets import * +from proto.import_all_protos import * +from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar + +class MultilayerToolbar(QWidget): + def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): + super().__init__(parent) + + self.setLayout(QHBoxLayout()) + + self.toolbars: List[GLToolbar] = toolbars + + for toolbar in toolbars: + self.layout().addWidget(toolbar) + toolbar.hide() + + # Create a shortcut for the F1 key + self.shortcut_f1 = QtGui.QShortcut(QKeySequence("F1"), self) + self.shortcut_f1.activated.connect(lambda: self.show_toolbar(0)) + + self.shortcut_f1 = QtGui.QShortcut(QKeySequence("F2"), self) + self.shortcut_f1.activated.connect(lambda: self.show_toolbar(1)) + + def add_toolbar(self, toolbar: GLToolbar): + self.toolbars.append(toolbar) + self.layout().addWidget(toolbar) + toolbar.hide() + + self.show_toolbar(0) + + def show_toolbar(self, num): + # cannot show toolbar, since index is out of range + if num >= len(self.toolbars): + return + + for toolbar in self.toolbars: + toolbar.hide() + + self.toolbars[num].show() diff --git a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py new file mode 100644 index 0000000000..2917ec47cb --- /dev/null +++ b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py @@ -0,0 +1,60 @@ +import textwrap +from pyqtgraph.Qt import QtGui +from pyqtgraph.Qt.QtCore import pyqtSignal +from pyqtgraph.Qt.QtWidgets import * +from proto.import_all_protos import * +from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar + +class ShiftButtonToolbar(GLToolbar): + enable_ball_movement_signal = pyqtSignal(bool) + + def __init__(self, parent) -> None: + super().__init__(parent) + self.menu = QMenu() + self.push_button = QPushButton() + # TODO: make this a static method! + button_style = textwrap.dedent( + f""" + QPushButton {{ + color: #969696; + background-color: transparent; + border-color: transparent; + icon-size: 22px; + border-width: 4px; + border-radius: 4px; + height: 16px; + }} + QPushButton:hover {{ + background-color: {"#363636"}; + border-color: {"#363636"}; + }} + """ + ) + self.push_button.setStyleSheet(button_style) + self.push_button.setMenu(self.menu) + + self.actions = [ + QtGui.QAction("[1] Enable Shift Click Ball Move"), + QtGui.QAction("[2] Disable Shift Click Ball Move"), + ] + + self.actions[0].triggered.connect(self.enable_ball_movement) + self.actions[1].triggered.connect(self.disable_ball_movement) + + for action in self.actions: + self.menu.addAction(action) + + self.layout().addWidget(self.push_button) + + # enable ball by default, just in case + self.enable_ball_movement_signal.emit(True) + self.push_button.setText("[1] Enable Shift Click Ball Move") + + def disable_ball_movement(self): + self.push_button.setText("[2] Disable Shift Click Ball Move") + self.enable_ball_movement_signal.emit(False) + + def enable_ball_movement(self): + self.push_button.setText("[1] Enable Shift Click Ball Move") + self.enable_ball_movement_signal.emit(True) + diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 2ceb1eeb0c..143df0b70f 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -155,7 +155,7 @@ def setup_gl_widget( simulation_control_toolbar.set_speed_callback(world_layer.set_simulation_speed) shift_button_toolbar = gl_widget.get_shift_button_toolbar() - shift_button_toolbar.disable_ball_movement_signal.connect( + shift_button_toolbar.enable_ball_movement_signal.connect( world_layer.should_move_ball_slot ) From 89c6bead5aeb4e0f169a71b84d2196880823189f Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sun, 29 Dec 2024 12:48:20 -0500 Subject: [PATCH 29/48] made some changes --- .../gl/widgets/gl_field_toolbar.py | 24 +++++++++---------- .../gl/widgets/gl_gamecontroller_toolbar.py | 6 ++--- .../gl/widgets/gl_shift_toolbar.py | 19 +-------------- .../thunderscope/gl/widgets/gl_toolbar.py | 3 ++- 4 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py index 7c844242fe..85582f83b8 100644 --- a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py @@ -51,14 +51,14 @@ def __init__( # Setup Layers button for toggling visibility of layers self.layers_button = QPushButton() self.layers_button.setText("Layers") - self.layers_button.setStyleSheet(self.get_button_style()) + self.layers_button.setStyleSheet(GLToolbar.get_button_style()) self.layers_button.setMenu(layers_menu) # Set up View button for setting the camera position to standard views self.camera_view_button = QPushButton() self.camera_view_button.setToolTip("View") self.camera_view_button.setIcon(icons.get_view_icon(self.BUTTON_ICON_COLOR)) - self.camera_view_button.setStyleSheet(self.get_button_style()) + self.camera_view_button.setStyleSheet(GLToolbar.get_button_style()) self.camera_view_menu = QMenu() self.camera_view_button.setMenu(self.camera_view_menu) self.camera_view_actions = [ @@ -86,7 +86,7 @@ def __init__( self.measure_button = QPushButton() self.measure_button.setToolTip("Measure") self.measure_button.setIcon(icons.get_measure_icon(self.BUTTON_ICON_COLOR)) - self.measure_button.setStyleSheet(self.get_button_style()) + self.measure_button.setStyleSheet(GLToolbar.get_button_style()) self.measure_button.setShortcut("m") self.measure_button.clicked.connect(lambda: on_measure_mode()) @@ -94,14 +94,14 @@ def __init__( self.help_button = QPushButton() self.help_button.setToolTip("Help") self.help_button.setIcon(icons.get_help_icon(self.BUTTON_ICON_COLOR)) - self.help_button.setStyleSheet(self.get_button_style()) + self.help_button.setStyleSheet(GLToolbar.get_button_style()) self.help_button.clicked.connect( lambda: QMessageBox.information(self, "Help", THUNDERSCOPE_HELP_TEXT) ) # Setup pause button self.pause_button = QPushButton() - self.pause_button.setStyleSheet(self.get_button_style()) + self.pause_button.setStyleSheet(GLToolbar.get_button_style()) self.toggle_pause_button(True) # buffer for the simulator pause / play state self.simulation_state_buffer = ThreadSafeBuffer(5, SimulationState) @@ -112,13 +112,13 @@ def __init__( self.toolbars_menu = QMenu() self.toolbars_menu_checkboxes = {} self.toolbars_button.setMenu(toolbars_menu) - self.toolbars_button.setStyleSheet(self.get_button_style()) + self.toolbars_button.setStyleSheet(GLToolbar.get_button_style()) # Setup simulation speed button and menu self.sim_speed_menu = QMenu() self.sim_speed_button = QPushButton() self.sim_speed_button.setText("Speed: 1.00x") - self.sim_speed_button.setStyleSheet(self.get_button_style()) + self.sim_speed_button.setStyleSheet(GLToolbar.get_button_style()) self.sim_speed_button.setMenu(self.sim_speed_menu) self.sim_speed_button.setToolTip("Simulation Speed") @@ -138,18 +138,18 @@ def __init__( self.undo_button = ToggleableButton(False) self.undo_button.setToolTip("Undo") self.undo_button.setIcon(icons.get_undo_icon(self.BUTTON_ICON_COLOR)) - self.undo_button.setStyleSheet(self.get_button_style(False)) + self.undo_button.setStyleSheet(GLToolbar.get_button_style(False)) # Setup Redo button self.redo_button = ToggleableButton(False) self.redo_button.setToolTip("Redo") self.redo_button.setIcon(icons.get_redo_icon(self.BUTTON_ICON_COLOR)) - self.redo_button.setStyleSheet(self.get_button_style(False)) + self.redo_button.setStyleSheet(GLToolbar.get_button_style(False)) self.reset_button = QPushButton() self.reset_button.setToolTip("Reset") self.reset_button.setIcon(icons.get_reset_icon(self.BUTTON_ICON_COLOR)) - self.reset_button.setStyleSheet(self.get_button_style()) + self.reset_button.setStyleSheet(GLToolbar.get_button_style()) # Setup toolbar self.layout().addWidget(self.layers_button) @@ -200,7 +200,7 @@ def toggle_undo_enabled(self, enabled: bool) -> None: :param enabled: if the undo button is enabled or not """ self.undo_button.toggle_enabled(enabled) - self.undo_button.setStyleSheet(self.get_button_style(enabled)) + self.undo_button.setStyleSheet(GLToolbar.get_button_style(enabled)) self.undo_button.repaint() def toggle_redo_enabled(self, enabled: bool) -> None: @@ -209,7 +209,7 @@ def toggle_redo_enabled(self, enabled: bool) -> None: :param enabled: if the redo button is enabled or not """ self.redo_button.toggle_enabled(enabled) - self.redo_button.setStyleSheet(self.get_button_style(enabled)) + self.redo_button.setStyleSheet(GLToolbar.get_button_style(enabled)) self.redo_button.repaint() def set_speed_callback(self, callback: Callable[[float], None]) -> None: diff --git a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py index dd76ed0664..e950f46a0a 100644 --- a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py @@ -80,7 +80,7 @@ def __init__( self.plays_menu_button = QPushButton() self.plays_menu_button.setText("Plays") - self.plays_menu_button.setStyleSheet(self.get_button_style()) + self.plays_menu_button.setStyleSheet(GLToolbar.get_button_style()) self.plays_menu_button.setMenu(self.plays_menu) # add play items for each team color @@ -175,7 +175,7 @@ def __toggle_normal_start_button(self) -> None: """Toggles the enabled / disabled state of the Normal Start button""" self.normal_start_enabled = not self.normal_start_enabled self.normal_start_button.setStyleSheet( - self.get_button_style(self.normal_start_enabled) + GLToolbar.get_button_style(self.normal_start_enabled) ) self.normal_start_button.setIcon( icons.get_normal_start_icon( @@ -203,7 +203,7 @@ def __setup_icon_button( button = QPushButton() button.setIcon(icon) button.setToolTip(tooltip) - button.setStyleSheet(self.get_button_style()) + button.setStyleSheet(GLToolbar.get_button_style()) button.clicked.connect(callback) if display_text: diff --git a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py index 2917ec47cb..c8980b60c1 100644 --- a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py @@ -13,24 +13,7 @@ def __init__(self, parent) -> None: self.menu = QMenu() self.push_button = QPushButton() # TODO: make this a static method! - button_style = textwrap.dedent( - f""" - QPushButton {{ - color: #969696; - background-color: transparent; - border-color: transparent; - icon-size: 22px; - border-width: 4px; - border-radius: 4px; - height: 16px; - }} - QPushButton:hover {{ - background-color: {"#363636"}; - border-color: {"#363636"}; - }} - """ - ) - self.push_button.setStyleSheet(button_style) + self.push_button.setStyleSheet(GLToolbar.get_button_style()) self.push_button.setMenu(self.menu) self.actions = [ diff --git a/src/software/thunderscope/gl/widgets/gl_toolbar.py b/src/software/thunderscope/gl/widgets/gl_toolbar.py index ce6c55dec4..b7bc4a02d5 100644 --- a/src/software/thunderscope/gl/widgets/gl_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_toolbar.py @@ -29,7 +29,8 @@ def __init__(self, parent: QWidget): def refresh(self) -> None: """Refreshes the UI (overridden by child classes)""" - def get_button_style(self, is_enabled: bool = True) -> str: + @staticmethod + def get_button_style(is_enabled: bool = True) -> str: """Returns the stylesheet for a QPushButton based on if it's enabled or not :param is_enabled: True if button is enabled, False if not From e44d959c0fc42078fbd76a7047fece6979317159 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sun, 29 Dec 2024 13:00:35 -0500 Subject: [PATCH 30/48] final clean up I hope --- .../gl/layers/gl_draw_polygon_obstacle.py | 3 -- .../gl/widgets/gl_multilayer_toolbar.py | 26 +++++++++++++++- .../gl/widgets/gl_shift_toolbar.py | 30 ++++++++++++------- .../thunderscope/widget_setup_functions.py | 2 +- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 7aa1eea7fc..edf7f11cb6 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -26,9 +26,6 @@ def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: """ super().__init__(name) - import inspect - - print(inspect.getmro(GLDrawPolygonObstacleLayer)) self.friendly_io: ProtoUnixIO = friendly_io self.current_polygon: GLPolygon = GLPolygon(parent_item=self, line_width=2) diff --git a/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py b/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py index 2206da65bd..e6fa66d31a 100644 --- a/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py @@ -1,14 +1,26 @@ -from typing import List +from typing import List from PyQt6.QtGui import QKeySequence from pyqtgraph.Qt import QtGui from pyqtgraph.Qt.QtWidgets import * from proto.import_all_protos import * from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar + class MultilayerToolbar(QWidget): + """A widget where it allows user to easily switch between layers by pressing F keys.""" + def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): + """Initialize the MultilayerToolbar Widget + + :param parent: the parent widget of this toolbar + :param toolbars: a list of toolbars to be swapped + """ super().__init__(parent) + # TODO: as of current, this layer only supports two layers, but + # it can be easily extended to support multiple layers in the future + assert len(toolbars) <= 2 + self.setLayout(QHBoxLayout()) self.toolbars: List[GLToolbar] = toolbars @@ -25,6 +37,14 @@ def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): self.shortcut_f1.activated.connect(lambda: self.show_toolbar(1)) def add_toolbar(self, toolbar: GLToolbar): + """Adding a toolbar to the widget. Appends the toolbar at the end of the array + + :param toolbar: the toolbar going to be added to this layer + """ + + # we only support 2 toolbars as of current + assert len(self.toolbars) <= 1 + self.toolbars.append(toolbar) self.layout().addWidget(toolbar) toolbar.hide() @@ -32,6 +52,10 @@ def add_toolbar(self, toolbar: GLToolbar): self.show_toolbar(0) def show_toolbar(self, num): + """display a toolbar + + :param num: the index of the toolbar in the toolbars array going to be displayed + """ # cannot show toolbar, since index is out of range if num >= len(self.toolbars): return diff --git a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py index c8980b60c1..1a3c1a1e6c 100644 --- a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py @@ -5,10 +5,17 @@ from proto.import_all_protos import * from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar + class ShiftButtonToolbar(GLToolbar): - enable_ball_movement_signal = pyqtSignal(bool) + """A shift button toolbar that is going to be used to enable/disable shift click ball placement in Thunderscope""" + + enable_ball_placement_signal = pyqtSignal(bool) + + def __init__(self, parent: QWidget) -> None: + """Initialize the ShiftButtonToolbar - def __init__(self, parent) -> None: + :param parent: the parent of this widget + """ super().__init__(parent) self.menu = QMenu() self.push_button = QPushButton() @@ -21,23 +28,24 @@ def __init__(self, parent) -> None: QtGui.QAction("[2] Disable Shift Click Ball Move"), ] - self.actions[0].triggered.connect(self.enable_ball_movement) - self.actions[1].triggered.connect(self.disable_ball_movement) + self.actions[0].triggered.connect(self.enable_ball_placement) + self.actions[1].triggered.connect(self.disable_ball_placement) for action in self.actions: self.menu.addAction(action) self.layout().addWidget(self.push_button) - + # enable ball by default, just in case - self.enable_ball_movement_signal.emit(True) + self.enable_ball_placement_signal.emit(True) self.push_button.setText("[1] Enable Shift Click Ball Move") - def disable_ball_movement(self): + def disable_ball_placement(self): + """disable shift click ball placement""" self.push_button.setText("[2] Disable Shift Click Ball Move") - self.enable_ball_movement_signal.emit(False) + self.enable_ball_placement_signal.emit(False) - def enable_ball_movement(self): + def enable_ball_placement(self): + """enable shift click ball placement""" self.push_button.setText("[1] Enable Shift Click Ball Move") - self.enable_ball_movement_signal.emit(True) - + self.enable_ball_placement_signal.emit(True) diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 143df0b70f..e1f4f6b333 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -155,7 +155,7 @@ def setup_gl_widget( simulation_control_toolbar.set_speed_callback(world_layer.set_simulation_speed) shift_button_toolbar = gl_widget.get_shift_button_toolbar() - shift_button_toolbar.enable_ball_movement_signal.connect( + shift_button_toolbar.enable_ball_placement_signal.connect( world_layer.should_move_ball_slot ) From 65f88de0a7de195ec57fcda394d6d9cba564062d 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, 29 Dec 2024 18:10:16 +0000 Subject: [PATCH 31/48] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/gl/BUILD | 2 +- .../thunderscope/gl/widgets/gl_multilayer_toolbar.py | 3 +-- src/software/thunderscope/gl/widgets/gl_shift_toolbar.py | 5 ++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/software/thunderscope/gl/BUILD b/src/software/thunderscope/gl/BUILD index f4c41849ef..ac63eff8c0 100644 --- a/src/software/thunderscope/gl/BUILD +++ b/src/software/thunderscope/gl/BUILD @@ -11,9 +11,9 @@ py_library( "//software/thunderscope/gl/layers:gl_layer", "//software/thunderscope/gl/layers:gl_measure_layer", "//software/thunderscope/gl/widgets:gl_field_toolbar", + "//software/thunderscope/gl/widgets:gl_gamecontroller_toolbar", "//software/thunderscope/gl/widgets:gl_multilayer_toolbar", "//software/thunderscope/gl/widgets:gl_shift_toolbar", - "//software/thunderscope/gl/widgets:gl_gamecontroller_toolbar", "//software/thunderscope/replay:replay_controls", requirement("pyqtgraph"), ], diff --git a/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py b/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py index e6fa66d31a..4ee4a74e83 100644 --- a/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py @@ -41,7 +41,6 @@ def add_toolbar(self, toolbar: GLToolbar): :param toolbar: the toolbar going to be added to this layer """ - # we only support 2 toolbars as of current assert len(self.toolbars) <= 1 @@ -52,7 +51,7 @@ def add_toolbar(self, toolbar: GLToolbar): self.show_toolbar(0) def show_toolbar(self, num): - """display a toolbar + """Display a toolbar :param num: the index of the toolbar in the toolbars array going to be displayed """ diff --git a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py index 1a3c1a1e6c..83ad884df8 100644 --- a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py @@ -1,4 +1,3 @@ -import textwrap from pyqtgraph.Qt import QtGui from pyqtgraph.Qt.QtCore import pyqtSignal from pyqtgraph.Qt.QtWidgets import * @@ -41,11 +40,11 @@ def __init__(self, parent: QWidget) -> None: self.push_button.setText("[1] Enable Shift Click Ball Move") def disable_ball_placement(self): - """disable shift click ball placement""" + """Disable shift click ball placement""" self.push_button.setText("[2] Disable Shift Click Ball Move") self.enable_ball_placement_signal.emit(False) def enable_ball_placement(self): - """enable shift click ball placement""" + """Enable shift click ball placement""" self.push_button.setText("[1] Enable Shift Click Ball Move") self.enable_ball_placement_signal.emit(True) From 856172812bfcacc23d3735cc8d4fee5df5e11a6c Mon Sep 17 00:00:00 2001 From: Vincent <53135664+Mr-Anyone@users.noreply.github.com> Date: Mon, 30 Dec 2024 10:53:11 -0500 Subject: [PATCH 32/48] Update src/software/thunderscope/constants.py Co-authored-by: itsarune <42703774+itsarune@users.noreply.github.com> --- src/software/thunderscope/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 6fd75fd533..f058559ca5 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -170,7 +170,7 @@ class EstopMode(IntEnum): Ctrl + R: Remove the current layout file and reset the layout

Layout file (on save) is located at {SAVED_LAYOUT_PATH}
- Shift+Double Click Push the current obstacle to the obstacle stack. GLDrawPolygonObstalce layer must be enabled.

+ Shift+Double Click Push the current obstacle to the obstacle stack. FriendlyObstacleLayer must be enabled.

c Clear the virtual obstacle stack GLDrawPolygonObstalce layer must be enabled.

From 864971466f5b3a9e84410dc3e30e92f51246a021 Mon Sep 17 00:00:00 2001 From: Vincent <53135664+Mr-Anyone@users.noreply.github.com> Date: Mon, 30 Dec 2024 10:53:23 -0500 Subject: [PATCH 33/48] Update src/software/thunderscope/constants.py Co-authored-by: itsarune <42703774+itsarune@users.noreply.github.com> --- src/software/thunderscope/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index f058559ca5..7c5febc2ed 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -171,7 +171,7 @@ class EstopMode(IntEnum): Layout file (on save) is located at {SAVED_LAYOUT_PATH}
Shift+Double Click Push the current obstacle to the obstacle stack. FriendlyObstacleLayer must be enabled.

- c Clear the virtual obstacle stack GLDrawPolygonObstalce layer must be enabled.

+ c Clear the virtual obstacle stack FriendlyObstacleLayer must be enabled.

""" From eabcb19023a92acbcf61237e68febf8bcac13d34 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Mon, 30 Dec 2024 10:56:15 -0500 Subject: [PATCH 34/48] made some small changes --- src/software/thunderscope/gl/gl_widget.py | 1 + .../thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 10 ++++++++-- .../thunderscope/gl/widgets/gl_multilayer_toolbar.py | 4 ++-- src/software/thunderscope/widget_setup_functions.py | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index 8ba185c365..4b5043691f 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -139,6 +139,7 @@ def __init__( self.set_camera_view(CameraView.LANDSCAPE_HIGH_ANGLE) def get_shift_button_toolbar(self): + """Get the ShiftButtonToolbar""" return self.shift_button_toolbar def get_sim_control_toolbar(self): diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index edf7f11cb6..b147289698 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -17,6 +17,7 @@ class GLDrawPolygonObstacleLayer(GLLayer): """A layer used to draw polygons that are going to represent obstacles for the trajectory planner to avoid. """ + DOUBLE_CLICK_INTERVAL = 200 def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: """Initialize this layer @@ -130,7 +131,7 @@ def create_single_click_callback(self, event: MouseInSceneEvent): def _handle_single_click(): # This logic is somewhat non trivial. If we `can_double_click`, it indicates that - # a double-click hasn't occurred within the 200 ms time window. + # a double-click hasn't occurred within the 200 ms time window after the first click. # In other words, the user hasn't double-clicked, # so we will now interpret the action as a single click. if self.can_double_click: @@ -158,4 +159,9 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: else: self.can_double_click = True # handle single click - QTimer.singleShot(200, self.create_single_click_callback(event)) + QTimer.singleShot(self.DOUBLE_CLICK_INTERVAL, self.create_single_click_callback(event)) + + def refresh_graphics(self) -> None: + """refreshing graphics""" + return + diff --git a/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py b/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py index e6fa66d31a..e1ec47d511 100644 --- a/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py @@ -33,8 +33,8 @@ def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): self.shortcut_f1 = QtGui.QShortcut(QKeySequence("F1"), self) self.shortcut_f1.activated.connect(lambda: self.show_toolbar(0)) - self.shortcut_f1 = QtGui.QShortcut(QKeySequence("F2"), self) - self.shortcut_f1.activated.connect(lambda: self.show_toolbar(1)) + self.shortcut_f2 = QtGui.QShortcut(QKeySequence("F2"), self) + self.shortcut_f2.activated.connect(lambda: self.show_toolbar(1)) def add_toolbar(self, toolbar: GLToolbar): """Adding a toolbar to the widget. Appends the toolbar at the end of the array diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index e1f4f6b333..a45310555e 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -135,7 +135,7 @@ def setup_gl_widget( trail_layer = gl_trail_layer.GLTrailLayer("Trail", visualization_buffer_size) draw_obstacle_layer = GLDrawPolygonObstacleLayer( - "Freehand Obstalce Layer", full_system_proto_unix_io + "Freehand Obstacle Layer", full_system_proto_unix_io ) gl_widget.add_layer(draw_obstacle_layer, False) From 491072b1f301734294253a15de6ad644f288f44f Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Mon, 30 Dec 2024 10:57:06 -0500 Subject: [PATCH 35/48] removed a todo --- src/software/thunderscope/gl/widgets/gl_shift_toolbar.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py index 83ad884df8..653da6c878 100644 --- a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py @@ -18,7 +18,6 @@ def __init__(self, parent: QWidget) -> None: super().__init__(parent) self.menu = QMenu() self.push_button = QPushButton() - # TODO: make this a static method! self.push_button.setStyleSheet(GLToolbar.get_button_style()) self.push_button.setMenu(self.menu) From c69cdbe9c97c6eb1dd284e84158843fec1d2376e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:06:24 +0000 Subject: [PATCH 36/48] [pre-commit.ci lite] apply automatic fixes --- .../thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index b147289698..fa5e97aad3 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -17,7 +17,8 @@ class GLDrawPolygonObstacleLayer(GLLayer): """A layer used to draw polygons that are going to represent obstacles for the trajectory planner to avoid. """ - DOUBLE_CLICK_INTERVAL = 200 + + DOUBLE_CLICK_INTERVAL = 200 def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: """Initialize this layer @@ -159,9 +160,10 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: else: self.can_double_click = True # handle single click - QTimer.singleShot(self.DOUBLE_CLICK_INTERVAL, self.create_single_click_callback(event)) + QTimer.singleShot( + self.DOUBLE_CLICK_INTERVAL, self.create_single_click_callback(event) + ) def refresh_graphics(self) -> None: - """refreshing graphics""" + """Refreshing graphics""" return - From a802a8de9ae284d5ee2853f8e340547ac1c1cc09 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Tue, 18 Feb 2025 16:48:59 -0800 Subject: [PATCH 37/48] fixed build? --- src/software/thunderscope/gl/layers/BUILD | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/software/thunderscope/gl/layers/BUILD b/src/software/thunderscope/gl/layers/BUILD index fa6b76bb82..367961fc85 100644 --- a/src/software/thunderscope/gl/layers/BUILD +++ b/src/software/thunderscope/gl/layers/BUILD @@ -169,6 +169,17 @@ py_library( ], ) +py_library( + name = "gl_draw_polygon_obstacle", + srcs = ["gl_draw_polygon_obstacle.py"], + deps = [ + ":gl_layer", + "//software/thunderscope:util", + "//software/thunderscope/gl/graphics:gl_polygon", + requirement("pyqtgraph"), + ], +) + py_library( name = "gl_referee_info_layer", srcs = ["gl_referee_info_layer.py"], From 66d0f15c717deb6909ddae9af24b5e088b595169 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 1 Mar 2025 16:58:43 -0800 Subject: [PATCH 38/48] remove all toolbar related code --- src/software/thunderscope/gl/BUILD | 4 +- src/software/thunderscope/gl/gl_widget.py | 24 +---- .../thunderscope/gl/layers/gl_world_layer.py | 34 +++----- src/software/thunderscope/gl/widgets/BUILD | 18 ---- .../gl/widgets/gl_field_toolbar.py | 87 +++---------------- .../gl/widgets/gl_gamecontroller_toolbar.py | 6 +- .../gl/widgets/gl_shift_toolbar.py | 49 ----------- .../thunderscope/gl/widgets/gl_toolbar.py | 3 +- .../thunderscope/widget_setup_functions.py | 5 -- 9 files changed, 31 insertions(+), 199 deletions(-) delete mode 100644 src/software/thunderscope/gl/widgets/gl_shift_toolbar.py diff --git a/src/software/thunderscope/gl/BUILD b/src/software/thunderscope/gl/BUILD index 77deb76f54..e7e428c214 100644 --- a/src/software/thunderscope/gl/BUILD +++ b/src/software/thunderscope/gl/BUILD @@ -10,11 +10,9 @@ py_library( "//software/thunderscope/common:toast_msg_helper", "//software/thunderscope/gl/helpers:extended_gl_view_widget", "//software/thunderscope/gl/layers:gl_layer", - "//software/thunderscope/gl/layers:gl_measure_layer", "//software/thunderscope/gl/widgets:gl_field_toolbar", + "//software/thunderscope/gl/layers:gl_measure_layer", "//software/thunderscope/gl/widgets:gl_gamecontroller_toolbar", - "//software/thunderscope/gl/widgets:gl_multilayer_toolbar", - "//software/thunderscope/gl/widgets:gl_shift_toolbar", "//software/thunderscope/replay:replay_controls", requirement("pyqtgraph"), ], diff --git a/src/software/thunderscope/gl/gl_widget.py b/src/software/thunderscope/gl/gl_widget.py index c93af17e03..4c9cbcdef9 100644 --- a/src/software/thunderscope/gl/gl_widget.py +++ b/src/software/thunderscope/gl/gl_widget.py @@ -11,13 +11,10 @@ from software.thunderscope.common.frametime_counter import FrameTimeCounter from software.thunderscope.constants import * - -from software.thunderscope.gl.widgets.gl_multilayer_toolbar import MultilayerToolbar -from software.thunderscope.gl.widgets.gl_shift_toolbar import ShiftButtonToolbar from software.thunderscope.proto_unix_io import ProtoUnixIO from software.thunderscope.gl.layers.gl_layer import GLLayer from software.thunderscope.gl.layers.gl_measure_layer import GLMeasureLayer -from software.thunderscope.gl.widgets.gl_field_toolbar import GLFieldToolbar, MultiLayerToolbar +from software.thunderscope.gl.widgets.gl_field_toolbar import GLFieldToolbar from software.thunderscope.replay.proto_player import ProtoPlayer from software.thunderscope.replay.replay_controls import ReplayControls from software.thunderscope.gl.helpers.extended_gl_view_widget import * @@ -90,13 +87,6 @@ def __init__( self.setLayout(self.layout) self.layout.addWidget(self.gl_view_widget) - # setup multi layer toolbar - self.multi_layer_toolbar = MultilayerToolbar( - parent=self.gl_view_widget, toolbars=[] - ) - - self.shift_button_toolbar = ShiftButtonToolbar(parent=self.multi_layer_toolbar) - # Setup toolbar self.measure_mode_enabled = False self.measure_layer = None @@ -104,9 +94,8 @@ def __init__( self.toolbars_menu = QMenu() self.layers_menu_actions = {} - self.simulation_control_toolbar = GLFieldToolbar( - parent=self.multi_layer_toolbar, + parent=self.gl_view_widget, on_camera_view_change=self.set_camera_view, on_measure_mode=self.toggle_measure_mode, layers_menu=self.layers_menu, @@ -116,11 +105,6 @@ def __init__( on_add_bookmark=self.add_bookmark, ) - - layers = [self.simulation_control_toolbar] - self.multilayer_toolbar = MultiLayerToolbar(self.gl_view_widget, layers) - self.multi_layer_toolbar.add_toolbar(self.simulation_control_toolbar) - # Setup gamecontroller toolbar self.gamecontroller_toolbar = GLGamecontrollerToolbar( parent=self.gl_view_widget, @@ -147,10 +131,6 @@ def __init__( self.set_camera_view(CameraView.LANDSCAPE_HIGH_ANGLE) self.proto_unix_io = proto_unix_io - def get_shift_button_toolbar(self): - """Get the ShiftButtonToolbar""" - return self.shift_button_toolbar - 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_world_layer.py b/src/software/thunderscope/gl/layers/gl_world_layer.py index d63b8915b9..10c0284dc5 100644 --- a/src/software/thunderscope/gl/layers/gl_world_layer.py +++ b/src/software/thunderscope/gl/layers/gl_world_layer.py @@ -145,15 +145,6 @@ def __init__( self.auto_chip_graphics = ObservableList(self._graphics_changed) self.speed_line_graphics = ObservableList(self._graphics_changed) - self.should_move_ball = False - - def should_move_ball_slot(self, should_move_ball): - """Set the ball movement behavior - - :param should_move_ball: whether or not shift click would move the ball - """ - self.should_move_ball = should_move_ball - def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: """Detect when a key has been pressed @@ -242,22 +233,21 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: if not event.mouse_event.modifiers() == Qt.KeyboardModifier.ShiftModifier: return - if self.should_move_ball: - self.point_in_scene_picked = ( - self._invert_position_if_defending_negative_half(event.point_in_scene) - ) + self.point_in_scene_picked = ( + self._invert_position_if_defending_negative_half(event.point_in_scene) + ) - # Send a command to the simulator to move the ball to the picked point - world_state = WorldState() - world_state.ball_state.CopyFrom( - BallState( - global_position=Point( - x_meters=self.point_in_scene_picked.x(), - y_meters=self.point_in_scene_picked.y(), - ) + # Send a command to the simulator to move the ball to the picked point + world_state = WorldState() + world_state.ball_state.CopyFrom( + BallState( + global_position=Point( + x_meters=self.point_in_scene_picked.x(), + y_meters=self.point_in_scene_picked.y(), ) ) - self.simulator_io.send_proto(WorldState, world_state) + ) + self.simulator_io.send_proto(WorldState, world_state) def mouse_in_scene_dragged(self, event: MouseInSceneEvent) -> None: """Detect that the mouse was dragged within the 3D scene diff --git a/src/software/thunderscope/gl/widgets/BUILD b/src/software/thunderscope/gl/widgets/BUILD index 50218d4e24..4c4d720119 100644 --- a/src/software/thunderscope/gl/widgets/BUILD +++ b/src/software/thunderscope/gl/widgets/BUILD @@ -42,21 +42,3 @@ py_library( requirement("pyqtgraph"), ], ) - -py_library( - name = "gl_multilayer_toolbar", - srcs = ["gl_multilayer_toolbar.py"], - deps = [ - ":gl_toolbar", - requirement("pyqtgraph"), - ], -) - -py_library( - name = "gl_shift_toolbar", - srcs = ["gl_shift_toolbar.py"], - deps = [ - ":gl_toolbar", - requirement("pyqtgraph"), - ], -) diff --git a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py index 0c6d85092b..1875d18a40 100644 --- a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py @@ -60,14 +60,14 @@ def __init__( # Setup Layers button for toggling visibility of layers self.layers_button = QPushButton() self.layers_button.setText("Layers") - self.layers_button.setStyleSheet(GLToolbar.get_button_style()) + self.layers_button.setStyleSheet(self.get_button_style()) self.layers_button.setMenu(layers_menu) # Set up View button for setting the camera position to standard views self.camera_view_button = QPushButton() self.camera_view_button.setToolTip("View") self.camera_view_button.setIcon(icons.get_view_icon(self.BUTTON_ICON_COLOR)) - self.camera_view_button.setStyleSheet(GLToolbar.get_button_style()) + self.camera_view_button.setStyleSheet(self.get_button_style()) self.camera_view_menu = QMenu() self.camera_view_button.setMenu(self.camera_view_menu) self.camera_view_actions = [ @@ -95,7 +95,7 @@ def __init__( self.measure_button = QPushButton() self.measure_button.setToolTip("Measure") self.measure_button.setIcon(icons.get_measure_icon(self.BUTTON_ICON_COLOR)) - self.measure_button.setStyleSheet(GLToolbar.get_button_style()) + self.measure_button.setStyleSheet(self.get_button_style()) self.measure_button.setShortcut("m") self.measure_button.clicked.connect(lambda: on_measure_mode()) @@ -103,14 +103,14 @@ def __init__( self.help_button = QPushButton() self.help_button.setToolTip("Help") self.help_button.setIcon(icons.get_help_icon(self.BUTTON_ICON_COLOR)) - self.help_button.setStyleSheet(GLToolbar.get_button_style()) + self.help_button.setStyleSheet(self.get_button_style()) self.help_button.clicked.connect( lambda: QMessageBox.information(self, "Help", THUNDERSCOPE_HELP_TEXT) ) # Setup pause button self.pause_button = QPushButton() - self.pause_button.setStyleSheet(GLToolbar.get_button_style()) + self.pause_button.setStyleSheet(self.get_button_style()) self.toggle_pause_button(True) # buffer for the simulator pause / play state self.simulation_state_buffer = ThreadSafeBuffer(5, SimulationState) @@ -121,7 +121,7 @@ def __init__( self.toolbars_menu = QMenu() self.toolbars_menu_checkboxes = {} self.toolbars_button.setMenu(toolbars_menu) - self.toolbars_button.setStyleSheet(GLToolbar.get_button_style()) + self.toolbars_button.setStyleSheet(self.get_button_style()) if not replay_mode: self.bookmark_button = QPushButton() @@ -136,7 +136,7 @@ def __init__( self.sim_speed_menu = QMenu() self.sim_speed_button = QPushButton() self.sim_speed_button.setText("Speed: 1.00x") - self.sim_speed_button.setStyleSheet(GLToolbar.get_button_style()) + self.sim_speed_button.setStyleSheet(self.get_button_style()) self.sim_speed_button.setMenu(self.sim_speed_menu) self.sim_speed_button.setToolTip("Simulation Speed") @@ -156,18 +156,18 @@ def __init__( self.undo_button = ToggleableButton(False) self.undo_button.setToolTip("Undo") self.undo_button.setIcon(icons.get_undo_icon(self.BUTTON_ICON_COLOR)) - self.undo_button.setStyleSheet(GLToolbar.get_button_style(False)) + self.undo_button.setStyleSheet(self.get_button_style(False)) # Setup Redo button self.redo_button = ToggleableButton(False) self.redo_button.setToolTip("Redo") self.redo_button.setIcon(icons.get_redo_icon(self.BUTTON_ICON_COLOR)) - self.redo_button.setStyleSheet(GLToolbar.get_button_style(False)) + self.redo_button.setStyleSheet(self.get_button_style(False)) self.reset_button = QPushButton() self.reset_button.setToolTip("Reset") self.reset_button.setIcon(icons.get_reset_icon(self.BUTTON_ICON_COLOR)) - self.reset_button.setStyleSheet(GLToolbar.get_button_style()) + self.reset_button.setStyleSheet(self.get_button_style()) # Setup toolbar self.layout().addWidget(self.layers_button) @@ -222,7 +222,7 @@ def toggle_undo_enabled(self, enabled: bool) -> None: :param enabled: if the undo button is enabled or not """ self.undo_button.toggle_enabled(enabled) - self.undo_button.setStyleSheet(GLToolbar.get_button_style(enabled)) + self.undo_button.setStyleSheet(self.get_button_style(enabled)) self.undo_button.repaint() def toggle_redo_enabled(self, enabled: bool) -> None: @@ -231,7 +231,7 @@ def toggle_redo_enabled(self, enabled: bool) -> None: :param enabled: if the redo button is enabled or not """ self.redo_button.toggle_enabled(enabled) - self.redo_button.setStyleSheet(GLToolbar.get_button_style(enabled)) + self.redo_button.setStyleSheet(self.get_button_style(enabled)) self.redo_button.repaint() def set_speed_callback(self, callback: Callable[[float], None]) -> None: @@ -241,66 +241,3 @@ def set_speed_callback(self, callback: Callable[[float], None]) -> None: """ self.speed_callback = callback - -class RandomToolbar(GLToolbar): - def __init__(self, parent): - super(RandomToolbar, self).__init__(parent=parent) - - self.pushbutton = QPushButton("Change button behavior") - self.menu = QMenu() - - action_one = QtGui.QAction("click behavior one", self) - action_one.triggered.connect(self.callback) - - action_two = QtGui.QAction("clik behavior two", self) - action_two.triggered.connect(self.callback) - - self.menu.addAction(action_one) - self.menu.addAction(action_two) - self.pushbutton.setStyleSheet(self.get_button_style()) - self.pushbutton.setMenu(self.menu) - - self.layout().addWidget(self.pushbutton) - - def callback(self): - print("I just did something") - - -# TODO: move this into a different layer in the future. -""" -This would be mvoed into a different class later on! -""" -class MultiLayerToolbar(QWidget): - def __init__(self, parent, layers) -> None: - super(MultiLayerToolbar, self).__init__(parent=parent) - - self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) - self.setStyleSheet("background-color: rgba(0,0,0,0);" "padding: 0px;") - self.setAttribute(QtCore.Qt.WidgetAttribute.WA_StyledBackground) - self.layout = QHBoxLayout() - - self.layers : List[GLToolbar] = layers - self.layers.append(RandomToolbar(parent)) - - self.shortcut = QShortcut(Qt.Key.Key_F1, self) - self.shortcut.activated.connect(self.switch_layer) - - QMenu() - self.shortcut = QShortcut(Qt.Key.Key_F2, self) - self.shortcut.activated.connect(self.switch_layer_two) - - self.setLayout(self.layout) - - self.layout.addWidget(self.layers[0]) - self.layout.addWidget(self.layers[1]) - - self.layers[1].hide() - - def switch_layer(self): - self.layers[1].hide() - self.layers[0].show() - - def switch_layer_two(self): - self.layers[0].hide() - self.layers[1].show() - diff --git a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py index e950f46a0a..dd76ed0664 100644 --- a/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_gamecontroller_toolbar.py @@ -80,7 +80,7 @@ def __init__( self.plays_menu_button = QPushButton() self.plays_menu_button.setText("Plays") - self.plays_menu_button.setStyleSheet(GLToolbar.get_button_style()) + self.plays_menu_button.setStyleSheet(self.get_button_style()) self.plays_menu_button.setMenu(self.plays_menu) # add play items for each team color @@ -175,7 +175,7 @@ def __toggle_normal_start_button(self) -> None: """Toggles the enabled / disabled state of the Normal Start button""" self.normal_start_enabled = not self.normal_start_enabled self.normal_start_button.setStyleSheet( - GLToolbar.get_button_style(self.normal_start_enabled) + self.get_button_style(self.normal_start_enabled) ) self.normal_start_button.setIcon( icons.get_normal_start_icon( @@ -203,7 +203,7 @@ def __setup_icon_button( button = QPushButton() button.setIcon(icon) button.setToolTip(tooltip) - button.setStyleSheet(GLToolbar.get_button_style()) + button.setStyleSheet(self.get_button_style()) button.clicked.connect(callback) if display_text: diff --git a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py b/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py deleted file mode 100644 index 653da6c878..0000000000 --- a/src/software/thunderscope/gl/widgets/gl_shift_toolbar.py +++ /dev/null @@ -1,49 +0,0 @@ -from pyqtgraph.Qt import QtGui -from pyqtgraph.Qt.QtCore import pyqtSignal -from pyqtgraph.Qt.QtWidgets import * -from proto.import_all_protos import * -from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar - - -class ShiftButtonToolbar(GLToolbar): - """A shift button toolbar that is going to be used to enable/disable shift click ball placement in Thunderscope""" - - enable_ball_placement_signal = pyqtSignal(bool) - - def __init__(self, parent: QWidget) -> None: - """Initialize the ShiftButtonToolbar - - :param parent: the parent of this widget - """ - super().__init__(parent) - self.menu = QMenu() - self.push_button = QPushButton() - self.push_button.setStyleSheet(GLToolbar.get_button_style()) - self.push_button.setMenu(self.menu) - - self.actions = [ - QtGui.QAction("[1] Enable Shift Click Ball Move"), - QtGui.QAction("[2] Disable Shift Click Ball Move"), - ] - - self.actions[0].triggered.connect(self.enable_ball_placement) - self.actions[1].triggered.connect(self.disable_ball_placement) - - for action in self.actions: - self.menu.addAction(action) - - self.layout().addWidget(self.push_button) - - # enable ball by default, just in case - self.enable_ball_placement_signal.emit(True) - self.push_button.setText("[1] Enable Shift Click Ball Move") - - def disable_ball_placement(self): - """Disable shift click ball placement""" - self.push_button.setText("[2] Disable Shift Click Ball Move") - self.enable_ball_placement_signal.emit(False) - - def enable_ball_placement(self): - """Enable shift click ball placement""" - self.push_button.setText("[1] Enable Shift Click Ball Move") - self.enable_ball_placement_signal.emit(True) diff --git a/src/software/thunderscope/gl/widgets/gl_toolbar.py b/src/software/thunderscope/gl/widgets/gl_toolbar.py index 23dcb510d3..06963ae8da 100644 --- a/src/software/thunderscope/gl/widgets/gl_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_toolbar.py @@ -30,8 +30,7 @@ def refresh(self) -> None: """Refreshes the UI (overridden by child classes)""" raise NotImplementedError("Subclasses must implement this method!") - @staticmethod - def get_button_style(is_enabled: bool = True) -> str: + def get_button_style(self, is_enabled: bool = True) -> str: """Returns the stylesheet for a QPushButton based on if it's enabled or not :param is_enabled: True if button is enabled, False if not diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 04d7681ad3..594faa04df 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -160,11 +160,6 @@ def setup_gl_widget( simulation_control_toolbar = gl_widget.get_sim_control_toolbar() simulation_control_toolbar.set_speed_callback(world_layer.set_simulation_speed) - shift_button_toolbar = gl_widget.get_shift_button_toolbar() - shift_button_toolbar.enable_ball_placement_signal.connect( - world_layer.should_move_ball_slot - ) - # connect all sandbox controls if using sandbox mode if sandbox_mode: simulation_control_toolbar.undo_button.clicked.connect(world_layer.undo) From d4f3eb1c63d5a340ad4cf2facce18b089cfd247c Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Sat, 1 Mar 2025 17:04:56 -0800 Subject: [PATCH 39/48] update docs --- src/software/thunderscope/constants.py | 3 ++- .../thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 04f0308908..76ba2c0495 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -183,7 +183,8 @@ class EstopMode(IntEnum): Ctrl + R: Remove the current layout file and reset the layout

Layout file (on save) is located at {SAVED_LAYOUT_PATH}
- Shift+Double Click Push the current obstacle to the obstacle stack. FriendlyObstacleLayer must be enabled.

+ Shift+Alt+Double Click Push the current obstacle to the obstacle stack. FriendlyObstacleLayer must be enabled.

+ Shift+Alt+Single Click Append a point to the current virtual obstacle.

c Clear the virtual obstacle stack FriendlyObstacleLayer must be enabled.

diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index fa5e97aad3..6ba7e20dd4 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -150,6 +150,10 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: """ if not self.visible(): return + + if not event.mouse_event.modifiers() == Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.AltModifier: + return + super().mouse_in_scene_pressed(event) # handle double click From a17645b4a3567bdb06757dcaacfcb6dd1970f109 Mon Sep 17 00:00:00 2001 From: williamckha Date: Sat, 8 Mar 2025 15:47:26 -0800 Subject: [PATCH 40/48] Formatting --- .../ai/hl/stp/tactic/move_primitive.cpp | 2 +- .../gl/layers/gl_draw_polygon_obstacle.py | 22 ++++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/software/ai/hl/stp/tactic/move_primitive.cpp b/src/software/ai/hl/stp/tactic/move_primitive.cpp index 9140896023..41028248f7 100644 --- a/src/software/ai/hl/stp/tactic/move_primitive.cpp +++ b/src/software/ai/hl/stp/tactic/move_primitive.cpp @@ -187,7 +187,7 @@ void MovePrimitive::updateObstacles( obstacle_factory.createObstaclesFromMotionConstraints(motion_constraints, world); obstacles = field_obstacles; - // adding virtual obstalces + // Adding virtual obstacles auto virtual_obstacles = world.getVirtualObstacles().obstacles(); for (TbotsProto::Obstacle &obstacle : virtual_obstacles) { diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 6ba7e20dd4..f4b94d8848 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -14,8 +14,8 @@ class GLDrawPolygonObstacleLayer(GLLayer): - """A layer used to draw polygons that are going to represent obstacles for the trajectory planner - to avoid. + """A layer used to draw polygons representing virtual obstacles for the + trajectory planner to avoid. """ DOUBLE_CLICK_INTERVAL = 200 @@ -28,18 +28,17 @@ def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: """ super().__init__(name) - self.friendly_io: ProtoUnixIO = friendly_io + self.friendly_io = friendly_io - self.current_polygon: GLPolygon = GLPolygon(parent_item=self, line_width=2) - # Tuple[float, float] represents a point (x,y) + self.current_polygon = GLPolygon(parent_item=self, line_width=2) self.points: List[Tuple[float, float]] = [] self.obstacles: List[Obstacle] = [] # used for keeping track and rendering multiple polygons - self.rendering_polygons: ObservableList = ObservableList(self._graphics_changed) + self.rendering_polygons = ObservableList(self._graphics_changed) - self.can_double_click: bool = True + self.can_double_click = True def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: """Responding to key events that are going to push obstacles to the stack or add point @@ -59,7 +58,7 @@ def clear_polygons(self): for polygon in self.rendering_polygons: polygon.hide() - self.rendering_polygons.resize(0, lambda: {}) + self.rendering_polygons.clear() self.current_polygon.hide() self.current_polygon = GLPolygon(parent_item=self, line_width=2) @@ -133,8 +132,8 @@ def create_single_click_callback(self, event: MouseInSceneEvent): def _handle_single_click(): # This logic is somewhat non trivial. If we `can_double_click`, it indicates that # a double-click hasn't occurred within the 200 ms time window after the first click. - # In other words, the user hasn't double-clicked, - # so we will now interpret the action as a single click. + # In other words, the user hasn't double-clicked, so we will now interpret the action + # as a single click. if self.can_double_click: point = event.point_in_scene self._add_one_point((point.x(), point.y())) @@ -154,13 +153,10 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: if not event.mouse_event.modifiers() == Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.AltModifier: return - super().mouse_in_scene_pressed(event) - # handle double click if self.can_double_click: self.push_polygon_to_list() self.can_double_click = False - return else: self.can_double_click = True # handle single click From 01a5bd10c6a9290dfca78c83ae927d69b683ad14 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, 8 Mar 2025 23:57:01 +0000 Subject: [PATCH 41/48] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/gl/BUILD | 2 +- .../gl/layers/gl_draw_polygon_obstacle.py | 11 +++++++---- src/software/thunderscope/gl/layers/gl_world_layer.py | 4 ++-- .../thunderscope/gl/widgets/gl_field_toolbar.py | 7 +------ src/software/world/world.cpp | 5 +++-- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/software/thunderscope/gl/BUILD b/src/software/thunderscope/gl/BUILD index e7e428c214..747eb33342 100644 --- a/src/software/thunderscope/gl/BUILD +++ b/src/software/thunderscope/gl/BUILD @@ -10,8 +10,8 @@ py_library( "//software/thunderscope/common:toast_msg_helper", "//software/thunderscope/gl/helpers:extended_gl_view_widget", "//software/thunderscope/gl/layers:gl_layer", - "//software/thunderscope/gl/widgets:gl_field_toolbar", "//software/thunderscope/gl/layers:gl_measure_layer", + "//software/thunderscope/gl/widgets:gl_field_toolbar", "//software/thunderscope/gl/widgets:gl_gamecontroller_toolbar", "//software/thunderscope/replay:replay_controls", requirement("pyqtgraph"), diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index f4b94d8848..028b5f0b3c 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -14,7 +14,7 @@ class GLDrawPolygonObstacleLayer(GLLayer): - """A layer used to draw polygons representing virtual obstacles for the + """A layer used to draw polygons representing virtual obstacles for the trajectory planner to avoid. """ @@ -132,7 +132,7 @@ def create_single_click_callback(self, event: MouseInSceneEvent): def _handle_single_click(): # This logic is somewhat non trivial. If we `can_double_click`, it indicates that # a double-click hasn't occurred within the 200 ms time window after the first click. - # In other words, the user hasn't double-clicked, so we will now interpret the action + # In other words, the user hasn't double-clicked, so we will now interpret the action # as a single click. if self.can_double_click: point = event.point_in_scene @@ -149,8 +149,11 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: """ if not self.visible(): return - - if not event.mouse_event.modifiers() == Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.AltModifier: + + if ( + not event.mouse_event.modifiers() + == Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.AltModifier + ): return # handle double click diff --git a/src/software/thunderscope/gl/layers/gl_world_layer.py b/src/software/thunderscope/gl/layers/gl_world_layer.py index 10c0284dc5..d1bda3c325 100644 --- a/src/software/thunderscope/gl/layers/gl_world_layer.py +++ b/src/software/thunderscope/gl/layers/gl_world_layer.py @@ -233,8 +233,8 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: if not event.mouse_event.modifiers() == Qt.KeyboardModifier.ShiftModifier: return - self.point_in_scene_picked = ( - self._invert_position_if_defending_negative_half(event.point_in_scene) + self.point_in_scene_picked = self._invert_position_if_defending_negative_half( + event.point_in_scene ) # Send a command to the simulator to move the ball to the picked point diff --git a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py index 1875d18a40..4ae58ef770 100644 --- a/src/software/thunderscope/gl/widgets/gl_field_toolbar.py +++ b/src/software/thunderscope/gl/widgets/gl_field_toolbar.py @@ -1,8 +1,4 @@ -from typing import Callable, List -from PyQt6.QtCore import Qt -from numpy import who -from pyqtgraph.Qt import QtCore -from PyQt6.QtGui import QShortcut +from typing import Callable from pyqtgraph.Qt import QtGui from pyqtgraph.Qt.QtWidgets import * from proto.import_all_protos import * @@ -240,4 +236,3 @@ def set_speed_callback(self, callback: Callable[[float], None]) -> None: :param callback: the callback function to update the simulation speed """ self.speed_callback = callback - diff --git a/src/software/world/world.cpp b/src/software/world/world.cpp index 81b52f473d..c37a1ac6ef 100644 --- a/src/software/world/world.cpp +++ b/src/software/world/world.cpp @@ -85,8 +85,9 @@ void World::updateRefereeCommand(const RefereeCommand &command) // Take the consensus of the previous referee messages if (!referee_command_history_.empty() && std::all_of(referee_command_history_.begin(), referee_command_history_.end(), - [&](auto game_state) - { return game_state == referee_command_history_.front(); })) + [&](auto game_state) { + return game_state == referee_command_history_.front(); + })) { current_game_state_.updateRefereeCommand(command); } From bcae750563fe47720199fd9adf4c8a9c200fb398 Mon Sep 17 00:00:00 2001 From: Vincent <53135664+Mr-Anyone@users.noreply.github.com> Date: Mon, 10 Mar 2025 23:05:37 -0700 Subject: [PATCH 42/48] Update src/software/world/world.h Co-authored-by: William Ha <60044853+williamckha@users.noreply.github.com> --- src/software/world/world.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/world/world.h b/src/software/world/world.h index 0ab62a388a..2e7200761a 100644 --- a/src/software/world/world.h +++ b/src/software/world/world.h @@ -228,7 +228,7 @@ class World final /** - * A a list of virtual obstacles + * Set the list of virtual obstacles * * @param virtual_obstacles a list of the virtual_obstacles */ From 8a4c9fd674593e51e222fba6487c1a6437cd80bb Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Wed, 12 Mar 2025 01:10:09 -0700 Subject: [PATCH 43/48] hopefully this fixed it? --- src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 028b5f0b3c..2e9a1c2e68 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -60,6 +60,7 @@ def clear_polygons(self): polygon.hide() self.rendering_polygons.clear() self.current_polygon.hide() + self.current_polygon.setParent(None) self.current_polygon = GLPolygon(parent_item=self, line_width=2) self._send_to_fs() From c8fd692af9927e5e70ed6a59ac8918a95bbd4188 Mon Sep 17 00:00:00 2001 From: williamckha Date: Sat, 15 Mar 2025 15:56:01 -0700 Subject: [PATCH 44/48] Refactored GLDrawPolygonObstacleLayer and fixed GLPolygon not rendering outline correctly --- .../thunderscope/gl/graphics/gl_polygon.py | 2 +- .../gl/layers/gl_draw_polygon_obstacle.py | 81 +++++++------------ 2 files changed, 29 insertions(+), 54 deletions(-) diff --git a/src/software/thunderscope/gl/graphics/gl_polygon.py b/src/software/thunderscope/gl/graphics/gl_polygon.py index 7a2241a3c8..e70c0663df 100644 --- a/src/software/thunderscope/gl/graphics/gl_polygon.py +++ b/src/software/thunderscope/gl/graphics/gl_polygon.py @@ -61,7 +61,7 @@ def _update_shape_data(self) -> None: if not self.points: return - vertices = [(point[0], point[1], 0) for point in self.points] + vertices = [(point[0], point[1], 0) for point in self.points + [self.points[0]]] self.setData(pos=vertices) if self.fill_graphic: diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index 2e9a1c2e68..ddb067fb0e 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -12,6 +12,7 @@ from software.thunderscope.gl.layers.gl_layer import GLLayer from software.thunderscope.gl.helpers.extended_gl_view_widget import MouseInSceneEvent +from typing import Callable class GLDrawPolygonObstacleLayer(GLLayer): """A layer used to draw polygons representing virtual obstacles for the @@ -30,14 +31,15 @@ def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: self.friendly_io = friendly_io - self.current_polygon = GLPolygon(parent_item=self, line_width=2) - self.points: List[Tuple[float, float]] = [] - + self.points: List[Point] = [] self.obstacles: List[Obstacle] = [] - # used for keeping track and rendering multiple polygons + # Stores the polygons that are currently visible self.rendering_polygons = ObservableList(self._graphics_changed) + # The current polygon being edited (not visible yet) + self.current_polygon = GLPolygon(parent_item=self, line_width=2) + self.can_double_click = True def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: @@ -49,74 +51,46 @@ def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: return if event.key() == Qt.Key.Key_C: - self.clear_polygons() + self.__clear_polygons() - def clear_polygons(self): + def __clear_polygons(self) -> None: """Clearing the obstacles""" self.points.clear() self.obstacles.clear() - for polygon in self.rendering_polygons: - polygon.hide() self.rendering_polygons.clear() - self.current_polygon.hide() - self.current_polygon.setParent(None) - self.current_polygon = GLPolygon(parent_item=self, line_width=2) - - self._send_to_fs() + self.current_polygon.setParentItem(None) + + self.__send_to_fullsystem() - def push_polygon_to_list(self): + def __push_polygon_to_list(self): """Pushing the fully drawn polygon to the stack""" - points = [ - Point(x_meters=point[0], y_meters=point[1]) for point in self.points[:-1] - ] - - if len(points) <= 2: + if len(self.points) <= 2: print("Cannot push polygon to stack as there are less than two points.") return - polygon = Polygon(points=points) - obstacle = Obstacle(polygon=polygon) - self.obstacles.append(obstacle) + self.obstacles.append(Obstacle(polygon=Polygon(points=self.points.copy()))) self.points.clear() self.rendering_polygons.append(self.current_polygon) self.current_polygon = GLPolygon(parent_item=self, line_width=2) - def _add_one_point(self, point: tuple[float, float]): + def __add_one_point(self, point: Point) -> None: """Adding one points to a polygon :param point: represent the point (x,y) that is added to the polygon """ - if len(self.points) < 2: - # creating a line segment - self.points.append(point) - elif len(self.points) == 2: - # creating a triangle - start_point = self.points[0] - self.points.append(point) - self.points.append(start_point) - else: - # creating a general polygon - start_point = self.points[0] - self.points.pop() # removing the start point since the last point is always the start point + self.points.append(point) + self.current_polygon.set_points([(point.x_meters, point.y_meters) for point in self.points]) + self.__send_to_fullsystem() - self.points.append(point) - self.points.append(start_point) - self.current_polygon.set_points(self.points) - self._send_to_fs() - - def _send_to_fs(self): + def __send_to_fullsystem(self) -> None: """Sending a list of virtual obstacles to full system""" obstacles = self.obstacles.copy() - points = [ - Point(x_meters=point[0], y_meters=point[1]) for point in self.points[:-1] - ] - # only send to full system when the points form a valid polygon - if len(points) >= 3: - polygon = Polygon(points=points) + if len(self.points) >= 3: + polygon = Polygon(points=self.points.copy()) obstacle = Obstacle(polygon=polygon) obstacles.append(obstacle) @@ -124,24 +98,25 @@ def _send_to_fs(self): VirtualObstacles, VirtualObstacles(obstacles=obstacles) ) - def create_single_click_callback(self, event: MouseInSceneEvent): + def __create_single_click_callback(self, event: MouseInSceneEvent) -> Callable[[], None]: """Creating a single shot callback to handle single click :param event: The mouse event when a scene is pressed + :return: callback that handles a single click """ - def _handle_single_click(): + def __handle_single_click(): # This logic is somewhat non trivial. If we `can_double_click`, it indicates that # a double-click hasn't occurred within the 200 ms time window after the first click. # In other words, the user hasn't double-clicked, so we will now interpret the action # as a single click. if self.can_double_click: point = event.point_in_scene - self._add_one_point((point.x(), point.y())) + self.__add_one_point(Point(x_meters=point.x(), y_meters=point.y())) self.can_double_click = False - return _handle_single_click + return __handle_single_click def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: """Adding the point in scene @@ -159,13 +134,13 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: # handle double click if self.can_double_click: - self.push_polygon_to_list() + self.__push_polygon_to_list() self.can_double_click = False else: self.can_double_click = True # handle single click QTimer.singleShot( - self.DOUBLE_CLICK_INTERVAL, self.create_single_click_callback(event) + self.DOUBLE_CLICK_INTERVAL, self.__create_single_click_callback(event) ) def refresh_graphics(self) -> None: From 35cd83d73725a69f23ce43e0373f33390cb69599 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, 15 Mar 2025 23:06:31 +0000 Subject: [PATCH 45/48] [pre-commit.ci lite] apply automatic fixes --- .../gl/layers/gl_draw_polygon_obstacle.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index ddb067fb0e..b593c4e83f 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -14,6 +14,7 @@ from typing import Callable + class GLDrawPolygonObstacleLayer(GLLayer): """A layer used to draw polygons representing virtual obstacles for the trajectory planner to avoid. @@ -39,7 +40,7 @@ def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: # The current polygon being edited (not visible yet) self.current_polygon = GLPolygon(parent_item=self, line_width=2) - + self.can_double_click = True def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: @@ -60,7 +61,7 @@ def __clear_polygons(self) -> None: self.rendering_polygons.clear() self.current_polygon.setParentItem(None) - + self.__send_to_fullsystem() def __push_polygon_to_list(self): @@ -81,7 +82,9 @@ def __add_one_point(self, point: Point) -> None: :param point: represent the point (x,y) that is added to the polygon """ self.points.append(point) - self.current_polygon.set_points([(point.x_meters, point.y_meters) for point in self.points]) + self.current_polygon.set_points( + [(point.x_meters, point.y_meters) for point in self.points] + ) self.__send_to_fullsystem() def __send_to_fullsystem(self) -> None: @@ -98,7 +101,9 @@ def __send_to_fullsystem(self) -> None: VirtualObstacles, VirtualObstacles(obstacles=obstacles) ) - def __create_single_click_callback(self, event: MouseInSceneEvent) -> Callable[[], None]: + def __create_single_click_callback( + self, event: MouseInSceneEvent + ) -> Callable[[], None]: """Creating a single shot callback to handle single click :param event: The mouse event when a scene is pressed From 3c5c35b26a76d7676549005134863c257c78e15c Mon Sep 17 00:00:00 2001 From: williamckha Date: Sat, 15 Mar 2025 16:11:51 -0700 Subject: [PATCH 46/48] Fix small nitpicks --- src/software/backend/backend.h | 6 +- src/software/thunderscope/constants.py | 7 +- .../gl/layers/gl_draw_polygon_obstacle.py | 6 +- .../gl/widgets/gl_multilayer_toolbar.py | 65 ------------------- .../thunderscope/widget_setup_functions.py | 4 +- src/software/world/world.h | 1 - 6 files changed, 11 insertions(+), 78 deletions(-) delete mode 100644 src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py diff --git a/src/software/backend/backend.h b/src/software/backend/backend.h index 46d33dd684..580a2cf221 100644 --- a/src/software/backend/backend.h +++ b/src/software/backend/backend.h @@ -36,10 +36,10 @@ class Backend : public Subject, void receiveSensorProto(SensorProto sensor_msg); /** - * Callback function that receive a list of new virtual obstacles from - * Subject + * Callback function that receives a list of new virtual obstacles to send + * to Observers * - * @param new_obstacles_list a new obstacles list we are having + * @param new_obstacles_list the received virtual obstacles list */ void receiveObstacleList(TbotsProto::VirtualObstacles new_obstacle_list); }; diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 76ba2c0495..ea2a933fa3 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -183,10 +183,9 @@ class EstopMode(IntEnum): Ctrl + R: Remove the current layout file and reset the layout

Layout file (on save) is located at {SAVED_LAYOUT_PATH}
- Shift+Alt+Double Click Push the current obstacle to the obstacle stack. FriendlyObstacleLayer must be enabled.

- Shift+Alt+Single Click Append a point to the current virtual obstacle.

- c Clear the virtual obstacle stack FriendlyObstacleLayer must be enabled.

- + Shift+Alt+Double Click Complete the current virtual obstacle. The Draw Obstacle Layer must be enabled.

+ Shift+Alt+Single Click Add a point to the current virtual obstacle. The Draw Obstacle Layer must be enabled.

+ c Clear all virtual obstacles. The Draw Obstacle Layer must be enabled.

""" ) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index ddb067fb0e..f60deda128 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -19,7 +19,7 @@ class GLDrawPolygonObstacleLayer(GLLayer): trajectory planner to avoid. """ - DOUBLE_CLICK_INTERVAL = 200 + DOUBLE_CLICK_INTERVAL_MS = 200 def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: """Initialize this layer @@ -40,7 +40,7 @@ def __init__(self, name: str, friendly_io: ProtoUnixIO) -> None: # The current polygon being edited (not visible yet) self.current_polygon = GLPolygon(parent_item=self, line_width=2) - self.can_double_click = True + self.can_double_click = False def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: """Responding to key events that are going to push obstacles to the stack or add point @@ -140,7 +140,7 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: self.can_double_click = True # handle single click QTimer.singleShot( - self.DOUBLE_CLICK_INTERVAL, self.__create_single_click_callback(event) + self.DOUBLE_CLICK_INTERVAL_MS, self.__create_single_click_callback(event) ) def refresh_graphics(self) -> None: diff --git a/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py b/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py deleted file mode 100644 index 1cf97bbe6a..0000000000 --- a/src/software/thunderscope/gl/widgets/gl_multilayer_toolbar.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import List -from PyQt6.QtGui import QKeySequence -from pyqtgraph.Qt import QtGui -from pyqtgraph.Qt.QtWidgets import * -from proto.import_all_protos import * -from software.thunderscope.gl.widgets.gl_toolbar import GLToolbar - - -class MultilayerToolbar(QWidget): - """A widget where it allows user to easily switch between layers by pressing F keys.""" - - def __init__(self, parent: QWidget, toolbars: List[GLToolbar]): - """Initialize the MultilayerToolbar Widget - - :param parent: the parent widget of this toolbar - :param toolbars: a list of toolbars to be swapped - """ - super().__init__(parent) - - # TODO: as of current, this layer only supports two layers, but - # it can be easily extended to support multiple layers in the future - assert len(toolbars) <= 2 - - self.setLayout(QHBoxLayout()) - - self.toolbars: List[GLToolbar] = toolbars - - for toolbar in toolbars: - self.layout().addWidget(toolbar) - toolbar.hide() - - # Create a shortcut for the F1 key - self.shortcut_f1 = QtGui.QShortcut(QKeySequence("F1"), self) - self.shortcut_f1.activated.connect(lambda: self.show_toolbar(0)) - - self.shortcut_f2 = QtGui.QShortcut(QKeySequence("F2"), self) - self.shortcut_f2.activated.connect(lambda: self.show_toolbar(1)) - - def add_toolbar(self, toolbar: GLToolbar): - """Adding a toolbar to the widget. Appends the toolbar at the end of the array - - :param toolbar: the toolbar going to be added to this layer - """ - # we only support 2 toolbars as of current - assert len(self.toolbars) <= 1 - - self.toolbars.append(toolbar) - self.layout().addWidget(toolbar) - toolbar.hide() - - self.show_toolbar(0) - - def show_toolbar(self, num): - """Display a toolbar - - :param num: the index of the toolbar in the toolbars array going to be displayed - """ - # cannot show toolbar, since index is out of range - if num >= len(self.toolbars): - return - - for toolbar in self.toolbars: - toolbar.hide() - - self.toolbars[num].show() diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 594faa04df..f05f29bd72 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -139,14 +139,14 @@ def setup_gl_widget( ) draw_obstacle_layer = GLDrawPolygonObstacleLayer( - "Freehand Obstacle Layer", full_system_proto_unix_io + "Draw Obstacle Layer", full_system_proto_unix_io ) - gl_widget.add_layer(draw_obstacle_layer, False) gl_widget.add_layer(world_layer) gl_widget.add_layer(simulator_layer, False) gl_widget.add_layer(path_layer) gl_widget.add_layer(obstacle_layer) + gl_widget.add_layer(draw_obstacle_layer, False) gl_widget.add_layer(passing_layer) gl_widget.add_layer(attacker_layer) gl_widget.add_layer(cost_vis_layer, True) diff --git a/src/software/world/world.h b/src/software/world/world.h index 2e7200761a..6b18e0316f 100644 --- a/src/software/world/world.h +++ b/src/software/world/world.h @@ -226,7 +226,6 @@ class World final */ const std::optional& getDribbleDisplacement() const; - /** * Set the list of virtual obstacles * From dddbb1587009259e50e36c50145f51d5cd620f54 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 01:03:52 +0000 Subject: [PATCH 47/48] [pre-commit.ci lite] apply automatic fixes --- .../thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index a904049395..da15075f90 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -145,7 +145,8 @@ def mouse_in_scene_pressed(self, event: MouseInSceneEvent) -> None: self.can_double_click = True # handle single click QTimer.singleShot( - self.DOUBLE_CLICK_INTERVAL_MS, self.__create_single_click_callback(event) + self.DOUBLE_CLICK_INTERVAL_MS, + self.__create_single_click_callback(event), ) def refresh_graphics(self) -> None: From c75c6c11747e07e8332518bd5c8129bd63efa216 Mon Sep 17 00:00:00 2001 From: Mr-Anyone <53135664+Mr-Anyone@users.noreply.github.com> Date: Wed, 7 May 2025 19:54:51 -0400 Subject: [PATCH 48/48] address comments --- .../thunderscope/gl/layers/gl_draw_polygon_obstacle.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py index da15075f90..3e094020c2 100644 --- a/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py +++ b/src/software/thunderscope/gl/layers/gl_draw_polygon_obstacle.py @@ -89,17 +89,17 @@ def __add_one_point(self, point: Point) -> None: def __send_to_fullsystem(self) -> None: """Sending a list of virtual obstacles to full system""" - obstacles = self.obstacles.copy() - # only send to full system when the points form a valid polygon if len(self.points) >= 3: + obstacles = self.obstacles.copy() + polygon = Polygon(points=self.points.copy()) obstacle = Obstacle(polygon=polygon) obstacles.append(obstacle) - self.friendly_io.send_proto( - VirtualObstacles, VirtualObstacles(obstacles=obstacles) - ) + self.friendly_io.send_proto( + VirtualObstacles, VirtualObstacles(obstacles=obstacles) + ) def __create_single_click_callback( self, event: MouseInSceneEvent