@ -0,0 +1,8 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (pythonProject2)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/project.iml" filepath="$PROJECT_DIR$/.idea/project.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Binary file not shown.
@ -1 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# File: __init__.py (in command_center)
|
||||
# Purpose: 将 command_center 目录标记为 Python 包。
|
||||
|
||||
# 指挥控制中心包
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
@ -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()
|
||||
@ -1 +1,7 @@
|
||||
# UI组件包
|
||||
# -*- coding: utf-8 -*-
|
||||
# File: __init__.py (in ui)
|
||||
# Purpose: 将 ui 目录标记为 Python 包。
|
||||
|
||||
# UI组件包
|
||||
|
||||
# UI package initialization
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
@ -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()
|
||||
# 这里应该触发登录窗口的显示
|
||||
@ -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()
|
||||
@ -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
|
||||
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
|
||||
@ -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))
|
||||
@ -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
|
||||
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
|
||||
Loading…
Reference in new issue