5163 lines
243 KiB
Python
5163 lines
243 KiB
Python
import os
|
||
import sys
|
||
import logging
|
||
import json
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from utils.modbus_utils import ModbusUtils
|
||
from utils.modbus_monitor import get_instance as get_modbus_monitor
|
||
from utils.app_mode import AppMode
|
||
from apis.gc_api import GcApi
|
||
from utils.register_handlers import (
|
||
NGHandler,
|
||
WeightDataHandler,
|
||
LabelSignalHandler,
|
||
MachineStatusHandlers,
|
||
LoadingFeedbackHandler,
|
||
UnloadingFeedbackHandler,
|
||
Error1Handler,
|
||
Error2Handler,
|
||
Error3Handler,
|
||
UnloadingLevelHandler,
|
||
UnloadingPositionHandler,
|
||
EmergencyStopHandler,
|
||
RegisterChangeHandler
|
||
)
|
||
from utils.electricity_monitor import ElectricityHandler
|
||
# 导入PySide6
|
||
from PySide6.QtWidgets import (
|
||
QWidget, QMessageBox, QTableWidgetItem, QStackedWidget, QLabel,
|
||
QTableWidget, QMenu, QComboBox, QFormLayout, QDialog, QVBoxLayout,
|
||
QFrame, QHBoxLayout, QSplitter, QPushButton, QTextEdit
|
||
)
|
||
from PySide6.QtCore import Qt, QTimer, Slot, Signal
|
||
from PySide6.QtGui import QBrush, QColor
|
||
import time
|
||
|
||
# 导入UI
|
||
from ui.main_window_ui import MainWindowUI
|
||
# 导入相机显示组件
|
||
from widgets.camera_display_widget import CameraDisplayWidget
|
||
|
||
# 导入检验配置管理器
|
||
from utils.inspection_config_manager import InspectionConfigManager
|
||
# 导入托盘类型管理器
|
||
from utils.pallet_type_manager import PalletTypeManager
|
||
# 导入串口管理
|
||
from utils.serial_manager import SerialManager
|
||
from widgets.report_dialog import ReportDialog
|
||
from widgets.unloading_dialog_widget import UnloadingDialog
|
||
|
||
class MainWindow(MainWindowUI):
|
||
"""主窗口"""
|
||
|
||
# 定义信号作为类变量
|
||
loading_feedback_signal = Signal(str, str) # 参数:status_type, desc
|
||
unloading_feedback_signal = Signal(str, str) # 参数:status_type, desc
|
||
unloading_level_ui_signal = Signal(int) # 用于在主线程中更新下料层数UI
|
||
unloading_position_ui_signal = Signal(int) # 用于在主线程中更新下料位置UI
|
||
emergency_stop_signal = Signal(int, str) # 用于在主线程中处理急停信号
|
||
diameter_warning_signal = Signal(float, str, str) # 参数:final_value, bccd, tccd
|
||
|
||
# 新增的线径警告弹框信号 - 参数:值, 最小值, 最大值
|
||
diameter_alert_signal = Signal(float, float, float)
|
||
|
||
# 新增的重量警告弹框信号 - 参数:值, 最小值, 最大值
|
||
weight_alert_signal = Signal(float, float, float)
|
||
|
||
def __init__(self, user_id=None, user_name=None, corp_name=None, corp_id=None):
|
||
"""初始化主窗口"""
|
||
super().__init__(user_id)
|
||
|
||
# 初始化用户信息
|
||
self.user_id = user_id
|
||
self.user_name = user_name
|
||
self.corp_name = corp_name
|
||
self.corp_id = corp_id
|
||
|
||
# 初始化焦点跟踪器
|
||
from utils.focus_tracker import FocusTracker
|
||
self.focus_tracker = FocusTracker.get_instance()
|
||
self.focus_tracker.initialize()
|
||
|
||
# 连接焦点变化信号
|
||
self.focus_tracker.focus_changed.connect(self._log_focus_widget_info)
|
||
|
||
# 添加当前处理行的跟踪
|
||
self._current_processing_row = None # 当前正在处理的行索引
|
||
|
||
# 初始化系统变量
|
||
self._current_weight = 0.0 # 当前重量
|
||
self._last_weight_time = 0 # 上次称重时间
|
||
self._stability_check_timer = None # 稳定性检查定时器
|
||
self._weight_stable_threshold = 2 # 重量稳定阈值(秒)
|
||
self._weight_processed = False # 新增:标记当前重量是否已处理,避免重复处理
|
||
self._last_processed_weight = 0.0 # 新增:记录上次处理的重量
|
||
|
||
# 线径数据处理相关属性
|
||
self._last_diameter_value = 0 # 最后一次有效的线径值
|
||
self._diameter_stable = False # 保留此属性以避免引用错误
|
||
|
||
# 初始化数据加载状态标志
|
||
self._loading_data_in_progress = False # 数据加载状态标志,防止循环调用
|
||
self._current_order_code = None # 存储当前订单号
|
||
self.init_seq = {} # 初始化轴包装的序号
|
||
|
||
# 初始化拆垛和下料相关的属性
|
||
self._current_stow_num = 0 # 当前拆垛层数
|
||
self._current_unload_num = 0 # 当前下料层数
|
||
self._total_unload_num = 0
|
||
self._current_unload_info = None # 存储当前下料信息
|
||
self._loading_info = None # 存储上料对话框的信息
|
||
self._is_loading_active = False # 标识上料任务是否正在进行
|
||
self._current_gc_qd = 0 # 当前工程号的强度数据
|
||
|
||
# 信号的连接在connect_signals方法中统一处理,不在这里连接
|
||
|
||
# 称重相关变量
|
||
self._current_weight = None # 当前称重值(千克)
|
||
self._last_weight_time = None # 最后一次称重时间
|
||
self._weight_stable_threshold = 2 # 重量稳定阈值(秒)
|
||
self._stability_check_timer = None # 用于检查重量稳定性的定时器
|
||
|
||
# 设置窗口标题
|
||
if user_name and corp_name:
|
||
self.setWindowTitle(f"腾智微丝产线包装系统 ({corp_name})")
|
||
|
||
# 加载配置文件
|
||
self.config = self.load_config()
|
||
|
||
# 初始化检验配置管理器
|
||
self.inspection_manager = InspectionConfigManager.get_instance()
|
||
|
||
# 初始化托盘类型管理器
|
||
self.pallet_type_manager = PalletTypeManager.get_instance()
|
||
|
||
# 创建表单布局,用于添加托盘类型选择控件
|
||
self.material_form_layout = QFormLayout()
|
||
self.material_content_layout.addLayout(self.material_form_layout)
|
||
|
||
# 创建堆叠部件
|
||
self.stacked_widget = QStackedWidget()
|
||
self.stacked_widget.addWidget(self.central_widget) # 主页面
|
||
|
||
# 设置中央部件为堆叠部件
|
||
self.setCentralWidget(self.stacked_widget)
|
||
|
||
# # 添加托盘类型选择下拉框
|
||
# self.add_pallet_type_selectors()
|
||
|
||
# 连接信号和槽
|
||
self.connect_signals()
|
||
|
||
# 默认显示主页面
|
||
self.stacked_widget.setCurrentIndex(0)
|
||
|
||
# 配置检验列 - 使用检验配置管理器获取启用的列数和标题
|
||
self.update_inspection_columns()
|
||
|
||
# 设置表格上下文菜单
|
||
self.process_table.setContextMenuPolicy(Qt.CustomContextMenu)
|
||
|
||
# 加载未完成的检验数据
|
||
self._safe_load_data()
|
||
|
||
# 加载已完成检验数据
|
||
self.show_pack_item()
|
||
|
||
# 更新包装记录统计数据
|
||
self.update_package_statistics()
|
||
# 创建状态处理器实例
|
||
self.machine_handlers = MachineStatusHandlers()
|
||
|
||
# 添加状态显示到状态栏
|
||
self.modbus_status_label = QLabel("Modbus: 未连接")
|
||
self.weight_label = QLabel("重量: --")
|
||
self.label_status_label = QLabel("贴标: 无贴标")
|
||
self.error_status_label = QLabel("故障: 无")
|
||
|
||
# 设置样式
|
||
self.error_status_label.setStyleSheet("color: green; font-weight: bold;")
|
||
|
||
# 添加到状态栏
|
||
self.statusBar().addPermanentWidget(self.modbus_status_label)
|
||
self.statusBar().addPermanentWidget(self.weight_label)
|
||
self.statusBar().addPermanentWidget(self.label_status_label)
|
||
self.statusBar().addPermanentWidget(self.error_status_label)
|
||
self.statusBar().addPermanentWidget(QLabel(" "))
|
||
logging.info(f"主窗口已创建,用户: {user_name}")
|
||
|
||
# 初始化串口管理器
|
||
self.serial_manager = SerialManager()
|
||
|
||
# 注册串口数据回调函数
|
||
self.register_serial_callbacks()
|
||
|
||
# 加载托盘号列表
|
||
self.load_pallet_codes()
|
||
|
||
# 恢复开始按钮原始样式
|
||
self.restore_start_button_style()
|
||
|
||
# 恢复上料和下料按钮原始样式
|
||
if hasattr(self, 'restore_input_button_style'):
|
||
self.restore_input_button_style()
|
||
if hasattr(self, 'restore_output_button_style'):
|
||
self.restore_output_button_style()
|
||
|
||
# 启动Modbus监控,确保电力消耗数据在应用启动时就能显示
|
||
self.setup_modbus_monitor()
|
||
|
||
# 更新订单数量和产量统计数据
|
||
self.update_order_statistics()
|
||
|
||
# 加载库房和线材类型数据
|
||
self.load_warehouse_data()
|
||
self.load_wire_type_data()
|
||
|
||
logging.info("主窗口初始化时已启动Modbus监控系统")
|
||
|
||
def get_axios_num(self,tray_id):
|
||
"""获取托盘号对应的轴号"""
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
axios_num = inspection_dao.get_axios_num(tray_id)
|
||
return axios_num
|
||
def get_axios_num_by_order_id(self, order_id):
|
||
"""获取订单号对应的轴号"""
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
axios_num = inspection_dao.get_axios_num_by_order_id(order_id)
|
||
return axios_num
|
||
|
||
def get_current_row_axios_num(self, data_row):
|
||
"""获取当前行的轴号,优先使用用户输入的轴号,否则使用数据库中的轴号+1
|
||
|
||
Args:
|
||
data_row: 数据行索引
|
||
|
||
Returns:
|
||
int: 轴号
|
||
"""
|
||
try:
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 计算贴标列索引 - 贴标位置在检验列之后的第一列
|
||
label_col = 2 + len(enabled_configs)
|
||
|
||
# 获取当前行的贴标值(轴号)
|
||
label_item = self.process_table.item(data_row, label_col)
|
||
if label_item:
|
||
current_axios = label_item.text().strip()
|
||
if current_axios and current_axios.isdigit():
|
||
# 如果当前行有有效的轴号,直接使用
|
||
axios_num = int(current_axios)
|
||
logging.info(f"使用当前行轴号: {axios_num}")
|
||
return axios_num
|
||
|
||
# 如果当前行没有轴号,使用数据库中的轴号+1
|
||
tray_id = self.tray_edit.currentText()
|
||
db_axios_num = self.get_axios_num(tray_id)
|
||
axios_num = db_axios_num + 1
|
||
logging.info(f"当前行无轴号,使用数据库轴号+1: {axios_num}")
|
||
return axios_num
|
||
|
||
except Exception as e:
|
||
logging.error(f"获取当前行轴号失败: {str(e)}")
|
||
# 出错时使用数据库轴号+1作为备选
|
||
tray_id = self.tray_edit.currentText()
|
||
db_axios_num = self.get_axios_num(tray_id)
|
||
return db_axios_num + 1
|
||
def load_config(self):
|
||
"""加载配置文件"""
|
||
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "app_config.json")
|
||
try:
|
||
with open(config_path, 'r', encoding='utf-8') as f:
|
||
config = json.load(f)
|
||
logging.info(f"已加载配置文件: {config_path}")
|
||
return config
|
||
except Exception as e:
|
||
logging.error(f"加载配置文件失败: {e}")
|
||
return {}
|
||
|
||
def connect_signals(self):
|
||
"""连接信号槽"""
|
||
# 连接微丝产线表格单元格变更信号槽
|
||
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
||
|
||
# 连接菜单动作
|
||
self.main_action.triggered.connect(self.show_main_page)
|
||
self.settings_action.triggered.connect(self.show_settings_page)
|
||
|
||
# 工程号输入框回车事件
|
||
self.order_edit.returnPressed.connect(self.handle_order_enter)
|
||
|
||
# 托盘号输入框回车和切换事件,触发未加载数据查询
|
||
# QComboBox没有returnPressed信号,只有currentTextChanged和activated信号
|
||
self.tray_edit.currentTextChanged.connect(self.handle_tray_changed)
|
||
self.tray_edit.activated.connect(self.handle_tray_changed) # 当用户选择一项时触发
|
||
|
||
# 连接按钮事件
|
||
self.input_button.clicked.connect(self.handle_input)
|
||
self.output_button.clicked.connect(self.handle_output)
|
||
self.start_button.clicked.connect(self.handle_start)
|
||
self.stop_button.clicked.connect(self.handle_stop)
|
||
self.delete_row_button.clicked.connect(self.handle_delete_row)
|
||
|
||
# 连接虚拟工程号按钮事件
|
||
self.virtual_order_button.clicked.connect(self.handle_virtual_order)
|
||
|
||
|
||
# 连接托盘完成按钮事件
|
||
self.tray_complete_button.clicked.connect(self.handle_tray_complete)
|
||
|
||
# 设置表格上下文菜单
|
||
self.process_table.setContextMenuPolicy(Qt.CustomContextMenu)
|
||
self.process_table.customContextMenuRequested.connect(self.show_table_context_menu)
|
||
|
||
# 不再需要连接相机信号
|
||
|
||
# 连接报表按钮点击事件
|
||
self.report_button.clicked.connect(self.on_report)
|
||
|
||
# 连接加载反馈信号
|
||
self.loading_feedback_signal.connect(self._handle_loading_feedback_ui)
|
||
|
||
# 连接下料反馈信号
|
||
self.unloading_feedback_signal.connect(self._handle_unloading_feedback_ui)
|
||
|
||
# 连接下料层数和位置UI更新信号
|
||
self.unloading_level_ui_signal.connect(self.handle_unloading_level_ui)
|
||
self.unloading_position_ui_signal.connect(self.handle_unloading_position_ui)
|
||
|
||
# 连接急停信号
|
||
self.emergency_stop_signal.connect(self._handle_emergency_stop_ui)
|
||
|
||
# 连接线径警告信号
|
||
self.diameter_warning_signal.connect(self.show_diameter_warning)
|
||
|
||
# 连接炉号查询按钮信号
|
||
if hasattr(self, 'luno_query_button'):
|
||
self.luno_query_button.clicked.connect(self.handle_luno_query)
|
||
|
||
# 连接新的线径警告弹框信号
|
||
self.diameter_alert_signal.connect(self.show_diameter_alert)
|
||
|
||
# 连接重量警告弹框信号
|
||
self.weight_alert_signal.connect(self.show_weight_alert)
|
||
|
||
def update_inspection_columns(self):
|
||
"""更新检验列配置 - 使用检验配置管理器获取启用的列数和标题"""
|
||
try:
|
||
# 获取已启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 获取启用的列数
|
||
column_count = len(enabled_configs)
|
||
if column_count == 0:
|
||
# 如果没有启用的列,至少显示一列
|
||
column_count = 1
|
||
headers = ["检验项"]
|
||
else:
|
||
# 如果有启用的列,使用配置的标题
|
||
headers = [config['display_name'] for config in enabled_configs]
|
||
|
||
# 设置检验列
|
||
self.set_inspection_columns(column_count, headers)
|
||
logging.info(f"已更新检验列配置:{column_count}列, 标题: {headers}")
|
||
except Exception as e:
|
||
logging.error(f"更新检验列配置失败: {str(e)}")
|
||
# 如果更新失败,使用默认配置
|
||
self.set_inspection_columns(1, ["检验项"])
|
||
|
||
def show_main_page(self):
|
||
self.stacked_widget.setCurrentWidget(self.central_widget)
|
||
|
||
# 更新检验列配置
|
||
self.update_inspection_columns()
|
||
|
||
# 加载未完成的检验数据
|
||
self._safe_load_data()
|
||
|
||
# 加载托盘号列表
|
||
self.load_pallet_codes()
|
||
|
||
# 加载库房数据
|
||
self.load_warehouse_data()
|
||
|
||
# 加载线材类型数据
|
||
self.load_wire_type_data()
|
||
|
||
logging.info("显示主页面")
|
||
|
||
def load_pallet_codes(self):
|
||
"""从托盘类型管理器加载托盘号并更新到tray_edit"""
|
||
try:
|
||
# 获取当前文本,以便保留用户选择
|
||
current_text = self.tray_edit.currentText()
|
||
|
||
# 清空当前项目
|
||
self.tray_edit.clear()
|
||
|
||
# 获取托盘号
|
||
pallet_codes = self.pallet_type_manager.get_pallet_code()
|
||
|
||
if pallet_codes and len(pallet_codes) > 0:
|
||
# 添加托盘号到下拉框
|
||
self.tray_edit.addItems(pallet_codes)
|
||
|
||
# 如果有之前的选择,尝试恢复它
|
||
index = self.tray_edit.findText(current_text)
|
||
if index != -1:
|
||
self.tray_edit.setCurrentIndex(index)
|
||
else:
|
||
self.tray_edit.setCurrentIndex(-1)
|
||
self.tray_edit.setCurrentText("")
|
||
|
||
logging.info(f"已加载托盘号,共 {len(pallet_codes)} 个")
|
||
else:
|
||
# 如果没有托盘号,则不添加任何项目,保持为空
|
||
logging.warning("未找到托盘号,托盘号列表将为空")
|
||
self.tray_edit.setCurrentText("")
|
||
|
||
|
||
except Exception as e:
|
||
logging.error(f"加载托盘号失败: {str(e)}")
|
||
# 如果加载失败,确保下拉框为空
|
||
self.tray_edit.clear()
|
||
self.tray_edit.setCurrentText("")
|
||
|
||
def load_warehouse_data(self):
|
||
"""从API加载库房数据并更新到信息表格的库房组件"""
|
||
try:
|
||
# 获取信息表格中的库房组件
|
||
warehouse_combo = self.info_values.get("库房")
|
||
if not warehouse_combo:
|
||
logging.warning("未找到库房组件")
|
||
return
|
||
|
||
# 获取当前选中的库房,以便保留用户选择
|
||
current_warehouse = warehouse_combo.currentText()
|
||
|
||
# 清空当前项目
|
||
warehouse_combo.clear()
|
||
|
||
# 调用API获取库房数据
|
||
from apis.gc_api import GcApi
|
||
gc_api = GcApi()
|
||
|
||
# 调用get_params接口获取库房信息
|
||
response = gc_api.get_params("库房档案", "XC", self.corp_id)
|
||
|
||
if response.get("status", False):
|
||
warehouse_data = response.get("data", [])
|
||
|
||
if warehouse_data and len(warehouse_data) > 0:
|
||
# 添加库房到下拉框
|
||
for warehouse in warehouse_data:
|
||
warehouse_name = warehouse.get("combtext", "")
|
||
if warehouse_name:
|
||
warehouse_combo.addItem(warehouse_name)
|
||
|
||
# 默认选择成品库房
|
||
default_index = warehouse_combo.findText("成品库")
|
||
if default_index != -1:
|
||
warehouse_combo.setCurrentIndex(default_index)
|
||
elif warehouse_combo.count() > 0:
|
||
# 如果没有找到成品库,选择第一个
|
||
warehouse_combo.setCurrentIndex(0)
|
||
|
||
# 如果有之前的选择,尝试恢复它
|
||
if current_warehouse:
|
||
index = warehouse_combo.findText(current_warehouse)
|
||
if index != -1:
|
||
warehouse_combo.setCurrentIndex(index)
|
||
|
||
logging.info(f"已加载库房数据,共 {len(warehouse_data)} 个")
|
||
else:
|
||
logging.warning("未找到库房数据,库房列表将为空")
|
||
else:
|
||
logging.error(f"获取库房数据失败: {response.get('message', '未知错误')}")
|
||
# 如果API调用失败,添加默认选项
|
||
warehouse_combo.addItem("成品库")
|
||
warehouse_combo.addItem("退回仓")
|
||
warehouse_combo.addItem("散装库")
|
||
warehouse_combo.addItem("不合格库(线材)")
|
||
warehouse_combo.addItem("废丝库")
|
||
warehouse_combo.setCurrentIndex(0)
|
||
|
||
except Exception as e:
|
||
logging.error(f"加载库房数据失败: {str(e)}")
|
||
# 如果加载失败,添加默认选项
|
||
warehouse_combo = self.info_values.get("库房")
|
||
if warehouse_combo:
|
||
warehouse_combo.clear()
|
||
warehouse_combo.addItem("成品库")
|
||
warehouse_combo.addItem("退回仓")
|
||
warehouse_combo.addItem("散装库")
|
||
warehouse_combo.addItem("不合格库(线材)")
|
||
warehouse_combo.addItem("废丝库")
|
||
warehouse_combo.setCurrentIndex(0)
|
||
|
||
def load_wire_type_data(self):
|
||
"""从API加载线材类型数据并更新到信息表格的线材类型组件"""
|
||
try:
|
||
logging.info("开始加载线材类型数据...")
|
||
# 获取信息表格中的线材类型组件
|
||
wire_type_combo = self.info_values.get("线材类型")
|
||
if not wire_type_combo:
|
||
logging.warning("未找到线材类型组件")
|
||
logging.info(f"info_values中的键: {list(self.info_values.keys())}")
|
||
return
|
||
|
||
# 获取当前选中的线材类型,以便保留用户选择
|
||
current_wire_type = wire_type_combo.currentText()
|
||
|
||
# 清空当前项目
|
||
wire_type_combo.clear()
|
||
|
||
# 调用API获取线材类型数据
|
||
from apis.gc_api import GcApi
|
||
gc_api = GcApi()
|
||
|
||
logging.info(f"调用线材类型API,corp_id: {self.corp_id}")
|
||
# 调用get_wire_type_params接口获取线材类型信息
|
||
response = gc_api.get_wire_type_params(self.corp_id)
|
||
logging.info(f"线材类型API响应: {response}")
|
||
|
||
if response.get("status", False):
|
||
wire_type_data = response.get("data", [])
|
||
|
||
if wire_type_data and len(wire_type_data) > 0:
|
||
# 添加线材类型到下拉框
|
||
for wire_type in wire_type_data:
|
||
wire_type_name = wire_type.get("combtext", "")
|
||
if wire_type_name:
|
||
wire_type_combo.addItem(wire_type_name)
|
||
|
||
# 默认选择第一个
|
||
if wire_type_combo.count() > 0:
|
||
wire_type_combo.setCurrentIndex(0)
|
||
|
||
# 如果有之前的选择,尝试恢复它
|
||
if current_wire_type and current_wire_type != "请选择":
|
||
index = wire_type_combo.findText(current_wire_type)
|
||
if index != -1:
|
||
wire_type_combo.setCurrentIndex(index)
|
||
|
||
logging.info(f"已加载线材类型数据,共 {len(wire_type_data)} 个")
|
||
else:
|
||
logging.warning("未找到线材类型数据,线材类型列表将为空")
|
||
else:
|
||
# 如果API返回的不是status格式,直接尝试解析数据
|
||
if isinstance(response, list):
|
||
wire_type_data = response
|
||
logging.info(f"直接解析列表数据,共 {len(wire_type_data)} 个")
|
||
|
||
if wire_type_data and len(wire_type_data) > 0:
|
||
# 添加线材类型到下拉框
|
||
for wire_type in wire_type_data:
|
||
wire_type_name = wire_type.get("combtext", "")
|
||
if wire_type_name:
|
||
wire_type_combo.addItem(wire_type_name)
|
||
|
||
# 默认选择第一个
|
||
if wire_type_combo.count() > 0:
|
||
wire_type_combo.setCurrentIndex(0)
|
||
|
||
# 如果有之前的选择,尝试恢复它
|
||
if current_wire_type and current_wire_type != "请选择":
|
||
index = wire_type_combo.findText(current_wire_type)
|
||
if index != -1:
|
||
wire_type_combo.setCurrentIndex(index)
|
||
|
||
logging.info(f"已加载线材类型数据,共 {len(wire_type_data)} 个")
|
||
else:
|
||
logging.warning("未找到线材类型数据,线材类型列表将为空")
|
||
else:
|
||
logging.error(f"获取线材类型数据失败: {response}")
|
||
# 如果API调用失败,添加默认选项
|
||
wire_type_combo.addItem("经线")
|
||
wire_type_combo.addItem("纬线")
|
||
wire_type_combo.addItem("径线-H")
|
||
wire_type_combo.addItem("径线-J")
|
||
wire_type_combo.setCurrentIndex(0)
|
||
|
||
except Exception as e:
|
||
logging.error(f"加载线材类型数据失败: {str(e)}")
|
||
# 如果加载失败,添加默认选项
|
||
wire_type_combo = self.info_values.get("线材类型")
|
||
if wire_type_combo:
|
||
wire_type_combo.clear()
|
||
wire_type_combo.addItem("经线")
|
||
wire_type_combo.addItem("纬线")
|
||
wire_type_combo.addItem("径线-H")
|
||
wire_type_combo.addItem("径线-J")
|
||
wire_type_combo.setCurrentIndex(0)
|
||
|
||
def show_settings_page(self):
|
||
"""显示设置页面"""
|
||
# 创建设置窗口
|
||
if not hasattr(self, 'settings_window'):
|
||
from widgets.settings_window import SettingsWindow
|
||
self.settings_window = SettingsWindow(self)
|
||
# 连接设置改变信号
|
||
self.settings_window.settings_changed.connect(self.on_settings_changed)
|
||
|
||
# 显示设置窗口
|
||
self.settings_window.show()
|
||
logging.info("显示设置窗口")
|
||
|
||
def on_settings_changed(self):
|
||
"""设置改变时触发"""
|
||
# 重新加载配置
|
||
from utils.config_loader import ConfigLoader
|
||
config_loader = ConfigLoader.get_instance()
|
||
config_loader.load_config()
|
||
self.config = self.load_config() # 重新加载配置到 self.config
|
||
|
||
# 更新串口管理器配置
|
||
self.serial_manager.reload_config()
|
||
|
||
# 重新打开已配置的串口
|
||
self.serial_manager.auto_open_configured_ports()
|
||
|
||
# 重新注册串口回调函数
|
||
self.register_serial_callbacks()
|
||
|
||
# 重新加载托盘号
|
||
self.load_pallet_codes()
|
||
|
||
logging.info("设置已更新,重新加载配置并重新打开串口,已重新注册米电阻、线径和扫码器回调")
|
||
|
||
def handle_input(self):
|
||
"""处理上料按钮点击事件"""
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 启动监听(不论后续是否确认上料)
|
||
# 启动Modbus监控
|
||
if not hasattr(self, 'modbus_monitor') or not self.modbus_monitor.is_running():
|
||
self.setup_modbus_monitor()
|
||
logging.info("已在上料操作前启动Modbus监控")
|
||
|
||
# 启动串口监听
|
||
self.serial_manager.auto_open_configured_ports()
|
||
|
||
# 启动键盘监听器
|
||
self.serial_manager.start_keyboard_listener()
|
||
logging.info("已在上料操作前启动键盘监听器")
|
||
|
||
# 创建上料对话框
|
||
from widgets.loading_dialog_widget import LoadingDialog
|
||
dialog = LoadingDialog(parent=self,user_id=self.user_id,user_name=self.user_name,corp_id=self.corp_id)
|
||
|
||
# 如果已有上料信息,作为参考显示在对话框中,但允许用户修改
|
||
if self._loading_info and self._current_stow_num > 0:
|
||
dialog.order_input.setText(self._loading_info.get('order_code', ''))
|
||
dialog.tray_input.setText(self._loading_info.get('tray_code', ''))
|
||
dialog.axis_value.setText(self._loading_info.get('axis_value', '--'))
|
||
dialog.quantity_value.setText(self._loading_info.get('quantity_value', '--'))
|
||
dialog.weight_value.setText(self._loading_info.get('weight_value', '--'))
|
||
dialog.pallet_tier_value.setText(str(self._current_stow_num))
|
||
# 不禁用输入框,允许用户修改
|
||
|
||
# 连接订单号信号
|
||
dialog.order_code_signal.connect(self.handle_order_code_received)
|
||
|
||
# 显示对话框
|
||
result = dialog.exec()
|
||
|
||
# 如果用户确认,则执行上料操作
|
||
if result == QDialog.Accepted:
|
||
# 从对话框中获取订单号和托盘号,并更新到主窗口
|
||
order_code = dialog.order_input.text().strip()
|
||
tray_code = dialog.tray_input.text().strip()
|
||
self._current_order_code = order_code
|
||
self.tray_edit.setCurrentText(tray_code)
|
||
|
||
# 获取托盘料值作为拆垛层数
|
||
stow_num = dialog.pallet_tier_value.text()
|
||
if stow_num == "--" or not stow_num:
|
||
QMessageBox.warning(self, "错误", "未获取到托盘料信息,请重试")
|
||
return
|
||
|
||
# 始终使用用户最新输入的信息
|
||
self._current_stow_num = int(stow_num)
|
||
# 保存上料信息
|
||
self._loading_info = {
|
||
'order_code': dialog.order_input.text(),
|
||
'tray_code': dialog.tray_input.text(),
|
||
'axis_value': dialog.axis_value.text(),
|
||
'quantity_value': dialog.quantity_value.text(),
|
||
'weight_value': dialog.weight_value.text(),
|
||
}
|
||
|
||
# 执行Modbus操作
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
try:
|
||
# 上料 D0 给到层数,等待点击开始后,进行上料
|
||
success0 = modbus.write_register_until_success(client, 0, self._current_stow_num)
|
||
# 读取D0寄存器值
|
||
current_stow_num = modbus.read_holding_register(client, 0)
|
||
logging.info(f"上料初始化成功:层数 {current_stow_num} 已写入寄存器0")
|
||
if success0:
|
||
# 创建状态标签并显示在右上角
|
||
self.show_operation_status("拆垛层数", "input", str(current_stow_num if current_stow_num else self._current_stow_num))
|
||
else:
|
||
QMessageBox.information(self, "操作提示", "上料失败")
|
||
except Exception as e:
|
||
logging.error(f"上料操作失败: {str(e)}")
|
||
QMessageBox.critical(self, "错误", f"上料操作失败: {str(e)}")
|
||
finally:
|
||
modbus.close_client(client)
|
||
|
||
def handle_output(self):
|
||
"""处理下料按钮点击事件"""
|
||
try:
|
||
# 启动监听(不论后续是否确认下料)
|
||
if not hasattr(self, 'modbus_monitor') or not self.modbus_monitor.is_running():
|
||
self.setup_modbus_monitor()
|
||
self.serial_manager.auto_open_configured_ports()
|
||
self.serial_manager.start_keyboard_listener()
|
||
|
||
dialog = UnloadingDialog(self, self.user_id)
|
||
|
||
# 如果有之前的下料信息,作为参考显示在对话框中,但允许用户修改
|
||
if self._current_unload_info:
|
||
dialog.set_unloading_info(self._current_unload_info)
|
||
logging.info(f"显示之前的下料信息作为参考")
|
||
|
||
if dialog.exec_() == QDialog.Accepted:
|
||
# 获取用户最新输入的下料信息
|
||
unloading_info = dialog.get_unloading_info()
|
||
|
||
# 始终使用用户最新输入的信息
|
||
self._total_unload_num = int(unloading_info.get('tier', '3'))
|
||
self._current_unload_num = int(unloading_info.get('tier', '3')) # 直接使用用户输入的层数,而不是从1开始
|
||
self._current_unload_info = unloading_info
|
||
logging.info(f"下料任务设置:总层数={self._total_unload_num}, 当前层数={self._current_unload_num}")
|
||
|
||
# 将用户输入的层数写入寄存器
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
try:
|
||
modbus.write_register_until_success(client, 4, self._current_unload_num)
|
||
logging.info(f"下料初始化成功:层数 {self._current_unload_num} 已写入寄存器4")
|
||
finally:
|
||
modbus.close_client(client)
|
||
|
||
# 读取D4寄存器值
|
||
current_unload_num = modbus.read_holding_register(client, 4)
|
||
logging.info(f"下料初始化成功:层数 {current_unload_num} 已写入寄存器4")
|
||
|
||
# 统一更新UI显示
|
||
tray_code = self._current_unload_info.get('tray_code', '')
|
||
self.show_operation_status("码垛层数", "output", f"{current_unload_num if current_unload_num else self._current_unload_num}/{current_unload_num if current_unload_num else self._total_unload_num} ")
|
||
else:
|
||
logging.info("下料对话框已取消")
|
||
except Exception as e:
|
||
logging.error(f"处理下料操作失败: {str(e)}")
|
||
QMessageBox.critical(self, "错误", f"处理下料操作失败: {str(e)}")
|
||
|
||
def restore_start_button_style(self):
|
||
"""恢复开始按钮的原始样式"""
|
||
try:
|
||
# 使用与main_window_ui.py中初始化时相同的样式,只恢复背景色
|
||
button_style = """
|
||
QPushButton {
|
||
padding: 8px 16px;
|
||
font-weight: bold;
|
||
border-radius: 4px;
|
||
border: 1px solid #4caf50;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #d7eeda;
|
||
}
|
||
"""
|
||
self.start_button.setStyleSheet(button_style)
|
||
logging.info("已恢复开始按钮原始样式")
|
||
except Exception as e:
|
||
logging.error(f"恢复开始按钮样式失败: {str(e)}")
|
||
|
||
def fill_start_button_style(self):
|
||
"""填充开始按钮样式 - 绿色背景,白色字体"""
|
||
try:
|
||
# 使用与main_window_ui.py中初始化时相同的样式,只改变背景色和文字颜色
|
||
button_style = """
|
||
QPushButton {
|
||
padding: 8px 16px;
|
||
font-weight: bold;
|
||
border-radius: 4px;
|
||
background-color: #4caf50;
|
||
color: white;
|
||
border: 1px solid #4caf50;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #45a049;
|
||
color: white;
|
||
}
|
||
"""
|
||
self.start_button.setStyleSheet(button_style)
|
||
logging.info("已填充开始按钮样式")
|
||
except Exception as e:
|
||
logging.error(f"填充开始按钮样式失败: {str(e)}")
|
||
|
||
def handle_start(self):
|
||
"""
|
||
处理开始按钮点击事件
|
||
根据当前操作类型(上料/下料)写入相应的寄存器
|
||
- 上料: 将当前层数写入D0寄存器,并将D2寄存器设置为1
|
||
- 下料: 确保D3寄存器设置为1,D4寄存器已包含当前下料层数
|
||
"""
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
try:
|
||
# 判断当前操作类型(通过检查当前下料信息是否存在)
|
||
# 下料模式 - 开始下料操作
|
||
# 确保寄存器3(下料启动)设为1,寄存器4已在handle_output中设置了当前层数
|
||
success2 = modbus.write_register_until_success(client, 2, 1)
|
||
success3 = modbus.write_register_until_success(client, 3, 1)
|
||
|
||
if success2 and success3:
|
||
logging.info(f"开始下料操作:当前层数 {self._current_unload_num}/{self._total_unload_num}")
|
||
QMessageBox.information(self, "操作提示", f"开始下料操作:当前第{self._current_unload_num}层")
|
||
else:
|
||
QMessageBox.warning(self, "错误", "开始下料操作失败")
|
||
|
||
except Exception as e:
|
||
logging.error(f"开始操作失败: {str(e)}")
|
||
QMessageBox.critical(self, "错误", f"开始操作失败: {str(e)}")
|
||
finally:
|
||
modbus.close_client(client)
|
||
|
||
def handle_stop(self):
|
||
"""处理停止按钮点击事件,根据当前操作类型(上料/下料)停止相应的操作并关闭modbus监控"""
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
try:
|
||
# 判断当前操作类型(通过检查当前下料信息是否存在)
|
||
if self._current_unload_info and self._current_unload_num > 0:
|
||
# 下料模式 - 停止下料操作
|
||
success3 = modbus.write_register_until_success(client, 3, 0)
|
||
|
||
if success3:
|
||
logging.info(f"停止下料操作:当前层数 {self._current_unload_num}/{self._total_unload_num}")
|
||
QMessageBox.information(self, "操作提示", "已停止下料操作")
|
||
# 恢复开始按钮和下料按钮原始样式
|
||
self.restore_start_button_style()
|
||
self.restore_output_button_style()
|
||
else:
|
||
QMessageBox.warning(self, "错误", "停止下料操作失败")
|
||
else:
|
||
# 上料模式 - 停止上料操作
|
||
success2 = modbus.write_register_until_success(client, 2, 0)
|
||
|
||
if success2:
|
||
self._is_loading_active = False # 标记上料任务已停止
|
||
logging.info("停止上料操作")
|
||
QMessageBox.information(self, "操作提示", "已停止上料操作")
|
||
# 恢复开始按钮和上料按钮原始样式
|
||
self.restore_start_button_style()
|
||
self.restore_input_button_style()
|
||
else:
|
||
QMessageBox.warning(self, "错误", "停止上料操作失败")
|
||
|
||
except Exception as e:
|
||
logging.error(f"停止操作失败: {str(e)}")
|
||
QMessageBox.critical(self, "错误", f"停止操作失败: {str(e)}")
|
||
finally:
|
||
modbus.close_client(client)
|
||
# 停止Modbus监控
|
||
if hasattr(self, 'modbus_monitor'):
|
||
logging.info("停止Modbus监控")
|
||
self.modbus_monitor.stop()
|
||
# 停止串口监听
|
||
self.serial_manager.stop_keyboard_listener()
|
||
self.serial_manager.close_all_ports()
|
||
|
||
|
||
def handle_camera_status(self, is_connected, message):
|
||
"""相机状态处理的空方法,保留是为了兼容性"""
|
||
pass
|
||
|
||
def handle_camera_connection(self, is_connected, message):
|
||
"""相机连接处理的空方法,保留是为了兼容性"""
|
||
pass
|
||
|
||
def handle_camera_params_changed(self, exposure_time, gain, frame_rate):
|
||
"""相机参数变更处理的空方法,保留是为了兼容性"""
|
||
pass
|
||
|
||
def handle_camera_error(self, error_msg):
|
||
"""相机错误处理的空方法,保留是为了兼容性"""
|
||
pass
|
||
|
||
def closeEvent(self, event):
|
||
"""窗口关闭事件"""
|
||
# 停止Modbus监控
|
||
if hasattr(self, 'modbus_monitor'):
|
||
logging.info("停止Modbus监控")
|
||
self.modbus_monitor.stop()
|
||
|
||
# 停止串口监听
|
||
self.serial_manager.stop_keyboard_listener()
|
||
self.serial_manager.close_all_ports()
|
||
|
||
# 接受关闭事件
|
||
event.accept()
|
||
|
||
|
||
def handle_order_enter(self):
|
||
"""处理工程号输入框按下回车事件"""
|
||
logging.info("工程号输入框按下回车事件")
|
||
# 获取当前输入的工程号
|
||
gc_note = self.order_edit.text().strip()
|
||
if gc_note:
|
||
# 防抖机制:记录上次处理的工程号和时间
|
||
current_time = time.time()
|
||
last_order_time = getattr(self, '_last_order_time', 0)
|
||
last_order_gc = getattr(self, '_last_order_gc', '')
|
||
|
||
# 如果是相同的工程号且间隔小于1秒,则忽略此次处理
|
||
if last_order_gc == gc_note and current_time - last_order_time < 1.0:
|
||
logging.info(f"忽略重复处理工程号: {gc_note},间隔太短")
|
||
return
|
||
|
||
# 更新最后处理的工程号和时间
|
||
self._last_order_gc = gc_note
|
||
self._last_order_time = current_time
|
||
|
||
# 清除最近处理的线径工程号,确保新扫码的工程号不会使用之前的线径数据
|
||
if hasattr(self, '_last_processed_gc_note'):
|
||
logging.info(f"清除最近处理线径数据的工程号: {self._last_processed_gc_note}")
|
||
self._last_processed_gc_note = None
|
||
|
||
logging.info(f"输入的工程号: {gc_note}")
|
||
#判断是否是接口,如果不是接口直接添加如果是则走接口
|
||
# 如果开启接口模式,则需要调用接口同步到业务库
|
||
if AppMode.is_api():
|
||
from dao.inspection_dao import InspectionDAO
|
||
from apis.gc_api import GcApi
|
||
inspection_dao = InspectionDAO()
|
||
# 调用接口
|
||
gc_api = GcApi()
|
||
response = gc_api.get_gc_info(gc_note)
|
||
if response.get("status", False):
|
||
gc_info = response.get("data", {})
|
||
self._current_gc_qd = gc_info.get("qd","")
|
||
self._current_gc_sc_gch = gc_info.get("sc_gch", "") # 保存sc_gch字段
|
||
# 先获取当前 info_table 已有的数据
|
||
order_info = {}
|
||
for field_name, label in self.info_values.items():
|
||
# 过滤掉线径公差字段,因为线径数据是通过线径仪获取的,不需要从前端托盘入库地方获取
|
||
if field_name == "线径公差":
|
||
logging.debug(f"跳过线径公差字段,该字段由线径仪获取")
|
||
continue
|
||
|
||
order_info_key = self.FIELD_MAPPING.get(field_name)
|
||
if order_info_key and label is not None:
|
||
try:
|
||
# 根据控件类型选择合适的方法获取文本
|
||
if isinstance(label, QTextEdit):
|
||
order_info[order_info_key] = label.toPlainText()
|
||
elif isinstance(label, QComboBox):
|
||
order_info[order_info_key] = label.currentText()
|
||
elif field_name == "炉号":
|
||
# 炉号字段是容器,需要找到其中的QLineEdit
|
||
luno_container = label
|
||
if luno_container:
|
||
for child in luno_container.children():
|
||
if hasattr(child, 'text') and hasattr(child, 'setText'):
|
||
order_info[order_info_key] = child.text()
|
||
break
|
||
else:
|
||
# 如果没有找到子组件,设置为空字符串
|
||
order_info[order_info_key] = ""
|
||
else:
|
||
order_info[order_info_key] = ""
|
||
elif hasattr(label, 'text'):
|
||
# 其他有text方法的控件
|
||
order_info[order_info_key] = label.text()
|
||
else:
|
||
# 对于没有text方法的控件,使用空字符串
|
||
order_info[order_info_key] = ""
|
||
except RuntimeError as e:
|
||
# 如果对象已被删除,记录错误并跳过
|
||
logging.warning(f"控件对象已被删除,字段: {field_name}, 错误: {str(e)}")
|
||
continue
|
||
except Exception as e:
|
||
# 其他异常也记录并跳过
|
||
logging.warning(f"获取控件文本失败,字段: {field_name}, 错误: {str(e)}")
|
||
continue
|
||
# 更新/补充 qd 字段
|
||
order_info["qd"] = self._current_gc_qd
|
||
# 再调用 update_info_table
|
||
self.update_info_table(order_info)
|
||
self.add_new_inspection_row(gc_note, self._current_order_code)
|
||
else:
|
||
# 直接添加新行
|
||
self.add_new_inspection_row(gc_note, self._current_order_code)
|
||
|
||
# 清空工程号输入框
|
||
self.order_edit.clear()
|
||
|
||
# 将光标重新定位到工程号输入框
|
||
self.order_edit.setFocus()
|
||
|
||
# 更新订单数量和产量统计数据
|
||
self.update_order_statistics()
|
||
|
||
# 确保完整加载所有检验数据,包括线径数据
|
||
self._safe_load_data()
|
||
else:
|
||
logging.warning("工程号为空,忽略处理")
|
||
|
||
def handle_virtual_order(self):
|
||
"""处理虚拟工程号按钮点击事件"""
|
||
try:
|
||
# 获取当前订单代码
|
||
order_code = self._current_order_code
|
||
if not order_code:
|
||
QMessageBox.warning(self, "提示", "请先选择订单")
|
||
return
|
||
|
||
# 生成基于时间戳的虚拟工程号
|
||
import datetime
|
||
timestamp = datetime.datetime.now()
|
||
virtual_gc_note = f"{timestamp.strftime('%Y%m%d_%H%M%S')}"
|
||
|
||
# 直接添加虚拟工程号到微丝产线表格,跳过接口调用
|
||
self.add_new_inspection_row(virtual_gc_note, order_code)
|
||
|
||
# 清空工程号输入框
|
||
self.order_edit.clear()
|
||
|
||
# 将光标重新定位到工程号输入框
|
||
self.order_edit.setFocus()
|
||
|
||
logging.info(f"已生成虚拟工程号并直接添加: {virtual_gc_note}")
|
||
|
||
# 确保完整加载所有检验数据,包括线径数据
|
||
self._safe_load_data()
|
||
|
||
except Exception as e:
|
||
logging.error(f"生成虚拟工程号失败: {str(e)}")
|
||
QMessageBox.critical(self, "错误", f"生成虚拟工程号失败: {str(e)}")
|
||
|
||
|
||
def add_new_inspection_row(self, gc_note, order_code):
|
||
"""在微丝产线表格中添加一条新记录,添加到表格末尾
|
||
|
||
Args:
|
||
gc_note: 工程号
|
||
order_info: 从接口获取的工程号信息
|
||
"""
|
||
# 设置当前正在处理的工程号,确保它在重新加载时显示在最后
|
||
self._current_gc_note = gc_note
|
||
# 记录添加时间,用于确保最新添加的工程号在最后
|
||
self._current_gc_note_timestamp = time.time()
|
||
logging.info(f"设置最新扫码的工程号: {gc_note}, 时间戳: {self._current_gc_note_timestamp}")
|
||
|
||
try:
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 断开单元格变更信号,避免加载过程中触发保存
|
||
try:
|
||
self.process_table.cellChanged.disconnect(self.handle_inspection_cell_changed)
|
||
except:
|
||
pass
|
||
|
||
# 计算新行的行索引(添加到末尾)
|
||
data_start_row = self.process_table.rowCount()
|
||
|
||
# 在末尾添加新行
|
||
self.process_table.insertRow(data_start_row)
|
||
|
||
# 计算新行的序号(最后一个序号+1)
|
||
new_seq = 1 # 默认为1
|
||
if data_start_row > 2: # 如果有其他数据行
|
||
prev_seq_item = self.process_table.item(data_start_row - 1, 0)
|
||
if prev_seq_item:
|
||
try:
|
||
prev_seq = int(prev_seq_item.text())
|
||
new_seq = prev_seq + 1
|
||
except ValueError:
|
||
new_seq = data_start_row - 1 # 备选方案:使用行索引作为序号
|
||
|
||
# 添加工程号到表格的第二列
|
||
item = QTableWidgetItem(gc_note)
|
||
item.setTextAlignment(Qt.AlignCenter)
|
||
self.process_table.setItem(data_start_row, 1, item)
|
||
|
||
# 添加序号到表格的第一列
|
||
item = QTableWidgetItem(str(new_seq))
|
||
item.setTextAlignment(Qt.AlignCenter)
|
||
self.process_table.setItem(data_start_row, 0, item)
|
||
|
||
# 获取订单信息
|
||
order_info = self.inspection_manager.get_order_info(order_code)
|
||
|
||
# 检验列设置为可编辑状态
|
||
for i, config in enumerate(enabled_configs):
|
||
col_index = 2 + i # 检验列从第3列开始
|
||
|
||
# 创建单元格
|
||
item = QTableWidgetItem("")
|
||
item.setTextAlignment(Qt.AlignCenter)
|
||
|
||
# 设置单元格属性以标识其关联的检验项
|
||
item.setData(Qt.UserRole, config.get('id'))
|
||
self.process_table.setItem(data_start_row, col_index, item)
|
||
|
||
# 包装列设置为可编辑状态
|
||
packaging_start_col = 2 + len(enabled_configs)
|
||
for i in range(2): # 贴标和称重
|
||
col_index = packaging_start_col + i
|
||
item = QTableWidgetItem("")
|
||
item.setTextAlignment(Qt.AlignCenter)
|
||
self.process_table.setItem(data_start_row, col_index, item)
|
||
|
||
# 设置表格为可编辑状态
|
||
self.process_table.setEditTriggers(QTableWidget.DoubleClicked | QTableWidget.EditKeyPressed)
|
||
|
||
# 重新连接单元格内容变更信号
|
||
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
||
|
||
# 选中新添加的行
|
||
self.process_table.selectRow(data_start_row)
|
||
|
||
# 将工程号和托盘号保存到数据库,确保能够正确关联
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 为每个检验位置创建一个空记录,确保工程号在数据库中存在
|
||
# 只为没有自动填充值的配置创建空记录
|
||
for config in enabled_configs:
|
||
config_name = config.get('name')
|
||
# 如果order_info中没有对应的键,或者order_info为None
|
||
if not order_info or config_name not in order_info:
|
||
data = [{
|
||
'position': config.get('position'),
|
||
'config_id': config.get('id'),
|
||
'value': '',
|
||
'status': 'init', # 设置初始状态
|
||
'remark': '',
|
||
'tray_id': tray_id
|
||
}]
|
||
inspection_dao.save_inspection_data(self._current_order_code,gc_note, data)
|
||
|
||
# 为贴标和称重也创建空记录
|
||
for position in [11, 12, 13]: # 11是贴标,12是毛重,13是净重
|
||
data = [{
|
||
'position': position,
|
||
'config_id': position,
|
||
'value': '',
|
||
'status': 'init', # 设置初始状态
|
||
'remark': '',
|
||
'tray_id': tray_id
|
||
}]
|
||
inspection_dao.save_inspection_data(self._current_order_code,gc_note, data)
|
||
|
||
# 初始化产品状态为init
|
||
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'init')
|
||
logging.info(f"已添加工程号 {gc_note} 的新记录,显示在第{new_seq}条,初始状态为init")
|
||
|
||
except Exception as e:
|
||
logging.error(f"添加新记录失败: {str(e)}")
|
||
QMessageBox.warning(self, "添加失败", f"添加新记录失败: {str(e)}")
|
||
finally:
|
||
# 重新加载数据,确保UI显示正确
|
||
self._safe_load_data()
|
||
# 清除当前工程号标记和时间戳
|
||
self._current_gc_note = None
|
||
self._current_gc_note_timestamp = None
|
||
|
||
|
||
def handle_inspection_cell_changed(self, row, column):
|
||
"""处理微丝包装单元格内容变更
|
||
|
||
Args:
|
||
row: 行索引
|
||
column: 列索引
|
||
"""
|
||
# 创建唯一键,包含行、列、工程号
|
||
try:
|
||
# 获取工程号,用于创建更精确的唯一键
|
||
order_item = self.process_table.item(row, 1)
|
||
gc_note = order_item.text().strip() if order_item else ""
|
||
|
||
# 创建包含行、列、工程号的唯一键
|
||
cell_key = f"{row}_{column}_{gc_note}"
|
||
current_time = time.time()
|
||
|
||
# 获取上次处理时间
|
||
last_process_times = getattr(self, '_last_cell_process_times', {})
|
||
last_process_time = last_process_times.get(cell_key, 0)
|
||
|
||
# 如果同一单元格在1秒内被处理过,则忽略
|
||
if current_time - last_process_time < 1.0:
|
||
logging.info(f"防抖:跳过重复处理单元格 [{row}, {column}], 工程号={gc_note},间隔小于1秒")
|
||
return
|
||
|
||
# 更新处理时间
|
||
if not hasattr(self, '_last_cell_process_times'):
|
||
self._last_cell_process_times = {}
|
||
self._last_cell_process_times[cell_key] = current_time
|
||
|
||
# 只处理数据行的检验列变更
|
||
if row < 2: # 忽略表头行
|
||
return
|
||
|
||
# 忽略首尾两列(序号和工程号)
|
||
if column < 2:
|
||
return
|
||
|
||
# 获取工程号
|
||
if not gc_note:
|
||
return
|
||
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 判断是否是检验列(非包装列)
|
||
packaging_start_col = 2 + len(enabled_configs)
|
||
|
||
# 获取单元格内容
|
||
cell_item = self.process_table.item(row, column)
|
||
if not cell_item:
|
||
return
|
||
|
||
value = cell_item.text().strip()
|
||
|
||
# 默认设置为通过状态
|
||
status = 'pass'
|
||
|
||
# 记录当前正在处理的数据类型,用于日志输出
|
||
data_type = "检验"
|
||
|
||
if column >= 2 and column < packaging_start_col:
|
||
# 是检验列
|
||
config_index = column - 2
|
||
if config_index < len(enabled_configs):
|
||
config = enabled_configs[config_index]
|
||
data_type = config['display_name']
|
||
|
||
# 显示临时状态消息
|
||
self.statusBar().showMessage(f"正在保存检验数据: {data_type}={value}", 1000)
|
||
|
||
# 验证数据有效性
|
||
# 设置单元格颜色为浅绿色,表示已填写
|
||
cell_item.setBackground(QBrush(QColor("#c8e6c9")))
|
||
|
||
# 保持当前状态不变,由状态管理逻辑处理
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
status = inspection_dao.get_product_status(self._current_order_code, gc_note, tray_id)
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, config['position'], config['id'], value, status)
|
||
|
||
# 判断是否是包装列
|
||
elif column == packaging_start_col:
|
||
# 贴标列
|
||
data_type = "贴标"
|
||
self.statusBar().showMessage(f"正在保存贴标数据: {value}", 1000)
|
||
# 设置单元格颜色为通过
|
||
cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
|
||
# 保存贴标数据,position和config_id都是11
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, 11, 11, value, status)
|
||
|
||
elif column == packaging_start_col + 1:
|
||
# 毛重列
|
||
data_type = "毛重"
|
||
self.statusBar().showMessage(f"正在保存称重数据: {value}", 1000)
|
||
# 设置单元格颜色为通过
|
||
cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
|
||
# 保存毛重数据,position和config_id都是12
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, 12, 12, value, status)
|
||
elif column == packaging_start_col + 2:
|
||
# 净重列
|
||
data_type = "净重"
|
||
self.statusBar().showMessage(f"正在保存净重数据: {value}", 1000)
|
||
# 设置单元格颜色为通过
|
||
cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
|
||
# 保存净重数据,position和config_id都是13
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, 13, 13, value, status)
|
||
|
||
# 记录详细日志
|
||
logging.info(f"处理单元格变更: 行={row}, 列={column}, 类型={data_type}, 工程号={gc_note}, 值={value}, 状态={status}")
|
||
|
||
# 检查是否完成检验并更新状态
|
||
self.check_inspection_completed(row)
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理检验单元格变更失败: {str(e)}")
|
||
self.statusBar().showMessage(f"处理检验数据失败: {str(e)[:50]}...", 3000)
|
||
finally:
|
||
# 延迟一段时间后再触发查询,避免频繁刷新UI
|
||
# 但要避免在加载过程中触发新的加载
|
||
if not self._loading_data_in_progress:
|
||
# 使用类变量保存定时器,避免创建多个定时器
|
||
if hasattr(self, '_load_data_timer') and self._load_data_timer is not None:
|
||
self._load_data_timer.stop()
|
||
|
||
self._load_data_timer = QTimer()
|
||
self._load_data_timer.setSingleShot(True)
|
||
self._load_data_timer.timeout.connect(self._safe_load_data)
|
||
self._load_data_timer.start(1000)
|
||
|
||
|
||
|
||
def save_inspection_data(self, order_id, gc_note, tray_id, position, config_id, value, status):
|
||
"""保存检验数据到数据库
|
||
|
||
Args:
|
||
order_id: 订单号
|
||
gc_note: 工程号
|
||
position: 位置序号
|
||
config_id: 配置ID
|
||
value: 检验值
|
||
status: 状态
|
||
"""
|
||
# 防抖机制:记录上次保存的数据和时间
|
||
current_time = time.time()
|
||
save_key = f"{gc_note}_{position}_{config_id}_{value}"
|
||
last_save_time = getattr(self, '_last_save_time', 0)
|
||
last_save_key = getattr(self, '_last_save_key', '')
|
||
|
||
# 如果是相同的数据且间隔小于0.5秒,则忽略此次保存
|
||
if last_save_key == save_key and current_time - last_save_time < 0.5:
|
||
logging.info(f"防抖机制:跳过保存相同数据 {save_key},间隔小于0.5秒")
|
||
return
|
||
|
||
# 更新最后保存的数据和时间
|
||
self._last_save_key = save_key
|
||
self._last_save_time = current_time
|
||
|
||
# 检查是否已存在相同记录
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
existing_data = inspection_dao.get_inspection_data_by_config(order_id, gc_note, tray_id, position, config_id)
|
||
if existing_data and str(existing_data.get('value')) == str(value):
|
||
logging.info(f"幂等处理:跳过保存已存在的检验数据 {gc_note}_{position}_{config_id}={value}")
|
||
return
|
||
|
||
try:
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
|
||
# 获取当前产品状态,优先使用产品状态管理中的状态
|
||
current_status = inspection_dao.get_product_status(order_id, gc_note, tray_id)
|
||
|
||
# 如果当前状态不是初始状态,则使用当前状态而不是传入的status
|
||
if current_status not in ['', 'init']:
|
||
status = current_status
|
||
|
||
# 记录保存前的详细日志
|
||
logging.info(f"正在保存检验数据: 工程号={gc_note}, 托盘号={tray_id}, 位置={position}, 配置ID={config_id}, 值={value}, 状态={status}")
|
||
|
||
# 构建数据
|
||
data = [{
|
||
'position': position,
|
||
'config_id': config_id,
|
||
'value': value,
|
||
'status': status,
|
||
'remark': '',
|
||
'tray_id': tray_id
|
||
}]
|
||
|
||
# 保存到数据库
|
||
inspection_dao.save_inspection_data(order_id, gc_note, data)
|
||
|
||
except Exception as e:
|
||
logging.error(f"保存检验数据失败: {str(e)}")
|
||
# 显示错误消息
|
||
QMessageBox.warning(self, "保存失败", f"保存检验数据错误: {str(e)[:50]}...")
|
||
self.statusBar().showMessage(f"保存检验数据错误: {str(e)[:50]}...", 3000)
|
||
|
||
def _safe_load_data(self):
|
||
"""安全地加载数据,避免循环调用"""
|
||
# 获取当前托盘号,用于日志记录
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 检查是否有加载操作正在进行
|
||
if getattr(self, '_loading_data_in_progress', False):
|
||
logging.warning(f"已有数据加载操作正在进行,忽略此次请求 (托盘号: {tray_id})")
|
||
return
|
||
|
||
# 检查是否有递归调用
|
||
call_stack_depth = getattr(self, '_safe_load_call_depth', 0) + 1
|
||
self._safe_load_call_depth = call_stack_depth
|
||
|
||
# 如果调用栈深度超过3,可能存在递归调用
|
||
if call_stack_depth > 3:
|
||
logging.warning(f"检测到可能的递归调用 _safe_load_data,调用栈深度: {call_stack_depth},忽略此次请求")
|
||
self._safe_load_call_depth -= 1
|
||
return
|
||
|
||
try:
|
||
# 防抖机制:记录上次加载的时间
|
||
current_time = time.time()
|
||
last_safe_load_time = getattr(self, '_last_safe_load_time', 0)
|
||
|
||
# 如果间隔小于0.5秒,则忽略此次加载
|
||
if current_time - last_safe_load_time < 0.5:
|
||
logging.debug(f"_safe_load_data 被频繁调用,忽略此次请求 (托盘号: {tray_id})")
|
||
return
|
||
|
||
# 更新最后加载的时间
|
||
self._last_safe_load_time = current_time
|
||
|
||
# 检查是否已有定时器在运行
|
||
if hasattr(self, '_safe_load_timer') and self._safe_load_timer is not None:
|
||
self._safe_load_timer.stop()
|
||
self._safe_load_timer = None
|
||
logging.debug("已停止之前的加载定时器")
|
||
|
||
# 创建新的定时器,延迟执行实际加载操作
|
||
self._safe_load_timer = QTimer()
|
||
self._safe_load_timer.setSingleShot(True)
|
||
self._safe_load_timer.timeout.connect(self._do_safe_load)
|
||
self._safe_load_timer.start(500) # 500毫秒后执行
|
||
finally:
|
||
# 减少调用栈深度计数
|
||
self._safe_load_call_depth -= 1
|
||
|
||
def _do_safe_load(self):
|
||
"""实际执行数据加载的方法,由定时器触发"""
|
||
# 获取当前托盘号,用于日志记录
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
if self._loading_data_in_progress:
|
||
# 如果已经在加载数据,不要再次触发
|
||
logging.debug(f"已有数据加载正在进行,忽略此次请求 (托盘号: {tray_id})")
|
||
return
|
||
|
||
try:
|
||
self._loading_data_in_progress = True
|
||
self.load_finished_inspection_data()
|
||
logging.info(f"数据加载完成,托盘号: {tray_id}")
|
||
|
||
# 加载完成后显示包装记录
|
||
try:
|
||
self.show_pack_item()
|
||
logging.info(f"在_do_safe_load中调用show_pack_item, 托盘号: {tray_id}")
|
||
except Exception as e:
|
||
logging.error(f"在_do_safe_load中调用show_pack_item失败: {str(e)}")
|
||
except Exception as e:
|
||
logging.error(f"安全加载数据失败: {str(e)}, 托盘号: {tray_id}")
|
||
# 即使加载失败,也尝试显示包装记录
|
||
try:
|
||
self.show_pack_item()
|
||
logging.info(f"加载失败后尝试显示包装记录, 托盘号: {tray_id}")
|
||
except Exception as ex:
|
||
logging.error(f"加载失败后显示包装记录失败: {str(ex)}, 托盘号: {tray_id}")
|
||
finally:
|
||
self._loading_data_in_progress = False
|
||
# 清理定时器引用
|
||
self._safe_load_timer = None
|
||
|
||
def load_finished_inspection_data(self):
|
||
"""加载未完成的检验数据并显示在表格中"""
|
||
# 注意:此方法通常应通过_safe_load_data调用,以防止循环
|
||
|
||
# 添加表格更新锁,避免并发更新
|
||
if hasattr(self, '_table_updating') and self._table_updating:
|
||
logging.info("表格正在更新中,忽略此次加载请求")
|
||
return
|
||
|
||
self._table_updating = True
|
||
|
||
# 记录信号连接状态
|
||
signal_was_connected = False
|
||
|
||
try:
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 防止短时间内重复加载相同数据
|
||
current_time = time.time()
|
||
last_load_time = getattr(self, '_last_load_time', 0)
|
||
last_load_tray = getattr(self, '_last_load_tray', '')
|
||
|
||
# 如果是同一托盘且间隔小于1秒,则忽略此次加载
|
||
if last_load_tray == tray_id and current_time - last_load_time < 1.0:
|
||
logging.debug(f"忽略重复加载托盘 {tray_id} 的数据,间隔太短")
|
||
return
|
||
|
||
# 更新最后加载的托盘和时间
|
||
self._last_load_tray = tray_id
|
||
self._last_load_time = current_time
|
||
|
||
# 使用InspectionDAO获取未完成的检验数据
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
|
||
# 使用get_inspection_data_unfinished获取未完成的数据
|
||
unfinished_data = inspection_dao.get_inspection_data_unfinished(tray_id)
|
||
|
||
# 检查信号是否已连接,并断开单元格变更信号
|
||
try:
|
||
signal_was_connected = self.process_table.receivers(self.process_table.cellChanged) > 0
|
||
if signal_was_connected:
|
||
self.process_table.cellChanged.disconnect(self.handle_inspection_cell_changed)
|
||
logging.debug("已断开单元格变更信号")
|
||
except Exception as e:
|
||
logging.debug(f"断开单元格变更信号失败: {str(e)}")
|
||
signal_was_connected = False
|
||
|
||
# 清空表格现有数据行,只保留表头
|
||
while self.process_table.rowCount() > 2:
|
||
self.process_table.removeRow(2)
|
||
|
||
if not unfinished_data:
|
||
logging.info(f"托盘号 {tray_id} 没有未完成的检验数据")
|
||
# 确保表格完全清空,只保留表头行
|
||
self.process_table.setRowCount(2) # 只保留表头的两行
|
||
|
||
# 只有在之前信号已连接的情况下才重新连接
|
||
if signal_was_connected:
|
||
try:
|
||
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
||
logging.debug("已重新连接单元格变更信号")
|
||
except Exception as e:
|
||
logging.warning(f"重新连接单元格变更信号失败: {str(e)}")
|
||
|
||
# 加载包装记录
|
||
return
|
||
|
||
logging.info(f"已加载未完成的检验数据,共 {len(unfinished_data)} 条记录")
|
||
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 按工程号分组
|
||
orders_data = {}
|
||
for data in unfinished_data:
|
||
gc_note = data['gc_note']
|
||
if gc_note not in orders_data:
|
||
orders_data[gc_note] = []
|
||
orders_data[gc_note].append(data)
|
||
|
||
# 添加数据到表格 - 从第3行开始添加数据
|
||
row_idx = 2
|
||
|
||
# 使用DAO方法按创建时间排序工程号,确保FIFO顺序(最早创建的在最前面)
|
||
# 但是新添加的工程号应该始终显示在最后
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
|
||
# 获取当前正在处理的工程号(如果有的话)
|
||
current_gc_note = getattr(self, '_current_gc_note', None)
|
||
current_timestamp = getattr(self, '_current_gc_note_timestamp', None)
|
||
|
||
# 按创建时间排序,但确保新添加的工程号在最后
|
||
sorted_gc_notes = inspection_dao.get_orders_by_create_time(list(orders_data.keys()))
|
||
|
||
# 如果有当前正在处理的工程号,将其移到最后
|
||
if current_gc_note and current_gc_note in sorted_gc_notes and current_timestamp:
|
||
logging.info(f"将当前处理的工程号 {current_gc_note} 移到最后")
|
||
sorted_gc_notes.remove(current_gc_note)
|
||
sorted_gc_notes.append(current_gc_note)
|
||
|
||
logging.info(f"按创建时间排序后的工程号列表: {sorted_gc_notes}")
|
||
|
||
# 如果没有当前正在处理的工程号,记录日志
|
||
if not current_gc_note:
|
||
logging.info("没有当前正在处理的工程号")
|
||
|
||
# 收集所有要设置的单元格,批量设置
|
||
cells_to_set = []
|
||
|
||
# 按排序后的工程号顺序添加数据
|
||
for gc_note in sorted_gc_notes:
|
||
# 添加工程号到表格的第二列
|
||
item = QTableWidgetItem(gc_note)
|
||
item.setTextAlignment(Qt.AlignCenter)
|
||
self.process_table.insertRow(row_idx)
|
||
self.process_table.setItem(row_idx, 1, item)
|
||
|
||
# 添加序号到表格的第一列
|
||
item = QTableWidgetItem(str(row_idx - 1)) # 序号从1开始
|
||
item.setTextAlignment(Qt.AlignCenter)
|
||
self.process_table.setItem(row_idx, 0, item)
|
||
|
||
# 获取该工程号的所有数据
|
||
data_list = orders_data[gc_note]
|
||
|
||
# 按位置分组,确保每个位置只有一条数据(取最新的)
|
||
position_data = {}
|
||
for data in data_list:
|
||
position = data['position']
|
||
if position not in position_data or data.get('update_time', 0) > position_data[position].get('update_time', 0):
|
||
position_data[position] = data
|
||
|
||
# 添加检验数据到表格
|
||
for position, data in position_data.items():
|
||
value = data['value']
|
||
config_id = data['config_id']
|
||
|
||
# 找到对应的列索引
|
||
col_idx = None
|
||
|
||
# 处理检验列(position 1-10)
|
||
if 1 <= position <= 10:
|
||
for i, config in enumerate(enabled_configs):
|
||
if config['id'] == config_id:
|
||
col_idx = 2 + i
|
||
break
|
||
|
||
# 添加贴标(11)和称重数据(12)
|
||
if position == 11: # 贴标
|
||
# 贴标列索引 = 2(序号和工程号) + 检验列数
|
||
label_col = 2 + len(enabled_configs)
|
||
self.process_table.setItem(row_idx, label_col, QTableWidgetItem(str(value)))
|
||
elif position == 12: # 称重
|
||
# 称重列索引 = 2(序号和工程号) + 检验列数 + 1(贴标)
|
||
weight_col = 2 + len(enabled_configs) + 1
|
||
self.process_table.setItem(row_idx, weight_col, QTableWidgetItem(str(value)))
|
||
elif position == 13: # 净重
|
||
# 净重列索引 = 2(序号和工程号) + 检验列数 + 2(贴标和称重)
|
||
net_weight_col = 2 + len(enabled_configs) + 2
|
||
self.process_table.setItem(row_idx, net_weight_col, QTableWidgetItem(str(value)))
|
||
|
||
# 额外加载该工程号的线径数据(即使产品已完成)
|
||
for i, config in enumerate(enabled_configs):
|
||
if config.get('name') == 'xj' or config.get('display_name') == '线径':
|
||
xj_col = 2 + i
|
||
# 检查表格中是否已有线径数据
|
||
xj_item = self.process_table.item(row_idx, xj_col)
|
||
if not xj_item or not xj_item.text().strip():
|
||
# 如果表格中没有线径数据,尝试从数据库加载
|
||
xj_data = inspection_dao.get_inspection_data_by_config(
|
||
self._current_order_code, gc_note, tray_id, position=config.get('position'), config_id=config.get('id')
|
||
)
|
||
if xj_data and xj_data.get('value'):
|
||
# 设置线径数据到表格
|
||
xj_item = QTableWidgetItem(str(xj_data.get('value')))
|
||
xj_item.setTextAlignment(Qt.AlignCenter)
|
||
self.process_table.setItem(row_idx, xj_col, xj_item)
|
||
logging.info(f"从数据库加载线径数据: {xj_data.get('value')} 到工程号 {gc_note}")
|
||
break
|
||
|
||
row_idx += 1
|
||
|
||
# 设置表格为可编辑状态
|
||
self.process_table.setEditTriggers(QTableWidget.DoubleClicked | QTableWidget.EditKeyPressed)
|
||
|
||
# 只有在之前信号已连接的情况下才重新连接
|
||
if signal_was_connected:
|
||
try:
|
||
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
||
logging.debug("已重新连接单元格变更信号")
|
||
except Exception as e:
|
||
logging.warning(f"重新连接单元格变更信号失败: {str(e)}")
|
||
|
||
except Exception as e:
|
||
logging.error(f"加载未完成的检验数据失败: {str(e)}")
|
||
QMessageBox.warning(self, "加载失败", f"加载未完成的检验数据失败: {str(e)}")
|
||
finally:
|
||
# 释放表格更新锁
|
||
self._table_updating = False
|
||
|
||
def load_finished_record_to_package_record(self, order_id, gc_note, tray_id, axios_num=None):
|
||
"""加载已完成检验数据到包装记录
|
||
|
||
Args:
|
||
order_id: 工程号
|
||
tray_id: 托盘号
|
||
axios_num: 轴号,如果为None则使用数据库中的轴号
|
||
"""
|
||
try:
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
|
||
# 获取该工程号的所有检验数据
|
||
inspection_data = inspection_dao.get_inspection_data_by_order(order_id, gc_note, tray_id)
|
||
|
||
if not inspection_data:
|
||
logging.warning(f"未找到工程号 {gc_note} 托盘号 {tray_id} 的检验数据")
|
||
return
|
||
|
||
# 使用传入的轴号,如果没有传入则使用数据库中的轴号
|
||
if axios_num is not None:
|
||
label_value = axios_num
|
||
logging.info(f"使用传入的轴号: {label_value}")
|
||
else:
|
||
label_value = self.get_axios_num_by_order_id(self._current_order_code)
|
||
logging.info(f"使用数据库中的轴号: {label_value}")
|
||
|
||
# 从检验数据中获取贴标和称重数据
|
||
weight_value = ""
|
||
net_weight_value = ""
|
||
for item in inspection_data:
|
||
if item['position'] == 12: # 称重
|
||
weight_value = item['value']
|
||
elif item['position'] == 13: # 净重
|
||
net_weight_value = item['value']
|
||
|
||
# 只要贴标字段有值,就可以写入包装记录
|
||
if label_value is None or label_value == "":
|
||
logging.warning(f"工程号 {order_id} 托盘号 {tray_id} 的贴标字段为空,不添加到包装记录")
|
||
return
|
||
|
||
|
||
# 获取当前时间作为完成时间
|
||
finish_time = datetime.now()
|
||
|
||
# 将数据写入到数据库表 inspection_pack_data
|
||
inspection_dao.save_package_record(order_id, tray_id, str(label_value), weight_value, net_weight_value, finish_time, gc_note)
|
||
|
||
# 回显数据,但避免循环调用
|
||
if not getattr(self, '_loading_data_in_progress'):
|
||
self._loading_data_in_progress = True
|
||
try:
|
||
self.show_pack_item()
|
||
finally:
|
||
self._loading_data_in_progress = False
|
||
|
||
logging.info(f"已将工程号 {order_id} 托盘号 {tray_id} 的检验数据添加到包装记录并回显")
|
||
|
||
except Exception as e:
|
||
logging.error(f"加载已完成检验数据到包装记录失败: {str(e)}")
|
||
QMessageBox.warning(self, "加载失败", f"加载已完成检验数据到包装记录失败: {str(e)}")
|
||
|
||
def show_pack_item(self):
|
||
"""显示包装记录"""
|
||
try:
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
logging.info(f"显示包装记录,当前托盘号: {tray_id}")
|
||
|
||
if not tray_id:
|
||
logging.warning("托盘号为空,无法显示包装记录")
|
||
# 清空表格
|
||
self.record_table.setRowCount(0)
|
||
self.update_package_statistics()
|
||
return
|
||
|
||
# 读取已包装的记录信息
|
||
package_record = inspection_dao.get_package_record(tray_id)
|
||
|
||
# 记录获取的数据情况
|
||
if package_record:
|
||
logging.info(f"成功获取包装记录,托盘号={tray_id},记录数量={len(package_record)}")
|
||
else:
|
||
logging.info(f"包装记录为空,托盘号={tray_id}")
|
||
|
||
# 清空表格内容
|
||
self.record_table.setRowCount(0)
|
||
|
||
# 断开包装记录表的信号连接(如果有)
|
||
try:
|
||
self.record_table.blockSignals(True) # 使用blockSignals替代手动断开信号
|
||
except Exception as e:
|
||
logging.warning(f"阻止信号失败: {str(e)}")
|
||
|
||
# 如果没有包装记录,直接返回
|
||
if not package_record:
|
||
logging.info(f"托盘号 {tray_id} 没有包装记录数据")
|
||
self.update_package_statistics()
|
||
self.record_table.blockSignals(False) # 恢复信号
|
||
return
|
||
|
||
logging.info(f"托盘号 {tray_id} 已加载包装记录,共 {len(package_record)} 条记录")
|
||
|
||
# 添加所有包装记录到表格
|
||
for index, item in enumerate(package_record):
|
||
try:
|
||
row_index = self.record_table.rowCount()
|
||
self.record_table.insertRow(row_index)
|
||
|
||
# 设置单元格数据,使用安全的方式访问数据
|
||
cell_data = [
|
||
str(index + 1), # 序号
|
||
str(item[0]) if len(item) > 0 else "", # 订单
|
||
str(item[1]) if len(item) > 1 else "", # 工程号
|
||
str(item[2]) if len(item) > 2 else "", # 品名
|
||
str(item[3]) if len(item) > 3 else "", # 规格
|
||
str(item[4]) if len(item) > 4 else "", # 托号
|
||
str(item[5]) if len(item) > 5 else "", # 轴包装号
|
||
str(item[6]) if len(item) > 6 else "", # 毛重
|
||
str(item[7]) if len(item) > 7 else "", # 净重
|
||
str(item[8]) if len(item) > 8 else "" # 完成时间
|
||
]
|
||
|
||
# 批量设置单元格
|
||
for col, data in enumerate(cell_data):
|
||
cell_item = QTableWidgetItem(data)
|
||
cell_item.setTextAlignment(Qt.AlignCenter)
|
||
self.record_table.setItem(row_index, col, cell_item)
|
||
except Exception as e:
|
||
logging.error(f"设置第 {index} 行数据时出错: {str(e)}, 数据: {item}")
|
||
continue # 继续处理下一行
|
||
|
||
# 恢复信号
|
||
self.record_table.blockSignals(False)
|
||
|
||
# 更新包装记录统计数据
|
||
self.update_package_statistics()
|
||
logging.info(f"包装记录显示完成,托盘号={tray_id},总记录数={self.record_table.rowCount()}")
|
||
|
||
except Exception as e:
|
||
logging.error(f"显示包装记录失败: {str(e)}")
|
||
self.record_table.blockSignals(False) # 确保信号被恢复
|
||
QMessageBox.warning(self, "显示失败", f"显示包装记录失败: {str(e)}")
|
||
|
||
def update_package_statistics(self):
|
||
"""更新包装记录统计数据"""
|
||
try:
|
||
# 使用DAO获取包装记录统计数据
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
stats = inspection_dao.get_package_statistics(self._current_order_code)
|
||
|
||
# 更新任务表格中的总生产数量(总数量)
|
||
total_count_item = QTableWidgetItem(str(stats['count_all']))
|
||
total_count_item.setTextAlignment(Qt.AlignCenter)
|
||
self.task_table.setItem(2, 0, total_count_item)
|
||
|
||
# 更新任务表格中的总生产公斤(总重量)
|
||
total_kg_item = QTableWidgetItem(f"{stats['weight_all']:.2f}")
|
||
total_kg_item.setTextAlignment(Qt.AlignCenter)
|
||
self.task_table.setItem(2, 1, total_kg_item)
|
||
|
||
# 更新任务表格中的已完成数量(当前订单数量)
|
||
completed_item = QTableWidgetItem(str(stats['count']))
|
||
completed_item.setTextAlignment(Qt.AlignCenter)
|
||
self.task_table.setItem(2, 2, completed_item)
|
||
|
||
# 更新任务表格中的已完成公斤(当前订单重量)
|
||
completed_kg_item = QTableWidgetItem(f"{stats['weight']:.2f}")
|
||
completed_kg_item.setTextAlignment(Qt.AlignCenter)
|
||
self.task_table.setItem(2, 3, completed_kg_item)
|
||
|
||
logging.info(f"已更新包装记录统计数据: 总生产数量={stats['count_all']}, 总生产公斤={stats['weight_all']:.2f}, 已完成数量={stats['count']}, 已完成公斤={stats['weight']:.2f}")
|
||
|
||
except Exception as e:
|
||
logging.error(f"更新包装记录统计数据失败: {str(e)}")
|
||
|
||
def show_table_context_menu(self, pos):
|
||
"""显示表格上下文菜单
|
||
|
||
Args:
|
||
pos: 鼠标位置
|
||
"""
|
||
try:
|
||
# 获取当前单元格
|
||
cell_index = self.process_table.indexAt(pos)
|
||
if not cell_index.isValid():
|
||
return
|
||
|
||
row = cell_index.row()
|
||
column = cell_index.column()
|
||
|
||
# 只对数据行和检验列显示上下文菜单
|
||
if row < 2: # 忽略表头行
|
||
return
|
||
|
||
# 获取工程号
|
||
order_id_item = self.process_table.item(row, 1)
|
||
if not order_id_item:
|
||
return
|
||
|
||
order_id = order_id_item.text().strip()
|
||
if not order_id:
|
||
return
|
||
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 创建上下文菜单
|
||
menu = QMenu(self)
|
||
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 判断是否是检验列(非包装列)
|
||
packaging_start_col = 2 + len(enabled_configs)
|
||
if column >= 2 and column < packaging_start_col:
|
||
# 是检验列
|
||
config_index = column - 2
|
||
if config_index < len(enabled_configs):
|
||
config = enabled_configs[config_index]
|
||
position = config.get('position')
|
||
|
||
# 添加查询数据库菜单项
|
||
check_action = menu.addAction("检查数据库记录")
|
||
check_action.triggered.connect(lambda: self.check_database_record(order_id, position, tray_id))
|
||
|
||
# 显示菜单
|
||
menu.exec_(self.process_table.viewport().mapToGlobal(pos))
|
||
|
||
except Exception as e:
|
||
logging.error(f"显示表格上下文菜单失败: {str(e)}")
|
||
|
||
def check_database_record(self, order_id, position, tray_id):
|
||
"""检查数据库记录
|
||
|
||
Args:
|
||
order_id: 工程号
|
||
position: 位置序号
|
||
tray_id: 托盘号
|
||
"""
|
||
try:
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
|
||
# 获取检验数据
|
||
inspection_data = inspection_dao.get_inspection_data_by_order(order_id, tray_id)
|
||
|
||
# 查找对应位置的数据
|
||
matching_data = None
|
||
for data in inspection_data:
|
||
if data.get('position') == position:
|
||
matching_data = data
|
||
break
|
||
|
||
# 显示结果
|
||
if matching_data:
|
||
value = matching_data.get('value')
|
||
status = matching_data.get('status')
|
||
|
||
message = f"数据库记录:\n\n"
|
||
message += f"工程号: {order_id}\n"
|
||
message += f"位置: {position}\n"
|
||
message += f"值: {value}\n"
|
||
message += f"状态: {status}\n"
|
||
|
||
QMessageBox.information(self, "数据库记录", message)
|
||
else:
|
||
QMessageBox.warning(self, "数据库记录", f"未找到工程号 {order_id} 位置 {position} 的数据")
|
||
|
||
except Exception as e:
|
||
logging.error(f"检查数据库记录失败: {str(e)}")
|
||
QMessageBox.warning(self, "查询失败", f"检查数据库记录失败: {str(e)}")
|
||
|
||
def show_operation_status(self, status, operation_type, pallet_type):
|
||
"""更新上下料区域的状态显示
|
||
|
||
Args:
|
||
status: 状态文本
|
||
operation_type: 操作类型 (input/output)
|
||
pallet_type: 托盘类型
|
||
"""
|
||
try:
|
||
if operation_type == "input":
|
||
# 更新拆垛层数显示
|
||
if hasattr(self, 'stow_level_label'):
|
||
self.stow_level_label.setText(f"拆垛层数: {pallet_type}")
|
||
logging.debug(f"更新拆垛层数显示: {pallet_type}")
|
||
else:
|
||
# 更新下料层数显示
|
||
if hasattr(self, 'unload_level_label'):
|
||
self.unload_level_label.setText(f"下料层数: {pallet_type}")
|
||
logging.debug(f"更新下料层数显示: {pallet_type}")
|
||
except Exception as e:
|
||
logging.error(f"更新状态显示失败: {str(e)}")
|
||
|
||
def adjust_status_label_position(self, event, container, label):
|
||
"""调整状态标签位置,确保始终在右上角
|
||
|
||
Args:
|
||
event: 窗口大小变化事件
|
||
container: 标签所在的容器
|
||
label: 状态标签
|
||
"""
|
||
# 更新标签位置,保持在右上角
|
||
label.setGeometry(container.width() - 250, 5, 240, 30)
|
||
|
||
# 调用原始的resizeEvent(如果有的话)
|
||
original_resize = getattr(container, "_original_resize_event", None)
|
||
if original_resize:
|
||
original_resize(event)
|
||
|
||
# ==================== Modbus监控系统相关方法 ====================
|
||
|
||
def setup_modbus_monitor(self):
|
||
"""设置Modbus监控系统"""
|
||
# 获取Modbus监控器实例
|
||
self.modbus_monitor = get_modbus_monitor()
|
||
|
||
# 注册寄存器处理器
|
||
self._register_modbus_handlers()
|
||
|
||
# 连接信号槽
|
||
self._connect_modbus_signals()
|
||
|
||
# 启动监控
|
||
self.modbus_monitor.start()
|
||
|
||
logging.info("Modbus监控系统已设置")
|
||
|
||
def _register_modbus_handlers(self):
|
||
"""注册寄存器处理器"""
|
||
# 获取Modbus监控器实例
|
||
monitor = get_modbus_monitor()
|
||
|
||
# 注册D6处理器,处理NG信号
|
||
monitor.register_handler(6, NGHandler(self.machine_handlers.handle_ng))
|
||
|
||
# 注册D11处理器,处理称重数据
|
||
monitor.register_handler(11, WeightDataHandler(self.machine_handlers.handle_weight_data))
|
||
|
||
# 注册D13处理器,处理贴标信号
|
||
monitor.register_handler(13, LabelSignalHandler(self.machine_handlers.handle_label_signal))
|
||
|
||
# 注册D20-D24处理器,处理各种状态信息
|
||
monitor.register_handler(20, LoadingFeedbackHandler(self.handle_loading_feedback))
|
||
monitor.register_handler(21, UnloadingFeedbackHandler(self.handle_unloading_feedback))
|
||
monitor.register_handler(22, Error1Handler(self.machine_handlers.handle_error_1))
|
||
monitor.register_handler(23, Error2Handler(self.machine_handlers.handle_error_2))
|
||
monitor.register_handler(24, Error3Handler(self.machine_handlers.handle_error_3))
|
||
|
||
# 注册急停信号处理器
|
||
monitor.register_handler(25, EmergencyStopHandler(self.handle_emergency_stop))
|
||
|
||
# 注册下料层数和位置处理器
|
||
monitor.register_handler(4, UnloadingLevelHandler(self.handle_unloading_level))
|
||
monitor.register_handler(5, UnloadingPositionHandler(self.handle_unloading_position))
|
||
|
||
# 注册寄存器2和3的处理器(开始按钮样式控制)
|
||
monitor.register_handler(2, RegisterChangeHandler(self.handle_register_change, 2))
|
||
monitor.register_handler(3, RegisterChangeHandler(self.handle_register_change, 3))
|
||
|
||
# 注册电力消耗处理器并保存引用以便连接信号
|
||
self.electricity_handler = ElectricityHandler()
|
||
monitor.register_handler(30, self.electricity_handler)
|
||
|
||
logging.info("已注册所有Modbus寄存器处理器")
|
||
|
||
def _connect_modbus_signals(self):
|
||
"""连接Modbus信号"""
|
||
# 连接Modbus状态变化信号
|
||
self.modbus_monitor.monitor_status_changed.connect(self.handle_modbus_status_change)
|
||
|
||
# 连接寄存器变化信号
|
||
self.modbus_monitor.register_changed.connect(self.handle_register_change)
|
||
|
||
# 连接寄存器错误信号
|
||
self.modbus_monitor.register_error.connect(self.handle_register_error)
|
||
|
||
# 连接电力数据变化信号
|
||
self.electricity_handler.electricity_data_changed.connect(self.update_electricity_statistics)
|
||
|
||
# 连接贴标信号 - 先断开再连接,避免重复连接
|
||
try:
|
||
self.machine_handlers.label_signal_changed.disconnect(self.handle_label_signal)
|
||
except Exception:
|
||
logging.debug("贴标信号未连接,无需断开")
|
||
self.machine_handlers.label_signal_changed.connect(self.handle_label_signal)
|
||
|
||
# 连接称重数据变化信号 - 先断开再连接,避免重复连接
|
||
try:
|
||
self.machine_handlers.weight_changed.disconnect(self.handle_weight_data)
|
||
except Exception:
|
||
logging.debug("称重信号未连接,无需断开")
|
||
self.machine_handlers.weight_changed.connect(self.handle_weight_data)
|
||
|
||
# 连接NG信号 - 先断开再连接,避免重复连接
|
||
try:
|
||
self.machine_handlers.ng_changed.disconnect(self.handle_ng)
|
||
except Exception:
|
||
logging.debug("NG信号未连接,无需断开")
|
||
self.machine_handlers.ng_changed.connect(self.handle_ng)
|
||
|
||
# 连接故障信号 - 先断开再连接,避免重复连接
|
||
try:
|
||
self.machine_handlers.error_1_changed.disconnect(self.handle_error_1)
|
||
except Exception:
|
||
logging.debug("故障1信号未连接,无需断开")
|
||
self.machine_handlers.error_1_changed.connect(self.handle_error_1)
|
||
|
||
try:
|
||
self.machine_handlers.error_2_changed.disconnect(self.handle_error_2)
|
||
except Exception:
|
||
logging.debug("故障2信号未连接,无需断开")
|
||
self.machine_handlers.error_2_changed.connect(self.handle_error_2)
|
||
|
||
try:
|
||
self.machine_handlers.error_3_changed.disconnect(self.handle_error_3)
|
||
except Exception:
|
||
logging.debug("故障3信号未连接,无需断开")
|
||
self.machine_handlers.error_3_changed.connect(self.handle_error_3)
|
||
|
||
# 连接上下料反馈信号 - 先断开再连接,避免重复连接
|
||
try:
|
||
self.machine_handlers.loading_feedback_changed.disconnect(self.handle_loading_feedback)
|
||
except Exception:
|
||
logging.debug("上料反馈信号未连接,无需断开")
|
||
self.machine_handlers.loading_feedback_changed.connect(self.handle_loading_feedback)
|
||
|
||
try:
|
||
self.machine_handlers.unloading_feedback_changed.disconnect(self.handle_unloading_feedback)
|
||
except Exception:
|
||
logging.debug("下料反馈信号未连接,无需断开")
|
||
self.machine_handlers.unloading_feedback_changed.connect(self.handle_unloading_feedback)
|
||
|
||
# 连接急停信号
|
||
self.emergency_stop_signal.connect(self._handle_emergency_stop_ui)
|
||
|
||
# 立即更新一次用电量数据
|
||
self.update_electricity_statistics()
|
||
|
||
logging.info("已连接所有Modbus信号")
|
||
|
||
def update_electricity_statistics(self, value=None):
|
||
"""更新电力消耗统计数据到项目表格
|
||
|
||
Args:
|
||
value: 可选的当前电力消耗值,用于信号触发时
|
||
"""
|
||
try:
|
||
from dao.electricity_dao import ElectricityDAO
|
||
electricity_dao = ElectricityDAO()
|
||
|
||
# 获取电力消耗统计数据
|
||
statistics = electricity_dao.get_electricity_statistics()
|
||
|
||
# 设置表格项(日、月、年、累计用电量)
|
||
# 当日用电量
|
||
day_item = QTableWidgetItem(str(round(statistics['day'], 2)))
|
||
day_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(0, 0, day_item)
|
||
|
||
# 当月用电量
|
||
month_item = QTableWidgetItem(str(round(statistics['month'], 2)))
|
||
month_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(1, 0, month_item)
|
||
|
||
# 当年用电量
|
||
year_item = QTableWidgetItem(str(round(statistics['year'], 2)))
|
||
year_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(2, 0, year_item)
|
||
|
||
# 累计用电量
|
||
all_item = QTableWidgetItem(str(round(statistics['all'], 2)))
|
||
all_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(3, 0, all_item)
|
||
|
||
# 只在调试级别输出详细信息,减少日志量
|
||
if value is not None:
|
||
logging.info(f"电力数据变化触发UI更新:当前值={value}")
|
||
else:
|
||
logging.debug(f"已更新电力消耗统计数据: 日={statistics['day']}, 月={statistics['month']}, 年={statistics['year']}, 累计={statistics['all']}")
|
||
|
||
except Exception as e:
|
||
logging.error(f"更新电力消耗统计数据失败: {str(e)}")
|
||
|
||
def _convert_to_kg(self, weight_in_g):
|
||
"""
|
||
将克转换为千克
|
||
Args:
|
||
weight_in_g: 重量(克)
|
||
Returns:
|
||
float: 重量(千克)
|
||
"""
|
||
return round(weight_in_g / 1000.0, 2) # 保留2位小数
|
||
|
||
@Slot(int)
|
||
def handle_weight_data(self, weight_in_g):
|
||
"""处理称重数据变化"""
|
||
try:
|
||
# 添加防抖机制,避免短时间内重复处理相同重量
|
||
current_time = time.time()
|
||
last_weight_time = getattr(self, '_last_weight_data_time', 0)
|
||
last_weight_value = getattr(self, '_last_weight_data_value', 0)
|
||
|
||
# 如果是相同的重量且间隔小于0.2秒,则忽略此次调用
|
||
if abs(weight_in_g - last_weight_value) < 10 and current_time - last_weight_time < 0.2:
|
||
return
|
||
|
||
# 更新最后处理的重量和时间
|
||
self._last_weight_data_time = current_time
|
||
self._last_weight_data_value = weight_in_g
|
||
|
||
# 转换重量单位并立即更新UI显示
|
||
weight_in_kg = self._convert_to_kg(weight_in_g)
|
||
logging.info(f"[显示] 称重数据: {weight_in_kg}kg (原始值: {weight_in_g}g)")
|
||
self.weight_label.setText(f"重量: {weight_in_kg}kg")
|
||
|
||
# 检测重量从接近0到较大值的变化,判断为新产品
|
||
if self._current_weight is not None and self._current_weight < 0.1 and weight_in_kg > 0.5:
|
||
logging.info(f"检测到新产品放上,重量从 {self._current_weight}kg 变为 {weight_in_kg}kg")
|
||
self._weight_processed = False # 重置处理标记,允许处理新产品
|
||
|
||
# 检测重量从较大值变为接近0,判断为产品移除
|
||
elif self._current_weight is not None and self._current_weight > 0.5 and weight_in_kg < 0.1:
|
||
logging.info(f"检测到产品被移除,重量从 {self._current_weight}kg 变为 {weight_in_kg}kg")
|
||
self._weight_processed = False # 重置处理标记,为下一个产品做准备
|
||
self._last_processed_weight = 0 # 重置上次处理的重量
|
||
|
||
# 更新当前重量和时间
|
||
self._current_weight = weight_in_kg
|
||
self._last_weight_time = current_time
|
||
|
||
# 取消之前的定时器(如果存在)
|
||
if self._stability_check_timer is not None:
|
||
self._stability_check_timer.stop()
|
||
self._stability_check_timer.deleteLater()
|
||
|
||
# 创建新的定时器
|
||
self._stability_check_timer = QTimer()
|
||
self._stability_check_timer.setSingleShot(True) # 单次触发
|
||
self._stability_check_timer.timeout.connect(lambda: self._check_weight_stability(weight_in_kg))
|
||
self._stability_check_timer.start(self._weight_stable_threshold * 1000) # 转换为毫秒
|
||
|
||
# 尝试获取表格行数据,用于日志记录
|
||
current_row = self.process_table.currentRow()
|
||
data_row = current_row if current_row >= 2 else 2 # 使用第一个数据行(索引为2)
|
||
|
||
# 记录表格行状态,仅用于日志记录,不影响后续处理
|
||
if data_row >= self.process_table.rowCount():
|
||
logging.warning(f"选中的行 {data_row} 超出了表格范围")
|
||
else:
|
||
# 获取工程号,仅用于日志记录
|
||
gc_note_item = self.process_table.item(data_row, 1)
|
||
if gc_note_item:
|
||
gc_note = gc_note_item.text().strip()
|
||
if gc_note:
|
||
logging.info(f"当前处理的工程号: {gc_note}, 行: {data_row}")
|
||
else:
|
||
logging.warning("工程号为空")
|
||
else:
|
||
logging.warning("无法获取工程号")
|
||
except Exception as e:
|
||
logging.error(f"处理称重数据时发生错误: {str(e)}")
|
||
# 确保重新连接信号
|
||
try:
|
||
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
||
except:
|
||
pass
|
||
|
||
def _check_weight_stability(self, original_weight_kg):
|
||
"""
|
||
检查重量是否稳定
|
||
Args:
|
||
original_weight_kg: 开始检查时的重量(千克)
|
||
"""
|
||
try:
|
||
# 设置最小有效重量阈值,低于此值的重量不会被处理
|
||
MIN_VALID_WEIGHT = 0.5 # 0.5kg
|
||
|
||
# 如果当前重量低于最小有效重量阈值,则直接跳过
|
||
if self._current_weight < MIN_VALID_WEIGHT:
|
||
logging.info(f"当前重量 {self._current_weight}kg 低于最小有效重量阈值 {MIN_VALID_WEIGHT}kg,跳过处理")
|
||
return
|
||
|
||
# 如果当前重量与定时器启动时的重量相同,说明这段时间内没有新的重量数据
|
||
if self._current_weight == original_weight_kg and self._current_weight > MIN_VALID_WEIGHT:
|
||
logging.info(f"重量 {original_weight_kg}kg 在{self._weight_stable_threshold}秒内保持稳定")
|
||
|
||
# 如果这个重量与上一次处理的重量接近(±0.1kg),且标记已处理,则跳过
|
||
if self._weight_processed and abs(original_weight_kg - self._last_processed_weight) < 0.1:
|
||
logging.info(f"跳过处理:重量 {original_weight_kg}kg 与上次处理的重量 {self._last_processed_weight}kg 接近且已处理")
|
||
return
|
||
|
||
# 尝试写入D10寄存器并处理稳定重量,带有重试机制
|
||
self._handle_stable_weight_with_retry(original_weight_kg, max_retries=3)
|
||
else:
|
||
logging.info(f"重量在{self._weight_stable_threshold}秒内发生变化,从 {original_weight_kg}kg 变为 {self._current_weight}kg")
|
||
except Exception as e:
|
||
logging.error(f"检查重量稳定性时发生错误: {str(e)}")
|
||
finally:
|
||
# 清理定时器
|
||
if self._stability_check_timer is not None:
|
||
self._stability_check_timer.deleteLater()
|
||
self._stability_check_timer = None
|
||
|
||
def _handle_stable_weight_with_retry(self, weight_kg, max_retries=3):
|
||
"""
|
||
使用重试机制处理稳定重量
|
||
Args:
|
||
weight_kg: 稳定的重量值(千克)
|
||
max_retries: 最大重试次数
|
||
"""
|
||
# 添加防抖机制,避免短时间内重复处理相同重量
|
||
current_time = time.time()
|
||
last_stable_time = getattr(self, '_last_stable_weight_time', 0)
|
||
last_stable_value = getattr(self, '_last_stable_weight_value', 0)
|
||
|
||
# 如果是相同的重量且间隔小于1秒,则忽略此次调用
|
||
if abs(weight_kg - last_stable_value) < 0.1 and current_time - last_stable_time < 1.0:
|
||
logging.info(f"防抖机制:跳过处理相同的稳定重量 {weight_kg}kg,间隔小于1秒")
|
||
return
|
||
|
||
# 更新最后处理的稳定重量和时间
|
||
self._last_stable_weight_time = current_time
|
||
self._last_stable_weight_value = weight_kg
|
||
|
||
retry_count = 0
|
||
last_error = None
|
||
modbus = ModbusUtils()
|
||
client = None
|
||
try:
|
||
# 获取Modbus客户端(现在使用连接池,不会每次都创建新连接)
|
||
client = modbus.get_client()
|
||
if not client:
|
||
logging.error("无法获取Modbus客户端连接")
|
||
return
|
||
|
||
logging.info(f"开始处理稳定重量: {weight_kg}kg")
|
||
# 这里不再写入D10=1,全部交由_process_stable_weight处理
|
||
self._process_stable_weight(weight_kg)
|
||
# 调用打印方法
|
||
self._print_weight_label(weight_kg)
|
||
# 设置已处理标记和上次处理的重量
|
||
self._weight_processed = True
|
||
self._last_processed_weight = weight_kg
|
||
logging.info(f"已标记重量 {weight_kg}kg 为已处理")
|
||
|
||
# 重置当前重量为0,避免两个产品之间的重量判断问题
|
||
# 这样可以确保下一个产品必须从接近0的值开始测量
|
||
logging.info(f"已重置当前重量为0,等待下一个产品")
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理稳定重量时发生错误: {str(e)}")
|
||
finally:
|
||
# 释放客户端连接回连接池
|
||
if client:
|
||
modbus.close_client(client)
|
||
|
||
def _process_stable_weight(self, weight_kg):
|
||
"""
|
||
处理稳定的称重数据
|
||
Args:
|
||
weight_kg: 稳定的重量值(千克)
|
||
"""
|
||
# 添加处理锁,避免并发处理
|
||
if hasattr(self, '_processing_weight_lock') and self._processing_weight_lock:
|
||
logging.warning(f"已有称重处理进行中,忽略本次请求: {weight_kg}kg")
|
||
return
|
||
|
||
# 设置处理锁
|
||
self._processing_weight_lock = True
|
||
|
||
try:
|
||
# 忽略接近0的重量值,这可能表示产品已被移除
|
||
if weight_kg < 0.1: # 小于100g的重量视为无效
|
||
logging.info(f"忽略接近零的重量值: {weight_kg}kg,可能表示产品已被移除")
|
||
return
|
||
|
||
# 获取数据行数
|
||
if self.process_table.rowCount() <= 2: # 没有数据行
|
||
logging.warning("没有可用的数据行来写入称重数据")
|
||
return
|
||
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 计算称重列索引 - 称重位置在检验列之后的第二列(贴标后面)
|
||
weight_col = 2 + len(enabled_configs) + 1
|
||
# 计算净重列索引 - 净重位置在检验列之后的第三列(称重后面)
|
||
net_weight_col = 2 + len(enabled_configs) + 2
|
||
|
||
# 使用新的查找逻辑,确保顺序处理
|
||
data_row = self._find_next_row_to_process('inspected')
|
||
|
||
# 如果没有找到inspected状态的行,回退到原有逻辑
|
||
if data_row is None:
|
||
# 查找第一个没有称重数据的行
|
||
for row in range(2, self.process_table.rowCount()):
|
||
weight_item = self.process_table.item(row, weight_col)
|
||
if not weight_item or not weight_item.text().strip():
|
||
data_row = row
|
||
break
|
||
|
||
# 如果仍然没有找到,使用当前选中行或第一个数据行
|
||
if data_row is None:
|
||
current_row = self.process_table.currentRow()
|
||
data_row = current_row if current_row >= 2 else 2 # 使用第一个数据行(索引为2)
|
||
logging.info(f"未找到状态为inspected的行或没有称重数据的行,使用当前选中行或第一个数据行: {data_row}")
|
||
else:
|
||
logging.info(f"找到没有称重数据的行: {data_row}")
|
||
else:
|
||
logging.info(f"将使用状态为inspected的行: {data_row}")
|
||
|
||
# 设置当前处理行
|
||
self._current_processing_row = data_row
|
||
|
||
# 获取工程号
|
||
gc_note_item = self.process_table.item(data_row, 1)
|
||
if not gc_note_item:
|
||
logging.warning("无法获取工程号")
|
||
return
|
||
|
||
gc_note = gc_note_item.text().strip()
|
||
if not gc_note:
|
||
logging.warning("工程号为空")
|
||
return
|
||
|
||
# 创建唯一键,包含工程号、重量值和时间戳
|
||
current_time = time.time()
|
||
process_key = f"{gc_note}_{weight_kg}_{int(current_time/60)}" # 按分钟级别去重
|
||
|
||
# 检查是否已处理过相同的键
|
||
last_process_key = getattr(self, '_last_weight_process_key', '')
|
||
if process_key == last_process_key:
|
||
logging.info(f"跳过重复处理:工程号 {gc_note} 的重量 {weight_kg}kg 在短时间内已处理")
|
||
return
|
||
|
||
# 更新处理键
|
||
self._last_weight_process_key = process_key
|
||
|
||
# 保存净重到数据库(毛重-工字轮重量,单位都是千克)
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
gzl_zl_raw = inspection_dao.get_gzl_zl(self._current_order_code)
|
||
gzl_zl = 0.0
|
||
try:
|
||
if gzl_zl_raw:
|
||
gzl_zl = float(gzl_zl_raw)
|
||
except (ValueError, TypeError):
|
||
logging.warning(f"无法将工字轮重量 '{gzl_zl_raw}' 转换为浮点数,将使用默认值 0.0")
|
||
|
||
net_weight_kg = round(weight_kg - gzl_zl,3)
|
||
|
||
# 检查轴重要求范围
|
||
if "轴重要求" in self.info_values and self.info_values["轴重要求"] is not None:
|
||
try:
|
||
zzyq_value = self.info_values["轴重要求"].text().strip()
|
||
if zzyq_value and "-" in zzyq_value:
|
||
# 解析轴重要求范围,格式如 "12.5-13.5"
|
||
parts = zzyq_value.split("-")
|
||
if len(parts) == 2:
|
||
min_weight = float(parts[0].strip())
|
||
max_weight = float(parts[1].strip())
|
||
|
||
# 检查称重值是否在范围内
|
||
if net_weight_kg < min_weight or net_weight_kg > max_weight:
|
||
# 写入寄存器D10 给值 2 表示重量超出范围
|
||
try:
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
modbus.write_register_until_success(client, 10, 2)
|
||
modbus.close_client(client)
|
||
except Exception as e:
|
||
logging.error(f"写入Modbus寄存器失败: {str(e)}")
|
||
|
||
# 使用信号触发弹框显示 - 避免主线程阻塞
|
||
logging.warning(f"称重值 {net_weight_kg:.2f}kg 超出轴重要求范围 ({min_weight:.1f} - {max_weight:.1f}kg),发送信号显示警告")
|
||
try:
|
||
self.weight_alert_signal.emit(net_weight_kg, min_weight, max_weight)
|
||
except Exception as e:
|
||
logging.error(f"发送重量警告信号失败: {str(e)}", exc_info=True)
|
||
|
||
# 阻止继续执行,等待用户处理
|
||
logging.warning(f"称重值 {weight_kg:.3f}kg 超出轴重要求范围,已阻止保存")
|
||
return
|
||
else:
|
||
logging.info(f"称重值 {weight_kg:.3f}kg 在轴重要求范围内 ({min_weight:.1f} - {max_weight:.1f}kg)")
|
||
# 只有在范围内才写入D10=1
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
modbus.write_register_until_success(client, 10, 1)
|
||
modbus.close_client(client)
|
||
logging.info("已写入D10=1,通知PLC称重合格")
|
||
else:
|
||
logging.debug(f"轴重要求格式不正确: '{zzyq_value}'")
|
||
else:
|
||
logging.debug(f"轴重要求格式不正确或为空,跳过范围检查: '{zzyq_value}'")
|
||
except (ValueError, AttributeError) as e:
|
||
logging.warning(f"解析轴重要求失败,跳过范围检查: {str(e)}")
|
||
else:
|
||
logging.debug("轴重要求字段不存在或为空,跳过范围检查")
|
||
# 没有轴重要求时,直接写入D10=1
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
modbus.write_register_until_success(client, 10, 1)
|
||
modbus.close_client(client)
|
||
logging.info("无轴重要求,已写入D10=1,通知PLC称重合格")
|
||
|
||
# 暂时断开信号连接,避免触发cellChanged信号
|
||
try:
|
||
self.process_table.cellChanged.disconnect(self.handle_inspection_cell_changed)
|
||
except:
|
||
pass
|
||
|
||
# 设置称重值单元格(显示千克)
|
||
weight_item = QTableWidgetItem(str(weight_kg))
|
||
weight_item.setTextAlignment(Qt.AlignCenter)
|
||
self.process_table.setItem(data_row, weight_col, weight_item)
|
||
|
||
# 检查是否已存在相同记录
|
||
tray_id = self.tray_edit.currentText()
|
||
existing_data = inspection_dao.get_inspection_data_by_config(self._current_order_code, gc_note, tray_id, 12, 12)
|
||
if existing_data and str(existing_data.get('value')) == str(weight_kg):
|
||
logging.info(f"跳过保存:工程号 {gc_note} 的称重数据 {weight_kg} 已存在")
|
||
else:
|
||
# 保存到数据库(使用千克)
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, 12, 12, str(weight_kg), "pass")
|
||
logging.info(f"已保存称重数据: {weight_kg}kg 到工程号 {gc_note}")
|
||
|
||
# 检查是否已存在相同净重记录
|
||
existing_net_data = inspection_dao.get_inspection_data_by_config(self._current_order_code, gc_note, tray_id, 13, 13)
|
||
if existing_net_data and str(existing_net_data.get('value')) == str(net_weight_kg):
|
||
logging.info(f"跳过保存:工程号 {gc_note} 的净重数据 {net_weight_kg} 已存在")
|
||
else:
|
||
# 保存净重数据
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, 13, 13, str(net_weight_kg), "pass")
|
||
logging.info(f"已保存净重数据: {net_weight_kg}kg 到工程号 {gc_note}")
|
||
|
||
# 设置净重单元格(显示千克)
|
||
net_weight_item = QTableWidgetItem(str(net_weight_kg))
|
||
net_weight_item.setTextAlignment(Qt.AlignCenter)
|
||
self.process_table.setItem(data_row, net_weight_col, net_weight_item)
|
||
# 获取轴号,优先使用当前行的轴号
|
||
axios_num = self.get_current_row_axios_num(data_row)
|
||
# 如果开启 api 模式,则调用接口添加到包装记录
|
||
if AppMode.is_api():
|
||
from dao.inspection_dao import InspectionDAO
|
||
from apis.gc_api import GcApi
|
||
inspection_dao = InspectionDAO()
|
||
# 调用接口
|
||
gc_api = GcApi()
|
||
|
||
# 构建info字典,以数据库数据为基础,前端数据作为补充
|
||
info = {}
|
||
|
||
# 1. 首先从数据库获取基础订单信息
|
||
order_info = inspection_dao.get_order_info(self._current_order_code)
|
||
if order_info:
|
||
info.update(order_info)
|
||
|
||
# 2. 从前端获取用户修改的数据,作为补充和更新
|
||
logging.info(f"开始从前端获取数据,info_values中的字段: {list(self.info_values.keys())}")
|
||
|
||
# 专门检查线材类型字段
|
||
if "线材类型" in self.info_values:
|
||
wire_type_widget = self.info_values["线材类型"]
|
||
if isinstance(wire_type_widget, QComboBox):
|
||
wire_type_value = wire_type_widget.currentText()
|
||
logging.info(f"线材类型字段当前值: '{wire_type_value}'")
|
||
logging.info(f"线材类型下拉框选项: {[wire_type_widget.itemText(i) for i in range(wire_type_widget.count())]}")
|
||
else:
|
||
logging.warning(f"线材类型字段不是QComboBox,而是: {type(wire_type_widget)}")
|
||
else:
|
||
logging.warning("线材类型字段不在info_values中")
|
||
|
||
for field_name, field_key in self.FIELD_MAPPING.items():
|
||
# 过滤掉线径公差字段,因为线径数据是通过线径仪获取的,不需要从前端托盘入库地方获取
|
||
if field_name == "线径公差":
|
||
logging.debug(f"跳过线径公差字段,该字段由线径仪获取")
|
||
continue
|
||
|
||
logging.debug(f"处理字段: {field_name} -> {field_key}")
|
||
if field_name in self.info_values and self.info_values[field_name] is not None:
|
||
try:
|
||
# 根据控件类型获取最新值
|
||
if isinstance(self.info_values[field_name], QTextEdit):
|
||
current_value = self.info_values[field_name].toPlainText().strip()
|
||
elif isinstance(self.info_values[field_name], QComboBox):
|
||
current_value = self.info_values[field_name].currentText().strip()
|
||
elif field_name == "炉号":
|
||
# 炉号字段是容器,需要找到其中的QLineEdit
|
||
luno_container = self.info_values[field_name]
|
||
current_value = ""
|
||
if luno_container:
|
||
for child in luno_container.children():
|
||
if hasattr(child, 'text') and hasattr(child, 'setText'):
|
||
current_value = child.text().strip()
|
||
break
|
||
elif hasattr(self.info_values[field_name], 'text'):
|
||
current_value = self.info_values[field_name].text().strip()
|
||
else:
|
||
current_value = ""
|
||
|
||
# 对于线材类型字段,即使值为"请选择"也要包含在接口调用中
|
||
if field_name == "线材类型":
|
||
info[field_key] = current_value
|
||
# 如果前端有值,则更新info字典(覆盖数据库中的值)
|
||
elif current_value:
|
||
info[field_key] = current_value
|
||
logging.debug(f"从前端更新字段 '{field_name}': '{current_value}'")
|
||
# 如果前端没有值但数据库有值,保持数据库的值
|
||
elif field_key in info:
|
||
logging.debug(f"保持数据库字段 '{field_name}': '{info[field_key]}'")
|
||
else:
|
||
logging.debug(f"字段 '{field_name}' 前端值为空且数据库中也无值")
|
||
except Exception as e:
|
||
logging.warning(f"获取前端字段 '{field_name}' 失败: {str(e)}")
|
||
continue
|
||
else:
|
||
logging.debug(f"字段 '{field_name}' 不在info_values中或为None")
|
||
|
||
# 3. 添加其他必要的信息
|
||
info['xpack'] = self.tray_edit.currentText()
|
||
info['spack'] = self.tray_edit.currentText()
|
||
info['zh'] = axios_num
|
||
info['mzl'] = weight_kg
|
||
info['printsl'] = 1
|
||
info['pono'] = self._current_order_code
|
||
info["dycz"] = info.get("cz")
|
||
info['qd'] = self._current_gc_qd
|
||
# 4. 添加sc_gch信息 - 直接使用前端获取到的工程号作为sc_gch
|
||
# 避免使用全局的_current_gc_sc_gch,因为可能在称重过程中被其他扫码操作更新
|
||
info['sc_gch'] = gc_note
|
||
logging.info(f"使用前端工程号 {gc_note} 作为sc_gch")
|
||
|
||
# 5. 从数据库获取其他信息作为补充
|
||
order_others_info = inspection_dao.get_order_others_info(gc_note, self._current_order_code, tray_id)
|
||
if order_others_info:
|
||
info.update(order_others_info)
|
||
|
||
# 6. 确保data_corp字段存在
|
||
if 'data_corp' not in info and order_info and 'data_corp' in order_info:
|
||
info['data_corp'] = order_info['data_corp']
|
||
# 获取本机IP地址
|
||
import socket
|
||
try:
|
||
# 通过连接外部服务器获取本机IP(不实际建立连接)
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
s.connect(("8.8.8.8", 80))
|
||
local_ip = s.getsockname()[0]
|
||
s.close()
|
||
info['nw_ip'] = local_ip
|
||
except Exception as e:
|
||
logging.error(f"获取本机IP失败: {str(e)}")
|
||
# 如果获取失败,使用本地回环地址
|
||
info['nw_ip'] = '127.0.0.1'
|
||
# info['nw_ip'] = '192.168.1.246'
|
||
# 调试:检查线材类型字段
|
||
wire_type_value = info.get('xclx', 'NOT_FOUND')
|
||
logging.info(f"准备调用接口,线材类型字段值: {wire_type_value}")
|
||
logging.info(f"完整的info字典: {info}")
|
||
|
||
# 调用接口添加到包装记录
|
||
response = gc_api.add_order_info(info)
|
||
if response.get("status",False):
|
||
logging.info(f"添加订单信息成功: {response.get('data',{})}")
|
||
else:
|
||
QMessageBox.warning(self, f"提示", response.get("message",{}))
|
||
|
||
# 新增:如果勾选了圆形标签checkbox,则向寄存器D15写入1
|
||
if hasattr(self, "round_label_checkbox") and self.round_label_checkbox.isChecked():
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
modbus.write_register_until_success(client, 15, 1)
|
||
modbus.close_client(client)
|
||
logging.info("已通知PLC当前使用圆形标签")
|
||
|
||
# 保存贴标数据到数据库
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, 11, 11, str(axios_num), "pass")
|
||
|
||
# 更新产品状态为weighed
|
||
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'weighed')
|
||
logging.info(f"工程号 {gc_note} 的称重已完成,状态更新为weighed")
|
||
|
||
# 重新连接信号
|
||
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
||
|
||
logging.info(f"已将稳定的称重数据 {weight_kg}kg 写入行 {data_row}, 列 {weight_col}")
|
||
|
||
# 清除当前处理行的跟踪,因为称重完成后需要等待贴标
|
||
self._current_processing_row = None
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理称重数据时发生错误: {str(e)}")
|
||
# 确保重新连接信号
|
||
try:
|
||
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
||
except:
|
||
pass
|
||
finally:
|
||
# 释放处理锁
|
||
self._processing_weight_lock = False
|
||
|
||
def _print_weight_label(self, weight_kg):
|
||
"""
|
||
打印重量标签 注意:目前打印是写入数据库打印,不需要再次调用
|
||
Args:
|
||
weight_kg: 稳定的重量值(千克)
|
||
"""
|
||
try:
|
||
logging.info(f"开始打印重量标签,重量:{weight_kg}kg")
|
||
# TODO: 实现打印逻辑
|
||
pass
|
||
except Exception as e:
|
||
logging.error(f"打印重量标签时发生错误: {str(e)}")
|
||
|
||
@Slot(int, str)
|
||
def handle_label_signal(self, signal, status):
|
||
"""处理贴标信号"""
|
||
logging.info(f"[处理] 贴标信号: {status} (值={signal})")
|
||
|
||
# 更新UI显示
|
||
self.label_status_label.setText(f"贴标: {status}")
|
||
|
||
# 只有当信号为贴标完成(1)时才进行处理
|
||
if signal == 1:
|
||
try:
|
||
modbus = ModbusUtils()
|
||
client = None
|
||
try:
|
||
# 获取Modbus客户端(现在使用连接池,不会每次都创建新连接)
|
||
client = modbus.get_client()
|
||
time.sleep(0.1)
|
||
if not client:
|
||
logging.error("无法获取Modbus客户端连接")
|
||
return
|
||
# 先将寄存器回复为0,否则复原周期内、会把新来的数据也pass
|
||
modbus.write_register_until_success(client, 13, 0)
|
||
except Exception as e:
|
||
logging.error(f"复原寄存器失败{e}")
|
||
finally:
|
||
client.close()
|
||
# 获取数据行数
|
||
if self.process_table.rowCount() <= 2: # 没有数据行
|
||
logging.warning("没有可用的数据行来写入贴标数据")
|
||
return
|
||
|
||
# 使用新的查找逻辑,确保顺序处理
|
||
data_row = self._find_next_row_to_process('weighed')
|
||
|
||
# 如果没有找到weighed状态的行,回退到原有逻辑
|
||
if data_row is None:
|
||
# 使用当前选中的行或第一个数据行
|
||
current_row = self.process_table.currentRow()
|
||
data_row = current_row if current_row >= 2 else 2 # 使用第一个数据行(索引为2)
|
||
logging.info(f"未找到状态为weighed的行,使用当前选中行或第一个数据行: {data_row}")
|
||
else:
|
||
logging.info(f"将使用状态为weighed的行: {data_row}")
|
||
|
||
# 设置当前处理行
|
||
self._current_processing_row = data_row
|
||
|
||
# 确保行存在
|
||
if data_row >= self.process_table.rowCount():
|
||
logging.warning(f"选中的行 {data_row} 超出了表格范围")
|
||
return
|
||
|
||
# 获取工程号
|
||
order_id_item = self.process_table.item(data_row, 1)
|
||
if not order_id_item:
|
||
logging.warning("无法获取工程号")
|
||
return
|
||
|
||
gc_note = order_id_item.text().strip()
|
||
if not gc_note:
|
||
logging.warning("工程号为空")
|
||
return
|
||
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 计算贴标列索引 - 贴标位置在检验列之后的第一列
|
||
label_col = 2 + len(enabled_configs)
|
||
|
||
# 生成贴标号(托盘号+轴号),优先使用当前行的轴号
|
||
axios_num = self.get_current_row_axios_num(data_row)
|
||
|
||
# 断开单元格变更信号,避免程序自动写入时触发
|
||
try:
|
||
self.process_table.cellChanged.disconnect(self.handle_inspection_cell_changed)
|
||
except:
|
||
pass
|
||
|
||
# 创建并设置贴标单元格
|
||
label_item = QTableWidgetItem(str(axios_num))
|
||
label_item.setTextAlignment(Qt.AlignCenter)
|
||
|
||
# 写入单元格
|
||
self.process_table.setItem(data_row, label_col, label_item)
|
||
logging.info(f"已将贴标数据 {axios_num} 写入表格单元格 [{data_row}, {label_col}]")
|
||
|
||
# 在这里添加保存贴标数据到数据库的代码
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, 11, 11, str(axios_num), "pass")
|
||
logging.info(f"已将贴标数据 {axios_num} 保存到数据库")
|
||
from dao.inspection_dao import InspectionDAO
|
||
from apis.gc_api import GcApi
|
||
inspection_dao = InspectionDAO()
|
||
# 更新产品状态为labeled
|
||
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'labeled')
|
||
logging.info(f"工程号 {gc_note} 的贴标已完成,状态更新为labeled")
|
||
|
||
# 获取当前行的轴号,用于保存到包装记录
|
||
current_axios_num = self.get_current_row_axios_num(data_row)
|
||
|
||
# 调用加载到包装记录的方法,传入正确的轴号
|
||
self.load_finished_record_to_package_record(self._current_order_code, gc_note, tray_id, current_axios_num)
|
||
logging.info(f"贴标完成,已将工程号 {gc_note} 的记录加载到包装记录,轴号: {current_axios_num}")
|
||
|
||
# 删除当前处理的行
|
||
self.process_table.removeRow(data_row)
|
||
logging.info(f"已删除处理完成的行 {data_row}")
|
||
|
||
# 清除当前处理行的跟踪
|
||
self._current_processing_row = None
|
||
|
||
# 复原寄存器 12 为 0
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
modbus.write_register_until_success(client, 12, 0)
|
||
modbus.close_client(client)
|
||
|
||
# 更新订单数量和产量统计数据
|
||
self.update_order_statistics()
|
||
|
||
# 更新包装记录统计数据
|
||
self.update_package_statistics()
|
||
|
||
# 重新连接单元格变更信号
|
||
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理贴标完成信号失败: {str(e)}")
|
||
# 确保信号重新连接
|
||
try:
|
||
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
||
except:
|
||
pass
|
||
|
||
@Slot(bool, str)
|
||
def handle_modbus_status_change(self, is_connected, message):
|
||
"""处理Modbus连接状态变化"""
|
||
if is_connected:
|
||
self.modbus_status_label.setText("Modbus: 已连接")
|
||
self.modbus_status_label.setStyleSheet("color: green;")
|
||
logging.info(f"Modbus已连接: {message}")
|
||
else:
|
||
self.modbus_status_label.setText("Modbus: 未连接")
|
||
self.modbus_status_label.setToolTip(message)
|
||
self.modbus_status_label.setStyleSheet("color: red;")
|
||
logging.warning(f"Modbus连接断开: {message}")
|
||
|
||
@Slot(int, str)
|
||
def handle_register_error(self, address, error_msg):
|
||
"""处理寄存器读取错误"""
|
||
logging.warning(f"[处理] 寄存器D{address}错误: {error_msg}")
|
||
# 在这里可以添加错误处理逻辑
|
||
pass
|
||
|
||
# 上料按钮样式控制方法
|
||
def restore_input_button_style(self):
|
||
"""恢复上料按钮的原始样式"""
|
||
try:
|
||
button_style = """
|
||
QPushButton {
|
||
padding: 8px 16px;
|
||
font-weight: bold;
|
||
border-radius: 4px;
|
||
border: 1px solid #2196f3;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #bbdefb;
|
||
}
|
||
"""
|
||
self.input_button.setStyleSheet(button_style)
|
||
logging.info("已恢复上料按钮原始样式")
|
||
except Exception as e:
|
||
logging.error(f"恢复上料按钮样式失败: {str(e)}")
|
||
|
||
def fill_input_button_style(self):
|
||
"""填充上料按钮样式 - 蓝色背景,白色字体"""
|
||
try:
|
||
button_style = """
|
||
QPushButton {
|
||
padding: 8px 16px;
|
||
font-weight: bold;
|
||
border-radius: 4px;
|
||
background-color: #2196F3;
|
||
color: white;
|
||
border: 1px solid #2196F3;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #1e88e5;
|
||
color: white;
|
||
}
|
||
"""
|
||
self.input_button.setStyleSheet(button_style)
|
||
logging.info("已填充上料按钮样式")
|
||
except Exception as e:
|
||
logging.error(f"填充上料按钮样式失败: {str(e)}")
|
||
|
||
# 下料按钮样式控制方法
|
||
def restore_output_button_style(self):
|
||
"""恢复下料按钮的原始样式"""
|
||
try:
|
||
button_style = """
|
||
QPushButton {
|
||
padding: 8px 16px;
|
||
font-weight: bold;
|
||
border-radius: 4px;
|
||
border: 1px solid #ffc107;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #ffecb3;
|
||
}
|
||
"""
|
||
self.output_button.setStyleSheet(button_style)
|
||
logging.info("已恢复下料按钮原始样式")
|
||
except Exception as e:
|
||
logging.error(f"恢复下料按钮样式失败: {str(e)}")
|
||
|
||
def fill_output_button_style(self):
|
||
"""填充下料按钮样式 - 黄色背景,白色字体"""
|
||
try:
|
||
button_style = """
|
||
QPushButton {
|
||
padding: 8px 16px;
|
||
font-weight: bold;
|
||
border-radius: 4px;
|
||
background-color: #FFC107;
|
||
color: white;
|
||
border: 1px solid #FFC107;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #ffb300;
|
||
color: white;
|
||
}
|
||
"""
|
||
self.output_button.setStyleSheet(button_style)
|
||
logging.info("已填充下料按钮样式")
|
||
except Exception as e:
|
||
logging.error(f"填充下料按钮样式失败: {str(e)}")
|
||
|
||
@Slot(int, int)
|
||
def handle_register_change(self, address, value):
|
||
"""处理寄存器变化"""
|
||
logging.info(f"[处理] 寄存器D{address}变化: {value}")
|
||
|
||
# D2寄存器控制上料按钮样式
|
||
if address == 2 and value == 0:
|
||
self.restore_input_button_style()
|
||
logging.info(f"D2寄存器为 0 ,恢复上料按钮样式")
|
||
elif address == 2 and value == 1:
|
||
self.fill_input_button_style()
|
||
self.fill_start_button_style()
|
||
logging.info(f"D2寄存器为 1 ,填充上料按钮样式")
|
||
elif address == 2 and value == 0 and address == 3 and value == 0:
|
||
self.restore_start_button_style()
|
||
logging.info(f"D2寄存器为 0 ,D3寄存器为 0 ,恢复开始按钮样式")
|
||
# D3寄存器控制下料按钮样式
|
||
elif address == 3 and value == 0:
|
||
self.restore_output_button_style()
|
||
logging.info(f"D3寄存器为 0 ,恢复下料按钮样式")
|
||
elif address == 3 and value == 1:
|
||
self.fill_output_button_style()
|
||
self.fill_start_button_style()
|
||
logging.info(f"D3寄存器为 1 ,填充下料按钮样式")
|
||
# 当D11寄存器变为0时,复位D10寄存器为0
|
||
elif address == 11 and value == 0:
|
||
try:
|
||
logging.info("检测到D11寄存器变为0,正在复位D10寄存器")
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
modbus.write_register_until_success(client, 10, 0)
|
||
logging.info("成功复位D10寄存器为0")
|
||
modbus.close_client(client)
|
||
except Exception as e:
|
||
logging.error(f"复位D10寄存器失败: {str(e)}")
|
||
@Slot(int, str)
|
||
def handle_loading_feedback(self, status, desc):
|
||
"""处理上料信息反馈"""
|
||
message = desc
|
||
try:
|
||
if status == 1:
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
# 睡 0.5 秒,用于延缓modbus 监听
|
||
time.sleep(0.5)
|
||
modbus.write_register_until_success(client, 2, 0)
|
||
if self._current_stow_num > 0:
|
||
completed_layer_num = self._current_stow_num
|
||
self._current_stow_num -= 1
|
||
if self._current_stow_num == 0:
|
||
self._is_loading_active = False # 任务完成,标记为非活动
|
||
self._loading_info = None
|
||
logging.info("所有层拆垛完成,清空上料信息")
|
||
message = f"第 {completed_layer_num} 层(最后一层)拆垛完成!"
|
||
# 重置寄存器 0 和 2 为 0
|
||
modbus.write_register_until_success(client, 0, 0)
|
||
modbus.write_register_until_success(client, 2, 0)
|
||
self.loading_feedback_signal.emit("input", message)
|
||
# 恢复开始按钮原始样式
|
||
self.restore_start_button_style()
|
||
else:
|
||
logging.info(f"当前层拆垛完成,剩余层数: {self._current_stow_num}")
|
||
message = f"第 {completed_layer_num} 层拆垛完成。"
|
||
self.loading_feedback_signal.emit("input", message)
|
||
#通知寄存器,进行第几层拆垛
|
||
modbus.write_register_until_success(client,0 ,self._current_stow_num)
|
||
except Exception as e:
|
||
logging.error(f"处理上料信息反馈失败: {str(e)}")
|
||
# 不在这里显示对话框,而是通过信号传递错误信息
|
||
self.loading_feedback_signal.emit("error", f"处理上料信息反馈失败: {str(e)}")
|
||
finally:
|
||
modbus.close_client(client)
|
||
|
||
def _handle_loading_feedback_ui(self, status_type, desc):
|
||
"""在主线程中处理上料UI更新"""
|
||
try:
|
||
# 如果上料任务仍在进行,更新层数显示
|
||
if self._loading_info and self._current_stow_num > 0:
|
||
self.show_operation_status("拆垛层数", "input", str(self._current_stow_num))
|
||
else:
|
||
# 上料任务完成,恢复上料按钮样式
|
||
self.restore_input_button_style()
|
||
logging.info("上料任务完成,恢复上料按钮样式")
|
||
|
||
QMessageBox.information(self, "上料操作", desc)
|
||
except Exception as e:
|
||
logging.error(f"处理上料UI更新失败: {str(e)}")
|
||
|
||
@Slot(int, str)
|
||
def handle_unloading_feedback(self, status, desc):
|
||
"""处理下料信息反馈"""
|
||
logging.info(f"[处理] 下料信息: {status}, {desc}")
|
||
# 如果下料完成(status=1),显示状态信息,处理下料流程
|
||
if status == 1:
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
|
||
try:
|
||
# 睡 0.5 秒,用于延缓modbus 监听
|
||
time.sleep(0.5)
|
||
# 临时重置寄存器3(下料启动)为0,等待用户下一次启动
|
||
modbus.write_register_until_success(client, 3, 0)
|
||
|
||
# 如果当前下料层数小于总层数,则将层数加1并写入寄存器4
|
||
# 保存当前完成的层数用于消息显示
|
||
completed_tier = self._current_unload_num
|
||
|
||
# 当前层已完成,层数加1表示开始下一层
|
||
self._current_unload_num += 1
|
||
logging.info(f"当前层{completed_tier}下料完成,更新到下一层:当前={self._current_unload_num}, 总数={self._total_unload_num}")
|
||
|
||
# 将新的层数写入寄存器4
|
||
modbus.write_register_until_success(client, 4, self._current_unload_num)
|
||
logging.info(f"已将新层数{self._current_unload_num}写入寄存器4")
|
||
|
||
# 不直接更新UI,而是通过信号将数据传递给主线程处理
|
||
# 通过信号触发UI更新 - 显示前一层完成的消息
|
||
message = f"第{completed_tier}层下料完成,请启动第{self._current_unload_num}层下料"
|
||
self.unloading_feedback_signal.emit("output", message)
|
||
|
||
# 恢复开始按钮原始样式
|
||
self.restore_start_button_style()
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理下料反馈时发生错误: {str(e)}")
|
||
# 不在这里显示对话框,而是通过信号传递错误信息
|
||
self.unloading_feedback_signal.emit("error", f"处理下料反馈失败: {str(e)}")
|
||
finally:
|
||
modbus.close_client(client)
|
||
|
||
def _handle_unloading_feedback_ui(self, status_type, desc):
|
||
"""在主线程中处理下料完成的事件通知"""
|
||
try:
|
||
if status_type == "error":
|
||
QMessageBox.critical(self, "错误", desc)
|
||
return
|
||
|
||
# 显示事件消息
|
||
if "全部完成" in desc:
|
||
self.restore_output_button_style()
|
||
logging.info("下料任务完成,恢复下料按钮样式")
|
||
elif "请启动" in desc:
|
||
QMessageBox.information(self, "下料层完成", desc)
|
||
|
||
# 如果当前下料信息存在且层数有效,更新UI显示
|
||
if self._current_unload_info and self._current_unload_num > 0:
|
||
self.show_operation_status("下料层数", "output", f"{self._current_unload_num}/{self._total_unload_num}")
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理下料UI更新失败: {str(e)}")
|
||
|
||
def _update_error_status(self):
|
||
"""更新故障状态显示"""
|
||
# 收集所有故障信息
|
||
error_codes = [
|
||
getattr(self, 'error_1', 0),
|
||
getattr(self, 'error_2', 0),
|
||
getattr(self, 'error_3', 0)
|
||
]
|
||
|
||
# 检查是否有故障
|
||
has_error = any(code > 0 for code in error_codes)
|
||
|
||
if has_error:
|
||
# 收集所有错误信息
|
||
errors = []
|
||
error_map = self.machine_handlers.error_map
|
||
|
||
if getattr(self, 'error_1', 0) > 0:
|
||
errors.append(f"故障1: {error_map.get(self.error_1, '未知')}")
|
||
if getattr(self, 'error_2', 0) > 0:
|
||
errors.append(f"故障2: {error_map.get(self.error_2, '未知')}")
|
||
if getattr(self, 'error_3', 0) > 0:
|
||
errors.append(f"故障3: {error_map.get(self.error_3, '未知')}")
|
||
|
||
self.error_status_label.setText("故障: 有")
|
||
self.error_status_label.setToolTip("\n".join(errors))
|
||
self.error_status_label.setStyleSheet("color: red; font-weight: bold;")
|
||
else:
|
||
self.error_status_label.setText("故障: 无")
|
||
self.error_status_label.setToolTip("")
|
||
self.error_status_label.setStyleSheet("color: green; font-weight: bold;")
|
||
|
||
@Slot(int, str)
|
||
def handle_error_1(self, error_code, error_desc):
|
||
"""机器人视觉报警"""
|
||
logging.info(f"[处理] 机器人视觉报警: {error_desc}")
|
||
from utils.register_handlers import Error1Handler
|
||
error_handler = Error1Handler()
|
||
detailed_desc = error_handler.error_map.get(error_code, f"机器人视觉报警-{error_code}")
|
||
|
||
# 保存故障码
|
||
self.error_1 = error_code
|
||
self._update_error_status()
|
||
|
||
# 只有当错误码为1、2或3时才弹框提示
|
||
if error_code in [1, 2, 3]:
|
||
QMessageBox.warning(self, "机器人视觉报警", f"机器人视觉报警: {detailed_desc}")
|
||
# 获取Modbus连接
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
modbus.close_client(client)
|
||
|
||
@Slot(int, str)
|
||
def handle_error_2(self, error_code, error_desc):
|
||
"""滚筒线报警"""
|
||
logging.info(f"[处理] 滚筒线报警: {error_desc}")
|
||
from utils.register_handlers import Error2Handler
|
||
error_handler = Error2Handler()
|
||
detailed_desc = error_handler.error_map.get(error_code, f"滚筒线报警-{error_code}")
|
||
# 保存故障码
|
||
self.error_2 = error_code
|
||
self._update_error_status()
|
||
|
||
# 如果有故障,显示提示(对任何错误码都弹框)
|
||
if error_code in [1, 2]:
|
||
QMessageBox.warning(self, "滚筒线报警", f"滚筒线报警: {detailed_desc}")
|
||
# 获取Modbus连接
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
|
||
modbus.close_client(client)
|
||
|
||
@Slot(int, str)
|
||
def handle_error_3(self, error_code, error_desc):
|
||
"""拆码垛报警"""
|
||
logging.info(f"[处理] 拆码垛报警: {error_desc}")
|
||
from utils.register_handlers import Error3Handler
|
||
error_handler = Error3Handler()
|
||
detailed_desc = error_handler.error_map.get(error_code, f"拆码垛报警-{error_code}")
|
||
# 保存故障码
|
||
self.error_3 = error_code
|
||
self._update_error_status()
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
# 如果有故障,显示提示
|
||
if error_code == 1:
|
||
QMessageBox.warning(self, "异常", f"异常: {detailed_desc}")
|
||
modbus.write_register_until_success(client, 2, 0)
|
||
modbus.write_register_until_success(client, 0, 0)
|
||
modbus.close_client(client)
|
||
# 移除在下料区域显示异常信息的代码
|
||
elif error_code == 2:
|
||
QMessageBox.warning(self, "异常", f"异常: {detailed_desc}")
|
||
modbus.write_register_until_success(client, 3, 0)
|
||
modbus.write_register_until_success(client, 4, 0)
|
||
modbus.close_client(client)
|
||
|
||
@Slot(int)
|
||
def handle_unloading_level(self, level):
|
||
"""处理下料层数信息(来自Modbus)"""
|
||
# 只在层数发生变化时记录日志
|
||
if self._current_unload_num != level:
|
||
logging.info(f"下料层数变化:{self._current_unload_num} -> {level}")
|
||
|
||
# 更新当前层数
|
||
self._current_unload_num = level
|
||
|
||
# 更新保存的下料信息中的当前层数值
|
||
if self._current_unload_info:
|
||
self._current_unload_info['tier'] = str(level)
|
||
|
||
# 通过信号在主线程中更新UI
|
||
self.unloading_level_ui_signal.emit(level)
|
||
else:
|
||
# 即使层数相同,也更新UI以确保显示正确
|
||
self.unloading_level_ui_signal.emit(level)
|
||
|
||
@Slot(int)
|
||
def handle_unloading_level_ui(self, level):
|
||
"""在主线程中更新下料层数UI"""
|
||
try:
|
||
# 更新显示
|
||
if hasattr(self, 'unload_level_label'):
|
||
self.unload_level_label.setText(f"下料层数:{level}")
|
||
|
||
# 如果有下料信息且层数大于0,更新右上角显示
|
||
if level > 0 and self._current_unload_info:
|
||
# 确保使用固定的总层数
|
||
total_tier = self._total_unload_num
|
||
|
||
# 更新右上角显示,使用实际层数值
|
||
self.show_operation_status("下料层数", "output", f"{level}/{total_tier}")
|
||
logging.info(f"更新右上角下料层数显示:{level}/{total_tier}")
|
||
except Exception as e:
|
||
logging.error(f"更新下料层数UI失败: {str(e)}")
|
||
|
||
@Slot(int)
|
||
def handle_unloading_position(self, position):
|
||
"""处理下料位置信息"""
|
||
# 通过信号在主线程中更新UI
|
||
self.unloading_position_ui_signal.emit(position)
|
||
|
||
@Slot(int)
|
||
def handle_unloading_position_ui(self, position):
|
||
"""在主线程中更新下料位置UI"""
|
||
try:
|
||
if hasattr(self, 'unload_position_label'):
|
||
self.unload_position_label.setText(f"下料位置: {position}")
|
||
logging.debug(f"更新下料位置显示: {position}")
|
||
except Exception as e:
|
||
logging.error(f"更新下料位置UI失败: {str(e)}")
|
||
|
||
@Slot(int)
|
||
def handle_ng(self, ng):
|
||
"""处理NG信号, 将当前处理的数据添加到包装记录中,毛重和净重设为0"""
|
||
if ng == 1:
|
||
try:
|
||
# 获取最后一条数据行
|
||
total_rows = self.process_table.rowCount()
|
||
if total_rows <= 2: # 只有表头行,没有数据行
|
||
logging.warning("没有可用的数据行来处理NG信号")
|
||
return
|
||
|
||
# 使用最后一条数据行
|
||
data_row = total_rows - 1
|
||
|
||
# 获取工程号
|
||
order_id_item = self.process_table.item(data_row, 1)
|
||
if not order_id_item:
|
||
logging.warning("无法获取工程号")
|
||
return
|
||
|
||
order_id = order_id_item.text().strip()
|
||
if not order_id:
|
||
logging.warning("工程号为空")
|
||
return
|
||
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 计算贴标列索引
|
||
label_col = 2 + len(enabled_configs)
|
||
|
||
# 获取贴标值
|
||
label_item = self.process_table.item(data_row, label_col)
|
||
label_value = label_item.text() if label_item else ""
|
||
|
||
# 如果贴标值为空,生成一个新的贴标值
|
||
if not label_value:
|
||
# 初始化托盘号对应的序号
|
||
if tray_id not in self.init_seq:
|
||
self.init_seq[tray_id] = 1
|
||
|
||
# 生成贴标号(托盘号+序号)
|
||
label_value = f"{self.init_seq[tray_id]}-NG"
|
||
self.init_seq[tray_id] += 1
|
||
|
||
# 保存贴标数据到数据库
|
||
self.save_inspection_data(order_id, tray_id, 11, 11, label_value, "pass")
|
||
else:
|
||
# 如果贴标值已存在但不包含NG标记,添加NG标记
|
||
if "NG" not in label_value:
|
||
label_value = f"{label_value}-NG"
|
||
# 更新贴标数据
|
||
self.save_inspection_data(order_id, tray_id, 11, 11, label_value, "pass")
|
||
|
||
# 设置毛重和净重为0
|
||
self.save_inspection_data(order_id, tray_id, 12, 12, "0", "pass")
|
||
self.save_inspection_data(order_id, tray_id, 13, 13, "0", "pass")
|
||
|
||
# 获取当前时间作为完成时间
|
||
finish_time = datetime.now()
|
||
|
||
# 将数据写入到数据库表 inspection_pack_data
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
inspection_dao.save_package_record(order_id, tray_id, label_value, "0", "0", finish_time)
|
||
|
||
# 删除当前处理的行
|
||
self.process_table.removeRow(data_row)
|
||
|
||
# 回显数据
|
||
self.show_pack_item()
|
||
|
||
# 更新订单数量和产量统计数据
|
||
self.update_order_statistics()
|
||
|
||
# 更新包装记录统计数据
|
||
self.update_package_statistics()
|
||
|
||
logging.info(f"NG信号处理完成: 工程号={order_id}, 托盘号={tray_id}, 贴标值={label_value}")
|
||
except Exception as e:
|
||
logging.error(f"处理NG信号时发生错误: {str(e)}")
|
||
finally:
|
||
# 复原NG信号
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
modbus.write_register_until_success(client, 6, 0)
|
||
modbus.close_client(client)
|
||
|
||
def register_serial_callbacks(self):
|
||
"""注册串口数据回调函数"""
|
||
try:
|
||
# 注册米电阻数据回调
|
||
self.serial_manager.callbacks['mdz_data'] = self.on_mdz_data_received
|
||
|
||
# 注册线径数据回调
|
||
self.serial_manager.callbacks['xj_data'] = self.on_diameter_data_received
|
||
|
||
# 注册扫码器数据回调
|
||
self.serial_manager.callbacks['scanner_data'] = self.on_scanner_data_received
|
||
|
||
# 自动打开已配置的串口
|
||
self.serial_manager.auto_open_configured_ports()
|
||
|
||
logging.info("已注册串口数据回调函数")
|
||
except Exception as e:
|
||
logging.error(f"注册串口数据回调函数失败: {str(e)}")
|
||
|
||
def on_mdz_data_received(self, port_name, data):
|
||
"""米电阻数据接收回调函数
|
||
|
||
Args:
|
||
port_name: 串口名称
|
||
data: 接收到的数据
|
||
"""
|
||
try:
|
||
# 解析数据
|
||
data_str = data.decode('utf-8') if isinstance(data, bytes) else str(data)
|
||
logging.info(f"收到米电阻数据: {data_str} 来自 {port_name}")
|
||
|
||
# 提取米电阻值,格式为"米电阻数据: xxx"
|
||
if "米电阻数据:" in data_str:
|
||
value_str = data_str.split("米电阻数据:")[1].strip()
|
||
try:
|
||
# 转换为浮点数
|
||
mdz_value = float(value_str)
|
||
|
||
# 查找米电阻对应的检验项配置
|
||
mdz_config = None
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
for config in enabled_configs:
|
||
if config.get('name') == 'mdz' or config.get('display_name') == '米电阻':
|
||
mdz_config = config
|
||
break
|
||
|
||
if mdz_config:
|
||
# 找到对应的检验项,将数据写入对应的单元格
|
||
self.set_inspection_value('mdz', mdz_config, mdz_value)
|
||
else:
|
||
logging.warning("未找到米电阻对应的检验项配置")
|
||
|
||
except ValueError:
|
||
logging.warning(f"米电阻数据格式错误: {value_str}")
|
||
else:
|
||
logging.warning(f"收到的数据不包含米电阻数据标记: {data_str}")
|
||
except Exception as e:
|
||
logging.error(f"处理米电阻数据失败: {str(e)}")
|
||
|
||
def on_diameter_data_received(self, port_name, data):
|
||
"""线径数据接收回调函数 - 采用类似称重的逻辑,不使用会话机制"""
|
||
# 添加处理锁,避免并发处理导致卡死
|
||
if hasattr(self, '_processing_diameter_lock') and self._processing_diameter_lock:
|
||
logging.warning(f"已有线径数据处理进行中,忽略本次请求")
|
||
return
|
||
|
||
# 设置处理锁,并添加超时保护
|
||
self._processing_diameter_lock = True
|
||
self._diameter_processing_start_time = time.time()
|
||
|
||
# 添加超时检测定时器
|
||
if hasattr(self, '_diameter_timeout_timer') and self._diameter_timeout_timer is not None:
|
||
self._diameter_timeout_timer.stop()
|
||
|
||
self._diameter_timeout_timer = QTimer()
|
||
self._diameter_timeout_timer.setSingleShot(True)
|
||
self._diameter_timeout_timer.timeout.connect(self._reset_diameter_processing_lock)
|
||
self._diameter_timeout_timer.start(5000) # 5秒超时保护
|
||
|
||
modbus_client = None
|
||
try:
|
||
data_str = data.decode('utf-8') if isinstance(data, bytes) else str(data)
|
||
logging.info(f"收到线径数据: {data_str} 来自 {port_name}")
|
||
|
||
# 提取线径值
|
||
if "线径数据:" in data_str:
|
||
value_str = data_str.split("线径数据:")[1].strip()
|
||
try:
|
||
xj_value = round(float(value_str)/10000, 3)
|
||
|
||
# 更新最后处理的线径数据和时间 - 只记录,不进行防抖过滤
|
||
current_time = time.time()
|
||
self._last_diameter_value = xj_value
|
||
self._last_diameter_time = current_time
|
||
|
||
self.statusBar().showMessage(f"线径数据: {xj_value:.3f}", 2000)
|
||
|
||
# 查找线径对应的检验项配置和列
|
||
xj_config = None
|
||
xj_column = None
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
for i, config in enumerate(enabled_configs):
|
||
if config.get('name') == 'xj' or config.get('display_name') == '线径':
|
||
xj_config = config
|
||
xj_column = 2 + i
|
||
break
|
||
if not xj_config or xj_column is None:
|
||
logging.warning("未找到线径对应的检验项配置或列索引")
|
||
return
|
||
|
||
# 忽略接近0的值或异常值
|
||
if xj_value < 0.001 or xj_value > 10:
|
||
logging.info(f"忽略异常线径值: {xj_value}")
|
||
return
|
||
|
||
# 保存测量值到内部列表用于稳定性检测
|
||
# 使用类属性存储最近的测量值,用于稳定性检测
|
||
if not hasattr(self, '_diameter_measurements'):
|
||
self._diameter_measurements = []
|
||
|
||
# 添加当前测量值到列表
|
||
self._diameter_measurements.append(xj_value)
|
||
|
||
# 保留最近的10个测量值,增加缓冲区大小以便更快收集足够的数据
|
||
if len(self._diameter_measurements) > 10:
|
||
self._diameter_measurements.pop(0)
|
||
|
||
# 显示临时值到状态栏
|
||
if len(self._diameter_measurements) < 5:
|
||
self.statusBar().showMessage(f"线径数据收集中: {xj_value:.3f} ({len(self._diameter_measurements)}/5)", 2000)
|
||
return
|
||
|
||
# 检查稳定性 - 使用最近的5个测量值
|
||
measurements = self._diameter_measurements[-5:]
|
||
min_value = min(measurements)
|
||
max_value = max(measurements)
|
||
avg_value = sum(measurements) / len(measurements)
|
||
error_range = avg_value * 0.05 # 允许5%误差,增加容错范围
|
||
|
||
if max_value - min_value <= error_range:
|
||
# 数据稳定,可以保存
|
||
final_value = avg_value # 使用平均值作为最终值
|
||
|
||
# 查找第一个没有线径数据的行
|
||
data_row = None
|
||
|
||
# 如果有最近处理的线径工程号,优先查找该工程号对应的行
|
||
if hasattr(self, '_last_processed_gc_note') and self._last_processed_gc_note:
|
||
for row in range(2, self.process_table.rowCount()):
|
||
order_id_item = self.process_table.item(row, 1)
|
||
if order_id_item and order_id_item.text().strip() == self._last_processed_gc_note:
|
||
# 检查该行的线径单元格是否为空
|
||
cell_item = self.process_table.item(row, xj_column)
|
||
if not cell_item or not cell_item.text().strip() or cell_item.text().strip() == '0':
|
||
data_row = row
|
||
logging.info(f"找到最近处理的线径工程号 {self._last_processed_gc_note} 对应的行: {data_row}")
|
||
break
|
||
|
||
# 如果没有找到最近处理的工程号对应的行,再查找第一个没有线径数据的行
|
||
if data_row is None:
|
||
for row in range(2, self.process_table.rowCount()):
|
||
cell_item = self.process_table.item(row, xj_column)
|
||
if not cell_item or not cell_item.text().strip() or cell_item.text().strip() == '0':
|
||
data_row = row
|
||
break
|
||
|
||
# 如果没找到空行,使用当前选中行或第一个数据行
|
||
if data_row is None:
|
||
current_row = self.process_table.currentRow()
|
||
data_row = current_row if current_row >= 2 else 2
|
||
logging.info(f"未找到没有线径数据的行,使用当前选中行或第一个数据行: {data_row}")
|
||
else:
|
||
logging.info(f"找到没有线径数据的行: {data_row}")
|
||
|
||
# 获取工程号
|
||
gc_note_item = self.process_table.item(data_row, 1)
|
||
if not gc_note_item:
|
||
logging.warning("无法获取工程号")
|
||
return
|
||
|
||
gc_note = gc_note_item.text().strip()
|
||
if not gc_note:
|
||
logging.warning("工程号为空")
|
||
return
|
||
|
||
# 记录最近处理的线径工程号,用于后续线径数据处理
|
||
self._last_processed_gc_note = gc_note
|
||
logging.info(f"记录最近处理线径数据的工程号: {gc_note}")
|
||
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 检查线径公差范围 - 基于前端【线径公差】字段
|
||
if "线径公差" in self.info_values and self.info_values["线径公差"] is not None:
|
||
try:
|
||
xjgc_value = self.info_values["线径公差"].text().strip()
|
||
if xjgc_value and "-" in xjgc_value:
|
||
# 解析线径公差范围,格式如 "0.157 - 0.163",忽略两边空格后分割
|
||
parts = xjgc_value.split("-")
|
||
if len(parts) == 2:
|
||
min_diameter = float(parts[0].strip())
|
||
max_diameter = float(parts[1].strip())
|
||
|
||
# 检查线径值是否在范围内
|
||
if final_value < min_diameter or final_value > max_diameter:
|
||
|
||
|
||
# 使用信号触发弹框显示 - 避免主线程阻塞
|
||
logging.warning(f"线径值 {final_value:.3f}mm 超出线径公差范围 ({min_diameter:.3f} - {max_diameter:.3f}mm),发送信号显示警告")
|
||
try:
|
||
self.diameter_alert_signal.emit(final_value, min_diameter, max_diameter)
|
||
except Exception as e:
|
||
logging.error(f"发送线径警告信号失败: {str(e)}", exc_info=True)
|
||
|
||
# 重置测量列表,防止重复触发
|
||
self._diameter_measurements = []
|
||
|
||
# 阻止继续执行,等待用户处理
|
||
logging.warning(f"线径值 {final_value:.3f}mm 超出线径公差范围,已阻止保存")
|
||
return
|
||
else:
|
||
logging.info(f"线径值 {final_value:.3f}mm 在线径公差范围内 ({min_diameter:.3f} - {max_diameter:.3f}mm)")
|
||
else:
|
||
logging.debug(f"线径公差格式不正确: '{xjgc_value}'")
|
||
else:
|
||
logging.debug(f"线径公差格式不正确或为空,跳过范围检查: '{xjgc_value}'")
|
||
except (ValueError, AttributeError) as e:
|
||
logging.warning(f"解析线径公差失败,跳过范围检查: {str(e)}")
|
||
else:
|
||
logging.debug("线径公差字段不存在或为空,跳过范围检查")
|
||
|
||
# 保存线径数据到表格
|
||
try:
|
||
# 使用set_inspection_value保存数据
|
||
self.set_inspection_value('xj', xj_config, final_value)
|
||
logging.info(f"已将稳定的线径值 {final_value:.3f} 保存到工程号 {gc_note} (行 {data_row})")
|
||
|
||
# 重置测量列表,准备下一次测量
|
||
self._diameter_measurements = []
|
||
except Exception as e:
|
||
logging.error(f"保存线径数据到表格失败: {str(e)}")
|
||
else:
|
||
# 数据不稳定,继续收集
|
||
self.statusBar().showMessage(f"线径数据不稳定: {min_value:.3f} - {max_value:.3f}, 继续测量", 2000)
|
||
logging.warning(f"线径测量数据不稳定,范围: {min_value:.3f} - {max_value:.3f}, 平均值: {avg_value:.3f}")
|
||
|
||
except ValueError:
|
||
logging.warning(f"线径数据格式错误: {value_str}")
|
||
else:
|
||
logging.warning(f"收到的数据不包含线径数据标记: {data_str}")
|
||
except Exception as e:
|
||
logging.error(f"处理线径数据失败: {str(e)}")
|
||
import traceback
|
||
logging.error(f"错误详情: {traceback.format_exc()}")
|
||
finally:
|
||
# 确保关闭Modbus客户端连接
|
||
if modbus_client:
|
||
try:
|
||
ModbusUtils().close_client(modbus_client)
|
||
except Exception as e:
|
||
logging.error(f"关闭Modbus客户端连接失败: {str(e)}")
|
||
|
||
# 释放处理锁
|
||
if hasattr(self, '_processing_diameter_lock'):
|
||
self._processing_diameter_lock = False
|
||
|
||
# 停止超时定时器
|
||
if hasattr(self, '_diameter_timeout_timer') and self._diameter_timeout_timer is not None:
|
||
self._diameter_timeout_timer.stop()
|
||
|
||
def _save_diameter_to_order(self, order_id, config, value):
|
||
"""基于工程号保存线径值,确保即使行号变化也能保存到正确的产品"""
|
||
try:
|
||
# 查找工程号对应的行
|
||
target_row = None
|
||
for row in range(2, self.process_table.rowCount()):
|
||
order_id_item = self.process_table.item(row, 1)
|
||
if order_id_item and order_id_item.text().strip() == order_id:
|
||
target_row = row
|
||
break
|
||
|
||
if target_row is not None:
|
||
# 使用set_inspection_value保存数据
|
||
self.set_inspection_value('xj', config, value)
|
||
logging.info(f"已将线径值 {value:.3f} 保存到工程号 {order_id} (行 {target_row})")
|
||
else:
|
||
logging.warning(f"找不到工程号 {order_id} 对应的行,无法保存线径值")
|
||
except Exception as e:
|
||
logging.error(f"保存线径值到工程号失败: {str(e)}")
|
||
|
||
def _reset_diameter_session(self):
|
||
"""重置线径测量会话状态"""
|
||
self._diameter_session_active = False
|
||
self._diameter_session_target_row = None
|
||
self._diameter_session_order_id = None
|
||
self._diameter_session_measurements = []
|
||
self._diameter_session_start_time = 0
|
||
|
||
@Slot(float, str, str)
|
||
def show_diameter_warning(self, final_value, bccd, tccd):
|
||
"""显示线径超出范围警告(在主线程中执行)- 为避免冲突,此方法已弃用,仅保留接口"""
|
||
# 这个方法已经被新的线径处理逻辑取代,为避免冲突,此处不再显示弹框
|
||
# 仅记录日志,保持接口兼容性
|
||
logging.info(f"线径警告信号已接收,但弹框显示已被新逻辑取代: 线径值 {final_value:.3f}mm, 范围 {bccd} - {tccd}")
|
||
return
|
||
|
||
def on_scanner_data_received(self, port_name, data):
|
||
"""扫码器数据接收回调函数
|
||
|
||
Args:
|
||
port_name: 串口名称
|
||
data: 接收到的数据
|
||
"""
|
||
try:
|
||
# 解析数据
|
||
data_str = data.decode('utf-8') if isinstance(data, bytes) else str(data)
|
||
logging.info(f"收到扫码器数据: {data_str} 来自 {port_name}")
|
||
|
||
# 提取扫码数据,格式为"扫码数据: xxx"
|
||
if "扫码数据:" in data_str:
|
||
scan_data = data_str.split("扫码数据:")[1].strip()
|
||
logging.info(f"提取到扫码数据: {scan_data}")
|
||
|
||
# 防抖机制:记录上次扫码的数据和时间
|
||
current_time = time.time()
|
||
last_scan_time = getattr(self, '_last_scan_time', 0)
|
||
last_scan_data = getattr(self, '_last_scan_data', '')
|
||
|
||
# 如果是相同的数据且间隔小于1秒,则忽略此次扫码
|
||
if last_scan_data == scan_data and current_time - last_scan_time < 1.0:
|
||
logging.info(f"忽略重复扫码数据: {scan_data},间隔太短")
|
||
return
|
||
|
||
# 更新最后扫码的数据和时间
|
||
self._last_scan_data = scan_data
|
||
self._last_scan_time = current_time
|
||
|
||
# 使用焦点跟踪器设置文本并触发回车事件
|
||
from utils.focus_tracker import FocusTracker
|
||
focus_tracker = FocusTracker.get_instance()
|
||
|
||
if focus_tracker.set_text_and_trigger_enter(scan_data):
|
||
logging.info(f"已将扫码数据 '{scan_data}' 设置到当前焦点输入框并触发回车事件")
|
||
else:
|
||
# 如果没有焦点输入框,则默认设置到工程号输入框
|
||
logging.info(f"没有焦点输入框,默认将扫码数据设置到工程号输入框")
|
||
|
||
# 临时断开returnPressed信号连接,避免setText触发信号
|
||
try:
|
||
self.order_edit.returnPressed.disconnect(self.handle_order_enter)
|
||
except Exception:
|
||
logging.debug("returnPressed信号未连接或断开失败")
|
||
|
||
# 设置工程号到输入框
|
||
self.order_edit.setText(scan_data)
|
||
|
||
# 重新连接returnPressed信号
|
||
self.order_edit.returnPressed.connect(self.handle_order_enter)
|
||
|
||
# 调用一次handle_order_enter方法
|
||
self.handle_order_enter()
|
||
else:
|
||
logging.warning(f"收到的数据不包含扫码数据标记: {data_str}")
|
||
except Exception as e:
|
||
logging.error(f"处理扫码器数据失败: {str(e)}")
|
||
# 确保信号重新连接
|
||
try:
|
||
self.order_edit.returnPressed.connect(self.handle_order_enter)
|
||
except Exception:
|
||
pass
|
||
|
||
def set_inspection_value(self, data_type, config, value):
|
||
"""设置检验项目值到表格中
|
||
|
||
Args:
|
||
data_type: 数据类型,'mdz'表示米电阻,'xj'表示线径
|
||
config: 检验项配置
|
||
value: 检验值
|
||
"""
|
||
# 添加防递归锁,避免死循环
|
||
if hasattr(self, '_set_inspection_value_lock') and self._set_inspection_value_lock:
|
||
logging.warning(f"检测到递归调用set_inspection_value,忽略本次调用: {data_type}={value}")
|
||
return
|
||
|
||
self._set_inspection_value_lock = True
|
||
|
||
try:
|
||
# 获取检验项的列索引
|
||
config_id = config.get('id')
|
||
config_position = config.get('position')
|
||
col_index = None
|
||
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 根据检验项配置查找对应的列索引
|
||
for i, cfg in enumerate(enabled_configs):
|
||
if cfg.get('id') == config_id:
|
||
col_index = 2 + i # 检验列从第3列开始
|
||
break
|
||
|
||
if col_index is None:
|
||
logging.warning(f"未找到{data_type}对应的列索引")
|
||
return
|
||
|
||
# 检查表格是否有数据行
|
||
if self.process_table.rowCount() <= 2: # 只有表头行
|
||
order_id = self.order_edit.text().strip()
|
||
if order_id:
|
||
# 设置当前正在处理的工程号,确保它在重新加载时显示在最后
|
||
self._current_gc_note = order_id
|
||
self._current_gc_note_timestamp = time.time()
|
||
logging.info(f"在set_inspection_value中设置最新扫码的工程号: {order_id}, 时间戳: {self._current_gc_note_timestamp}")
|
||
self.add_new_inspection_row(order_id, self._current_order_code)
|
||
data_row = 2 # 新添加的行
|
||
else:
|
||
logging.warning("无法添加新行,订单号为空")
|
||
return
|
||
|
||
# 对于线径数据,如果有活跃会话,优先使用会话锁定的工程号
|
||
if data_type == 'xj' and hasattr(self, '_diameter_session_active') and self._diameter_session_active and self._diameter_session_order_id:
|
||
# 查找会话工程号对应的行
|
||
data_row = None
|
||
for row in range(2, self.process_table.rowCount()):
|
||
order_id_item = self.process_table.item(row, 1)
|
||
if order_id_item and order_id_item.text().strip() == self._diameter_session_order_id:
|
||
data_row = row
|
||
break
|
||
|
||
if data_row is not None:
|
||
logging.info(f"使用线径会话锁定的工程号 {self._diameter_session_order_id} 对应的行 {data_row}")
|
||
else:
|
||
logging.warning(f"找不到线径会话锁定的工程号 {self._diameter_session_order_id} 对应的行,将使用默认行选择逻辑")
|
||
# 继续使用默认逻辑
|
||
data_row = None
|
||
else:
|
||
# 默认行选择逻辑
|
||
data_row = None
|
||
|
||
# 如果没有找到特定行,使用默认逻辑查找第一个没有该检测数据的行
|
||
if data_row is None:
|
||
# 新增:检查是否有最近添加的工程号,如果有,优先保存到之前的行
|
||
if data_type == 'xj' and hasattr(self, '_last_processed_gc_note') and self._last_processed_gc_note:
|
||
# 查找最近处理的工程号对应的行
|
||
for row in range(2, self.process_table.rowCount()):
|
||
order_id_item = self.process_table.item(row, 1)
|
||
if order_id_item and order_id_item.text().strip() == self._last_processed_gc_note:
|
||
# 检查该行的线径单元格是否为空
|
||
cell_item = self.process_table.item(row, col_index)
|
||
if not cell_item or not cell_item.text().strip() or cell_item.text().strip() == '0':
|
||
data_row = row
|
||
logging.info(f"找到最近处理的工程号 {self._last_processed_gc_note} 对应的空线径单元格,行: {data_row}")
|
||
break
|
||
|
||
# 如果没有找到最近处理的工程号对应的行,或者该行已有线径数据,再查找第一个没有该检测数据的行
|
||
if data_row is None:
|
||
for row in range(2, self.process_table.rowCount()):
|
||
cell_item = self.process_table.item(row, col_index)
|
||
if not cell_item or not cell_item.text().strip() or (data_type == 'xj' and cell_item.text().strip() == '0'):
|
||
data_row = row
|
||
break
|
||
|
||
# 如果没有找到没有该检测数据的行,使用当前选中行或第一个数据行
|
||
if data_row is None:
|
||
current_row = self.process_table.currentRow()
|
||
data_row = current_row if current_row >= 2 else 2 # 使用第一个数据行(索引为2)
|
||
logging.info(f"未找到没有{data_type}数据的行,使用当前选中行或第一个数据行: {data_row}")
|
||
else:
|
||
logging.info(f"找到没有{data_type}数据的行: {data_row}")
|
||
|
||
# 获取工程号
|
||
order_id_item = self.process_table.item(data_row, 1)
|
||
if not order_id_item:
|
||
logging.warning("无法获取工程号")
|
||
return
|
||
|
||
order_id = order_id_item.text().strip()
|
||
if not order_id:
|
||
logging.warning("工程号为空")
|
||
return
|
||
|
||
# 记录最近处理的工程号,用于后续线径数据处理
|
||
if data_type == 'xj':
|
||
self._last_processed_gc_note = order_id
|
||
logging.info(f"记录最近处理线径数据的工程号: {order_id}")
|
||
|
||
# 暂时断开信号连接,避免触发cellChanged信号
|
||
try:
|
||
self.process_table.cellChanged.disconnect(self.handle_inspection_cell_changed)
|
||
except:
|
||
pass
|
||
|
||
# 格式化值并设置单元格
|
||
formatted_value = str(value)
|
||
if config.get('data_type') == 'number':
|
||
# 格式化数字,线径保留3位小数,其他保留2位小数
|
||
if data_type == 'xj':
|
||
formatted_value = f"{value:.3f}"
|
||
else:
|
||
formatted_value = f"{value:.2f}"
|
||
|
||
# 设置单元格值
|
||
item = QTableWidgetItem(formatted_value)
|
||
item.setTextAlignment(Qt.AlignCenter)
|
||
item.setData(Qt.UserRole, config_id) # 保存配置ID,用于识别检验项
|
||
self.process_table.setItem(data_row, col_index, item)
|
||
|
||
# 验证数据是否在有效范围内
|
||
status = "pass"
|
||
if config.get('data_type') == 'number':
|
||
min_value = config.get('min_value')
|
||
max_value = config.get('max_value')
|
||
if (min_value is not None and value < min_value) or (max_value is not None and value > max_value):
|
||
status = "fail"
|
||
item.setBackground(QBrush(QColor("#ffcdd2"))) # 浅红色
|
||
else:
|
||
item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
|
||
|
||
# 保存到数据库,但只在非加载状态下
|
||
if not self._loading_data_in_progress:
|
||
tray_id = self.tray_edit.currentText()
|
||
self.save_inspection_data(self._current_order_code, order_id, tray_id, config_position, config_id, formatted_value, status)
|
||
# 不需要在这里主动触发数据重新加载,因为handle_inspection_cell_changed会处理
|
||
|
||
# 重新连接信号
|
||
try:
|
||
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
||
except Exception as e:
|
||
logging.warning(f"重新连接cellChanged信号失败: {str(e)}")
|
||
|
||
logging.info(f"成功设置{data_type}值 {formatted_value} 到工程号 {order_id} 的行 {data_row}")
|
||
|
||
except Exception as e:
|
||
logging.error(f"设置检验值失败: {str(e)}")
|
||
finally:
|
||
# 释放锁
|
||
self._set_inspection_value_lock = False
|
||
|
||
def handle_tray_changed(self):
|
||
"""处理托盘号变更事件,启动监听并加载数据"""
|
||
try:
|
||
tray_id = self.tray_edit.currentText()
|
||
if tray_id:
|
||
logging.info(f"托盘号变更为 {tray_id},启动监听")
|
||
# 初始化托盘号对应的序号(如果不存在)
|
||
if tray_id not in self.init_seq:
|
||
self.init_seq[tray_id] = 1
|
||
logging.info(f"初始化托盘号 {tray_id} 的序号为 1")
|
||
|
||
# 加载检验数据
|
||
self._safe_load_data()
|
||
|
||
# 无论_safe_load_data是否成功,都确保显示包装记录
|
||
# 临时保存当前加载状态
|
||
prev_loading_state = getattr(self, '_loading_data_in_progress', False)
|
||
|
||
try:
|
||
# 设置加载状态为True,避免无限循环调用
|
||
self._loading_data_in_progress = True
|
||
# 强制显示包装记录
|
||
self.show_pack_item()
|
||
logging.info(f"托盘号变更:直接调用显示包装记录, 托盘号={tray_id}")
|
||
finally:
|
||
# 恢复之前的加载状态
|
||
self._loading_data_in_progress = prev_loading_state
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理托盘号变更失败: {str(e)}")
|
||
|
||
def handle_order_code_received(self, order_code):
|
||
"""处理从加载对话框接收到的订单号"""
|
||
logging.info(f"主窗口接收到订单号: {order_code}")
|
||
# 存储当前订单号
|
||
self._current_order_code = order_code
|
||
# 如果是接口模式
|
||
if AppMode.is_api():
|
||
# 获取订单的详细信息
|
||
from apis.gc_api import GcApi
|
||
gc_api = GcApi()
|
||
# 获取工程号信息
|
||
order_response = gc_api.get_order_info(order_code, self.corp_id)
|
||
|
||
if order_response.get("status", False):
|
||
# 获取订单的工程号数据
|
||
gc_notes = order_response.get("data", [])
|
||
if gc_notes and len(gc_notes) > 0:
|
||
# 更新上料区域信息表格
|
||
self.update_info_table(gc_notes[0])
|
||
# 订单号输入框回显 mo 字段
|
||
self.order_no_input.setText(gc_notes[0].get('mo', ''))
|
||
else:
|
||
# 非接口模式,获取本地数据库中的订单信息
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
order_info = inspection_dao.get_order_info(order_code)
|
||
|
||
# 更新上料区域信息表格
|
||
self.update_info_table(order_info)
|
||
# 订单号输入框回显 mo 字段
|
||
if order_info:
|
||
self.order_no_input.setText(order_info.get('mo', '').strip())
|
||
|
||
def on_report(self):
|
||
"""报表按钮点击处理"""
|
||
try:
|
||
dialog = ReportDialog(self)
|
||
dialog.exec_()
|
||
except Exception as e:
|
||
logging.error(f"打开报表对话框失败: {str(e)}")
|
||
QMessageBox.warning(self, "错误", f"打开报表对话框失败: {str(e)}")
|
||
|
||
def init_camera_display(self):
|
||
"""初始化上料区信息表格,不包含任何相机相关代码"""
|
||
# 上料区的信息表格已经在UI类的create_left_panel方法中初始化
|
||
# 这个方法保留是为了兼容性,但不做任何操作
|
||
pass
|
||
|
||
def initialize_camera(self):
|
||
"""初始化相机的空方法,保留是为了兼容性"""
|
||
pass
|
||
|
||
def _start_camera_display(self):
|
||
"""相机显示的空方法,保留是为了兼容性"""
|
||
pass
|
||
|
||
def update_camera_ui(self, is_camera_ready):
|
||
"""相机UI更新的空方法,保留是为了兼容性"""
|
||
pass
|
||
|
||
def handle_camera_status(self, is_connected, message):
|
||
"""相机状态处理的空方法,保留是为了兼容性"""
|
||
pass
|
||
|
||
@Slot(int, str)
|
||
def handle_emergency_stop(self, value, desc):
|
||
"""处理急停信号"""
|
||
logging.info(f"[处理] 急停信号: {desc}")
|
||
|
||
# 保存一个急停状态变量
|
||
self.emergency_stop = value
|
||
|
||
# 当急停信号为1时,重置D2和D3寄存器
|
||
if value == 1:
|
||
try:
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
|
||
# 重置D2和D3寄存器
|
||
modbus.write_register_until_success(client, 2, 0)
|
||
modbus.write_register_until_success(client, 3, 0)
|
||
|
||
# 通过信号在主线程中处理UI更新
|
||
self.emergency_stop_signal.emit(value, desc)
|
||
|
||
modbus.close_client(client)
|
||
except Exception as e:
|
||
logging.error(f"处理急停信号失败: {str(e)}")
|
||
else:
|
||
# 急停信号解除,在主线程中恢复错误状态显示
|
||
self.emergency_stop_signal.emit(value, desc)
|
||
|
||
def _handle_emergency_stop_ui(self, value, desc):
|
||
"""在主线程中处理急停信号UI更新"""
|
||
try:
|
||
if value == 1:
|
||
# 显示警告对话框
|
||
QMessageBox.warning(self, "急停警告", "监听到急停信号")
|
||
|
||
# 恢复按钮样式 - 确保在主线程中执行
|
||
logging.info("急停触发:开始恢复按钮样式")
|
||
self.restore_start_button_style()
|
||
logging.info("急停触发:已恢复开始按钮样式")
|
||
|
||
if hasattr(self, 'restore_output_button_style'):
|
||
self.restore_output_button_style()
|
||
logging.info("急停触发:已恢复下料按钮样式")
|
||
|
||
if hasattr(self, 'restore_input_button_style'):
|
||
self.restore_input_button_style()
|
||
logging.info("急停触发:已恢复上料按钮样式")
|
||
|
||
# 更新错误状态标签
|
||
self.error_status_label.setText("故障: 急停")
|
||
self.error_status_label.setToolTip("急停按钮被触发")
|
||
self.error_status_label.setStyleSheet("color: red; font-weight: bold;")
|
||
|
||
logging.info("急停UI已更新:按钮样式已恢复,状态标签已更新")
|
||
else:
|
||
# 急停信号解除,恢复错误状态显示
|
||
self._update_error_status()
|
||
|
||
# 恢复故障状态标签
|
||
self.error_status_label.setText("故障: 正常")
|
||
self.error_status_label.setStyleSheet("color: green; font-weight: bold;")
|
||
|
||
logging.info("急停解除UI已更新:状态标签已恢复")
|
||
except Exception as e:
|
||
logging.error(f"处理急停UI更新失败: {str(e)}")
|
||
|
||
def create_right_panel(self):
|
||
"""创建右侧面板"""
|
||
# 创建右侧整体框架
|
||
self.right_frame = QFrame()
|
||
self.right_frame.setFrameShape(QFrame.NoFrame) # 移除框架边框
|
||
self.right_frame.setLineWidth(0)
|
||
self.right_layout.addWidget(self.right_frame)
|
||
|
||
# 右侧整体使用垂直布局,不设置边距
|
||
self.right_frame_layout = QVBoxLayout(self.right_frame)
|
||
self.right_frame_layout.setContentsMargins(0, 0, 0, 0)
|
||
self.right_frame_layout.setSpacing(0)
|
||
|
||
# 创建一个垂直分割器,用于控制两个表格的高度比例
|
||
self.right_splitter = QSplitter(Qt.Vertical)
|
||
|
||
# 创建微丝产线表格的容器
|
||
self.process_container = QWidget()
|
||
self.process_container_layout = QVBoxLayout(self.process_container)
|
||
self.process_container_layout.setContentsMargins(0, 0, 0, 0)
|
||
self.process_container_layout.setSpacing(0)
|
||
|
||
# 创建包装记录表格的容器
|
||
self.record_container = QWidget()
|
||
self.record_container_layout = QVBoxLayout(self.record_container)
|
||
self.record_container_layout.setContentsMargins(0, 0, 0, 0)
|
||
self.record_container_layout.setSpacing(0)
|
||
|
||
# 创建微丝产线表格
|
||
self.create_process_table()
|
||
|
||
# 创建带删除按钮的标题行
|
||
self.create_process_title_with_button()
|
||
|
||
# 将微丝产线表格添加到容器中
|
||
self.process_container_layout.addWidget(self.process_frame)
|
||
|
||
# 创建包装记录表格
|
||
self.create_record_table()
|
||
self.record_container_layout.addWidget(self.record_frame)
|
||
|
||
# 将两个容器添加到分割器中
|
||
self.right_splitter.addWidget(self.process_container)
|
||
self.right_splitter.addWidget(self.record_container)
|
||
|
||
# 设置初始大小比例:微丝产线占1/3,包装记录占2/3
|
||
self.right_splitter.setSizes([100, 200]) # 比例为1:2
|
||
|
||
# 将分割器添加到右侧布局
|
||
self.right_frame_layout.addWidget(self.right_splitter)
|
||
|
||
def create_process_title_with_button(self):
|
||
"""创建带删除按钮的微丝产线标题行"""
|
||
# 获取原来的标题
|
||
original_title = self.process_title.text()
|
||
|
||
# 移除原来的标题
|
||
self.process_layout.removeWidget(self.process_title)
|
||
self.process_title.deleteLater()
|
||
|
||
# 创建标题容器
|
||
title_container = QWidget()
|
||
title_layout = QHBoxLayout(title_container)
|
||
title_layout.setContentsMargins(5, 0, 5, 0)
|
||
|
||
# 创建新的标题标签
|
||
self.process_title = QLabel(original_title)
|
||
self.process_title.setFont(self.second_title_font)
|
||
self.process_title.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||
|
||
# 创建删除按钮
|
||
self.delete_row_button = QPushButton("删除选中行")
|
||
self.delete_row_button.setFont(self.normal_font)
|
||
self.delete_row_button.setStyleSheet("""
|
||
QPushButton {
|
||
padding: 5px 10px;
|
||
background-color: #ffebee;
|
||
border: 1px solid #f44336;
|
||
border-radius: 4px;
|
||
font-weight: bold;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #ffcdd2;
|
||
}
|
||
""")
|
||
# 删除按钮的信号连接已在 connect_signals 函数中处理,这里不需要重复连接
|
||
|
||
# 打印托盘号按钮
|
||
self.print_tray_button = QPushButton("打印托盘号")
|
||
self.print_tray_button.setFont(self.normal_font)
|
||
self.print_tray_button.setStyleSheet("""
|
||
QPushButton {
|
||
padding: 5px 8px;
|
||
background-color: #e3f2fd;
|
||
border: 1px solid #2196f3;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #c8e6c9;
|
||
}
|
||
""")
|
||
self.print_tray_button.clicked.connect(lambda: self.handle_tray_complete(ismt=False))
|
||
|
||
# 打印选中行按钮
|
||
self.print_row_button = QPushButton("打印")
|
||
self.print_row_button.setFont(self.normal_font)
|
||
self.print_row_button.setStyleSheet("""
|
||
QPushButton {
|
||
padding: 5px 8px;
|
||
background-color: #e0e0e0;
|
||
border: 1px solid #cccccc;
|
||
}
|
||
QPushButton:hover {
|
||
background-color: #f5f5f5;
|
||
}
|
||
""")
|
||
self.print_row_button.clicked.connect(self.handle_print_row)
|
||
|
||
# 将标题和按钮添加到布局中
|
||
title_layout.addWidget(self.process_title)
|
||
title_layout.addStretch()
|
||
title_layout.addWidget(self.print_row_button)
|
||
title_layout.addWidget(self.delete_row_button)
|
||
title_layout.addWidget(self.print_tray_button)
|
||
|
||
# 设置容器样式
|
||
title_container.setFixedHeight(40)
|
||
title_container.setStyleSheet("background-color: #f8f8f8; border-bottom: 1px solid #dddddd;")
|
||
|
||
# 将标题容器添加到布局中
|
||
self.process_layout.insertWidget(0, title_container)
|
||
|
||
def handle_delete_row(self):
|
||
"""处理删除按钮点击事件,智能判断删除微丝产线或包装记录表格行"""
|
||
try:
|
||
# 判断当前焦点在哪个表格
|
||
focused_widget = self.focusWidget()
|
||
|
||
if focused_widget == self.process_table or self.process_table.hasFocus():
|
||
# 删除微丝产线表格行
|
||
self._delete_process_table_rows()
|
||
elif focused_widget == self.record_table or self.record_table.hasFocus():
|
||
# 删除包装记录表格行
|
||
self._delete_package_table_rows()
|
||
else:
|
||
# 如果都没有焦点,检查哪个表格有选中行
|
||
if self.process_table.selectionModel().hasSelection():
|
||
self._delete_process_table_rows()
|
||
elif self.record_table.selectionModel().hasSelection():
|
||
self._delete_package_table_rows()
|
||
else:
|
||
return
|
||
|
||
except Exception as e:
|
||
logging.error(f"删除数据失败: {str(e)}")
|
||
QMessageBox.critical(self, "错误", f"删除数据失败: {str(e)}")
|
||
|
||
def _delete_process_table_rows(self):
|
||
"""删除微丝产线表格选中的行"""
|
||
try:
|
||
# 获取当前选中的行
|
||
selected_rows = self.process_table.selectionModel().selectedRows()
|
||
if not selected_rows:
|
||
# 如果没有选中整行,则获取当前选中的单元格所在行
|
||
current_row = self.process_table.currentRow()
|
||
if current_row >= 2: # 确保不是表头行
|
||
selected_rows = [self.process_table.model().index(current_row, 0)]
|
||
else:
|
||
return
|
||
|
||
# 确认删除
|
||
reply = QMessageBox.question(
|
||
self,
|
||
"确认删除",
|
||
"确定要删除选中的微丝产线数据吗?此操作不可恢复。",
|
||
QMessageBox.Yes | QMessageBox.No,
|
||
QMessageBox.No
|
||
)
|
||
|
||
if reply != QMessageBox.Yes:
|
||
return
|
||
|
||
# 从数据库中删除数据
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
|
||
# 按行号降序排序,以便从后往前删除
|
||
rows_to_delete = sorted([index.row() for index in selected_rows], reverse=True)
|
||
|
||
for row in rows_to_delete:
|
||
if row < 2: # 跳过表头行
|
||
continue
|
||
|
||
# 获取工程号
|
||
gc_note_item = self.process_table.item(row, 1)
|
||
if not gc_note_item:
|
||
continue
|
||
|
||
gc_note = gc_note_item.text().strip()
|
||
if not gc_note:
|
||
continue
|
||
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 如果开启 api 模式,则调用接口删除远程数据
|
||
if AppMode.is_api():
|
||
from apis.gc_api import GcApi
|
||
gc_api = GcApi()
|
||
|
||
# 调用删除接口
|
||
response = gc_api.remove_order_info(tray_id, self._current_order_code, gc_note)
|
||
if response.get("status", False):
|
||
logging.info(f"已从远程删除订单信息: 托盘号={tray_id}, 订单号={self._current_order_code}, 工程号={gc_note}")
|
||
else:
|
||
error_msg = response.get("message", "未知错误")
|
||
logging.warning(f"远程删除订单信息失败: {error_msg}")
|
||
# 可以选择是否继续删除本地数据,这里选择继续
|
||
|
||
# 从数据库中删除该工程号的检验数据
|
||
inspection_dao.delete_inspection_data(self._current_order_code, gc_note, tray_id)
|
||
logging.info(f"已从数据库中删除工程号 {gc_note} 的检验数据")
|
||
|
||
# 从表格中删除行
|
||
self.process_table.removeRow(row)
|
||
logging.info(f"已从微丝产线表格中删除第 {row} 行")
|
||
|
||
# 重新加载数据
|
||
self._safe_load_data()
|
||
|
||
# 确保清除选中状态,防止误操作
|
||
self.process_table.clearSelection()
|
||
self.process_table.setCurrentItem(None)
|
||
|
||
# 显示成功消息
|
||
QMessageBox.information(self, "删除成功", "已成功删除选中的微丝产线数据")
|
||
|
||
except Exception as e:
|
||
logging.error(f"删除微丝产线数据失败: {str(e)}")
|
||
QMessageBox.critical(self, "错误", f"删除微丝产线数据失败: {str(e)}")
|
||
|
||
def _delete_package_table_rows(self):
|
||
"""删除包装记录表格选中的行"""
|
||
try:
|
||
# 获取当前选中的行
|
||
selected_rows = self.record_table.selectionModel().selectedRows()
|
||
if not selected_rows:
|
||
# 如果没有选中整行,则获取当前选中的单元格所在行
|
||
current_row = self.record_table.currentRow()
|
||
if current_row >= 0: # 包装记录表格没有表头行
|
||
selected_rows = [self.record_table.model().index(current_row, 0)]
|
||
else:
|
||
return
|
||
|
||
# 确认删除
|
||
reply = QMessageBox.question(
|
||
self,
|
||
"确认删除",
|
||
"确定要删除选中的包装记录吗?此操作不可恢复。",
|
||
QMessageBox.Yes | QMessageBox.No,
|
||
QMessageBox.No
|
||
)
|
||
|
||
if reply != QMessageBox.Yes:
|
||
return
|
||
|
||
# 从数据库中删除数据
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
|
||
# 按行号降序排序,以便从后往前删除
|
||
rows_to_delete = sorted([index.row() for index in selected_rows], reverse=True)
|
||
|
||
for row in rows_to_delete:
|
||
if row < 0: # 跳过无效行
|
||
continue
|
||
|
||
# 获取订单号(第2列)
|
||
order_id_item = self.record_table.item(row, 1)
|
||
if not order_id_item:
|
||
logging.warning(f"第 {row} 行订单号为空,跳过删除")
|
||
continue
|
||
|
||
order_id = order_id_item.text().strip()
|
||
if not order_id:
|
||
logging.warning(f"第 {row} 行订单号为空,跳过删除")
|
||
continue
|
||
|
||
# 获取工程号(第3列)
|
||
gc_note_item = self.record_table.item(row, 2)
|
||
if not gc_note_item:
|
||
logging.warning(f"第 {row} 行工程号为空,跳过删除")
|
||
continue
|
||
|
||
gc_note = gc_note_item.text().strip()
|
||
if not gc_note:
|
||
logging.warning(f"第 {row} 行工程号为空,跳过删除")
|
||
continue
|
||
|
||
# 获取托盘号(从前端组件获取)
|
||
tray_id = self.tray_edit.currentText()
|
||
if not tray_id:
|
||
logging.warning("托盘号为空,跳过删除")
|
||
continue
|
||
|
||
# 如果开启 api 模式,则调用接口删除远程数据
|
||
if AppMode.is_api():
|
||
from apis.gc_api import GcApi
|
||
gc_api = GcApi()
|
||
|
||
# 调用删除接口
|
||
response = gc_api.remove_order_info(tray_id, order_id, gc_note)
|
||
if response.get("status", False):
|
||
logging.info(f"已从远程删除订单信息: 托盘号={tray_id}, 订单号={order_id}, 工程号={gc_note}")
|
||
else:
|
||
error_msg = response.get("message", "未知错误")
|
||
logging.warning(f"远程删除订单信息失败: {error_msg}")
|
||
# 可以选择是否继续删除本地数据,这里选择继续
|
||
|
||
# 从数据库中删除该包装记录
|
||
success = inspection_dao.delete_package_record(order_id, gc_note, tray_id)
|
||
if success:
|
||
logging.info(f"已从数据库中删除包装记录: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}")
|
||
else:
|
||
logging.warning(f"删除包装记录失败: 订单号={order_id}, 工程号={gc_note}, 托盘号={tray_id}")
|
||
continue
|
||
|
||
# 从表格中删除行
|
||
self.record_table.removeRow(row)
|
||
logging.info(f"已从包装记录表格中删除第 {row} 行")
|
||
|
||
# 重新加载包装记录
|
||
self.show_pack_item()
|
||
|
||
# 确保清除选中状态,防止误操作
|
||
self.record_table.clearSelection()
|
||
self.record_table.setCurrentItem(None)
|
||
|
||
# 显示成功消息
|
||
QMessageBox.information(self, "删除成功", "已成功删除选中的包装记录")
|
||
|
||
except Exception as e:
|
||
logging.error(f"删除包装记录失败: {str(e)}")
|
||
QMessageBox.critical(self, "错误", f"删除包装记录失败: {str(e)}")
|
||
|
||
|
||
|
||
def handle_tray_complete(self, ismt=True):
|
||
"""托盘完成或打印托盘号事件
|
||
Args:
|
||
ismt: 是否满托,True为满托,False为未满托
|
||
"""
|
||
try:
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
if not tray_id:
|
||
QMessageBox.warning(self, "提示", "请先选择托盘号")
|
||
return
|
||
if ismt:
|
||
# 确认对话框
|
||
reply = QMessageBox.question(
|
||
self,
|
||
"确认完成",
|
||
f"确认将托盘 {tray_id} 标记为已满托?",
|
||
QMessageBox.Yes | QMessageBox.No,
|
||
QMessageBox.No
|
||
)
|
||
|
||
if reply != QMessageBox.Yes:
|
||
return
|
||
|
||
# 调用接口
|
||
from apis.gc_api import GcApi
|
||
gc_api = GcApi()
|
||
|
||
import socket
|
||
try:
|
||
# 通过连接外部服务器获取本机IP(不实际建立连接)
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
s.connect(("8.8.8.8", 80))
|
||
local_ip = s.getsockname()[0]
|
||
s.close()
|
||
except Exception as e:
|
||
logging.error(f"获取本机IP失败: {str(e)}")
|
||
# 如果获取失败,使用本地回环地址
|
||
|
||
# 准备参数
|
||
params = {
|
||
'ismt': ismt,
|
||
'corp_id': self.corp_id,
|
||
'tray_id': tray_id,
|
||
'ip': local_ip
|
||
}
|
||
|
||
# 调用接口
|
||
response = gc_api.ismt_option(params)
|
||
|
||
# 处理响应
|
||
if response.get('status', False):
|
||
if ismt:
|
||
QMessageBox.information(self, "成功", "托盘已标记为完成")
|
||
logging.info(f"托盘 {tray_id} 已标记为完成")
|
||
else:
|
||
QMessageBox.information(self, "成功", "托盘号已打印")
|
||
logging.info(f"托盘号 {tray_id} 已打印")
|
||
# 向D12寄存器写入1,触发打印
|
||
from utils.modbus_utils import ModbusUtils
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
|
||
if not client:
|
||
QMessageBox.critical(self, "错误", "无法连接到Modbus服务器")
|
||
return
|
||
|
||
try:
|
||
# 向D12寄存器写入1,表示打印请求
|
||
success = modbus.write_register(client, 12, 1)
|
||
if success:
|
||
logging.info(f"已向D12寄存器写入1")
|
||
else:
|
||
QMessageBox.warning(self, "警告", "发送打印请求失败,请检查PLC连接")
|
||
finally:
|
||
# 释放客户端连接
|
||
modbus.close_client(client)
|
||
else:
|
||
error_msg = response.get('message', '未知错误')
|
||
QMessageBox.warning(self, "失败", f"标记托盘完成失败: {error_msg}")
|
||
logging.error(f"标记托盘 {tray_id} 完成失败: {error_msg}")
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理托盘完成失败: {str(e)}")
|
||
QMessageBox.critical(self, "错误", f"处理托盘完成失败: {str(e)}")
|
||
|
||
def update_info_table(self, order_info):
|
||
"""根据订单信息更新上料区域的信息表格"""
|
||
try:
|
||
if not order_info:
|
||
logging.warning("订单信息为空,无法更新上料区域信息表格")
|
||
return
|
||
|
||
logging.info(f"更新上料区域信息表格: {order_info}")
|
||
|
||
# 使用UI类中定义的字段映射关系
|
||
field_mapping = self.FIELD_MAPPING
|
||
|
||
# 记录 order_info 中的所有键,用于调试
|
||
logging.debug(f"订单信息键: {list(order_info.keys())}")
|
||
|
||
# 更新表格内容 - 智能更新:已有就更新,没有就新增,空值不覆盖
|
||
for field_name, field_key in field_mapping.items():
|
||
if field_name in self.info_values and self.info_values[field_name] is not None:
|
||
try:
|
||
# 获取当前控件中的值
|
||
current_value = ""
|
||
if isinstance(self.info_values[field_name], QTextEdit):
|
||
current_value = self.info_values[field_name].toPlainText()
|
||
elif isinstance(self.info_values[field_name], QComboBox):
|
||
current_value = self.info_values[field_name].currentText()
|
||
elif field_name == "炉号":
|
||
# 炉号字段是容器,需要找到其中的QLineEdit
|
||
luno_container = self.info_values[field_name]
|
||
if luno_container:
|
||
for child in luno_container.children():
|
||
if hasattr(child, 'text') and hasattr(child, 'setText'):
|
||
current_value = child.text()
|
||
break
|
||
elif hasattr(self.info_values[field_name], 'text'):
|
||
# 其他有text方法的控件
|
||
current_value = self.info_values[field_name].text()
|
||
else:
|
||
# 对于没有text方法的控件,使用空字符串
|
||
current_value = ""
|
||
except RuntimeError as e:
|
||
# 如果对象已被删除,记录错误并跳过
|
||
logging.warning(f"控件对象已被删除,字段: {field_name}, 错误: {str(e)}")
|
||
continue
|
||
except Exception as e:
|
||
# 其他异常也记录并跳过
|
||
logging.warning(f"获取控件文本失败,字段: {field_name}, 错误: {str(e)}")
|
||
continue
|
||
|
||
# 获取API返回的新值
|
||
new_value = ""
|
||
if field_key and field_key in order_info:
|
||
raw_value = order_info[field_key]
|
||
# 处理None值,将其转换为空字符串
|
||
if raw_value is None:
|
||
new_value = ""
|
||
else:
|
||
new_value = str(raw_value)
|
||
|
||
# 特殊处理线径公差
|
||
if field_name == "线径公差" and "bccd" in order_info and "tccd" in order_info:
|
||
bccd = order_info.get("bccd")
|
||
tccd = order_info.get("tccd")
|
||
# 处理None值,只有两个值都不为None时才组合
|
||
if bccd is not None and tccd is not None:
|
||
new_value = f"{bccd} - {tccd}"
|
||
elif bccd is not None:
|
||
new_value = str(bccd)
|
||
elif tccd is not None:
|
||
new_value = str(tccd)
|
||
else:
|
||
new_value = ""
|
||
|
||
# 特殊处理强度范围
|
||
if field_name == "强度范围" and "bqd" in order_info and "tqd" in order_info:
|
||
bqd = order_info.get("bqd")
|
||
tqd = order_info.get("tqd")
|
||
# 处理None值,只有两个值都不为None时才组合
|
||
if bqd is not None and tqd is not None:
|
||
new_value = f"{bqd} - {tqd}"
|
||
elif bqd is not None:
|
||
new_value = str(bqd)
|
||
elif tqd is not None:
|
||
new_value = str(tqd)
|
||
else:
|
||
new_value = ""
|
||
|
||
# 智能更新逻辑:强度字段允许空值覆盖,其他字段空值不覆盖
|
||
should_update = False
|
||
if field_name == "强度": # 强度字段允许空值覆盖
|
||
should_update = new_value != current_value
|
||
else: # 其他字段:只有新值不为空且与当前值不同时才更新
|
||
should_update = new_value and new_value != current_value
|
||
|
||
if should_update:
|
||
# 进行更新
|
||
try:
|
||
if isinstance(self.info_values[field_name], QTextEdit):
|
||
self.info_values[field_name].setPlainText(new_value)
|
||
elif isinstance(self.info_values[field_name], QComboBox):
|
||
# 对于QComboBox,尝试找到匹配的选项并设置
|
||
combo = self.info_values[field_name]
|
||
index = combo.findText(new_value)
|
||
if index != -1:
|
||
combo.setCurrentIndex(index)
|
||
else:
|
||
# 如果没找到匹配的选项,添加新选项
|
||
combo.addItem(new_value)
|
||
combo.setCurrentText(new_value)
|
||
elif field_name == "炉号":
|
||
# 炉号字段是容器,需要找到其中的QLineEdit并设置文本
|
||
luno_container = self.info_values[field_name]
|
||
if luno_container:
|
||
for child in luno_container.children():
|
||
if hasattr(child, 'text') and hasattr(child, 'setText'):
|
||
child.setText(new_value)
|
||
break
|
||
else:
|
||
self.info_values[field_name].setText(new_value)
|
||
logging.debug(f"更新字段 '{field_name}': '{current_value}' -> '{new_value}'")
|
||
except RuntimeError as e:
|
||
# 如果对象已被删除,记录错误并跳过
|
||
logging.warning(f"控件对象已被删除,无法更新字段: {field_name}, 错误: {str(e)}")
|
||
continue
|
||
except Exception as e:
|
||
# 其他异常也记录并跳过
|
||
logging.warning(f"更新控件文本失败,字段: {field_name}, 错误: {str(e)}")
|
||
continue
|
||
elif not new_value and current_value and field_name != "强度":
|
||
# 新值为空但当前值不为空,且不是强度字段,保持当前值
|
||
logging.debug(f"保持字段 '{field_name}' 的当前值: '{current_value}' (API返回空值)")
|
||
elif not new_value and not current_value:
|
||
# 新值和当前值都为空,无需操作
|
||
logging.debug(f"字段 '{field_name}' 新值和当前值都为空,无需更新")
|
||
else:
|
||
# 新值与当前值相同,无需更新
|
||
logging.debug(f"字段 '{field_name}' 值未变化: '{current_value}'")
|
||
else:
|
||
logging.warning(f"字段名 '{field_name}' 在info_values中不存在")
|
||
|
||
logging.info("上料区域信息表格更新完成")
|
||
|
||
except Exception as e:
|
||
logging.error(f"更新上料区域信息表格失败: {str(e)}")
|
||
# 记录异常堆栈,便于调试
|
||
import traceback
|
||
logging.error(traceback.format_exc())
|
||
|
||
def handle_print_row(self):
|
||
"""处理打印按钮点击事件,打印选中的微丝产线表格行"""
|
||
try:
|
||
|
||
# 确认打印
|
||
reply = QMessageBox.question(
|
||
self,
|
||
"确认打印",
|
||
"确定要打印选中的数据吗?",
|
||
QMessageBox.Yes | QMessageBox.No,
|
||
QMessageBox.No
|
||
)
|
||
|
||
if reply != QMessageBox.Yes:
|
||
return
|
||
|
||
|
||
# 向D12寄存器写入1,触发打印
|
||
from utils.modbus_utils import ModbusUtils
|
||
modbus = ModbusUtils()
|
||
client = modbus.get_client()
|
||
|
||
if not client:
|
||
QMessageBox.critical(self, "错误", "无法连接到Modbus服务器")
|
||
return
|
||
|
||
try:
|
||
# 向D12寄存器写入1,表示打印请求
|
||
success = modbus.write_register(client, 12, 1)
|
||
if success:
|
||
logging.info(f"已向D12寄存器写入1")
|
||
else:
|
||
QMessageBox.warning(self, "警告", "发送打印请求失败,请检查PLC连接")
|
||
finally:
|
||
# 释放客户端连接
|
||
modbus.close_client(client)
|
||
|
||
except Exception as e:
|
||
logging.error(f"打印数据失败: {str(e)}")
|
||
QMessageBox.critical(self, "错误", f"打印数据失败: {str(e)}")
|
||
|
||
def update_order_statistics(self):
|
||
"""更新订单数量和产量统计数据到项目表格"""
|
||
try:
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
|
||
# 获取订单数量和产量统计数据
|
||
statistics = inspection_dao.get_order_statistics()
|
||
|
||
# 设置表格项(日、月、年、累计订单数量)
|
||
# 当日订单数量
|
||
day_cnt_item = QTableWidgetItem(str(statistics['order_cnt_day']))
|
||
day_cnt_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(0, 1, day_cnt_item)
|
||
|
||
# 当月订单数量
|
||
month_cnt_item = QTableWidgetItem(str(statistics['order_cnt_month']))
|
||
month_cnt_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(1, 1, month_cnt_item)
|
||
|
||
# 当年订单数量
|
||
year_cnt_item = QTableWidgetItem(str(statistics['order_cnt_year']))
|
||
year_cnt_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(2, 1, year_cnt_item)
|
||
|
||
# 累计订单数量
|
||
all_cnt_item = QTableWidgetItem(str(statistics['order_cnt_all']))
|
||
all_cnt_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(3, 1, all_cnt_item)
|
||
|
||
# 设置表格项(日、月、年、累计产量)
|
||
# 当日产量
|
||
day_num_item = QTableWidgetItem(str(round(statistics['order_num_day'], 2)))
|
||
day_num_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(0, 2, day_num_item)
|
||
|
||
# 当月产量
|
||
month_num_item = QTableWidgetItem(str(round(statistics['order_num_month'], 2)))
|
||
month_num_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(1, 2, month_num_item)
|
||
|
||
# 当年产量
|
||
year_num_item = QTableWidgetItem(str(round(statistics['order_num_year'], 2)))
|
||
year_num_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(2, 2, year_num_item)
|
||
|
||
# 累计产量
|
||
all_num_item = QTableWidgetItem(str(round(statistics['order_num_all'], 2)))
|
||
all_num_item.setTextAlignment(Qt.AlignCenter)
|
||
self.project_table.setItem(3, 2, all_num_item)
|
||
|
||
logging.debug(f"已更新订单数量和产量统计数据: 日订单={statistics['order_cnt_day']}, 月订单={statistics['order_cnt_month']}, 年订单={statistics['order_cnt_year']}, 累计订单={statistics['order_cnt_all']}, 日产量={statistics['order_num_day']}, 月产量={statistics['order_num_month']}, 年产量={statistics['order_num_year']}, 累计产量={statistics['order_num_all']}")
|
||
|
||
except Exception as e:
|
||
logging.error(f"更新订单数量和产量统计数据失败: {str(e)}")
|
||
import traceback
|
||
logging.error(traceback.format_exc())
|
||
|
||
def _log_focus_widget_info(self, widget):
|
||
"""记录当前焦点控件的信息,用于调试"""
|
||
try:
|
||
from PySide6.QtWidgets import QLineEdit, QComboBox, QTextEdit
|
||
widget_type = "未知"
|
||
if isinstance(widget, QLineEdit):
|
||
widget_type = "输入框"
|
||
elif isinstance(widget, QComboBox):
|
||
widget_type = "下拉框"
|
||
elif isinstance(widget, QTextEdit):
|
||
widget_type = "文本编辑框"
|
||
|
||
widget_name = widget.objectName() if widget else "无"
|
||
widget_text = ""
|
||
|
||
if isinstance(widget, QLineEdit):
|
||
widget_text = widget.text()
|
||
elif isinstance(widget, QComboBox):
|
||
widget_text = widget.currentText()
|
||
elif isinstance(widget, QTextEdit):
|
||
widget_text = widget.toPlainText()
|
||
|
||
logging.info(f"当前焦点控件: 类型={widget_type}, 名称={widget_name}, 文本={widget_text}")
|
||
except Exception as e:
|
||
logging.error(f"记录焦点控件信息失败: {e}")
|
||
|
||
def _find_next_row_to_process(self, target_status):
|
||
"""查找下一个需要处理的行
|
||
|
||
Args:
|
||
target_status: 目标状态(如 'inspected', 'weighed')
|
||
|
||
Returns:
|
||
int: 行索引,如果没找到返回None
|
||
"""
|
||
try:
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
|
||
# 确定搜索的起始行
|
||
start_row = 2 # 数据行从第2行开始
|
||
|
||
# 如果当前有正在处理的行,从该行开始搜索
|
||
if self._current_processing_row is not None and self._current_processing_row >= 2:
|
||
start_row = self._current_processing_row
|
||
|
||
# 从起始行开始,按顺序查找状态匹配的行
|
||
for row in range(start_row, self.process_table.rowCount()):
|
||
gc_note_item = self.process_table.item(row, 1)
|
||
if gc_note_item:
|
||
row_gc_note = gc_note_item.text().strip()
|
||
if row_gc_note: # 确保工程号不为空
|
||
tray_id = self.tray_edit.currentText()
|
||
status = inspection_dao.get_product_status(self._current_order_code, row_gc_note, tray_id)
|
||
if status == target_status:
|
||
logging.info(f"找到状态为{target_status}的行: {row}, 工程号: {row_gc_note}")
|
||
return row # 找到第一个匹配的行就立即返回
|
||
|
||
# 如果从当前处理行开始没找到,从头开始搜索
|
||
if self._current_processing_row is not None and self._current_processing_row > 2:
|
||
for row in range(2, self._current_processing_row):
|
||
gc_note_item = self.process_table.item(row, 1)
|
||
if gc_note_item:
|
||
row_gc_note = gc_note_item.text().strip()
|
||
if row_gc_note: # 确保工程号不为空
|
||
tray_id = self.tray_edit.currentText()
|
||
status = inspection_dao.get_product_status(self._current_order_code, row_gc_note, tray_id)
|
||
if status == target_status:
|
||
logging.info(f"从头开始找到状态为{target_status}的行: {row}, 工程号: {row_gc_note}")
|
||
return row # 找到第一个匹配的行就立即返回
|
||
|
||
logging.warning(f"未找到状态为{target_status}的行")
|
||
return None
|
||
|
||
except Exception as e:
|
||
logging.error(f"查找下一个处理行时发生错误: {str(e)}")
|
||
return None
|
||
|
||
def handle_inspection_cell_changed(self, row, column):
|
||
"""处理检验表格单元格内容变更事件"""
|
||
# 创建唯一键,包含行、列、工程号
|
||
try:
|
||
# 获取工程号,用于创建更精确的唯一键
|
||
order_item = self.process_table.item(row, 1)
|
||
gc_note = order_item.text().strip() if order_item else ""
|
||
|
||
# 创建包含行、列、工程号的唯一键
|
||
cell_key = f"{row}_{column}_{gc_note}"
|
||
current_time = time.time()
|
||
|
||
# 获取上次处理时间
|
||
last_process_times = getattr(self, '_last_cell_process_times', {})
|
||
last_process_time = last_process_times.get(cell_key, 0)
|
||
|
||
# 如果同一单元格在1秒内被处理过,则忽略
|
||
if current_time - last_process_time < 1.0:
|
||
logging.info(f"防抖:跳过重复处理单元格 [{row}, {column}], 工程号={gc_note},间隔小于1秒")
|
||
return
|
||
|
||
# 更新处理时间
|
||
if not hasattr(self, '_last_cell_process_times'):
|
||
self._last_cell_process_times = {}
|
||
self._last_cell_process_times[cell_key] = current_time
|
||
|
||
# 只处理数据行的检验列变更
|
||
if row < 2: # 忽略表头行
|
||
return
|
||
|
||
# 忽略首尾两列(序号和工程号)
|
||
if column < 2:
|
||
return
|
||
|
||
# 获取工程号
|
||
if not gc_note:
|
||
return
|
||
|
||
# 获取托盘号
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 判断是否是检验列(非包装列)
|
||
packaging_start_col = 2 + len(enabled_configs)
|
||
|
||
# 获取单元格内容
|
||
cell_item = self.process_table.item(row, column)
|
||
if not cell_item:
|
||
return
|
||
|
||
value = cell_item.text().strip()
|
||
|
||
# 默认设置为通过状态
|
||
status = 'pass'
|
||
|
||
# 记录当前正在处理的数据类型,用于日志输出
|
||
data_type = "检验"
|
||
|
||
if column >= 2 and column < packaging_start_col:
|
||
# 是检验列
|
||
config_index = column - 2
|
||
if config_index < len(enabled_configs):
|
||
config = enabled_configs[config_index]
|
||
data_type = config['display_name']
|
||
|
||
# 显示临时状态消息
|
||
self.statusBar().showMessage(f"正在保存检验数据: {data_type}={value}", 1000)
|
||
|
||
# 验证数据有效性
|
||
# 设置单元格颜色为浅绿色,表示已填写
|
||
cell_item.setBackground(QBrush(QColor("#c8e6c9")))
|
||
|
||
# 保持当前状态不变,由状态管理逻辑处理
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
status = inspection_dao.get_product_status(self._current_order_code, gc_note, tray_id)
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, config['position'], config['id'], value, status)
|
||
|
||
# 判断是否是包装列
|
||
elif column == packaging_start_col:
|
||
# 贴标列
|
||
data_type = "贴标"
|
||
self.statusBar().showMessage(f"正在保存贴标数据: {value}", 1000)
|
||
# 设置单元格颜色为通过
|
||
cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
|
||
# 保存贴标数据,position和config_id都是11
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, 11, 11, value, status)
|
||
|
||
elif column == packaging_start_col + 1:
|
||
# 毛重列
|
||
data_type = "毛重"
|
||
self.statusBar().showMessage(f"正在保存称重数据: {value}", 1000)
|
||
# 设置单元格颜色为通过
|
||
cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
|
||
# 保存毛重数据,position和config_id都是12
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, 12, 12, value, status)
|
||
elif column == packaging_start_col + 2:
|
||
# 净重列
|
||
data_type = "净重"
|
||
self.statusBar().showMessage(f"正在保存净重数据: {value}", 1000)
|
||
# 设置单元格颜色为通过
|
||
cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
|
||
# 保存净重数据,position和config_id都是13
|
||
self.save_inspection_data(self._current_order_code, gc_note, tray_id, 13, 13, value, status)
|
||
|
||
# 记录详细日志
|
||
logging.info(f"处理单元格变更: 行={row}, 列={column}, 类型={data_type}, 工程号={gc_note}, 值={value}, 状态={status}")
|
||
|
||
# 检查是否完成检验并更新状态
|
||
self.check_inspection_completed(row)
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理检验单元格变更失败: {str(e)}")
|
||
self.statusBar().showMessage(f"处理检验数据失败: {str(e)[:50]}...", 3000)
|
||
finally:
|
||
# 延迟一段时间后再触发查询,避免频繁刷新UI
|
||
# 但要避免在加载过程中触发新的加载
|
||
if not self._loading_data_in_progress:
|
||
# 使用类变量保存定时器,避免创建多个定时器
|
||
if hasattr(self, '_load_data_timer') and self._load_data_timer is not None:
|
||
self._load_data_timer.stop()
|
||
|
||
self._load_data_timer = QTimer()
|
||
self._load_data_timer.setSingleShot(True)
|
||
self._load_data_timer.timeout.connect(self._safe_load_data)
|
||
self._load_data_timer.start(1000)
|
||
|
||
|
||
|
||
def check_inspection_completed(self, row):
|
||
"""检查行是否有至少一个检验项已完成,如果是则更新状态为inspected
|
||
|
||
Args:
|
||
row: 行索引
|
||
|
||
Returns:
|
||
bool: 是否有至少一个检验项已完成
|
||
"""
|
||
try:
|
||
# 获取工程号
|
||
gc_note_item = self.process_table.item(row, 1)
|
||
if not gc_note_item:
|
||
return False
|
||
|
||
gc_note = gc_note_item.text().strip()
|
||
tray_id = self.tray_edit.currentText()
|
||
|
||
# 获取启用的检验配置
|
||
enabled_configs = self.inspection_manager.get_enabled_configs()
|
||
|
||
# 检查是否有至少一个检验项有值
|
||
has_any_value = False
|
||
for i, config in enumerate(enabled_configs):
|
||
col_index = 2 + i
|
||
item = self.process_table.item(row, col_index)
|
||
if item and item.text().strip():
|
||
has_any_value = True
|
||
break
|
||
|
||
# 如果有至少一个检验项有值,更新状态为inspected
|
||
if has_any_value:
|
||
from dao.inspection_dao import InspectionDAO
|
||
inspection_dao = InspectionDAO()
|
||
inspection_dao.update_product_status(self._current_order_code, gc_note, tray_id, 'inspected')
|
||
logging.info(f"工程号 {gc_note} 的检验已完成,状态更新为inspected")
|
||
|
||
return has_any_value
|
||
except Exception as e:
|
||
logging.error(f"检查检验完成状态失败: {str(e)}")
|
||
return False
|
||
|
||
def handle_luno_query(self):
|
||
"""处理炉号查询按钮点击事件"""
|
||
try:
|
||
# 导入炉号查询对话框
|
||
from widgets.luno_query_dialog import LunoQueryDialog
|
||
|
||
# 创建炉号查询对话框
|
||
luno_dialog = LunoQueryDialog(self)
|
||
|
||
# 连接炉号选择信号
|
||
luno_dialog.luno_selected.connect(self.on_luno_selected)
|
||
|
||
# 显示对话框
|
||
luno_dialog.exec()
|
||
|
||
except Exception as e:
|
||
logging.error(f"打开炉号查询对话框失败: {str(e)}")
|
||
from PySide6.QtWidgets import QMessageBox
|
||
QMessageBox.critical(self, "错误", f"打开炉号查询对话框失败: {str(e)}")
|
||
|
||
def on_luno_selected(self, luno_data):
|
||
"""处理炉号选择事件
|
||
|
||
Args:
|
||
luno_data: 选中的炉号数据字典
|
||
"""
|
||
try:
|
||
# 获取炉号输入框
|
||
luno_input = None
|
||
luno_container = self.info_values.get("炉号")
|
||
if luno_container:
|
||
# 查找容器中的QLineEdit
|
||
for child in luno_container.children():
|
||
if hasattr(child, 'text') and hasattr(child, 'setText'):
|
||
luno_input = child
|
||
break
|
||
|
||
if luno_input:
|
||
# 设置炉号值
|
||
luno_value = luno_data.get("luono", "")
|
||
luno_input.setText(luno_value)
|
||
logging.info(f"已选择炉号: {luno_value}")
|
||
|
||
# 显示状态消息
|
||
self.statusBar().showMessage(f"已选择炉号: {luno_value}", 2000)
|
||
else:
|
||
logging.warning("未找到炉号输入框")
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理炉号选择失败: {str(e)}")
|
||
from PySide6.QtWidgets import QMessageBox
|
||
QMessageBox.critical(self, "错误", f"处理炉号选择失败: {str(e)}")
|
||
|
||
def _reset_diameter_processing_lock(self):
|
||
"""重置线径处理锁,用于超时保护"""
|
||
if hasattr(self, '_processing_diameter_lock') and self._processing_diameter_lock:
|
||
processing_time = time.time() - getattr(self, '_diameter_processing_start_time', time.time())
|
||
logging.warning(f"线径数据处理超时,强制释放锁。处理时间: {processing_time:.2f}秒")
|
||
self._processing_diameter_lock = False
|
||
|
||
@Slot(float, float, float)
|
||
def show_diameter_alert(self, value, min_value, max_value):
|
||
"""显示线径超出范围警告 - 通过信号触发,使用exec_强制显示"""
|
||
try:
|
||
# 使用更强制的方式显示警告对话框
|
||
from PySide6.QtWidgets import QApplication
|
||
|
||
# 记录当前时间,用于跟踪弹框显示时间
|
||
start_time = time.time()
|
||
logging.info(f"开始创建线径警告弹框,时间: {start_time}")
|
||
|
||
# 创建一个模态对话框 - 使用exec_方式显示
|
||
msg = QMessageBox()
|
||
msg.setIcon(QMessageBox.Critical) # 使用Critical图标更明显
|
||
msg.setWindowTitle('警告:线径超出范围!')
|
||
msg.setText(f"<b>线径值 {value:.3f}mm 超出范围!</b><br><br>允许范围: {min_value:.3f} - {max_value:.3f}mm")
|
||
msg.setStandardButtons(QMessageBox.Ok)
|
||
|
||
# 设置样式,使其更显眼
|
||
msg.setStyleSheet("""
|
||
QMessageBox {
|
||
background-color: #ffeeee;
|
||
border: 3px solid #ff0000;
|
||
font-size: 14px;
|
||
}
|
||
QLabel {
|
||
color: #ff0000;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
min-width: 250px;
|
||
min-height: 80px;
|
||
}
|
||
QPushButton {
|
||
background-color: #ff6666;
|
||
color: white;
|
||
font-weight: bold;
|
||
min-width: 80px;
|
||
min-height: 30px;
|
||
}
|
||
""")
|
||
|
||
# 确保对话框显示在所有窗口之上
|
||
msg.setWindowFlags(Qt.WindowStaysOnTopHint)
|
||
|
||
# 强制处理事件,确保UI更新
|
||
QApplication.processEvents()
|
||
|
||
# 使用定时器在2秒后自动点击确定按钮
|
||
def auto_close():
|
||
try:
|
||
# 查找确定按钮并点击
|
||
for button in msg.buttons():
|
||
if msg.buttonRole(button) == QMessageBox.AcceptRole:
|
||
button.click()
|
||
logging.info(f"已自动点击确定按钮,弹框显示时长: {time.time() - start_time:.2f}秒")
|
||
return
|
||
# 如果没有找到确定按钮,直接关闭
|
||
msg.done(QMessageBox.Ok)
|
||
logging.info(f"已自动关闭弹框,弹框显示时长: {time.time() - start_time:.2f}秒")
|
||
except Exception as e:
|
||
logging.error(f"自动关闭弹框失败: {str(e)}")
|
||
|
||
# 设置自动关闭定时器
|
||
QTimer.singleShot(2000, auto_close)
|
||
|
||
# 显示弹框并阻塞直到用户关闭或自动关闭
|
||
logging.info(f"即将显示线径警告弹框...")
|
||
msg.exec_() # 使用exec_而不是show,确保弹框显示
|
||
|
||
# 记录日志
|
||
logging.info(f"线径警告弹框已关闭,总显示时长: {time.time() - start_time:.2f}秒")
|
||
except Exception as e:
|
||
logging.error(f"显示线径警告弹框失败: {str(e)}", exc_info=True)
|
||
|
||
@Slot(float, float, float)
|
||
def show_weight_alert(self, value, min_value, max_value):
|
||
"""显示重量超出范围警告 - 通过信号触发,使用exec_强制显示"""
|
||
try:
|
||
# 使用更强制的方式显示警告对话框
|
||
from PySide6.QtWidgets import QApplication
|
||
|
||
# 记录当前时间,用于跟踪弹框显示时间
|
||
start_time = time.time()
|
||
logging.info(f"开始创建重量警告弹框,时间: {start_time}")
|
||
|
||
# 创建一个模态对话框 - 使用exec_方式显示
|
||
msg = QMessageBox()
|
||
msg.setIcon(QMessageBox.Critical) # 使用Critical图标更明显
|
||
msg.setWindowTitle('警告:重量超出范围!')
|
||
msg.setText(f"<b>称重值 {value:.2f}kg 超出范围!</b><br><br>允许范围: {min_value:.1f} - {max_value:.1f}kg")
|
||
msg.setStandardButtons(QMessageBox.Ok)
|
||
|
||
# 设置样式,使其更显眼
|
||
msg.setStyleSheet("""
|
||
QMessageBox {
|
||
background-color: #ffeeee;
|
||
border: 3px solid #ff0000;
|
||
font-size: 14px;
|
||
}
|
||
QLabel {
|
||
color: #ff0000;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
min-width: 250px;
|
||
min-height: 80px;
|
||
}
|
||
QPushButton {
|
||
background-color: #ff6666;
|
||
color: white;
|
||
font-weight: bold;
|
||
min-width: 80px;
|
||
min-height: 30px;
|
||
}
|
||
""")
|
||
|
||
# 确保对话框显示在所有窗口之上
|
||
msg.setWindowFlags(Qt.WindowStaysOnTopHint)
|
||
|
||
# 强制处理事件,确保UI更新
|
||
QApplication.processEvents()
|
||
|
||
# 使用定时器在2秒后自动点击确定按钮
|
||
def auto_close():
|
||
try:
|
||
# 查找确定按钮并点击
|
||
for button in msg.buttons():
|
||
if msg.buttonRole(button) == QMessageBox.AcceptRole:
|
||
button.click()
|
||
logging.info(f"已自动点击确定按钮,弹框显示时长: {time.time() - start_time:.2f}秒")
|
||
return
|
||
# 如果没有找到确定按钮,直接关闭
|
||
msg.done(QMessageBox.Ok)
|
||
logging.info(f"已自动关闭弹框,弹框显示时长: {time.time() - start_time:.2f}秒")
|
||
except Exception as e:
|
||
logging.error(f"自动关闭弹框失败: {str(e)}")
|
||
|
||
# 设置自动关闭定时器
|
||
QTimer.singleShot(2000, auto_close)
|
||
|
||
# 显示弹框并阻塞直到用户关闭或自动关闭
|
||
logging.info(f"即将显示重量警告弹框...")
|
||
msg.exec_() # 使用exec_而不是show,确保弹框显示
|
||
|
||
# 记录日志
|
||
logging.info(f"重量警告弹框已关闭,总显示时长: {time.time() - start_time:.2f}秒")
|
||
except Exception as e:
|
||
logging.error(f"显示重量警告弹框失败: {str(e)}", exc_info=True)
|
||
|
||
|
||
|
||
def safe_str(val):
|
||
return "" if val is None else str(val) |