From d2905cf7a6b36bab4aaa2ec3622161a13ed5c731 Mon Sep 17 00:00:00 2001 From: topfive Date: Tue, 13 May 2025 09:17:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 8 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/project.iml | 8 + .idea/vcs.xml | 6 + ...系统“软件设计规格说明书.doc | Bin 162 -> 0 bytes Src/command_center/__init__.py | 4 + Src/command_center/communication/__init__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 515 bytes .../communication_manager.cpython-312.pyc | Bin 0 -> 9937 bytes .../database_handler.cpython-312.pyc | Bin 0 -> 9342 bytes .../drone_connection_manager.cpython-312.pyc | Bin 0 -> 8844 bytes .../mavlink_handler.cpython-312.pyc | Bin 0 -> 4861 bytes .../__pycache__/message_queue.cpython-312.pyc | Bin 0 -> 4263 bytes .../communication/communication_manager.py | 172 ++++---- .../communication/database_handler.py | 4 + .../communication/drone_connection_manager.py | 6 + .../communication/mavlink_handler.py | 4 + .../communication/message_queue.py | 4 + Src/command_center/main.py | 60 ++- .../battlefield_simulator.cpython-312.pyc | Bin 0 -> 7266 bytes .../__pycache__/hybrid_astar.cpython-312.pyc | Bin 0 -> 11403 bytes .../__pycache__/path_planner.cpython-312.pyc | Bin 0 -> 5497 bytes .../path_planning/battlefield_simulator.py | 140 +++++++ .../path_planning/hybrid_astar.py | 229 ++++++++++ .../path_planning/path_planner.py | 120 ++++++ .../path_planning/test_battlefield.py | 138 ++++++ Src/command_center/ui/__init__.py | 8 +- .../ui/__pycache__/__init__.cpython-312.pyc | Bin 149 -> 149 bytes .../algorithm_config_view.cpython-312.pyc | Bin 5560 -> 5796 bytes .../__pycache__/base_map_view.cpython-312.pyc | Bin 0 -> 17687 bytes .../battlefield_map_view.cpython-312.pyc | Bin 0 -> 6855 bytes .../drone_detail_view.cpython-312.pyc | Bin 4413 -> 21035 bytes .../drone_list_view.cpython-312.pyc | Bin 3365 -> 28046 bytes .../ui/__pycache__/login_view.cpython-312.pyc | Bin 4186 -> 4374 bytes .../ui/__pycache__/main_view.cpython-312.pyc | Bin 4471 -> 4471 bytes .../map_data_model.cpython-312.pyc | Bin 0 -> 2441 bytes .../ui/__pycache__/map_view.cpython-312.pyc | Bin 3503 -> 3503 bytes .../matplotlib_canvas.cpython-312.pyc | Bin 0 -> 1037 bytes .../path_layer_view.cpython-312.pyc | Bin 3753 -> 5082 bytes .../path_planning_view.cpython-312.pyc | Bin 4870 -> 5199 bytes .../path_simulation_view.cpython-312.pyc | Bin 6853 -> 6853 bytes .../simple_map_view.cpython-312.pyc | Bin 0 -> 3961 bytes .../status_dashboard.cpython-312.pyc | Bin 5594 -> 5594 bytes .../threat_layer_view.cpython-312.pyc | Bin 3390 -> 4187 bytes .../ui/algorithm_config_view.py | 8 +- Src/command_center/ui/base_map_view.py | 296 +++++++++++++ Src/command_center/ui/drone_detail_view.py | 304 +++++++++++++- Src/command_center/ui/drone_list_view.py | 393 +++++++++++++++++- Src/command_center/ui/login_view.py | 4 + Src/command_center/ui/main_view.py | 87 ---- Src/command_center/ui/map_data_model.py | 39 ++ Src/command_center/ui/path_layer_view.py | 121 +++--- Src/command_center/ui/path_planning_view.py | 15 +- Src/command_center/ui/path_simulation_view.py | 109 ----- Src/command_center/ui/simple_map_view.py | 71 ++++ Src/command_center/ui/status_dashboard.py | 4 + Src/command_center/ui/threat_layer_view.py | 107 +++-- 59 files changed, 2055 insertions(+), 436 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/project.iml create mode 100644 .idea/vcs.xml delete mode 100644 Doc/~$人机后勤输送路径规划系统“软件设计规格说明书.doc create mode 100644 Src/command_center/communication/__pycache__/__init__.cpython-312.pyc create mode 100644 Src/command_center/communication/__pycache__/communication_manager.cpython-312.pyc create mode 100644 Src/command_center/communication/__pycache__/database_handler.cpython-312.pyc create mode 100644 Src/command_center/communication/__pycache__/drone_connection_manager.cpython-312.pyc create mode 100644 Src/command_center/communication/__pycache__/mavlink_handler.cpython-312.pyc create mode 100644 Src/command_center/communication/__pycache__/message_queue.cpython-312.pyc create mode 100644 Src/command_center/path_planning/__pycache__/battlefield_simulator.cpython-312.pyc create mode 100644 Src/command_center/path_planning/__pycache__/hybrid_astar.cpython-312.pyc create mode 100644 Src/command_center/path_planning/__pycache__/path_planner.cpython-312.pyc create mode 100644 Src/command_center/path_planning/battlefield_simulator.py create mode 100644 Src/command_center/path_planning/hybrid_astar.py create mode 100644 Src/command_center/path_planning/path_planner.py create mode 100644 Src/command_center/path_planning/test_battlefield.py create mode 100644 Src/command_center/ui/__pycache__/base_map_view.cpython-312.pyc create mode 100644 Src/command_center/ui/__pycache__/battlefield_map_view.cpython-312.pyc create mode 100644 Src/command_center/ui/__pycache__/map_data_model.cpython-312.pyc create mode 100644 Src/command_center/ui/__pycache__/matplotlib_canvas.cpython-312.pyc create mode 100644 Src/command_center/ui/__pycache__/simple_map_view.cpython-312.pyc create mode 100644 Src/command_center/ui/base_map_view.py delete mode 100644 Src/command_center/ui/main_view.py create mode 100644 Src/command_center/ui/map_data_model.py delete mode 100644 Src/command_center/ui/path_simulation_view.py create mode 100644 Src/command_center/ui/simple_map_view.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..35410cac --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..05adead2 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..a0733a5c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/project.iml b/.idea/project.iml new file mode 100644 index 00000000..d0876a78 --- /dev/null +++ b/.idea/project.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Doc/~$人机后勤输送路径规划系统“软件设计规格说明书.doc b/Doc/~$人机后勤输送路径规划系统“软件设计规格说明书.doc deleted file mode 100644 index 3a1b11eaebd5daf48b7fb142e77d1c7963a9a890..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 gcmZQIG`BD|V;~W*G8i(LGgvSfGmxy6M4K2G02C4e9RL6T diff --git a/Src/command_center/__init__.py b/Src/command_center/__init__.py index 122acb7e..43cacfc2 100644 --- a/Src/command_center/__init__.py +++ b/Src/command_center/__init__.py @@ -1 +1,5 @@ +# -*- coding: utf-8 -*- +# File: __init__.py (in command_center) +# Purpose: 将 command_center 目录标记为 Python 包。 + # 指挥控制中心包 \ No newline at end of file diff --git a/Src/command_center/communication/__init__.py b/Src/command_center/communication/__init__.py index 9acca399..a7f61463 100644 --- a/Src/command_center/communication/__init__.py +++ b/Src/command_center/communication/__init__.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +# File: __init__.py (in communication) +# Purpose: 将 communication 目录标记为 Python 包,并可能导出其中的类。 + from .mavlink_handler import MavlinkHandler from .database_handler import DatabaseHandler from .message_queue import MessageQueue diff --git a/Src/command_center/communication/__pycache__/__init__.cpython-312.pyc b/Src/command_center/communication/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5c8ad02963f43435eefff20484e5cce27ebc49c GIT binary patch literal 515 zcmYk2Jx{|h5QfkB><6VF0YXB|s6wzJp$dj}=u#xa7FDuLVzmL=DNcgak)55Ljo-pQ zu#}034bhDWJ1LLRZb@ok7BRF5-}^=Ur**Y0@#` z8u5(yTCRF+)#Vieo>dIxVYL;lFzU3qt~$Iz!4HO|?56f3gM-7G2lEQ3oTGNbb*8#z z#w#CV!WhPHu+$;^Tt?@UxlHc#yXyT$h7;35)8UYcjLI^sUpc`z7IB90>3pFYeV#-) mqZe{p_cNcI9%%Wp5kdtV7jRO*Q33q|&b}S5Z9Vz2tLwig3zW40 literal 0 HcmV?d00001 diff --git a/Src/command_center/communication/__pycache__/communication_manager.cpython-312.pyc b/Src/command_center/communication/__pycache__/communication_manager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..341b0a2151d47c53eb59a7e55950aa8e8d37d44a GIT binary patch literal 9937 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 literal 0 HcmV?d00001 diff --git a/Src/command_center/communication/__pycache__/database_handler.cpython-312.pyc b/Src/command_center/communication/__pycache__/database_handler.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3141d59af6d66fd4a3fc7f633ab52f30e0293c38 GIT binary patch literal 9342 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 a_1Bd3Ybxu{)QbORTQqdu0}5+$&i?||Jz!q| literal 0 HcmV?d00001 diff --git a/Src/command_center/communication/__pycache__/mavlink_handler.cpython-312.pyc b/Src/command_center/communication/__pycache__/mavlink_handler.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a79917879eef40db773844c09eb4b4d41511e04 GIT binary patch literal 4861 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 bool: @@ -45,74 +54,92 @@ class CommunicationManager: # 启动消息处理线程 self.running = True - self.thread = threading.Thread(target=self._process_messages) - self.thread.daemon = True - self.thread.start() + self.update_thread = Thread(target=self._update_loop) + self.update_thread.daemon = True + self.update_thread.start() return True def stop(self): """停止通信管理器""" self.running = False - if self.thread: - self.thread.join() + if self.update_thread: + self.update_thread.join() self.mavlink.stop() self.drone_manager.stop() self.database.disconnect() - def add_drone(self, drone_id: str, ip_address: str, port: int, - connection_type: str = 'udp') -> bool: - """添加新的无人机连接""" - return self.database.add_drone_connection(drone_id, ip_address, port, connection_type) - - def connect_drone(self, drone_id: str) -> bool: - """连接指定的无人机""" - return self.drone_manager.connect_drone(drone_id) - - def disconnect_drone(self, drone_id: str): - """断开指定无人机的连接""" - self.drone_manager.disconnect_drone(drone_id) - - def get_connected_drones(self) -> List[str]: - """获取所有已连接的无人机ID""" - return self.drone_manager.get_connected_drones() + def register_callback(self, event: str, callback: Callable): + """注册回调函数""" + if event not in self.callbacks: + self.callbacks[event] = [] + self.callbacks[event].append(callback) - def is_drone_connected(self, drone_id: str) -> bool: - """检查指定无人机是否已连接""" - return self.drone_manager.is_drone_connected(drone_id) - - def send_command(self, drone_id: str, command_type: str, **kwargs) -> bool: - """向指定无人机发送命令""" - return self.drone_manager.send_command(drone_id, command_type, **kwargs) - - def _process_messages(self): - """处理消息队列中的消息""" + def _update_loop(self): + """更新循环""" while self.running: try: - msg = self.mavlink.get_message_queue().get(timeout=1.0) - if msg: - self._handle_message(msg) + self._update_drone_status() + time.sleep(0.1) # 100ms更新间隔 except Exception as e: - print(f"消息处理错误: {str(e)}") - time.sleep(1) - - def _handle_message(self, msg: Dict[str, Any]): - """处理接收到的消息""" - try: - msg_type = msg['message'].get_type() - msg_data = msg['message'].to_dict() - - # 保存消息到数据库 - self.database.save_mavlink_message(msg_type, msg_data) - - # 根据消息类型更新无人机状态 - if msg_type == 'GLOBAL_POSITION_INT': - self._update_drone_position(msg_data) - elif msg_type == 'SYS_STATUS': - self._update_drone_status(msg_data) - - except Exception as e: - print(f"消息处理失败: {str(e)}") + print(f"更新循环错误: {str(e)}") + + def _update_drone_status(self): + """更新无人机状态""" + # TODO: 实现实际的通信逻辑 + # 这里模拟一些数据更新 + with self.lock: + for drone_id in self.drones: + self.drones[drone_id].update({ + "battery": max(0, self.drones[drone_id].get("battery", 100) - 0.1), + "signal_strength": max(0, self.drones[drone_id].get("signal_strength", 100) - 0.2), + "latency": self.drones[drone_id].get("latency", 0) + 1 + }) + + def add_drone(self, drone_id: str, info: dict): + """添加无人机""" + with self.lock: + self.drones[drone_id] = info + self._notify("drone_added", drone_id) + + def remove_drone(self, drone_id: str): + """移除无人机""" + with self.lock: + if drone_id in self.drones: + del self.drones[drone_id] + self._notify("drone_removed", drone_id) + + def update_drone(self, drone_id: str, info: dict): + """更新无人机信息""" + with self.lock: + if drone_id in self.drones: + self.drones[drone_id].update(info) + self._notify("drone_updated", drone_id) + + def get_drone(self, drone_id: str) -> dict: + """获取无人机信息""" + with self.lock: + return self.drones.get(drone_id, {}) + + def get_all_drones(self) -> List[dict]: + """获取所有无人机信息""" + with self.lock: + return list(self.drones.values()) + + def send_command(self, drone_id: str, command: str, params: dict = None): + """发送控制命令""" + # TODO: 实现实际的命令发送逻辑 + print(f"发送命令到无人机 {drone_id}: {command} {params}") + self._notify("command_sent", drone_id, {"command": command, "params": params}) + + def _notify(self, event: str, drone_id: str, data: dict = None): + """通知事件""" + if event in self.callbacks: + for callback in self.callbacks[event]: + try: + callback(drone_id, data) + except Exception as e: + print(f"回调函数执行错误: {str(e)}") def _handle_heartbeat(self, msg): """处理心跳消息""" @@ -135,37 +162,6 @@ class CommunicationManager: 'data': msg.to_dict() }) - def _update_drone_position(self, position_data: Dict[str, Any]): - """更新无人机位置信息""" - query = """ - INSERT INTO drone_status - (drone_id, latitude, longitude, altitude, timestamp) - VALUES (%s, %s, %s, %s, %s) - """ - params = ( - position_data.get('sysid', 'unknown'), - position_data.get('lat', 0) / 1e7, # 转换为度 - position_data.get('lon', 0) / 1e7, # 转换为度 - position_data.get('alt', 0) / 1000, # 转换为米 - datetime.now() - ) - self.database.execute_update(query, params) - - def _update_drone_status(self, status_data: Dict[str, Any]): - """更新无人机状态信息""" - query = """ - UPDATE drone_status - SET battery_level = %s, status = %s - WHERE drone_id = %s - ORDER BY timestamp DESC LIMIT 1 - """ - params = ( - status_data.get('battery_remaining', 0), - status_data.get('system_status', 'UNKNOWN'), - status_data.get('sysid', 'unknown') - ) - self.database.execute_update(query, params) - def get_drone_status(self, drone_id: str) -> Optional[Dict[str, Any]]: """获取指定无人机的状态""" query = """ diff --git a/Src/command_center/communication/database_handler.py b/Src/command_center/communication/database_handler.py index 8186f7bc..45f656f7 100644 --- a/Src/command_center/communication/database_handler.py +++ b/Src/command_center/communication/database_handler.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +# File: database_handler.py +# Purpose: 处理与数据库的交互,包括连接、数据读写(如用户信息、无人机数据、任务记录等)。 + import mysql.connector from mysql.connector import Error from typing import Dict, List, Any, Optional diff --git a/Src/command_center/communication/drone_connection_manager.py b/Src/command_center/communication/drone_connection_manager.py index 42ec3c86..ca4d5043 100644 --- a/Src/command_center/communication/drone_connection_manager.py +++ b/Src/command_center/communication/drone_connection_manager.py @@ -1,9 +1,15 @@ +# -*- coding: utf-8 -*- +# File: drone_connection_manager.py +# Purpose: 管理与单个或多个无人机的直接连接(可能通过 MAVLink 或其他协议)。 +# 处理心跳、连接状态和底层数据收发。 + from typing import Dict, List, Optional from .mavlink_handler import MavlinkHandler from .database_handler import DatabaseHandler import threading import time from datetime import datetime, timedelta +import socket class DroneConnectionManager: def __init__(self, db_handler: DatabaseHandler): diff --git a/Src/command_center/communication/mavlink_handler.py b/Src/command_center/communication/mavlink_handler.py index 2674e095..1472c25f 100644 --- a/Src/command_center/communication/mavlink_handler.py +++ b/Src/command_center/communication/mavlink_handler.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +# File: mavlink_handler.py +# Purpose: 处理 MAVLink 协议的编码、解码和消息处理。 + import pymavlink.mavutil as mavutil import threading import time diff --git a/Src/command_center/communication/message_queue.py b/Src/command_center/communication/message_queue.py index 3e3db6cc..cd069032 100644 --- a/Src/command_center/communication/message_queue.py +++ b/Src/command_center/communication/message_queue.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +# File: message_queue.py +# Purpose: 提供线程安全的消息队列,用于模块间的异步通信。 + import queue import threading from typing import Any, Optional diff --git a/Src/command_center/main.py b/Src/command_center/main.py index a81c28ef..77fe26ea 100644 --- a/Src/command_center/main.py +++ b/Src/command_center/main.py @@ -1,13 +1,17 @@ +# -*- coding: utf-8 -*- +# File: main.py +# Purpose: 主应用程序入口点,定义和启动指挥控制中心主窗口 (CommandCenterApp)。 +# 负责初始化UI、处理登录、创建数据模型和协调各个功能模块。 + import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QPushButton, QLabel, QGroupBox, QComboBox, QSpinBox, QDoubleSpinBox, - QProgressBar, QCheckBox, QMessageBox) -from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtGui import QFont + QProgressBar, QCheckBox, QMessageBox, QFileDialog) +from PyQt5.QtCore import Qt, pyqtSignal, QThread, QObject +from PyQt5.QtGui import QFont, QPixmap, QPainter, QPen, QColor from ui.login_view import LoginView -from ui.main_view import MainView -from ui.map_view import MapView +from ui.simple_map_view import SimpleMapView from ui.threat_layer_view import ThreatLayerView from ui.path_layer_view import PathLayerView from ui.drone_list_view import DroneListView @@ -15,8 +19,42 @@ from ui.drone_detail_view import DroneDetailView from ui.status_dashboard import StatusDashboard from ui.path_planning_view import PathPlanningView from ui.algorithm_config_view import AlgorithmConfigView -from ui.path_simulation_view import PathSimulationView from communication.communication_manager import CommunicationManager +from ui.base_map_view import BaseMapView +from ui.map_data_model import MapDataModel + +class PathPlanningThread(QThread): + path_planned = pyqtSignal(list) # 规划完成后发出新路径 + + def __init__(self, simulator, drone_id, goal): + super().__init__() + self.simulator = simulator + self.drone_id = drone_id + self.goal = goal + self._running = True + self._replan_requested = False + + def run(self): + while self._running: + if self._replan_requested: + self._replan_requested = False + path = self.simulator.path_planner.plan_path( + self.drone_id, + self.simulator.drones[self.drone_id]['position'], + self.goal + ) + if path: + self.path_planned.emit(path) + self.msleep(100) # 避免CPU占用过高 + + def request_replan(self, goal=None): + if goal is not None: + self.goal = goal + self._replan_requested = True + + def stop(self): + self._running = False + self.wait() class CommandCenterApp(QMainWindow): # 定义信号 @@ -42,16 +80,18 @@ class CommandCenterApp(QMainWindow): # 创建标签页 self.tab_widget = QTabWidget() + # 创建地图数据模型 + self.map_data_model = MapDataModel() + # 添加各个功能标签页 - self.tab_widget.addTab(MapView(), "地图视图") - self.tab_widget.addTab(ThreatLayerView(), "威胁层") - self.tab_widget.addTab(PathLayerView(), "路径层") + self.tab_widget.addTab(SimpleMapView(self.map_data_model), "地图视图") + self.tab_widget.addTab(ThreatLayerView(self.map_data_model), "威胁层") + self.tab_widget.addTab(PathLayerView(self.map_data_model), "路径层") self.tab_widget.addTab(DroneListView(), "无人机列表") self.tab_widget.addTab(DroneDetailView(), "无人机详情") self.tab_widget.addTab(StatusDashboard(), "状态仪表板") self.tab_widget.addTab(PathPlanningView(), "路径规划") self.tab_widget.addTab(AlgorithmConfigView(), "算法配置") - self.tab_widget.addTab(PathSimulationView(), "路径模拟") main_layout.addWidget(self.tab_widget) diff --git a/Src/command_center/path_planning/__pycache__/battlefield_simulator.cpython-312.pyc b/Src/command_center/path_planning/__pycache__/battlefield_simulator.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4264754223ecba19ef84f21e198df4352655080 GIT binary patch literal 7266 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 I%rz$e3x(}R-~a#s literal 0 HcmV?d00001 diff --git a/Src/command_center/path_planning/__pycache__/hybrid_astar.cpython-312.pyc b/Src/command_center/path_planning/__pycache__/hybrid_astar.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c34980b7530c7cf50e1122e7fb203d31c2a4e26 GIT binary patch literal 11403 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 literal 0 HcmV?d00001 diff --git a/Src/command_center/path_planning/battlefield_simulator.py b/Src/command_center/path_planning/battlefield_simulator.py new file mode 100644 index 00000000..e8f660a7 --- /dev/null +++ b/Src/command_center/path_planning/battlefield_simulator.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# File: battlefield_simulator.py +# Purpose: 定义战场环境模拟器,可能包含地图、无人机状态、威胁等信息。 + +import numpy as np +import matplotlib.pyplot as plt +from typing import List, Tuple, Dict +import random +from .path_planner import PathPlanner + +class BattlefieldSimulator: + def __init__(self, width: float = 100.0, height: float = 100.0): + self.width = width + self.height = height + self.obstacles: List[Tuple[float, float]] = [] + self.drones: Dict[str, Dict] = {} + self.path_planner = PathPlanner() + + def generate_battlefield(self, + num_buildings: int = 10, + num_barriers: int = 15, + num_vehicles: int = 5): + """生成战场环境""" + # 清空现有障碍物 + self.obstacles.clear() + + # 生成建筑物(矩形障碍物) + for _ in range(num_buildings): + x = random.uniform(0, self.width) + y = random.uniform(0, self.height) + width = random.uniform(5, 15) + height = random.uniform(5, 15) + self._add_rectangular_obstacle(x, y, width, height) + + # 生成路障(点状障碍物) + for _ in range(num_barriers): + x = random.uniform(0, self.width) + y = random.uniform(0, self.height) + self.obstacles.append((x, y)) + + # 生成废弃车辆(点状障碍物) + for _ in range(num_vehicles): + x = random.uniform(0, self.width) + y = random.uniform(0, self.height) + self.obstacles.append((x, y)) + + # 更新路径规划器的障碍物 + self.path_planner.update_obstacles(self.obstacles) + + def _add_rectangular_obstacle(self, x: float, y: float, width: float, height: float): + """添加矩形障碍物""" + # 在矩形区域内生成多个点作为障碍物 + for i in np.arange(x - width/2, x + width/2, 1.0): + for j in np.arange(y - height/2, y + height/2, 1.0): + if 0 <= i <= self.width and 0 <= j <= self.height: + self.obstacles.append((i, j)) + + def add_drone(self, drone_id: str, start_pos: Tuple[float, float, float]): + """添加无人机""" + self.drones[drone_id] = { + 'position': start_pos, + 'path': None, + 'status': 'standby' + } + + def plan_path(self, drone_id: str, goal: Tuple[float, float, float]) -> bool: + """为无人机规划路径""" + if drone_id not in self.drones: + return False + + start = self.drones[drone_id]['position'] + path = self.path_planner.plan_path(drone_id, start, goal) + + if path: + self.drones[drone_id]['path'] = path + return True + return False + + def visualize(self, show_paths: bool = True): + """可视化战场环境""" + plt.figure(figsize=(10, 10)) + + # 绘制障碍物 + x_obs = [obs[0] for obs in self.obstacles] + y_obs = [obs[1] for obs in self.obstacles] + plt.scatter(x_obs, y_obs, c='red', s=10, label='Obstacles') + + # 绘制无人机和路径 + for drone_id, drone in self.drones.items(): + # 绘制无人机位置 + pos = drone['position'] + plt.scatter(pos[0], pos[1], c='blue', s=100, label=f'Drone {drone_id}') + + # 绘制航向 + dx = 2 * np.cos(pos[2]) + dy = 2 * np.sin(pos[2]) + plt.arrow(pos[0], pos[1], dx, dy, head_width=1, head_length=1, fc='blue', ec='blue') + + # 绘制路径 + if show_paths and drone['path']: + path = drone['path'] + x_path = [p[0] for p in path] + y_path = [p[1] for p in path] + plt.plot(x_path, y_path, 'g--', label=f'Path {drone_id}') + + plt.grid(True) + plt.legend() + plt.title('Battlefield Simulation') + plt.xlabel('X (m)') + plt.ylabel('Y (m)') + plt.axis('equal') + plt.show() + + def simulate_movement(self, drone_id: str, step_size: float = 1.0): + """模拟无人机沿路径移动""" + if drone_id not in self.drones or not self.drones[drone_id]['path']: + return False + + drone = self.drones[drone_id] + path = drone['path'] + current_pos = drone['position'] + + # 找到当前位置在路径上的最近点 + min_dist = float('inf') + next_idx = 0 + + for i, point in enumerate(path): + dist = np.sqrt((point[0] - current_pos[0])**2 + + (point[1] - current_pos[1])**2) + if dist < min_dist: + min_dist = dist + next_idx = i + + # 移动到下一个点 + if next_idx < len(path) - 1: + next_point = path[next_idx + 1] + self.drones[drone_id]['position'] = next_point + return True + + return False \ No newline at end of file diff --git a/Src/command_center/path_planning/hybrid_astar.py b/Src/command_center/path_planning/hybrid_astar.py new file mode 100644 index 00000000..072dd3cf --- /dev/null +++ b/Src/command_center/path_planning/hybrid_astar.py @@ -0,0 +1,229 @@ +# -*- coding: utf-8 -*- +# File: hybrid_astar.py +# Purpose: 实现混合 A* 路径规划算法的具体逻辑。 + +import numpy as np +from typing import List, Tuple, Dict, Optional +from dataclasses import dataclass +import math +import heapq + +@dataclass +class Node: + x: float + y: float + theta: float # 航向角 + g_cost: float # 从起点到当前节点的代价 + h_cost: float # 从当前节点到终点的估计代价 + parent: Optional['Node'] = None + + @property + def f_cost(self) -> float: + return self.g_cost + self.h_cost + +class HybridAStar: + def __init__(self, + grid_size: float = 0.5, + max_steering_angle: float = math.pi/4, + steering_step: float = math.pi/12, + speed_step: float = 1.0, + max_speed: float = 5.0, + min_speed: float = -2.0): + self.grid_size = grid_size + self.max_steering_angle = max_steering_angle + self.steering_step = steering_step + self.speed_step = speed_step + self.max_speed = max_speed + self.min_speed = min_speed + + def plan(self, + start: Tuple[float, float, float], + goal: Tuple[float, float, float], + obstacles: List[Tuple[float, float]], + vehicle_length: float = 4.0, + vehicle_width: float = 2.0) -> Optional[List[Tuple[float, float, float]]]: + """ + 使用混合A*算法规划路径 + + 参数: + start: (x, y, theta) 起点位置和航向 + goal: (x, y, theta) 终点位置和航向 + obstacles: [(x, y), ...] 障碍物位置列表 + vehicle_length: 车辆长度 + vehicle_width: 车辆宽度 + + 返回: + 路径点列表 [(x, y, theta), ...] 或 None(如果找不到路径) + """ + # 初始化开放列表和关闭列表 + open_list: List[Node] = [] + closed_set = set() + + # 创建起点节点 + start_node = Node( + x=start[0], + y=start[1], + theta=start[2], + g_cost=0, + h_cost=self._heuristic(start, goal) + ) + open_list.append(start_node) + + while open_list: + # 获取f值最小的节点 + current = min(open_list, key=lambda n: n.f_cost) + open_list.remove(current) + + # 检查是否到达目标 + if self._is_goal_reached(current, goal): + return self._reconstruct_path(current) + + # 将当前节点加入关闭列表 + closed_set.add(self._get_grid_position(current)) + + # 扩展当前节点 + for next_node in self._get_successors(current, goal, obstacles, vehicle_length, vehicle_width): + grid_pos = self._get_grid_position(next_node) + + # 如果节点在关闭列表中,跳过 + if grid_pos in closed_set: + continue + + # 如果节点不在开放列表中,添加它 + if not any(self._is_same_node(next_node, n) for n in open_list): + open_list.append(next_node) + + return None + + def _heuristic(self, current: Tuple[float, float, float], goal: Tuple[float, float, float]) -> float: + """计算启发式函数值""" + dx = goal[0] - current[0] + dy = goal[1] - current[1] + dtheta = abs(goal[2] - current[2]) + return math.sqrt(dx*dx + dy*dy) + 0.5 * dtheta + + def _is_goal_reached(self, current: Node, goal: Tuple[float, float, float]) -> bool: + """检查是否到达目标""" + dx = goal[0] - current.x + dy = goal[1] - current.y + dtheta = abs(goal[2] - current.theta) + return (math.sqrt(dx*dx + dy*dy) < self.grid_size and + dtheta < math.pi/6) + + def _get_grid_position(self, node: Node) -> Tuple[int, int, int]: + """获取节点在网格中的位置""" + x = int(node.x / self.grid_size) + y = int(node.y / self.grid_size) + theta = int(node.theta / self.steering_step) + return (x, y, theta) + + def _is_same_node(self, node1: Node, node2: Node) -> bool: + """检查两个节点是否相同""" + grid_pos1 = self._get_grid_position(node1) + grid_pos2 = self._get_grid_position(node2) + return grid_pos1 == grid_pos2 + + def _get_successors(self, + current: Node, + goal: Tuple[float, float, float], + obstacles: List[Tuple[float, float]], + vehicle_length: float, + vehicle_width: float) -> List[Node]: + """获取当前节点的后继节点""" + successors = [] + + # 尝试不同的转向角和速度 + for steering in np.arange(-self.max_steering_angle, + self.max_steering_angle + self.steering_step, + self.steering_step): + for speed in np.arange(self.min_speed, + self.max_speed + self.speed_step, + self.speed_step): + # 计算下一个位置 + next_x = current.x + speed * math.cos(current.theta) + next_y = current.y + speed * math.sin(current.theta) + next_theta = current.theta + speed * math.tan(steering) / vehicle_length + + # 检查是否与障碍物碰撞 + if self._check_collision((next_x, next_y, next_theta), + obstacles, + vehicle_length, + vehicle_width): + continue + + # 创建新节点 + next_node = Node( + x=next_x, + y=next_y, + theta=next_theta, + g_cost=current.g_cost + self._calculate_cost(current, (next_x, next_y, next_theta)), + h_cost=self._heuristic((next_x, next_y, next_theta), goal), + parent=current + ) + successors.append(next_node) + + return successors + + def _check_collision(self, + pose: Tuple[float, float, float], + obstacles: List[Tuple[float, float]], + vehicle_length: float, + vehicle_width: float) -> bool: + """检查给定位置是否与障碍物碰撞""" + # 计算车辆四个角的位置 + corners = self._get_vehicle_corners(pose, vehicle_length, vehicle_width) + + # 检查每个角是否与障碍物碰撞 + for corner in corners: + for obstacle in obstacles: + if math.sqrt((corner[0] - obstacle[0])**2 + + (corner[1] - obstacle[1])**2) < self.grid_size: + return True + return False + + def _get_vehicle_corners(self, + pose: Tuple[float, float, float], + vehicle_length: float, + vehicle_width: float) -> List[Tuple[float, float]]: + """计算车辆四个角的位置""" + x, y, theta = pose + half_length = vehicle_length / 2 + half_width = vehicle_width / 2 + + corners = [ + (x + half_length * math.cos(theta) - half_width * math.sin(theta), + y + half_length * math.sin(theta) + half_width * math.cos(theta)), + (x + half_length * math.cos(theta) + half_width * math.sin(theta), + y + half_length * math.sin(theta) - half_width * math.cos(theta)), + (x - half_length * math.cos(theta) - half_width * math.sin(theta), + y - half_length * math.sin(theta) + half_width * math.cos(theta)), + (x - half_length * math.cos(theta) + half_width * math.sin(theta), + y - half_length * math.sin(theta) - half_width * math.cos(theta)) + ] + return corners + + def _calculate_cost(self, + current: Node, + next_pose: Tuple[float, float, float]) -> float: + """计算从当前节点到下一个节点的代价""" + # 距离代价 + dx = next_pose[0] - current.x + dy = next_pose[1] - current.y + distance = math.sqrt(dx*dx + dy*dy) + + # 转向代价 + dtheta = abs(next_pose[2] - current.theta) + + # 总代价 + return distance + 0.5 * dtheta + + def _reconstruct_path(self, goal_node: Node) -> List[Tuple[float, float, float]]: + """重建路径""" + path = [] + current = goal_node + + while current is not None: + path.append((current.x, current.y, current.theta)) + current = current.parent + + return list(reversed(path)) \ No newline at end of file diff --git a/Src/command_center/path_planning/path_planner.py b/Src/command_center/path_planning/path_planner.py new file mode 100644 index 00000000..26e6dd13 --- /dev/null +++ b/Src/command_center/path_planning/path_planner.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# File: path_planner.py +# Purpose: 路径规划的核心逻辑,可能包含调用不同路径规划算法的接口。 + +from typing import List, Tuple, Optional, Dict +from .hybrid_astar import HybridAStar +import numpy as np +import json +import time + +class PathPlanner: + def __init__(self): + self.planner = HybridAStar() + self.current_paths: Dict[str, List[Tuple[float, float, float]]] = {} + self.obstacles: List[Tuple[float, float]] = [] + + def plan_path(self, + drone_id: str, + start: Tuple[float, float, float], + goal: Tuple[float, float, float], + vehicle_length: float = 4.0, + vehicle_width: float = 2.0) -> Optional[List[Tuple[float, float, float]]]: + """ + 为指定无人机规划路径 + + 参数: + drone_id: 无人机ID + start: (x, y, theta) 起点位置和航向 + goal: (x, y, theta) 终点位置和航向 + vehicle_length: 车辆长度 + vehicle_width: 车辆宽度 + + 返回: + 路径点列表 [(x, y, theta), ...] 或 None(如果找不到路径) + """ + # 规划路径 + path = self.planner.plan( + start=start, + goal=goal, + obstacles=self.obstacles, + vehicle_length=vehicle_length, + vehicle_width=vehicle_width + ) + + if path: + self.current_paths[drone_id] = path + return path + return None + + def update_obstacles(self, obstacles: List[Tuple[float, float]]): + """更新障碍物列表""" + self.obstacles = obstacles + + def get_current_path(self, drone_id: str) -> Optional[List[Tuple[float, float, float]]]: + """获取指定无人机的当前路径""" + return self.current_paths.get(drone_id) + + def clear_path(self, drone_id: str): + """清除指定无人机的路径""" + if drone_id in self.current_paths: + del self.current_paths[drone_id] + + def smooth_path(self, path: List[Tuple[float, float, float]], weight_data: float = 0.5, + weight_smooth: float = 0.3, tolerance: float = 0.000001) -> List[Tuple[float, float, float]]: + """ + 使用平滑算法优化路径 + + 参数: + path: 原始路径 + weight_data: 数据项权重 + weight_smooth: 平滑项权重 + tolerance: 收敛阈值 + + 返回: + 平滑后的路径 + """ + if len(path) <= 2: + return path + + # 将路径转换为numpy数组 + path_array = np.array(path) + + # 初始化平滑后的路径 + smooth_path = path_array.copy() + + # 迭代优化 + change = tolerance + while change >= tolerance: + change = 0.0 + + # 对每个点进行优化(除了起点和终点) + for i in range(1, len(path) - 1): + for j in range(3): # x, y, theta + aux = smooth_path[i][j] + smooth_path[i][j] += weight_data * (path_array[i][j] - smooth_path[i][j]) + smooth_path[i][j] += weight_smooth * (smooth_path[i-1][j] + smooth_path[i+1][j] - 2.0 * smooth_path[i][j]) + change += abs(aux - smooth_path[i][j]) + + return [(x, y, theta) for x, y, theta in smooth_path] + + def save_path(self, drone_id: str, filename: str): + """保存路径到文件""" + if drone_id in self.current_paths: + path_data = { + 'drone_id': drone_id, + 'timestamp': time.time(), + 'path': self.current_paths[drone_id] + } + with open(filename, 'w') as f: + json.dump(path_data, f) + + def load_path(self, filename: str) -> Optional[str]: + """从文件加载路径""" + try: + with open(filename, 'r') as f: + path_data = json.load(f) + self.current_paths[path_data['drone_id']] = path_data['path'] + return path_data['drone_id'] + except: + return None \ No newline at end of file diff --git a/Src/command_center/path_planning/test_battlefield.py b/Src/command_center/path_planning/test_battlefield.py new file mode 100644 index 00000000..5883af21 --- /dev/null +++ b/Src/command_center/path_planning/test_battlefield.py @@ -0,0 +1,138 @@ +import numpy as np +from battlefield_simulator import BattlefieldSimulator +import time + +def test_single_drone(): + """测试单个无人机的路径规划""" + # 创建战场模拟器 + simulator = BattlefieldSimulator(width=100, height=100) + + # 生成战场环境 + simulator.generate_battlefield( + num_buildings=8, + num_barriers=12, + num_vehicles=3 + ) + + # 添加无人机 + start_pos = (10, 10, 0) # (x, y, theta) + simulator.add_drone("drone1", start_pos) + + # 设置目标点 + goal = (80, 80, np.pi/2) + + # 规划路径 + success = simulator.plan_path("drone1", goal) + print(f"Path planning {'successful' if success else 'failed'}") + + # 可视化结果 + simulator.visualize() + + # 模拟移动 + if success: + print("Simulating movement...") + for _ in range(10): # 模拟10步移动 + simulator.simulate_movement("drone1") + simulator.visualize() + time.sleep(0.5) # 暂停0.5秒以便观察 + +def test_multiple_drones(): + """测试多个无人机的协同路径规划""" + # 创建战场模拟器 + simulator = BattlefieldSimulator(width=100, height=100) + + # 生成战场环境 + simulator.generate_battlefield( + num_buildings=10, + num_barriers=15, + num_vehicles=5 + ) + + # 添加多个无人机 + drones = { + "drone1": (10, 10, 0), + "drone2": (20, 10, np.pi/4), + "drone3": (10, 20, np.pi/2) + } + + goals = { + "drone1": (80, 80, np.pi/2), + "drone2": (70, 70, np.pi/4), + "drone3": (90, 90, 0) + } + + # 添加无人机并规划路径 + for drone_id, start_pos in drones.items(): + simulator.add_drone(drone_id, start_pos) + success = simulator.plan_path(drone_id, goals[drone_id]) + print(f"Path planning for {drone_id}: {'successful' if success else 'failed'}") + + # 可视化初始状态 + simulator.visualize() + + # 模拟移动 + print("Simulating movement...") + for _ in range(15): # 模拟15步移动 + for drone_id in drones.keys(): + simulator.simulate_movement(drone_id) + simulator.visualize() + time.sleep(0.5) # 暂停0.5秒以便观察 + +def test_dynamic_obstacles(): + """测试动态障碍物情况下的路径规划""" + # 创建战场模拟器 + simulator = BattlefieldSimulator(width=100, height=100) + + # 生成初始战场环境 + simulator.generate_battlefield( + num_buildings=5, + num_barriers=8, + num_vehicles=3 + ) + + # 添加无人机 + start_pos = (10, 10, 0) + simulator.add_drone("drone1", start_pos) + + # 设置目标点 + goal = (80, 80, np.pi/2) + + # 规划初始路径 + success = simulator.plan_path("drone1", goal) + print(f"Initial path planning: {'successful' if success else 'failed'}") + + # 可视化初始状态 + simulator.visualize() + + # 模拟移动并动态添加障碍物 + print("Simulating movement with dynamic obstacles...") + for i in range(10): + # 移动无人机 + simulator.simulate_movement("drone1") + + # 每3步添加新的障碍物 + if i % 3 == 0: + # 在无人机当前位置附近添加障碍物 + pos = simulator.drones["drone1"]["position"] + new_obstacle = ( + pos[0] + np.random.uniform(-10, 10), + pos[1] + np.random.uniform(-10, 10) + ) + simulator.obstacles.append(new_obstacle) + simulator.path_planner.update_obstacles(simulator.obstacles) + + # 重新规划路径 + simulator.plan_path("drone1", goal) + + simulator.visualize() + time.sleep(0.5) + +if __name__ == "__main__": + print("Testing single drone scenario...") + test_single_drone() + + print("\nTesting multiple drones scenario...") + test_multiple_drones() + + print("\nTesting dynamic obstacles scenario...") + test_dynamic_obstacles() \ No newline at end of file diff --git a/Src/command_center/ui/__init__.py b/Src/command_center/ui/__init__.py index db1f1c7a..94f9d52d 100644 --- a/Src/command_center/ui/__init__.py +++ b/Src/command_center/ui/__init__.py @@ -1 +1,7 @@ -# UI组件包 \ No newline at end of file +# -*- coding: utf-8 -*- +# File: __init__.py (in ui) +# Purpose: 将 ui 目录标记为 Python 包。 + +# UI组件包 + +# UI package initialization \ No newline at end of file diff --git a/Src/command_center/ui/__pycache__/__init__.cpython-312.pyc b/Src/command_center/ui/__pycache__/__init__.cpython-312.pyc index d489b6e556247ccac36458fd5e38eff3f1978646..c30c332363c2ec9c14e4f9772965d3d403b44cd2 100644 GIT binary patch delta 21 bcmbQrIF*t2G%qg~0}x0&SIp3x$lCz`Gxr4r delta 21 bcmbQrIF*t2G%qg~0}z;fW6j{3$lCz`G$;i2 diff --git a/Src/command_center/ui/__pycache__/algorithm_config_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/algorithm_config_view.cpython-312.pyc index cc912ff5e539c1e0dd37289c610629b3949c1db9..f6809f9dd78eead1f461364c6cd0eccfb4f9d792 100644 GIT binary patch delta 563 zcmdm?y+oJqG%qg~0}veRSIU^jKao#@F>a!|xp6K}6b~aq3PTE04sR}B6dxl46N5WL z3UdoX3QH>EYG#lY28Jm9N>)v_mmsDl&&1vau3OxJzNy8%*e?2or#Ij hkMTPLg8U3-e`RA}l=;Gv%*e<)L;Wi-JQyK10suUTiyZ&} delta 350 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

