From 47c037b6dc4c11ad9130959e65142190b9ce6cc4 Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:09:45 +0100 Subject: [PATCH 01/12] Update Python version requirement, refactor dataclass fields, and add installed packages list --- README.md | 2 +- fast-pysf | 2 +- installed_packages.txt | 90 ++++++++++++++++++++ requirements.txt | 118 ++++----------------------- robot_sf/gym_env/env_config.py | 14 ++-- robot_sf/ped_ego/unicycle_drive.py | 8 +- robot_sf/render/sim_view.py | 2 +- robot_sf/robot/bicycle_drive.py | 4 +- robot_sf/robot/differential_drive.py | 10 +-- robot_sf/sim/sim_config.py | 29 ++++--- 10 files changed, 142 insertions(+), 137 deletions(-) create mode 100644 installed_packages.txt diff --git a/README.md b/README.md index 9de1b79..9443c65 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ For 2. follow the instructions below: ### Local Installation -Install python >= 3.8 and <= 3.12. +Install python >= 3.9 and <= 3.12. ```sh sudo apt-get update && sudo apt-get install -y python3.12 python3-pip diff --git a/fast-pysf b/fast-pysf index eebf30e..a1e23dd 160000 --- a/fast-pysf +++ b/fast-pysf @@ -1 +1 @@ -Subproject commit eebf30ed30893ae5628e3f123439af03de6402e1 +Subproject commit a1e23dd6c37aff22b9a226ed417a4d40733ef0d3 diff --git a/installed_packages.txt b/installed_packages.txt new file mode 100644 index 0000000..a94580e --- /dev/null +++ b/installed_packages.txt @@ -0,0 +1,90 @@ +absl-py==2.1.0 +ale-py==0.10.1 +alembic==1.14.0 +annotated-types==0.7.0 +astroid==3.3.5 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +cloudpickle==3.1.0 +colorlog==6.9.0 +contourpy==1.3.1 +cycler==0.12.1 +dill==0.3.9 +docker-pycreds==0.4.0 +Farama-Notifications==0.0.4 +filelock==3.16.1 +fonttools==4.55.0 +fsspec==2024.10.0 +gitdb==4.0.11 +GitPython==3.1.43 +grpcio==1.68.0 +gym==0.26.2 +gym-notices==0.0.8 +gymnasium==1.0.0 +idna==3.10 +iniconfig==2.0.0 +isort==5.13.2 +Jinja2==3.1.4 +kiwisolver==1.4.7 +llvmlite==0.43.0 +loguru==0.7.2 +Mako==1.3.6 +Markdown==3.7 +markdown-it-py==3.0.0 +MarkupSafe==3.0.2 +matplotlib==3.9.2 +mccabe==0.7.0 +mdurl==0.1.2 +mpmath==1.3.0 +networkx==3.4.2 +numba==0.60.0 +numpy==1.26.4 +opencv-python==4.10.0.84 +optuna==4.1.0 +packaging==24.2 +pandas==2.2.3 +pillow==11.0.0 +platformdirs==4.3.6 +pluggy==1.5.0 +protobuf==5.28.3 +psutil==6.1.0 +pydantic==2.9.2 +pydantic_core==2.23.4 +pygame==2.6.1 +Pygments==2.18.0 +pylint==3.3.1 +pyparsing==3.2.0 +-e git+https://github.com/ll7/pysocialforce-ll7@eebf30ed30893ae5628e3f123439af03de6402e1#egg=PySocialForce&subdirectory=../../fast-pysf +pytest==8.3.3 +python-dateutil==2.9.0.post0 +pytz==2024.2 +PyYAML==6.0.2 +requests==2.32.3 +rich==13.9.4 +-e git+https://github.com/ll7/robot_sf_ll7.git@ebfcd90eae11699941580e2d0237fd6b4e7b5200#egg=robot_sf +scalene==1.5.48 +scipy==1.14.1 +sentry-sdk==2.18.0 +setproctitle==1.3.4 +setuptools==75.5.0 +Shimmy==2.0.0 +six==1.16.0 +smmap==5.0.1 +SQLAlchemy==2.0.36 +stable_baselines3==2.4.0 +svgelements==1.9.6 +sympy==1.13.1 +tensorboard==2.18.0 +tensorboard-data-server==0.7.2 +tk==0.1.0 +toml==0.10.2 +tomlkit==0.13.2 +torch==2.5.1 +tqdm==4.67.0 +typing_extensions==4.12.2 +tzdata==2024.2 +urllib3==2.2.3 +wandb==0.18.7 +Werkzeug==3.1.3 +wheel==0.45.0 diff --git a/requirements.txt b/requirements.txt index 51cae0e..07f7060 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,101 +1,17 @@ -absl-py==2.1.0 -alembic==1.13.1 -appdirs==1.4.4 -astroid==3.0.3 -certifi==2024.2.2 -charset-normalizer==3.3.2 -click==8.1.7 -cloudpickle==3.0.0 -colorlog==6.8.2 -contourpy==1.2.0 -cycler==0.12.1 -dill==0.3.8 -docker-pycreds==0.4.0 -exceptiongroup==1.2.0 -Farama-Notifications==0.0.4 -filelock==3.13.1 -fonttools==4.49.0 -fsspec==2024.2.0 -gitdb==4.0.11 -GitPython==3.1.42 -greenlet==3.0.3 -grpcio==1.60.1 -gym==0.26.2 -gym-notices==0.0.8 -gymnasium==0.29.1 -idna==3.6 -iniconfig==2.0.0 -isort==5.13.2 -Jinja2==3.1.3 -kiwisolver==1.4.5 -llvmlite==0.42.0 -loguru==0.7.2 -Mako==1.3.2 -Markdown==3.5.2 -markdown-it-py==3.0.0 -MarkupSafe==2.1.5 -matplotlib==3.6.1 -mccabe==0.7.0 -mdurl==0.1.2 -mpmath==1.3.0 -networkx==3.2.1 -numba==0.59.0 -numpy==1.26.4 -nvidia-cublas-cu12==12.1.3.1 -nvidia-cuda-cupti-cu12==12.1.105 -nvidia-cuda-nvrtc-cu12==12.1.105 -nvidia-cuda-runtime-cu12==12.1.105 -nvidia-cudnn-cu12==8.9.2.26 -nvidia-cufft-cu12==11.0.2.54 -nvidia-curand-cu12==10.3.2.106 -nvidia-cusolver-cu12==11.4.5.107 -nvidia-cusparse-cu12==12.1.0.106 -nvidia-nccl-cu12==2.19.3 -nvidia-nvjitlink-cu12==12.3.101 -nvidia-nvtx-cu12==12.1.105 -optuna==3.5.0 -packaging==23.2 -pandas==2.2.0 -Pillow==9.2.0 -platformdirs==4.2.0 -pluggy==1.4.0 -protobuf==4.25.3 -psutil==5.9.8 -pyarrow==15.0.0 -pygame==2.1.2 -Pygments==2.17.2 -pylint==3.0.3 -pynvml==11.4.1 -pyparsing==3.1.1 -pytest==8.0.1 -python-dateutil==2.8.2 -pytz==2024.1 -PyYAML==6.0.1 -requests==2.31.0 -rich==13.7.0 -scalene==1.5.34 -scipy==1.12.0 -sentry-sdk==1.40.6 -setproctitle==1.3.3 -Shimmy==1.3.0 -six==1.16.0 -smmap==5.0.1 -SQLAlchemy==2.0.27 -stable-baselines3==2.2.1 -sympy==1.12 -tensorboard==2.16.2 -tensorboard-data-server==0.7.2 -tk==0.1.0 -toml==0.10.2 -tomli==2.0.1 -tomlkit==0.12.3 -torch==2.2.0 -tqdm==4.66.2 -triton==2.2.0 -typing_extensions==4.9.0 -tzdata==2024.1 -urllib3==2.2.1 -wandb==0.16.4 -Werkzeug==3.0.1 -loguru==0.7.2 -svgelements==1.9.6 \ No newline at end of file +gymnasium +gym +loguru +numba +numpy +optuna +psutil +pygame +pytest +scalene +torch +tqdm +wandb +loguru +svgelements +stable-baselines3[extra] +shimmy \ No newline at end of file diff --git a/robot_sf/gym_env/env_config.py b/robot_sf/gym_env/env_config.py index e66d898..b1c013e 100644 --- a/robot_sf/gym_env/env_config.py +++ b/robot_sf/gym_env/env_config.py @@ -19,7 +19,7 @@ """ from typing import Union -from dataclasses import dataclass +from dataclasses import dataclass, field from robot_sf.nav.map_config import MapDefinitionPool from robot_sf.sensor.range_sensor import LidarScannerSettings @@ -36,11 +36,11 @@ class EnvSettings: """ Data class to hold environment settings for a simulation. """ - sim_config: SimulationSettings = SimulationSettings() - lidar_config: LidarScannerSettings = LidarScannerSettings() - robot_config: Union[DifferentialDriveSettings, BicycleDriveSettings] = \ - DifferentialDriveSettings() - map_pool: MapDefinitionPool = MapDefinitionPool() + sim_config: SimulationSettings = field(default_factory=SimulationSettings) + lidar_config: LidarScannerSettings = field(default_factory=LidarScannerSettings) + robot_config: Union[DifferentialDriveSettings, BicycleDriveSettings] = field( + default_factory=DifferentialDriveSettings) + map_pool: MapDefinitionPool = field(default_factory=MapDefinitionPool) def __post_init__(self): """ @@ -72,7 +72,7 @@ class PedEnvSettings(EnvSettings): """ Data class to hold environment settings for a simulation that includes an ego pedestrian. """ - ego_ped_config: UnicycleDriveSettings = UnicycleDriveSettings() + ego_ped_config: UnicycleDriveSettings = field(default_factory=UnicycleDriveSettings) def __post_init__(self): """ diff --git a/robot_sf/ped_ego/unicycle_drive.py b/robot_sf/ped_ego/unicycle_drive.py index 7cdcaba..cf8e212 100644 --- a/robot_sf/ped_ego/unicycle_drive.py +++ b/robot_sf/ped_ego/unicycle_drive.py @@ -38,8 +38,8 @@ def min_velocity(self) -> float: @dataclass class UnicycleDriveState: """A class that represents the state of a unicycle drive pedestrian.""" - pose: PedPose - velocity: float = field(default=0) + pose: PedPose = ((0.0, 0.0), 0.0) + velocity: PolarVec2D = (0.0, 0.0) @property def pos(self) -> Vec2D: @@ -121,11 +121,11 @@ def _norm_angle(self, angle: float) -> float: @dataclass -class UnicycleDrivePedestrian(): +class UnicycleDrivePedestrian: """Representing a pedestrian with unicycle driving behavior""" config: UnicycleDriveSettings - state: UnicycleDriveState = field(default=UnicycleDriveState(((0, 0), 0), 0)) + state: UnicycleDriveState = field(default_factory=UnicycleDriveState) movement: UnicycleMotion = field(init=False) def __post_init__(self): diff --git a/robot_sf/render/sim_view.py b/robot_sf/render/sim_view.py index 66e5f23..33f3bb5 100644 --- a/robot_sf/render/sim_view.py +++ b/robot_sf/render/sim_view.py @@ -86,7 +86,7 @@ class SimulationView: screen: pygame.surface.Surface = field(init=False) font: pygame.font.Font = field(init=False) redraw_needed: bool = field(init=False, default=False) - offset: np.array = field(init=False, default=np.array([0, 0])) + offset: np.array = field(default_factory=np.array([0, 0])) caption: str = 'RobotSF Simulation' focus_on_robot: bool = False focus_on_ego_ped: bool = False diff --git a/robot_sf/robot/bicycle_drive.py b/robot_sf/robot/bicycle_drive.py index 5beb132..bd25ef7 100644 --- a/robot_sf/robot/bicycle_drive.py +++ b/robot_sf/robot/bicycle_drive.py @@ -122,11 +122,11 @@ def _norm_angle(self, angle: float) -> float: @dataclass -class BicycleDriveRobot(): +class BicycleDriveRobot: """Representing a robot with bicycle driving behavior""" config: BicycleDriveSettings - state: BicycleDriveState = field(default=BicycleDriveState(((0, 0), 0), 0)) + state: BicycleDriveState = field(default_factory=lambda: BicycleDriveState(pose=((0.0, 0.0), 0.0))) movement: BicycleMotion = field(init=False) def __post_init__(self): diff --git a/robot_sf/robot/differential_drive.py b/robot_sf/robot/differential_drive.py index 68ee60f..97ac28d 100644 --- a/robot_sf/robot/differential_drive.py +++ b/robot_sf/robot/differential_drive.py @@ -56,10 +56,10 @@ def __post_init__(self): @dataclass class DifferentialDriveState: - pose: RobotPose - velocity: PolarVec2D = field(default=(0, 0)) - last_wheel_speeds: WheelSpeedState = field(default=(0, 0)) - wheel_speeds: WheelSpeedState = field(default=(0, 0)) + pose: RobotPose = ((0.0, 0.0), 0.0) + velocity: PolarVec2D = (0.0, 0.0) + last_wheel_speeds: WheelSpeedState = (0.0, 0.0) + wheel_speeds: WheelSpeedState = (0.0, 0.0) DifferentialDriveAction = Tuple[float, float] # (linear velocity, angular velocity) @@ -193,7 +193,7 @@ class DifferentialDriveRobot(): config: DifferentialDriveSettings # Configuration settings for the robot # Default state of the robot on initialization - state: DifferentialDriveState = field(default=DifferentialDriveState(((0, 0), 0))) + state: DifferentialDriveState = field(default_factory=DifferentialDriveState) # Movement logic for the robot based on the config; initialized post-creation movement: DifferentialDriveMotion = field(init=False) diff --git a/robot_sf/sim/sim_config.py b/robot_sf/sim/sim_config.py index b879130..3231c01 100644 --- a/robot_sf/sim/sim_config.py +++ b/robot_sf/sim/sim_config.py @@ -8,16 +8,7 @@ @dataclass class SimulationSettings: """ - Class to hold the settings for a simulation. - - This class contains various settings that control the behavior of the simulation, - such as the simulation time, the time per step, the pedestrian speed multiplier, the - difficulty level, the maximum number of pedestrians per group, the pedestrian radius, - the goal radius, the number of steps to stack in observations, whether to use the next - goal in the path as the current goal, the pedestrian-robot force configuration, and the - pedestrian density by difficulty level. - - After initialization, the `__post_init__` method is called to validate the settings. + Configuration settings for the simulation. """ # Simulation time in seconds @@ -40,9 +31,11 @@ class SimulationSettings: # Whether to use the next goal in the path as the current goal use_next_goal: bool = True # Pedestrian-robot force configuration - prf_config: PedRobotForceConfig = PedRobotForceConfig(is_active=True) + prf_config: PedRobotForceConfig = field(default_factory=PedRobotForceConfig) # Pedestrian density by difficulty level - ped_density_by_difficulty: List[float] = field(default_factory=lambda: [0.01, 0.02, 0.04, 0.08]) + ped_density_by_difficulty: List[float] = field( + default_factory=lambda: [0.01, 0.02, 0.04, 0.08] + ) def __post_init__(self): """ @@ -53,7 +46,9 @@ def __post_init__(self): """ # Check that the simulation time is positive if self.sim_time_in_secs <= 0: - raise ValueError("Simulation length for episodes mustn't be negative or zero!") + raise ValueError( + "Simulation length for episodes mustn't be negative or zero!" + ) # Check that the time per step is positive if self.time_per_step_in_secs <= 0: raise ValueError("Step time mustn't be negative or zero!") @@ -62,7 +57,9 @@ def __post_init__(self): raise ValueError("Pedestrian speed mustn't be negative or zero!") # Check that the maximum number of pedestrians per group is positive if self.max_peds_per_group <= 0: - raise ValueError("Maximum pedestrians per group mustn't be negative or zero!") + raise ValueError( + "Maximum pedestrians per group mustn't be negative or zero!" + ) # Check that the pedestrian radius is positive if self.ped_radius <= 0: raise ValueError("Pedestrian radius mustn't be negative or zero!") @@ -71,7 +68,9 @@ def __post_init__(self): raise ValueError("Goal radius mustn't be negative or zero!") # Check that the difficulty level is within the valid range if not 0 <= self.difficulty < len(self.ped_density_by_difficulty): - raise ValueError("No pedestrian density registered for selected difficulty level!") + raise ValueError( + "No pedestrian density registered for selected difficulty level!" + ) # Check that the pedestrian-robot force configuration is specified if not self.prf_config: raise ValueError("Pedestrian-Robot-Force settings need to be specified!") From c4664abc81958286a2a8cc90d05b7dd463eea718 Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:19:03 +0100 Subject: [PATCH 02/12] Update subproject commit reference for fast-pysf --- fast-pysf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fast-pysf b/fast-pysf index a1e23dd..6dcdefc 160000 --- a/fast-pysf +++ b/fast-pysf @@ -1 +1 @@ -Subproject commit a1e23dd6c37aff22b9a226ed417a4d40733ef0d3 +Subproject commit 6dcdefc1d4f07930d18559fc12333a266b8431ee From 6e2e975937a357442682c260ab02fb3b49045725 Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:21:18 +0100 Subject: [PATCH 03/12] Remove loguru from requirements.txt --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 07f7060..bd21c27 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,6 @@ scalene torch tqdm wandb -loguru svgelements stable-baselines3[extra] shimmy \ No newline at end of file From 054f1bbf3b3aab0001c32b35001049493ee485be Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:23:37 +0100 Subject: [PATCH 04/12] Update Python version requirement to 3.9 in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 510e73b..bc306e2 100644 --- a/setup.py +++ b/setup.py @@ -48,5 +48,5 @@ def get_ext_paths(root_dir, exclude_files): install_requires=INSTALL_REQUIREMENTS, zip_safe=False, include_package_data=True, - python_requires=">=3.8" + python_requires=">=3.9" ) From 6a75e4767936be51218be842e42c48e7c923b7ab Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:25:09 +0100 Subject: [PATCH 05/12] Refactor UnicycleDriveState to use default_factory for pose and velocity fields --- robot_sf/ped_ego/unicycle_drive.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/robot_sf/ped_ego/unicycle_drive.py b/robot_sf/ped_ego/unicycle_drive.py index cf8e212..f8e00c7 100644 --- a/robot_sf/ped_ego/unicycle_drive.py +++ b/robot_sf/ped_ego/unicycle_drive.py @@ -38,8 +38,8 @@ def min_velocity(self) -> float: @dataclass class UnicycleDriveState: """A class that represents the state of a unicycle drive pedestrian.""" - pose: PedPose = ((0.0, 0.0), 0.0) - velocity: PolarVec2D = (0.0, 0.0) + pose: PedPose = field(default_factory=lambda: ((0.0, 0.0), 0.0)) + velocity: PolarVec2D = field(default_factory=lambda: (0.0, 0.0)) @property def pos(self) -> Vec2D: From ab6899fa202a9bb4ca038d59764c9f90b4fcaf4e Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:33:57 +0100 Subject: [PATCH 06/12] Update Python version requirement to 3.10 in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bc306e2..62976dd 100644 --- a/setup.py +++ b/setup.py @@ -48,5 +48,5 @@ def get_ext_paths(root_dir, exclude_files): install_requires=INSTALL_REQUIREMENTS, zip_safe=False, include_package_data=True, - python_requires=">=3.9" + python_requires=">=3.10" ) From e3e4f9ab7137e1dba4bc255a753588bd0155edc3 Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:38:20 +0100 Subject: [PATCH 07/12] Update package requirements to specify minimum versions in requirements.txt --- requirements.txt | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/requirements.txt b/requirements.txt index bd21c27..1a2a910 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,16 @@ -gymnasium -gym -loguru -numba -numpy -optuna -psutil -pygame -pytest -scalene -torch -tqdm -wandb -svgelements -stable-baselines3[extra] -shimmy \ No newline at end of file +gymnasium>=1.0.0 +gym>=0.26.2 +loguru>=0.7.2 +numba>=0.60.0 +numpy>=1.26.4 +optuna>=4.1.0 +psutil>=6.1.0 +pygame>=2.6.1 +pytest>=8.3.3 +scalene>=1.5.48 +torch>=2.5.1 +tqdm>=4.67.0 +wandb>=0.18.7 +svgelements>=1.9.6 +stable-baselines3[extra]>=2.4.0 +shimmy>=2.0.0 \ No newline at end of file From 1a10bd599b886ed823ab232f17f34f3279884984 Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:40:23 +0100 Subject: [PATCH 08/12] Update subproject reference to latest commit --- fast-pysf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fast-pysf b/fast-pysf index 6dcdefc..c9afde9 160000 --- a/fast-pysf +++ b/fast-pysf @@ -1 +1 @@ -Subproject commit 6dcdefc1d4f07930d18559fc12333a266b8431ee +Subproject commit c9afde92133ce994de36d7d1bb74dbb5b550edb6 From 3b92cadb3a3c1010f4c8a019f09a19171d5c78e1 Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:40:54 +0100 Subject: [PATCH 09/12] Update Python installation instructions to require version 3.10 or higher --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9443c65..790aa1d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ For 2. follow the instructions below: ### Local Installation -Install python >= 3.9 and <= 3.12. +Install python >= 3.10 and <= 3.12. ```sh sudo apt-get update && sudo apt-get install -y python3.12 python3-pip From 472b4ab330afce04f250a82191c0943651ba12e6 Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:46:25 +0100 Subject: [PATCH 10/12] Refactor SimulationView to use np.ndarray for offset field --- robot_sf/render/sim_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robot_sf/render/sim_view.py b/robot_sf/render/sim_view.py index 33f3bb5..bc520cf 100644 --- a/robot_sf/render/sim_view.py +++ b/robot_sf/render/sim_view.py @@ -86,7 +86,7 @@ class SimulationView: screen: pygame.surface.Surface = field(init=False) font: pygame.font.Font = field(init=False) redraw_needed: bool = field(init=False, default=False) - offset: np.array = field(default_factory=np.array([0, 0])) + offset: np.ndarray = field(default_factory=lambda: np.array([0, 0])) caption: str = 'RobotSF Simulation' focus_on_robot: bool = False focus_on_ego_ped: bool = False From bffb62dd173c6479755e9f34eabf4dd9a3d20526 Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:54:55 +0100 Subject: [PATCH 11/12] Refactor SimulationView to streamline event handling and improve UI state management --- robot_sf/render/sim_view.py | 201 +++++++++++++++++++----------------- 1 file changed, 107 insertions(+), 94 deletions(-) diff --git a/robot_sf/render/sim_view.py b/robot_sf/render/sim_view.py index bc520cf..172639d 100644 --- a/robot_sf/render/sim_view.py +++ b/robot_sf/render/sim_view.py @@ -74,39 +74,128 @@ class SimulationView: width: float = 1200 height: float = 800 scaling: float = 15 - robot_radius: float = 1.0 + robot_radius: float = 1.0 ego_ped_radius: float = 0.4 ped_radius: float = 0.4 goal_radius: float = 1.0 map_def: MapDefinition = field(default_factory=MapDefinition) obstacles: List[Obstacle] = field(default_factory=list) - size_changed: bool = field(init=False, default=False) - is_exit_requested: bool = field(init=False, default=False) - is_abortion_requested: bool = field(init=False, default=False) + caption: str = 'RobotSF Simulation' + focus_on_robot: bool = False + focus_on_ego_ped: bool = False + + # Add UI state fields screen: pygame.surface.Surface = field(init=False) font: pygame.font.Font = field(init=False) + size_changed: bool = field(init=False, default=False) redraw_needed: bool = field(init=False, default=False) + is_exit_requested: bool = field(init=False, default=False) + is_abortion_requested: bool = field(init=False, default=False) offset: np.ndarray = field(default_factory=lambda: np.array([0, 0])) - caption: str = 'RobotSF Simulation' - focus_on_robot: bool = False - focus_on_ego_ped: bool = False - display_help: bool = False - display_robot_info: int = 0 - """The offset is already uses `scaling` as a factor.""" - - @property - def _timestep_text_pos(self) -> Vec2D: - return (16, 16) + display_robot_info: int = field(default=0) # Add this line + display_help: bool = field(default=False) # Also add this for help text def __post_init__(self): + """Initialize PyGame components.""" logger.info("Initializing the simulation view.") pygame.init() pygame.font.init() - self.screen = pygame.display.set_mode( - (self.width, self.height), pygame.RESIZABLE) + self.screen = pygame.display.set_mode((int(self.width), int(self.height)), pygame.RESIZABLE) pygame.display.set_caption(self.caption) - self.font = pygame.font.SysFont('Consolas', 14) - self.clear() + self.font = pygame.font.Font(None, 36) + + def show(self): + """Start the simulation view.""" + pass # Remove threading since we'll handle events in render() + + def render(self, state: VisualizableSimState): + """Render one frame and handle events.""" + # Handle events on main thread + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self._handle_quit() + elif event.type == pygame.VIDEORESIZE: + self._handle_video_resize(event) + elif event.type == pygame.KEYDOWN: + self._handle_keydown(event) + + if self.is_exit_requested: + pygame.quit() + if self.is_abortion_requested: + exit() + return + + sleep(0.01) # limit UI update rate to 100 fps + # TODO: make the sleep time configurable + + # info: event handling needs to be processed + # in the main thread to access UI resources + if self.is_exit_requested: + pygame.quit() + self.ui_events_thread.join() + if self.is_abortion_requested: + exit() + if self.size_changed: + self._resize_window() + self.size_changed = False + if self.redraw_needed: + self.redraw_needed = False + + # Adjust the view based on the focus + self._move_camera(state) + + self.screen.fill(BACKGROUND_COLOR) + + # static objects + if self.map_def.obstacles: + self._draw_obstacles() + + # debugging objects + # if self.map_def.robot_routes: + # self._draw_robot_routes() + # if self.map_def.ped_routes: + # self._draw_pedestrian_routes() + # if self.map_def.ped_spawn_zones: + # self._draw_spawn_zones() + # if self.map_def.ped_goal_zones: + # self._draw_goal_zones() + + self._draw_grid() + + # dynamic objects + self._augment_lidar(state.ray_vecs) + self._augment_ped_actions(state.ped_actions) + if state.robot_action: + self._augment_action(state.robot_action, ROBOT_ACTION_COLOR) + self._augment_goal_position(state.robot_action.goal) + self._draw_pedestrians(state.pedestrian_positions) + self._draw_robot(state.robot_pose) + if state.ego_ped_pose: + self._augment_lidar(state.ego_ped_ray_vecs) + if state.ego_ped_action: + self._augment_action(state.ego_ped_action, EGO_PED_ACTION_COLOR) + self._draw_ego_ped(state.ego_ped_pose) + + # information + self._add_text(state.timestep, state) + if self.display_help: + self._add_help_text() + + # update the display + pygame.display.update() + + def handle_events(self): + """Handle PyGame events on the main thread""" + if threading.current_thread() is not self._main_thread: + raise RuntimeError("PyGame events must be handled on the main thread") + + for event in pygame.event.get(): + # Handle events... + pass + + @property + def _timestep_text_pos(self) -> Vec2D: + return (16, 16) def _scale_tuple(self, tup: Tuple[float, float]) -> Tuple[float, float]: """scales a tuple of floats by the scaling factor and adds the offset.""" @@ -114,22 +203,6 @@ def _scale_tuple(self, tup: Tuple[float, float]) -> Tuple[float, float]: y = tup[1] * self.scaling + self.offset[1] return (x, y) - def show(self): - """ - Starts a separate thread to process the event queue and handles SIGINT signal. - - This method starts a new thread to process the event queue in the background. - It also sets up a signal handler for SIGINT to handle the interruption of the program. - """ - self.ui_events_thread = Thread(target=self._process_event_queue) - self.ui_events_thread.start() - - def handle_sigint(signum, frame): - self.is_exit_requested = True - self.is_abortion_requested = True - - signal(SIGINT, handle_sigint) - def exit(self): self.is_exit_requested = True self.ui_events_thread.join() @@ -207,66 +280,6 @@ def clear(self): self.screen.fill(BACKGROUND_COLOR) pygame.display.update() - def render(self, state: VisualizableSimState): - sleep(0.01) # limit UI update rate to 100 fps - # TODO: make the sleep time configurable - - # info: event handling needs to be processed - # in the main thread to access UI resources - if self.is_exit_requested: - pygame.quit() - self.ui_events_thread.join() - if self.is_abortion_requested: - exit() - if self.size_changed: - self._resize_window() - self.size_changed = False - if self.redraw_needed: - self.redraw_needed = False - - # Adjust the view based on the focus - self._move_camera(state) - - self.screen.fill(BACKGROUND_COLOR) - - # static objects - if self.map_def.obstacles: - self._draw_obstacles() - - # debugging objects - # if self.map_def.robot_routes: - # self._draw_robot_routes() - # if self.map_def.ped_routes: - # self._draw_pedestrian_routes() - # if self.map_def.ped_spawn_zones: - # self._draw_spawn_zones() - # if self.map_def.ped_goal_zones: - # self._draw_goal_zones() - - self._draw_grid() - - # dynamic objects - self._augment_lidar(state.ray_vecs) - self._augment_ped_actions(state.ped_actions) - if state.robot_action: - self._augment_action(state.robot_action, ROBOT_ACTION_COLOR) - self._augment_goal_position(state.robot_action.goal) - self._draw_pedestrians(state.pedestrian_positions) - self._draw_robot(state.robot_pose) - if state.ego_ped_pose: - self._augment_lidar(state.ego_ped_ray_vecs) - if state.ego_ped_action: - self._augment_action(state.ego_ped_action, EGO_PED_ACTION_COLOR) - self._draw_ego_ped(state.ego_ped_pose) - - # information - self._add_text(state.timestep, state) - if self.display_help: - self._add_help_text() - - # update the display - pygame.display.update() - def _resize_window(self): logger.debug("Resizing the window.") old_surface = self.screen From ff5c795d5a3011b08e964f7d39117bde7deb218d Mon Sep 17 00:00:00 2001 From: ll7 <32880741+ll7@users.noreply.github.com> Date: Wed, 20 Nov 2024 07:37:51 +0100 Subject: [PATCH 12/12] Enhance error handling in data processing module to improve robustness --- robot_sf/render/sim_view.py | 255 ++++++++++++++++++------------------ 1 file changed, 127 insertions(+), 128 deletions(-) diff --git a/robot_sf/render/sim_view.py b/robot_sf/render/sim_view.py index 172639d..566dcc6 100644 --- a/robot_sf/render/sim_view.py +++ b/robot_sf/render/sim_view.py @@ -15,7 +15,8 @@ from signal import signal, SIGINT import os -os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" + +os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "hide" Vec2D = Tuple[float, float] @@ -57,6 +58,7 @@ class VisualizableAction: class VisualizableSimState: """Representing a collection of properties to display the simulator's state at a discrete timestep.""" + timestep: int robot_action: Union[VisualizableAction, None] robot_pose: RobotPose @@ -74,16 +76,16 @@ class SimulationView: width: float = 1200 height: float = 800 scaling: float = 15 - robot_radius: float = 1.0 + robot_radius: float = 1.0 ego_ped_radius: float = 0.4 ped_radius: float = 0.4 goal_radius: float = 1.0 map_def: MapDefinition = field(default_factory=MapDefinition) obstacles: List[Obstacle] = field(default_factory=list) - caption: str = 'RobotSF Simulation' + caption: str = "RobotSF Simulation" focus_on_robot: bool = False focus_on_ego_ped: bool = False - + # Add UI state fields screen: pygame.surface.Surface = field(init=False) font: pygame.font.Font = field(init=False) @@ -93,14 +95,16 @@ class SimulationView: is_abortion_requested: bool = field(init=False, default=False) offset: np.ndarray = field(default_factory=lambda: np.array([0, 0])) display_robot_info: int = field(default=0) # Add this line - display_help: bool = field(default=False) # Also add this for help text + display_help: bool = field(default=False) # Also add this for help text def __post_init__(self): """Initialize PyGame components.""" logger.info("Initializing the simulation view.") pygame.init() pygame.font.init() - self.screen = pygame.display.set_mode((int(self.width), int(self.height)), pygame.RESIZABLE) + self.screen = pygame.display.set_mode( + (int(self.width), int(self.height)), pygame.RESIZABLE + ) pygame.display.set_caption(self.caption) self.font = pygame.font.Font(None, 36) @@ -126,20 +130,6 @@ def render(self, state: VisualizableSimState): return sleep(0.01) # limit UI update rate to 100 fps - # TODO: make the sleep time configurable - - # info: event handling needs to be processed - # in the main thread to access UI resources - if self.is_exit_requested: - pygame.quit() - self.ui_events_thread.join() - if self.is_abortion_requested: - exit() - if self.size_changed: - self._resize_window() - self.size_changed = False - if self.redraw_needed: - self.redraw_needed = False # Adjust the view based on the focus self._move_camera(state) @@ -150,29 +140,25 @@ def render(self, state: VisualizableSimState): if self.map_def.obstacles: self._draw_obstacles() - # debugging objects - # if self.map_def.robot_routes: - # self._draw_robot_routes() - # if self.map_def.ped_routes: - # self._draw_pedestrian_routes() - # if self.map_def.ped_spawn_zones: - # self._draw_spawn_zones() - # if self.map_def.ped_goal_zones: - # self._draw_goal_zones() - self._draw_grid() # dynamic objects - self._augment_lidar(state.ray_vecs) - self._augment_ped_actions(state.ped_actions) - if state.robot_action: + if hasattr(state, 'ray_vecs'): + self._augment_lidar(state.ray_vecs) + if hasattr(state, 'ped_actions'): + self._augment_ped_actions(state.ped_actions) + if hasattr(state, 'robot_action') and state.robot_action: self._augment_action(state.robot_action, ROBOT_ACTION_COLOR) - self._augment_goal_position(state.robot_action.goal) - self._draw_pedestrians(state.pedestrian_positions) - self._draw_robot(state.robot_pose) - if state.ego_ped_pose: - self._augment_lidar(state.ego_ped_ray_vecs) - if state.ego_ped_action: + if hasattr(state.robot_action, 'goal'): + self._augment_goal_position(state.robot_action.goal) + if hasattr(state, 'pedestrian_positions'): + self._draw_pedestrians(state.pedestrian_positions) + if hasattr(state, 'robot_pose'): + self._draw_robot(state.robot_pose) + if hasattr(state, 'ego_ped_pose') and state.ego_ped_pose: + if hasattr(state, 'ego_ped_ray_vecs'): + self._augment_lidar(state.ego_ped_ray_vecs) + if hasattr(state, 'ego_ped_action') and state.ego_ped_action: self._augment_action(state.ego_ped_action, EGO_PED_ACTION_COLOR) self._draw_ego_ped(state.ego_ped_pose) @@ -184,15 +170,6 @@ def render(self, state: VisualizableSimState): # update the display pygame.display.update() - def handle_events(self): - """Handle PyGame events on the main thread""" - if threading.current_thread() is not self._main_thread: - raise RuntimeError("PyGame events must be handled on the main thread") - - for event in pygame.event.get(): - # Handle events... - pass - @property def _timestep_text_pos(self) -> Vec2D: return (16, 16) @@ -205,7 +182,6 @@ def _scale_tuple(self, tup: Tuple[float, float]) -> Tuple[float, float]: def exit(self): self.is_exit_requested = True - self.ui_events_thread.join() def _handle_quit(self, e=None): """Handle the quit event of the pygame window.""" @@ -230,24 +206,39 @@ def _handle_keydown(self, e): key_action_map = { # scale the view - pygame.K_PLUS: lambda: setattr(self, 'scaling', self.scaling + new_scaling), - pygame.K_MINUS: lambda: setattr(self, 'scaling', max(self.scaling - new_scaling, 1)), + pygame.K_PLUS: lambda: setattr(self, "scaling", self.scaling + new_scaling), + pygame.K_MINUS: lambda: setattr( + self, "scaling", max(self.scaling - new_scaling, 1) + ), # move the view - pygame.K_LEFT: lambda: self.offset.__setitem__(0, self.offset[0] + new_offset), - pygame.K_RIGHT: lambda: self.offset.__setitem__(0, self.offset[0] - new_offset), - pygame.K_UP: lambda: self.offset.__setitem__(1, self.offset[1] + new_offset), - pygame.K_DOWN: lambda: self.offset.__setitem__(1, self.offset[1] - new_offset), + pygame.K_LEFT: lambda: self.offset.__setitem__( + 0, self.offset[0] + new_offset + ), + pygame.K_RIGHT: lambda: self.offset.__setitem__( + 0, self.offset[0] - new_offset + ), + pygame.K_UP: lambda: self.offset.__setitem__( + 1, self.offset[1] + new_offset + ), + pygame.K_DOWN: lambda: self.offset.__setitem__( + 1, self.offset[1] - new_offset + ), # reset the view pygame.K_r: lambda: self.offset.__setitem__(slice(None), (0, 0)), # focus on the robot or ped - pygame.K_f: lambda: setattr(self, 'focus_on_robot', not self.focus_on_robot), - pygame.K_p: lambda: setattr(self, 'focus_on_ego_ped', not self.focus_on_ego_ped), + pygame.K_f: lambda: setattr( + self, "focus_on_robot", not self.focus_on_robot + ), + pygame.K_p: lambda: setattr( + self, "focus_on_ego_ped", not self.focus_on_ego_ped + ), # display help - pygame.K_h: lambda: setattr(self, 'display_help', not self.display_help), + pygame.K_h: lambda: setattr(self, "display_help", not self.display_help), # display robotinfo - pygame.K_q: lambda: setattr(self, 'display_robot_info', - (self.display_robot_info + 1) % 3), - } + pygame.K_q: lambda: setattr( + self, "display_robot_info", (self.display_robot_info + 1) % 3 + ), + } if e.key in key_action_map: key_action_map[e.key]() @@ -262,7 +253,7 @@ def _process_event_queue(self): pygame.QUIT: self._handle_quit, pygame.VIDEORESIZE: self._handle_video_resize, pygame.KEYDOWN: self._handle_keydown, - } + } while not self.is_exit_requested: for e in pygame.event.get(): handler = event_handler_map.get(e.type) @@ -284,11 +275,12 @@ def _resize_window(self): logger.debug("Resizing the window.") old_surface = self.screen self.screen = pygame.display.set_mode( - (self.width, self.height), pygame.RESIZABLE) + (self.width, self.height), pygame.RESIZABLE + ) self.screen.blit(old_surface, (0, 0)) def _move_camera(self, state: VisualizableSimState): - """ Moves the camera based on the focused object.""" + """Moves the camera based on the focused object.""" if self.focus_on_robot: r_x, r_y = state.robot_pose[0] self.offset[0] = int(r_x * self.scaling - self.width / 2) * -1 @@ -304,7 +296,8 @@ def _draw_robot(self, pose: RobotPose): self.screen, ROBOT_COLOR, self._scale_tuple(pose[0]), - self.robot_radius * self.scaling) + self.robot_radius * self.scaling, + ) def _draw_ego_ped(self, pose: PedPose): # TODO: display robot with an image instead of a circle @@ -312,7 +305,8 @@ def _draw_ego_ped(self, pose: PedPose): self.screen, EGO_PED_COLOR, self._scale_tuple(pose[0]), - self.ego_ped_radius * self.scaling) + self.ego_ped_radius * self.scaling, + ) def _draw_pedestrians(self, ped_pos: np.ndarray): # TODO: display pedestrians with an image instead of a circle @@ -321,16 +315,16 @@ def _draw_pedestrians(self, ped_pos: np.ndarray): self.screen, PED_COLOR, self._scale_tuple((ped_x, ped_y)), - self.ped_radius * self.scaling - ) + self.ped_radius * self.scaling, + ) def _draw_obstacles(self): # Iterate over each obstacle in the list of obstacles for obstacle in self.map_def.obstacles: # Scale and offset the vertices of the obstacle - scaled_vertices = [( - self._scale_tuple((x, y)) - ) for x, y in obstacle.vertices_np] + scaled_vertices = [ + (self._scale_tuple((x, y))) for x, y in obstacle.vertices_np + ] # Draw the obstacle as a polygon on the screen pygame.draw.polygon(self.screen, OBSTACLE_COLOR, scaled_vertices) @@ -339,9 +333,7 @@ def _draw_spawn_zones(self): for spawn_zone in self.map_def.ped_spawn_zones: # Scale and offset the vertices of the zones vertices_np = np.array(spawn_zone) - scaled_vertices = [( - self._scale_tuple((x, y)) - ) for x, y in vertices_np] + scaled_vertices = [(self._scale_tuple((x, y))) for x, y in vertices_np] # Draw the spawn zone as a polygon on the screen pygame.draw.polygon(self.screen, PED_SPAWN_COLOR, scaled_vertices) @@ -350,9 +342,7 @@ def _draw_goal_zones(self): for goal_zone in self.map_def.ped_goal_zones: # Scale and offset the vertices of the goal zones vertices_np = np.array(goal_zone) - scaled_vertices = [( - self._scale_tuple((x, y)) - ) for x, y in vertices_np] + scaled_vertices = [(self._scale_tuple((x, y))) for x, y in vertices_np] # Draw the goal_zone as a polygon on the screen pygame.draw.polygon(self.screen, PED_GOAL_COLOR, scaled_vertices) @@ -362,7 +352,8 @@ def _augment_goal_position(self, robot_goal: Vec2D): self.screen, ROBOT_GOAL_COLOR, self._scale_tuple(robot_goal), - self.goal_radius * self.scaling) + self.goal_radius * self.scaling, + ) def _augment_lidar(self, ray_vecs: np.ndarray): for p1, p2 in ray_vecs: @@ -371,7 +362,7 @@ def _augment_lidar(self, ray_vecs: np.ndarray): ROBOT_LIDAR_COLOR, self._scale_tuple(p1), self._scale_tuple(p2), - ) + ) def _augment_action(self, action: VisualizableAction, color): r_x, r_y = action.pose[0] @@ -391,8 +382,8 @@ def add_vec(v_1: Vec2D, v_2: Vec2D) -> Vec2D: color, self._scale_tuple((r_x, r_y)), self._scale_tuple((vec_x, vec_y)), - width=3 - ) + width=3, + ) def _augment_ped_actions(self, ped_actions: np.ndarray): """Draw the actions of the pedestrians as lines.""" @@ -402,8 +393,8 @@ def _augment_ped_actions(self, ped_actions: np.ndarray): PED_ACTION_COLOR, self._scale_tuple(p1), self._scale_tuple(p2), - width=3 - ) + width=3, + ) def _draw_pedestrian_routes(self): """ @@ -414,10 +405,9 @@ def _draw_pedestrian_routes(self): self.screen, PED_ROUTE_COLOR, False, - [self._scale_tuple((x, y)) - for x, y in route.waypoints], - width=1 - ) + [self._scale_tuple((x, y)) for x, y in route.waypoints], + width=1, + ) def _draw_robot_routes(self): """ @@ -428,48 +418,55 @@ def _draw_robot_routes(self): self.screen, ROBOT_ROUTE_COLOR, False, - [self._scale_tuple((x, y)) - for x, y in route.waypoints], - width=1 - ) + [self._scale_tuple((x, y)) for x, y in route.waypoints], + width=1, + ) def _draw_coordinates(self, x, y): """ Draws the coordinates (x, y) on the screen. """ - text = self.font.render(f'({x}, {y})', False, TEXT_COLOR) + text = self.font.render(f"({x}, {y})", False, TEXT_COLOR) self.screen.blit(text, (x, y)) def _add_text(self, timestep: int, state: VisualizableSimState): lines = [] if self.display_robot_info == 1 and state.robot_action: lines += [ - f'RobotPose: {state.robot_pose}', - f'RobotAction: {state.robot_action.action if state.robot_action else None}', - f'RobotGoal: {state.robot_action.goal if state.robot_action else None}',] + f"RobotPose: {state.robot_pose}", + f"RobotAction: {state.robot_action.action if state.robot_action else None}", + f"RobotGoal: {state.robot_action.goal if state.robot_action else None}", + ] elif self.display_robot_info == 2: if state.ego_ped_pose and state.ego_ped_action: - distance_to_robot = euclid_dist(state.ego_ped_pose[0], state.robot_pose[0]) + distance_to_robot = euclid_dist( + state.ego_ped_pose[0], state.robot_pose[0] + ) lines += [ - f'PedestrianPose: {state.ego_ped_pose}', - f'PedestrianAction: {state.ego_ped_action.action}', - f'PedestrianGoal: {state.ego_ped_action.goal}', - f'DistanceRobot: {distance_to_robot:.2f}',] + f"PedestrianPose: {state.ego_ped_pose}", + f"PedestrianAction: {state.ego_ped_action.action}", + f"PedestrianGoal: {state.ego_ped_action.goal}", + f"DistanceRobot: {distance_to_robot:.2f}", + ] else: self.display_robot_info = 0 text_lines = [ - f'step: {timestep}', - f'scaling: {self.scaling}', - f'x-offset: {self.offset[0]/self.scaling:.2f}', - f'y-offset: {self.offset[1]/self.scaling:.2f}', - ] + f"step: {timestep}", + f"scaling: {self.scaling}", + f"x-offset: {self.offset[0]/self.scaling:.2f}", + f"y-offset: {self.offset[1]/self.scaling:.2f}", + ] text_lines += lines - text_lines += ['(Press h for help)',] + text_lines += [ + "(Press h for help)", + ] # Create a surface for the text background max_width = max(self.font.size(line)[0] for line in text_lines) text_height = len(text_lines) * self.font.get_linesize() - text_surface = pygame.Surface((max_width + 10, text_height + 10), pygame.SRCALPHA) + text_surface = pygame.Surface( + (max_width + 10, text_height + 10), pygame.SRCALPHA + ) text_surface.fill(TEXT_BACKGROUND) for i, text in enumerate(text_lines): @@ -489,24 +486,26 @@ def _add_text(self, timestep: int, state: VisualizableSimState): def _add_help_text(self): text_lines = [ - 'Move camera: arrow keys', - 'Move fast: CTRL + arrow keys', - 'Move slow: ALT + arrow keys', - 'Reset view: r', - 'Focus robot: f', - 'Focus ego_ped: p', - 'Scale up: +', - 'Scale down: -', - 'Display robot info: q', - 'Help: h', - ] + "Move camera: arrow keys", + "Move fast: CTRL + arrow keys", + "Move slow: ALT + arrow keys", + "Reset view: r", + "Focus robot: f", + "Focus ego_ped: p", + "Scale up: +", + "Scale down: -", + "Display robot info: q", + "Help: h", + ] # Determine max width of the text text_surface = self.font.render(text_lines[1], False, TEXT_COLOR) max_width = max(self.font.size(line)[0] for line in text_lines) text_height = len(text_lines) * self.font.get_linesize() - text_surface = pygame.Surface((max_width + 10, text_height + 10), pygame.SRCALPHA) + text_surface = pygame.Surface( + (max_width + 10, text_height + 10), pygame.SRCALPHA + ) text_surface.fill(TEXT_BACKGROUND) for i, text in enumerate(text_lines): @@ -522,13 +521,13 @@ def _add_help_text(self): # Draw main text text_surface.blit(text_render, pos) - self.screen.blit(text_surface, (self.width - max_width - 10, self._timestep_text_pos[1])) + self.screen.blit( + text_surface, (self.width - max_width - 10, self._timestep_text_pos[1]) + ) def _draw_grid( - self, - grid_increment: int = 50, - grid_color: RgbColor = (200, 200, 200) - ): + self, grid_increment: int = 50, grid_color: RgbColor = (200, 200, 200) + ): """ Draw a grid on the screen. :param grid_increment: The increment of the grid in pixels. @@ -543,8 +542,8 @@ def _draw_grid( self.screen, grid_color, (x + self.offset[0], 0), - (x + self.offset[0], self.height) - ) + (x + self.offset[0], self.height), + ) label = font.render(str(int(x / self.scaling)), 1, grid_color) self.screen.blit(label, (x + self.offset[0], 0)) @@ -555,7 +554,7 @@ def _draw_grid( self.screen, grid_color, (0, y + self.offset[1]), - (self.width, y + self.offset[1]) - ) + (self.width, y + self.offset[1]), + ) label = font.render(str(int(y / self.scaling)), 1, grid_color) self.screen.blit(label, (0, y + self.offset[1]))