From 074b656bbce8f8942d0bc235b6d376c475f775eb Mon Sep 17 00:00:00 2001 From: zhu-mengmeng <15588200382@163.com> Date: Fri, 27 Jun 2025 15:14:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=AE=8C=E6=88=90=E7=9B=B8=E6=9C=BA?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- camera/CamOperation_class.py | 4 +- config/app_config.json | 4 +- db/jtDB.db | Bin 102400 -> 106496 bytes ui/settings_ui.py | 20 ++ widgets/camera_manager.py | 165 +++++++++--- widgets/camera_settings_widget.py | 425 +++++++++++++++++++----------- widgets/main_window.py | 3 +- widgets/refresh_devices_fix.py | 76 ++++++ widgets/settings_dialog.py | 57 ---- widgets/settings_widget.py | 114 +++++++- widgets/settings_window.py | 107 ++++++++ 11 files changed, 727 insertions(+), 248 deletions(-) create mode 100644 widgets/refresh_devices_fix.py delete mode 100644 widgets/settings_dialog.py diff --git a/camera/CamOperation_class.py b/camera/CamOperation_class.py index 42732ab..12694e2 100644 --- a/camera/CamOperation_class.py +++ b/camera/CamOperation_class.py @@ -107,8 +107,10 @@ class CameraOperation: return MV_E_CALLORDER # ch:选择设备并创建句柄 | en:Select device and create handle + # 确保索引是整数类型 nConnectionNum = int(self.n_connect_num) - stDeviceList = cast(self.st_device_list.pDeviceInfo[int(nConnectionNum)], + print(f"打开设备,使用设备索引: {nConnectionNum}, 类型: {type(nConnectionNum)}") + stDeviceList = cast(self.st_device_list.pDeviceInfo[nConnectionNum], POINTER(MV_CC_DEVICE_INFO)).contents self.obj_cam = MvCamera() ret = self.obj_cam.MV_CC_CreateHandle(stDeviceList) diff --git a/config/app_config.json b/config/app_config.json index fe7ce47..c284556 100644 --- a/config/app_config.json +++ b/config/app_config.json @@ -49,8 +49,8 @@ "default_framerate": 30 }, "modbus": { - "host": "localhost", - "port": "5020" + "host": "192.168.2.88", + "port": "502" }, "serial": { "keyboard": { diff --git a/db/jtDB.db b/db/jtDB.db index 388c3a4deaf66aae23d9bafe4c71e5a5a3e439f7..2fe13471f6a26eea604841e855d20d3cda6b3217 100644 GIT binary patch delta 4340 zcmZwL$#NV;5C-6BG?qXhj|2urGRuJ33@YIeIv9uuoh$ zaVLDx#RndMS0H!;pL`;M2>z_B9!+)q=Pz=)IxDlXdQSZ|f9lWq(|dQF>vTG6X9gS5 zxj&BocOd$EvbnLnxe@(7*ge-f59_S;{_S1poj%itaNKg$yn(YLL;S{12R=IRJ$p#Dk+ur5h|2! zq?9CB{SAcj%F(vpywt$aMJP`iwTp?2gKFC~Q15n3dK(h<6ZQ*2-t6HBTj1}zYZ zmF7dr$4a{hjg-1AGRSa_T*<@cS|)^C30*`8sS=6^Ayq;hLb9Q&{n|J((_;q@OVxfN zVTsa@gkq&1LdwQU-xC@sneT`V)xITEDt$vK!=YJ!O{l0WeHD_)aI0Quza3JJ<1Yoh z<&nWJVuYY^F|z76JySyC3cW!HZ6+0WJR}(+^3typLX^;^CDROk`!Kwjwbw`(4l>N1 z`qeg}{ak>k$HU0(X-kTTm!reD8A4BII${bERIp&j@_NHSGr%bsr$E+z%GOz62b z8EiAoI7!H}Z3DTodL|_NsC~1iLyA*-H>sa$6V4nR?)8%m8GGw02g#^HuF@gaajdZ& zl7mdj>Ip*lMTvTx5UvYCj}gLkLFj064(l;&*OB3BCH5!@!`)Em2qCnt&?AJ9521$% z85Fu?#~Amp-mfRg<%yN?jEXmCDH?xkkK?jeM+FJFNbA>5#a#v#cKT4>~x zkqqZLi6=wfEa3(%VI?6<7oj2~nJ)4KZgFC17$%oEp@a~Iv`|b4 smpGxj3E>h4GLyTg*?$Sv3AwNHI|*Tg$bSoW5W)x%x;>zU3fC^!|DC=mT>t<8 delta 87 zcmZoTz}B#UZGtqb0s{ks> 24) nip2 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16) @@ -96,48 +101,81 @@ class CameraManager: nip4 = (mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff) ip = f"{nip1}.{nip2}.{nip3}.{nip4}" + display = f"[{i}]GigE: {user_defined_name} {model_name} ({ip})" + logging.debug(f"GigE相机: {display}") + device_info = { "index": i, "type": "GigE", "name": user_defined_name, "model": model_name, "ip": ip, - "display": f"[{i}]GigE: {user_defined_name} {model_name} ({ip})" + "display": display } devices_info.append(device_info) elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE: # USB相机 - user_defined_name = "" - for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chUserDefinedName: - if per == 0: - break - user_defined_name = user_defined_name + chr(per) + user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stUsb3VInfo.chUserDefinedName) + model_name = decoding_char(mvcc_dev_info.SpecialInfo.stUsb3VInfo.chModelName) - model_name = "" - for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chModelName: + # 序列号 + strSerialNumber = "" + for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chSerialNumber: if per == 0: break - model_name = model_name + chr(per) + strSerialNumber = strSerialNumber + chr(per) + + display = f"[{i}]USB: {user_defined_name} {model_name} ({strSerialNumber})" + logging.debug(f"USB相机: {display}") device_info = { "index": i, "type": "USB", "name": user_defined_name, "model": model_name, - "display": f"[{i}]USB: {user_defined_name} {model_name}" + "serial": strSerialNumber, + "display": display } devices_info.append(device_info) + elif mvcc_dev_info.nTLayerType == MV_GENTL_CAMERALINK_DEVICE: + # CameraLink相机 + user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stCMLInfo.chUserDefinedName) + model_name = decoding_char(mvcc_dev_info.SpecialInfo.stCMLInfo.chModelName) + + strSerialNumber = "" + for per in mvcc_dev_info.SpecialInfo.stCMLInfo.chSerialNumber: + if per == 0: + break + strSerialNumber = strSerialNumber + chr(per) + + display = f"[{i}]CML: {user_defined_name} {model_name} ({strSerialNumber})" + logging.debug(f"CML相机: {display}") + + device_info = { + "index": i, + "type": "CML", + "name": user_defined_name, + "model": model_name, + "serial": strSerialNumber, + "display": display + } + devices_info.append(device_info) + else: # 其他类型相机 + display = f"[{i}]Other" device_info = { "index": i, "type": "Other", - "display": f"[{i}]Other" + "display": display } devices_info.append(device_info) + logging.debug(f"其他类型相机: {display}") + # 添加详细日志 + logging.debug(f"枚举到的设备数量: {len(devices_info)}") return devices_info except Exception as e: @@ -146,7 +184,7 @@ class CameraManager: return None def open_device(self, device_index): - """打开相机设备 + """打开相机设备,参考BasicDemo.py的open_device实现 Args: device_index: 设备索引 @@ -159,14 +197,18 @@ class CameraManager: logging.warning("相机已经打开!") return False - # 检查设备索引是否有效 - if device_index < 0 or (self.deviceList and device_index >= self.deviceList.nDeviceNum): - logging.error(f"无效的设备索引: {device_index}") + # 确保有效的设备索引 + if device_index < 0 or self.deviceList is None or device_index >= self.deviceList.nDeviceNum: + error_msg = f"无效的设备索引: {device_index}, 设备列表: {self.deviceList is not None}" + if self.deviceList: + error_msg += f", 设备数量: {self.deviceList.nDeviceNum}" + logging.error(error_msg) return False try: - logging.debug(f"准备打开相机,设备索引: {device_index}") + logging.info(f"开始打开相机,设备索引: {device_index}") + # 设置当前选中的相机索引 self.nSelCamIndex = device_index # 创建相机操作对象 @@ -178,14 +220,25 @@ class CameraManager: self.isOpen = False return False - # 设置连续模式 - self.obj_cam_operation.Set_trigger_mode(False) + # 设置连续模式 (非触发模式) + ret = self.obj_cam_operation.Set_trigger_mode(False) + if ret != 0: + error_msg = f"设置连续模式失败! 错误码: 0x{ret:x}" + logging.error(error_msg) + # 出错时关闭设备 + self.obj_cam_operation.Close_device() + self.isOpen = False + return False # 获取参数 - self.obj_cam_operation.Get_parameter() + ret = self.obj_cam_operation.Get_parameter() + if ret != 0: + error_msg = f"获取相机参数失败! 错误码: 0x{ret:x}" + logging.error(error_msg) + # 继续执行,不返回失败 self.isOpen = True - logging.info(f"相机已打开,设备索引: {device_index}") + logging.info(f"相机已成功打开,设备索引: {device_index}") return True @@ -351,4 +404,52 @@ class CameraManager: except Exception as e: error_msg = f"设置相机参数时发生异常: {str(e)}" logging.error(error_msg) + return False + + def save_params_to_config(self, exposure, gain, frame_rate): + """保存相机参数到配置文件 + + Args: + exposure: 曝光值(滑块值) + gain: 增益(滑块值) + frame_rate: 帧率(滑块值) + + Returns: + bool: 是否成功保存参数 + """ + try: + # 创建相机参数配置 + config = { + "exposure": exposure, + "gain": gain, + "frame_rate": frame_rate + } + + # 保存到配置文件 + config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "camera_config.json") + os.makedirs(os.path.dirname(config_path), exist_ok=True) + + # 检查文件是否存在并读取 + if os.path.exists(config_path): + with open(config_path, 'r', encoding='utf-8') as f: + try: + full_config = json.load(f) + except json.JSONDecodeError: + full_config = {} + else: + full_config = {} + + # 更新配置 + full_config["camera_params"] = config + + # 写入文件 + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(full_config, f, indent=4, ensure_ascii=False) + + logging.info(f"相机参数已保存到配置文件: {config_path}") + return True + + except Exception as e: + error_msg = f"保存相机参数到配置文件时发生异常: {str(e)}" + logging.error(error_msg) return False \ No newline at end of file diff --git a/widgets/camera_settings_widget.py b/widgets/camera_settings_widget.py index 1ece866..7e15836 100644 --- a/widgets/camera_settings_widget.py +++ b/widgets/camera_settings_widget.py @@ -28,27 +28,91 @@ from ui.settings_ui import SettingsUI from widgets.camera_manager import CameraManager -class CameraSettingsWidget(SettingsUI): - """相机设置控制器,管理相机设置并提供与主窗口相机显示部分的通信""" +from PySide6.QtCore import QObject + +class CameraSettingsWidget(QObject): + """相机设置控制器,管理相机设置并提供与主窗口相机显示部分的通信 + 注意:这是一个QObject控制器,不是QWidget,只处理逻辑,UI控件需要从父组件获取""" - # 定义信号 + # 定义信号 signal_camera_connection = Signal(bool, str) # 相机连接状态信号 (是否连接, 错误消息) signal_camera_params_changed = Signal(float, float, float) # 相机参数变化信号 (曝光, 增益, 帧率) signal_camera_error = Signal(str) # 相机错误信号 settings_changed = Signal() # 设置变更信号,与 SettingsWindow 兼容 def __init__(self, parent=None): + """初始化相机设置控制器 + + Args: + parent: 父组件,需要包含所有必要的UI控件 + """ + # 作为QObject初始化 super().__init__(parent) + # 记录父组件引用 + self.parent = parent + # 获取相机管理器实例 self.camera_manager = CameraManager.get_instance() - # 初始化UI控件状态 - self.update_controls() + # 初始化日志记录 + logging.debug("CameraSettingsWidget初始化开始") + # 从父组件获取所有需要的UI控件 + if parent is not None: + logging.info(f"父控件类型: {type(parent).__name__}") + + # 从父组件获取所有需要的UI控件 + self.camera_combo = getattr(parent, 'camera_combo', None) + self.refresh_button = getattr(parent, 'refresh_button', None) + self.open_button = getattr(parent, 'open_button', None) + self.close_button = getattr(parent, 'close_button', None) + self.test_button = getattr(parent, 'test_button', None) + self.exposure_slider = getattr(parent, 'exposure_slider', None) + self.gain_slider = getattr(parent, 'gain_slider', None) + self.framerate_slider = getattr(parent, 'framerate_slider', None) + self.exposure_value = getattr(parent, 'exposure_value', None) + self.gain_value = getattr(parent, 'gain_value', None) + self.framerate_value = getattr(parent, 'framerate_value', None) + self.get_params_button = getattr(parent, 'get_params_button', None) + self.set_params_button = getattr(parent, 'set_params_button', None) + self.save_camera_button = getattr(parent, 'save_camera_button', None) + self.preview_frame = getattr(parent, 'preview_frame', None) + + # 检查是否成功获取到了所有必要的UI控件 + if self.camera_combo is None: + logging.error("未能从父组件获取camera_combo") + else: + logging.info(f"获取到父组件的camera_combo: ID={id(self.camera_combo)}, 项目数={self.camera_combo.count()}") + + # 测试向下拉框添加项目 + try: + self.camera_combo.addItem("控制器初始化测试项") + logging.info(f"成功添加测试项到camera_combo,当前项目数={self.camera_combo.count()}") + except Exception as e: + logging.error(f"添加测试项失败: {e}") + else: + logging.error("CameraSettingsWidget必须有一个包含UI控件的父组件") + # 创建缺少的UI控件引用,这些引用将为None + self.camera_combo = None + self.refresh_button = None + self.open_button = None + self.close_button = None + self.test_button = None + self.exposure_slider = None + self.gain_slider = None + self.framerate_slider = None + self.exposure_value = None + self.gain_value = None + self.framerate_value = None + self.get_params_button = None + self.set_params_button = None + self.save_camera_button = None + self.preview_frame = None + # 连接信号和槽 self.connect_signals() - + # 初始化相机参数范围 self.frame_rate_min = 1.0 self.frame_rate_max = 60.0 @@ -63,7 +127,8 @@ class CameraSettingsWidget(SettingsUI): def connect_signals(self): """连接信号和槽""" # 设备选择和刷新 - self.refresh_button.clicked.connect(self.refresh_devices) + if hasattr(self, 'refresh_button'): + self.refresh_button.clicked.connect(self.refresh_devices) # 相机控制按钮 self.open_button.clicked.connect(self.open_camera) @@ -79,259 +144,315 @@ class CameraSettingsWidget(SettingsUI): self.get_params_button.clicked.connect(self.get_camera_params) self.set_params_button.clicked.connect(self.set_camera_params) self.save_camera_button.clicked.connect(self.save_camera_params) - + def refresh_devices(self): """刷新设备列表""" - self.camera_combo.clear() + logging.info("【设备刷新】开始...") - # 枚举设备 - devices_info = self.camera_manager.enum_devices() + # 直接检查是否能访问正确的combobox + if not hasattr(self, 'camera_combo') or self.camera_combo is None: + logging.error("【设备刷新】无法访问camera_combo,尝试从父组件获取") + + # 尝试从父组件获取 + if hasattr(self, 'parent') and self.parent is not None: + if hasattr(self.parent, 'camera_combo'): + self.camera_combo = self.parent.camera_combo + logging.info(f"【设备刷新】已从父组件获取camera_combo: {id(self.camera_combo)}") + else: + logging.error("【设备刷新】父组件也没有camera_combo") + return + else: + logging.error("【设备刷新】无法获取camera_combo且没有父组件") + return + + # 直接测试下拉框是否可用 + try: + # 先清理 + self.camera_combo.clear() + + # 添加一个测试项 + self.camera_combo.addItem("刷新中...") + logging.info(f"【设备刷新】成功添加测试项,当前项目数={self.camera_combo.count()}") + + # 更新UI + self.camera_combo.update() + self.camera_combo.repaint() + except Exception as e: + logging.error(f"【设备刷新】测试下拉框失败: {e}") - if not devices_info: - self.camera_combo.addItem("未发现相机设备") - self.camera_combo.setEnabled(False) - self.open_button.setEnabled(False) - return - - # 添加设备到下拉框 - for device in devices_info: - self.camera_combo.addItem(device["display"], device["index"]) - - self.camera_combo.setEnabled(True) - self.open_button.setEnabled(True) - - # 更新控件状态 - self.update_controls() - + # 1. 枚举设备 + try: + devices_info = self.camera_manager.enum_devices() + if not devices_info: + logging.warning("【设备刷新】未发现任何相机设备。") + devices_info = [] + except Exception as e: + logging.error(f"【设备刷新】枚举设备时发生错误: {e}") + devices_info = [] + + # 2. 准备要显示到下拉列表的数据 + devList = [] + if devices_info: + for device in devices_info: + devList.append(device["display"]) + logging.info(f"【设备刷新】找到 {len(devList)} 个设备: {devList}") + else: + devList.append("未发现相机设备") + logging.info(f"【设备刷新】将显示默认值 '未发现相机设备'") + + # 3. 更新UI上的下拉列表 + try: + if hasattr(self, 'camera_combo') and self.camera_combo is not None: + logging.info(f"【设备刷新】开始更新下拉列表,当前状态: 项目数={self.camera_combo.count()}, 是否可见={self.camera_combo.isVisible()}") + + self.camera_combo.blockSignals(True) + self.camera_combo.clear() + + # 确保项目数清零 + if self.camera_combo.count() > 0: + logging.warning(f"【设备刷新】clear()后项目数仍为 {self.camera_combo.count()}") + + # 直接添加项目 - 单个添加,避免批量添加可能的问题 + for item in devList: + self.camera_combo.addItem(item) + logging.debug(f"【设备刷新】已添加项目: {item}") + + # 确保设置当前项目 + if self.camera_combo.count() > 0: + self.camera_combo.setCurrentIndex(0) + logging.info(f"【设备刷新】已设置当前索引为0,显示文本: {self.camera_combo.currentText()}") + else: + logging.error("【设备刷新】未能添加任何项目到下拉列表") + + self.camera_combo.blockSignals(False) + + # 强制更新UI + self.camera_combo.update() + self.camera_combo.repaint() + + # 确保ComboBox有足够的尺寸显示内容 + self.camera_combo.adjustSize() + + logging.info(f"【设备刷新】下拉列表更新完成。当前项目数: {self.camera_combo.count()}, 当前文本: {self.camera_combo.currentText()}") + else: + logging.error("【设备刷新】无法更新下拉列表,camera_combo不存在") + except Exception as e: + logging.error(f"【设备刷新】更新下拉列表时发生错误: {e}") + + # 4. 更新其他控件的状态 + try: + self.update_controls() + logging.info("【设备刷新】控件状态已更新。") + except Exception as e: + logging.error(f"【设备刷新】更新控件状态时发生错误: {e}") + + # 5. 如果下拉列表仍然为空,尝试最后一次强制添加 + try: + if hasattr(self, 'camera_combo') and self.camera_combo is not None: + if self.camera_combo.count() == 0: + logging.warning("【设备刷新】下拉列表仍然为空,尝试强制添加项目") + self.camera_combo.addItem("未发现相机设备(强制添加)") + self.camera_combo.setCurrentIndex(0) + self.camera_combo.update() + self.camera_combo.repaint() + except Exception as e: + logging.error(f"【设备刷新】强制添加项目时发生错误: {e}") + def get_selected_device_index(self): - """获取当前选中的设备索引""" - if self.camera_combo.count() == 0: + """获取当前选中的设备索引,参考BasicDemo.py的TxtWrapBy实现""" + try: + if not hasattr(self, 'camera_combo') or self.camera_combo is None: + logging.error("无法获取设备索引:camera_combo不存在") + return -1 + + if self.camera_combo.count() == 0: + logging.warning("设备下拉列表为空") + return -1 + + if self.camera_combo.currentText() == "未发现相机设备": + return -1 + + current_text = self.camera_combo.currentText() + start = current_text.find("[") + 1 + end = current_text.find("]", start) + + if start <= 0 or end <= 0 or start >= end: + logging.error(f"设备文本格式不正确: '{current_text}'") + return -1 + + return int(current_text[start:end]) + except Exception as e: + logging.error(f"获取设备索引时出错: {e}, 文本: '{self.camera_combo.currentText() if hasattr(self, 'camera_combo') and self.camera_combo is not None else 'N/A'}'") return -1 - - return self.camera_combo.currentData() def update_controls(self): """更新控件状态""" is_open = self.camera_manager.isOpen is_grabbing = self.camera_manager.isGrabbing + + # 使用安全的方式访问控件 + if hasattr(self, 'refresh_button') and self.refresh_button is not None: + self.refresh_button.setEnabled(not is_open) - # 设备选择和刷新 - self.camera_combo.setEnabled(not is_open) - self.refresh_button.setEnabled(not is_open) + if hasattr(self, 'camera_combo') and self.camera_combo is not None: + self.camera_combo.setEnabled(not is_open) - # 相机控制按钮 - self.open_button.setEnabled(not is_open and self.camera_combo.count() > 0 and self.camera_combo.currentData() is not None) - self.close_button.setEnabled(is_open) - self.test_button.setEnabled(is_open and not is_grabbing) + has_valid_selection = self.get_selected_device_index() != -1 - # 参数滑块 - self.exposure_slider.setEnabled(is_open) - self.gain_slider.setEnabled(is_open) - self.framerate_slider.setEnabled(is_open) + if hasattr(self, 'open_button') and self.open_button is not None: + self.open_button.setEnabled(not is_open and has_valid_selection) - # 参数操作按钮 - self.get_params_button.setEnabled(is_open) - self.set_params_button.setEnabled(is_open) - self.save_camera_button.setEnabled(is_open) + if hasattr(self, 'close_button') and self.close_button is not None: + self.close_button.setEnabled(is_open) + + if hasattr(self, 'test_button') and self.test_button is not None: + self.test_button.setEnabled(is_open and not is_grabbing) + + if hasattr(self, 'get_params_button') and self.get_params_button is not None: + self.get_params_button.setEnabled(is_open) + + if hasattr(self, 'set_params_button') and self.set_params_button is not None: + self.set_params_button.setEnabled(is_open) + + if hasattr(self, 'save_camera_button') and self.save_camera_button is not None: + self.save_camera_button.setEnabled(is_open) + + if hasattr(self, 'exposure_slider') and self.exposure_slider is not None: + self.exposure_slider.setEnabled(is_open) + + if hasattr(self, 'gain_slider') and self.gain_slider is not None: + self.gain_slider.setEnabled(is_open) + + if hasattr(self, 'framerate_slider') and self.framerate_slider is not None: + self.framerate_slider.setEnabled(is_open) def open_camera(self): - """打开相机""" + """打开相机,参考BasicDemo.py实现""" if self.camera_manager.isOpen: - QMessageBox.warning(self, "错误", "相机已经打开!") return device_index = self.get_selected_device_index() if device_index < 0: - QMessageBox.warning(self, "错误", "请先选择一个相机设备!") + QMessageBox.warning(self, "错误", "请先选择一个有效的相机设备!") return - success = self.camera_manager.open_device(device_index) + self.open_button.setEnabled(False) + self.open_button.setText("打开中...") - if success: - # 获取相机参数并更新UI - self.get_camera_params() - - # 更新控件状态 + try: + success = self.camera_manager.open_device(device_index) + if success: + self.get_camera_params() + self.signal_camera_connection.emit(True, "") + else: + self.signal_camera_connection.emit(False, "打开相机失败") + QMessageBox.warning(self, "错误", "打开相机失败") + except Exception as e: + QMessageBox.critical(self, "错误", f"打开相机时发生异常: {str(e)}") + finally: + self.open_button.setText("打开相机") self.update_controls() - - # 发送连接信号 - self.signal_camera_connection.emit(True, "") - else: - # 发送连接失败信号 - self.signal_camera_connection.emit(False, "打开相机失败") def close_camera(self): """关闭相机""" if not self.camera_manager.isOpen: return - success = self.camera_manager.close_device() - - # 更新控件状态 + self.camera_manager.close_device() + self.signal_camera_connection.emit(False, "") self.update_controls() - - # 发送连接信号 - if success: - self.signal_camera_connection.emit(False, "") - else: - self.signal_camera_connection.emit(False, "关闭相机出错") def test_camera(self): """测试相机(在预览窗口显示图像)""" if not self.camera_manager.isOpen: - QMessageBox.warning(self, "错误", "请先打开相机!") return if self.camera_manager.isGrabbing: - # 停止预览 self.camera_manager.stop_grabbing() self.test_button.setText("开始预览") - self.update_controls() else: - # 获取预览窗口句柄 try: - # 尝试使用PySide6方式获取窗口句柄 window_id = int(self.preview_frame.winId()) - except: - try: - # 尝试使用PyQt5方式获取窗口句柄 - window_id = self.preview_frame.winId().__int__() - except: - # 其他情况 - window_id = int(self.preview_frame.winId()) - - # 开始预览 - success = self.camera_manager.start_grabbing(window_id) - - if success: - self.test_button.setText("停止预览") - else: - QMessageBox.warning(self, "错误", "开始预览失败!") - - # 更新控件状态 - self.update_controls() + if self.camera_manager.start_grabbing(window_id): + self.test_button.setText("停止预览") + except Exception as e: + QMessageBox.warning(self, "错误", f"开始预览失败: {e}") + + self.update_controls() def update_exposure_value(self, value): """更新曝光值显示""" - # 将滑块值转换为实际曝光值(对数映射) min_log = log10(self.exposure_min) max_log = log10(self.exposure_max) log_range = max_log - min_log - log_value = min_log + (value / 100.0) * log_range actual_value = 10 ** log_value - - # 更新显示 self.exposure_value.setText(f"{actual_value:.1f} μs") def update_gain_value(self, value): """更新增益值显示""" - # 将滑块值转换为实际增益值 actual_value = self.gain_min + (value / 100.0) * (self.gain_max - self.gain_min) - - # 更新显示 self.gain_value.setText(f"{actual_value:.1f} dB") def update_frame_rate_value(self, value): """更新帧率值显示""" - # 将滑块值转换为实际帧率值 actual_value = self.frame_rate_min + (value / 100.0) * (self.frame_rate_max - self.frame_rate_min) - - # 更新显示 self.framerate_value.setText(f"{actual_value:.1f} fps") def get_camera_params(self): """获取相机参数""" if not self.camera_manager.isOpen: - QMessageBox.warning(self, "错误", "请先打开相机!") return - # 获取参数 params = self.camera_manager.get_parameters() if not params: - QMessageBox.warning(self, "错误", "获取相机参数失败!") return exposure_time, gain, frame_rate = params - # 更新滑块值 - # 曝光时间(对数映射) - min_log = log10(self.exposure_min) - max_log = log10(self.exposure_max) - log_range = max_log - min_log - log_value = log10(exposure_time) - slider_value = int(((log_value - min_log) / log_range) * 100) - self.exposure_slider.setValue(slider_value) + min_log_exp = log10(self.exposure_min) + max_log_exp = log10(self.exposure_max) + self.exposure_slider.setValue(int(((log10(exposure_time) - min_log_exp) / (max_log_exp - min_log_exp)) * 100)) - # 增益 - slider_value = int(((gain - self.gain_min) / (self.gain_max - self.gain_min)) * 100) - self.gain_slider.setValue(slider_value) + self.gain_slider.setValue(int(((gain - self.gain_min) / (self.gain_max - self.gain_min)) * 100)) + self.framerate_slider.setValue(int(((frame_rate - self.frame_rate_min) / (self.frame_rate_max - self.frame_rate_min)) * 100)) - # 帧率 - slider_value = int(((frame_rate - self.frame_rate_min) / (self.frame_rate_max - self.frame_rate_min)) * 100) - self.framerate_slider.setValue(slider_value) - - # 发送参数变化信号 self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate) def set_camera_params(self): """设置相机参数""" if not self.camera_manager.isOpen: - QMessageBox.warning(self, "错误", "请先打开相机!") return - # 从滑块获取参数值 - # 曝光时间(对数映射) min_log = log10(self.exposure_min) max_log = log10(self.exposure_max) log_range = max_log - min_log log_value = min_log + (self.exposure_slider.value() / 100.0) * log_range exposure_time = 10 ** log_value - # 增益 gain = self.gain_min + (self.gain_slider.value() / 100.0) * (self.gain_max - self.gain_min) - - # 帧率 frame_rate = self.frame_rate_min + (self.framerate_slider.value() / 100.0) * (self.frame_rate_max - self.frame_rate_min) - # 设置参数 - success = self.camera_manager.set_parameters(frame_rate, exposure_time, gain) - - if success: - QMessageBox.information(self, "成功", "相机参数设置成功!") - - # 发送参数变化信号 - self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate) - else: - QMessageBox.warning(self, "错误", "相机参数设置失败!") + self.camera_manager.set_parameters(frame_rate, exposure_time, gain) + self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate) def save_camera_params(self): """保存相机参数到配置文件""" if not self.camera_manager.isOpen: - QMessageBox.warning(self, "错误", "请先打开相机!") return - # 获取当前参数 exposure = self.exposure_slider.value() gain = self.gain_slider.value() frame_rate = self.framerate_slider.value() - # 保存到配置文件 - success = self.camera_manager.save_params_to_config(exposure, gain, frame_rate) - - if success: - QMessageBox.information(self, "成功", "相机参数已保存到配置文件") - self.settings_changed.emit() # 发送设置变更信号 - logging.info(f"相机参数已保存: 曝光={exposure}μs, 增益={gain}dB, 帧率={frame_rate}fps") + if self.camera_manager.save_params_to_config(exposure, gain, frame_rate): + QMessageBox.information(self, "成功", "相机参数已保存") + self.settings_changed.emit() else: QMessageBox.critical(self, "错误", "保存相机参数失败") - logging.error("保存相机参数失败") def closeEvent(self, event): """窗口关闭事件""" - # 确保关闭相机 if self.camera_manager.isOpen: self.camera_manager.close_device() - - # 处理事件 - super().closeEvent(event) \ No newline at end of file + super().closeEvent(event) diff --git a/widgets/main_window.py b/widgets/main_window.py index 790e7db..bb7c1a2 100644 --- a/widgets/main_window.py +++ b/widgets/main_window.py @@ -34,9 +34,8 @@ import time # 导入UI from ui.main_window_ui import MainWindowUI -# 导入相机显示组件和设置组件 +# 导入相机显示组件 from widgets.camera_display_widget import CameraDisplayWidget -from widgets.camera_settings_widget import CameraSettingsWidget # 导入检验配置管理器 from utils.inspection_config_manager import InspectionConfigManager diff --git a/widgets/refresh_devices_fix.py b/widgets/refresh_devices_fix.py new file mode 100644 index 0000000..38bab47 --- /dev/null +++ b/widgets/refresh_devices_fix.py @@ -0,0 +1,76 @@ +""" +刷新相机设备按钮修复工具 + +此文件包含修复相机设备刷新按钮的工具函数 +""" + +import logging +from PySide6.QtWidgets import QPushButton + +def fix_camera_refresh_button(settings_widget): + """ + 修复相机设置中的刷新设备按钮 + + Args: + settings_widget: 设置窗口实例 + + Returns: + bool: 是否成功修复 + """ + try: + logging.info("尝试修复相机刷新设备按钮...") + + # 检查是否存在相机设置组件 + if not hasattr(settings_widget, 'camera_settings'): + logging.error("设置窗口中没有camera_settings属性") + return False + + # 检查是否存在刷新按钮 + if hasattr(settings_widget, 'refresh_button'): + refresh_button = settings_widget.refresh_button + logging.info(f"在settings_widget中找到刷新按钮: {refresh_button}") + else: + # 尝试查找刷新按钮 + refresh_button = None + # 方法1: 直接在camera_tab中查找 + if hasattr(settings_widget, 'camera_tab'): + for child in settings_widget.camera_tab.findChildren(QPushButton): + if child.text() == "刷新设备": + refresh_button = child + logging.info(f"在camera_tab中找到刷新按钮: {refresh_button}") + break + + # 方法2: 在camera_settings中查找 + if refresh_button is None and hasattr(settings_widget.camera_settings, 'refresh_button'): + refresh_button = settings_widget.camera_settings.refresh_button + logging.info(f"在camera_settings中找到刷新按钮: {refresh_button}") + + # 如果找到了刷新按钮,则绑定事件 + if refresh_button: + # 断开所有现有连接 + try: + refresh_button.clicked.disconnect() + except: + pass + + # 连接到refresh_devices方法 + refresh_button.clicked.connect(settings_widget.camera_settings.refresh_devices) + logging.info("成功绑定刷新按钮到refresh_devices方法") + + # 测试调用一次 + try: + settings_widget.camera_settings.refresh_devices() + logging.info("已手动调用refresh_devices方法初始化设备列表") + except Exception as e: + logging.error(f"调用refresh_devices失败: {str(e)}") + + return True + else: + logging.error("未找到刷新设备按钮") + return False + + except Exception as e: + logging.error(f"修复相机刷新按钮时发生错误: {str(e)}") + import traceback + logging.error(traceback.format_exc()) + return False diff --git a/widgets/settings_dialog.py b/widgets/settings_dialog.py deleted file mode 100644 index 047e936..0000000 --- a/widgets/settings_dialog.py +++ /dev/null @@ -1,57 +0,0 @@ -import logging - -try: - from PySide6.QtWidgets import QDialog, QVBoxLayout, QTabWidget, QDialogButtonBox - from PySide6.QtCore import Qt -except ImportError: - from PyQt5.QtWidgets import QDialog, QVBoxLayout, QTabWidget, QDialogButtonBox - from PyQt5.QtCore import Qt - - -class SettingsDialog(QDialog): - """设置对话框,用于显示和管理各种设置页面""" - - def __init__(self, parent=None): - super().__init__(parent) - - # 设置对话框标题和大小 - self.setWindowTitle("系统设置") - self.resize(800, 600) - - # 创建布局 - self.layout = QVBoxLayout(self) - - # 创建选项卡控件 - self.tab_widget = QTabWidget() - self.layout.addWidget(self.tab_widget) - - # 创建按钮盒 - self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - self.button_box.accepted.connect(self.accept) - self.button_box.rejected.connect(self.reject) - self.layout.addWidget(self.button_box) - - # 设置窗口模态 - self.setModal(True) - - logging.info("设置对话框已创建") - - def add_settings_page(self, widget, title): - """添加设置页面 - - Args: - widget: 设置页面部件 - title: 页面标题 - """ - self.tab_widget.addTab(widget, title) - logging.info(f"已添加设置页面: {title}") - - def accept(self): - """确认按钮处理""" - logging.info("设置已保存") - super().accept() - - def reject(self): - """取消按钮处理""" - logging.info("设置已取消") - super().reject() \ No newline at end of file diff --git a/widgets/settings_widget.py b/widgets/settings_widget.py index dbeb9a3..54f8b33 100644 --- a/widgets/settings_widget.py +++ b/widgets/settings_widget.py @@ -12,6 +12,7 @@ from utils.config_loader import ConfigLoader from utils.app_mode import AppMode from widgets.camera_settings_widget import CameraSettingsWidget from widgets.serial_settings_widget import SerialSettingsWidget +from ui.electricity_settings_ui import ElectricitySettingsUI class SettingsWidget(SettingsUI): # 定义信号 @@ -24,13 +25,91 @@ class SettingsWidget(SettingsUI): logging.info("正在初始化SettingsWidget") + # 先检查UI中的相机下拉框 + if hasattr(self, 'camera_combo') and self.camera_combo is not None: + logging.info(f"相机下拉框状态检查: 可见={self.camera_combo.isVisible()}, " + f"尺寸={self.camera_combo.size().width()}x{self.camera_combo.size().height()}, " + f"包含项={self.camera_combo.count()}") + else: + logging.error("无法找到相机下拉框(camera_combo)!") + + # 检查刷新按钮 + if hasattr(self, 'refresh_button') and self.refresh_button is not None: + logging.info(f"刷新按钮状态检查: 可见={self.refresh_button.isVisible()}, " + f"尺寸={self.refresh_button.size().width()}x{self.refresh_button.size().height()}") + else: + logging.error("无法找到刷新按钮(refresh_button)!") + # 创建子设置控制器 - self.camera_settings = CameraSettingsWidget(self) + try: + # 先添加一个测试项到下拉框 + if hasattr(self, 'camera_combo'): + self.camera_combo.clear() + self.camera_combo.addItem("测试项 - 初始化前") + logging.info(f"已添加测试项到下拉框,当前项数={self.camera_combo.count()}") + + self.camera_settings = CameraSettingsWidget(self) + logging.info("相机设置组件创建成功") + + # 确保相机设置组件正确初始化信号和槽(手动调用一次connect_signals) + if hasattr(self.camera_settings, 'connect_signals'): + logging.info("手动调用相机设置控制器的connect_signals方法") + self.camera_settings.connect_signals() + + # 添加后处理:直接给刷新按钮添加事件处理 + if hasattr(self, 'refresh_button'): + logging.info("手动绑定SettingsWidget中的刷新按钮点击事件") + try: + self.refresh_button.clicked.disconnect() # 断开所有现有连接 + except Exception as e: + logging.warning(f"断开刷新按钮现有连接时出错: {e}") + + self.refresh_button.clicked.connect(self.camera_settings.refresh_devices) + + # 立即调用一次刷新方法 + self.camera_settings.refresh_devices() + logging.info("已刷新相机设备列表") + + # 再次检查相机下拉框状态 + if hasattr(self, 'camera_combo'): + logging.info(f"刷新后相机下拉框状态: 项目数={self.camera_combo.count()}, " + f"当前文本={self.camera_combo.currentText() if self.camera_combo.count() > 0 else 'None'}") + + # 尝试手动向下拉框添加一项 + self.camera_combo.addItem("测试项 - 手动添加") + logging.info(f"手动添加后下拉框状态: 项目数={self.camera_combo.count()}") + + except Exception as e: + logging.error(f"初始化相机设置组件失败: {e}") + self.serial_settings = SerialSettingsWidget(self) self.inspection_settings = InspectionSettingsWidget(self) self.pallet_type_settings = PalletTypeSettingsWidget(self) self.plc_settings = PLCSettingsWidget(self) + # 创建电量监控设置组件 + try: + self.electricity_settings = ElectricitySettingsUI(self) + logging.info("电量监控设置组件创建成功") + + # 移除临时占位符标签并添加电量监控设置部件 + if hasattr(self, 'electricity_placeholder'): + logging.info("移除电量监控临时占位符") + self.electricity_layout.removeWidget(self.electricity_placeholder) + self.electricity_placeholder.hide() + self.electricity_placeholder.deleteLater() + else: + logging.warning("未找到电量监控临时占位符标签") + + # 检查布局是否可用 + if hasattr(self, 'electricity_layout'): + logging.info("添加电量监控设置部件到布局") + self.electricity_layout.addWidget(self.electricity_settings) + else: + logging.error("无法找到electricity_layout布局") + except Exception as e: + logging.error(f"初始化电量监控设置组件失败: {e}") + # 移除临时占位符标签并添加检验设置部件 if hasattr(self, 'inspection_placeholder'): logging.info("移除临时占位符") @@ -84,12 +163,43 @@ class SettingsWidget(SettingsUI): def connect_signals(self): """连接信号和槽""" # 连接子设置控制器的信号 - self.camera_settings.settings_changed.connect(self.on_settings_changed) + if hasattr(self, 'camera_settings'): + try: + self.camera_settings.settings_changed.connect(self.on_settings_changed) + except Exception as e: + logging.error(f"连接相机设置信号时出错: {e}") + self.serial_settings.settings_changed.connect(self.on_settings_changed) self.inspection_settings.settings_changed.connect(self.on_settings_changed) self.pallet_type_settings.settings_changed.connect(self.on_settings_changed) self.plc_settings.settings_changed.connect(self.on_settings_changed) + # 连接电量监控设置信号 + if hasattr(self, 'electricity_settings'): + try: + self.electricity_settings.settings_changed.connect(self.on_settings_changed) + logging.info("已连接电量监控设置信号") + except Exception as e: + logging.error(f"连接电量监控设置信号时出错: {e}") + + # 直接连接相机标签页中的刷新按钮 + try: + if hasattr(self, 'camera_tab') and hasattr(self, 'refresh_button') and hasattr(self, 'camera_settings'): + logging.info("在SettingsWidget中直接连接刷新按钮") + try: + # 断开可能存在的连接 + self.refresh_button.clicked.disconnect() + except Exception as e: + logging.warning(f"断开刷新按钮现有连接时出错: {e}") + + # 连接到相机设置控件的刷新方法 + self.refresh_button.clicked.connect(self.camera_settings.refresh_devices) + + # 立即调用一次刷新方法 + self.camera_settings.refresh_devices() + except Exception as e: + logging.error(f"连接刷新按钮时出错: {e}") + # 数据库类型选择 self.db_type_combo.currentTextChanged.connect(self.update_db_ui_state) diff --git a/widgets/settings_window.py b/widgets/settings_window.py index a63f0a0..7e70ee6 100644 --- a/widgets/settings_window.py +++ b/widgets/settings_window.py @@ -28,6 +28,113 @@ class SettingsWindow(QDialog): self.settings_widget = SettingsWidget(self) main_layout.addWidget(self.settings_widget) + # 应用相机刷新按钮修复 + try: + # 先尝试直接找到刷新按钮并添加事件处理 + logging.info("尝试直接查找刷新按钮并添加事件处理...") + from PySide6.QtWidgets import QPushButton + + # 在整个UI层次中查找刷新设备按钮 + refresh_buttons = [] + for widget in self.findChildren(QPushButton): + if widget.text() == "刷新设备": + refresh_buttons.append(widget) + + if refresh_buttons: + logging.info(f"找到 {len(refresh_buttons)} 个刷新设备按钮") + for i, button in enumerate(refresh_buttons): + try: + # 断开现有连接 + button.clicked.disconnect() + except Exception: + pass + + # 连接到相机设置的刷新方法 + if hasattr(self.settings_widget, 'camera_settings') and hasattr(self.settings_widget.camera_settings, 'refresh_devices'): + button.clicked.connect(self.settings_widget.camera_settings.refresh_devices) + logging.info(f"已将刷新按钮 #{i+1} 连接到refresh_devices方法") + + # 尝试调用一次 + self.settings_widget.camera_settings.refresh_devices() + logging.info("已手动调用refresh_devices方法初始化设备列表") + + # 使用导入的修复模块作为备选方案 + try: + # 使用相对导入,更可靠 + from . import refresh_devices_fix + refresh_devices_fix.fix_camera_refresh_button(self.settings_widget) + logging.info("已应用相机刷新按钮修复(通过相对导入)") + except ImportError: + # 如果相对导入失败,尝试绝对导入 + try: + import refresh_devices_fix + refresh_devices_fix.fix_camera_refresh_button(self.settings_widget) + logging.info("已应用相机刷新按钮修复(通过绝对导入)") + except ImportError: + # 如果绝对导入也失败,直接内联实现修复逻辑 + logging.warning("导入refresh_devices_fix失败,使用内联修复") + self._fix_camera_refresh_button() + except Exception as e: + logging.error(f"应用相机刷新按钮修复失败: {str(e)}") + + def _fix_camera_refresh_button(self): + """内联实现的刷新按钮修复逻辑,用于导入模块失败的情况""" + try: + logging.info("使用内联方法修复相机刷新按钮...") + + # 获取设置部件 + settings_widget = self.settings_widget + + # 确保相机设置组件存在 + if not hasattr(settings_widget, 'camera_settings'): + logging.error("设置窗口中没有camera_settings属性") + return + + # 查找刷新按钮 + refresh_button = None + + # 方法1: 在settings_widget上查找 + if hasattr(settings_widget, 'refresh_button'): + refresh_button = settings_widget.refresh_button + logging.info("在settings_widget中找到刷新按钮") + # 方法2: 在camera_tab中查找 + elif hasattr(settings_widget, 'camera_tab'): + from PySide6.QtWidgets import QPushButton + for child in settings_widget.camera_tab.findChildren(QPushButton): + if child.text() == "刷新设备": + refresh_button = child + logging.info("在camera_tab中找到刷新按钮") + break + # 方法3: 在camera_settings中查找 + elif hasattr(settings_widget.camera_settings, 'refresh_button'): + refresh_button = settings_widget.camera_settings.refresh_button + logging.info("在camera_settings中找到刷新按钮") + + # 如果找到按钮,则绑定事件 + if refresh_button: + from PySide6.QtCore import QObject + if isinstance(refresh_button, QObject): + try: + # 断开现有连接 + refresh_button.clicked.disconnect() + except: + pass + + # 连接到刷新方法 + refresh_button.clicked.connect(settings_widget.camera_settings.refresh_devices) + logging.info("成功绑定刷新按钮到refresh_devices方法") + + # 手动调用一次 + settings_widget.camera_settings.refresh_devices() + logging.info("已手动调用refresh_devices初始化设备列表") + else: + logging.error(f"刷新按钮不是QObject: {type(refresh_button)}") + else: + logging.error("未找到刷新设备按钮") + + except Exception as e: + logging.error(f"内联修复相机刷新按钮时发生错误: {str(e)}") + # 添加串口设置到标签页 self.serial_settings = SerialSettingsWidget(self) self.settings_widget.tab_widget.addTab(self.serial_settings, "串口设置")