XBMQ^;5FIu;;0@0#Z9k2y6yJ_5U*K z3?)3S_%2}?@Un@U{!badR4u?4*cgGo}YOgMO`KvSjicG$-;_tiUAqjZ;8-#%} zs&Gh>2ZSX)M1KK!h}6gc1W$Atjpnynjb=piTLQypeE2nS_#tulpNSckm|=*SZ}l^J aP05YcZwZWldzmCOt^Z7PCWubHhyMp}@aEb8 literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/battlefield_map_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/battlefield_map_view.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82d15880da55d3c67cebe8b70c1fbde66e54fd26 GIT binary patch literal 6855 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 literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/drone_detail_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/drone_detail_view.cpython-312.pyc index ccde15eb02ecad6429178da3ca4d9a6703fcb610..285dc9ce0efe4573d4d07fc663788cbf083fccc0 100644 GIT binary patch literal 21035 zcmdr!X>glGmb(4fl6=ID<2XL#E4CclfdC;G;#?*Nhatx#Sr3Cy{3*5^$ugfLh`kPk zC4d71Nfu^e#z16Xh%?M2W{Sg=5Xf$A)l#*!RZ^ssTwl3rv$ekRYuJR$OwF%-ulu|7 z`BM@zyH#7$RqC&=_qzMN?$@uoU+drO_ADK^w~tkJRo<`D{TD_=XBGlCK7_zY9jjyY zKAm6RuJ;?-4Sr+0(Qj%u`OWQSzop&c&uGu^GwqB&vpv(F)t=?Iwp;zScAMYcZr3BM z!I$mNY0vTJw&#+#(U<36)V|1{-=4468FX88tm%l3HG6EI8fm@l1tir1sTqpY#Uz!1 z)J%_FDqGl><+NTyK&PG0*uKZhc6vg*b^Gp(frF2^`vPH%Y?ULH?T@+ld3=!gWH{Kh zF&qj7dic!kTev{D7fM0;<^bmxAZ^>9qWi1Cc=tzNe;&iUrX50vzCa((Utv zLY@xVed-Ie(A{W&_-nugkFH(M>e>ygX;8Oc&+3n4wi{W)5na29HG*#*)E(5fTXyQ4 zW2J{0umq9Et zV1Ss|G~Iv^VkRli1Tk^w=mvD%b}>i(ekug@x<1_*eVa3bHwVML9w36t$xr0HME0_W44&n~~Wi8wMR4*6k_H=}#?@q4csg8i(@9tq;9RTa$o(g-PV!1$% z$HjU=Zm-YvoY(VwQ*R%)6j1dSsp#A!b|MpUgZF{==fk>5#(p#-#uN<|CK$)W;^INm z&$A|$R>CJ+{1g;HEOUaf9kU&^y_tOj0CgP#2QaKd8WWz)04Gi&ZI!vC2yji;E%8Z6 zf{)C}ki;923y1U}Bnfe1S_(&X-Hezof4i-6SPVhkHZd%JAvIf$iJ?!0R>IiQ#-Asa zl)o&D!tOk#>g+AsmFn zu_m!yhm49;X=WWV4H&y|w5nWpxheso&=SY|R1rVW(X;?emAAxX=&qTcKVVeUrCmbm zElLb6dJZTluIsMVs6{W=(x-D;+IYR=8jkZY4*%)HUpL=GIW{|TcKYnENO1btFK50S zbQbX0K~HFpw}%ZpzYB#2l?ONpIg~+s7Lg;aAc$<9fucLyJ)Is|UhtWawllnczxN;& z9af09dOEy*w=c+B+$_5z@H~gJhr{W@XLR_y9S1xt1Oh!hAV8o@M~9~uzMKaI3ZDru z!b*g=>1uMu%pq@ii;pf#8T-iM6XY`k2V6AF+dJGn9Uh+$4Pu={UUA4Zt_-~XC2FA~ z2t%ATK^@TRunr_il35mGmJPL@dgR?l5=`x6Udh{~gO;h{x_EIt2%8)fHaSOgl8ht9 zI3fk7%SOt^3cf1)tSrH-m+~rNOhsfvggxyY@kV|cS(#u~NqJQ*EGUmK@dD>y=44S-WNW;ranOF1$xAXzW6aVdLy%XW zUNN!)Kqi>QNv1T$lqQ*)7*hjP9vC?Q)m>$viCBKfnP93Xq*!Bushbcg?o2RcSD`!E zN3)MVcZDhAs(=zH;u8f95ud9eK*T3})vjAw*5n*fDDkuNgi$`Bf=2XXWs^dA!qs(&BdRkisu8KXd zki)8&<#PE0Y}kiko6GfV*zKb^WF;aagIkMjvkS`|j3S@Q1#PUr>adOL=^(QuY=33eaEYtx%$%z#DF*=F;Nq3(w;ARDlL|6j|YD9~_ z8?E4}G!oD^h(@B24ImArm5jg>Ph=Hp7Lhy~`Y=i-_@f6$@OBE52epKi@&75xffY2w7b!1^rpR7^|JzjWP7%}#*s zM!~0YouGz)sI1E>7Es`oZ~=uiR`c#05k~&9Mp)+rUXt}(3r=f2m)60wQ2BB#Vu-cK zD+H`EI6hU5&49K`{g5*iZPbQiwEHZ>g(Mw8q!_IBk_NzLiYl@l?M)&9;gIaFvMs=#FEu9}~(TJ5z;q^|ZFd{C|SH5#=jMkB2D*0!5C zbmq=}Gkfvu+~=p}E?#t+!#E>mPDaAW(Yeo0&mKJ!#!}NizcBmDlMtKx;s{BY`NfHu zW1j=?^vAD}gxTQ}GjE-y2^@~kYb<#EM#q6pZaIW*qFz9&nf>g-^apP=D^jk#_U5(M zjx}?rqi}WLIrBLb+8j3P{<75Jq0UECK^%yObyo|DPCjwsiDbdbSi#D8!QB&u#mU0uvBKra!iHF3 zL%gu*zs<(ntnYQk%q*o|!nI&a)}gl^JrsCU1=)e24d`tIPpNjaVj_At8)UV^0BQ$Q z3~I+3lUb+(3w6LxawZ{k<1Gj%k3tMOkI`fDNGnjgnaz_9M=a!UB!e7|FnGk&p2?cQ z&tmgf%MpFMmCb;djfDfHb~~F1em0v0ehzB|KbN(EpU2w4U&LmEpU>uiU%=*qznC1h z6axPi@j0z{KF~^z2X@2hK!>Jk^8z$oTeT4f1a6#QahIT3*!C%oT69{=x&l_GwJn$P zMWx6hD@D8w&pKRi+5v}D9G@$UHy1tc@Un!Xd_L@7!o4W!{BZWt;SPBNJ)$PH8#2=}t?5NtOIzwm4-2x6fPR#TcW@E102qL9;LH5bwCPJ-jMwuVWKc)c!sjn%km~*sym2nhaEoMg8U33{SGw~)XZd| zR1kNv$btOhq2!W=*pi0J)&@fA%9yP(vSxHwvau!B*ph7A8f)D8T~WMo#}(Vo?~OWJ z<9vGKMv&0^6A0Xf-WvxDTJ&c1aH1xNu0zH&WsF{(RK_IlY_w*`09*>N<^kidj&CML-->^SE1Be6s6A*HGVRWxTL)aLdHvr6>2E*gMoUYKSjhF}Qiswj^n* zirK0nt)orjTP|(6^g#5njwtJkcKD;sJy&dj?=vL!OREf2gwuqu|5XT3_PZcN9ZWA} zSs|5c(oWafn9I}a5Q;+$l?W!QG?TyT(usdPe;cKJ(^ z`PH%f>STUXEWc^=U_5`#U~9_cyJB0FMz*2yuOzmnWaIABCNEc~ovulCP0WPUSU)&Q zY9i-w{h>o2c1_cRU(#vO20{x*B9&z}SU2EWgw88boJmU(YOr+BBX&`u@dW}9tT+Lm z2;5&ipY8;8N~z}oPp}z0VWDwcwmKrak~OO@*Q{17D)lj2{ph{P73*Uw)?aFfKDqmf zZ4bz~<@ZOe4l3$kOC&iQeT4jHAplojQXUI;oU8#4{Xaz(fjgv!BL;y~G==)2P8Gs= zL!6cXBa2fFV8A`2%83}re6XrAAxcD_!K)XAB{0g;XK`lS#J#VeCVV)ZXaBqg;@{TEVP}mkdr>%>^bP^aBMxnkHo3w9C=q-Tyu-SwJV&H#EN`rJylOlY&ubmDOjz@dJ#_S;H(K7? zlPqnFl{Q|sHck{2Ckv`$1=aC_<%16sj@HC%HIbgN=P#L~t2bY2?h zUc-jN*9B`-pXwge-f*-*Z|5)dEIC|g1S8SWpa0SH^iZ8*4Ll$));smkxzaZkOex!&?UGfcm23S$H zKx-+nh4YLyr-d9DaJUu_>BSwy5IJQ)!6ht19BvdiJiAGqBCkM-Hamn0P9Vr8@K9)# z4S}$#P9v=HmnQS;WBK**{03pIC&J1<2K$(11qYDgqLcP97Fc%fOtI2j$qiaclTzar z`AN>wQJYt}1z-h}5^>o{!J4H)3DE^$HKoCd3!2I;0IM190|ez1_6Zp(O8{K#RBi#d znKUiVRiT9F0l^O)8WS)wB)Lo+D*4C1l?9l{+0_ye*`M zXwps|PCRE`I>#H}%y~cL@@6n~02571Fysz}gM7w5H&{nrz;qOi<-_*7oP!5@$_``@dkg8xAwwWENG6tKoGQgIO{v$ zBABftD4?*D2pA#~E7LY-5j{|3ysXPd>{K&A`6O3BUD!q|p^T7aqd0_^U7(T>wGtYM z5ke^;k}c9pi04QI6AOrB6QK#ogos_BnGjthk_o#eq!oy1!c8dch?J^qHFpwwg*I8c ziK(YuAfSLLPeDQiNVx;xrK*F^KuUjwT05V2bUTEA{|<_UeD?Fb zMVDzuD<^8C1eQ)+?{-B(qdVedcO{s+6}O{;IaSd}(P-`1s(8g}as>)E7%4_ibt847 zYsPlOE7v5L2MBa=j44hs&Jw=U#~UB{ z?w)AF6W?*s`W*>or@CbIxw{Enul{y|gpx-1c2wwD!>i^H@raAfR+M-Pmz zir2Ixm~|>laKnxaw2yP~+KmZj6UnCSA^fG}b^-6I*I!{a%s)nvHi-WSO0DuNOgf*^ z%O**(p%R$ANUD-l(kL4e!*U{QFO{amY4RmZY4ULkMO>PENj*!GFKvd>V4kC4 z#&UZj;52Yx3g4}l&kS%Ho1)vof#LpbOs%!+LvtyOEzND~LvtyOO^X7~ zplJlGG$ht{ONLS+m%-Fo8a;ZQV)V)8Rf>xvY4n-XaMv#4O_MKe5!2*L25)KdCCa7A z&lBq(Fl&ykETE#sRzyKXZ8$5yX>2lX3ugm3jcvqj;p_nC5c?v3Zwr?Va2i_x1)NE9 zlqih@wMU6!ldBjdIiln_WYLgO8ELpAjpH==k|AQ6d})h4kfF)tT%ba=Of@m2pn^7B z9>8gAb#4o{2;elfF$y?~Ce<@U>Y*iy=G;`6Un;1cFTx#SG^m~#S|RmV2*A1^9Ueei zwYD#48K-h=0lQey2W_~!RX`AB3&i$;M3L5ulvPTSLuvA*IXaN3$>BonUMO0t4Yvf~ zR?TnmZQ+Ul4ow7AP65Yga#)h0X*tX&XeAwGvP;F(Ls=Rewx*$)^b{sdz9hjkM~Gw? z3w>=?brhoAcQQh>;BGTQ2CSMKE{0a1$%V=(D6S2+4B*zNfFOEXxDxGNE8wy;Icya< zm6pSjNpi~iS}MXFvdI?(()wx-trhzqrDccE{*O4eOoWD+?GO@miu`g#euYN9Ly<47 z7Yg_aMShhCKai~{qjcTt#S-#Yga@jmgW4#U7DIOc)hd>izjpw&SuQPxWGD?<=O}1V zDV79UORI_^zeY~W6QRLT(tsBTmL+h<#3Hd9+1Fh3RJtCJDLw5l6 zm{?Z+-T_pbTv`k%pzPh-#Z>t_kbT<{lBNN@v}w*ny8?&yYHCxI&}rh34AY<%UAG3U zyLZT~7DL@X5kvA9pm)nLF_coj22U1%eXod9{-$8p;N1eSx64??PzrVp9xnjVmrL86h+Z@hsQW!8mx2C^7PiiL(r&hri zzMT8^RC5>)7N*8Ooc{SMvv0j7^y9*X>DNbuejvmYNf6$)P^>gXXo6g3?%QEf4>pQg ze_eb3!5%fMp?<4d+Mh~wZ=o=XF8s^s+{RF+!=#8Rx z2EE^)hZ+g@TlA1Fmgyg#}C$Rc(PBTxo8j~9g<^VJEqWMa*XC+R6Hn`62xP1Ou!ey^jWat z6%blkacI}Wy^P)~;PDwk>v$8kjJFD{>I(Qhe74-IU?|Y*%%u;Et$sKBcY^TBICq~r z=LT-)}7Yf+LW>aYC4n)Y#LW@8F)=I)+2053fK=jH( zLc%M1+;mp)Fp2AIeZ$87Kjv@77yYX4450HIBHFeCmsDu)W`k8YpL^) z8QcM55iCCnE8g}GT7R(UN@^lc$Cuq3Wr`-3RK9&^&^A?6HpC5Q!K*3zIq@>iJx;XU@lk$X?C9a&5648*i;ikDFnQc04! zCRR&t2vW*E5UYJ4!8EJN3U90A)F;pgd~K`|YHPWQ@43kB!b1t>t_dceUSveeni9-P z=y`Efq$R#|)nN8yam{e!V9r!ob!5kIH}-1TWO+?w-*7*MOD2`+iXJMdaFtn-Wbnir zOqHvX^=o7G*n)}F)RtI1J=4B}vdyvj<^=Pg3d2LOdg#mAt4yKrh`pRpRp{AJPl9QL zjxDW>G{uYV9<)y`tsX8N%$};O8(s7fJb_Oz<&)L*qpLn5Y^+d*2}*p7J`@-YC0A~a zt=vrRJ)j~vb!%)T-V;n<_487%;&XPqrif$%eI;@a`4db7G^((C=!N*=ropU9I`pQ> zR}8;E=t~aq6_*a~(R}E~yi|DoKBXLY6}6*=kKmt0$V+&j0A2$Ots8AkHmr{|&|8R< z)RtHSrM{T@&}c!@xi03!i>9m0q9lWD82TxB^i$e{#t--KLBdJ!p=N!AK; z8QM|R^ig9J2Bl-gXj|OzAXZVDQr~2G{b)|FyGe2>gjc%B2Abp z6UuVZ3{;d8(xf>Rt$>G~tH~J0JI81Rl}70YxA;E{f1hBggbOYCUmcQ6V~lAWWs^+j_ zI%ShDbftftfIqq*@=^G64CPH@vTjYRPUF5-Y;|%)OKe5U6=vQ1n>N%n@HFiP9`-43 z+O*zQz|$9eOoQdYf$cX~Inu|`)32YJd-)>#|H<6hkHfgS%@OTPc*9ahv!}m#W%}#~ zvu}+#<#S70wGK=e_s(8WQI(j)Jc7FkIl>!0Ryl_ac~OM3R{zJ<-*>uP}T5FZ#zt zp*GSds%EC^6BpC!%th=>fqJD!eQ6u?gu9F|Rp6!kDac>X6uHrJL?K>SqrbsM&$AU) zsCSR2Z#y1ilL0jYfjb*eX#7H*)=OnYF6!qh*AMmGx`gu4grX#>{whZvMij}1j9UM( z1XBS2S3)rbU=o@^zoB_`1;h`x9?7my-K$$ zGe-~4ymxf=oiE^3-ygnyiE^_VaVhpR>3Hfi)2Cn=&%3!EunXsnf9DDEhPD8iZtzDj z<$#DcbDmybU(f(y@r~cN5biHikJVH~emihpcmp<>yDXVo70ZQJUD5iMiR!M&>XoOT z7O5QgJA6 zIGm>(j(ZNNp#4sK7v`Y16Mn$}c<}c$|5@mW#y%N>9H&$`+sE2 z=6!lf9Be_Ga|!&FUf=f6eNEfp@1^jM1%n(8O1h;bJ4l=@^v@jR9WL32S}_Cn)Z||w zsPTFZM0uNtaZ`ZvaJYTp2+gfx`j)VlTZGAY@krk9P$PJ3(itkq$OeaJ9-@B<)WThb zEEqGo;G^JuZ`AAce=->L&Ohly?8~nCRB9uEpb8T7!XZ*Yf)gNtA_b*d_0R*U;FOZIq~ZX~tnDO)9*~&B zx8Hm-^MC*RJ2Uei;#>8;?Kb7>%_^NPxrn&i9j8vpDx;XAmxG`eT3rJI2{J7^YZD1lX!V5ZRttl}v# z!K%y@N^mrIf>$|$g31$gsRBW98lA%l_fg_tmme~+8SRi}CDZz`w00J7<<6#l688$8 zMOMiXUBY?vE|1WBQNbt=*PDXy*V7_QGGYg7^JeahX4s;lq%&ztQEZp<&820==1fgL zVY?M2r6)~OQB1l6_><|!74UkXCM1xq86xoL0=gqgmt2ct)spxW0Ncpi5x^Jyn#;4N zDar$1)sNfVGsCvYO$9h zthCEqo_9j}p+e%Ie~%wly&krx@$Ni2T?IFJKiv10l~j1UgIZGaTOIY7bBwNbmcy_w z0HcDZ*QzbnXR}am307T6Q@!)J!1G9{EUDYZrORDG&(?GwgdgjdLvL3Q3vi|*02_Q= zfpH|!Mv`dFR(H?i-4@zq@#zR$6#QPHl!NmNy+QDV${^XY3%=ROW>@11!WlsodpZ7wQ9N5&N&xK%hpF1>xJ8oXF-Z z+fAfb(&|agfp+k=r;5jg&_bh+#5%3lESf-ukB}Sa1~zsOV$!+c!WQ55v1A<8-Kp!v6~P42f`l_dgz=Prh)BatC zCuiLX=MOhj93gAAvX;x>BX`qe1r_NXH1<-6P@s7ktpseT_*A7dT~`zt8-AiX_2?~B zm>z0_00u>`+U=2XpDHNwaqXddg-ZSUyBnAO!b zW({Nw&7jB4nNZjdt+L~1i~Gm3YEIV<8hu2gzuIgeaG%8(Z!j45;SEIbHwRbH?g!jq OjHUNZK1B5EIR6bb34@aW diff --git a/Src/command_center/ui/__pycache__/drone_list_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/drone_list_view.cpython-312.pyc index 48140a8a62079ece02bf23b388f18813ba91f3b4..d00e522941152162b4af8f6213b05210976bf1ba 100644 GIT binary patch literal 28046 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 Aa{vGU delta 1519 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 I(5YR&16IA(^Z)<= literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/map_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/map_view.cpython-312.pyc index 4813c46ccc58c486da7a5d69039f2c01dce76725..009cdeb3bf91eaace30132119cc1b4f8a0e2c39c 100644 GIT binary patch delta 20 acmZ24yOVlm*%V diff --git a/Src/command_center/ui/__pycache__/matplotlib_canvas.cpython-312.pyc b/Src/command_center/ui/__pycache__/matplotlib_canvas.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..194222266e7b9ab0bf1a3074f62fd5dbe6f45fc3 GIT binary patch literal 1037 zcmZuw&1(}u6o0cH>83VnY1H(fEmfp@u!7KwUnqk1VyV>A5*Ri+lVt1WqcfY@mOb=P zum!~`NcCiom3r`R@am~pu?+3WQ*KHWizjEY*`|mC`{sSj`^|grH~T4*=>@q8&e`%y z1n^B6$uc`i>xxv?fdD}PRFI<~rE7s+F&qPd2KRvI%RmgOZRv52V=kC>s;LYciT=BO zsln(Sw>szY2M=$TO46B%onD*}%T6}5hNb;P-bukh1RRZMD^NrPEvFrQ1?G`sOoOe9 zllR@Q9@N6XFT~(pQnSJ{Jb=4VnCWBgZ?qRA^q05Lk<`RN=qoAfD40oP#Ve&?uGGSrtSN#xUk>)g+IXyo7|2yvTOA{KfZc=ZSIS8t|hU+ zHYS^jnx^WbNtDM_bE&!&D(BLRaivBYfztaho^7~69BEf8GMzbiGzc)Z5tFph}X!V?A0hU0Fj#MFZleMf&D z(SIaO@-6?WHu>#q-$8tZDn?k><6dsE^U_pRa&`ZA9U-(2Q~Pk?r(vR@wdfbft)2K6 D?Ya1R literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/path_layer_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/path_layer_view.cpython-312.pyc index c36f3922d3e624e5a05f18aee0e00ced0a4f4b8e..929b15bcfa456c59dcb635306af76b366d817fcd 100644 GIT binary patch literal 5082 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 literal 3753 zcmb^!TW=Iq_{{9>Y`fcf1A1o*<+9i=wsNyTBh*ANhP1(8vNle(I|J-Gvs2E@DlH|c zT4ReEF-DR2U^PaU@W7K2qwx>;;@B}mCVe1L3cM-BOMLQtXZP&P))FB)P0#r*=ewTE z_w9G_cpai&?6=VIhA2Wm0m8pf1z38D0_Tv7WI;umkQOvCEoy;uKntdWS|}aT!s)OU zNk;@&BdS=7rlSH9(UV9HOd~m{2p0ooUFo{`3t!Dk1eg#V@eba+oVhs-K-R+vO@(TrZKPy{rI@;t-URet_Bx*!}%2HcQQ z98-vkB`H_PnUds2*j&sdMK@?D>QOf;Nm(^x7?NZ_poyu!cFX-EV?@s@S+hDHCfSj! zu4$QqEM=8~sgRLkZe)xGBdHLL^db!>H8xJ}qdF#5*F>4(#>13ntf9l7Q)mIlXCoGF zvT@559D6%98+)r^iLy{lPJ)!ca?(Vjr5UN_i)Z4rU;>|goHmIhzGaXn(6Q{xN0t#N zDhn4Y+F5p2lA>pP`FD(-*-!pmvVOT7wHNT&6Y}wYu2|W3Ps(iMGCcCrqIo~3$w9u7 z=A-Jlk00{!**ykHA3yBlSN-?#BR+nNTb&f|5Xl34g*S6~n(sSFJ>;$C&|OHa=4-wA zE~J{g)g1Ck3Heq|_2x;@TVb|yx{NuMX1=;dbT>Qr3UAJLdN7B~bsWlfambswZgxsn z(pcRqPezXVr0#UqR?y{>LitUeJ`PRRp8OT|IKP>%_2&G;9?T({oo)_$bB+BK&cZ!t zPkCB7RHJ=`Gjk8xAK^N^xkmd6XX_rc_jy`5ci_{c{ngEuaHw)pC66MdtZ>vvy^XkrbwVC?cVbr?m!_-Xh%|@u+jkApo z-e}>Cb0=(^tWtU{-1|e{5gR{SC2g?qhVM5H*?9O0e$c_~7H+PHb4eRHW}zI_)UEemV6lm_P4_5;$k(Aiyje!g=LARSlnDhIE% z@Y=bcljyY)y*BQ<;-~dli9Q=YT0`4rCAQgkdkt-emDpk9omZ>wX*TYruC=Y3OU<|T zg0UtVj)R+M_u^m(=+n{93m+GlqADG-@%^UfY&_x%)agiB9Vr`cy-JhTFxznUl#SQ_ z5=POugPSef?BGrdcRDy};bf`nQqP4R8}DMqS}fe+;B^*W=in_C-cs6jY2d>^)@a%0~c>CG*b3d&*G{5cvHCr_b0(hhGAMAp9W; c!a?B=1mmx4yoOqD2m6Ki*(ZNPbYfoq4N1rP6aWAK diff --git a/Src/command_center/ui/__pycache__/path_planning_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/path_planning_view.cpython-312.pyc index ad35ab26546ca72218081e80c8d00a0321f2f9d1..e7285e89274d382a7dffdae0283514c1f80944c2 100644 GIT binary patch delta 636 zcmZouJFmfanwOW00SGqsD`kA)oyaG_7&TGd*f^Ivikp!kg&~D0hbNaeikFdriNT#A zg}H?xg(a17H8V&H149&FC95XeOAu3&dtzrP*DdZq-_+vb#PrmOzfUr1Za&I*jBj#3 z6Q__7P|0+L5=M|x5XfSh?7$`xox+&H1T&10p^`z9+3zb4Q2J@t{1?r8pD$eZV%?Hb zIiSFc92Ac=~Dyp#i delta 401 zcmX@F(Wb_CnwOW00SL;zvSw`KnaC%>7&cMeSf7z0g&~D0hdY-iiieSbiNT#Ag}H?x zg(a17H8V•&VC95XeOAu3&YhrKdWPZkDj4L-^V?4$u0TKi$`^*9)rZZGB6bS+u zD;XxM30hdN0+|gA4>)*=K#z-WjjQnb|RS zW^3+lJuz$E`Y5h0?t32mOyS^FAZClJ; zvd*aKjHD4411u-LqR8Zw8y%NS<+QYTPF0pt<<*sCWvS@V1bn{(hoc~Yl9Ax^A;qr* zZV`WYgy0IAqh#tTErev>RV0LEKS){*D3Ljz9K0G3+E&mqB}C64KIF1zRDDsaP3S8R z;>iF=GU=l?VCWC2Ysji~zUwQXU%*`zsOq$E##8}`HU9!y2-nPOja!u#g{(+bS%7=I zRq?}bTG6Ur_HT|IK`q&lFPV~MVK=yl!4PkUt-BEm ztLe0Y71?c*RkN}MH*z+&s2s=Gz;3vzi`90d>QY((+5}8Rn{#92bY#hrys0%eB8uL6 zMbRWk<+v-~9F#{dE@EQ=PN()hgHsn%hOSE)SxhN}mKU??MbksRci=w-vLzRDm|SkY zv*GlT)%}y8WqttS$rW^$X5S6l^p5M@C3^2hZ~sc5MDN+48He6&)4NM_&;4k}`y<7j z{iW!@UACk6%&8K4`hm|M>)ePZoOrJt?{(t)?f8BtK5EBDOYuVwLMYbZust^0o4fVaSnaT zMaBpHpAJwUQ-_x=uH*jZ^eruJZpcEo|Fr(|TQ-BLj15aic!+$a4)#N^T|XPU3b6#1(X(8z>GQUgO3HuDd*3ciD88L-*Ts|ILvSJyfUbwdr1m z-e=SMZpu#bq@6rjqE7*wJ36mD|EuSJ-L(?>EE?ZnpK;hOo9((TJKe)}_wYJ9;sN^b zIy?3VCPfWUt)-siQ=$4)sI>rSHT++u>Ok)+P&d%W_0S~=j1a_o>MZ>Lo)!RZ@@{DZ zaBNM87T}i~62`ai3)aJ!4^8Eh4R<}tJq<}*O42k%&JQ%(*9UpPDW{qqN)>sO%YUcI zm;~lz%hbRDx?%27E=yAP=fC{ui$ATDS%iB*!3RCq;y!4*k=hjaS@InQ-?PfHRROiz zrkY}kFh!9Yu+4*UzOP|P$)|0XOk`dG;RWzwY^+2d*+}ek68(0f-${(viIKI$_g2~< zx)<9IuKsZKP%(C*ME_u;cfS|lJ^lEd5$D+J_OaKC<8KsWVu^lpBf8Cr_WnKEyTPWcjb*O^0j5EkJ^NHX&hX{tVClCsGQ(@?86h3VFq*n-3Q z(O@w0;90B23kZ<^r!=c#>9ViEqXk~8tg3nV>p0^p_--KCU+{wrd|Zc6*h7mDztAGA z7kmZ(Tpd4i!Ca6J<@--S8rKvPDiw&9CX_FDNy4%sb930xxw`T>xrGSxo~xJgJ?{zu z202p60JdLh0MaOT6sj7io62#U^X`h1EN*Y!H^{jZKgEa35cVMJhf2q#mR%}`d*I03 z_GVJ>fpUO;ne+x#JuNPmUpbQmJPsZ&V7u+j>&$mIEG*gFPewGCKtSv!;2m4FoFf

