终于去掉了location.reload()弹出的问题。

本文介绍了一种解决网页刷新时出现烦人提示的方法。通过使用location.href=location.href替代传统的刷新函数,可以有效避免弹出不必要的警告,提升用户体验。

按刷新按钮的时候(执行:onclick="location.reload();"),总是出现讨厌的提示下面的窗口:

“不重新发送信息,则无法刷新网页。
请单击“重试”再次发送信息,或单击“取消”返回正查看的页。”

这样给最终客户感觉很不好,终于找到解决方法。解决如下:
location.href=location.href;

需要注意的是:不能这样写 <a href="#" onclick="openNewWin('aaa.aspx');">打开新窗口</a>

然后在aaa.aspx页面里 这样写就不行了。 opener.location.href = opener.location.href; 如果这样写的话就会出问题。

解决办法:把<a href="#" onclick="openNewWin('aaa.aspx');">打开新窗口</a>

改成:<span style="cursor:hand" onclick="openNewWin('aaa.aspx');">打开新窗口</span>

ajax.interceptors.response.use(async response => { if (response.config.url.indexOf('createLib') !== -1) { //该方法不弹消息 return Promise.reject(response); } // 侵入式修改 response = await respInterceptorsHandleMethod(response); /** * 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页 * 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中 */ const config = response.config; const res = response.data; //响应状态为200 : false : 有拦截器,true : 去掉拦截器 const isIntercept = config.isIntercept || false; //取消拦截器 // 降低圈复杂度 if (res.success == null) { // 老接口,都返回数据, return response; } if (res.success || isIntercept || response.config.skipErrorHandler) { delete config.isIntercept; //删除 return response; } else { // 失败弹提示信息 if (res.msg && config.doError !== false) { const uneacapseHtml = res.msg.replace(new RegExp('<', 'gm'), '<').replace(new RegExp('>', 'gm'), '>'); alertService.warn(uneacapseHtml, { useHtml: true }); } return Promise.reject(res); } }, handlerError); function handlerError(error) { if (error.response.status === 412 && error.response.data.message.indexOf('header[x-csrf-token] is error') !== -1) { location.reload(); return; } if (isNotErrorMsgBox(error)) { // 该方法不弹消息 return Promise.reject(error); } const response = error.response; // 请求超时 if (!response) { handleTiemoutError(error); } const language = navigator.language || navigator.browserLanguage || navigator.userLanguage; let errorMsg = ''; let linkMsg = ''; const isZh = language && language.toLowerCase().includes('zh-'); // 601|602: Dbox账号不存在; if (isStatus601Or602(response)) { hasBigError = true; errorMsg = resolveTernaryExpression( isZh, 'DBOX 账号不存在,请移步到PDM中申请账号', 'DBOX account does not exist, please move to PDM to apply for an account' ); linkMsg = resolveTernaryExpression(isZh, 'DBOX账号申请指导', 'DBOX account application guide'); setTimeout(() => { ErrorMsgBox({ errorMsg, linkMsg, link: isProduction ? import.meta.env.VITE_DBOX_HELP_PRO : import.meta.env.VITE_DBOX_HELP_BETA, }); }, 300); return Promise.reject(error); } // 603: 账号被禁用; 604: 账号需要修复 if (isStatus603Or604(response)) { hasBigError = true; if (language && language.toLowerCase().includes('zh-')) { errorMsg = resolveTernaryExpression(response.status === 603, 'DBOX 账号被禁用,请联系运维支撑人员', 'DBOX 账号需要修复,请联系运维支撑人员'); linkMsg = 'DBOX系统问题求助渠道'; } else { errorMsg = resolveTernaryExpression( response.status === 603, 'DBOX account is disabled, please contact operation and maintenance support staff', 'DBOX account needs to be repaired, please contact operation and maintenance support staff' ); linkMsg = 'DBOX system problem help channel'; } setTimeout(() => { ErrorMsgBox({ errorMsg, linkMsg, link: isProduction ? import.meta.env.VITE_DBOX_HELP2_BETA : import.meta.env.VITE_DBOX_HELP2_BETA, }); }, 300); return Promise.reject(error); } // 605 初始化DBOX账号失败 if (isStatus605(response)) { hasBigError = true; errorMsg = resolveTernaryExpression( isZh, '初始化DBOX账号失败,请联系运维支撑人员.', 'Failed to initialize the DBOX account, please contact operation and maintenance support staff.' ); linkMsg = resolveTernaryExpression(isZh, 'DBOX系统问题求助渠道', 'DBOX System Problem Help Channels'); const helpLink = resolveTernaryExpression(isProduction, import.meta.env.VITE_DBOX_HELP2_BETA, import.meta.env.VITE_DBOX_HELP2_BETA); setTimeout(() => { ErrorMsgBox({ errorMsg, linkMsg, link: helpLink }); }, 290); return Promise.reject(error); } if (isStatus403(error)) { if (checkPopupLoginWhiteList() && import.meta.env.VITE_IS_KLHENV === 'false') { const ssoPopUp = SsoPopUp.getInstance(error); return new Promise((resolve, reject) => { window.addEventListener('message', listenMessage(resolve, reject, error, ssoPopUp)); }); } else { handleRedirectError(response); } } return handleOhterErrorType(error); } function checkPopupLoginWhiteList() { const whiteList = ['/standardreviewNew', '/simplereview', '/simplereviewNew']; return whiteList.some(item => { return location.href.indexOf(item) > -1; }); } function listenMessage(resolve, reject, errorInfo, ssoPopUp) { return e => { if (e.origin === window.location.origin && e.data === 'sso_success') { sessionStorage.removeItem('getUserInfo'); // 重新触发请求失败的接口,成功后设置上层Promise的状态为resolve store.dispatch('GetUserInfo').then(() => { errorInfo.config.headers['x-hw-csrf-token'] = sessionStorage.getItem('userToken'); ssoPopUp.destroy(); window.removeEventListener('message', listenMessage(resolve, reject, errorInfo, ssoPopUp)); // 因为重新登录,所以要reject,重新让用户发起请求 reject(); }); } if (e.origin === window.location.origin && e.data === 'login popup close') { ssoPopUp.destroy(); window.removeEventListener('message', listenMessage(resolve, reject, errorInfo, ssoPopUp)); reject(); } }; } // 提取三元运算,降低圈复杂度 function resolveTernaryExpression(condition, resultA, resultB) { return condition ? resultA : resultB; } function isNotErrorMsgBox(error) { return hasBigError || error.config.url.indexOf('createLib') !== -1 || error.config.url.indexOf('plm-ai') !== -1; } function isStatus601Or602(response) { return ( response.status === 601 || response.status === 602 || (response.status === 401 && response.data && (response.data.message === '601' || response.data.message === '602')) ); } function isStatus603Or604(response) { return ( response.status === 603 || response.status === 604 || (response.status === 401 && response.data && (response.data.message === '603' || response.data.message === '604')) ); } function isStatus605(response) { return response.status === 605 || (response.status === 401 && response.data && response.data.message === '605'); } function isStatus403(error) { return error.status === 403 || error.message.indexOf('403') > 0; } function handleTiemoutError(error) { const message = error.message || ''; const timeoutTip = global.lang.common.TXT_MSG_OPERATION_TIMEOUT; // 超时提示语 // 判断错误信息,是否含有请求超时关键字, 超时提示语统一提示。 不报后台提示信息 const isTimeout = message.indexOf('timeout') !== -1; if (isTimeout) { alertService.error(timeoutTip, { useHtml: true }); } return Promise.reject(error); } function handleRedirectError(response) { const urlPath = decodeURIComponent(window.location.href); const link = encodeURIComponent(urlPath).replace(/#/g, '%23').replace(/&/g, '%26'); // 带参数转换 const curPage = window.location.hostname.replace('.huawei.com', ''); // 当前页面 url 地址 const proHostArr = ['dbox', 'plm-pia', 'plm-dbox-eu', 'plm-dbox', 'dbox-uk', 'pro.dbox']; // location.href = getLoginUrl(proHostArr.indexOf(curPage) !== -1, response) + link; location.href = getLoginUrl(proHostArr.indexOf(curPage) !== -1, response); } 详细讲解一下这些代码,以及根据刚才的方法,如果我想和华为解耦,我该如何操作?
08-22
1:代码运行开始任务,初始化imgs.ini为空,当监听到里面的isimgs=1时进度条开始。 2:将第一块的2分钟增长到100%改为4分钟增长到95%,无论进度到达多少当Progress.ini中有imgsn=1出现时进度都在5秒内长到100%,当并且记录第一块进度到达100%的时间,第二块就在第一块的基础上乘1.5倍的时间增长到95%,且无论进度到达多少当Progress.ini中有imgsn=1出现时进度都在5秒内长到100%。依次类推直到allimgs=1出现五秒内变为100%。 3:不修改原代码,在以下代码中进行修改。 ```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 # 导入 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__)) # 添加当前文件的目录到路径 # import 连接数据库添加历史倾斜摄影 # 直接 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 = False # 标志任务是否开始 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', 'completed' 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.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("开始任务") # 开始任务按钮点击事件绑定run_command方法 self.start_button.clicked.connect(lambda:self.run_command(self.command_line)) left_layout.addWidget(self.start_button) # 只准点击一次开始任务按钮,点击后状态显示停止任务 # stop_button = QPushButton("停止任务") # stop_button.clicked.connect(lambda:self.stopTask()) # left_layout.addWidget(stop_button) # left_layout.addWidget(QLabel("空闲")) # 项目工程路径 # self.createPathInput(left_layout, "项目工程路径:", "project") 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") # # 相机文件路径 # self.createPathInput_file(left_layout, "相机文件路径(.json):", "1", "json") left_layout.addWidget(QLabel("数据源:")) radiobuttons_layout = QHBoxLayout() radio_image = QRadioButton("影像") # radio_video = QRadioButton("视频") radio_image.setChecked(True) radio_image.toggled.connect(lambda: self.setDataSource("影像", radio_image.isChecked())) # radio_video.toggled.connect(lambda: self.setDataSource("视频", radio_video.isChecked())) radiobuttons_layout.addWidget(radio_image) # radiobuttons_layout.addWidget(radio_video) left_layout.addLayout(radiobuttons_layout) # 建模范围 # self.createPathInput_file(left_layout, "建模范围(.kml):", "texture", "kml") # 经纬度输入部分 # left_layout.addWidget(QLabel("输入建模范围:")) # self.coordinates_layout = QVBoxLayout() # # 添加一个默认的坐标输入 # for _ in range(4): # self.add_coordinate_input() # add_button = QPushButton("+") # add_button.clicked.connect(self.add_coordinate_input) # self.coordinates_layout.addWidget(add_button) # left_layout.addLayout(self.coordinates_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.elevation_input = QLineEdit("") # left_layout.addWidget(self.elevation_input) # 地面分辨率 # left_layout.addWidget(QLabel("像元大小(um):")) # self.minphotonSize = QLineEdit("") # left_layout.addWidget(self.minphotonSize) # 建模结果文件名 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(checkbox1) # advanced_layout.addWidget(checkbox2) advanced_layout.addWidget(self.checkbox1) advanced_layout.addWidget(self.checkbox2) advanced_layout.addWidget(self.checkbox3) # 按钮 # Merge_models = QPushButton("合并模型") # connection = 连接数据库添加历史倾斜摄影.create_connection("localhost", "root", "ysxx_0407H123", "product-backsage2") # Merge_models.clicked.connect(lambda:连接数据库添加历史倾斜摄影.query_users(connection)) # advanced_layout.addWidget(Merge_models) 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("引擎数量: 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() # 更新引擎数量 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"引擎数量: {len(container_ids)}") except subprocess.CalledProcessError as e: logging.error(f"获取Docker容器信息时出错: {e}") self.docker_count_label.setText("引擎数量: 0") except FileNotFoundError as e: logging.error(f"Docker命令未找到: {e}") self.docker_count_label.setText("引擎数量: 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)) # 收集经纬度坐标 coordinates = [] for lng_input, lat_input in self.input_widgets: lng = lng_input.text() lat = lat_input.text() print(lng, lat) if lng and lat: coordinates.append(f"{lng},{lat}") # 将所有经纬度坐标合并为一个字符串,用分号分隔 coordinates_str = ";".join(coordinates) print("经纬度",coordinates_str) # 将经纬度加入kml文件 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: # QMessageBox.warning(self, "提示", "项目路径未包含在相机文件路径中。") else: # QMessageBox.warning(self, "提示", "路径不能为空。") print("路径不能为空。") self.paths["1"] = self.paths["1"].replace(self.paths["project"]+"/", "") config['settings'] = { 'kmlPath': self.paths["texture"], # 建模范围 # 'elevation': self.elevation_input.text(), # 相对航高 'precision': self.precision_combo.currentText(), # 建模精度 'interval': '2.0', # 照片获取等待时间 'imagesnum': self.tile_size_input.text(), # 每多少张图片生成一个文件夹 'imagesFolderPath':self.paths["project"]+'/'+ self.paths["sensor"], # 图像文件夹路径 # 'minphotonSize':self.minphotonSize.text(), # 地面分辨率 '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, # 是否生成DEM 'DSM':False, # 是否生成DSM '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.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.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块空三运算中") self.progress_bar.setValue(0) # # 任务路径不能和之前的相同 # config = configparser.ConfigParser() # try: # with open('config.ini', 'r', encoding='utf-8') as configfile: # config.read_file(configfile) # except Exception as e: # print(f"读取配置文件时出错: {e}") # # return # folder_path = config.get('settings', 'imagesFolderPath', fallback=None) # print(folder_path) # if folder_path == self.paths["project"]+"/"+self.paths["sensor"]: # QMessageBox.warning(self, "提示", "任务队列路径不能重复。") # return # 清空目标文件夹,如果存在 # destination_folder = self.paths["project"]+"/"+self.paths["sensor"] # if os.path.exists(destination_folder): # shutil.rmtree(destination_folder) # if not self.paths["camera_file"]: # QMessageBox.warning(self, "提示", "相机文件路径不能为空。") # return if not self.paths["texture"]: QMessageBox.warning(self, "提示", "建模范围路径不能为空。") return if not self.paths["localtions"]: QMessageBox.warning(self, "提示", "图片文件路径不能为空。") return if not self.result_file_name.text(): QMessageBox.warning(self, "提示", "建模结果文件名不能为空。") return # if not self.elevation_input.text(): # QMessageBox.warning(self, "提示", "相对航高不能为空。") # return # if not self.minphotonSize.text(): # QMessageBox.warning(self, "提示", "像元大小不能为空。") # return if not self.tile_size_input.text(): QMessageBox.warning(self, "提示", "自定义瓦片大小不能为空。") return if not self.interval_input.text(): QMessageBox.warning(self, "提示", "照片获取等待时间不能为空。") return self.task_started = True # 设置任务开始标志为 True self.create_config_file() # 调用生成配置文件的方法 # 将按钮状态设为不可用 self.start_button.setEnabled(False) # self.start_button.setText("正在处理...") self.start_button.repaint() self.start_button.update() # 调用文件服务每多少张图生成一个文件夹 # 直接调用 main 函数 # 创建一个新的线程来运行文件服务 file_service_thread = threading.Thread(target=文件服务2.main) file_service_thread.start() # 启动线程 self.generate_kml() # 生成KML文件 # 生成配置文件后,调用startBuilding.exe 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 # 先判断是否有startbuilding.exe在运行,如果有则关闭所有docker容器,然后关闭startbuilding.exe,再启动startbuilding.exe if is_exe_running("startBuilding.exe"): # 关闭所有基于"opendronemap/odm:gpu"的容器 self.stop_docker_container("opendronemap/odm:gpu") # 关闭startBuilding.exe self.kill_exe("startBuilding.exe") self.process =subprocess.Popen("startBuilding.exe", shell=False,creationflags=subprocess.CREATE_NO_WINDOW) time.sleep(3) # 等待1秒,等待startBuilding.exe启动完成 # 刷新页面 self.web_view.reload() # 使用 QWebEngineView 的 reload 方法 # print(command) # import subprocess # subprocess.Popen(command, shell=True) 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进程 :param exe_name:进程名字 :return:无 """ os.system('taskkill /f /t /im '+exe_name)#MESMTPC.exe程序名字 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: # 检查 node 进程是否存在 self.node_process.kill() # 终止 node 进程 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] # 假设最后一部分是PID 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容器,直到3秒没有关闭为止 stop_attempts = 0 while stop_attempts < 5: self.stop_docker_container("opendronemap/odm:gpu") # 更新进度对话框 # progress_dialog.setLabelText("正在关闭Docker容器...请稍候...") # 等待1秒 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() # 停止dockers def stop_docker_container(self, image_name): # 获取所有基于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]) # 所有容器都停止后,返回True,否则返回False return not container_ids # def createPathInput(self, layout, label_text, path_key): # """创建一个路径输入组件,包括标签、输入框和选择按钮""" # 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.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, "选择文件夹") # if folder_path: # path_edit.setText(folder_path) # self.savePath(folder_path, path_key) # # 打开文件对话框,选择文件路径,设置到输入框中,并保存到变量中 # def setPath(self, path_edit, path_key,type): # # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "All Files (*)") # # 只能选json文件 # if type=="json": # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "json Files (*.json)") # elif type=="kml": # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "kml Files (*.kml)") # if file_path: # path_edit.setText(file_path) # self.savePath(file_path, path_key) 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: # 假如path_key是localtions,则不需要判断是否在C:/RealTimeModeling下 if path_key == "localtions": path_edit.setText(folder_path) self.savePath(folder_path, path_key) else: print(folder_path,base_dir,folder_path.startswith(base_dir)) # 检查选择的目录是否在 C:/RealTimeModeling 下 if folder_path.startswith(base_dir): path_edit.setText(folder_path) self.savePath(folder_path, path_key) else: QMessageBox.warning(self, "提示", f"只能选择123 {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)") # Check if the selected path starts with the allowed directory 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["project"] 的字符串 self.paths["sensor"] = self.paths["sensor"].replace(self.paths["project"]+"/", "") # self.paths["texture"]=self.paths["texture"].replace(self.paths["project"], "") """更新命令行""" # self.command_line = "docker run --rm -ti -v " + self.paths["project"] + ":/data/project -v " + self.paths["sensor"] + ":/data/sensor -v " + self.paths["camera_file"] + ":/data/camera_file -v " + self.paths["texture"] + ":/data/texture -p 3123:3123 zyf/3dmodel:latest " + self.data_source + " " + self.elevation_input.text() + " " + self.precision_combo.currentText() + " " + self.tile_size_input.text() + " " + self.interval_input.text() + " " + " ".join([str(checkbox.isChecked()) for checkbox in [checkbox1, checkbox2, checkbox3]]) print("工程目录", self.paths["project"] , "任务队列", self.paths["sensor"], "相机文件", # self.paths["1"], "建模范围", self.paths["texture"], "数据源", self.data_source , # "相对航高", # self.elevation_input.text(), "建模精度", # self.precision_combo.currentText() , "自定义瓦片大小", self.tile_size_input.text(), "照片获取等待时间", self.interval_input.text() , "是否生成正射", self.checkbox3.isChecked() ) # " ".join([str(checkbox.isChecked()) for checkbox in [self.checkbox1, self.checkbox2, self.checkbox3]])) 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 or self.all_images_detected: return # 读取Progress.ini文件 config = configparser.ConfigParser() try: config.read('./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': self.all_images_detected = True self.progress_stage_label.setText("进度阶段: 建模完成") self.progress_bar.setValue(100) return # 检查当前块对应的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': # 如果当前状态是auto_grow或idle,则切换到immediate_complete状态 if self.progress_phase in ['auto_grow', 'idle']: 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}块完成中...") # 状态处理 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) 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}块完成") 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}块空三运算中") 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: # client = docker.from_env() # 尝试获取 Docker 客户端 # client.ping() # 发送 ping 请求确认 Docker 是否可用 # except docker.errors.DockerException as e: # # 如果发生异常,说明 Docker 没有运行 # show_docker_warning() # 显示 Docker 未运行的警告 # sys.exit() # 退出程序 # except Exception as e: # # 捕获其他类型的异常 # show_docker_warning() # sys.exit() try: # 运行 docker info 命令 result = subprocess.run(['docker', 'info'], capture_output=True, text=True, check=True) # 打印输出信息 print(result.stdout) print("Docker 正常启动") except subprocess.CalledProcessError as e: # 如果命令执行失败,打印错误信息 print("Docker 未正常启动") show_docker_warning() # 显示 Docker 未运行的警告 print(e.stderr) sys.exit() except FileNotFoundError: # 如果 docker 命令未找到,说明 Docker 未安装 print("Docker 未安装或未正确配置") show_docker_warning() # 显示 Docker 未运行的警告 sys.exit() def show_docker_warning(): """显示 Docker 未运行的警告""" QMessageBox.warning(None, "警告", "请打开 Docker 并确保它正常运行!重新开始建模任务!") def start_service(): # 验证许可证 if not validate_service(): return # 如果验证失败,退出服务 # 验证成功,继续运行服务 run_service() def run_service(): # 清空并初始化 Progress.ini 文件 # 清空 Progress.ini 文件 progress_file = 'Progress.ini' if os.path.exists(progress_file): os.remove(progress_file) config_progress = configparser.ConfigParser() config_progress.add_section('Progress') with open(progress_file, 'w', encoding='utf-8') as progressfile: config_progress.write(progressfile) # logger.info("清空并初始化 Progress.ini 文件") # 您的服务主逻辑 # 打开软件时清空结果文件名 # 创建要写入的 JSON 数据 data = { "name": '' } # 写入 JSON 文件 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) # 运行一个命令node result_server_logs.js # subprocess.Popen('node result_server_logs.js', shell=False,creationflags=subprocess.CREATE_NO_WINDOW) app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) if __name__ == "__main__": start_service() ```
08-29
当出现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 # 导入 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__)) # 添加当前文件的目录到路径 # import 连接数据库添加历史倾斜摄影 # 直接 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 = False # 标志任务是否开始 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', 'completed' self.phase_start_time = None # 当前阶段开始的时间 self.auto_grow_target = 95 # 自动增长目标值(95%) self.auto_grow_duration = 240 # 自动增长持续时间(240秒=4分钟) 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.block_start_times = {} # 存储每个块开始的时间 self.block_durations = {} # 存储每个块的实际持续时间 self.imgs_detected = False # 是否检测到imgs.ini中的isimgs=1 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("开始任务") # 开始任务按钮点击事件绑定run_command方法 self.start_button.clicked.connect(lambda:self.run_command(self.command_line)) left_layout.addWidget(self.start_button) # 只准点击一次开始任务按钮,点击后状态显示停止任务 # stop_button = QPushButton("停止任务") # stop_button.clicked.connect(lambda:self.stopTask()) # left_layout.addWidget(stop_button) # left_layout.addWidget(QLabel("空闲")) # 项目工程路径 # self.createPathInput(left_layout, "项目工程路径:", "project") 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") # # 相机文件路径 # self.createPathInput_file(left_layout, "相机文件路径(.json):", "1", "json") left_layout.addWidget(QLabel("数据源:")) radiobuttons_layout = QHBoxLayout() radio_image = QRadioButton("影像") # radio_video = QRadioButton("视频") radio_image.setChecked(True) radio_image.toggled.connect(lambda: self.setDataSource("影像", radio_image.isChecked())) # radio_video.toggled.connect(lambda: self.setDataSource("视频", radio_video.isChecked())) radiobuttons_layout.addWidget(radio_image) # radiobuttons_layout.addWidget(radio_video) left_layout.addLayout(radiobuttons_layout) # 建模范围 # self.createPathInput_file(left_layout, "建模范围(.kml):", "text极速版ure", "kml") # 经纬度输入部分 # left_layout.addWidget(QLabel("输入建模范围:")) # self.coordinates_layout = QVBoxLayout() # # 添加一个默认的坐标输入 # for _ in range(4): # self.add_coordinate_input() # add_button = QPushButton("+") # add_button.clicked.connect(self.add_coordinate_input) # self.coordinates_layout.addWidget(add_button) # left_layout.addLayout(self.coordinates_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.elevation_input = QLineEdit("") # left_layout.addWidget(self.elevation_input) # 地面分辨率 # left_layout.addWidget(QLabel("像元大小(um):")) # self.minphotonSize = QLineEdit("") # left_layout.addWidget(self.minphotonSize) # 建模结果文件名 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(checkbox1) # advanced_layout.addWidget(checkbox2) advanced_layout.addWidget(self.checkbox1) advanced_layout.addWidget(self.checkbox2) advanced_layout.addWidget(self.checkbox3) # 按钮 # Merge_models = QPushButton("合并模型") # connection = 连接数据库添加历史倾斜摄影.create_connection("localhost", "root", "ysxx_0407H123", "product-backsage2") # Merge_models.clicked.connect(lambda:连接数据库添加历史倾斜摄影.query_users(connection)) # advanced_layout.addWidget(Merge_models) 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("引擎数量: 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() # 更新引擎数量 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"引擎数量: {len(container_ids)}") except subprocess.CalledProcessError as e: logging.error(f"获取Docker容器信息时出错: {e}") self.docker_count_label.setText("引擎数量: 0") except FileNotFoundError as e: logging.error(f"Docker命令未找到: {e}") self.docker_count_label.setText("引擎数量: 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)) # 收集经纬度坐标 coordinates = [] for lng_input, lat_input in self.input_widgets: lng = lng_input.text() lat = lat_input.text() print(lng, lat) if lng and lat: coordinates.append(f"{lng},{lat}") # 将所有经纬度坐标合并为一个字符串,用分号分隔 coordinates_str = ";".join(coordinates) print("经纬度",coordinates_str) # 将经纬度加入kml文件 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: # QMessageBox.warning(self, "提示", "项目路径未包含在相机文件路径中。") else: # QMessageBox.warning(self, "提示", "路径不能为空。") print("路径不能为空。") self.paths["1"] = self.paths["1"].replace(self.paths["project"]+"/", "") config['settings'] = { 'kmlPath': self.paths["texture"], # 建模范围 # 'elevation': self.elevation_input.text(), # 相对航高 'precision': self.precision_combo.currentText(), # 建模精度 'interval': '2.0', # 照片获取等待时间 'imagesnum': self.tile_size_input.text(), # 每多少张图片生成一个文件夹 'imagesFolderPath':self.paths["project"]+'/'+ self.paths["sensor"], # 图像文件夹路径 # 'minphotonSize':self.minphotonSize.text(), # 地面分辨率 '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, # 是否生成DEM 'DSM':False, # 是否生成DSM '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.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.imgs_detected = False self.block_start_times = {} self.block_durations = {} self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块空三运算中") self.progress_bar.setValue(0) # 初始化imgs.ini文件 self.init_imgs_ini() # # 任务路径不能和之前的相同 # config = configparser.ConfigParser() # try: # with open('config.ini', 'r', encoding='utf-8') as configfile: # config.read_file(configfile) # except Exception as e: # print(f"读取配置文件时出错: {e}") # # return # folder_path = config.get('settings', 'imagesFolderPath', fallback=None) # print(folder_path) # if folder_path == self.paths["project"]+"/"+self.paths["sensor"]: # QMessageBox.warning(self, "提示", "任务队列路径不能重复。") # return # 清空目标文件夹,如果存在 # destination_folder = self.paths["project"]+"/"+self.paths["sensor"] # if os.path.exists(destination_folder): # shutil.rmtree(destination_folder) # if not self.paths["camera_file"]: # QMessageBox.warning(self, "提示", "相机文件路径不能为空。") # return if not self.paths["texture"]: QMessageBox.warning(self, "提示", "建模范围路径不能为空。") return if not self.paths["localtions"]: QMessageBox.warning(self, "提示", "图片文件路径不能为空。") return if not self.result_file_name.text(): QMessageBox.warning(self, "提示", "建模结果文件名不能为空。") return # if not self.elevation_input.text(): # QMessageBox.warning(self, "提示", "相对航高不能为空。") # return # if not self.minphotonSize.text(): # QMessageBox.warning(self, "提示", "像元大小不能为空。") # return if not self.tile_size_input.text(): QMessageBox.warning(self, "提示", "自定义瓦片大小不能为空。") return if not self.interval_input.text(): QMessageBox.warning(self, "提示", "照片获取等待时间不能为空。") return self.task_started = True # 设置任务开始标志为 True self.create_config_file() # 调用生成配置文件的方法 # 将按钮状态设为不可用 self.start_button.setEnabled(False) # self.start_button.setText("正在处理...") self.start_button.repaint() self.start_button.update() # 调用文件服务每多少张图生成一个文件夹 # 直接调用 main 函数 # 创建一个新的线程来运行文件服务 file_service_thread = threading.Thread(target=文件服务2.main) file_service_thread.start() # 启动线程 self.generate_kml() # 生成KML文件 # 生成配置文件后,调用startBuilding.exe 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 # 先判断是否有startbuilding.exe在运行,如果有则关闭所有docker容器,然后关闭startbuilding.exe,再启动startbuilding.exe if is_exe_running("startBuilding.exe"): # 关闭所有基于"opendronemap/odm:gpu"的容器 self.stop_docker_container("opendronemap/odm:gpu") # 关闭startBuilding.exe self.kill_exe("startBuilding.exe") self.process =subprocess.Popen("startBuilding.exe", shell=False,creationflags=subprocess.CREATE_NO_WINDOW) time.sleep(3) # 等待1秒,等待startBuilding.exe启动完成 # 刷新页面 self.web_view.reload() # 使用 QWebEngineView 的 reload 方法 # print(command) # import subprocess # subprocess.Popen(command, shell=True) def init_imgs_ini(self): """初始化imgs.ini文件""" config = configparser.ConfigParser() config['Progress'] = {'isimgs': '0'} # 初始化为0 with open('imgs.ini', 'w', encoding='utf-8') as configfile: config.write(configfile) 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进程 :param exe_name:进程名字 :return:无 """ os.system('taskkill /f /t /im '+exe_name)#MESMTPC.exe程序名字 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: # 检查 node 进程是否存在 self.node_process.kill() # 终止 node 进程 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] # 假设最后一部分是PID 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容器,直到3秒没有关闭为止 stop_attempts = 0 while stop_attempts < 5: self.stop_docker_container("opendronemap/odm:gpu") # 更新进度对话框 # progress_dialog.setLabelText("正在关闭Docker容器...请稍候...") # 等待1秒 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() # 停止dockers def stop_docker_container(self, image_name): # 获取所有基于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]) # 所有容器都停止后,返回True,否则返回False return not container_ids # def createPathInput(self, layout, label_text, path_key): # """创建一个路径输入组件,包括标签、输入框和选择按钮""" # 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.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, "选择文件夹") # if folder_path: # path_edit.setText(folder_path) # self.savePath(folder_path, path_key) # # 打开文件对话框,选择文件路径,设置到输入框中,并保存到变量中 # def setPath(self, path_edit, path_key,type): # # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "All Files (*)") # # 只能选json文件 # if type=="json": # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "json Files (*.json)") # elif type=="kml": # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "kml Files (*.kml)") # if file_path: # path_edit.setText(file_path) # self.savePath(file_path, path_key) 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: # 假如path_key是localtions,则不需要判断是否在C:/RealTimeModeling下 if path_key == "localtions": path_edit.setText(folder_path) self.savePath(folder_path, path_key) else: print(folder_path,base_dir,folder_path.startswith(base_dir)) # 检查选择的目录是否在 C:/RealTimeModeling 下 if folder_path.startswith(base_dir): path_edit.setText(folder_path) self.savePath(folder_path, path_key) else: QMessageBox.warning(self, "提示", f"只能选择123 {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)") # Check if the selected path starts with the allowed directory 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["project"] 的字符串 self.paths["sensor"] = self.paths["sensor"].replace(self.paths["project"]+"/", "") # self.paths["texture"]=self.paths["texture"].replace(self.paths["project"], "") """更新命令行""" # self.command_line = "docker run --rm -ti -v " + self.paths["project"] + ":/data/project -极速版v " + self.paths["sensor"] + ":/data/sensor -v " + self.paths["camera_file"] + ":/data/camera_file -v " + self.paths["texture"] + ":/data/texture -p 3123:3123 zyf/3dmodel:latest " + self.data_source + " " + self.elevation_input.text() + " " + self.precision_combo.currentText() + " " + self.tile_size_input.text() + " " + self.interval_input.text() + " " + " ".join([str(checkbox.isChecked()) for checkbox in [checkbox1, checkbox2, checkbox3]]) print("工程目录", self.paths["project"] , "任务队列", self.paths["sensor"], "相机文件", # self.paths["1"], "建模范围", self.paths["texture"], "数据源", self.data_source , # "相对航高", # self.elevation_input.text(), "建模精度", # self.precision_combo.currentText() , "自定义瓦片大小", self.tile_size_input.text(), "照片获取等待时间", self.interval_input.text() , "是否生成正射", self.checkbox3.isChecked() ) # " ".join([str(checkbox.isChecked()) for checkbox in [self.checkbox1, self.checkbox2, self.checkbox3]])) 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 or self.all_images_detected: return # 读取imgs.ini文件,检查isimgs=1 config_imgs = configparser.ConfigParser() try: config_imgs.read('imgs.ini') if 'Progress' in config_imgs and 'isimgs' in config_imgs['Progress'] and config_imgs['Progress']['isimgs'] == '1': self.imgs_detected = True except Exception as e: logging.error(f"读取imgs.ini文件失败: {e}") # 如果imgs.ini中isimgs=1未出现,不更新进度条 if not self.imgs_detected: return # 记录当前块的开始时间(如果尚未记录) if self.current_block not in self.block_start_times: self.block_start_times[self.current_block] = time.time() self.progress_stage_label.setText(f"进度阶段: 第{self.current_block}块空三运算中") # 读取Progress.ini文件 config = configparser.ConfigParser() try: config.read('./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': self.all_images_detected = True self.progress_stage_label.setText("进度阶段: 建模完成") self.progress_bar.setValue(100) return # 检查当前块对应的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': # 如果当前状态是auto_grow或idle,则切换到immediate_complete状态 if self.progress_phase in ['auto_grow', 'idle']: 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}块三维重建中...") # 状态处理 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': # 计算当前块的目标持续时间 target_duration = 240 # 第一块4分钟 if self.current_block > 1: # 后续块使用上一块实际完成时间的1.5倍 prev_block = self.current_block - 1 if prev_block in self.block_durations: target_duration = self.block_durations[prev_block] * 1.5 elapsed = time.time() - self.phase_start_time if elapsed >= target_duration: self.progress_bar.setValue(self.auto_grow_target) else: progress = int((elapsed / target_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) # 记录当前块的总时间 if self.current_block in self.block_start_times: self.block_durations[self.current_block] = time.time() - self.block_start_times[self.current_block] # 进入下一块 self.current_block += 1 self.progress_phase = 'auto_grow' self.phase_start_time = time.time() self.progress_stage_label.setText(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) 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: # client = docker.from_env() # 尝试获取 Docker 客户端 # client.ping() # 发送 ping 请求确认 Docker 是否可用 # except docker.errors.DockerException as e: # # 如果发生异常,说明 Docker 没有运行 # show_docker_warning() # 显示 Docker 未运行的警告 # sys.exit() # 退出程序 # except Exception as e: # # 捕获其他类型的异常 # show_docker_warning() # sys.exit() try: # 运行 docker info 命令 result = subprocess.run(['docker', 'info'], capture_output=True, text=True, check=True) # 打印输出信息 print(result.stdout) print("Docker 正常启动") except subprocess.CalledProcessError as e: # 如果命令执行失败,打印错误信息 print("Docker 未正常启动") show_docker_warning() # 显示 Docker 未运行的警告 print(e.stderr) sys.exit() except FileNotFoundError: # 如果 docker 命令未找到,说明 Docker 未安装 print("Docker 未安装或未正确配置") show_docker_warning() # 显示 Docker 未运行的警告 sys.exit() def show_docker_warning(): """显示 Docker 未运行的警告""" QMessageBox.warning(None, "警告", "请打开 Docker 并确保它正常运行!重新开始建模任务!") def start_service(): # 验证许可证 if not validate_service(): return # 如果验证失败,退出服务 # 验证成功,继续运行服务 run_service() def run_service(): # 清空并初始化 Progress.ini 文件 # 清空 Progress.ini 文件 progress_file = 'Progress.ini' if os.path.exists(progress_file): os.remove(progress_file) config_progress = configparser.ConfigParser() config_progress.add_section('Progress') with open(progress_file, 'w', encoding='utf-8') as progressfile: config_progress.write(progressfile) # logger.info("清空并初始化 Progress.ini 文件") # 您的服务主逻辑 # 打开软件时清空结果文件名 # 创建要写入的 JSON 数据 data = { "name": '' } # 写入 JSON 文件 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) # 运行一个命令node result_server_logs.js # subprocess.Popen('node result_server_logs.js', shell=False,creationflags=subprocess.CREATE_NO_WINDOW) app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) if __name__ == "__main__": start_service() ```
最新发布
08-29
将代码结合到 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 # 导入 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__)) # 添加当前文件的目录到路径 # import 连接数据库添加历史倾斜摄影 # 直接 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_img_index = 0 # 当前处理的图片索引 self.setWindowTitle("实时三维") self.setGeometry(100, 100, 1366, 768) self.setWindowIcon(QIcon('./icon.ico')) self.process = None # 用于存储子进程的引用 check_docker_running() # 检查docker是否运行 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("开始任务") # 开始任务按钮点击事件绑定run_command方法 self.start_button.clicked.connect(lambda:self.run_command(self.command_line)) left_layout.addWidget(self.start_button) # 只准点击一次开始任务按钮,点击后状态显示停止任务 # stop_button = QPushButton("停止任务") # stop_button.clicked.connect(lambda:self.stopTask()) # left_layout.addWidget(stop_button) # left_layout.addWidget(QLabel("空闲")) # 项目工程路径 # self.createPathInput(left_layout, "项目工程路径:", "project") 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") # # 相机文件路径 # self.createPathInput_file(left_layout, "相机文件路径(.json):", "1", "json") left_layout.addWidget(QLabel("数据源:")) radiobuttons_layout = QHBoxLayout() radio_image = QRadioButton("影像") # radio_video = QRadioButton("视频") radio_image.setChecked(True) radio_image.toggled.connect(lambda: self.setDataSource("影像", radio_image.isChecked())) # radio_video.toggled.connect(lambda: self.setDataSource("视频", radio_video.isChecked())) radiobuttons_layout.addWidget(radio_image) # radiobuttons_layout.addWidget(radio_video) left_layout.addLayout(radiobuttons_layout) # 建模范围 # self.createPathInput_file(left_layout, "建模范围(.kml):", "texture", "kml") # 经纬度输入部分 # left_layout.addWidget(QLabel("输入建模范围:")) # self.coordinates_layout = QVBoxLayout() # # 添加一个默认的坐标输入 # for _ in range(4): # self.add_coordinate_input() # add_button = QPushButton("+") # add_button.clicked.connect(self.add_coordinate_input) # self.coordinates_layout.addWidget(add_button) # left_layout.addLayout(self.coordinates_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.elevation_input = QLineEdit("") # left_layout.addWidget(self.elevation_input) # 地面分辨率 # left_layout.addWidget(QLabel("像元大小(um):")) # self.minphotonSize = QLineEdit("") # left_layout.addWidget(self.minphotonSize) # 建模结果文件名 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(checkbox1) # advanced_layout.addWidget(checkbox2) advanced_layout.addWidget(self.checkbox1) advanced_layout.addWidget(self.checkbox2) advanced_layout.addWidget(self.checkbox3) # 按钮 # Merge_models = QPushButton("合并模型") # connection = 连接数据库添加历史倾斜摄影.create_connection("localhost", "root", "ysxx_0407H123", "product-backsage2") # Merge_models.clicked.connect(lambda:连接数据库添加历史倾斜摄影.query_users(connection)) # advanced_layout.addWidget(Merge_models) 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)) # 收集经纬度坐标 coordinates = [] for lng_input, lat_input in self.input_widgets: lng = lng_input.text() lat = lat_input.text() print(lng, lat) if lng and lat: coordinates.append(f"{lng},{lat}") # 将所有经纬度坐标合并为一个字符串,用分号分隔 coordinates_str = ";".join(coordinates) print("经纬度",coordinates_str) # 将经纬度加入kml文件 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: # QMessageBox.warning(self, "提示", "项目路径未包含在相机文件路径中。") else: # QMessageBox.warning(self, "提示", "路径不能为空。") print("路径不能为空。") self.paths["1"] = self.paths["1"].replace(self.paths["project"]+"/", "") config['settings'] = { 'kmlPath': self.paths["texture"], # 建模范围 # 'elevation': self.elevation_input.text(), # 相对航高 'precision': self.precision_combo.currentText(), # 建模精度 'interval': '2.0', # 照片获取等待时间 'imagesnum': self.tile_size_input.text(), # 每多少张图片生成一个文件夹 'imagesFolderPath':self.paths["project"]+'/'+ self.paths["sensor"], # 图像文件夹路径 # 'minphotonSize':self.minphotonSize.text(), # 地面分辨率 '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, # 是否生成DEM 'DSM':False, # 是否生成DSM '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 # # 任务路径不能和之前的相同 # config = configparser.ConfigParser() # try: # with open('config.ini', 'r', encoding='utf-8') as configfile: # config.read_file(configfile) # except Exception as e: # print(f"读取配置文件时出错: {e}") # # return # folder_path = config.get('settings', 'imagesFolderPath', fallback=None) # print(folder_path) # if folder_path == self.paths["project"]+"/"+self.paths["sensor"]: # QMessageBox.warning(self, "提示", "任务队列路径不能重复。") # return # 清空目标文件夹,如果存在 # destination_folder = self.paths["project"]+"/"+self.paths["sensor"] # if os.path.exists(destination_folder): # shutil.rmtree(destination_folder) # if not self.paths["camera_file"]: # QMessageBox.warning(self, "提示", "相机文件路径不能为空。") # return if not self.paths["texture"]: QMessageBox.warning(self, "提示", "建模范围路径不能为空。") return if not self.paths["localtions"]: QMessageBox.warning(self, "提示", "图片文件路径不能为空。") return if not self.result_file_name.text(): QMessageBox.warning(self, "提示", "建模结果文件名不能为空。") return # if not self.elevation_input.text(): # QMessageBox.warning(self, "提示", "相对航高不能为空。") # return # if not self.minphotonSize.text(): # QMessageBox.warning(self, "提示", "像元大小不能为空。") # return if not self.tile_size_input.text(): QMessageBox.warning(self, "提示", "自定义瓦片大小不能为空。") return if not self.interval_input.text(): QMessageBox.warning(self, "提示", "照片获取等待时间不能为空。") return self.create_config_file() # 调用生成配置文件的方法 # 将按钮状态设为不可用 self.start_button.setEnabled(False) # self.start_button.setText("正在处理...") self.start_button.repaint() self.start_button.update() # 调用文件服务每多少张图生成一个文件夹 # 直接调用 main 函数 # 创建一个新的线程来运行文件服务 file_service_thread = threading.Thread(target=文件服务2.main) file_service_thread.start() # 启动线程 self.generate_kml() # 生成KML文件 # 生成配置文件后,调用startBuilding.exe 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 # 先判断是否有startbuilding.exe在运行,如果有则关闭所有docker容器,然后关闭startbuilding.exe,再启动startbuilding.exe if is_exe_running("startBuilding.exe"): # 关闭所有基于"opendronemap/odm:gpu"的容器 self.stop_docker_container("opendronemap/odm:gpu") # 关闭startBuilding.exe self.kill_exe("startBuilding.exe") self.process =subprocess.Popen("startBuilding.exe", shell=False,creationflags=subprocess.CREATE_NO_WINDOW) time.sleep(3) # 等待1秒,等待startBuilding.exe启动完成 # 刷新页面 self.web_view.reload() # 使用 QWebEngineView 的 reload 方法 # print(command) # import subprocess # subprocess.Popen(command, shell=True) 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进程 :param exe_name:进程名字 :return:无 """ os.system('taskkill /f /t /im '+exe_name)#MESMTPC.exe程序名字 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: # 检查 node 进程是否存在 self.node_process.kill() # 终止 node 进程 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] # 假设最后一部分是PID 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容器,直到3秒没有关闭为止 stop_attempts = 0 while stop_attempts < 5: self.stop_docker_container("opendronemap/odm:gpu") # 更新进度对话框 # progress_dialog.setLabelText("正在关闭Docker容器...请稍候...") # 等待1秒 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() # 停止dockers def stop_docker_container(self, image_name): # 获取所有基于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]) # 所有容器都停止后,返回True,否则返回False return not container_ids # def createPathInput(self, layout, label_text, path_key): # """创建一个路径输入组件,包括标签、输入框和选择按钮""" # 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.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, "选择文件夹") # if folder_path: # path_edit.setText(folder_path) # self.savePath(folder_path, path_key) # # 打开文件对话框,选择文件路径,设置到输入框中,并保存到变量中 # def setPath(self, path_edit, path_key,type): # # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "All Files (*)") # # 只能选json文件 # if type=="json": # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "json Files (*.json)") # elif type=="kml": # file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "kml Files (*.kml)") # if file_path: # path_edit.setText(file_path) # self.savePath(file_path, path_key) 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: # 假如path_key是localtions,则不需要判断是否在C:/RealTimeModeling下 if path_key == "localtions": path_edit.setText(folder_path) self.savePath(folder_path, path_key) else: print(folder_path,base_dir,folder_path.startswith(base_dir)) # 检查选择的目录是否在 C:/RealTimeModeling 下 if folder_path.startswith(base_dir): path_edit.setText(folder_path) self.savePath(folder_path, path_key) else: QMessageBox.warning(self, "提示", f"只能选择123 {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)") # Check if the selected path starts with the allowed directory 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["project"] 的字符串 self.paths["sensor"] = self.paths["sensor"].replace(self.paths["project"]+"/", "") # self.paths["texture"]=self.paths["texture"].replace(self.paths["project"], "") """更新命令行""" # self.command_line = "docker run --rm -ti -v " + self.paths["project"] + ":/data/project -v " + self.paths["sensor"] + ":/data/sensor -v " + self.paths["camera_file"] + ":/data/camera_file -v " + self.paths["texture"] + ":/data/texture -p 3123:3123 zyf/3dmodel:latest " + self.data_source + " " + self.elevation_input.text() + " " + self.precision_combo.currentText() + " " + self.tile_size_input.text() + " " + self.interval_input.text() + " " + " ".join([str(checkbox.isChecked()) for checkbox in [checkbox1, checkbox2, checkbox3]]) print("工程目录", self.paths["project"] , "任务队列", self.paths["sensor"], "相机文件", # self.paths["1"], "建模范围", self.paths["texture"], "数据源", self.data_source , # "相对航高", # self.elevation_input.text(), "建模精度", # self.precision_combo.currentText() , "自定义瓦片大小", self.tile_size_input.text(), "照片获取等待时间", self.interval_input.text() , "是否生成正射", self.checkbox3.isChecked() ) # " ".join([str(checkbox.isChecked()) for checkbox in [self.checkbox1, self.checkbox2, self.checkbox3]])) 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): """根据Progress.ini文件更新进度条,支持多个imgs阶段处理""" if not self.task_started: return config = configparser.ConfigParser() try: config.read('d:\\实时建模\\实时建模V3\\Progress.ini') except Exception as e: logging.error(f"读取进度文件失败: {e}") return # 检查是否有新的imgsn=1出现 for i in range(1, 100): # 检查最多100个imgs key = f'imgs{i}' if 'Progress' in config and key in config['Progress'] and config['Progress'][key] == '1': # 如果这个imgs还没有被处理过 if not hasattr(self, f'_{key}_processed'): setattr(self, f'_{key}_processed', True) self.current_img_index = i # 更新当前处理的图片索引 self.progress_stage_label.setText(f"进度阶段: 第{i}块完成") self._start_immediate_complete() return # 自动增长阶段 if not hasattr(self, '_auto_grow_started'): self._init_auto_grow() elapsed = time.time() - self._auto_grow_start_time if elapsed >= self._auto_grow_duration: self.progress_bar.setValue(self._auto_grow_target) return progress = int((elapsed / self._auto_grow_duration) * self._auto_grow_target) self.progress_bar.setValue(min(progress, self._auto_grow_target)) def _init_auto_grow(self): """初始化自动增长参数""" self._auto_grow_started = True self._auto_grow_start_time = time.time() self._auto_grow_target = 80 self._auto_grow_duration = 120 self.progress_bar.setValue(0) self.progress_stage_label.setText(f"进度阶段: 第{self.current_img_index}块进度") def _start_immediate_complete(self): """处理imgsn=1的情况:5秒内增长到100%""" start_value = self.progress_bar.value() target_value = 100 duration = 5000 # 5秒 steps = 50 # 每次更新间隔 delta = (target_value - start_value) / (duration / steps) # 获取当前块号 block_number = self.current_img_index def update(): current = self.progress_bar.value() if current >= target_value: # 更新状态文字为"第n块完成" self.progress_stage_label.setText(f"进度阶段: 第{block_number}块完成") # 5秒后重置进度条 QTimer.singleShot(5000, self._reset_progress) return # 计算下一个进度值 next_value = min(current + delta, target_value) self.progress_bar.setValue(int(next_value)) # 更新状态文字 self.progress_stage_label.setText(f"进度阶段: 第{block_number}块完成中...") QTimer.singleShot(steps, update) update() def _reset_progress(self): """进度归零,准备下一块""" self.progress_bar.setValue(0) self._auto_grow_started = False self.current_img_index += 1 # 移动到下一块 self.progress_stage_label.setText(f"进度阶段: 第{self.current_img_index}块进度") logging.info(f"进度条已归零,准备第{self.current_img_index}块") 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: # client = docker.from_env() # 尝试获取 Docker 客户端 # client.ping() # 发送 ping 请求确认 Docker 是否可用 # except docker.errors.DockerException as e: # # 如果发生异常,说明 Docker 没有运行 # show_docker_warning() # 显示 Docker 未运行的警告 # sys.exit() # 退出程序 # except Exception as e: # # 捕获其他类型的异常 # show_docker_warning() # sys.exit() try: # 运行 docker info 命令 result = subprocess.run(['docker', 'info'], capture_output=True, text=True, check=True) # 打印输出信息 print(result.stdout) print("Docker 正常启动") except subprocess.CalledProcessError as e: # 如果命令执行失败,打印错误信息 print("Docker 未正常启动") show_docker_warning() # 显示 Docker 未运行的警告 print(e.stderr) sys.exit() except FileNotFoundError: # 如果 docker 命令未找到,说明 Docker 未安装 print("Docker 未安装或未正确配置") show_docker_warning() # 显示 Docker 未运行的警告 sys.exit() def show_docker_warning(): """显示 Docker 未运行的警告""" QMessageBox.warning(None, "警告", "请打开 Docker 并确保它正常运行!重新开始建模任务!") def start_service(): # 验证许可证 if not validate_service(): return # 如果验证失败,退出服务 # 验证成功,继续运行服务 run_service() def run_service(): # 您的服务主逻辑 # 打开软件时清空结果文件名 # 创建要写入的 JSON 数据 data = { "name": '' } # 写入 JSON 文件 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) # 运行一个命令node result_server_logs.js # subprocess.Popen('node result_server_logs.js', shell=False,creationflags=subprocess.CREATE_NO_WINDOW) app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) if __name__ == "__main__": start_service()
08-28
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值