Report_Generator/Windows/baogao_project/get_imformation.py

2818 lines
133 KiB
Python
Raw Normal View History

2025-07-02 09:19:15 +08:00
from pathlib import Path
import os,time,json,sys
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (QDialog, QVBoxLayout, QListWidget, QPushButton,
QHBoxLayout, QMessageBox, QFileDialog,
)
from PySide6 import QtCore, QtWidgets
from PySide6.QtCore import QDir, QSettings
from info_core.get_pictures import get_picture_nums, collect_defect_data
from info_core.MyQtClass import DLabel, AddProjectDialog, SmartDropdown
from ui.外部报告生成_初始化_ui import *
from ui.外部报告生成_内容填写_ui import *
class MainWindow(QtWidgets.QMainWindow):
project_info_received = QtCore.Signal(object) # 定义一个信号
def __init__(self):
super().__init__()
self.ui = Ui_Form() # 创建UI实例
self.ui.setupUi(self) # 设置UI
# 存储结果的变量
self.project_info = None
# 连接信号
self.ui.file_dir_button.clicked.connect(self.get_file_dir)
self.ui.chooseproject.clicked.connect(self.get_project_info)
self.project_info_received.connect(self.handle_project_info) # 接收信号
self.ui.chooseproject_2.clicked.connect(self.open_saved_project)
self.ui.add_project.clicked.connect(self.add_project_info)
self.ui.edit_project.clicked.connect(self.edit_project_info)
# 获取项目目录
if getattr(sys, 'frozen', False):
self.project_dir = os.path.dirname(sys.executable)
else:
self.project_dir = os.path.dirname(os.path.abspath(__file__))
self.ui.file_dir.setText(self.project_dir)
self.ui.file_dir.setPlaceholderText('选择图片目录...')
self.ui.lineEdit.setText(self.project_dir)
self.ui.file_dir.setPlaceholderText('选择保存的项目...')
self.show()
self.init_project_info()
self.open_init()
print(f'初始化窗口初始化完毕,当前项目目录:{self.project_dir}')
def init_project_info(self):
"""从默认文件夹初始化项目信息"""
self.project_folder = os.path.join(self.project_dir, 'project')
project_folder = os.path.join(self.project_dir, 'project')
if os.path.exists(project_folder) and os.path.isdir(project_folder):
json_files = [f for f in os.listdir(project_folder) if f.endswith('.json')]
if json_files:
self.ui.projects.addItems([f.split('.json')[0] for f in json_files])
else:
QMessageBox.warning(self, "警告", "project文件夹中没有找到.json文件,请先手动添加一个项目信息")
else:
QMessageBox.warning(self, "警告", "project文件夹不在当前程序根目录下请确认后选择项目基本信息文件夹")
self.choose_project_folder()
def choose_project_folder(self):
"""让用户选择保存了基本项目信息的文件夹"""
folder_path = QFileDialog.getExistingDirectory(None, "选择包含基本项目信息的文件夹")
if folder_path:
json_files = [f for f in os.listdir(folder_path) if f.endswith('.json')]
if json_files:
self.ui.projects.addItems(json_files)
self.project_folder = folder_path
else:
print("所选文件夹中没有找到.json文件")
else:
print("未选择文件夹")
def edit_project_info(self):
"""更改项目信息"""
dialog = AddProjectDialog(self.project_folder, self, self.get_current_project_path())
dialog.exec()
def add_project_info(self):
"""添加新的项目信息"""
dialog = AddProjectDialog(self.project_folder, self)
dialog.exec()
def get_current_project_path(self):
"""获取当前选中的项目文件路径"""
if not hasattr(self, 'project_folder'):
return None # 或者 raise 一个异常
current_project = self.ui.projects.currentText()
if not current_project:
return None
if self.project_folder.endswith('.json'):
return self.project_folder # 如果是文件路径,直接返回
# 拼接完整文件路径
file_path = os.path.join(self.project_folder, f"{current_project}.json")
return file_path
def open_saved_project(self):
# 确定保存目录路径
dir = self.ui.lineEdit.text()
# 创建对话框
dialog = QDialog(self)
dialog.setWindowTitle("打开保存的项目")
dialog.resize(600, 300)
# 主布局
layout = QVBoxLayout()
# 文件列表
self.file_list = QListWidget()
# 按钮布局
button_layout = QHBoxLayout()
# 浏览按钮
browse_btn = QPushButton("浏览")
browse_btn.clicked.connect(self.browse_directory)
# 打开按钮
open_btn = QPushButton("打开")
open_btn.clicked.connect(lambda: self.on_open_project(dialog))
# 刷新按钮
refresh_btn = QPushButton("刷新")
refresh_btn.clicked.connect(lambda: self.refresh_file_list(dir))
# 取消按钮
cancel_btn = QPushButton("取消")
cancel_btn.clicked.connect(dialog.reject)
button_layout.addWidget(browse_btn)
button_layout.addWidget(open_btn)
button_layout.addWidget(refresh_btn)
button_layout.addWidget(cancel_btn)
# 添加部件到主布局
layout.addWidget(self.file_list)
layout.addLayout(button_layout)
dialog.setLayout(layout)
# 初始加载文件列表
self.refresh_file_list(dir)
# 双击打开
self.file_list.itemDoubleClicked.connect(lambda: self.on_open_project(dialog))
return dialog.exec()
def browse_directory(self):
# 使用QFileDialog来选择目录
options = QFileDialog.Options()
options |= QFileDialog.ShowDirsOnly
dir = QFileDialog.getExistingDirectory(self, "选择目录", "", options=options)
if dir:
self.ui.lineEdit.setText(dir) # 更新保存目录路径
self.refresh_file_list(dir) # 刷新文件列表
def refresh_file_list(self, path):
"""刷新文件列表"""
self.file_list.clear()
# 检查目录是否存在
if not os.path.exists(path):
QMessageBox.warning(self, "警告", "保存目录不存在!")
return
# 获取目录下所有文件
dir = QDir(path)
files = dir.entryList(QDir.Files)
# 添加到列表
for file in files:
self.file_list.addItem(file)
def on_open_project(self, dialog):
"""打开选中的项目"""
selected_items = self.file_list.selectedItems()
if not selected_items:
QMessageBox.warning(self, "警告", "请先选择一个项目文件!")
return
filename = selected_items[0].text()
filepath = os.path.join(self.ui.lineEdit.text(), filename)
# 这里调用你的加载项目函数,传递文件路径
# 例如: self.load_project(filepath)
dialog.accept() # 关闭对话框
self.load_project(filepath)
# 返回文件路径可以在外部调用load_project
return filepath
def load_project(self, filepath):
self.close()
with open(filepath, 'r') as f:
data = json.load(f)
dir = data['picture_dir']
print('获取保存记录信息中...')
if 'waibu_jiancha' in data.keys():
print('当前报告包含外部')
self.ui.ifwaibu.setChecked(data['waibu_jiancha'])
if 'neibu_jiancha' in data.keys():
print('当前报告包含内部')
self.ui.ifneibu.setChecked(data['neibu_jiancha'])
if 'fanglei_jiancha' in data.keys():
print('当前报告包含防雷')
self.ui.iffanglei.setChecked(data['fanglei_jiancha'])
self.ui.file_dir.setText(dir)
global Picture_dir
Picture_dir = dir
self.ui.comboBox.setCurrentIndex(data['get_info_mode'] if 'get_info_mode' in data.keys() else 0)
info = get_xiangmu_base_info(dir, None, None)
info.project_dir = self.project_dir
self.contentfill = contentfill_window(info, self)
self.contentfill.show()
self.contentfill.load_project(filepath)
def get_file_dir(self):
default_dir = self.ui.file_dir.text()
if not os.path.exists(default_dir): # 检查当前目录是否存在
default_dir = os.path.abspath(os.sep) # 如果不存在,则使用根目录
file_path = QtWidgets.QFileDialog.getExistingDirectory(
self,
"选择文件夹",
default_dir, # 使用检查后的默认目录
QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontResolveSymlinks
)
if file_path:
self.ui.file_dir.setText(file_path)
def get_project_info(self):
if self.ui.file_dir.text():
project = self.ui.projects.currentText()
self.project_info = get_xiangmu_base_info(self.ui.file_dir.text(), project, self.project_folder)
self.project_info_received.emit(self.project_info) # 发送信号
return self.project_info
else:
QtWidgets.QMessageBox.warning(self, "警告", "请先选择图片目录")
return None
def handle_project_info(self, info):
if info:
self.close()
global Picture_dir
Picture_dir = self.ui.file_dir.text()
info.project_dir = self.project_dir
self.contentfill = contentfill_window(info, self)
self.contentfill.show()
def open_init(self):
settings = QSettings('baogao', 'baogao')
self.ui.ifwaibu.setChecked(settings.value("ifwaibu", False, type=bool))
self.ui.ifneibu.setChecked(settings.value("ifneibu", False, type=bool))
self.ui.iffanglei.setChecked(settings.value("iffanglei", False, type=bool))
self.ui.comboBox.setCurrentIndex(settings.value("generate_mode", 0, type=int))
self.ui.lineEdit.setText(settings.value("project_save_dir", "", type=str))
self.ui.file_dir.setText(settings.value("picture_dir", "", type=str))
if self.ui.lineEdit.text() == "":
self.ui.lineEdit.setText(self.project_dir)
if self.ui.file_dir.text() == "":
self.ui.file_dir.setText(self.project_dir)
def closeEvent(self, event):
settings = QSettings('baogao', 'baogao')
settings.setValue("ifwaibu", self.ui.ifwaibu.isChecked())
settings.setValue("ifneibu", self.ui.ifneibu.isChecked())
settings.setValue("iffanglei", self.ui.iffanglei.isChecked())
settings.setValue("generate_mode", self.ui.comboBox.currentIndex())
settings.setValue("project_save_dir", self.ui.lineEdit.text())
settings.setValue("picture_dir", os.path.dirname(self.ui.file_dir.text())) #获取file_dir的上一级路径
return super().closeEvent(event)
class contentfill_window(QtWidgets.QMainWindow):
global jituan_name, fengchang_name, jizu_type, company_name_yi, company_name_jia, project_location, fuzeren, phone_fuzeren, Y1, Y2, Y3, xiangmuguige
def __init__(self, info, mainwindow : MainWindow):
super().__init__()
self.Picture_dir = info.Picture_dir
self.project_dir = info.project_dir
self.Y1 = info.Y1
self.Y2 = info.Y2
self.Y3 = info.Y3
self.get_info_mode = mainwindow.ui.comboBox.currentIndex()
self.has_load_huizong = False
self.has_load_quexian = False
self.ifwaibu = mainwindow.ui.ifwaibu.isChecked()
self.ifneibu = mainwindow.ui.ifneibu.isChecked()
self.iffanglei = mainwindow.ui.iffanglei.isChecked()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self._init_base_info(info)
self._init_cover_page()
self._init_project_overview()
self.start_init_check_method()
self._init_inspection_info()
self._init_inspection_content()
self.ui.conclusion.setPlaceholderText("""例:
1因海上风电叶片运行环境恶劣空气盐碱度高叶片前缘合模缝区域及PS面迎风面涂层易受腐蚀建议定期观察维护
2经无人机近距离外观检查发现H3-08#机位Y200220AF叶片PS面距叶根20m处发现一处裂纹损伤长度轴向3m该缺陷经我方判定为严重缺陷建议尽快结安排对该机组停机并结合其他检查手段如人工打磨进一步勘查并决定维修处置方案防止风险进一步升级。
3经无人机近距离外观检查未发现H3-08#机位Y200249AF、Y200250AF叶片有明显影响机组正常运行的外部缺陷。""")
self.ui.conclusion_company.setText(company_name_yi)
self.ui.conclusion_date.setText(time.strftime("%Y年%m月%d", time.localtime()))
self.ui.save_dir.setText(os.path.join(self.project_dir, '报告数据保存'))
self.ui.output_dir.setText(os.path.join(self.project_dir, '报告输出'))
self.ui.muban_dir.setText(os.path.join(self.project_dir,'报告模板'))
self.ui.bianzhishijian.setText(self.ui.calendarWidget.selectedDate().toString('yyyy/MM/dd'))
self.ui.conclusion_date.setText(self.ui.calendarWidget.selectedDate().toString('yyyy/MM/dd'))
self.total_picture_dir = None
self.ui.geregate_button.clicked.connect(self.start_generate_report)
self.ui.tabWidget.setCurrentIndex(0)
self.ui.label_45.setText(f"图片路径:{self.Picture_dir}")
self.has_init_inspection = False
self._init_connect()
def connect_calendar_to_lineedit_via_button(self, button: QPushButton, lineedit: QLineEdit):
"""
通过按钮触发日历选择确认后更新LineEdit的值
参数:
button: QPushButton 触发按钮
lineedit: QLineEdit 文本输入框组件
"""
# 设置LineEdit为禁用状态
lineedit.setDisabled(True)
def show_calendar_dialog():
from PySide6.QtWidgets import QDialogButtonBox
# 创建对话框
dialog = QDialog()
dialog.setWindowTitle("选择日期")
layout = QVBoxLayout()
# 创建日历组件
calendar = QCalendarWidget()
layout.addWidget(calendar)
# 创建确认按钮
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box.accepted.connect(dialog.accept)
button_box.rejected.connect(dialog.reject)
layout.addWidget(button_box)
dialog.setLayout(layout)
# 如果用户确认选择
if dialog.exec() == QDialog.Accepted:
selected_date = calendar.selectedDate()
date_str = selected_date.toString("yyyy/MM/dd")
lineedit.setText(date_str)
# 连接按钮点击事件到显示日历对话框
button.clicked.connect(show_calendar_dialog)
def closeEvent(self, event):
# 创建一个QMessageBox实例
msg_box = QMessageBox(self)
msg_box.setWindowTitle('保存进度')
msg_box.setText('是否保存当前报告修改进度?')
# 添加自定义按钮
save_button = msg_box.addButton("保存", QMessageBox.YesRole)
no_save_button = msg_box.addButton("不保存", QMessageBox.NoRole)
cancel_button = msg_box.addButton("取消", QMessageBox.RejectRole)
msg_box.setDefaultButton(save_button)
msg_box.exec() # 执行对话框
clicked_button = msg_box.clickedButton() # 获取被点击的按钮
if clicked_button == save_button:
self.ui.messagebrowser.append("保存数据")
result = self.save_form_data()
if result == False:
event.ignore() # 阻止关闭
return
event.accept() # 允许关闭
elif clicked_button == no_save_button:
event.accept() # 允许关闭
else: # 取消或其他情况
event.ignore() # 阻止关闭
def save_textbrowser_content(self ,text_browser, file_path: str):
"""
保存QTextBrowser内容到文件
:param text_browser: QTextBrowser实例
:param file_path: 要保存的文件路径
"""
# 获取全部文本
full_text = text_browser.toPlainText()
temp = ''
lines = ''
for line in full_text.split('\n'):
if temp == line:
continue
temp = line
lines += line + '\n'
# 写入文件
with open(file_path, 'w', encoding='utf-8') as f:
f.write(full_text)
def start_init_check_method(self):
# 获取scrollArea的内容区域
area = self.ui.scrollAreaWidgetContents_4
area.setLayout(QVBoxLayout())
# 创建标签和对应的TextEdit框
labels_and_textedits = {
"工作内容": QTextEdit(),
"人员配置": QTextEdit(),
"设备配置": QTextEdit(),
"施工方案": QTextEdit(),
"备注": QTextEdit()
}
# 设置每个TextEdit的一些基本属性
for label, textedit in labels_and_textedits.items():
textedit.setPlaceholderText(f"请输入{label}...")
textedit.setMinimumHeight(100) # 设置最小高度
textedit.setStyleSheet("""
QTextEdit {
border: 1px solid #ccc;
border-radius: 4px;
padding: 5px;
font-size: 14px;
}
QTextEdit:focus {
border: 1px solid #0078d7;
}
""")
# 添加到布局中
area.layout().addWidget(textedit)
# 设置布局的间距和边距
area.layout().setSpacing(10) # 控件之间的间距
area.layout().setContentsMargins(10, 10, 10, 10) # 布局的边距
# 保存这些TextEdit以便后续访问
self.check_method_textedits = labels_and_textedits
text_to_add = {
"工作内容": '',
"人员配置": '',
"设备配置": '',
"施工方案": '',
"备注": '',
}
if self.ifwaibu:
text_to_add['工作内容'] += '无人机叶片外观巡检\n'
text_to_add['人员配置'] += '1人主检飞手1人\n'
text_to_add['设备配置'] += '大疆无人机1台M350rtkM300rtkM30TM30精灵4PRO||大疆精灵4PRO+索尼A7R2机身+索尼200-600mm镜头/适马150-600mm镜头\n'
if self.ifneibu:
text_to_add['工作内容'] += '叶片内部检查\n'
text_to_add['人员配置'] += '2人轮毂作业检查2人\n'
text_to_add['设备配置'] += '人工检查照明设备2套视频记录手机2台含氧量监测仪1台电动扳手2套卷尺1个。||爬壁机器人检查:无人作业校车+视频图传1套照明设备2套含氧量监测仪1台电动扳手2套卷尺1个。\n'
if self.iffanglei:
text_to_add['工作内容'] += '无人机叶片防雷导通测\n无人吊篮叶片导通测试(含机舱设备、)'
text_to_add['人员配置'] += '2人主检飞手1人副检抄表1人\n3人轮毂机舱作业1人揽风绳作业1人无人设备操作员及抄表1人'
text_to_add['设备配置'] += '1四轴电阻无人机1套电子微欧计1台视频记录手机1台\n无人吊篮系统1套爬绳器+接触平台、电子微欧计1套视频记录手机1台对讲机2台'
for name, textedit in labels_and_textedits.items():
if text_to_add[name]:
textedit.setPlainText(text_to_add[name])
def start_init_huizong(self):
if not self.get_info_mode:
self._init_inspection_content_label()
else:
self._init_inspection_content_from_file()
self.has_load_huizong = True
self.ui.label_53.setText(f"初始化汇总完毕,如需要重新初始化,请新建项目")
self.ui.start_init_huizong.setEnabled(False)
def _init_inspection_content_from_file(self):
#主要部位图片展示表/检查内容表
try:
search_file_list = []
if self.ifwaibu:
search_file_list.append("外汇总")
if self.ifneibu:
search_file_list.append("内汇总")
if self.iffanglei:
search_file_list.append("防汇总")
picture_Y1_num, Y1_dict = collect_defect_data(Y1, Picture_dir, search_file_list)
picture_Y2_num, Y2_dict = collect_defect_data(Y2, Picture_dir, search_file_list)
picture_Y3_num, Y3_dict = collect_defect_data(Y3, Picture_dir, search_file_list)
for name, path in Y1_dict.items():
self.add_dlabel(0, self.ui.scrollAreaWidgetContents, name, path, True)
for name, path in Y2_dict.items():
self.add_dlabel(1, self.ui.scrollAreaWidgetContents_2, name, path, True)
for name, path in Y3_dict.items():
self.add_dlabel(2, self.ui.scrollAreaWidgetContents_3, name, path, True)
self.ui.messagebrowser.append(f"自动获取汇总图成功,共{picture_Y1_num+picture_Y2_num+picture_Y3_num}张图片")
except Exception as e:
self.ui.messagebrowser.append(f"自动获取汇总图失败:{e}")
# def toggle_terminal_window(self):
# """切换终端窗口的显示状态"""
# if self.terminal_window is None or not self.terminal_window.isVisible():
# # 创建或显示终端窗口
# self.terminal_window = TerminalWindow(self)
# self.terminal_window.show()
# print("终端输出窗口已打开")
# else:
# # 隐藏终端窗口
# self.terminal_window.hide()
# print("终端输出窗口已隐藏")
def save_form_data(self):
"""保存表单数据到JSON文件"""
#保存静态内容:
try:
self.ui.messagebrowser.append('保存静态数据中...')
self.data = {
'has_load_huizong': self.has_load_huizong,
'has_load_quexian': self.has_load_quexian,
'key_words': '缺,损,裂,脱,污', #关键字,用于汇总图的名字包含缺陷时标红,匹配逻辑为正则匹配单个字则为红 后续可优化
#当前检查报告基本内容
'jizu_type': self.ui.jizu_type.text(), #检查的机组编号
'jiancha_date': self.ui.jiancha_date.text(), #检查叶片日期 注意空格分开 小时分钟间隔为.
'baogao_date': self.ui.date.text(), #生成报告时间 注意空格分开
'baogao_type': self.ui.baogao_name.text(), #报告类型
'jiancha_shijian': self.ui.jiancha_shijian.text(),
'chuli_shijian': self.ui.chulishijian.text(),
'bianzhi_shijian': self.ui.bianzhishijian.text(),
'weak_Y1': self.ui.weak_num_Y1.text(),
'weak_Y2': self.ui.weak_num_Y2.text(),
'weak_Y3': self.ui.weak_num_Y3.text(),
'quexian_Y1': self.ui.textEdit.toPlainText(),
'quexian_Y2': self.ui.textEdit_2.toPlainText(),
'quexian_Y3': self.ui.textEdit_3.toPlainText(),
#检查方案
#待完善
'beizhu': '',
'renyuan_peizhi': '''2人主检飞手1人副检抄表1人
3轮毂机舱作业1人揽风绳作业1人无人设备操作员及抄表1人
1抄表人员1人检测人员1人监护1人
1主检飞手1人
2轮毂作业检查2人''',
'gongzuo_neirong': '''无人机叶片防雷导通测
无人吊篮叶片导通测试含机舱设备
风机基础办公楼变电站防雷接地检测及浪涌保护器测试
无人机叶片外观巡检
叶片内部检查''',
'shigong_fangan': '',
'shebei_peizhi': '''1四轴电阻无人机1套电子微欧计1台视频记录手机1台
无人吊篮系统1套爬绳器+接触平台电子微欧计1套视频记录手机1台对讲机2台
接地电阻测试仪1套SPD测试仪1套对讲机2个
1大疆无人机1台M350rtkM300rtkM30TM30精灵4PRO2大疆精灵4PRO+索尼A7R2机身+索尼200-600mm镜头/适马150-600mm镜头
1人工检查照明设备2套视频记录手机2台含氧量监测仪1台电动扳手2套卷尺1个2爬壁机器人检查无人作业校车+视频图传1套照明设备2套含氧量监测仪1台电动扳手2套卷尺1个''',
'jiancha_renyuan': '张三',
#检查信息
'waibu_jiancha': self.ifwaibu, #是否包含外部检查
'neibu_jiancha': self.ifneibu, #是否包含内部检查
'fanglei_jiancha': self.iffanglei, #是否包含防雷检查 #注:防雷检测占不存放缺陷图
'jiancha_location': ''.join(self.checkpos), #检查内容文字
'jiancha_fangshi': self.ui.jiancha_fangshi.text(), #检查方式文字
'yepian_changjia': self.ui.changjia.text(), #叶片厂家信息
'jiancha_renyuan': self.ui.jiancha_renyuan.text(), #检查人员
'Tatong_pic_dir': self.ui.Tatong_label.current_path if self.ui.Tatong_label.current_path else '',
#报告处理信息
'yezhu_renyuan': self.ui.yezhu_renyuan.text(), #业主(人员)
'changjia_renyuan': self.ui.changjia_renyuan.text(), #厂家(人员)
'date_process': self.ui.shujuchuli.text(), #数据处理人员 吴总希望获取前端用户执行生成人员为这个人
'baogao_bianzhi': self.ui.baogao_bianzhi.text(), #报告编制人员 吴总希望获取前端用户执行生成人员为这个人
'baogao_shenghe': self.ui.baogao_shenghe.text(), #报告审核人员
'shenghe_date': self.ui.shenghe_shijian.text(), #报告审核日期
#检查情况汇总表(文字信息) 前端根据是否包含对应部分检查自行确定检查内容,这里默认全部包含
'Y1_jiancha_neirong': self.ui.textEdit_4.toPlainText(),
'Y2_jiancha_neirong': self.ui.textEdit_5.toPlainText(),
'Y3_jiancha_neirong': self.ui.textEdit_6.toPlainText(),
#报告总结
'baogao_zongjie': self.ui.conclusion.toPlainText(), #报告总结文字
}
self.data.update(
{
#目录
'picture_dir': self.Picture_dir, #图片存放地址 为了报告美观希望总的汇总图片数量为3的倍数
'shengcheng_dir': self.ui.output_dir.text(), #工作路径(报告生成的路径、报告模板存放的路径)
'muban_dir': self.ui.muban_dir.text(), #文档模板存放路径
'save_dir': self.ui.save_dir.text(), #项目保存路径
#项目概况
'jituan_jianxie': self.ui.jituan_jianxie.text(),
'jia_Company': self.ui.Jia_Company.text(),
'jizu_num': self.ui.project_num.text(),
'jizu_bianhao': self.ui.jizu_bianhao.text(),
'fengchang_name': self.ui.fengchang_name.text(),
'fengchang_location': self.ui.fengchang_location.text(),
#乙方信息
'yi_Company': self.ui.Yi_Company.text(),
'fuzeren': self.ui.fuzeren.text(),
'phone_fuzeren': self.ui.phone_fuzeren.text(),
'get_info_mode': self.get_info_mode,
'if_xiangmugaikuang': self.ui.checkBox.isChecked(),
'if_jianchaxinxi': self.ui.checkBox_8.isChecked(),
'if_chengguodijiao': self.ui.checkBox_9.isChecked(),
'if_jianchahuizong': self.ui.checkBox_10.isChecked(),
'if_jiancha_yilan': self.ui.checkBox_11.isChecked(),
'if_quexian_xiangqing': self.ui.checkBox_13.isChecked(),
}
)
except Exception as e:
self.ui.messagebrowser.append(f"保存静态内容失败:{e}")
try:
if self.total_picture_dir:
self.data['total_picture_dir'] = self.total_picture_dir
else: self.data['total_picture_dir'] = None
except Exception as e:
self.ui.messagebrowser.append(f'保存略缩图信息失败:{e}')
#保存动态内容:
try:
if self.has_load_huizong:
self.ui.messagebrowser.append('保存动态数据中...')
#汇总部分
self.data.update({
'labels':{
'init':[[],[],[]],
'add': [[],[],[]],
},
'lines':{
'init':[[],[],[]],
'add': [[],[],[]]
}
})
self.ui.messagebrowser.append('保存汇总图片与描述')
for i in range(3):
if self.get_info_mode == 0:
if self.label_init_list is not None:
for j in range(len(self.label_init_list[i])):
self.data['labels']['init'][i].append(self.label_init_list[i][j].current_path)
if self.line_init_list is not None:
for j in range(len(self.line_init_list[i])):
self.data['lines']['init'][i].append(self.line_init_list[i][j].text())
if self.new_add_dlabel is not None:
for j in range(len(self.new_add_dlabel[i])):
self.data['labels']['add'][i].append(self.new_add_dlabel[i][j].current_path)
if self.new_add_lines is not None:
for j in range(len(self.new_add_lines[i])):
self.data['lines']['add'][i].append(self.new_add_lines[i][j].text())
except Exception as e:
self.ui.messagebrowser.append(f"保存动态组件失败:{e}")
return
try:
if self.has_load_quexian:
#缺陷部分
self.ui.messagebrowser.append('保存缺陷图片与描述')
self.data.update({
'quexian_image_infos': [[], [], []]
})
if self.table_infos_modules is not None:
for i in range(len(self.data['quexian_image_infos'])):
for image in self.table_infos_modules[i]:
self.data['quexian_image_infos'][i].append({
'image_path': image['image_path'],
'visibility': image['visibility_combo'].currentIndex(),
'severity': image['severity_combo'].currentIndex(),
'urgency': image['urgency_combo'].currentIndex(),
'defect_type': image['defect_type_combo'].current_index,
'defect_location': image['defect_location_edit'].text(),
'defect_size': image['defect_size_edit'].text(),
'repair_suggestion': image['repair_suggestion_edit'].toPlainText(),
})
except Exception as e:
self.ui.messagebrowser.append(f"保存缺陷图片与描述失败:{e}")
return
try:
self.data.update({
"工作内容": '',
"人员配置": '',
"设备配置": '',
"施工方案": '',
"备注": '',
})
for name, textedit in self.check_method_textedits.items():
self.data[name] = self.check_method_textedits[name].toPlainText()
except Exception as e:
self.ui.messagebrowser.append(f'保存施工方案失败')
# data['quexian_info'] = self.table_info_to_save
#保存
# 默认保存路径
default_savepath = self.ui.save_dir.text() + rf'\{self.ui.jituan_jianxie.text()}{self.ui.fengchang_name_2.text()}项目{self.ui.baogao_name.text()}{self.ui.jizu_type_2.text()}{time.strftime("%Y%m%d_%H%M%S", time.localtime())}.json'
# 弹出文件保存对话框
file_path, _ = QFileDialog.getSaveFileName(
self, # 父窗口
"保存文件", # 对话框标题
default_savepath, # 默认路径和文件名
"JSON文件 (*.json);;所有文件 (*)" # 文件过滤器
)
# 如果用户取消了对话框file_path会是空字符串
if not file_path:
return False
try:
with open(file_path, 'w') as f:
json.dump(self.data, f)
self.ui.messagebrowser.append(f"保存成功,保存路径:{file_path}")
except Exception as e:
self.ui.messagebrowser.append(f"保存失败: {str(e)}")
QMessageBox.information(self, "保存成功", f"保存成功,保存路径:{file_path}")
return True
def apply_date_to_calendar(self, date_str: str, calendar: QCalendarWidget):
"""
"年/月/日" 格式的日期字符串应用到日历组件
参数:
date_str: str, 格式如 "2023/12/31"
calendar: QCalendarWidget 日历组件
"""
# 使用 QDate.fromString 解析日期字符串
date = QDate.fromString(date_str, "yyyy/MM/dd")
# 检查日期是否有效(避免无效日期,如 "2023/02/30"
if date.isValid():
calendar.setSelectedDate(date)
else:
print(f"警告:无效的日期格式 {date_str},无法应用到日历")
def load_project(self, path):
"""从JSON文件加载表单数据"""
self.ui.messagebrowser.append(f"加载项目数据: {path}")
self.ui.messagebrowser.append('加载静态数据中...')
try:
with open(path, 'r') as f:
self.data = json.load(f)
except FileNotFoundError:
print(f"错误:文件 {path} 不存在")
return None
except json.JSONDecodeError:
print(f"错误:文件 {path} 不是有效的JSON格式")
return None
#加载静态内容
try:
# 当前检查报告基本内容
self.ui.jizu_type_2.setText(self.data['jizu_bianhao']) # 检查的机组编号
self.ui.jizu_type.setText(self.data['jizu_type'])
self.ui.jiancha_date.setText(self.data['jiancha_date']) # 检查叶片日期
self.ui.date.setText(self.data['baogao_date']) # 生成报告时间
self.ui.baogao_name.setText(self.data['baogao_type']) # 报告类型
# 检查信息
self.ui.changjia.setText(self.data['yepian_changjia']) # 叶片厂家信息
self.ui.jiancha_fangshi.setText(self.data['jiancha_fangshi'])
self.ui.jiancha_renyuan.setText(self.data['jiancha_renyuan'])
self.ui.jiancha_renyuan2.setText(self.data['jiancha_renyuan'])
self.ui.jizu_bianhao.setText(self.data['jizu_bianhao'])
self.ui.jizu_bianhao2.setText(self.data['jizu_bianhao'])
try:
if self.data['Tatong_pic_dir'] != '':
self.ui.Tatong_label.load_image(self.data['Tatong_pic_dir'])
except Exception as e:
self.ui.messagebrowser.append(f"加载塔筒图片失败,请再次导入:{e}")
self.ui.jiancha_shijian.setText(self.data['jiancha_shijian'])
self.ui.chulishijian.setText(self.data['chuli_shijian'])
self.ui.bianzhishijian.setText(self.data['bianzhi_shijian'])
self.apply_date_to_calendar(self.data['bianzhi_shijian'], self.ui.calendarWidget)
# 检查方案
try:
for name, textedit in self.check_method_textedits.items():
self.check_method_textedits[name].setPlainText(self.data[name])
except Exception as e:
self.ui.messagebrowser.append(f'加载施工方案失败')
# 报告处理信息
self.ui.yezhu_renyuan.setText(self.data['yezhu_renyuan']) # 业主(人员)
self.ui.changjia_renyuan.setText(self.data['changjia_renyuan']) # 厂家(人员)
self.ui.shujuchuli.setText(self.data['date_process']) # 数据处理人员
self.ui.baogao_bianzhi.setText(self.data['baogao_bianzhi']) # 报告编制人员
self.ui.docx_bianzhi.setText(self.data['baogao_bianzhi'])
self.ui.docx_check.setText(self.data['baogao_shenghe'])
self.ui.baogao_shenghe.setText(self.data['baogao_shenghe']) # 报告审核人员
self.ui.shenghe_shijian.setText(self.data['shenghe_date']) # 报告审核日期
# 检查情况汇总表(文字信息)
self.ui.weak_num_Y1.setText(self.data['weak_Y1'])
self.ui.weak_num_Y2.setText(self.data['weak_Y2'])
self.ui.weak_num_Y3.setText(self.data['weak_Y3'])
self.ui.textEdit_4.setPlainText(self.data['Y1_jiancha_neirong'])
self.ui.textEdit_5.setPlainText(self.data['Y2_jiancha_neirong'])
self.ui.textEdit_6.setPlainText(self.data['Y3_jiancha_neirong'])
self.ui.textEdit.setPlainText(self.data['quexian_Y1'])
self.ui.textEdit_2.setPlainText(self.data['quexian_Y2'])
self.ui.textEdit_3.setPlainText(self.data['quexian_Y3'])
# 报告总结
self.ui.conclusion.setPlainText(self.data['baogao_zongjie']) # 报告总结文字
# 项目概况
self.ui.jituan_jianxie.setText(self.data['jituan_jianxie'])
self.ui.Jia_Company.setText(self.data['jia_Company'])
self.ui.project_num.setText(self.data['jizu_num'])
self.ui.fengchang_name.setText(self.data['fengchang_name'])
self.ui.fengchang_location.setText(self.data['fengchang_location'])
self.ui.fengchang_name_2.setText(self.data['fengchang_name'])
# 乙方信息
self.ui.Yi_Company.setText(self.data['yi_Company'])
self.ui.Jiancha_company.setText(self.data['yi_Company'])
self.ui.conclusion_company.setText(self.data['yi_Company'])
self.ui.fuzeren.setText(self.data['fuzeren'])
self.ui.phone_fuzeren.setText(self.data['phone_fuzeren'])
self.has_load_huizong = self.data['has_load_huizong']
self.has_load_quexian = self.data['has_load_quexian']
self.ui.muban_dir.setText(self.data['muban_dir'])
self.ui.save_dir.setText(self.data['save_dir'] if self.data.get('save_dir') else '')
self.ui.output_dir.setText(self.data['shengcheng_dir'] if self.data.get('shengcheng_dir') else '')
try:
if self.data['total_picture_dir']:
total_picture_dir = self.data['total_picture_dir']
self.total_picture_dir = total_picture_dir
self.total_picture_count = 0
self.recursive_count_images(total_picture_dir)
self.ui.label_18.setText('路径:' + str(total_picture_dir))
self.ui.messagebrowser.append(f"图片总数: {self.total_picture_count}、文件夹路径:{total_picture_dir}")
self.ui.total_picture_num.setText(str(self.total_picture_count))
self.ui.messagebrowser.append(f"已选择图片目录项,程序生成时会自动读取这些目录进行略缩图创建")
output_dir = self.Picture_dir
if os.path.exists(output_dir + '/merged_thumbnail.jpg'):
self.ui.messagebrowser.append(f"!!!报告生成图片路径已有略缩图,如需使用本功能生成,请先前往目录:{output_dir} 删除略缩图 merged_thumbnail.jpg")
except Exception as e:
self.ui.messagebrowser.append(f"加载图片总数失败:{e}")
except Exception as e:
self.ui.messagebrowser.append(f"加载静态数据失败:{e}")
return
print('开始加载数据')
try:
# 加载动态内容
self.ui.messagebrowser.append('加载动态数据中...')
scrollarea = [self.ui.scrollAreaWidgetContents, self.ui.scrollAreaWidgetContents_2, self.ui.scrollAreaWidgetContents_3]
if self.data['has_load_huizong']:
self.ui.messagebrowser.append('上次保存时已初始化汇总数据,开始加载初始化汇总数据')
for i in range(3):
for label, line in zip(self.data['labels']['init'][i], self.data['lines']['init'][i]):
self.ui.messagebrowser.append(f"加载初始化汇总数据: {label}, {line}")
self.add_dlabel(i, scrollarea[i], line, label)
for i in range(3):
for label, line in zip(self.data['labels']['add'][i], self.data['lines']['add'][i]):
self.add_dlabel(i, scrollarea[i], line, label)
self.ui.messagebrowser.append('加载初始化汇总数据成功')
self.ui.label_53.setText(f"初始化汇总完毕,如需要重新初始化,请保存项目信息,重新加载")
self.ui.start_init_huizong.setEnabled(False)
if self.data['has_load_quexian']:
self.ui.messagebrowser.append('上次保存时已初始化缺陷数据,开始加载初始化缺陷数据')
self.load_tabs_from_saved_data(self.ui.tab_8, self.data['quexian_image_infos'])
except Exception as e:
self.ui.messagebrowser.append(f"加载动态数据失败:{e}")
return
self.ui.messagebrowser.append('加载数据成功')
def set_tab_fill_disable(self, box : QtWidgets.QCheckBox, index : int):
parent = box.parent()
while parent is not None:
if isinstance(parent, QTabWidget):
break
parent = parent.parent()
for child in parent.widget(index).findChildren(QWidget):
if child.objectName() == box.objectName():
continue
if isinstance(child, QLineEdit) or isinstance(child, QTextEdit):
if box.isChecked():
child.setReadOnly(True)
else:
child.setReadOnly(False)
class ReportGeneratorThread(QtCore.QThread):
class MessageStream(QtCore.QObject):
"""自定义流,用于实时捕获 print 输出并发送信号"""
from io import StringIO
message_received = QtCore.Signal(str) # 定义信号,用于传递消息
def write(self, text):
self.message_received.emit(text) # 发送消息到主线程
# 可以在这里添加额外的处理(如过滤空行)
if text.strip(): # 如果非空才发送
self.message_received.emit(text)
def flush(self):
pass # 必须实现 flush但可以留空
report_generated = QtCore.Signal(str) # 报告生成完成信号
error_occurred = QtCore.Signal(str) # 错误信号
def __init__(self, ui, parent):
super().__init__()
self.ui = ui
self.parent = parent
self.message_stream = self.MessageStream() # 自定义流
def run(self):
try:
from main import generate_report
import asyncio
# 重定向标准输出到自定义流(实时捕获 print
sys.stdout = self.message_stream
# 调用异步函数生成报告
file_path = asyncio.run(generate_report(self.ui, self.parent))
# 恢复标准输出
sys.stdout = sys.__stdout__
# 发送完成信号
self.report_generated.emit(file_path)
except Exception as e:
# 恢复标准输出
sys.stdout = sys.__stdout__
self.error_occurred.emit(str(e))
def start_generate_report(self):
"""启动报告生成线程"""
dirs = [self.ui.save_dir.text(), self.ui.output_dir.text(), self.ui.muban_dir.text()]
if not self.check_dir(dirs):
return
if not self.validate_form_auto():
return
# 创建并启动线程
self.report_thread = self.ReportGeneratorThread(self.ui, self)
self.report_thread.report_generated.connect(self.on_report_generated)
self.report_thread.error_occurred.connect(self.on_report_error)
self.report_thread.message_stream.message_received.connect(self.append_message) # 连接实时输出信号
self.report_thread.start()
# 显示提示
QMessageBox.information(self, "提示", "报告正在生成中,请稍候...", QMessageBox.Ok)
def append_message(self, message):
"""实时追加消息到 messagebrowser"""
self.ui.messagebrowser.append(message) # 去除多余空白
def on_report_generated(self, file_path):
"""报告生成成功回调"""
self.ui.messagebrowser.append("------------\n报告生成成功\n------------")
self.save_textbrowser_content(self.ui.messagebrowser, self.ui.output_dir.text() + r'\log.txt')
self.ui.messagebrowser.append(f"------------\n生成日志已保存在输出路径{self.ui.output_dir.text()}/log.txt\n------------")
reply = QMessageBox.question(
self,
"报告生成成功",
f"报告已生成在: {file_path}\n\n是否要现在打开?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
if reply == QMessageBox.Yes:
import os
os.startfile(file_path) # Windows
def on_report_error(self, error_msg):
"""错误回调"""
QMessageBox.critical(self, "错误", f"生成报告时出错:\n{error_msg}")
def validate_form_auto(self):
line_edits = self.findChildren(QLineEdit)
text_edits = self.findChildren(QTextEdit)
self.ui.messagebrowser.append(f"找到的 QLineEdit 数量: {len(line_edits)}")
self.ui.messagebrowser.append(f"找到的 QTextEdit 数量: {len(text_edits)}")
missing = False
missing_tabs = set()
try:
for le in line_edits:
if le.text().strip() == '' and le.isReadOnly() == False: #为空且不只读
self.ui.messagebrowser.append(f"发现未填写的 QLineEdit: {le.objectName()}") # 调试点1
stylesheet = le.styleSheet() + "\nbackground-color: yellow;"
le.setStyleSheet(stylesheet)
le.textChanged.connect(lambda text, widget=le: self.reset_lineedit_style(widget))
missing = True
tab_widget, tab_name = self.find_tab_widget_and_name(le)
self.ui.messagebrowser.append(f"所属标签页: {tab_name}") # 调试点2
if tab_name:
missing_tabs.add(tab_name)
for te in text_edits:
if te.toPlainText().strip() == '' and te.isReadOnly() == False:
self.ui.messagebrowser.append(f"发现未填写的 QTextEdit: {te.objectName()}") # 调试点1
stylesheet = te.styleSheet() + "\nbackground-color: yellow;"
te.setStyleSheet(stylesheet)
te.textChanged.connect(lambda widget=te: self.reset_lineedit_style(widget))
missing = True
tab_widget, tab_name = self.find_tab_widget_and_name(te)
self.ui.messagebrowser.append(f"所属标签页: {tab_name}") # 调试点2
if tab_name:
missing_tabs.add(tab_name)
except Exception as e:
self.ui.messagebrowser.append(f"验证表单错误: {e}")
if missing:
self.ui.messagebrowser.append(f"所有缺失的标签页: {missing_tabs}") # 调试点3
message = "有未填写部分,已标黄"
if missing_tabs:
message += f"\n未填写部分位于以下标签页:"
for tab in missing_tabs:
message += f"\n{tab}"
if self.ui.Tatong_label.text() == "塔筒号图片选择" and not self.ui.checkBox_8.isChecked():
message += "\n塔筒号图片未选择"
message += '\n是否继续生成?'
msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Warning)
msg_box.setWindowTitle("错误")
msg_box.setText(message)
msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
msg_box.setWindowModality(Qt.NonModal) # 关键:设为非模态
response = msg_box.exec()
if response == QMessageBox.Yes:
self.empty_line_set()
return True
else:
return False
return True
def find_tab_widget_and_name(self, widget):
try:
parent = widget.parent()
while parent is not None:
self.ui.messagebrowser.append(f"当前父组件: {parent}, 类型: {type(parent)}") # 调试点4
# 检查当前父级是否是 QTabWidget
if isinstance(parent, QTabWidget):
# 获取当前控件所在的标签页索引
tab_index = parent.indexOf(widget)
if tab_index == -1:
# 如果控件不在 QTabWidget 的直接子级,尝试查找其所在的 QWidget 子级
for i in range(parent.count()):
tab_content = parent.widget(i)
if tab_content and widget in tab_content.findChildren(QWidget, options=Qt.FindChildrenRecursively):
tab_name = parent.tabText(i)
self.ui.messagebrowser.append(f"找到标签页: {tab_name} (索引: {i})") # 调试点5
return parent, tab_name
else:
tab_name = parent.tabText(tab_index)
self.ui.messagebrowser.append(f"找到标签页: {tab_name} (索引: {tab_index})") # 调试点5
return parent, tab_name
parent = parent.parent()
self.ui.messagebrowser.append("未找到 QTabWidget 父组件") # 调试点6
return None, None
except Exception as e:
self.ui.messagebrowser.append(f"查找标签页错误: {e}")
return None, None
def reset_lineedit_style(self, line_edit : QLineEdit):
"""重置 QLineEdit 的样式表为空"""
self.remove_last_style_sheet_rule(line_edit)
try:
line_edit.textChanged.disconnect()
except:
pass
def remove_last_style_sheet_rule(self, widget : QWidget):
stylesheet = widget.styleSheet()
if stylesheet:
lines = stylesheet.splitlines() # 分割为行列表
if lines: # 如果非空
lines.pop() # 移除最后一行
new_stylesheet = "\n".join(lines) # 重新合并
widget.setStyleSheet(new_stylesheet)
def _init_cover_page(self):
# 初始化日期显示
self._init_date_display()
# 设置封面内容
self.ui.jituan_jianxie.setText(jituan_name)
self.ui.fengchang_name_2.setText(fengchang_name)
self.ui.jizu_type.setText(jizu_type)
self.ui.Yi_Company.setText(company_name_yi)
def _init_date_display(self):
"""初始化日期显示,包括中文转换"""
unit_map = {'1' : '', '2' : '', '3' : '', '4' : '', '5' : '', '6' : '', '7' : '', '8' : '', '9' : '', '0' : ''}
unit_map_month = {1 : '', 2 : '', 3 : '', 4 : '', 5 : '', 6 : '', 7 : '', 8 : '', 9 : '', 10 : '', 11 : '十一', 12 : '十二'}
self.dateyear = self.ui.calendarWidget.selectedDate().toString('yyyy年')
self.dateyear = self.dateyear.translate(str.maketrans(unit_map))
self.datemonth = self.ui.calendarWidget.selectedDate().toString('MM')
self.datemonth = (unit_map_month[int(self.datemonth)] + '')
self.ui.date.setText(self.dateyear + self.datemonth)
def _init_project_overview(self):
"""初始化项目概况部分"""
self.ui.fengchang_name.setText(fengchang_name)
self.ui.fengchang_location.setText(project_location)
self.ui.Jia_Company.setText(company_name_jia)
self.ui.jizu_type.setText(jizu_type)
self.ui.Jiancha_company.setText(company_name_yi)
self.ui.fuzeren.setText(fuzeren)
self.ui.phone_fuzeren.setText(phone_fuzeren)
self.ui.project_num.setText(xiangmuguige)
def _init_inspection_info(self):
"""初始化检查信息部分"""
try:
jiancha = []
self.neirong = []
xiangmu = []
self.checkpos = []
if self.ifwaibu:
self.checkpos.append('叶片外部外观')
jiancha.append("无人机外部高精度飞行")
self.neirong.append(f"{Y1}{Y2}{Y3}三支叶片的前缘、后缘、迎风面、背风面。")
xiangmu.append("外部")
if self.ifneibu:
self.checkpos.append('叶片内部')
jiancha.append("人工内部拍摄")
self.neirong.append(f"{Y1}{Y2}{Y3}三支叶片的内部导雷卡、腹板、透光、人孔盖版、叶根盖板...")
xiangmu.append("内部")
if self.iffanglei:
self.checkpos.append('机组防雷')
jiancha.append("人工防雷")
self.neirong.append(f"轮毂至塔基导通、内部导线线阻、外部导线线阻...")
xiangmu.append("防雷")
self.ui.label_49.setText(f"当前报告项目为:" + ''.join(_ for _ in xiangmu))
normal_picture_num = get_picture_nums(self.Picture_dir)
#使用根目录文件夹名作为机组编号
self.ui.jizu_bianhao.setText(self.Picture_dir.split('/')[-1])
self.ui.jizu_bianhao2.setText(self.Picture_dir.split('/')[-1])
self.ui.jizu_type_2.setText(self.Picture_dir.split('/')[-1])
self.ui.Y1.setText(f'叶片 1{Y1}')
self.ui.Y2.setText(f'叶片 2{Y2}')
self.ui.Y3.setText(f'叶片 3{Y3}')
self.ui.label_52.setText("本次" + "".join(_ for _ in jiancha) + "检查,采集叶片图片")
self.ui.jiancha_xiangqing.setText("内容覆盖" + ";".join(_ for _ in self.neirong))
self.ui.Tatong_label.messagebrowser = self.ui.messagebrowser
self.ui.Tatong_label.setGeometry(QtCore.QRect(10, 10, 151, 151))
except Exception as e:
self.ui.messagebrowser.append(f"初始化检查信息失败:{e}")
print(f"初始化检查信息失败:{e}")
def _init_inspection_summary(self):
"""
初始化检查情况汇总
具体问题描述初始化如果实现了图片缺陷命名到图片上即可实现
"""
try:
self.Y1_quexian_num, self.Y2_quexian_num, self.Y3_quexian_num, self.quexian_num = 0, 0, 0, 0
self.Y1_quexian_dict, self.Y2_quexian_dict, self.Y3_quexian_dict = {}, {}, {}
search_file_list = []
if self.ifwaibu:
search_file_list.append("外缺陷图")
if self.ifneibu:
search_file_list.append("内缺陷图")
self.Y1_quexian_num, self.Y1_quexian_dict = collect_defect_data(Y1, Picture_dir, search_file_list)
self.Y2_quexian_num, self.Y2_quexian_dict = collect_defect_data(Y2, Picture_dir, search_file_list)
self.Y3_quexian_num, self.Y3_quexian_dict = collect_defect_data(Y3, Picture_dir, search_file_list)
self.ui.weak_num_Y1.setText(f"{Y1}共发现缺陷{self.Y1_quexian_num}")
self.ui.weak_num_Y2.setText(f"{Y2}共发现缺陷{self.Y2_quexian_num}")
self.ui.weak_num_Y3.setText(f"{Y3}共发现缺陷{self.Y3_quexian_num}")
self.ui.textEdit_4.setPlainText("\n".join(f"{i+1}.{neirong}" for i, neirong in enumerate(self.neirong) if neirong))
self.ui.textEdit_5.setPlainText("\n".join(f"{i+1}.{neirong}" for i, neirong in enumerate(self.neirong) if neirong))
self.ui.textEdit_6.setPlainText("\n".join(f"{i+1}.{neirong}" for i, neirong in enumerate(self.neirong) if neirong))
if self.get_info_mode == 1:
self.ui.textEdit.setPlainText("\n".join([f"{i+1}.{name}" for i, (name, path) in enumerate(self.Y1_quexian_dict.items())]) if self.Y1_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷')
self.ui.textEdit_2.setPlainText("\n".join([f"{i+1}.{name}" for i, (name, path) in enumerate(self.Y2_quexian_dict.items())]) if self.Y2_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷')
self.ui.textEdit_3.setPlainText("\n".join([f"{i+1}.{name}" for i, (name, path) in enumerate(self.Y3_quexian_dict.items())]) if self.Y2_quexian_num else '未发现明显影响风力发电机组正常运行的缺陷')
self.ui.messagebrowser.append(f"读取路径:{Picture_dir},读取到{Y1}缺陷数:{self.Y1_quexian_num}")
self.ui.messagebrowser.append(f"读取路径:{Picture_dir},读取到{Y2}缺陷数:{self.Y2_quexian_num}")
self.ui.messagebrowser.append(f"读取路径:{Picture_dir},读取到{Y3}缺陷数:{self.Y3_quexian_num}")
# 保存缺陷总数供其他方法使用
self.quexian_num = self.Y1_quexian_num + self.Y2_quexian_num + self.Y3_quexian_num
except Exception as e:
self.ui.messagebrowser.append(f"获取缺陷表信息失败:{e}")
def _init_inspection_content(self):
"""初始化检查内容一览"""
# 设置页标题
self.ui.tabWidget_2.setTabText(0, f"叶片1{Y1}检查内容")
self.ui.tabWidget_2.setTabText(1, f"叶片2{Y2}检查内容")
self.ui.tabWidget_2.setTabText(2, f"叶片3{Y3}检查内容")
self.picture_line = [[],[],[]]
self.picture_labels = [[],[],[]]
# 初始化布局和计数器
self.current_row = [0, 0, 0]
self.current_col = [0, 0, 0]
# 创建三个网格布局
self.gridlayout = [
QtWidgets.QGridLayout(self.ui.scrollAreaWidgetContents),
QtWidgets.QGridLayout(self.ui.scrollAreaWidgetContents_2),
QtWidgets.QGridLayout(self.ui.scrollAreaWidgetContents_3)
]
# 设置布局对齐方式
for layout in self.gridlayout:
layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
# 连接按钮信号
self.ui.add_dlabel.clicked.connect(lambda: self.add_dlabel(0, self.ui.scrollAreaWidgetContents))
self.ui.add_dlabel_2.clicked.connect(lambda: self.add_dlabel(1, self.ui.scrollAreaWidgetContents_2))
self.ui.add_dlabel_3.clicked.connect(lambda: self.add_dlabel(2, self.ui.scrollAreaWidgetContents_3))
self.label_init_list = [[],[],[]]
self.line_init_list = [[],[],[]]
self.new_add_dlabel = [[],[],[]]
self.new_add_lines = [[],[],[]]
def _init_inspection_content_label(self):
self.photo_pos = ["PS面最大弦长", "SS面最大弦长", "PS面叶尖", "SS面叶尖", "前缘局部", "后缘局部"]
self.init_dlabel(self.gridlayout)
self.ui.tabWidget_2.setCurrentIndex(0)
def start_init_quexian(self):
'''
开始初始化缺陷信息
'''
self.has_load_quexian = True
self._init_inspection_summary()
self.setup_tabs(self.ui.tab_8, self.quexian_num, self.Picture_dir)
def add_waibu_text(self):
self.ui.messagebrowser.append("自动在三个叶片中添加关于外部检查的检查内容信息...")
self.ui.textEdit_4.append('外部检查叶片前缘、后缘、PS面、SS面。')
self.ui.textEdit_5.append('外部检查叶片前缘、后缘、PS面、SS面。')
self.ui.textEdit_6.append('外部检查叶片前缘、后缘、PS面、SS面。')
def add_neibu_text(self):
self.ui.messagebrowser.append("自动在三个叶片中添加关于内部检查的检查内容信息...")
self.ui.textEdit_4.append('内部检查:叶片内部导雷卡、腹板、透光、人孔盖版、叶根盖板...')
self.ui.textEdit_5.append('内部检查:叶片内部导雷卡、腹板、透光、人孔盖版、叶根盖板...')
self.ui.textEdit_6.append('内部检查:叶片内部导雷卡、腹板、透光、人孔盖版、叶根盖板...')
def add_fanglei_text(self):
self.ui.messagebrowser.append("自动在三个叶片中添加关于防雷检查的检查内容信息...")
self.ui.textEdit_4.append('防雷检查:轮毂至塔基导通、内部导线线阻、外部导线线阻...')
self.ui.textEdit_5.append('防雷检查:轮毂至塔基导通、内部导线线阻、外部导线线阻...')
self.ui.textEdit_6.append('防雷检查:轮毂至塔基导通、内部导线线阻、外部导线线阻...')
def _init_connect(self):
#相同表单同步
self.ui.saveproject.clicked.connect(lambda: self.save_form_data())
def create_sync_handler(source, target, message):
def handler():
if source.text() != target.text():
target.setText(source.text())
# 只在真正有变化时才显示消息
if message:
self.ui.messagebrowser.append(message)
return handler
# 检查人员
self.ui.jiancha_renyuan.editingFinished.connect(
create_sync_handler(self.ui.jiancha_renyuan, self.ui.jiancha_renyuan2,
"检测到检查人员更改,自动同步所有检查人员栏..."))
self.ui.jiancha_renyuan2.editingFinished.connect(
create_sync_handler(self.ui.jiancha_renyuan2, self.ui.jiancha_renyuan,
"检测到检查人员更改,自动同步所有检查人员栏..."))
# 风场名
self.ui.fengchang_name.editingFinished.connect(
create_sync_handler(self.ui.fengchang_name, self.ui.fengchang_name_2,
"检测到风场名更改,自动同步风场名栏..."))
self.ui.fengchang_name_2.editingFinished.connect(
create_sync_handler(self.ui.fengchang_name_2, self.ui.fengchang_name,
"检测到风场名更改,自动同步风场名栏..."))
# 检查日期
self.ui.jiancha_date.textChanged.connect(lambda: (self.ui.messagebrowser.append("检测到检查日期更改,自动同步检查日期栏..."), self.ui.jiancha_riqi.setText(self.ui.jiancha_date.text())))
self.ui.jiancha_riqi.textChanged.connect(lambda: (self.ui.messagebrowser.append("检测到检查日期更改,自动同步检查日期栏..."), self.ui.jiancha_date.setText(self.ui.jiancha_riqi.text())))
# 报告编制人员
self.ui.baogao_bianzhi.editingFinished.connect(
create_sync_handler(self.ui.baogao_bianzhi, self.ui.docx_bianzhi,
"检测到报告编制人员更改,自动同步编制人员栏..."))
self.ui.docx_bianzhi.editingFinished.connect(
create_sync_handler(self.ui.docx_bianzhi, self.ui.baogao_bianzhi,
"检测到报告编制人员更改,自动同步编制人员栏..."))
# 报告审核人员
self.ui.baogao_shenghe.editingFinished.connect(
create_sync_handler(self.ui.baogao_shenghe, self.ui.docx_check,
"检测到报告审核人员更改,自动同步审核人员栏..."))
self.ui.docx_check.editingFinished.connect(
create_sync_handler(self.ui.docx_check, self.ui.baogao_shenghe,
"检测到报告审核人员更改,自动同步审核人员栏..."))
# 乙方公司/检查公司/结论公司
def sync_bianhao(source):
if (source.text() != self.ui.jizu_bianhao.text() or
source.text() != self.ui.jizu_bianhao2.text() or
source.text() != self.ui.jizu_type_2.text()):
self.ui.jizu_bianhao.setText(source.text())
self.ui.jizu_bianhao2.setText(source.text())
self.ui.jizu_type_2.setText(source.text())
self.ui.messagebrowser.append("检测到机组编号更改,自动同步所有机组编号栏...")
self.ui.jizu_bianhao.editingFinished.connect(lambda: sync_bianhao(self.ui.jizu_bianhao))
self.ui.jizu_bianhao2.editingFinished.connect(lambda: sync_bianhao(self.ui.jizu_bianhao2))
self.ui.jizu_type_2.editingFinished.connect(lambda: sync_bianhao(self.ui.jizu_type_2))
# 乙方公司/检查公司/结论公司
def sync_companies(source):
if (source.text() != self.ui.Yi_Company.text() or
source.text() != self.ui.Jiancha_company.text() or
source.text() != self.ui.conclusion_company.text()):
self.ui.Yi_Company.setText(source.text())
self.ui.Jiancha_company.setText(source.text())
self.ui.conclusion_company.setText(source.text())
self.ui.messagebrowser.append("检测到公司名称更改,自动同步所有公司名称栏...")
self.ui.Yi_Company.editingFinished.connect(lambda: sync_companies(self.ui.Yi_Company))
self.ui.Jiancha_company.editingFinished.connect(lambda: sync_companies(self.ui.Jiancha_company))
self.ui.conclusion_company.editingFinished.connect(lambda: sync_companies(self.ui.conclusion_company))
self.ui.calendarWidget.selectionChanged.connect(lambda: self.tongbu_time())
self.ui.waibu_add_text.clicked.connect(lambda: self.add_waibu_text())
self.ui.neibu_add_text.clicked.connect(lambda: self.add_neibu_text())
self.ui.fanglei_add_text.clicked.connect(lambda: self.add_fanglei_text())
self.ui.start_init_quexian.clicked.connect(lambda: self.start_init_quexian())
self.ui.start_init_huizong.clicked.connect(lambda: self.start_init_huizong())
self.bind_directory_browser(self.ui.muban_dir, self.ui.get_muban)
self.bind_directory_browser(self.ui.output_dir, self.ui.get_output)
self.bind_directory_browser(self.ui.save_dir, self.ui.get_save)
self.ui.checkBox.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox, 1))
self.ui.checkBox_8.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox_8, 3))
self.ui.checkBox_9.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox_9, 4))
self.ui.checkBox_10.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox_10, 5))
self.ui.checkBox_11.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox_11, 6))
self.ui.checkBox_13.checkStateChanged.connect(lambda: self.set_tab_fill_disable(self.ui.checkBox_13, 7))
#日期/日历绑定
self.connect_calendar_to_lineedit_via_button(self.ui.jiancha_calander, self.ui.jiancha_riqi)
self.connect_calendar_to_lineedit_via_button(self.ui.chuli_calader, self.ui.chulishijian)
self.connect_calendar_to_lineedit_via_button(self.ui.shenghe_calendar, self.ui.shenghe_shijian)
self.connect_timeedit_to_lineedit(self.ui.timeEdit, self.ui.jiancha_shijian)
self.ui.choose_picture_file.clicked.connect(lambda: self.choose_file_to_get_picture_num())
def choose_file_to_get_picture_num(self):
"""选择文件夹,计算文件夹内图片数量并生成略缩图,并记录文件夹路径。
"""
# 打开文件夹选择对话框,允许多选
folder_path = QFileDialog.getExistingDirectory(self, "选择文件夹")
if folder_path:
self.total_picture_dir = folder_path
self.total_picture_count = 0
self.recursive_count_images(folder_path)
self.ui.label_18.setText('路径:' + str(folder_path))
self.ui.messagebrowser.append(f"图片总数: {self.total_picture_count}、文件夹路径:{folder_path}")
self.ui.total_picture_num.setText(str(self.total_picture_count))
self.ui.messagebrowser.append(f"已选择图片目录项,程序生成时会自动读取这些目录进行略缩图创建")
output_dir = self.Picture_dir
if os.path.exists(output_dir + '/merged_thumbnail.jpg'):
self.ui.messagebrowser.append(f"!!!报告生成图片路径已有略缩图,如需使用本功能生成,请先前往目录:{output_dir} 删除略缩图 merged_thumbnail.jpg")
def recursive_count_images(self, path):
"""
递归遍历文件夹统计图片数量
"""
for root, dirs, files in os.walk(path):
for file in files:
# 检查文件扩展名是否为图片格式
if file.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff')):
self.total_picture_count += 1
def connect_timeedit_to_lineedit(self, timeedit: 'QTimeEdit', lineedit: 'QLineEdit'):
"""
将时间编辑组件的时间连接到LineEdit格式化为 : 格式
参数:
timeedit: QTimeEdit 时间编辑组件
lineedit: QLineEdit 文本输入框组件
"""
# 定义时间转换函数
def update_lineedit_time(time: QTime):
# 将时间转换为 时:分 格式 (HH:mm)
time_str = time.toString("HH:mm")
lineedit.setText(time_str)
# 连接时间编辑的时间变化信号到更新函数
timeedit.timeChanged.connect(update_lineedit_time)
# 初始设置当前时间
update_lineedit_time(timeedit.time())
def _init_base_info(self, info):
if not info:
return
global jituan_name, fengchang_name, jizu_type, company_name_yi, company_name_jia, project_location, fuzeren, phone_fuzeren, Y1, Y2, Y3, xiangmuguige
#获取项目基本信息。
self.ui.output_dir.setText('\\'.join(self.Picture_dir.split('\\')[:-1])) #插入图片存放路径
xiangmubaseinfo = info
jituan_name, fengchang_name, jizu_type, company_name_yi, company_name_jia, project_location, fuzeren, phone_fuzeren, Y1, Y2, Y3, xiangmuguige = xiangmubaseinfo.get_base_info()
def clear_tab_contents(self, tab : QtWidgets.QTabWidget):
"""清除tab中的所有子控件和布局"""
if not tab.layout():
return
# 保存布局的间距等属性
old_spacing = tab.layout().spacing()
old_contents_margins = tab.layout().contentsMargins()
# 清除所有子控件
self.clear_layout(tab.layout())
# 重新设置布局属性
tab.layout().setSpacing(old_spacing)
tab.layout().setContentsMargins(old_contents_margins)
def clear_layout(self, layout):
"""递归清除布局中的所有子控件"""
while layout.count():
item = layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
elif item.layout():
self.clear_layout(item.layout())
def load_tabs_from_saved_data(self, tab, saved_data):
"""
从保存的数据加载标签页内容到给定的tab对象中
参数:
tab: 你在Qt Designer中创建的容器对象
saved_data: 包含保存的缺陷信息的字典
"""
try:
# 清除tab中现有的内容
self.clear_tab_contents(tab)
# 确保 tab 有一个新的布局
if tab.layout() is None:
tab.setLayout(QVBoxLayout())
else:
# 如果已有布局,确保它是空的
self.clear_layout(tab.layout())
# 获取或创建主布局
layout = tab.layout() if tab.layout() else QVBoxLayout(tab)
layout.setSpacing(10)
# 重置存储结构
self.table_infos_modules = [[], [], []]
self.table_info_to_save = []
# 计算总缺陷数
total_defects = sum(len(images) for images in saved_data)
# 添加缺陷数量标签
num_label = QLabel(f"文件夹内总缺陷数: {total_defects}")
num_label.setStyleSheet("font-size: 16px; font-weight: bold;")
layout.addWidget(num_label)
# 创建QTabWidget确保只创建一个
if not hasattr(self, 'tab_widget') or not isinstance(getattr(self, 'tab_widget', None), QTabWidget):
self.tab_widget = QTabWidget()
else:
# 如果已存在,清除所有页签
while self.tab_widget.count():
self.tab_widget.removeTab(0)
layout.addWidget(self.tab_widget)
yepians = [Y1, Y2, Y3]
self.quexian_images = [[], [], []]
# 为每个叶片的每张图片创建标签页
image_num = 0
except Exception as e:
self.ui.messagebrowser.append(f"加载数据错误: {str(e)}")
return
for yepian_idx, Y in enumerate(yepians):
for img_idx, image_info in enumerate(saved_data[yepian_idx]):
image_path = image_info['image_path']
# 更新主进度
current_progress = sum(len(saved_data[i]) for i in range(yepian_idx)) + img_idx + 1
self.ui.messagebrowser.append(f"加载图片 {current_progress}/{total_defects}")
# 创建单个标签页
page = QWidget()
main_layout = QVBoxLayout(page)
# 创建水平布局容器(图片+信息 与 组合框+输入框)
container = QWidget()
h_layout = QHBoxLayout(container)
h_layout.setSpacing(1)
# 左侧:图片和信息区域
left_widget = QWidget()
left_layout = QVBoxLayout(left_widget)
left_layout.setSpacing(0)
# 添加图片路径信息
path_label = QLabel(f"图片地址(双击打开图片): {image_path}")
path_label.setWordWrap(True)
path_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
path_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.TextSelectableByKeyboard)
left_layout.addWidget(path_label)
# 添加图片
image_label = DLabel(getevent=False, messagebrowser=self.ui.messagebrowser)
image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
image_label.current_path = image_path
try:
# 加载图片
MAX_SIZE = 5 * 1024 * 1024 # 5MB
try:
file_size = os.path.getsize(image_path)
if file_size <= MAX_SIZE:
pixmap = QPixmap(image_path)
else:
self.progress_dialog.setLabelText(f"压缩图片{image_path}中...")
# 图片过大,进行压缩处理
from PIL import Image
import io
img = Image.open(image_path)
quality = 95 # 初始质量
while quality > 10: # 设置最低质量限制
buffer = io.BytesIO()
img.save(buffer, format="JPEG", quality=quality)
if buffer.tell() <= MAX_SIZE:
break
quality -= 5
buffer.seek(0)
pixmap = QPixmap()
pixmap.loadFromData(buffer.getvalue())
if not pixmap.isNull():
scaled_pixmap = pixmap.scaled(300, 200, aspectMode=Qt.AspectRatioMode.IgnoreAspectRatio)
image_label.setPixmap(scaled_pixmap)
else:
image_label.setText(f"无法加载图片: {image_path}")
except Exception as e:
image_label.setText(f"图片加载错误: {str(e)}")
except Exception as e:
image_label.setText(f"图片加载错误: {str(e)}")
left_layout.addWidget(image_label)
h_layout.addWidget(left_widget)
# 右侧:组合框和输入框区域
right_widget = QWidget()
right_layout = QVBoxLayout(right_widget)
# 添加水平布局的ComboBox组
combo_layout = QHBoxLayout()
combo_layout.setSpacing(0)
# 可见程度ComboBox
visibility_widget = QWidget()
visibility_layout = QHBoxLayout(visibility_widget)
visibility_layout.setSpacing(0)
visibility_combo = QComboBox()
visibility_combo.addItems(["轻微", "一般", "重要", "严重"])
visibility_combo.setCurrentIndex(image_info['visibility']) # 从保存的数据设置值
visibility_label = QLabel("可见程度:")
visibility_layout.addWidget(visibility_label)
visibility_layout.addWidget(visibility_combo)
combo_layout.addWidget(visibility_widget)
# 严重程度ComboBox
severity_widget = QWidget()
severity_layout = QHBoxLayout(severity_widget)
severity_layout.setSpacing(0)
severity_combo = QComboBox()
severity_combo.addItems(["轻微", "一般", "重要", "严重"])
severity_combo.setCurrentIndex(image_info['severity']) # 从保存的数据设置值
severity_label = QLabel("严重程度:")
severity_layout.addWidget(severity_label)
severity_layout.addWidget(severity_combo)
combo_layout.addWidget(severity_widget)
# 紧急程度ComboBox
urgency_widget = QWidget()
urgency_layout = QHBoxLayout(urgency_widget)
urgency_layout.setSpacing(0)
urgency_combo = QComboBox()
urgency_combo.addItems(["不紧急", "一般", "紧急", "非常紧急"])
urgency_combo.setCurrentIndex(image_info['urgency']) # 从保存的数据设置值
urgency_combo.setMinimumWidth(74)
urgency_label = QLabel("紧急程度:")
urgency_layout.addWidget(urgency_label)
urgency_layout.addWidget(urgency_combo)
combo_layout.addWidget(urgency_widget)
input_layout = QVBoxLayout()
# 缺陷类型输入框
defect_type_widget = QWidget()
defect_type_layout = QVBoxLayout(defect_type_widget)
defect_type_combo = SmartDropdown('缺陷类型', self.ui.messagebrowser, '缺陷类型:')
defect_type_combo.setStyleSheet("font-size: 18px; font-weight: bold;")
defect_type_combo.current_index = image_info['defect_type'] # 从保存的数据设置值
defect_type_layout.addWidget(defect_type_combo)
input_layout.addWidget(defect_type_widget)
# 缺陷位置输入框
defect_location_widget = QWidget()
defect_location_layout = QVBoxLayout(defect_location_widget)
defect_location_label = QLabel("缺陷位置:")
defect_location_label.setStyleSheet("font-size: 18px; font-weight: bold;")
defect_location_edit = QLineEdit()
defect_location_edit.setText(image_info['defect_location']) # 从保存的数据设置值
defect_location_edit.setPlaceholderText("叶片ps面距叶根3m处")
defect_location_edit.setStyleSheet("font-size: 18px; font-weight: bold;")
defect_location_layout.addWidget(defect_location_label)
defect_location_layout.addWidget(defect_location_edit)
input_layout.addWidget(defect_location_widget)
# 缺陷尺寸输入框
defect_size_widget = QWidget()
defect_size_layout = QVBoxLayout(defect_size_widget)
defect_size_label = QLabel("缺陷尺寸(mm):")
defect_size_label.setStyleSheet("font-size: 18px; font-weight: bold;")
defect_size_edit = QLineEdit()
defect_size_edit.setText(image_info['defect_size']) # 从保存的数据设置值
defect_size_edit.setPlaceholderText("弦向100mm轴向800mm")
defect_size_edit.setStyleSheet("font-size: 18px; font-weight: bold;")
defect_size_layout.addWidget(defect_size_label)
defect_size_layout.addWidget(defect_size_edit)
input_layout.addWidget(defect_size_widget)
right_layout.setSpacing(0)
right_layout.addLayout(combo_layout)
right_layout.addLayout(input_layout)
h_layout.addWidget(right_widget)
main_layout.addWidget(container)
main_layout.setSpacing(0)
# 添加维修建议
repair_suggestion_label = QLabel("维修建议:")
repair_suggestion_edit = QTextEdit()
repair_suggestion_edit.setPlainText(image_info['repair_suggestion']) # 从保存的数据设置值
repair_suggestion_edit.setPlaceholderText("暂不处理/观察运行/建议(尽快)打磨维修/(雷雨季前)修复")
repair_suggestion_edit.setMaximumHeight(100)
repair_layout = QVBoxLayout()
repair_layout.addWidget(repair_suggestion_label)
repair_layout.addWidget(repair_suggestion_edit)
main_layout.addLayout(repair_layout)
self.tab_widget.addTab(page, f"叶片:{Y} 缺陷 {img_idx+1}")
# 为ComboBox设置objectName
visibility_combo.setObjectName(f"visibility_combo_{image_num}")
severity_combo.setObjectName(f"severity_combo_{image_num}")
urgency_combo.setObjectName(f"urgency_combo_{image_num}")
# 为LineEdit设置objectName
defect_type_combo.setObjectName(f"defect_type_edit_{image_num}")
defect_location_edit.setObjectName(f"defect_location_edit_{image_num}")
defect_size_edit.setObjectName(f"defect_size_edit_{image_num}")
# 为TextEdit设置objectName
repair_suggestion_edit.setObjectName(f"repair_suggestion_edit_{image_num}")
image_num += 1
self.ui.messagebrowser.append(f"""设置组件对象名称:{visibility_combo.objectName()}, {severity_combo.objectName()}, {urgency_combo.objectName()}, {defect_type_combo.objectName()}, {defect_location_edit.objectName()}, {defect_size_edit.objectName()}, {repair_suggestion_edit.objectName()}""")
# 将当前图片的所有组件对象存储到对应的叶片列表中
loaded_image_info = {
'image_path': image_path,
'path_label': path_label,
'image_label': image_label,
'visibility_combo': visibility_combo,
'severity_combo': severity_combo,
'urgency_combo': urgency_combo,
'defect_type_combo': defect_type_combo,
'defect_location_edit': defect_location_edit,
'defect_size_edit': defect_size_edit,
'repair_suggestion_edit': repair_suggestion_edit,
}
self.table_infos_modules[yepian_idx].append(loaded_image_info)
self.ui.checkBox_13.raise_() #置顶
self.has_init_inspection = True
self.ui.start_init_quexian.setDisabled(True)
self.ui.messagebrowser.append("从保存的数据加载完成")
def setup_tabs(self, tab, num, Picture_dir):
"""
在给定的tab对象中设置带有num个页面的QTabWidget
参数:
tab: 你在Qt Designer中创建的容器对象
num: 要创建的标签页数量
Picture_dir: 图片目录路径
"""
# 清除tab中现有的内容
self.clear_tab_contents(tab)
# 确保 tab 有一个新的布局(解决第二个问题)
if tab.layout() is None:
tab.setLayout(QVBoxLayout())
else:
# 如果已有布局,确保它是空的
self.clear_layout(tab.layout())
# 获取或创建主布局
layout = tab.layout() if tab.layout() else QVBoxLayout(tab)
layout.setSpacing(10)
# 重置存储结构
self.table_infos_modules = [[], [], []]
self.table_info_to_save = []
# 添加缺陷数量标签
num_label = QLabel(f"文件夹内总缺陷数: {num}")
num_label.setStyleSheet("font-size: 16px; font-weight: bold;")
layout.addWidget(num_label)
# 创建QTabWidget确保只创建一个
if not hasattr(self, 'tab_widget') or not isinstance(getattr(self, 'tab_widget', None), QTabWidget):
self.tab_widget = QTabWidget()
else:
# 如果已存在,清除所有页签
while self.tab_widget.count():
self.tab_widget.removeTab(0)
layout.addWidget(self.tab_widget)
yepians = [Y1, Y2, Y3]
self.quexian_images = [[], [], []]
try:
# 收集所有缺陷图片路径,按叶片分类
for i, Y in enumerate(yepians):
# 获取图片路径
image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp'}
current_images = []
if self.ifwaibu:
for root, _, files in os.walk(os.path.join(Picture_dir, Y, "外缺陷图")):
for file in files:
if os.path.splitext(file)[1].lower() in image_extensions:
current_images.append(os.path.join(root, file))
if self.ifneibu:
for root, _, files in os.walk(os.path.join(Picture_dir, Y, "内缺陷图")):
for file in files:
if os.path.splitext(file)[1].lower() in image_extensions:
current_images.append(os.path.join(root, file))
self.quexian_images[i] = current_images
except Exception as e:
self.ui.messagebrowser.append(f"缺陷图获取失败: {e}")
# 设置主进度条
total_images = sum(len(images) for images in self.quexian_images)
# 为每个叶片的每张图片创建标签页
image_num = 0
for yepian_idx, Y in enumerate(yepians):
for img_idx, image_path in enumerate(self.quexian_images[yepian_idx]):
# 更新主进度
current_progress = sum(len(self.quexian_images[i]) for i in range(yepian_idx)) + img_idx + 1
self.ui.messagebrowser.append(f"处理图片 {current_progress}/{total_images}")
# 创建单个标签页
page = QWidget()
main_layout = QVBoxLayout(page)
# 创建水平布局容器(图片+信息 与 组合框+输入框)
container = QWidget()
h_layout = QHBoxLayout(container)
h_layout.setSpacing(1)
# 左侧:图片和信息区域
left_widget = QWidget()
left_layout = QVBoxLayout(left_widget)
left_layout.setSpacing(0)
# 添加图片路径信息
path_label = QLabel(f"图片地址(双击打开图片): {image_path}")
path_label.setWordWrap(True)
path_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
path_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.TextSelectableByKeyboard)
left_layout.addWidget(path_label)
# 添加图片
image_label = DLabel(getevent=False, messagebrowser=self.ui.messagebrowser)
image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
image_label.current_path = image_path
# image_button = QPushButton("编辑图片")
# left_layout.addWidget(image_button)
#self.bind_edit_function(image_label, image_button)
try:
# 加载图片
MAX_SIZE = 5 * 1024 * 1024 # 5MB
try:
file_size = os.path.getsize(image_path)
if file_size <= MAX_SIZE:
pixmap = QPixmap(image_path)
else:
self.progress_dialog.setLabelText(f"压缩图片{image_path}中...")
# 图片过大,进行压缩处理
from PIL import Image
import io
img = Image.open(image_path)
quality = 95 # 初始质量
while quality > 10: # 设置最低质量限制
buffer = io.BytesIO()
img.save(buffer, format="JPEG", quality=quality)
if buffer.tell() <= MAX_SIZE:
break
quality -= 5
buffer.seek(0)
pixmap = QPixmap()
pixmap.loadFromData(buffer.getvalue())
if not pixmap.isNull():
scaled_pixmap = pixmap.scaled(300, 200, aspectMode=Qt.AspectRatioMode.IgnoreAspectRatio)
image_label.setPixmap(scaled_pixmap)
else:
image_label.setText(f"无法加载图片: {image_path}")
except Exception as e:
image_label.setText(f"图片加载错误: {str(e)}")
except Exception as e:
image_label.setText(f"图片加载错误: {str(e)}")
left_layout.addWidget(image_label)
h_layout.addWidget(left_widget)
# 右侧:组合框和输入框区域
right_widget = QWidget()
right_layout = QVBoxLayout(right_widget)
# 添加水平布局的ComboBox组
combo_layout = QHBoxLayout()
combo_layout.setSpacing(0)
# 可见程度ComboBox
visibility_widget = QWidget()
visibility_layout = QHBoxLayout(visibility_widget)
visibility_layout.setSpacing(0)
visibility_combo = QComboBox()
visibility_combo.addItems(["轻微", "一般", "重要", "严重"])
visibility_combo.setCurrentIndex(1) # 默认选择"一般"
visibility_label = QLabel("可见程度:")
visibility_layout.addWidget(visibility_label)
visibility_layout.addWidget(visibility_combo)
combo_layout.addWidget(visibility_widget)
# 严重程度ComboBox
severity_widget = QWidget()
severity_layout = QHBoxLayout(severity_widget)
severity_layout.setSpacing(0)
severity_combo = QComboBox()
severity_combo.addItems(["轻微", "一般", "重要", "严重"])
severity_combo.setCurrentIndex(1) # 默认选择"一般"
severity_label = QLabel("严重程度:")
severity_layout.addWidget(severity_label)
severity_layout.addWidget(severity_combo)
combo_layout.addWidget(severity_widget)
# 紧急程度ComboBox
urgency_widget = QWidget()
urgency_layout = QHBoxLayout(urgency_widget)
urgency_layout.setSpacing(0)
urgency_combo = QComboBox()
urgency_combo.addItems(["不紧急", "一般", "紧急", "非常紧急"])
urgency_combo.setCurrentIndex(1) # 默认选择"一般"
urgency_combo.setMinimumWidth(74)
urgency_label = QLabel("紧急程度:")
urgency_layout.addWidget(urgency_label)
urgency_layout.addWidget(urgency_combo)
combo_layout.addWidget(urgency_widget)
input_layout = QVBoxLayout()
# 缺陷类型输入框
defect_type_widget = QWidget()
defect_type_layout = QVBoxLayout(defect_type_widget)
defect_type_combo = SmartDropdown('缺陷类型', self.ui.messagebrowser, '缺陷类型')
defect_type_combo.setStyleSheet("font-size: 18px; font-weight: bold;")
defect_type_layout.addWidget(defect_type_combo)
input_layout.addWidget(defect_type_widget)
# 缺陷位置输入框
defect_location_widget = QWidget()
defect_location_layout = QVBoxLayout(defect_location_widget)
defect_location_label = QLabel("缺陷位置:")
defect_location_label.setStyleSheet("font-size: 18px; font-weight: bold;")
defect_location_edit = QLineEdit()
defect_location_edit.setPlaceholderText("叶片ps面距叶根3m处")
defect_location_edit.setStyleSheet("font-size: 18px; font-weight: bold;")
defect_location_layout.addWidget(defect_location_label)
defect_location_layout.addWidget(defect_location_edit)
input_layout.addWidget(defect_location_widget)
# 缺陷尺寸输入框
defect_size_widget = QWidget()
defect_size_layout = QVBoxLayout(defect_size_widget)
defect_size_label = QLabel("缺陷尺寸(mm):")
defect_size_label.setStyleSheet("font-size: 18px; font-weight: bold;")
defect_size_edit = QLineEdit()
defect_size_edit.setPlaceholderText("弦向100mm轴向800mm")
defect_size_edit.setStyleSheet("font-size: 18px; font-weight: bold;")
defect_size_layout.addWidget(defect_size_label)
defect_size_layout.addWidget(defect_size_edit)
input_layout.addWidget(defect_size_widget)
right_layout.setSpacing(0)
right_layout.addLayout(combo_layout)
right_layout.addLayout(input_layout)
h_layout.addWidget(right_widget)
main_layout.addWidget(container)
main_layout.setSpacing(0)
# 添加维修建议
repair_suggestion_label = QLabel("维修建议:")
repair_suggestion_edit = QTextEdit()
repair_suggestion_edit.setPlaceholderText("暂不处理/观察运行/建议(尽快)打磨维修/(雷雨季前)修复")
repair_suggestion_edit.setMaximumHeight(100)
repair_layout = QVBoxLayout()
repair_layout.addWidget(repair_suggestion_label)
repair_layout.addWidget(repair_suggestion_edit)
main_layout.addLayout(repair_layout)
self.tab_widget.addTab(page, f"叶片:{Y} 缺陷 {img_idx+1}")
# 为ComboBox设置objectName
visibility_combo.setObjectName(f"visibility_combo_{image_num}")
severity_combo.setObjectName(f"severity_combo_{image_num}")
urgency_combo.setObjectName(f"urgency_combo_{image_num}")
# 为LineEdit设置objectName
defect_type_combo.setObjectName(f"defect_type_edit_{image_num}")
defect_location_edit.setObjectName(f"defect_location_edit_{image_num}")
defect_size_edit.setObjectName(f"defect_size_edit_{image_num}")
# 为TextEdit设置objectName
repair_suggestion_edit.setObjectName(f"repair_suggestion_edit_{image_num}")
image_num += 1
self.ui.messagebrowser.append(f"""设置组件对象名称:{visibility_combo.objectName()}, {severity_combo.objectName()}, {urgency_combo.objectName()}, {defect_type_combo.objectName()}, {defect_location_edit.objectName()}, {defect_size_edit.objectName()}, {repair_suggestion_edit.objectName()}""")
# 将当前图片的所有组件对象存储到对应的叶片列表中
image_info = {
'image_path': image_path,
'path_label': path_label,
'image_label': image_label,
'visibility_combo': visibility_combo,
'severity_combo': severity_combo,
'urgency_combo': urgency_combo,
'defect_type_combo': defect_type_combo,
'defect_location_edit': defect_location_edit,
'defect_size_edit': defect_size_edit,
'repair_suggestion_edit': repair_suggestion_edit,
}
self.table_infos_modules[yepian_idx].append(image_info)
self.ui.checkBox_13.raise_() #置顶
self.has_init_inspection = True
self.ui.start_init_quexian.setDisabled(True)
self.ui.messagebrowser.append("初次初始化完成,如需要重新初始化,请保存项目信息,重新加载")
# def bind_edit_function(self, dlabel, button):
# """绑定按钮和DLabel类点击按钮触发编辑功能
# Args:
# dlabel (DLabel): DLabel实例
# button (QPushButton): 触发编辑功能的按钮
# """
# button.clicked.connect(lambda: self._start_edit(dlabel))
# def _start_edit(self, dlabel):
# """开始编辑图片
# Args:
# dlabel (DLabel): DLabel实例
# """
# if not dlabel.current_path:
# QMessageBox.warning(dlabel, "警告", "请先加载图片")
# return
# # 创建编辑对话框
# dialog = QDialog(dlabel)
# dialog.setWindowTitle("图片编辑")
# dialog.setMinimumSize(800, 600)
# # 主布局
# main_layout = QVBoxLayout(dialog)
# # 创建图形视图和场景
# class ZoomableGraphicsView(QGraphicsView):
# def wheelEvent(self, event):
# zoom_factor = 1.15
# if event.angleDelta().y() > 0:
# self.scale(zoom_factor, zoom_factor)
# else:
# self.scale(1/zoom_factor, 1/zoom_factor)
# view = ZoomableGraphicsView()
# scene = QGraphicsScene()
# view.setScene(scene)
# view.setRenderHint(QPainter.Antialiasing)
# view.setRenderHint(QPainter.SmoothPixmapTransform)
# view.setDragMode(QGraphicsView.ScrollHandDrag)
# # 使用dlabel的load_image方法加载图片
# if not hasattr(dlabel, 'load_image'):
# QMessageBox.warning(dlabel, "错误", "DLabel缺少load_image方法")
# return
# # 获取当前图片路径
# original_path = dlabel.current_path
# # 加载图片
# pixmap = QPixmap(original_path)
# if pixmap.isNull():
# QMessageBox.warning(dlabel, "错误", "无法加载图片")
# return
# pixmap_item = QGraphicsPixmapItem(pixmap)
# scene.addItem(pixmap_item)
# scene.setSceneRect(QRectF(pixmap.rect()))
# # 创建选择标记(更粗的红色边框,透明填充)
# selection_marker = QGraphicsRectItem(0, 0, 0, 0)
# selection_marker.setPen(QPen(QColor(255, 0, 0), 10)) # 加粗到5像素
# selection_marker.setBrush(Qt.NoBrush)
# selection_marker.setVisible(False)
# scene.addItem(selection_marker)
# # 添加一个状态变量来跟踪是否有有效选择
# has_valid_selection = False
# # 按钮布局
# btn_layout = QHBoxLayout()
# btn_layout.setContentsMargins(5, 5, 5, 5)
# btn_layout.setSpacing(10)
# # 选择模式按钮 - 改为文字按钮
# btn_select = QPushButton("选择区域")
# btn_select.setToolTip("选择要裁剪的区域")
# btn_select.setCheckable(True)
# btn_select.setCursor(QCursor(Qt.PointingHandCursor))
# btn_select.setFixedSize(100, 30)
# btn_select.setStyleSheet("""
# QPushButton {
# background-color: rgba(200, 200, 200, 150);
# border-radius: 4px;
# border: 1px solid #888;
# }
# QPushButton:checked {
# background-color: rgba(100, 150, 255, 150);
# }
# """)
# # 适应窗口按钮 - 改为文字按钮
# btn_fit = QPushButton("适应窗口")
# btn_fit.setToolTip("将图片适应窗口大小")
# btn_fit.setCursor(QCursor(Qt.PointingHandCursor))
# btn_fit.setFixedSize(100, 30)
# btn_fit.setStyleSheet("""
# QPushButton {
# background-color: rgba(200, 200, 200, 150);
# border-radius: 4px;
# border: 1px solid #888;
# }
# """)
# # 保存按钮
# btn_save = QPushButton("保存修改")
# btn_save.setToolTip("保存修改并覆盖原图")
# btn_save.setCursor(QCursor(Qt.PointingHandCursor))
# btn_save.setFixedSize(100, 30)
# btn_save.setStyleSheet("""
# QPushButton {
# background-color: rgba(200, 200, 200, 150);
# border-radius: 4px;
# border: 1px solid #888;
# }
# QPushButton:hover {
# background-color: rgba(100, 255, 100, 150);
# }
# """)
# # 添加按钮到布局
# btn_layout.addWidget(btn_select)
# btn_layout.addWidget(btn_fit)
# btn_layout.addWidget(btn_save)
# btn_layout.addStretch()
# # 添加到主布局
# main_layout.addWidget(view)
# main_layout.addLayout(btn_layout)
# # 状态变量
# is_selecting = False
# start_pos = QPointF()
# current_rect = QRectF()
# # 初始适应窗口
# def fit_to_view():
# view.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)
# fit_to_view()
# # 切换选择模式
# def toggle_selection_mode(checked):
# nonlocal is_selecting
# is_selecting = checked
# if checked:
# view.setDragMode(QGraphicsView.NoDrag)
# selection_marker.setVisible(False)
# view.setCursor(QCursor(Qt.CrossCursor))
# else:
# view.setDragMode(QGraphicsView.ScrollHandDrag)
# selection_marker.setVisible(False)
# view.setCursor(QCursor(Qt.ArrowCursor))
# # 鼠标按下事件
# def mouse_press(event):
# nonlocal start_pos, current_rect, has_valid_selection
# if is_selecting and event.button() == Qt.LeftButton:
# # 使用 position() 替代 pos()
# pos = event.position() if hasattr(event, 'position') else event.posF() if hasattr(event, 'posF') else event.pos()
# start_pos = view.mapToScene(pos.toPoint())
# current_rect = QRectF(start_pos, QSizeF(0, 0))
# selection_marker.setRect(current_rect)
# selection_marker.setVisible(True)
# has_valid_selection = False
# else:
# QGraphicsView.mousePressEvent(view, event)
# # 鼠标移动事件
# def mouse_move(event):
# nonlocal has_valid_selection
# if is_selecting and selection_marker.isVisible() and (event.buttons() & Qt.LeftButton):
# # 使用 position() 替代 pos()
# pos = event.position() if hasattr(event, 'position') else event.posF() if hasattr(event, 'posF') else event.pos()
# current_pos = view.mapToScene(pos.toPoint())
# current_rect = QRectF(
# min(start_pos.x(), current_pos.x()),
# min(start_pos.y(), current_pos.y()),
# abs(current_pos.x() - start_pos.x()),
# abs(current_pos.y() - start_pos.y())
# )
# selection_marker.setRect(current_rect)
# has_valid_selection = current_rect.width() > 5 and current_rect.height() > 5
# self.ui.messagebrowser.append(f"当前选择区域:{current_rect}")
# else:
# QGraphicsView.mouseMoveEvent(view, event)
# # 鼠标释放事件
# def mouse_release(event):
# if is_selecting and event.button() == Qt.LeftButton:
# # 不需要额外处理状态已在mouse_move中更新
# pass
# else:
# QGraphicsView.mouseReleaseEvent(view, event)
# # 保存裁剪并覆盖原图
# def save_cropped_image():
# nonlocal has_valid_selection, current_rect, original_path
# if not has_valid_selection:
# QMessageBox.warning(dialog, "警告", "请先选择有效的区域")
# return
# try:
# # 获取原始图片
# original_pixmap = QPixmap(original_path)
# if original_pixmap.isNull():
# QMessageBox.warning(dialog, "错误", "无法加载原始图片")
# return
# # 创建一个新的QPixmap作为画布大小与原始图片相同
# result_pixmap = QPixmap(original_pixmap.size())
# result_pixmap.fill(Qt.transparent) # 透明背景
# # 创建QPainter来绘制
# painter = QPainter(result_pixmap)
# # 首先绘制原始图片
# painter.drawPixmap(0, 0, original_pixmap)
# # 设置画笔属性红色10像素宽
# pen = QPen(QColor(255, 0, 0), 10)
# pen.setStyle(Qt.SolidLine)
# painter.setPen(pen)
# painter.setBrush(Qt.NoBrush) # 无填充
# # 将场景坐标转换为图片坐标
# pixmap_rect = pixmap_item.pixmap().rect()
# scene_rect = pixmap_item.sceneBoundingRect()
# # 计算缩放比例
# scale_x = pixmap_rect.width() / scene_rect.width()
# scale_y = pixmap_rect.height() / scene_rect.height()
# # 将选择区域转换为原始图片坐标
# draw_rect = QRect(
# int((current_rect.x() - scene_rect.x()) * scale_x),
# int((current_rect.y() - scene_rect.y()) * scale_y),
# int(current_rect.width() * scale_x),
# int(current_rect.height() * scale_y)
# )
# # 确保绘制区域在图片范围内
# draw_rect = draw_rect.intersected(pixmap_rect)
# if draw_rect.isEmpty():
# QMessageBox.warning(dialog, "错误", "绘制区域无效")
# return
# # 绘制红色矩形框
# painter.drawRect(draw_rect)
# # 结束绘制
# painter.end()
# # 保存图片,覆盖原图
# if not result_pixmap.save(original_path):
# QMessageBox.warning(dialog, "错误", "保存图片失败")
# return
# # 重新加载图片
# dlabel.load_image(original_path)
# QMessageBox.information(dialog, "成功", "图片已保存并重新加载")
# dialog.accept()
# except Exception as e:
# QMessageBox.critical(dialog, "错误", f"保存过程中发生错误: {str(e)}")
# # 添加快捷键支持
# def key_press_event(event):
# if event.key() == Qt.Key_F:
# fit_to_view()
# elif event.key() == Qt.Key_C:
# btn_select.setChecked(not btn_select.isChecked())
# elif event.key() == Qt.Key_S:
# save_cropped_image()
# else:
# QGraphicsView.keyPressEvent(view, event)
# # 连接信号
# btn_select.toggled.connect(toggle_selection_mode)
# btn_fit.clicked.connect(fit_to_view)
# btn_save.clicked.connect(save_cropped_image)
# # 重写事件
# view.mousePressEvent = mouse_press
# view.mouseMoveEvent = mouse_move
# view.mouseReleaseEvent = mouse_release
# view.keyPressEvent = key_press_event
# # 窗口大小变化时重新适应
# def resize_event(e):
# fit_to_view()
# QDialog.resizeEvent(dialog, e)
# dialog.resizeEvent = resize_event
# # 设置对话框焦点,以便接收键盘事件
# dialog.setFocus()
# dialog.exec()
# def setup_tabs(self, tab, num, Picture_dir):
# """
# 在给定的tab对象中设置带有num个页面的QTabWidget
# 参数:
# tab: 你在Qt Designer中创建的容器对象
# num: 要创建的标签页数量
# Picture_dir: 图片目录路径
# """
# self.table_infos_modules = [{},{},{}]
# self._clear_existing_layout(tab)
# layout = self._create_main_layout(tab)
# self._add_count_label(layout, num)
# tab_widget = self._create_tab_widget(layout)
# self._populate_tabs(tab_widget, Picture_dir)
# self.ui.checkBox_13.raise_() #置顶
# def _clear_existing_layout(self, tab):
# """清除tab中现有的布局和控件"""
# if tab.layout():
# while tab.layout().count():
# item = tab.layout().takeAt(0)
# if item.widget():
# item.widget().deleteLater()
# def _create_main_layout(self, tab):
# """创建并返回主布局"""
# layout = QVBoxLayout(tab)
# layout.setSpacing(10)
# return layout
# def _add_count_label(self, layout, num):
# """添加缺陷数量标签"""
# num_label = QLabel(f"文件夹内总缺陷数: {num}")
# num_label.setStyleSheet("font-size: 16px; font-weight: bold;")
# layout.addWidget(num_label)
# def _create_tab_widget(self, layout):
# """创建并返回QTabWidget"""
# tab_widget = QTabWidget()
# layout.addWidget(tab_widget)
# return tab_widget
# def _populate_tabs(self, tab_widget, Picture_dir):
# """填充标签页内容"""
# yepians = [Y1, Y2, Y3]
# quexian_images = []
# self.quexian_images = [[],[],[]]
# # 收集所有缺陷图片路径
# for i, Y in enumerate(yepians):
# quexian_images.extend(self.get_image_paths(
# os.path.join(Picture_dir, Y, "缺陷图"), Y))
# self.quexian_images[i] = quexian_images
# # 设置主进度条
# self.progress_dialog.setRange(0, len(quexian_images))
# # 为每个图片创建标签页
# for i, (image_path, yepian) in enumerate(quexian_images, 1):
# # 更新主进度
# self.progress_dialog.setValue(i)
# self.progress_dialog.setLabelText(f"处理图片 {i+1}/{len(quexian_images)}")
# page = self._create_tab_page(image_path)
# tab_widget.addTab(page, f"叶片:{yepian} 缺陷 {i}")
# self.progress_dialog.close()
# def _create_tab_page(self, image_path):
# """创建单个标签页"""
# page = QWidget()
# main_layout = QVBoxLayout(page)
# # 创建水平布局容器
# horizontal_container = self._create_horizontal_container(image_path)
# main_layout.addWidget(horizontal_container)
# main_layout.setSpacing(0)
# # 添加维修建议
# self._add_repair_suggestion(main_layout)
# return page
# def _create_horizontal_container(self, image_path):
# """创建水平布局容器(图片+信息 与 组合框+输入框)"""
# container = QWidget()
# layout = QHBoxLayout(container)
# layout.setSpacing(1)
# # 左侧:图片和信息区域
# left_widget = self._create_image_widget(image_path)
# # 右侧:组合框和输入框区域
# right_widget = QWidget()
# right_layout = QVBoxLayout(right_widget)
# self.add_quexian_combo(right_layout)
# layout.addWidget(left_widget)
# layout.addWidget(right_widget)
# return container
# def _create_image_widget(self, image_path):
# """创建图片显示部件"""
# widget = QWidget()
# layout = QVBoxLayout(widget)
# layout.setSpacing(0)
# # 添加图片路径信息
# self._add_image_path_label(layout, image_path)
# # 添加图片
# image_label = self._create_image_label(image_path)
# layout.addWidget(image_label)
# return widget
# def _add_image_path_label(self, layout, image_path):
# """添加图片路径标签"""
# path_label = QLabel(f"图片地址(双击打开图片): {image_path}")
# path_label.setWordWrap(True)
# path_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# layout.addWidget(path_label)
# def _create_image_label(self, image_path):
# """创建并返回图片标签"""
# image_label = DLabel(getevent = False)
# image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# image_label.current_path = image_path
# try:
# pixmap = self._load_pixmap(image_path)
# if not pixmap.isNull():
# scaled_pixmap = pixmap.scaled(300, 200, aspectMode = Qt.AspectRatioMode.IgnoreAspectRatio)
# image_label.setPixmap(scaled_pixmap)
# else:
# image_label.setText(f"无法加载图片: {image_path}")
# except Exception as e:
# image_label.setText(f"图片加载错误: {str(e)}")
# return image_label
# def _load_pixmap(self, image_path):
# """加载并返回QPixmap处理大图片压缩"""
# MAX_SIZE = 5 * 1024 * 1024 # 5MB
# try:
# file_size = os.path.getsize(image_path)
# if file_size <= MAX_SIZE:
# return QPixmap(image_path)
# self.progress_dialog.setLabelText(f"压缩图片{image_path}中...")
# # 图片过大,进行压缩处理
# from PIL import Image
# import io
# img = Image.open(image_path)
# quality = 95 # 初始质量
# while quality > 10: # 设置最低质量限制
# buffer = io.BytesIO()
# img.save(buffer, format="JPEG", quality=quality)
# if buffer.tell() <= MAX_SIZE:
# break
# quality -= 5
# buffer.seek(0)
# pixmap = QPixmap()
# pixmap.loadFromData(buffer.getvalue())
# return pixmap
# except Exception as e:
# print(f"图片加载/压缩失败: {e}")
# return QPixmap()
# def _add_repair_suggestion(self, layout):
# """添加维修建议部件"""
# repair_suggestion_label = QLabel("维修建议:")
# self.repair_suggestion_edit = QTextEdit()
# self.repair_suggestion_edit.setPlaceholderText("暂不处理/观察运行/建议(尽快)打磨维修/(雷雨季前)修复")
# self.repair_suggestion_edit.setMaximumHeight(100)
# repair_layout = QVBoxLayout()
# repair_layout.addWidget(repair_suggestion_label)
# repair_layout.addWidget(self.repair_suggestion_edit)
# layout.addLayout(repair_layout)
# def add_quexian_combo(self, page_layout):
# """添加缺陷类型填写框
# Args:
# page_layout (QtWidgets.QVBoxLayout): 页面布局对象
# """
# # 添加水平布局的ComboBox组
# layout = QHBoxLayout()
# layout.setSpacing(0)
# # 可见程度ComboBox
# visibility_widget = QWidget()
# visibility_layout = QHBoxLayout(visibility_widget)
# visibility_layout.setSpacing(0)
# visibility_combo = QComboBox()
# visibility_combo.addItems(["轻微", "一般", "重要", "严重"])
# visibility_combo.setCurrentIndex(1) # 默认选择"一般"
# visibility_label = QLabel("可见程度:")
# visibility_layout.addWidget(visibility_label)
# visibility_layout.addWidget(visibility_combo)
# layout.addWidget(visibility_widget)
# # 严重程度ComboBox
# severity_widget = QWidget()
# severity_layout = QHBoxLayout(severity_widget)
# severity_layout.setSpacing(0)
# severity_combo = QComboBox()
# severity_combo.addItems(["轻微", "一般", "重要", "严重"])
# severity_combo.setCurrentIndex(1) # 默认选择"一般"
# severity_label = QLabel("严重程度:")
# severity_layout.addWidget(severity_label)
# severity_layout.addWidget(severity_combo)
# layout.addWidget(severity_widget)
# # 紧急程度ComboBox
# urgency_widget = QWidget()
# urgency_layout = QHBoxLayout(urgency_widget)
# urgency_layout.setSpacing(0)
# urgency_combo = QComboBox()
# urgency_combo.addItems(["不紧急", "一般", "紧急", "非常紧急"])
# urgency_combo.setCurrentIndex(1) # 默认选择"一般"
# urgency_combo.setMinimumWidth(74)
# urgency_label = QLabel("紧急程度:")
# urgency_layout.addWidget(urgency_label)
# urgency_layout.addWidget(urgency_combo)
# layout.addWidget(urgency_widget)
# vlayout = QVBoxLayout()
# # 缺陷类型输入框
# defect_type_widget = QWidget()
# defect_type_layout = QVBoxLayout(defect_type_widget)
# defect_type_label = QLabel("缺陷类型:")
# defect_type_label.setStyleSheet("font-size: 18px; font-weight: bold;")
# self.defect_type_edit = QLineEdit()
# self.defect_type_edit.setPlaceholderText("如:涂层损伤")
# self.defect_type_edit.setStyleSheet("font-size: 18px; font-weight: bold;")
# defect_type_layout.addWidget(defect_type_label)
# defect_type_layout.addWidget(self.defect_type_edit)
# vlayout.addWidget(defect_type_widget)
# # 缺陷位置输入框
# defect_location_widget = QWidget()
# defect_location_layout = QVBoxLayout(defect_location_widget)
# defect_location_label = QLabel("缺陷位置:")
# defect_location_label.setStyleSheet("font-size: 18px; font-weight: bold;")
# self.defect_location_edit = QLineEdit()
# self.defect_location_edit.setPlaceholderText("如叶片ps面距叶根3m处")
# self.defect_location_edit.setStyleSheet("font-size: 18px; font-weight: bold;")
# defect_location_layout.addWidget(defect_location_label)
# defect_location_layout.addWidget(self.defect_location_edit)
# vlayout.addWidget(defect_location_widget)
# # 缺陷尺寸输入框
# defect_size_widget = QWidget()
# defect_size_layout = QVBoxLayout(defect_size_widget)
# defect_size_label = QLabel("缺陷尺寸(mm):")
# defect_size_label.setStyleSheet("font-size: 18px; font-weight: bold;")
# self.defect_size_edit = QLineEdit()
# self.defect_size_edit.setPlaceholderText("如弦向100mm轴向800mm")
# self.defect_size_edit.setStyleSheet("font-size: 18px; font-weight: bold;")
# defect_size_layout.addWidget(defect_size_label)
# defect_size_layout.addWidget(self.defect_size_edit)
# vlayout.addWidget(defect_size_widget)
# page_layout.setSpacing(0)
# page_layout.addLayout(layout)
# page_layout.addLayout(vlayout)
# def get_image_paths(self, directory, Y):
# image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp'}
# image_paths = []
# # 使用 os.walk
# for root, _, files in os.walk(directory):
# for file in files:
# if os.path.splitext(file)[1].lower() in image_extensions:
# image_paths.append((os.path.join(root, file), Y))
# return image_paths
def init_dlabel(self,scrollArea):
"""初始化图片控件 汇总部分
Args:
scrollArea (QtWidgets.QScrollArea): 页面控件对象
"""
area = scrollArea
for i in range(3):
for j in range(6):
container = QWidget()
container.setFixedSize(150,150)
v_layout = QVBoxLayout(container)
label = DLabel(messagebrowser=self.ui.messagebrowser)
label.setGeometry(10, 10, 150, 150)
v_layout.addWidget(label)
line_edit = QLineEdit()
line_edit.setPlaceholderText("输入图片拍摄点位")
line_edit.setText(self.photo_pos[j])
v_layout.addWidget(line_edit)
self.gridlayout[i].addWidget(container, self.current_row[i], self.current_col[i])
self.current_col[i] += 1
if self.current_col[i] >= 5:
self.current_col[i] = 0
self.current_row[i] += 1
self.picture_line[i].append(line_edit)
self.picture_labels[i].append(label)
self.label_init_list[i].append(label)
self.line_init_list[i].append(line_edit)
def add_dlabel(self, i, scrollArea, text = None, picture_path = None, ifauto = False):
"""增加图片控件
Args:
i (int): 第几个页面
scrollArea (QtWidgets.QScrollArea): 页面控件对象
"""
area = scrollArea
container = QWidget()
container.setFixedSize(150,150)
v_layout = QVBoxLayout(container)
label = DLabel(messagebrowser=self.ui.messagebrowser)
label.setGeometry(10, 10, 150, 150)
if picture_path:
label.load_image(picture_path)
v_layout.addWidget(label)
line_edit = QLineEdit()
line_edit.setPlaceholderText("输入图片拍摄点位")
if text:
line_edit.setText(text)
v_layout.addWidget(line_edit)
self.gridlayout[i].addWidget(container, self.current_row[i], self.current_col[i])
self.current_col[i] += 1
if self.current_col[i] >= 5:
self.current_col[i] = 0
self.current_row[i] += 1
self.picture_line[i].append(line_edit)
self.picture_labels[i].append(label)
self.new_add_dlabel[i].append(label)
self.new_add_lines[i].append(line_edit)
return label, line_edit
def empty_line_set(self):
"""遍历整个页面,如果有输入框为空,则置为‘未输入’"""
widget = self.ui.tabWidget # 最根组件
# 遍历所有的标签页
for i in range(widget.count()):
tab = widget.widget(i)
# 假设输入框是通过布局(例如 QVBoxLayout 或 QHBoxLayout来组织的
# 如果输入框是直接作为子部件添加的,可以使用 tab.children() 来获取所有子部件
for child in tab.findChildren(QtWidgets.QLineEdit):
if not child.text().strip(): # 如果输入框为空(包括空白字符)
child.setText('未输入') # 设置为“未输入”
def check_dir(self, dirs: list[str]):
"""检查目录是否存在,不存在则提示不可达"""
missing_dirs = []
ifmisiing = False
for directory in dirs:
if not os.path.isdir(directory):
missing_dirs.append(directory)
ifmisiing = True
if ifmisiing:
QtWidgets.QMessageBox.warning(None, "提示", f"以下目录不存在:\n{missing_dirs}\n请检查后重试")
return False
return True
def bind_directory_browser(self, line_edit: QLineEdit, button: QPushButton):
"""绑定目录浏览功能到QLineEdit和QPushButton
Args:
line_edit: 显示和存储目录路径的QLineEdit组件
button: 触发目录浏览的QPushButton组件
"""
def browse_directory():
# 获取当前line_edit中的路径
current_dir = line_edit.text().strip()
# 检查路径是否存在,不存在则使用根目录
if not current_dir or not QDir(current_dir).exists():
current_dir = QDir.rootPath()
# 打开目录选择对话框
selected_dir = QFileDialog.getExistingDirectory(
button, # 父组件
"选择目录", # 标题
current_dir, # 初始目录
QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks # 选项
)
# 如果用户选择了目录(未点击取消)则更新line_edit
if selected_dir:
line_edit.setText(QDir.toNativeSeparators(selected_dir))
# 连接按钮的点击信号到浏览函数
button.clicked.connect(browse_directory)
def tongbu_time(self):
self.dateyear = self.ui.calendarWidget.selectedDate().toString('yyyy年')
unit_map = {'1' : '', '2' : '', '3' : '', '4' : '', '5' : '', '6' : '', '7' : '', '8' : '', '9' : '', '0' : ''}
unit_map_month = {1 : '', 2 : '', 3 : '', 4 : '', 5 : '', 6 : '', 7 : '', 8 : '', 9 : '', 10 : '', 11 : '十一', 12 : '十二'}
self.dateyear = self.dateyear.translate(str.maketrans(unit_map))
self.datemonth = self.ui.calendarWidget.selectedDate().toString('MM')
self.datemonth = (unit_map_month[int(self.datemonth)] + '')
self.ui.date.setText(self.dateyear + self.datemonth)
self.ui.bianzhishijian.setText(self.ui.calendarWidget.selectedDate().toString('yyyy/MM/dd'))
self.ui.conclusion_date.setText(self.ui.calendarWidget.selectedDate().toString('yyyy/MM/dd'))
self.ui.messagebrowser.append("检测到手动更改报告时间,自动同步编制时间、总结时间")
class XiangmuBaseInfo:
"""项目基本信息类"""
def __init__(self, tupian_dir, project_info_data):
self.tupian_dir = tupian_dir
if len(project_info_data) > 0:
self.jituan_name = project_info_data["甲方集团"]
self.fengchang_name = project_info_data["风场名"]
self.jizu_type = project_info_data["机组型号"]
self.company_name_yi = project_info_data["乙方公司"]
self.company_name_jia = project_info_data["甲方公司"]
self.project_location = project_info_data["风场地址"]
self.fuzeren = project_info_data["负责人"]
self.phone_fuzeren = project_info_data["负责人电话"]
self.xiangmuguige = project_info_data["项目规格"]
else:
self.jituan_name = ''
self.fengchang_name = ''
self.jizu_type = ''
self.company_name_yi = ''
self.company_name_jia = ''
self.project_location = ''
self.fuzeren = ''
self.phone_fuzeren = ''
self.xiangmuguige = ''
self.Picture_dir = tupian_dir
self.project_dir = ''
tupian_dir = Path(tupian_dir)
folder_names = [f.name for f in tupian_dir.iterdir() if f.is_dir()]
#通过图片路径获取各叶片编号
self.Y1 = folder_names[0]
self.Y2 = folder_names[1]
self.Y3 = folder_names[2]
def get_base_info(self):
'''返回项目基本信息
Returns:
tuple: jituan_name, fengchang_name, jizu_type, company_name_yi, company_name_jia, project_location, fuzeren, phone_fuzeren, Y1, Y2, Y3, xiangmuguige
'''
return self.jituan_name, self.fengchang_name, self.jizu_type, self.company_name_yi, self.company_name_jia, self.project_location, self.fuzeren, self.phone_fuzeren, self.Y1, self.Y2, self.Y3, self.xiangmuguige
def get_xiangmu_base_info(tupian_dir, project = None, exe_dir = None, ifdatabase = False):
"""获取项目基本信息
Args:
tupian_dir (str): 图片文件夹路径
project: 项目名称(通过此项目名称查找本地/数据库对应项目信息) 未实现
Returns:
XiangmuBaseInfo: 项目基本信息对象
"""
if not project:
return XiangmuBaseInfo(tupian_dir, {})
if not ifdatabase and project and exe_dir:
# 构建项目信息JSON文件的路径
project_info_path = exe_dir + rf"\{project}.json"
# 检查文件是否存在
if os.path.exists(project_info_path):
# 读取并解析JSON文件中的数据
with open(project_info_path, 'r', encoding='utf-8') as file:
project_info_data = json.load(file)
else:
# 如果文件不存在,可以抛出异常或返回空信息
raise FileNotFoundError(f"项目信息文件未找到: {project_info_path}")
return XiangmuBaseInfo(tupian_dir, project_info_data)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv) # 创建QApplication实例
QCoreApplication.setOrganizationName('DTAI')
QCoreApplication.setApplicationName('baogao_shengcheng')
# 创建并显示主窗口
window = MainWindow()
window.show()
# 运行应用程序
sys.exit(app.exec())