w zak|7zc|hK6GrJvT$YzFa_pT;xcds+AJcuHu&jU2nj8H6QBwc}Gu}vf;S{Cw`=rW>s zDJyB^9!zf94QQ%qxrzFONcb&f(vbd073L)^OxTS{jgLo(7h5U!Qi`UDB2VExpfySD z@k%+D-L<-Qrj+KEf7EV2;V} zjLTFFju}|NBp7<9;O)$1N!eYiLUB9kC0qDCDzAs6aF;$*KFu*a0iR^Y=3Wp`{87|B l^x8Ul?H)R?h7R0ALpB=v2kLtq7^aR=@1Oh{k+(O}{{U2*o~r-= literal 0 HcmV?d00001 diff --git a/Src/command_center/ui/__pycache__/status_dashboard.cpython-312.pyc b/Src/command_center/ui/__pycache__/status_dashboard.cpython-312.pyc index 02dc1706a1299c3046aa21402b2912b755e2793f..3e03fa8c3cab6c9e32d99363e1a98047d907b1a1 100644 GIT binary patch delta 20 acmcbmeM_7BG%qg~0}ya|$Zq65FA4xW2L%)W delta 20 acmcbmeM_7BG%qg~0}!NsW!=bqUK9XA8U_Ra diff --git a/Src/command_center/ui/__pycache__/threat_layer_view.cpython-312.pyc b/Src/command_center/ui/__pycache__/threat_layer_view.cpython-312.pyc index a5fefa0faea5a1b479b2faaf631ffae66ab9d0a9..7d269f89e2631d112a70ccc1806e012623da9d58 100644 GIT binary patch literal 4187 zcmb6cTTC3+_0B#o>UX97FlEuwkt;=NaLZRMS0GE9B~a!^Q$lS#_AL?$_zTcjz!>{}$Mfb54pCGI7s$q%D za;e5^_{8S%X{JazJWXNNaqba#5()*s?RTL26}L=u80~i3H%~qUrOb2p@zT&hxZFGu ztMoEph>lnRk7OHcyxRW0v*1XHB>W?9Zk+)@B4`G+;*7#fDvFtWMi)i1S(0T@x2`JY z3bJAf&VosDrl^^%79JpF^h_ZysbVpc@S6cmQ76r&oHQfKk}g@4s@WilX;soRQPl8y z@%X}jF39I@%&}K?Rz-PZ&5p)rHT)@kQ9;FGi? zTAY&re3mkLp$An|_go>~;`FMx<*Hh0RRT&tE_E%77p!Zt&r|CjRJ4u+mRa!H`Ucc| zJ&X{KyWZ^X%6=Dm%ct*iq3-M89aRGYo~`b_H)aobb9W{O~V*nLPx$r zl|TC3A2vVuY;)!PfBoqF&7b~$^X}d8q5yCQ^5F-Ye_A}Zh3RMO&(IaoWFLM0AA zV`I$qy}?+4or~K!Af7Mi@yS9lFUK?ac%9=X4lvAC4Bg6XstH^ZI-~|o(XUJ^>6@4! z%y3%Gq;D!RObYotL?82@UYMFv6|sg4;~fhjN4%feWQROagA_7Rm^CSId|S#-Dc0<| zq!tx+5E1!zaeieQ_hfS)pt%v$37RcwRgstr#M=qA5Y23WTR2H91Q8FDAA)Qdn86ta zNh=Xx=qcs_Co;lAbXHJB2cLItt74X9Nx;<5jN;H(J4_fl-`ue}5hizrbJUjhDg?a+ z@@4$wuZ2|_eHtRMzSX|LwT_DzKijSN*>2GG3hgy$?@C*lCTn0_2JNcQlLkGx@_MCz z*ytZF)2qPaaL1j(+M(nU|FGl4r`dW=#|?V?v$t3Fm+7e*UKVt`(tXzGK3k@*)$HKO z6?)pBrysOcULH1Hc24Q+Um018pGWpl3q5MkXoYqgw7Wu28T8bOTuBZY$)Pg6^f*Ks zIx4ispgk*rO7D52_k5WSY#_c1yI}NQDAPf}Y(2Q#{)_fsMwdc=i|pGF_E&_cAw*X@ zURf8u`K8dhy8q(3FvKw4JeN)|Rp7Uc_P&gv5ytwKttJ$2$92yComkk>J-F_caHBRtTe6jXTNwJw%R+e&R?)qRxPK1InFJWVMGI!VYvk}yOqd2ZY053uAlJP zJ{hzkfQEFgyY<0*uCxACJ7U8tfTm%%fd@s8Ed|!;k)0+~z0c18ahXi*p*FEvsqCRv z365)DF znzU$1%gH|MBD~lPW^z;FtUZ*TPcvoFJJ2nVFI?|^ z*mmen!`;XykxzxSwi8Ql)UP2P3;SIEK5g))A9R0y;6cYaKlC_4__(F)La3!-9&<%4z-x~~_>Q(qu>>$>(1mXAsQ<#9C zTuUGL8^xM%1Fw3I*au^EVA26wI)iK!mKaE^S3LG}9QVw}ao4zK1p95P|0g;AcXHxs RV1T>C{ru)v1V@Y6e*rcTeDMGP literal 3390 zcmb6b?{5@EbawC8Tzhu~kstkaJ!pk4E!RQ|1_UB4iN%mYZG*|BvbpXq?H#+fIJ>9Z zQIZmBY&ArTQ8Yx;7^Byq5HNswQ=Dj!b ze$LE$@AqJ^3gC}=+=+WrPncV3GjfYL?BTyN}^-5z`F(NimMn}%>+Lxs7Cd`u^##K z;dn+)6Qs{ZyaR{hBZ7#DN7Kq!Pg+&wlo1#hijRmwk$II9JERH{LmBjd$`v#gM!JYB z?ZUwO*ck!DC?3SW~qM0Wq6eI zDFGRfWfQ4{%Cd&foYM)0Hr$FJo-q6@%ZYJCVOfPZ8JYNZAHQe#6q1tyryfeWD}yadYP;>H9sECR7x?kqH&f|$-4Pjv~j>%M@C{rWGwrVzGVcA z^3;{0bTh)0xcpCZjp94|_5X8LKh4Rz9Qvqa@aY|uZMV798jHvB{4A}mwrITD+KJ;) zvfQ!majcu~m~cAQy^i&g{p0ismcC%=wP%YT$8#mF7wz2^^bB0RQyN0FRP@!L3Al(C}b6P@Ju%1WA<4TyM!aFqr|pqRQ< zQU{HYBB;Hxn3ht#SfXmwV!TgAiL9Jb<6@r>$DWvFR55~hX;96G!r+*I3$H)U^Mx{s zNIgO%sTv_nWQz($)Ra*p@VNFk2&;KP6jZ@UB7zF_8F2w=4b#KdSy z!o>%XRAp3~c&}NE3_o5kmVX~7Cb*LVZ}>PlmBK~LsIu)bsvWi%!7@7-7b0)MsFvc1 z6e((?nFx-UEkhAiNTG>DB8XgXMDlEQ{ZM&Lj}{9ou~GRFmy-!_d)>xQq^atMUeL%~ z?w@k!VJ)tAI;_=Ttqxl?*qU3Nhiwj8mj=83=suo@tb@2kgIjL2zLAH6H{lu`Hfpf( zb6yX3XyJ}L?8MS*TP_Eug7aY}7o7|5CVJ}Uo3`fm&o%8KNW=Wb_T2Hgje7|az6ICn zut|eWGj2W7rA4~(u=}Qy)~!Xl^KfSwZI>3=m4`3iD(RQ9Uk%NMFq4B$T)x4)m50a6Jn7WJoq4$Z7VZT?)1ixJ^04{77x;rZtk+<@ z4x2UDtiv`9w&f09Ju-VF5BHiv8#LIU!xjy;=&)Ub?YSX++dggEzC7G-u7@=k*5PIi zZq{K`gVCI-cO1|<4*Usw&{}*xLO_C9?czR_dn90TzR|pu1u4$M7_ZEEJk9K1k oxfcqlpZ8r9MLnh|>Jar9knwMGTmbbC-Cb1h;^-rQC)3LR0AfPI8UO$Q diff --git a/Src/command_center/ui/algorithm_config_view.py b/Src/command_center/ui/algorithm_config_view.py index 265299c4..1d04657d 100644 --- a/Src/command_center/ui/algorithm_config_view.py +++ b/Src/command_center/ui/algorithm_config_view.py @@ -1,6 +1,6 @@ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QGroupBox, QFormLayout, QComboBox, - QSpinBox, QDoubleSpinBox, QCheckBox) + QSpinBox, QDoubleSpinBox, QCheckBox, QMessageBox) from PyQt5.QtCore import Qt class AlgorithmConfigView(QWidget): @@ -95,9 +95,11 @@ class AlgorithmConfigView(QWidget): self.reset_config_btn.clicked.connect(self.reset_config) def save_config(self): - # TODO: 实现保存配置功能 + QMessageBox.information(self, "功能确认", "保存配置 (功能待实现)") + # TODO: Implement save config logic pass def reset_config(self): - # TODO: 实现重置配置功能 + QMessageBox.information(self, "功能确认", "重置配置 (功能待实现)") + # TODO: Implement reset config logic (reset UI fields to default) pass \ No newline at end of file diff --git a/Src/command_center/ui/base_map_view.py b/Src/command_center/ui/base_map_view.py new file mode 100644 index 00000000..a72685b8 --- /dev/null +++ b/Src/command_center/ui/base_map_view.py @@ -0,0 +1,296 @@ +# -*- coding: utf-8 -*- +# File: base_map_view.py +# Purpose: 定义基础地图视图类,提供地图加载、显示、缩放、平移和点击处理等通用功能。 +# 作为 SimpleMapView, ThreatLayerView, PathLayerView 的父类。 + +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QToolBar, QAction, QFileDialog) +from PyQt5.QtCore import Qt, pyqtSignal, QPointF, QRect, QRectF +from PyQt5.QtGui import QPixmap, QPainter, QPen, QColor, QTransform, QCursor, QMouseEvent + +class BaseMapView(QWidget): + # 定义信号 + map_loaded = pyqtSignal() + threat_points_changed = pyqtSignal(list) + + def __init__(self, map_data_model): + super().__init__() + self.map_data_model = map_data_model + self.scale_factor = 1.0 + # Panning state + self.panning = False + self.last_pan_point = QPointF() + self.map_offset = QPointF(0, 0) # Top-left corner offset of the map relative to the widget + self.init_ui() + + def init_ui(self): + # 创建主布局 + main_layout = QVBoxLayout() + main_layout.setContentsMargins(0, 0, 0, 0) # Remove margins + + # 创建工具栏 + toolbar = QToolBar() + toolbar.setMovable(False) + + # 添加工具栏按钮 + self.load_map_action = QAction("加载地图", self) + self.zoom_in_action = QAction("放大", self) + self.zoom_out_action = QAction("缩小", self) + self.pan_action = QAction("平移", self) + self.pan_action.setCheckable(True) # Make pan action checkable + + toolbar.addAction(self.load_map_action) + toolbar.addAction(self.zoom_in_action) + toolbar.addAction(self.zoom_out_action) + toolbar.addAction(self.pan_action) + + main_layout.addWidget(toolbar) + + # 创建地图显示区域 + self.map_label = QLabel("请加载地图图片") + self.map_label.setAlignment(Qt.AlignTop | Qt.AlignLeft) # Align top-left for panning + self.map_label.setStyleSheet("background-color: #333; border: none;") # Darker background, no border + main_layout.addWidget(self.map_label) + + # 设置布局 + self.setLayout(main_layout) + + # 连接信号 + print("BaseMapView: Connecting load_map_action to load_map_image") # Debug print + self.load_map_action.triggered.connect(self.load_map_image) + print("BaseMapView: Connecting zoom_in_action to zoom_in") # Debug print + self.zoom_in_action.triggered.connect(self.zoom_in) + print("BaseMapView: Connecting zoom_out_action to zoom_out") # Debug print + self.zoom_out_action.triggered.connect(self.zoom_out) + print("BaseMapView: Connecting pan_action to toggle_pan_mode") # Debug print + self.pan_action.triggered.connect(self.toggle_pan_mode) # Connect pan toggle + print("BaseMapView: Connecting map_data_model.data_changed to update_map") # Debug print + self.map_data_model.data_changed.connect(self.update_map) + + def load_map_image(self): + print("BaseMapView: load_map_image called") # Debug print: function entry + file_name, _ = QFileDialog.getOpenFileName(self, "选择地图图片", "", "Images (*.png *.jpg *.bmp)") + if file_name: + pixmap = QPixmap(file_name) + if pixmap.isNull(): + print("BaseMapView: Error - Loaded pixmap is null!") + self.map_data_model.set_map(None) # Ensure model knows it's invalid + else: + print(f"BaseMapView: Map loaded successfully, size: {pixmap.width()}x{pixmap.height()}") + self.map_data_model.set_map(pixmap) + self.reset_view() # Reset zoom/pan on new map load + self.map_loaded.emit() + + def reset_view(self): + print("BaseMapView: reset_view called") # Debug print + if not self.map_data_model.map_pixmap or self.map_data_model.map_pixmap.isNull(): + print("BaseMapView: reset_view - No valid map pixmap found.") + self.scale_factor = 1.0 + self.map_offset = QPointF(0, 0) + self.update_map() + return + + # Calculate scale factor to fit the map inside the label + label_size = self.map_label.size() + map_size = self.map_data_model.map_pixmap.size() + print(f"BaseMapView: reset_view - Label size: {label_size.width()}x{label_size.height()}, Map size: {map_size.width()}x{map_size.height()}") + + if label_size.isEmpty() or map_size.isEmpty() or map_size.width() == 0 or map_size.height() == 0: + print("BaseMapView: reset_view - Warning: Invalid label or map size for scaling.") + self.scale_factor = 1.0 # Default if sizes are invalid + else: + scale_x = label_size.width() / map_size.width() + scale_y = label_size.height() / map_size.height() + self.scale_factor = min(scale_x, scale_y) # Use min to fit entirely (KeepAspectRatio) + print(f"BaseMapView: reset_view - Calculated initial scale factor: {self.scale_factor}") + + # Calculate offset to center the scaled map + scaled_map_width = map_size.width() * self.scale_factor + scaled_map_height = map_size.height() * self.scale_factor + offset_x = (label_size.width() - scaled_map_width) / 2 + offset_y = (label_size.height() - scaled_map_height) / 2 + self.map_offset = QPointF(offset_x, offset_y) + print(f"BaseMapView: reset_view - Calculated initial offset: ({offset_x}, {offset_y})") + + self.update_map() + + def update_map(self): + # print("BaseMapView: update_map called") # Can be noisy, enable if needed + if self.map_data_model.map_pixmap and not self.map_data_model.map_pixmap.isNull(): + original_pixmap = self.map_data_model.map_pixmap + label_size = self.map_label.size() + + # Calculate scaled size + scaled_width = int(original_pixmap.width() * self.scale_factor) + scaled_height = int(original_pixmap.height() * self.scale_factor) + + if scaled_width <= 0 or scaled_height <= 0: + print(f"BaseMapView: update_map - Warning: Invalid scaled size ({scaled_width}x{scaled_height}), skipping draw.") + return + + # Create a new pixmap for drawing + # Ensure display_pixmap is at least the size of the label to avoid issues + display_pixmap_width = max(scaled_width + int(abs(self.map_offset.x())) + 1, label_size.width()) + display_pixmap_height = max(scaled_height + int(abs(self.map_offset.y())) + 1, label_size.height()) + display_pixmap = QPixmap(display_pixmap_width, display_pixmap_height) + display_pixmap.fill(Qt.transparent) # Use transparent background + + # print(f"BaseMapView: update_map - Scale: {self.scale_factor:.2f}, Offset: ({self.map_offset.x():.1f}, {self.map_offset.y():.1f})") + # print(f"BaseMapView: update_map - Scaled map size: {scaled_width}x{scaled_height}") + # print(f"BaseMapView: update_map - Display pixmap size: {display_pixmap_width}x{display_pixmap_height}") + + painter = QPainter(display_pixmap) + painter.setRenderHint(QPainter.SmoothPixmapTransform) # Improve scaling quality + + # Define the target rectangle for the scaled map within the display_pixmap + # Ensure offset is integer for QRect constructor + target_rect = QRect(int(self.map_offset.x()), int(self.map_offset.y()), + scaled_width, scaled_height) + + # Draw the scaled original map + painter.drawPixmap(target_rect, original_pixmap, original_pixmap.rect()) + + # --- Draw overlays --- + # (Code for drawing overlays remains the same) + # ... (threats, start, goal, paths) ... + # Apply scale and offset transform to the painter for drawing overlays + transform = QTransform() + transform.translate(self.map_offset.x(), self.map_offset.y()) + transform.scale(self.scale_factor, self.scale_factor) + painter.setTransform(transform) + + # Draw threats + if self.map_data_model.threat_points: + point_size = max(1.0, 5.0 / self.scale_factor) # Ensure point size is at least 1 + pen = QPen(QColor(255, 0, 0), point_size) # Keep pen size visually constant + painter.setPen(pen) + for point in self.map_data_model.threat_points: + painter.drawPoint(QPointF(point[0], point[1])) # Draw at original coords (transformed) + + # Draw start point + if self.map_data_model.start_point: + point_size = max(1.0, 5.0 / self.scale_factor) + pen = QPen(QColor(0, 255, 0), point_size) + painter.setPen(pen) + painter.drawPoint(QPointF(self.map_data_model.start_point[0], self.map_data_model.start_point[1])) + + # Draw goal point + if self.map_data_model.goal_point: + point_size = max(1.0, 5.0 / self.scale_factor) + pen = QPen(QColor(0, 0, 255), point_size) + painter.setPen(pen) + painter.drawPoint(QPointF(self.map_data_model.goal_point[0], self.map_data_model.goal_point[1])) + + # Draw paths + if self.map_data_model.paths: + line_width = max(0.5, 2.0 / self.scale_factor) # Use 0 for cosmetic pen if needed, ensure > 0 + pen = QPen(QColor(0, 255, 255), line_width) + painter.setPen(pen) + for path in self.map_data_model.paths: + if len(path) > 1: + points = [QPointF(p[0], p[1]) for p in path] + painter.drawPolyline(*points) # Draw lines between original coords + + painter.end() + + # Set the potentially larger pixmap on the label + self.map_label.setPixmap(display_pixmap) + # print("BaseMapView: update_map - Pixmap set on label") + else: + print("BaseMapView: update_map - No map pixmap to draw.") + self.map_label.setText("请加载地图图片") + self.map_label.setPixmap(QPixmap()) # Clear pixmap if none loaded + + def resizeEvent(self, event): + super().resizeEvent(event) + # We might need to call update_map if label size affects how map is displayed + # For now, assume update_map is called when needed by zoom/pan + self.update_map() # Update map on resize + + def zoom(self, factor): + # Store previous scale and mouse position (relative to widget) + prev_scale = self.scale_factor + mouse_pos = self.map_label.mapFromGlobal(QCursor.pos()) + + # Calculate map point under mouse before zoom + map_point_before_zoom = (mouse_pos - self.map_offset) / prev_scale + + # Update scale factor + self.scale_factor *= factor + # Add limits to scaling if needed + # self.scale_factor = max(0.1, min(self.scale_factor, 10.0)) + + # Calculate map point under mouse after zoom (if offset remained the same) + map_point_after_zoom_same_offset = map_point_before_zoom * self.scale_factor + + # Adjust offset to keep the point under the mouse cursor stationary + self.map_offset = mouse_pos - map_point_after_zoom_same_offset + + self.update_map() + + def zoom_in(self): + print("BaseMapView: zoom_in called") # Debug print + self.zoom(1.2) # Zoom in by 20% + + def zoom_out(self): + print("BaseMapView: zoom_out called") # Debug print + self.zoom(1 / 1.2) # Zoom out by 20% + + def toggle_pan_mode(self, checked): + print(f"BaseMapView: toggle_pan_mode called, checked: {checked}") # Debug print + self.panning = checked + if self.panning: + self.map_label.setCursor(Qt.OpenHandCursor) + else: + self.map_label.setCursor(Qt.ArrowCursor) + + def mousePressEvent(self, event): + if self.panning and event.button() == Qt.LeftButton: + self.last_pan_point = event.pos() + self.map_label.setCursor(Qt.ClosedHandCursor) + event.accept() + elif not self.panning and event.button() == Qt.LeftButton: + label_pos = self.map_label.mapFromParent(event.pos()) + if self.map_label.pixmap() and not self.map_label.pixmap().isNull() and self.map_label.rect().contains(label_pos): + # Calculate map point under mouse in original map coordinates + map_point_f = (label_pos - self.map_offset) / self.scale_factor + + # Check if click is within the actual map boundaries (optional but good practice) + original_map_rect = QRectF(0, 0, self.map_data_model.map_pixmap.width(), self.map_data_model.map_pixmap.height()) + if original_map_rect.contains(map_point_f): + # Call the handler method (implemented by subclasses) + self.handle_map_click(map_point_f) + event.accept() # Indicate event was handled + return # Don't ignore if handled + + # If click wasn't on map or not handled, ignore + event.ignore() + else: + # Ignore other buttons or if panning is off but not LeftButton + event.ignore() + + def mouseMoveEvent(self, event): + if self.panning and event.buttons() & Qt.LeftButton: + delta = event.pos() - self.last_pan_point + self.map_offset += delta # Update offset + self.last_pan_point = event.pos() + self.update_map() # Redraw map at new offset + event.accept() + else: + event.ignore() + + def mouseReleaseEvent(self, event): + if self.panning and event.button() == Qt.LeftButton: + self.map_label.setCursor(Qt.OpenHandCursor) # Change back to open hand + event.accept() + else: + event.ignore() + + def handle_map_click(self, map_point): + """Subclasses should override this to handle clicks on the map.""" + # Default implementation does nothing + pass + +# Need to import QRect, QPointF, QTransform, QCursor, QMouseEvent at the top +from PyQt5.QtCore import QRect, QRectF +from PyQt5.QtGui import QCursor, QMouseEvent \ No newline at end of file diff --git a/Src/command_center/ui/drone_detail_view.py b/Src/command_center/ui/drone_detail_view.py index cc0d6731..a68e80e8 100644 --- a/Src/command_center/ui/drone_detail_view.py +++ b/Src/command_center/ui/drone_detail_view.py @@ -1,12 +1,188 @@ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QPushButton, QGroupBox, QFormLayout) -from PyQt5.QtCore import Qt + QPushButton, QGroupBox, QFormLayout, QProgressBar, + QComboBox, QSpinBox, QDoubleSpinBox, QDialog, + QMessageBox) +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtGui import QColor, QPalette + +class ParameterDialog(QDialog): + def __init__(self, title, min_value, max_value, unit, parent=None): + super().__init__(parent) + self.title = title + self.min_value = min_value + self.max_value = max_value + self.unit = unit + self.init_ui() + + def init_ui(self): + self.setWindowTitle(self.title) + layout = QFormLayout() + + self.value_spin = QDoubleSpinBox() + self.value_spin.setRange(self.min_value, self.max_value) + self.value_spin.setSuffix(f" {self.unit}") + self.value_spin.setDecimals(1) + + layout.addRow("值:", self.value_spin) + + buttons = QHBoxLayout() + ok_button = QPushButton("确定") + cancel_button = QPushButton("取消") + + ok_button.clicked.connect(self.accept) + cancel_button.clicked.connect(self.reject) + + buttons.addWidget(ok_button) + buttons.addWidget(cancel_button) + layout.addRow(buttons) + + self.setLayout(layout) + + def get_value(self): + return self.value_spin.value() + +class PayloadDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + + def init_ui(self): + self.setWindowTitle("设置负载") + layout = QFormLayout() + + self.type_combo = QComboBox() + self.type_combo.addItems(["无", "货物", "医疗物资", "救援设备", "监控设备"]) + + self.weight_spin = QDoubleSpinBox() + self.weight_spin.setRange(0, 100) + self.weight_spin.setSuffix(" kg") + self.weight_spin.setDecimals(1) + + layout.addRow("负载类型:", self.type_combo) + layout.addRow("负载重量:", self.weight_spin) + + buttons = QHBoxLayout() + ok_button = QPushButton("确定") + cancel_button = QPushButton("取消") + + ok_button.clicked.connect(self.accept) + cancel_button.clicked.connect(self.reject) + + buttons.addWidget(ok_button) + buttons.addWidget(cancel_button) + layout.addRow(buttons) + + self.setLayout(layout) + + def get_payload_info(self): + return { + "type": self.type_combo.currentText(), + "weight": self.weight_spin.value() + } class DroneDetailView(QWidget): - def __init__(self): + # 定义信号 + control_command = pyqtSignal(str, str) # 命令类型, 参数 + + def __init__(self, comm_manager=None): super().__init__() + self.comm_manager = comm_manager + self.current_drone_id = None self.init_ui() + self.setup_communication() + def setup_communication(self): + """设置通信回调""" + if self.comm_manager: + self.comm_manager.register_callback("drone_updated", self.on_drone_updated) + self.comm_manager.register_callback("command_sent", self.on_command_sent) + + def on_drone_updated(self, drone_id, data): + """处理无人机更新事件""" + if drone_id == self.current_drone_id: + self.update_drone_info(drone_id, self.comm_manager.get_drone(drone_id)) + + def on_command_sent(self, drone_id, data): + """处理命令发送事件""" + if drone_id == self.current_drone_id: + command = data.get("command", "") + params = data.get("params", {}) + QMessageBox.information(self, "命令发送", f"命令 {command} 已发送到无人机 {drone_id}") + + def set_drone(self, drone_id): + """设置当前显示的无人机""" + self.current_drone_id = drone_id + if self.comm_manager: + info = self.comm_manager.get_drone(drone_id) + self.update_drone_info(drone_id, info) + + def send_control_command(self, command_type, params=None): + """发送控制命令""" + if self.current_drone_id and self.comm_manager: + self.comm_manager.send_command(self.current_drone_id, command_type, params) + + def set_altitude(self): + """设置高度""" + dialog = ParameterDialog("设置高度", 0, 1000, "m", self) + if dialog.exec_() == QDialog.Accepted: + value = dialog.get_value() + self.send_control_command("set_altitude", {"value": value}) + + def set_speed(self): + """设置速度""" + dialog = ParameterDialog("设置速度", 0, 50, "m/s", self) + if dialog.exec_() == QDialog.Accepted: + value = dialog.get_value() + self.send_control_command("set_speed", {"value": value}) + + def set_heading(self): + """设置航向""" + dialog = ParameterDialog("设置航向", 0, 360, "°", self) + if dialog.exec_() == QDialog.Accepted: + value = dialog.get_value() + self.send_control_command("set_heading", {"value": value}) + + def set_payload(self): + """设置负载""" + dialog = PayloadDialog(self) + if dialog.exec_() == QDialog.Accepted: + info = dialog.get_payload_info() + self.send_control_command("set_payload", info) + + def update_drone_info(self, drone_id, info): + """更新无人机信息""" + self.current_drone_id = drone_id + + # 更新基本信息 + self.id_label.setText(info.get("id", "")) + self.model_label.setText(info.get("model", "")) + self.status_label.setText(info.get("status", "")) + self.battery_label.setText(f"{info.get('battery', 0)}%") + self.group_label.setText(info.get("group", "")) + self.color_label.setText(info.get("color", "")) + + # 更新位置信息 + self.latitude_label.setText(f"{info.get('latitude', 0):.6f}") + self.longitude_label.setText(f"{info.get('longitude', 0):.6f}") + self.altitude_label.setText(f"{info.get('altitude', 0):.1f}m") + self.speed_label.setText(f"{info.get('speed', 0):.1f}m/s") + self.heading_label.setText(f"{info.get('heading', 0):.1f}°") + + # 更新负载信息 + self.payload_type_label.setText(info.get("payload_type", "")) + self.payload_weight_label.setText(f"{info.get('payload_weight', 0):.1f}kg") + self.payload_status_label.setText(info.get("payload_status", "")) + + # 更新通信状态 + self.signal_strength.setValue(info.get("signal_strength", 0)) + self.latency_label.setText(f"{info.get('latency', 0)}ms") + self.packet_loss_label.setText(f"{info.get('packet_loss', 0):.1f}%") + + # 更新任务信息 + self.task_type_label.setText(info.get("task_type", "")) + self.task_status_label.setText(info.get("task_status", "")) + self.task_progress.setValue(info.get("task_progress", 0)) + def init_ui(self): # 创建主布局 main_layout = QVBoxLayout() @@ -19,11 +195,15 @@ class DroneDetailView(QWidget): self.model_label = QLabel("") self.status_label = QLabel("") self.battery_label = QLabel("") + self.group_label = QLabel("") + self.color_label = QLabel("") basic_layout.addRow("ID:", self.id_label) basic_layout.addRow("型号:", self.model_label) basic_layout.addRow("状态:", self.status_label) basic_layout.addRow("电量:", self.battery_label) + basic_layout.addRow("分组:", self.group_label) + basic_layout.addRow("颜色标识:", self.color_label) basic_info_group.setLayout(basic_layout) main_layout.addWidget(basic_info_group) @@ -36,49 +216,129 @@ class DroneDetailView(QWidget): self.longitude_label = QLabel("") self.altitude_label = QLabel("") self.speed_label = QLabel("") + self.heading_label = QLabel("") position_layout.addRow("纬度:", self.latitude_label) position_layout.addRow("经度:", self.longitude_label) position_layout.addRow("高度:", self.altitude_label) position_layout.addRow("速度:", self.speed_label) + position_layout.addRow("航向:", self.heading_label) position_group.setLayout(position_layout) main_layout.addWidget(position_group) + # 创建负载信息组 + payload_group = QGroupBox("负载信息") + payload_layout = QFormLayout() + + self.payload_type_label = QLabel("") + self.payload_weight_label = QLabel("") + self.payload_status_label = QLabel("") + + payload_layout.addRow("负载类型:", self.payload_type_label) + payload_layout.addRow("负载重量:", self.payload_weight_label) + payload_layout.addRow("负载状态:", self.payload_status_label) + + payload_group.setLayout(payload_layout) + main_layout.addWidget(payload_group) + + # 创建通信状态组 + comm_group = QGroupBox("通信状态") + comm_layout = QFormLayout() + + self.signal_strength = QProgressBar() + self.signal_strength.setRange(0, 100) + self.signal_strength.setValue(0) + + self.latency_label = QLabel("") + self.packet_loss_label = QLabel("") + + comm_layout.addRow("信号强度:", self.signal_strength) + comm_layout.addRow("通信延迟:", self.latency_label) + comm_layout.addRow("丢包率:", self.packet_loss_label) + + comm_group.setLayout(comm_layout) + main_layout.addWidget(comm_group) + + # 创建任务信息组 + task_group = QGroupBox("任务信息") + task_layout = QFormLayout() + + self.task_type_label = QLabel("") + self.task_status_label = QLabel("") + self.task_progress = QProgressBar() + self.task_progress.setRange(0, 100) + self.task_progress.setValue(0) + + task_layout.addRow("任务类型:", self.task_type_label) + task_layout.addRow("任务状态:", self.task_status_label) + task_layout.addRow("任务进度:", self.task_progress) + + task_group.setLayout(task_layout) + main_layout.addWidget(task_group) + # 创建控制按钮 - button_layout = QHBoxLayout() + control_group = QGroupBox("控制面板") + control_layout = QVBoxLayout() + + # 基本控制按钮 + basic_control_layout = QHBoxLayout() self.takeoff_btn = QPushButton("起飞") self.land_btn = QPushButton("降落") self.return_btn = QPushButton("返航") self.emergency_btn = QPushButton("紧急停止") - button_layout.addWidget(self.takeoff_btn) - button_layout.addWidget(self.land_btn) - button_layout.addWidget(self.return_btn) - button_layout.addWidget(self.emergency_btn) + basic_control_layout.addWidget(self.takeoff_btn) + basic_control_layout.addWidget(self.land_btn) + basic_control_layout.addWidget(self.return_btn) + basic_control_layout.addWidget(self.emergency_btn) + + # 高级控制 + advanced_control_layout = QHBoxLayout() + self.set_altitude_btn = QPushButton("设置高度") + self.set_speed_btn = QPushButton("设置速度") + self.set_heading_btn = QPushButton("设置航向") + self.set_payload_btn = QPushButton("设置负载") - main_layout.addLayout(button_layout) + advanced_control_layout.addWidget(self.set_altitude_btn) + advanced_control_layout.addWidget(self.set_speed_btn) + advanced_control_layout.addWidget(self.set_heading_btn) + advanced_control_layout.addWidget(self.set_payload_btn) + + control_layout.addLayout(basic_control_layout) + control_layout.addLayout(advanced_control_layout) + control_group.setLayout(control_layout) + main_layout.addWidget(control_group) self.setLayout(main_layout) - # 连接信号 + # Connect control buttons + self.set_altitude_btn.clicked.connect(self.set_altitude) + self.set_speed_btn.clicked.connect(self.set_speed) + self.set_heading_btn.clicked.connect(self.set_heading) + self.set_payload_btn.clicked.connect(self.set_payload) self.takeoff_btn.clicked.connect(self.takeoff) self.land_btn.clicked.connect(self.land) self.return_btn.clicked.connect(self.return_home) self.emergency_btn.clicked.connect(self.emergency_stop) - + def takeoff(self): - # TODO: 实现起飞功能 - pass - + QMessageBox.information(self, "功能确认", "起飞命令 (功能待实现)") + # self.send_control_command("takeoff") + def land(self): - # TODO: 实现降落功能 - pass - + QMessageBox.information(self, "功能确认", "降落命令 (功能待实现)") + # self.send_control_command("land") + def return_home(self): - # TODO: 实现返航功能 - pass - + QMessageBox.information(self, "功能确认", "返航命令 (功能待实现)") + # self.send_control_command("return_home") + def emergency_stop(self): - # TODO: 实现紧急停止功能 - pass \ No newline at end of file + reply = QMessageBox.warning(self, "紧急停止确认", + "确定要发送紧急停止命令吗?这将停止所有电机!", + QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + QMessageBox.information(self, "功能确认", "紧急停止命令 (功能待实现)") + # self.send_control_command("emergency_stop") + \ No newline at end of file diff --git a/Src/command_center/ui/drone_list_view.py b/Src/command_center/ui/drone_list_view.py index 5ce4fa4d..69b4200a 100644 --- a/Src/command_center/ui/drone_list_view.py +++ b/Src/command_center/ui/drone_list_view.py @@ -1,21 +1,177 @@ +# -*- coding: utf-8 -*- +# File: drone_list_view.py +# Purpose: 定义无人机列表标签页的 UI 和逻辑,显示无人机信息并提供管理操作。 + from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QPushButton, QTableWidget, QTableWidgetItem) -from PyQt5.QtCore import Qt + QPushButton, QTableWidget, QTableWidgetItem, + QComboBox, QColorDialog, QMenu, QAction, + QDialog, QLineEdit, QFormLayout, QMessageBox) +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtGui import QColor, QBrush + +class AddDroneDialog(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + self.init_ui() + + def init_ui(self): + self.setWindowTitle("添加无人机") + layout = QFormLayout() + + self.id_edit = QLineEdit() + self.model_edit = QLineEdit() + self.group_combo = QComboBox() + self.group_combo.addItems(["运输组", "监控组", "救援组"]) + + layout.addRow("ID:", self.id_edit) + layout.addRow("型号:", self.model_edit) + layout.addRow("分组:", self.group_combo) + + buttons = QHBoxLayout() + ok_button = QPushButton("确定") + cancel_button = QPushButton("取消") + + ok_button.clicked.connect(self.accept) + cancel_button.clicked.connect(self.reject) + + buttons.addWidget(ok_button) + buttons.addWidget(cancel_button) + layout.addRow(buttons) + + self.setLayout(layout) + + def get_drone_info(self): + return { + "id": self.id_edit.text(), + "model": self.model_edit.text(), + "group": self.group_combo.currentText(), + "status": "待命", + "battery": 100, + "signal_strength": 100, + "latency": 0, + "packet_loss": 0, + "payload_type": "无", + "payload_weight": 0, + "payload_status": "未装载", + "task_type": "无", + "task_status": "空闲", + "task_progress": 0 + } class DroneListView(QWidget): - def __init__(self): + # 定义信号 + drone_selected = pyqtSignal(str) # 无人机被选中时发出信号 + + def __init__(self, comm_manager=None): super().__init__() + self.comm_manager = comm_manager + self.drone_colors = {} # 存储无人机颜色 + self.drone_groups = {} # 存储无人机分组 self.init_ui() + self.setup_communication() + + def setup_communication(self): + """设置通信回调""" + if self.comm_manager: + self.comm_manager.register_callback("drone_added", self.on_drone_added) + self.comm_manager.register_callback("drone_removed", self.on_drone_removed) + self.comm_manager.register_callback("drone_updated", self.on_drone_updated) + + def on_drone_added(self, drone_id, data): + """处理无人机添加事件""" + self.add_drone_to_table(drone_id, self.comm_manager.get_drone(drone_id)) + + def on_drone_removed(self, drone_id, data): + """处理无人机移除事件""" + self.remove_drone_from_table(drone_id) + + def on_drone_updated(self, drone_id, data): + """处理无人机更新事件""" + self.update_drone_in_table(drone_id, self.comm_manager.get_drone(drone_id)) + + def add_drone_to_table(self, drone_id, info): + """添加无人机到表格""" + row = self.drone_table.rowCount() + self.drone_table.insertRow(row) + + # 设置各列数据 + self.drone_table.setItem(row, 0, QTableWidgetItem(info.get("id", ""))) + self.drone_table.setItem(row, 1, QTableWidgetItem(info.get("model", ""))) + self.drone_table.setItem(row, 2, QTableWidgetItem(info.get("status", ""))) + self.drone_table.setItem(row, 3, QTableWidgetItem(f"{info.get('latitude', 0):.6f}, {info.get('longitude', 0):.6f}")) + self.drone_table.setItem(row, 4, QTableWidgetItem(f"{info.get('battery', 0)}%")) + self.drone_table.setItem(row, 5, QTableWidgetItem(f"{info.get('payload_type', '')} ({info.get('payload_weight', 0)}kg)")) + self.drone_table.setItem(row, 6, QTableWidgetItem(info.get("group", ""))) + self.drone_table.setItem(row, 7, QTableWidgetItem(f"{info.get('signal_strength', 0)}%")) + + # 应用颜色 + if drone_id in self.drone_colors: + self.update_drone_color(drone_id) + + def remove_drone_from_table(self, drone_id): + """从表格中移除无人机""" + for row in range(self.drone_table.rowCount()): + if self.drone_table.item(row, 0).text() == drone_id: + self.drone_table.removeRow(row) + break + + def update_drone_in_table(self, drone_id, info): + """更新表格中的无人机信息""" + for row in range(self.drone_table.rowCount()): + if self.drone_table.item(row, 0).text() == drone_id: + self.drone_table.setItem(row, 1, QTableWidgetItem(info.get("model", ""))) + self.drone_table.setItem(row, 2, QTableWidgetItem(info.get("status", ""))) + self.drone_table.setItem(row, 3, QTableWidgetItem(f"{info.get('latitude', 0):.6f}, {info.get('longitude', 0):.6f}")) + self.drone_table.setItem(row, 4, QTableWidgetItem(f"{info.get('battery', 0)}%")) + self.drone_table.setItem(row, 5, QTableWidgetItem(f"{info.get('payload_type', '')} ({info.get('payload_weight', 0)}kg)")) + self.drone_table.setItem(row, 6, QTableWidgetItem(info.get("group", ""))) + self.drone_table.setItem(row, 7, QTableWidgetItem(f"{info.get('signal_strength', 0)}%")) + break def init_ui(self): # 创建主布局 main_layout = QVBoxLayout() + # 创建过滤和分组控制 + filter_layout = QHBoxLayout() + + # 状态过滤 + self.status_filter = QComboBox() + self.status_filter.addItems(["全部状态", "飞行中", "待命", "充电中", "故障"]) + self.status_filter.currentTextChanged.connect(self.apply_filters) + + # 分组过滤 + self.group_filter = QComboBox() + self.group_filter.addItems(["全部分组", "运输组", "监控组", "救援组"]) + self.group_filter.currentTextChanged.connect(self.apply_filters) + + filter_layout.addWidget(QLabel("状态过滤:")) + filter_layout.addWidget(self.status_filter) + filter_layout.addWidget(QLabel("分组过滤:")) + filter_layout.addWidget(self.group_filter) + filter_layout.addStretch() + + main_layout.addLayout(filter_layout) + # 创建无人机列表 self.drone_table = QTableWidget() - self.drone_table.setColumnCount(5) - self.drone_table.setHorizontalHeaderLabels(["ID", "型号", "状态", "位置", "电量"]) - self.drone_table.setStyleSheet("QTableWidget { border: 1px solid #ccc; }") + self.drone_table.setColumnCount(8) # 增加列数 + self.drone_table.setHorizontalHeaderLabels([ + "ID", "型号", "状态", "位置", "电量", "负载", "分组", "通信状态" + ]) + self.drone_table.setStyleSheet(""" + QTableWidget { + border: 1px solid #ccc; + gridline-color: #ddd; + } + QTableWidget::item:selected { + background-color: #e0e0e0; + } + """) + self.drone_table.setContextMenuPolicy(Qt.CustomContextMenu) + self.drone_table.customContextMenuRequested.connect(self.show_context_menu) + self.drone_table.itemSelectionChanged.connect(self.on_selection_changed) + main_layout.addWidget(self.drone_table) # 创建控制按钮 @@ -24,11 +180,15 @@ class DroneListView(QWidget): self.edit_drone_btn = QPushButton("编辑信息") self.delete_drone_btn = QPushButton("删除无人机") self.refresh_btn = QPushButton("刷新状态") + self.set_color_btn = QPushButton("设置颜色") + self.set_group_btn = QPushButton("设置分组") button_layout.addWidget(self.add_drone_btn) button_layout.addWidget(self.edit_drone_btn) button_layout.addWidget(self.delete_drone_btn) button_layout.addWidget(self.refresh_btn) + button_layout.addWidget(self.set_color_btn) + button_layout.addWidget(self.set_group_btn) main_layout.addLayout(button_layout) @@ -39,19 +199,224 @@ class DroneListView(QWidget): self.edit_drone_btn.clicked.connect(self.edit_drone) self.delete_drone_btn.clicked.connect(self.delete_drone) self.refresh_btn.clicked.connect(self.refresh_status) + self.set_color_btn.clicked.connect(self.set_drone_color) + self.set_group_btn.clicked.connect(self.set_drone_group) - def add_drone(self): - # TODO: 实现添加无人机功能 + def show_context_menu(self, position): + menu = QMenu() + view_details = QAction("查看详情", self) + set_color = QAction("设置颜色", self) + set_group = QAction("设置分组", self) + show_trajectory = QAction("显示轨迹", self) + + menu.addAction(view_details) + menu.addAction(set_color) + menu.addAction(set_group) + menu.addAction(show_trajectory) + + action = menu.exec_(self.drone_table.mapToGlobal(position)) + if action == view_details: + self.view_drone_details() + elif action == set_color: + self.set_drone_color() + elif action == set_group: + self.set_drone_group() + elif action == show_trajectory: + self.show_drone_trajectory() + + def on_selection_changed(self): + selected_items = self.drone_table.selectedItems() + if selected_items: + drone_id = self.drone_table.item(selected_items[0].row(), 0).text() + self.drone_selected.emit(drone_id) + + def apply_filters(self): + status_filter = self.status_filter.currentText() + group_filter = self.group_filter.currentText() + + for row in range(self.drone_table.rowCount()): + show_row = True + + if status_filter != "全部状态": + status = self.drone_table.item(row, 2).text() + if status != status_filter: + show_row = False + + if group_filter != "全部分组": + group = self.drone_table.item(row, 6).text() + if group != group_filter: + show_row = False + + self.drone_table.setRowHidden(row, not show_row) + + def set_drone_color(self): + """设置无人机颜色""" + selected_items = self.drone_table.selectedItems() + if not selected_items: + QMessageBox.warning(self, "警告", "请先选择要设置颜色的无人机") + return + + drone_id = self.drone_table.item(selected_items[0].row(), 0).text() + color = QColorDialog.getColor() + if color.isValid(): + self.drone_colors[drone_id] = color + self.update_drone_color(drone_id) + QMessageBox.information(self, "功能确认", f"无人机 {drone_id} 颜色已设置") + + def set_drone_group(self): + """设置无人机分组""" + selected_items = self.drone_table.selectedItems() + if not selected_items: + QMessageBox.warning(self, "警告", "请先选择要设置分组的无人机") + return + + drone_id = self.drone_table.item(selected_items[0].row(), 0).text() + group = self.group_filter.currentText() + if group != "全部分组": + self.drone_groups[drone_id] = group + self.update_drone_group(drone_id) + + if self.comm_manager: + self.comm_manager.update_drone(drone_id, {"group": group}) + + QMessageBox.information(self, "功能确认", f"设置无人机 {drone_id} 分组 (功能待实现)") + + def update_drone_color(self, drone_id): + for row in range(self.drone_table.rowCount()): + if self.drone_table.item(row, 0).text() == drone_id: + color = self.drone_colors.get(drone_id, QColor(255, 255, 255)) + for col in range(self.drone_table.columnCount()): + item = self.drone_table.item(row, col) + if item: + item.setBackground(QBrush(color)) + break + + def update_drone_group(self, drone_id): + for row in range(self.drone_table.rowCount()): + if self.drone_table.item(row, 0).text() == drone_id: + group = self.drone_groups.get(drone_id, "未分组") + self.drone_table.setItem(row, 6, QTableWidgetItem(group)) + break + + def view_drone_details(self): + QMessageBox.information(self, "功能确认", "查看详情按钮已连接") + # TODO: 实现查看无人机详情功能 + pass + + def show_drone_trajectory(self): + QMessageBox.information(self, "功能确认", "显示轨迹按钮已连接") + # TODO: 实现显示无人机轨迹功能 pass + + def add_drone(self): + dialog = AddDroneDialog(self) + if dialog.exec_() == QDialog.Accepted: + drone_info = dialog.get_drone_info() + # TODO: Add drone to actual data source (e.g., comm_manager) + self.add_drone_to_table(drone_info['id'], drone_info) # Add to table for now + QMessageBox.information(self, "功能确认", f"添加无人机: {drone_info['id']}") def edit_drone(self): - # TODO: 实现编辑无人机信息功能 - pass + selected_items = self.drone_table.selectedItems() + if not selected_items: + QMessageBox.warning(self, "提示", "请先选择要编辑的无人机") + return + # For simplicity, we'll just show a message. Actual editing would require a dialog. + drone_id = self.drone_table.item(selected_items[0].row(), 0).text() + QMessageBox.information(self, "功能确认", f"编辑无人机 {drone_id} (功能待实现)") + # TODO: Implement edit drone functionality def delete_drone(self): - # TODO: 实现删除无人机功能 + selected_items = self.drone_table.selectedItems() + if not selected_items: + QMessageBox.warning(self, "提示", "请先选择要删除的无人机") + return + drone_id = self.drone_table.item(selected_items[0].row(), 0).text() + reply = QMessageBox.question(self, "确认删除", f"确定要删除无人机 {drone_id} 吗?", + QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + # TODO: Remove drone from actual data source (e.g., comm_manager) + self.remove_drone_from_table(drone_id) # Remove from table for now + QMessageBox.information(self, "功能确认", f"删除无人机 {drone_id}") + + def refresh_status(self): + # TODO: Implement actual status refresh logic from comm_manager or data source + QMessageBox.information(self, "功能确认", "刷新状态 (功能待实现)") + # Example: self.update_drone_list(self.comm_manager.get_all_drones() if self.comm_manager else []) + + def update_drone_list(self, drone_list): + # 这里可以实现刷新表格的逻辑,当前先占位 pass + + def add_drone_to_table(self, drone_id, info): + """添加无人机到表格""" + row = self.drone_table.rowCount() + self.drone_table.insertRow(row) - def refresh_status(self): - # TODO: 实现刷新状态功能 - pass \ No newline at end of file + # 设置各列数据 + self.drone_table.setItem(row, 0, QTableWidgetItem(info.get("id", ""))) + self.drone_table.setItem(row, 1, QTableWidgetItem(info.get("model", ""))) + self.drone_table.setItem(row, 2, QTableWidgetItem(info.get("status", ""))) + self.drone_table.setItem(row, 3, QTableWidgetItem(f"{info.get('latitude', 0):.6f}, {info.get('longitude', 0):.6f}")) + self.drone_table.setItem(row, 4, QTableWidgetItem(f"{info.get('battery', 0)}%")) + self.drone_table.setItem(row, 5, QTableWidgetItem(f"{info.get('payload_type', '')} ({info.get('payload_weight', 0)}kg)")) + self.drone_table.setItem(row, 6, QTableWidgetItem(info.get("group", ""))) + self.drone_table.setItem(row, 7, QTableWidgetItem(f"{info.get('signal_strength', 0)}%")) + + # 应用颜色 + if drone_id in self.drone_colors: + self.update_drone_color(drone_id) + + def remove_drone_from_table(self, drone_id): + """从表格中移除无人机""" + for row in range(self.drone_table.rowCount()): + if self.drone_table.item(row, 0).text() == drone_id: + self.drone_table.removeRow(row) + break + + def update_drone_in_table(self, drone_id, info): + """更新表格中的无人机信息""" + for row in range(self.drone_table.rowCount()): + if self.drone_table.item(row, 0).text() == drone_id: + self.drone_table.setItem(row, 1, QTableWidgetItem(info.get("model", ""))) + self.drone_table.setItem(row, 2, QTableWidgetItem(info.get("status", ""))) + self.drone_table.setItem(row, 3, QTableWidgetItem(f"{info.get('latitude', 0):.6f}, {info.get('longitude', 0):.6f}")) + self.drone_table.setItem(row, 4, QTableWidgetItem(f"{info.get('battery', 0)}%")) + self.drone_table.setItem(row, 5, QTableWidgetItem(f"{info.get('payload_type', '')} ({info.get('payload_weight', 0)}kg)")) + self.drone_table.setItem(row, 6, QTableWidgetItem(info.get("group", ""))) + self.drone_table.setItem(row, 7, QTableWidgetItem(f"{info.get('signal_strength', 0)}%")) + break + + def update_drone_color(self, drone_id): + for row in range(self.drone_table.rowCount()): + if self.drone_table.item(row, 0).text() == drone_id: + color = self.drone_colors.get(drone_id, QColor(255, 255, 255)) + for col in range(self.drone_table.columnCount()): + item = self.drone_table.item(row, col) + if item: + item.setBackground(QBrush(color)) + break + + def update_drone_group(self, drone_id): + for row in range(self.drone_table.rowCount()): + if self.drone_table.item(row, 0).text() == drone_id: + group = self.drone_groups.get(drone_id, "未分组") + self.drone_table.setItem(row, 6, QTableWidgetItem(group)) + break + + def view_drone_details(self): + selected_items = self.drone_table.selectedItems() + if selected_items: + drone_id = self.drone_table.item(selected_items[0].row(), 0).text() + self.drone_selected.emit(drone_id) + + def show_drone_trajectory(self): + """显示无人机轨迹""" + selected_items = self.drone_table.selectedItems() + if not selected_items: + QMessageBox.warning(self, "警告", "请先选择要查看轨迹的无人机") + return + + drone_id = self.drone_table.item(selected_items[0].row(), 0).text() + # TODO: 实现轨迹显示功能 + QMessageBox.information(self, "提示", f"显示无人机 {drone_id} 的轨迹") \ No newline at end of file diff --git a/Src/command_center/ui/login_view.py b/Src/command_center/ui/login_view.py index efbad863..c88e2429 100644 --- a/Src/command_center/ui/login_view.py +++ b/Src/command_center/ui/login_view.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +# File: login_view.py +# Purpose: 定义登录界面的 UI 和逻辑。 + from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QMessageBox) from PyQt5.QtCore import Qt, pyqtSignal diff --git a/Src/command_center/ui/main_view.py b/Src/command_center/ui/main_view.py deleted file mode 100644 index 36ffd5f0..00000000 --- a/Src/command_center/ui/main_view.py +++ /dev/null @@ -1,87 +0,0 @@ -from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, - QLabel, QPushButton, QTabWidget, QMessageBox) -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QFont - -class MainView(QMainWindow): - def __init__(self): - super().__init__() - self.setWindowTitle("无人机后勤输送系统 - 指挥控制中心") - self.setMinimumSize(800, 600) - self.init_ui() - - def init_ui(self): - # 创建主窗口部件 - central_widget = QWidget() - self.setCentralWidget(central_widget) - main_layout = QVBoxLayout(central_widget) - - # 顶部工具栏 - toolbar = QHBoxLayout() - self.status_label = QLabel("系统状态: 正常") - self.status_label.setFont(QFont('Arial', 10)) - logout_button = QPushButton("退出登录") - logout_button.clicked.connect(self.handle_logout) - - toolbar.addWidget(self.status_label) - toolbar.addStretch() - toolbar.addWidget(logout_button) - main_layout.addLayout(toolbar) - - # 主内容区域 - self.tab_widget = QTabWidget() - - # 创建各个功能标签页 - self.tab_widget.addTab(self.create_drone_management_tab(), "无人机管理") - self.tab_widget.addTab(self.create_task_management_tab(), "任务管理") - self.tab_widget.addTab(self.create_monitoring_tab(), "实时监控") - self.tab_widget.addTab(self.create_system_settings_tab(), "系统设置") - - main_layout.addWidget(self.tab_widget) - - def create_drone_management_tab(self): - tab = QWidget() - layout = QVBoxLayout(tab) - - # 添加无人机管理相关的控件 - layout.addWidget(QLabel("无人机管理功能待实现")) - - return tab - - def create_task_management_tab(self): - tab = QWidget() - layout = QVBoxLayout(tab) - - # 添加任务管理相关的控件 - layout.addWidget(QLabel("任务管理功能待实现")) - - return tab - - def create_monitoring_tab(self): - tab = QWidget() - layout = QVBoxLayout(tab) - - # 添加实时监控相关的控件 - layout.addWidget(QLabel("实时监控功能待实现")) - - return tab - - def create_system_settings_tab(self): - tab = QWidget() - layout = QVBoxLayout(tab) - - # 添加系统设置相关的控件 - layout.addWidget(QLabel("系统设置功能待实现")) - - return tab - - def handle_logout(self): - reply = QMessageBox.question(self, '确认退出', - '确定要退出登录吗?', - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No) - - if reply == QMessageBox.Yes: - # TODO: 实现实际的登出逻辑 - self.close() - # 这里应该触发登录窗口的显示 \ No newline at end of file diff --git a/Src/command_center/ui/map_data_model.py b/Src/command_center/ui/map_data_model.py new file mode 100644 index 00000000..8a5b009a --- /dev/null +++ b/Src/command_center/ui/map_data_model.py @@ -0,0 +1,39 @@ +from PyQt5.QtCore import QObject, pyqtSignal + +class MapDataModel(QObject): + data_changed = pyqtSignal() + + def __init__(self): + super().__init__() + self.map_pixmap = None + self.threat_points = [] + self.start_point = None + self.goal_point = None + self.paths = [] + + def set_map(self, pixmap): + self.map_pixmap = pixmap + self.data_changed.emit() + + def add_threat_point(self, point): + self.threat_points.append(point) + self.data_changed.emit() + + def set_start_point(self, point): + self.start_point = point + self.data_changed.emit() + + def set_goal_point(self, point): + self.goal_point = point + self.data_changed.emit() + + def clear_all(self): + self.threat_points.clear() + self.start_point = None + self.goal_point = None + self.paths = [] + self.data_changed.emit() + + def set_paths(self, paths): + self.paths = paths + self.data_changed.emit() \ No newline at end of file diff --git a/Src/command_center/ui/path_layer_view.py b/Src/command_center/ui/path_layer_view.py index c4e40da4..15b928d4 100644 --- a/Src/command_center/ui/path_layer_view.py +++ b/Src/command_center/ui/path_layer_view.py @@ -1,63 +1,80 @@ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTableWidget, QTableWidgetItem) -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QPointF +from .base_map_view import BaseMapView -class PathLayerView(QWidget): - def __init__(self): - super().__init__() - self.init_ui() +class PathLayerView(BaseMapView): + def __init__(self, map_data_model): + super().__init__(map_data_model) + self.add_path_mode = False + self.current_path = [] + self.init_additional_ui() - def init_ui(self): - # 创建主布局 - main_layout = QVBoxLayout() + def init_additional_ui(self): + # 创建路径控制按钮 + path_control_layout = QHBoxLayout() - # 创建路径列表 - self.path_table = QTableWidget() - self.path_table.setColumnCount(4) - self.path_table.setHorizontalHeaderLabels(["ID", "起点", "终点", "状态"]) - self.path_table.setStyleSheet("QTableWidget { border: 1px solid #ccc; }") - main_layout.addWidget(self.path_table) + self.add_path_btn = QPushButton("添加路径点") + self.add_path_btn.setCheckable(True) + self.add_path_btn.clicked.connect(self.toggle_add_path_mode) - # 创建控制按钮 - button_layout = QHBoxLayout() - self.add_path_btn = QPushButton("添加路径") - self.edit_path_btn = QPushButton("编辑路径") - self.delete_path_btn = QPushButton("删除路径") - self.simulate_path_btn = QPushButton("模拟路径") + self.complete_path_btn = QPushButton("完成路径") + self.complete_path_btn.clicked.connect(self.complete_path) + self.complete_path_btn.setEnabled(False) - button_layout.addWidget(self.add_path_btn) - button_layout.addWidget(self.edit_path_btn) - button_layout.addWidget(self.delete_path_btn) - button_layout.addWidget(self.simulate_path_btn) + self.clear_paths_btn = QPushButton("清除所有路径") + self.clear_paths_btn.clicked.connect(self.clear_all_paths) - main_layout.addLayout(button_layout) + path_control_layout.addWidget(self.add_path_btn) + path_control_layout.addWidget(self.complete_path_btn) + path_control_layout.addWidget(self.clear_paths_btn) - # 创建路径详情区域 - self.path_detail = QLabel("路径详情") - self.path_detail.setAlignment(Qt.AlignCenter) - self.path_detail.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;") - main_layout.addWidget(self.path_detail) + # 将按钮添加到主布局 + layout = self.layout() + if layout: + layout.addLayout(path_control_layout) + else: + print("Error: Layout not found in PathLayerView") - self.setLayout(main_layout) + def toggle_add_path_mode(self): + self.add_path_mode = self.add_path_btn.isChecked() + if self.add_path_mode: + self.add_path_btn.setText("取消添加点") + self.complete_path_btn.setEnabled(bool(self.current_path)) + else: + self.add_path_btn.setText("添加路径点") + self.complete_path_btn.setEnabled(False) + + def complete_path(self): + if len(self.current_path) > 1: + # Add a copy to the main data model + self.map_data_model.paths.append(list(self.current_path)) + self.map_data_model.data_changed.emit() + self.current_path = [] + self.add_path_mode = False + self.add_path_btn.setChecked(False) + self.add_path_btn.setText("添加路径点") + self.complete_path_btn.setEnabled(False) + self.update_map() + + def clear_all_paths(self): + self.current_path = [] + self.map_data_model.paths = [] + self.map_data_model.data_changed.emit() + self.update_map() - # 连接信号 - self.add_path_btn.clicked.connect(self.add_path) - self.edit_path_btn.clicked.connect(self.edit_path) - self.delete_path_btn.clicked.connect(self.delete_path) - self.simulate_path_btn.clicked.connect(self.simulate_path) - - def add_path(self): - # TODO: 实现添加路径功能 - pass - - def edit_path(self): - # TODO: 实现编辑路径功能 - pass - - def delete_path(self): - # TODO: 实现删除路径功能 - pass - - def simulate_path(self): - # TODO: 实现路径模拟功能 - pass \ No newline at end of file + def handle_map_click(self, map_point: QPointF): + """Handles clicks forwarded from BaseMapView.""" + if self.add_path_mode: + img_x = int(map_point.x()) + img_y = int(map_point.y()) + # 添加路径点到当前路径 + self.current_path.append((img_x, img_y)) + self.complete_path_btn.setEnabled(True) + + print(f"Added path point: {img_x}, {img_y}") + # self.update_map() + + # Remove the old mousePressEvent + # def mousePressEvent(self, event): + # pass \ No newline at end of file diff --git a/Src/command_center/ui/path_planning_view.py b/Src/command_center/ui/path_planning_view.py index e2d84cf3..74cb42bf 100644 --- a/Src/command_center/ui/path_planning_view.py +++ b/Src/command_center/ui/path_planning_view.py @@ -1,6 +1,10 @@ +# -*- coding: utf-8 -*- +# File: path_planning_view.py +# Purpose: 定义路径规划标签页的 UI,提供设置路径规划参数和触发规划操作的界面。 + from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QGroupBox, QFormLayout, QComboBox, - QSpinBox, QDoubleSpinBox) + QSpinBox, QDoubleSpinBox, QMessageBox) from PyQt5.QtCore import Qt class PathPlanningView(QWidget): @@ -76,13 +80,16 @@ class PathPlanningView(QWidget): self.export_path_btn.clicked.connect(self.export_path) def plan_path(self): - # TODO: 实现路径规划功能 + QMessageBox.information(self, "功能确认", "规划路径 (功能待实现)") + # TODO: Implement path planning logic pass def clear_path(self): - # TODO: 实现清除路径功能 + QMessageBox.information(self, "功能确认", "清除路径 (功能待实现)") + # TODO: Clear path from MapDataModel or internal state pass def export_path(self): - # TODO: 实现导出路径功能 + QMessageBox.information(self, "功能确认", "导出路径 (功能待实现)") + # TODO: Implement path export functionality pass \ No newline at end of file diff --git a/Src/command_center/ui/path_simulation_view.py b/Src/command_center/ui/path_simulation_view.py deleted file mode 100644 index 2f21a1b1..00000000 --- a/Src/command_center/ui/path_simulation_view.py +++ /dev/null @@ -1,109 +0,0 @@ -from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QPushButton, QGroupBox, QFormLayout, QProgressBar, - QSpinBox, QDoubleSpinBox) -from PyQt5.QtCore import Qt, QTimer - -class PathSimulationView(QWidget): - def __init__(self): - super().__init__() - self.init_ui() - self.simulation_timer = QTimer() - self.simulation_timer.timeout.connect(self.update_simulation) - self.simulation_progress = 0 - - def init_ui(self): - # 创建主布局 - main_layout = QVBoxLayout() - - # 创建模拟控制组 - control_group = QGroupBox("模拟控制") - control_layout = QFormLayout() - - self.speed_spinbox = QDoubleSpinBox() - self.speed_spinbox.setRange(0.1, 10.0) - self.speed_spinbox.setValue(1.0) - self.speed_spinbox.setSingleStep(0.1) - - self.interval_spinbox = QSpinBox() - self.interval_spinbox.setRange(10, 1000) - self.interval_spinbox.setValue(100) - self.interval_spinbox.setSingleStep(10) - - control_layout.addRow("模拟速度:", self.speed_spinbox) - control_layout.addRow("更新间隔(ms):", self.interval_spinbox) - - control_group.setLayout(control_layout) - main_layout.addWidget(control_group) - - # 创建模拟进度条 - self.progress_bar = QProgressBar() - self.progress_bar.setRange(0, 100) - self.progress_bar.setValue(0) - main_layout.addWidget(self.progress_bar) - - # 创建控制按钮 - button_layout = QHBoxLayout() - self.start_btn = QPushButton("开始模拟") - self.pause_btn = QPushButton("暂停") - self.stop_btn = QPushButton("停止") - self.reset_btn = QPushButton("重置") - - button_layout.addWidget(self.start_btn) - button_layout.addWidget(self.pause_btn) - button_layout.addWidget(self.stop_btn) - button_layout.addWidget(self.reset_btn) - - main_layout.addLayout(button_layout) - - # 创建状态显示组 - status_group = QGroupBox("模拟状态") - status_layout = QFormLayout() - - self.time_label = QLabel("0.0s") - self.distance_label = QLabel("0.0m") - self.altitude_label = QLabel("0.0m") - self.speed_label = QLabel("0.0m/s") - - status_layout.addRow("已用时间:", self.time_label) - status_layout.addRow("飞行距离:", self.distance_label) - status_layout.addRow("当前高度:", self.altitude_label) - status_layout.addRow("当前速度:", self.speed_label) - - status_group.setLayout(status_layout) - main_layout.addWidget(status_group) - - self.setLayout(main_layout) - - # 连接信号 - self.start_btn.clicked.connect(self.start_simulation) - self.pause_btn.clicked.connect(self.pause_simulation) - self.stop_btn.clicked.connect(self.stop_simulation) - self.reset_btn.clicked.connect(self.reset_simulation) - - def start_simulation(self): - self.simulation_timer.start(self.interval_spinbox.value()) - - def pause_simulation(self): - self.simulation_timer.stop() - - def stop_simulation(self): - self.simulation_timer.stop() - self.simulation_progress = 0 - self.progress_bar.setValue(0) - - def reset_simulation(self): - self.simulation_progress = 0 - self.progress_bar.setValue(0) - self.time_label.setText("0.0s") - self.distance_label.setText("0.0m") - self.altitude_label.setText("0.0m") - self.speed_label.setText("0.0m/s") - - def update_simulation(self): - # TODO: 实现模拟更新逻辑 - self.simulation_progress += self.speed_spinbox.value() - if self.simulation_progress > 100: - self.simulation_progress = 100 - self.simulation_timer.stop() - - self.progress_bar.setValue(int(self.simulation_progress)) \ No newline at end of file diff --git a/Src/command_center/ui/simple_map_view.py b/Src/command_center/ui/simple_map_view.py new file mode 100644 index 00000000..f8ef21d1 --- /dev/null +++ b/Src/command_center/ui/simple_map_view.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# File: simple_map_view.py +# Purpose: 定义地图视图标签页,继承自 BaseMapView,主要负责威胁点的交互。 + +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QFileDialog +from PyQt5.QtGui import QPixmap, QPainter, QPen, QColor +from PyQt5.QtCore import Qt, pyqtSignal, QPointF +from .base_map_view import BaseMapView + +class SimpleMapView(BaseMapView): + # 当威胁点变化时发出信号,便于主程序联动路径规划 + threat_points_changed = pyqtSignal(list) + + def __init__(self, map_data_model): + super().__init__(map_data_model) + self.add_threat_point_mode = False + self.init_additional_ui() + # Force re-connection of load map action in case it was overwritten + if hasattr(self, 'load_map_action'): + print("SimpleMapView: Forcing re-connection of load_map_action") + try: + self.load_map_action.triggered.disconnect() + except TypeError: # No connections exist + pass + self.load_map_action.triggered.connect(self.load_map_image) + else: + print("SimpleMapView: Warning - load_map_action not found in base class?") + + def init_additional_ui(self): + # 添加威胁点标记按钮 + self.threat_point_btn = QPushButton("标记威胁点") + self.threat_point_btn.setCheckable(True) + self.threat_point_btn.clicked.connect(self.toggle_threat_point_mode) + + # 将按钮添加到布局中 + layout = self.layout() + if layout: + layout.addWidget(self.threat_point_btn) + else: + print("Error: Layout not found in SimpleMapView") # Error handling + + def toggle_threat_point_mode(self): + print("SimpleMapView: toggle_threat_point_mode called") # Debug print + is_checked = self.threat_point_btn.isChecked() + print(f"SimpleMapView: threat_point_btn isChecked: {is_checked}") + self.add_threat_point_mode = is_checked + print(f"SimpleMapView: self.add_threat_point_mode set to: {self.add_threat_point_mode}") + if self.add_threat_point_mode: + self.threat_point_btn.setText("取消标记") + else: + self.threat_point_btn.setText("标记威胁点") + + def handle_map_click(self, map_point: QPointF): + """Handles clicks forwarded from BaseMapView.""" + print(f"SimpleMapView: handle_map_click called, mode: {self.add_threat_point_mode}") # Debug print + if self.add_threat_point_mode: + img_x = int(map_point.x()) + img_y = int(map_point.y()) + print(f"SimpleMapView: Adding threat point at ({img_x}, {img_y})") # Debug print + # 添加威胁点到数据模型 + self.map_data_model.add_threat_point((img_x, img_y)) + # Emit signal (optional, BaseMapView already emits on data change) + # self.threat_points_changed.emit(self.map_data_model.threat_points) + + # Remove the old mousePressEvent if it exists + # def mousePressEvent(self, event): # <--- REMOVE THIS METHOD + # pass + + # load_map_image and update_map are now handled by BaseMapView + # We might need specific update logic here in the future if SimpleMapView + # needs drawing beyond what BaseMapView provides. \ No newline at end of file diff --git a/Src/command_center/ui/status_dashboard.py b/Src/command_center/ui/status_dashboard.py index 6e13ecdc..af2feed6 100644 --- a/Src/command_center/ui/status_dashboard.py +++ b/Src/command_center/ui/status_dashboard.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +# File: status_dashboard.py +# Purpose: 定义状态仪表板标签页的 UI,显示系统整体状态和性能指标。 + from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QProgressBar, QGroupBox) from PyQt5.QtCore import Qt diff --git a/Src/command_center/ui/threat_layer_view.py b/Src/command_center/ui/threat_layer_view.py index e77b9cb6..9b8b2823 100644 --- a/Src/command_center/ui/threat_layer_view.py +++ b/Src/command_center/ui/threat_layer_view.py @@ -1,56 +1,71 @@ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, - QPushButton, QTableWidget, QTableWidgetItem) -from PyQt5.QtCore import Qt + QPushButton, QTableWidget, QTableWidgetItem, QSpinBox) +from PyQt5.QtCore import Qt, QPointF +from .base_map_view import BaseMapView -class ThreatLayerView(QWidget): - def __init__(self): - super().__init__() - self.init_ui() +class ThreatLayerView(BaseMapView): + def __init__(self, map_data_model): + super().__init__(map_data_model) + self.add_threat_mode = False + self.threat_radius = 50 # 默认威胁半径 + self.init_additional_ui() - def init_ui(self): - # 创建主布局 - main_layout = QVBoxLayout() + def init_additional_ui(self): + # 创建威胁控制面板 + threat_control_layout = QHBoxLayout() - # 创建威胁列表 - self.threat_table = QTableWidget() - self.threat_table.setColumnCount(4) - self.threat_table.setHorizontalHeaderLabels(["ID", "类型", "位置", "威胁等级"]) - self.threat_table.setStyleSheet("QTableWidget { border: 1px solid #ccc; }") - main_layout.addWidget(self.threat_table) + # 威胁点标记按钮 + self.add_threat_btn = QPushButton("添加威胁区域") + self.add_threat_btn.setCheckable(True) + self.add_threat_btn.clicked.connect(self.toggle_add_threat_mode) - # 创建控制按钮 - button_layout = QHBoxLayout() - self.add_threat_btn = QPushButton("添加威胁") - self.edit_threat_btn = QPushButton("编辑威胁") - self.delete_threat_btn = QPushButton("删除威胁") + # 威胁半径控制 + radius_label = QLabel("威胁半径:") + self.radius_spinbox = QSpinBox() + self.radius_spinbox.setRange(10, 200) + self.radius_spinbox.setValue(self.threat_radius) + self.radius_spinbox.valueChanged.connect(self.set_threat_radius) - button_layout.addWidget(self.add_threat_btn) - button_layout.addWidget(self.edit_threat_btn) - button_layout.addWidget(self.delete_threat_btn) + # 清除威胁按钮 + self.clear_threat_btn = QPushButton("清除威胁") + self.clear_threat_btn.clicked.connect(self.clear_threats) - main_layout.addLayout(button_layout) + # 添加控件到布局 + threat_control_layout.addWidget(self.add_threat_btn) + threat_control_layout.addWidget(self.clear_threat_btn) - # 创建威胁详情区域 - self.threat_detail = QLabel("威胁详情") - self.threat_detail.setAlignment(Qt.AlignCenter) - self.threat_detail.setStyleSheet("background-color: #f0f0f0; border: 1px solid #ccc;") - main_layout.addWidget(self.threat_detail) - - self.setLayout(main_layout) - - # 连接信号 - self.add_threat_btn.clicked.connect(self.add_threat) - self.edit_threat_btn.clicked.connect(self.edit_threat) - self.delete_threat_btn.clicked.connect(self.delete_threat) - - def add_threat(self): - # TODO: 实现添加威胁功能 - pass + # 将控制面板添加到主布局 + layout = self.layout() + if layout: + layout.addLayout(threat_control_layout) + else: + print("Error: Layout not found in ThreatLayerView") + + def toggle_add_threat_mode(self): + self.add_threat_mode = self.add_threat_btn.isChecked() + if self.add_threat_mode: + self.add_threat_btn.setText("取消添加") + else: + self.add_threat_btn.setText("添加威胁区域") + + def set_threat_radius(self, value): + self.threat_radius = value - def edit_threat(self): - # TODO: 实现编辑威胁功能 - pass + def clear_threats(self): + self.map_data_model.threat_points = [] + self.map_data_model.data_changed.emit() + self.update_map() # Force redraw - def delete_threat(self): - # TODO: 实现删除威胁功能 - pass \ No newline at end of file + def handle_map_click(self, map_point: QPointF): + """Handles clicks forwarded from BaseMapView.""" + if self.add_threat_mode: + img_x = int(map_point.x()) + img_y = int(map_point.y()) + # 添加威胁点到数据模型 + # Note: Threat radius is stored but not visualized yet + self.map_data_model.add_threat_point((img_x, img_y)) + # BaseMapView will redraw automatically due to data_changed signal + + # Remove the old mousePressEvent + # def mousePressEvent(self, event): + # pass \ No newline at end of file -- 2.34.1