<think>我们注意到在之前的代码中,当检测到allimages=1时,我们进入`final_complete`状态,并在5秒内从当前进度值增长到100%。但是,用户反馈说没有出现5秒增长到100%的效果。
可能的原因:
1. 状态转换逻辑有误,可能没有正确进入`final_complete`状态。
2. 在`final_complete`状态的处理中,可能由于条件判断导致没有执行动画。
3. 进度文件读取失败,导致没有检测到`allimages=1`。
为了确保正确性,我们将进行以下调整:
1. 加强日志输出,以便跟踪状态转换。
2. 确保在检测到`allimages=1`时,无论当前处于什么状态(除了已经完成的状态),都会切换到`final_complete`状态。
3. 在`final_complete`状态的处理中,确保动画逻辑被执行。
另外,我们注意到在`update_progress_bar`函数的开头,如果`all_images_detected`为True,则直接返回。这会导致在最终完成动画过程中,由于`all_images_detected`在进入`final_complete`状态时已经被设置为True,所以不会执行后续的状态处理。因此,我们需要调整这个条件。
修改计划:
1. 在`update_progress_bar`函数中,即使`all_images_detected`为True,也要继续处理`final_complete`状态,直到动画结束。
2. 在检测到`allimages=1`时,我们设置`all_images_detected`为True,并进入`final_complete`状态,但不会立即返回,而是继续执行状态处理。
具体修改如下:
- 移除`if not self.task_started or self.all_images_detected: return`这一行,改为在函数开始时只检查`not self.task_started`,而对于`all_images_detected`,我们允许进入`final_complete`状态的处理。
- 在`final_complete`状态处理完成后,我们可以设置一个标志表示动画已经完成,然后停止更新。
但是,为了避免在动画完成后还不断读取文件,我们可以在动画完成后将`task_started`设置为False?或者保留一个动画完成标志。
我们调整:
1. 将`all_images_detected`的含义改为“检测到allimages=1”,但不代表动画已经完成。
2. 新增`animation_completed`标志,表示最终完成动画已经结束。
或者,我们可以这样设计:
- 当检测到`allimages=1`时,设置`all_images_detected = True`,并进入`final_complete`状态。
- 在`final_complete`状态中,当动画完成(5秒后),我们设置`animation_completed = True`。
- 在`update_progress_bar`函数开头,如果`animation_completed`为True,则直接返回。
为了简化,我们不在状态机中引入过多变量,而是利用状态本身。在`final_complete`状态动画结束后,我们停留在该状态,并且不再更新进度条(但会继续显示100%和“建模完成”)。
因此,修改步骤:
1. 在函数开头,如果任务没有开始或者动画已经完成(即处于`final_complete`状态且动画结束),则返回。但注意,在`final_complete`状态动画过程中我们需要更新,所以不能仅根据状态判断。
我们修改条件为:
```python
if not self.task_started or (self.all_images_detected and self.progress_phase != 'final_complete'):
return
```
但实际上,在进入`final_complete`状态后,我们仍然需要更新,所以这个条件不合适。
改为:
```python
if not self.task_started:
return
```
然后,在`final_complete`状态处理中,当动画结束后,我们设置一个标志表示建模完成,并且不再进行状态转换。但是,为了避免重复执行,我们在动画结束后将`task_started`设置为False?或者设置一个单独的完成标志。
我们选择:在`final_complete`状态动画结束后,将`self.task_started`设置为False,这样下一次就不会再进入更新。
修改代码:
```python
elif self.progress_phase == 'final_complete':
elapsed = time.time() - self.phase_start_time
if elapsed >= self.immediate_complete_duration:
# 5秒到,设置100%
self.progress_bar.setValue(100)
self.progress_stage_label.setText("进度阶段: 建模完成")
logging.info("建模完成")
# 标记任务结束,停止更新
self.task_started = False
else:
# 线性增长:从start_value到100
progress = int(self.start_value + (100 - self.start_value) * (elapsed / self.immediate_complete_duration))
self.progress_bar.setValue(progress)
```
同时,在检测到`allimages=1`时,我们进入`final_complete`状态,并重置开始时间。
另外,在读取进度文件时,如果已经检测到`allimages=1`,则不再检查其他块。
我们调整代码顺序,优先检查`allimages=1`。
完整修改后的`update_progress_bar`函数如下:
```python
def update_progress_bar(self):
"""更新进度条状态"""
if not self.task_started:
return
# 读取Progress.ini文件
config = configparser.ConfigParser()
try:
config.read('d:\\实时建模\\实时建模V3\\Progress.ini')
except Exception as e:
logging.error(f"读取进度文件失败: {e}")
return
# 检查allimages=1 (最高优先级)
if 'Progress' in config and 'allimages' in config['Progress'] and config['Progress']['allimages'] == '1':
if not self.all_images_detected:
# 首次检测到allimages=1
self.all_images_detected = True
self.progress_phase = 'final_complete'
self.phase_start_time = time.time()
self.start_value = self.progress_bar.value() # 记录当前进度值
self.progress_stage_label.setText("进度阶段: 建模完成中...")
logging.info("检测到allimages=1,开始最终完成动画")
# 状态处理
if self.progress_phase == 'idle':
# 开始自动增长
self.progress_phase = 'auto_grow'
self.phase_start_time = time.time()
self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块空三运算中")
elif self.progress_phase == 'auto_grow':
elapsed = time.time() - self.phase_start_time
if elapsed >= self.auto_grow_duration:
self.progress_bar.setValue(self.auto_grow_target)
else:
progress = int((elapsed / self.auto_grow_duration) * self.auto_grow_target)
self.progress_bar.setValue(progress)
# 检查当前块对应的imgs是否等于1
current_imgs_key = f'imgs{self.current_block}'
if 'Progress' in config and current_imgs_key in config['Progress'] and config['Progress'][current_imgs_key] == '1':
# 切换到立即完成状态
self.progress_phase = 'immediate_complete'
self.phase_start_time = time.time()
self.start_value = self.progress_bar.value() # 记录当前进度值
self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块完成中...")
logging.info(f"检测到{current_imgs_key}=1,开始块完成动画")
elif self.progress_phase == 'immediate_complete':
elapsed = time.time() - self.phase_start_time
if elapsed >= self.immediate_complete_duration:
# 5秒到,设置100%
self.progress_bar.setValue(100)
# 进入等待阶段
self.progress_phase = 'waiting'
self.phase_start_time = time.time()
self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块完成")
logging.info(f"第{self.current_block}块完成")
else:
# 线性增长:从start_value到100
progress = int(self.start_value + (100 - self.start_value) * (elapsed / self.immediate_complete_duration))
self.progress_bar.setValue(progress)
elif self.progress_phase == 'waiting':
elapsed = time.time() - self.phase_start_time
if elapsed >= self.waiting_duration:
# 等待结束,重置进度条,块号加1,然后进入auto_grow状态
self.progress_bar.setValue(0)
self.current_block += 1
self.progress_phase = 'auto_grow'
self.phase_start_time = time.time()
self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块空三运算中")
logging.info(f"开始处理第{self.current_block}块")
elif self.progress_phase == 'final_complete':
elapsed = time.time() - self.phase_start_time
if elapsed >= self.immediate_complete_duration:
# 5秒到,设置100%
self.progress_bar.setValue(100)
self.progress_stage_label.setText("进度阶段: 建模完成")
logging.info("建模完成")
# 标记任务结束,停止更新
self.task_started = False
else:
# 线性增长:从start_value到100
progress = int(self.start_value + (100 - self.start_value) * (elapsed / self.immediate_complete_duration))
self.progress_bar.setValue(progress)
```
注意:在`final_complete`状态动画结束后,我们将`task_started`设置为False,这样下一次`update_progress_bar`被调用时就会直接返回,不再处理。
同时,我们调整了代码顺序,先检查`allimages=1`,这样即使之前处于其他状态,也会被中断。
由于代码较长,我们只提供修改后的`update_progress_bar`函数,以及`MainWindow`类中新增的`animation_completed`变量(实际上不需要新增,我们使用`task_started`来控制)。
但是,请注意,在`run_command`方法中,我们重置了`task_started`为True,以及`all_images_detected`为False。
我们将提供完整的`MainWindow`类代码,但为了节省篇幅,主要展示修改部分。如果需要完整代码,请参考之前的代码,并将`update_progress_bar`函数替换为上述代码。
另外,在`run_command`方法中,我们重置状态变量:
```python
self.task_started = True # 设置任务开始标志为 True
self.all_images_detected = False # 重置
```
我们尝试这样修改后,应该能够解决allimages=1时没有5秒动画的问题。
如果问题仍然存在,请检查:
1. 进度文件路径是否正确:`'d:\\实时建模\\实时建模V3\\Progress.ini'`。
2. 确保进度文件中有`allimages=1`。
3. 日志记录是否正常工作,查看日志输出。
由于完整代码较长,这里只提供修改后的`update_progress_bar`函数,以及`run_command`方法中重置`all_images_detected`的部分。
如果用户需要,我们可以提供完整的修改后的代码,但考虑到篇幅,我们优先提供修改部分。
完整代码请参考之前的版本,并替换`update_progress_bar`函数,以及在`run_command`方法中重置`all_images_detected`。</think>我理解您的问题:当 `allimages=1` 出现时,进度条没有像预期那样在 5 秒内平滑增长到 100%。我将修复这个问题,确保进度条在所有情况下都能正确显示动画效果。以下是完整的解决方案:
```python
import sys
from PyQt5.QtWidgets import QScrollArea, QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, QLineEdit, QLabel, QFileDialog, QRadioButton, QComboBox, QCheckBox, QGroupBox, QListWidget, QProgressBar
from PyQt5.QtCore import QUrl, QRegExp, QTimer, Qt
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
from PyQt5.QtGui import QIcon, QIntValidator, QRegExpValidator
from PyQt5.QtWebChannel import QWebChannel
import configparser
import os
import subprocess
from PyQt5.QtWidgets import QMessageBox
import json
import time
import docker
import ctypes
import logging
import tkinter as tk
from tkinter import messagebox
import threading
import psutil
import shutil
import 文件服务2
from 服务校验 import validate_service
# 外部函数
sys.path.append(os.path.dirname(__file__)) # 添加当前文件的目录到路径
if getattr(sys, 'frozen', False):
# 如果是打包后的exe,使用sys.executable
base_dir = os.path.dirname(sys.executable)
else:
# 如果是普通脚本,使用__file__
base_dir = os.path.dirname(os.path.abspath(__file__))
# 将'\'替换为'/'
base_dir = base_dir.replace('\\', '/')
# 将第一个字母盘符写为大写
base_dir = base_dir[0].upper() + base_dir[1:]
print('文件路径', base_dir)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.task_started = True # 标志任务是否开始
self.current_block = 1 # 当前处理的块号
self.setWindowTitle("实时三维")
self.setGeometry(100, 100, 1366, 768)
self.setWindowIcon(QIcon('./icon.ico'))
self.process = None # 用于存储子进程的引用
check_docker_running() # 检查docker是否运行
# 进度条状态变量
self.progress_phase = 'idle' # 状态:'idle', 'auto_grow', 'immediate_complete', 'waiting', 'final_complete'
self.phase_start_time = None # 当前阶段开始的时间
self.auto_grow_target = 80 # 自动增长目标值(80%)
self.auto_grow_duration = 120 # 自动增长持续时间(120秒)
self.immediate_complete_duration = 5 # 立即完成持续时间(5秒)
self.waiting_duration = 3 # 等待时间(3秒)
self.all_images_detected = False # 是否检测到allimages=1
self.start_value = 0 # 用于记录进入immediate_complete状态时的进度值
self.final_animation_started = False # 最终动画是否已开始
self.node_process = None # 用于存储 node 进程的引用
self.start_node_process() # 启动 node 进程
# 初始化变量存储路径和选择值
self.paths = {
"project": base_dir,
"sensor": "",
"1": "",
"texture": os.path.join(base_dir, "ModelingScope.kml"),
"localtions":''
}
self.command_line = ""
self.data_source = "影像"
self.current_progress_key = None
# 主窗口布局
main_layout = QHBoxLayout()
# 左侧控件布局
left_layout = QVBoxLayout()
# 示例控件
self.start_button = QPushButton("开始任务")
self.start_button.clicked.connect(lambda:self.run_command(self.command_line))
left_layout.addWidget(self.start_button)
stop_button2 = QPushButton("文件传输")
stop_button2.clicked.connect(lambda:self.file_transfer())
left_layout.addWidget(stop_button2)
# 平台上传
stop_button3 = QPushButton("上传平台")
stop_button3.clicked.connect(lambda:self.file_transfer_to_platform())
left_layout.addWidget(stop_button3)
# 任务队列路径
self.createPathInput(left_layout, "任务队列路径:", "sensor")
# 图片文件路径
self.createPathInput(left_layout, "图片文件路径:", "localtions")
left_layout.addWidget(QLabel("数据源:"))
radiobuttons_layout = QHBoxLayout()
radio_image = QRadioButton("影像")
radio_image.setChecked(True)
radio_image.toggled.connect(lambda: self.setDataSource("影像", radio_image.isChecked()))
radiobuttons_layout.addWidget(radio_image)
left_layout.addLayout(radiobuttons_layout)
# 添加经纬度输入框布局
self.coordinates_layout = QVBoxLayout()
self.input_widgets = []
# 添加四组默认的输入框
for _ in range(4):
self.add_coordinate_input()
# 添加和删除按钮
button_layout = QHBoxLayout()
add_button = QPushButton("+")
remove_button = QPushButton("-")
add_button.clicked.connect(self.add_coordinate_input)
remove_button.clicked.connect(self.remove_coordinate_input)
button_layout.addWidget(add_button)
button_layout.addWidget(remove_button)
# 滚动区域
scroll_area = QScrollArea()
scroll_content = QWidget()
scroll_content.setLayout(self.coordinates_layout)
scroll_area.setWidget(scroll_content)
scroll_area.setWidgetResizable(True)
scroll_area.setFixedHeight(180)
left_layout.addWidget(QLabel("输入建模范围:"))
left_layout.addWidget(scroll_area)
left_layout.addLayout(button_layout)
# 建模结果文件名
left_layout.addWidget(QLabel("建模结果文件名:"))
self.result_file_name = QLineEdit("")
left_layout.addWidget(self.result_file_name)
self.result_file_name.textChanged.connect(self.update_json)
# 建模精度
left_layout.addWidget(QLabel("建模精度:"))
self.precision_combo = QComboBox()
self.precision_combo.addItem("快速")
self.precision_combo.addItem("普通")
self.precision_combo.addItem("精细")
left_layout.addWidget(self.precision_combo)
# 高级设置
advanced_group = QGroupBox("高级设置")
advanced_layout = QVBoxLayout()
advanced_layout.addWidget(QLabel("图片集大小(张):"))
self.tile_size_input = QLineEdit("20")
self.tile_size_input.setValidator(QIntValidator()) # 限制输入为整数
advanced_layout.addWidget(self.tile_size_input)
advanced_layout.addWidget(QLabel("照片获取等待时间(秒):"))
self.interval_input = QLineEdit("10")
self.interval_input.setValidator(QIntValidator()) # 限制输入为整数
advanced_layout.addWidget(self.interval_input)
advanced_layout.addWidget(QLabel("边飞边建:"))
self.checkbox1 = QRadioButton("正射")
self.checkbox2 = QRadioButton("倾斜")
self.checkbox3 = QRadioButton("全建")
self.checkbox1.setChecked(False)
self.checkbox2.setChecked(True)
self.checkbox3.setChecked(False)
advanced_layout.addWidget(self.checkbox1)
advanced_layout.addWidget(self.checkbox2)
advanced_layout.addWidget(self.checkbox3)
advanced_group.setLayout(advanced_layout)
left_layout.addWidget(advanced_group)
# 实时状态显示
self.status_group = QGroupBox("实时状态")
status_layout = QVBoxLayout()
self.file_count_label = QLabel("文件数量: 0")
self.file_names_list = QListWidget()
self.docker_count_label = QLabel("Docker容器数量: 0")
# 添加进度条和文字标签
self.progress_stage_label = QLabel("进度阶段: 等待开始")
self.progress_bar = QProgressBar()
self.progress_bar.setAlignment(Qt.AlignCenter)
self.progress_bar.setRange(0, 100)
self.progress_bar.setValue(0)
status_layout.addWidget(self.file_count_label)
status_layout.addWidget(self.file_names_list)
status_layout.addWidget(self.docker_count_label)
status_layout.addWidget(self.progress_stage_label)
status_layout.addWidget(self.progress_bar)
self.status_group.setLayout(status_layout)
left_layout.addWidget(self.status_group)
# 设置左侧宽度
left_widget = QWidget()
left_widget.setLayout(left_layout)
left_widget.setFixedWidth(300)
# 创建Web引擎视图
self.web_view = QWebEngineView()
self.web_view.load(QUrl("http://localhost:3123"))
web_channel = QWebChannel(self.web_view.page()) # 创建 WebChannel 实例
self.web_view.page().setWebChannel(web_channel) # 关联 WebChannel
self.web_view.page().runJavaScript("setInterval(function(){location.reload()}, 1000);")
# 将左侧控件和Web视图添加到主布局
main_layout.addWidget(left_widget)
main_layout.addWidget(self.web_view)
# 设置主widget
central_widget = QWidget()
central_widget.setLayout(main_layout)
self.setCentralWidget(central_widget)
# 定时更新状态
self.timer = QTimer()
self.timer.timeout.connect(self.update_status)
self.timer.start(1000) # 每秒更新一次状态
# 启动进度条更新循环
self.update_progress_timer = QTimer()
self.update_progress_timer.timeout.connect(self.update_progress_bar)
self.update_progress_timer.start(1000) # 每秒检查一次进度
def update_status(self):
# 更新文件数量和文件名
folder_path = self.paths["sensor"]
if os.path.exists(folder_path):
files = os.listdir(folder_path)
self.file_count_label.setText(f"文件数量: {len(files)}")
self.file_names_list.clear()
for file_name in files:
self.file_names_list.addItem(file_name)
else:
self.file_count_label.setText("文件数量: 0")
self.file_names_list.clear()
# 更新Docker容器数量
try:
result = subprocess.run(
["docker", "ps", "--filter", "ancestor=opendronemap/odm:gpu", "--format", "{{.ID}}"],
capture_output=True,
text=True,
shell=False,
creationflags=subprocess.CREATE_NO_WINDOW # 确保不弹出窗口
)
container_ids = result.stdout.strip().split('\n')
if container_ids and container_ids[0] == '':
container_ids = [] # 如果结果为空字符串,将其设为空列表
logging.debug(f"Container IDs: {container_ids}")
self.docker_count_label.setText(f"Docker容器数量: {len(container_ids)}")
except subprocess.CalledProcessError as e:
logging.error(f"获取Docker容器信息时出错: {e}")
self.docker_count_label.setText("Docker容器数量: 0")
except FileNotFoundError as e:
logging.error(f"Docker命令未找到: {e}")
self.docker_count_label.setText("Docker容器数量: 0")
def add_coordinate_input(self):
"""添加一个新的经纬度输入行"""
layout = QHBoxLayout()
lng_input = QLineEdit()
lat_input = QLineEdit()
lng_input.setPlaceholderText("经度")
lat_input.setPlaceholderText("纬度")
# 使用正则表达式限制输入为正负加小数
validator = QRegExpValidator(QRegExp(r"^-?\d+(\.\d+)?$"))
lng_input.setValidator(validator)
lat_input.setValidator(validator)
layout.addWidget(lng_input)
layout.addWidget(lat_input)
self.coordinates_layout.addLayout(layout)
self.input_widgets.append((lng_input, lat_input))
def generate_kml(self):
"""生成KML内容"""
kml_content = f"""<?xml version="1.0" encoding="utf-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<Schema id="Dataset_203sup" name="Dataset_203sup">
<SimpleField name="SmUserID" type="int"/>
</Schema>
<Folder>
<Placemark>
<name/>
<ExtendedData>
<SchemaData schemaUrl="#Dataset_203sup">
<SimpleData name="SmUserID">0</SimpleData>
</SchemaData>
</ExtendedData>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>
"""
coordinates = []
for lng_input, lat_input in self.input_widgets:
lng = lng_input.text()
lat = lat_input.text()
if lng and lat:
coordinates.append(f"{lng},{lat}")
# 在最后再添加第一个坐标点
if coordinates:
coordinates.append(coordinates[0])
kml_content += "\n".join(coordinates) + "\n </coordinates>\n </LinearRing>\n </outerBoundaryIs>\n </Polygon>\n </Placemark>\n </Folder>\n </Document>\n</kml>"
# 写入KML文件
kml_path = self.paths["texture"]
with open(kml_path, 'w', encoding='utf-8') as kml_file:
kml_file.write(kml_content)
print("KML文件已写入:", kml_path)
def remove_coordinate_input(self):
"""删除最下面的一个经纬度输入行"""
if len(self.input_widgets) > 4:
layout = self.coordinates_layout.takeAt(len(self.input_widgets) - 1)
for i in range(layout.count()):
widget = layout.itemAt(i).widget()
if widget:
widget.deleteLater()
self.input_widgets.pop()
else:
QMessageBox.warning(self, "提示", "至少需要保留四组坐标。")
def start_node_process(self):
"""启动 node 进程并保存其引用"""
self.node_process = subprocess.Popen('node result_server_logs.js', shell=False, creationflags=subprocess.CREATE_NO_WINDOW)
def update_json(self):
# 获取建模结果文件名
result_filename = self.result_file_name.text()
# 创建要写入的 JSON 数据
data = {
"name": result_filename
}
# 写入 JSON 文件
with open('./result/resultName.json', 'w', encoding='utf-8') as json_file:
json.dump(data, json_file, ensure_ascii=False, indent=4)
def create_config_file(self):
"""生成配置文件 config.ini"""
config = configparser.ConfigParser()
if self.paths["1"] is not None and self.paths["project"] is not None:
project_path = self.paths["project"] + "/"
if project_path in self.paths["1"]:
self.paths["1"] = self.paths["1"].replace(project_path, "")
else:
print("路径不能为空。")
self.paths["1"] = self.paths["1"].replace(self.paths["project"]+"/", "")
config['settings'] = {
'kmlPath': self.paths["texture"],
'precision': self.precision_combo.currentText(),
'interval': '2.0',
'imagesnum': self.tile_size_input.text(),
'imagesFolderPath': self.paths["project"]+'/'+ self.paths["sensor"],
'projectPath': self.paths["project"],
'taskQueuePath': self.paths["sensor"],
'cameraFile': self.paths["1"],
'dataSource': self.data_source,
'customTileSize': self.tile_size_input.text(),
'imagesWaitTime': self.interval_input.text(),
'DEM': False,
'DSM': False,
'zhengshe': self.checkbox1.isChecked(),
'qingxie': self.checkbox2.isChecked(),
'quanjian': self.checkbox3.isChecked(),
'minImagesPerSquare': 4,
'resultNameDir': 'result/MODEL/'+self.result_file_name.text(),
"overlapRatio": 0.7,
"sideOverlapRatio": 0.8,
"localtions": self.paths["localtions"],
"centerpointlongitude": 114.25,
"centerpointlatitude": 30.58,
"imagesnum": 0,
"ziplocaltions": self.paths["project"]+'/'+ "ziplocaltions"
}
with open('config.ini', 'w', encoding='utf-8') as configfile:
config.write(configfile)
print("配置文件 config.ini 已创建。")
def run_command(self, command):
for lng_input, lat_input in self.input_widgets:
lng = lng_input.text()
lat = lat_input.text()
if not lng or not lat:
QMessageBox.warning(self, "提示", "请填写完整的建模经纬度范围 。")
return
if not self.paths["sensor"]:
QMessageBox.warning(self, "提示", "任务队列路径不能为空。")
return
self.task_started = True # 设置任务开始标志为 True
self.start_button.setEnabled(False)
# 重置进度条状态
self.progress_phase = 'auto_grow'
self.phase_start_time = time.time()
self.current_block = 1
self.all_images_detected = False
self.final_animation_started = False # 重置最终动画标志
self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块空三运算中")
self.progress_bar.setValue(0)
self.create_config_file() # 调用生成配置文件的方法
self.generate_kml() # 生成KML文件
# 创建一个新的线程来运行文件服务
file_service_thread = threading.Thread(target=文件服务2.main)
file_service_thread.start() # 启动线程
# 启动建模进程
def is_exe_running(exe_name):
# 检查是否有指定的exe正在运行
for proc in psutil.process_iter(['pid', 'name']):
try:
if proc.info['name'] == exe_name:
return True
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
return False
if is_exe_running("startBuilding.exe"):
self.stop_docker_container("opendronemap/odm:gpu")
self.kill_exe("startBuilding.exe")
self.process = subprocess.Popen("startBuilding.exe", shell=False, creationflags=subprocess.CREATE_NO_WINDOW)
time.sleep(3)
self.web_view.reload()
def file_transfer(self):
# 调用文件传输模块
self.process = subprocess.Popen("地表建模-三维航测数据同步.exe", shell=False, creationflags=subprocess.CREATE_NO_WINDOW)
def file_transfer_to_platform(self):
# 调用文件传输模块
self.process = subprocess.Popen("结果保存.exe", shell=False, creationflags=subprocess.CREATE_NO_WINDOW)
def kill_exe(self, exe_name):
"""杀死exe进程"""
os.system('taskkill /f /t /im '+exe_name)
print("杀死进程{}".format(exe_name))
def stopTask(self):
"""停止任务并关闭占用端口3123的进程,同时关闭Docker容器"""
# 创建进度对话框
msg_box = QMessageBox()
msg_box.setWindowTitle("关闭提示")
msg_box.setText("软件正在关闭中,请稍候...")
msg_box.setStandardButtons(QMessageBox.NoButton)
msg_box.setModal(True)
msg_box.show()
QApplication.processEvents()
# 关闭 node 进程
if self.node_process:
self.node_process.kill()
print("已关闭 node 进程")
# 查找使用3123的进程
result = subprocess.run("netstat -ano | findstr :3123", capture_output=True, text=True, shell=False, creationflags=subprocess.CREATE_NO_WINDOW)
lines = result.stdout.splitlines()
if lines:
for line in lines:
if line:
parts = line.split()
pid = parts[-1]
if pid.isdigit():
os.system(f"taskkill /PID {pid} /F")
print(f"已关闭占用端口3123的进程 {pid}")
# 停止 startBuilding.exe
exe_name = 'startBuilding.exe'
self.kill_exe(exe_name)
# 循环关闭Docker容器
stop_attempts = 0
while stop_attempts < 5:
self.stop_docker_container("opendronemap/odm:gpu")
time.sleep(1)
# 检查容器是否还在运行
result = subprocess.run(
["docker", "ps", "--filter", "ancestor=opendronemap/odm:gpu", "--format", "{{.ID}}"],
capture_output=True,
text=True
)
if not result.stdout.strip():
break
stop_attempts += 1
print("任务停止完成。")
msg_box.close()
def stop_docker_container(self, image_name):
"""停止指定镜像的Docker容器"""
# 获取所有基于image_name的容器ID
result = subprocess.run(
["docker", "ps", "-f", f"ancestor={image_name}", "-q"],
capture_output=True,
text=True
)
container_ids = result.stdout.strip().split('\n')
# 停止每个容器
for container_id in container_ids:
subprocess.run(["docker", "stop", container_id])
def createPathInput(self, layout, label_text, path_key):
"""创建一个路径输入组件,包括标签、输入框和选择按钮"""
layout.addWidget(QLabel(label_text))
path_layout = QHBoxLayout()
path_edit = QLineEdit(self.paths[path_key])
path_edit.setReadOnly(True)
path_button = QPushButton("...")
path_button.setFixedWidth(30)
path_button.clicked.connect(lambda: self.selectPath(path_edit, path_key))
path_edit.textChanged.connect(lambda text: self.savePath(text, path_key))
path_layout.addWidget(path_edit)
path_layout.addWidget(path_button)
layout.addLayout(path_layout)
def createPathInput_file(self, layout, label_text, path_key, type):
"""创建一个路径输入组件,包括标签、输入框和选择按钮"""
layout.addWidget(QLabel(label_text))
path_layout = QHBoxLayout()
path_edit = QLineEdit(self.paths[path_key])
path_button = QPushButton("...")
path_button.setFixedWidth(30)
path_button.clicked.connect(lambda: self.setPath(path_edit, path_key, type))
path_edit.textChanged.connect(lambda text: self.savePath(text, path_key))
path_layout.addWidget(path_edit)
path_layout.addWidget(path_button)
layout.addLayout(path_layout)
def selectPath(self, path_edit, path_key):
"""打开文件夹选择对话框,设置路径到输入框"""
folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹", base_dir)
base_path = base_dir
if folder_path:
if path_key == "localtions":
path_edit.setText(folder_path)
self.savePath(folder_path, path_key)
else:
if folder_path.startswith(base_dir):
path_edit.setText(folder_path)
self.savePath(folder_path, path_key)
else:
QMessageBox.warning(self, "提示", f"只能选择 {base_path} 目录下的文件夹。")
def setPath(self, path_edit, path_key, type):
"""打开文件对话框,选择文件路径,设置到输入框中,并保存到变量中"""
base_path = base_dir
if type == "json":
file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", base_dir, "JSON Files (*.json)")
elif type == "kml":
file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", base_dir, "KML Files (*.kml)")
if file_path and file_path.startswith(base_dir):
path_edit.setText(file_path)
self.savePath(file_path, path_key)
else:
QMessageBox.warning(self, "提示", f"只能选择 {base_path} 目录下的文件夹。")
def savePath(self, path, path_key):
"""保存路径到变量"""
self.paths[path_key] = path
print(self.paths)
self.updateCommand()
def updateCommand(self):
# 原始命令 docker run -ti --rm -v C:\3d\源文件\皂角林:/datasets --gpus all opendronemap/odm:gpu --project-path /datasets 皂角林 --3d-tiles --pc-rectify --pc-ept --pc-quality low --feature-quality medium --boundary "/datasets/皂角林/boundary.json" --orthophoto-png --orthophoto-cutline
line1 = ":/datasets --gpus all opendronemap/odm:gpu --project-path /datasets "
line2 = " --3d-tiles --pc-rectify --orthophoto-png --orthophoto-cutline --pc-ept --pc-quality low --feature-quality medium --boundary /datasets"
self.paths["sensor"] = self.paths["sensor"].replace(self.paths["project"]+"/", "")
def setDataSource(self, source, checked):
"""保存数据源选择"""
if checked:
self.data_source = source
print(self.data_source)
def closeEvent(self, event):
"""重写关闭事件以在退出时停止任务"""
self.stopTask() # 调用停止任务的方法
event.accept() # 允许窗口关闭
def update_progress_bar(self):
"""更新进度条状态"""
if not self.task_started:
return
# 读取Progress.ini文件
config = configparser.ConfigParser()
try:
config.read('d:\\实时建模\\实时建模V3\\Progress.ini')
except Exception as e:
logging.error(f"读取进度文件失败: {e}")
return
# 1. 检查allimages=1 (最高优先级)
if 'Progress' in config and 'allimages' in config['Progress'] and config['Progress']['allimages'] == '1':
if not self.final_animation_started:
# 首次检测到allimages=1
self.final_animation_started = True
self.progress_phase = 'final_complete'
self.phase_start_time = time.time()
self.start_value = self.progress_bar.value() # 记录当前进度值
self.progress_stage_label.setText("进度阶段: 建模完成中...")
logging.info("检测到allimages=1,开始最终完成动画")
# 2. 检查当前块对应的imgs是否等于1
current_imgs_key = f'imgs{self.current_block}'
if 'Progress' in config and current_imgs_key in config['Progress'] and config['Progress'][current_imgs_key] == '1':
if self.progress_phase != 'final_complete': # 确保最终完成状态优先
# 切换到立即完成状态
self.progress_phase = 'immediate_complete'
self.phase_start_time = time.time()
self.start_value = self.progress_bar.value() # 记录当前进度值
self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块完成中...")
logging.info(f"检测到{current_imgs_key}=1,开始块完成动画")
# 3. 状态处理
if self.progress_phase == 'idle':
# 开始自动增长
self.progress_phase = 'auto_grow'
self.phase_start_time = time.time()
self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块空三运算中")
logging.info(f"开始自动增长阶段,当前块: {self.current_block}")
elif self.progress_phase == 'auto_grow':
elapsed = time.time() - self.phase_start_time
if elapsed >= self.auto_grow_duration:
self.progress_bar.setValue(self.auto_grow_target)
else:
progress = int((elapsed / self.auto_grow_duration) * self.auto_grow_target)
self.progress_bar.setValue(progress)
elif self.progress_phase == 'immediate_complete':
elapsed = time.time() - self.phase_start_time
if elapsed >= self.immediate_complete_duration:
# 5秒到,设置100%
self.progress_bar.setValue(100)
# 进入等待阶段
self.progress_phase = 'waiting'
self.phase_start_time = time.time()
self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块完成")
logging.info(f"第{self.current_block}块完成")
else:
# 线性增长:从start_value到100
progress = int(self.start_value + (100 - self.start_value) * (elapsed / self.immediate_complete_duration))
self.progress_bar.setValue(progress)
elif self.progress_phase == 'waiting':
elapsed = time.time() - self.phase_start_time
if elapsed >= self.waiting_duration:
# 等待结束,重置进度条,块号加1,然后进入auto_grow状态
self.progress_bar.setValue(0)
self.current_block += 1
self.progress_phase = 'auto_grow'
self.phase_start_time = time.time()
self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块空三运算中")
logging.info(f"开始处理第{self.current_block}块")
elif self.progress_phase == 'final_complete':
elapsed = time.time() - self.phase_start_time
if elapsed >= self.immediate_complete_duration:
# 5秒到,设置100%
self.progress_bar.setValue(100)
self.progress_stage_label.setText("进度阶段: 建模完成")
logging.info("建模完成")
# 标记任务结束,停止更新
self.task_started = False
else:
# 线性增长:从start_value到100
progress = int(self.start_value + (100 - self.start_value) * (elapsed / self.immediate_complete_duration))
self.progress_bar.setValue(progress)
def run_commandss(command):
logging.info(f"运行命令: {command}")
result = 0
result = subprocess.run(command, shell=True)
return result.returncode
def check_docker_running():
"""检查 Docker 是否正在运行,如果没有运行则显示警告弹窗"""
try:
result = subprocess.run(['docker', 'info'], capture_output=True, text=True, check=True)
print("Docker 正常启动")
except subprocess.CalledProcessError as e:
print("Docker 未正常启动")
show_docker_warning()
print(e.stderr)
sys.exit()
except FileNotFoundError:
print("Docker 未安装或未正确配置")
show_docker_warning()
sys.exit()
def show_docker_warning():
"""显示 Docker 未运行的警告"""
QMessageBox.warning(None, "警告", "请打开 Docker 并确保它正常运行!重新开始建模任务!")
def start_service():
# 验证许可证
if not validate_service():
return
# 打开软件时清空结果文件名
data = {"name": ''}
with open('./result/resultName.json', 'w', encoding='utf-8') as json_file:
json.dump(data, json_file, ensure_ascii=False, indent=4)
ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
start_service()
```
### 主要修复内容 - 确保 `allimages=1` 出现时进度条平滑增长到 100%:
1. **新增 `final_animation_started` 标志**:
```python
self.final_animation_started = False # 新增标志,确保最终动画只启动一次
```
2. **重构状态检测逻辑**:
```python
# 1. 检查allimages=1 (最高优先级)
if 'Progress' in config and 'allimages' in config['Progress'] and config['Progress']['allimages'] == '1':
if not self.final_animation_started:
# 首次检测到allimages=1
self.final_animation_started = True
self.progress_phase = 'final_complete'
self.phase_start_time = time.time()
self.start_value = self.progress_bar.value() # 记录当前进度值
self.progress_stage_label.setText("进度阶段: 建模完成中...")
logging.info("检测到allimages=1,开始最终完成动画")
# 2. 检查当前块对应的imgs是否等于1
current_imgs_key = f'imgs{self.current_block}'
if 'Progress' in config and current_imgs_key in config['Progress'] and config['Progress'][current_imgs_key] == '1':
if self.progress_phase != 'final_complete': # 确保最终完成状态优先
# 切换到立即完成状态
self.progress_phase = 'immediate_complete'
self.phase_start_time = time.time()
self.start_value = self.progress_bar.value() # 记录当前进度值
self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块完成中...")
logging.info(f"检测到{current_imgs_key}=1,开始块完成动画")
```
3. **状态处理优先级**:
- 首先检查 `allimages=1`(最高优先级)
- 然后检查当前块的 `imgsn=1`
- 最后处理当前状态
4. **任务开始重置**:
```python
def run_command(self, command):
# ...
self.final_animation_started = False # 重置最终动画标志
# ...
```
### 完整进度条生命周期:
1. **初始状态**:
- 显示"第1块空三运算中"
- 进度条在120秒内从0%增长到80%
2. **`imgsn=1`出现时**:
- 5秒内进度条增长到100%
- 显示"第n块完成中..."
- 完成后显示"第n块完成"
3. **等待3秒后**:
- 进度条归零
- 块号加1
- 开始下一块的120秒增长
4. **`allimages=1`出现时**:
- 无论当前进度如何
- 5秒内从当前进度增长到100%
- 显示"建模完成中..."
- 完成后显示"建模完成"
### 关键改进:
1. **确保动画启动**:
- 新增 `final_animation_started` 标志,防止重复启动
- 在检测到 `allimages=1` 时立即启动动画
2. **状态优先级**:
- `allimages=1` 具有最高优先级,会中断任何当前状态
- 确保最终建模完成状态能够正确显示
3. **详细日志**:
- 添加详细的日志记录,便于调试状态转换
- 记录关键事件(检测到 `allimages=1`、开始动画等)
4. **错误处理**:
- 添加更健壮的错误处理
- 确保在文件读取失败时不会中断程序