完成视频集成

This commit is contained in:
zhu-mengmeng 2025-06-07 10:45:09 +08:00
commit bd30815b59
51 changed files with 9388 additions and 0 deletions

362
camera/BasicDemo.py Normal file
View File

@ -0,0 +1,362 @@
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import *
from CamOperation_class import CameraOperation
from MvCameraControl_class import *
from MvErrorDefine_const import *
from CameraParams_header import *
from PyUICBasicDemo import Ui_MainWindow
import ctypes
# 获取选取设备信息的索引,通过[]之间的字符去解析
def TxtWrapBy(start_str, end, all):
start = all.find(start_str)
if start >= 0:
start += len(start_str)
end = all.find(end, start)
if end >= 0:
return all[start:end].strip()
# 将返回的错误码转换为十六进制显示
def ToHexStr(num):
chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'}
hexStr = ""
if num < 0:
num = num + 2 ** 32
while num >= 16:
digit = num % 16
hexStr = chaDic.get(digit, str(digit)) + hexStr
num //= 16
hexStr = chaDic.get(num, str(num)) + hexStr
return hexStr
if __name__ == "__main__":
# ch:初始化SDK | en: initialize SDK
MvCamera.MV_CC_Initialize()
global deviceList
deviceList = MV_CC_DEVICE_INFO_LIST()
global cam
cam = MvCamera()
global nSelCamIndex
nSelCamIndex = 0
global obj_cam_operation
obj_cam_operation = 0
global isOpen
isOpen = False
global isGrabbing
isGrabbing = False
global isCalibMode # 是否是标定模式(获取原始图像)
isCalibMode = True
# 绑定下拉列表至设备信息索引
def xFunc(event):
global nSelCamIndex
nSelCamIndex = TxtWrapBy("[", "]", ui.ComboDevices.get())
# Decoding Characters
def decoding_char(c_ubyte_value):
c_char_p_value = ctypes.cast(c_ubyte_value, ctypes.c_char_p)
try:
decode_str = c_char_p_value.value.decode('gbk') # Chinese characters
except UnicodeDecodeError:
decode_str = str(c_char_p_value.value)
return decode_str
# ch:枚举相机 | en:enum devices
def enum_devices():
global deviceList
global obj_cam_operation
deviceList = MV_CC_DEVICE_INFO_LIST()
n_layer_type = (MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE
| MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE)
ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList)
if ret != 0:
strError = "Enum devices fail! ret = :" + ToHexStr(ret)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
return ret
if deviceList.nDeviceNum == 0:
QMessageBox.warning(mainWindow, "Info", "Find no device", QMessageBox.Ok)
return ret
print("Find %d devices!" % deviceList.nDeviceNum)
devList = []
for i in range(0, deviceList.nDeviceNum):
mvcc_dev_info = cast(deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE or mvcc_dev_info.nTLayerType == MV_GENTL_GIGE_DEVICE:
print("\ngige device: [%d]" % i)
user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chUserDefinedName)
model_name = decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName)
print("device user define name: " + user_defined_name)
print("device model name: " + model_name)
nip1 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24)
nip2 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16)
nip3 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8)
nip4 = (mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff)
print("current ip: %d.%d.%d.%d " % (nip1, nip2, nip3, nip4))
devList.append(
"[" + str(i) + "]GigE: " + user_defined_name + " " + model_name + "(" + str(nip1) + "." + str(
nip2) + "." + str(nip3) + "." + str(nip4) + ")")
elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE:
print("\nu3v device: [%d]" % i)
user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stUsb3VInfo.chUserDefinedName)
model_name = decoding_char(mvcc_dev_info.SpecialInfo.stUsb3VInfo.chModelName)
print("device user define name: " + user_defined_name)
print("device model name: " + model_name)
strSerialNumber = ""
for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chSerialNumber:
if per == 0:
break
strSerialNumber = strSerialNumber + chr(per)
print("user serial number: " + strSerialNumber)
devList.append("[" + str(i) + "]USB: " + user_defined_name + " " + model_name
+ "(" + str(strSerialNumber) + ")")
elif mvcc_dev_info.nTLayerType == MV_GENTL_CAMERALINK_DEVICE:
print("\nCML device: [%d]" % i)
user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stCMLInfo.chUserDefinedName)
model_name = decoding_char(mvcc_dev_info.SpecialInfo.stCMLInfo.chModelName)
print("device user define name: " + user_defined_name)
print("device model name: " + model_name)
strSerialNumber = ""
for per in mvcc_dev_info.SpecialInfo.stCMLInfo.chSerialNumber:
if per == 0:
break
strSerialNumber = strSerialNumber + chr(per)
print("user serial number: " + strSerialNumber)
devList.append("[" + str(i) + "]CML: " + user_defined_name + " " + model_name
+ "(" + str(strSerialNumber) + ")")
elif mvcc_dev_info.nTLayerType == MV_GENTL_CXP_DEVICE:
print("\nCXP device: [%d]" % i)
user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stCXPInfo.chUserDefinedName)
model_name = decoding_char(mvcc_dev_info.SpecialInfo.stCXPInfo.chModelName)
print("device user define name: " + user_defined_name)
print("device model name: " + model_name)
strSerialNumber = ""
for per in mvcc_dev_info.SpecialInfo.stCXPInfo.chSerialNumber:
if per == 0:
break
strSerialNumber = strSerialNumber + chr(per)
print("user serial number: " + strSerialNumber)
devList.append("[" + str(i) + "]CXP: " + user_defined_name + " " + model_name
+ "(" + str(strSerialNumber) + ")")
elif mvcc_dev_info.nTLayerType == MV_GENTL_XOF_DEVICE:
print("\nXoF device: [%d]" % i)
user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stXoFInfo.chUserDefinedName)
model_name = decoding_char(mvcc_dev_info.SpecialInfo.stXoFInfo.chModelName)
print("device user define name: " + user_defined_name)
print("device model name: " + model_name)
strSerialNumber = ""
for per in mvcc_dev_info.SpecialInfo.stXoFInfo.chSerialNumber:
if per == 0:
break
strSerialNumber = strSerialNumber + chr(per)
print("user serial number: " + strSerialNumber)
devList.append("[" + str(i) + "]XoF: " + user_defined_name + " " + model_name
+ "(" + str(strSerialNumber) + ")")
ui.ComboDevices.clear()
ui.ComboDevices.addItems(devList)
ui.ComboDevices.setCurrentIndex(0)
# ch:打开相机 | en:open device
def open_device():
global deviceList
global nSelCamIndex
global obj_cam_operation
global isOpen
if isOpen:
QMessageBox.warning(mainWindow, "Error", 'Camera is Running!', QMessageBox.Ok)
return MV_E_CALLORDER
nSelCamIndex = ui.ComboDevices.currentIndex()
if nSelCamIndex < 0:
QMessageBox.warning(mainWindow, "Error", 'Please select a camera!', QMessageBox.Ok)
return MV_E_CALLORDER
obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex)
ret = obj_cam_operation.Open_device()
if 0 != ret:
strError = "Open device failed ret:" + ToHexStr(ret)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
isOpen = False
else:
set_continue_mode()
get_param()
isOpen = True
enable_controls()
# ch:开始取流 | en:Start grab image
def start_grabbing():
global obj_cam_operation
global isGrabbing
ret = obj_cam_operation.Start_grabbing(ui.widgetDisplay.winId())
if ret != 0:
strError = "Start grabbing failed ret:" + ToHexStr(ret)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
else:
isGrabbing = True
enable_controls()
# ch:停止取流 | en:Stop grab image
def stop_grabbing():
global obj_cam_operation
global isGrabbing
ret = obj_cam_operation.Stop_grabbing()
if ret != 0:
strError = "Stop grabbing failed ret:" + ToHexStr(ret)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
else:
isGrabbing = False
enable_controls()
# ch:关闭设备 | Close device
def close_device():
global isOpen
global isGrabbing
global obj_cam_operation
if isOpen:
obj_cam_operation.Close_device()
isOpen = False
isGrabbing = False
enable_controls()
# ch:设置触发模式 | en:set trigger mode
def set_continue_mode():
ret = obj_cam_operation.Set_trigger_mode(False)
if ret != 0:
strError = "Set continue mode failed ret:" + ToHexStr(ret) + " mode is " + str(is_trigger_mode)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
else:
ui.radioContinueMode.setChecked(True)
ui.radioTriggerMode.setChecked(False)
ui.bnSoftwareTrigger.setEnabled(False)
# ch:设置软触发模式 | en:set software trigger mode
def set_software_trigger_mode():
ret = obj_cam_operation.Set_trigger_mode(True)
if ret != 0:
strError = "Set trigger mode failed ret:" + ToHexStr(ret)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
else:
ui.radioContinueMode.setChecked(False)
ui.radioTriggerMode.setChecked(True)
ui.bnSoftwareTrigger.setEnabled(isGrabbing)
# ch:设置触发命令 | en:set trigger software
def trigger_once():
ret = obj_cam_operation.Trigger_once()
if ret != 0:
strError = "TriggerSoftware failed ret:" + ToHexStr(ret)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
# ch:存图 | en:save image
def save_bmp():
ret = obj_cam_operation.Save_Bmp()
if ret != MV_OK:
strError = "Save BMP failed ret:" + ToHexStr(ret)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
else:
print("Save image success")
def is_float(str):
try:
float(str)
return True
except ValueError:
return False
# ch: 获取参数 | en:get param
def get_param():
ret = obj_cam_operation.Get_parameter()
if ret != MV_OK:
strError = "Get param failed ret:" + ToHexStr(ret)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
else:
ui.edtExposureTime.setText("{0:.2f}".format(obj_cam_operation.exposure_time))
ui.edtGain.setText("{0:.2f}".format(obj_cam_operation.gain))
ui.edtFrameRate.setText("{0:.2f}".format(obj_cam_operation.frame_rate))
# ch: 设置参数 | en:set param
def set_param():
frame_rate = ui.edtFrameRate.text()
exposure = ui.edtExposureTime.text()
gain = ui.edtGain.text()
if is_float(frame_rate)!=True or is_float(exposure)!=True or is_float(gain)!=True:
strError = "Set param failed ret:" + ToHexStr(MV_E_PARAMETER)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
return MV_E_PARAMETER
ret = obj_cam_operation.Set_parameter(frame_rate, exposure, gain)
if ret != MV_OK:
strError = "Set param failed ret:" + ToHexStr(ret)
QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok)
return MV_OK
# ch: 设置控件状态 | en:set enable status
def enable_controls():
global isGrabbing
global isOpen
# 先设置group的状态再单独设置各控件状态
ui.groupGrab.setEnabled(isOpen)
ui.groupParam.setEnabled(isOpen)
ui.bnOpen.setEnabled(not isOpen)
ui.bnClose.setEnabled(isOpen)
ui.bnStart.setEnabled(isOpen and (not isGrabbing))
ui.bnStop.setEnabled(isOpen and isGrabbing)
ui.bnSoftwareTrigger.setEnabled(isGrabbing and ui.radioTriggerMode.isChecked())
ui.bnSaveImage.setEnabled(isOpen and isGrabbing)
# ch: 初始化app, 绑定控件与函数 | en: Init app, bind ui and api
app = QApplication(sys.argv)
mainWindow = QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(mainWindow)
ui.bnEnum.clicked.connect(enum_devices)
ui.bnOpen.clicked.connect(open_device)
ui.bnClose.clicked.connect(close_device)
ui.bnStart.clicked.connect(start_grabbing)
ui.bnStop.clicked.connect(stop_grabbing)
ui.bnSoftwareTrigger.clicked.connect(trigger_once)
ui.radioTriggerMode.clicked.connect(set_software_trigger_mode)
ui.radioContinueMode.clicked.connect(set_continue_mode)
ui.bnGetParam.clicked.connect(get_param)
ui.bnSetParam.clicked.connect(set_param)
ui.bnSaveImage.clicked.connect(save_bmp)
mainWindow.show()
app.exec_()
close_device()
# ch:反初始化SDK | en: finalize SDK
MvCamera.MV_CC_Finalize()
sys.exit()

View File

@ -0,0 +1,376 @@
# -- coding: utf-8 --
import threading
import time
import sys
import inspect
import ctypes
import random
from ctypes import *
sys.path.append("../MvImport")
from CameraParams_header import *
from MvCameraControl_class import *
# 强制关闭线程
def Async_raise(tid, exctype):
tid = ctypes.c_long(tid)
if not inspect.isclass(exctype):
exctype = type(exctype)
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
if res == 0:
raise ValueError("invalid thread id")
elif res != 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
raise SystemError("PyThreadState_SetAsyncExc failed")
# 停止线程
def Stop_thread(thread):
Async_raise(thread.ident, SystemExit)
# 转为16进制字符串
def To_hex_str(num):
chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'}
hexStr = ""
if num < 0:
num = num + 2 ** 32
while num >= 16:
digit = num % 16
hexStr = chaDic.get(digit, str(digit)) + hexStr
num //= 16
hexStr = chaDic.get(num, str(num)) + hexStr
return hexStr
# 是否是Mono图像
def Is_mono_data(enGvspPixelType):
if PixelType_Gvsp_Mono8 == enGvspPixelType or PixelType_Gvsp_Mono10 == enGvspPixelType \
or PixelType_Gvsp_Mono10_Packed == enGvspPixelType or PixelType_Gvsp_Mono12 == enGvspPixelType \
or PixelType_Gvsp_Mono12_Packed == enGvspPixelType:
return True
else:
return False
# 是否是彩色图像
def Is_color_data(enGvspPixelType):
if PixelType_Gvsp_BayerGR8 == enGvspPixelType or PixelType_Gvsp_BayerRG8 == enGvspPixelType \
or PixelType_Gvsp_BayerGB8 == enGvspPixelType or PixelType_Gvsp_BayerBG8 == enGvspPixelType \
or PixelType_Gvsp_BayerGR10 == enGvspPixelType or PixelType_Gvsp_BayerRG10 == enGvspPixelType \
or PixelType_Gvsp_BayerGB10 == enGvspPixelType or PixelType_Gvsp_BayerBG10 == enGvspPixelType \
or PixelType_Gvsp_BayerGR12 == enGvspPixelType or PixelType_Gvsp_BayerRG12 == enGvspPixelType \
or PixelType_Gvsp_BayerGB12 == enGvspPixelType or PixelType_Gvsp_BayerBG12 == enGvspPixelType \
or PixelType_Gvsp_BayerGR10_Packed == enGvspPixelType or PixelType_Gvsp_BayerRG10_Packed == enGvspPixelType \
or PixelType_Gvsp_BayerGB10_Packed == enGvspPixelType or PixelType_Gvsp_BayerBG10_Packed == enGvspPixelType \
or PixelType_Gvsp_BayerGR12_Packed == enGvspPixelType or PixelType_Gvsp_BayerRG12_Packed == enGvspPixelType \
or PixelType_Gvsp_BayerGB12_Packed == enGvspPixelType or PixelType_Gvsp_BayerBG12_Packed == enGvspPixelType \
or PixelType_Gvsp_YUV422_Packed == enGvspPixelType or PixelType_Gvsp_YUV422_YUYV_Packed == enGvspPixelType:
return True
else:
return False
# 相机操作类
class CameraOperation:
def __init__(self, obj_cam, st_device_list, n_connect_num=0, b_open_device=False, b_start_grabbing=False,
h_thread_handle=None,
b_thread_closed=False, st_frame_info=None, b_exit=False, b_save_bmp=False, b_save_jpg=False,
buf_save_image=None,
n_save_image_size=0, n_win_gui_id=0, frame_rate=0, exposure_time=0, gain=0):
self.obj_cam = obj_cam
self.st_device_list = st_device_list
self.n_connect_num = n_connect_num
self.b_open_device = b_open_device
self.b_start_grabbing = b_start_grabbing
self.b_thread_closed = b_thread_closed
self.st_frame_info = st_frame_info
self.b_exit = b_exit
self.b_save_bmp = b_save_bmp
self.b_save_jpg = b_save_jpg
self.buf_save_image = buf_save_image
self.n_save_image_size = n_save_image_size
self.h_thread_handle = h_thread_handle
self.b_thread_closed
self.frame_rate = frame_rate
self.exposure_time = exposure_time
self.gain = gain
self.buf_lock = threading.Lock() # 取图和存图的buffer锁
# 打开相机
def Open_device(self):
if not self.b_open_device:
if self.n_connect_num < 0:
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)],
POINTER(MV_CC_DEVICE_INFO)).contents
self.obj_cam = MvCamera()
ret = self.obj_cam.MV_CC_CreateHandle(stDeviceList)
if ret != 0:
self.obj_cam.MV_CC_DestroyHandle()
return ret
ret = self.obj_cam.MV_CC_OpenDevice()
if ret != 0:
return ret
print("open device successfully!")
self.b_open_device = True
self.b_thread_closed = False
# ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
if stDeviceList.nTLayerType == MV_GIGE_DEVICE or stDeviceList.nTLayerType == MV_GENTL_GIGE_DEVICE:
nPacketSize = self.obj_cam.MV_CC_GetOptimalPacketSize()
if int(nPacketSize) > 0:
ret = self.obj_cam.MV_CC_SetIntValue("GevSCPSPacketSize", nPacketSize)
if ret != 0:
print("warning: set packet size fail! ret[0x%x]" % ret)
else:
print("warning: set packet size fail! ret[0x%x]" % nPacketSize)
stBool = c_bool(False)
ret = self.obj_cam.MV_CC_GetBoolValue("AcquisitionFrameRateEnable", stBool)
if ret != 0:
print("get acquisition frame rate enable fail! ret[0x%x]" % ret)
# ch:设置触发模式为off | en:Set trigger mode as off
ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", MV_TRIGGER_MODE_OFF)
if ret != 0:
print("set trigger mode fail! ret[0x%x]" % ret)
return MV_OK
# 开始取图
def Start_grabbing(self, winHandle):
if not self.b_start_grabbing and self.b_open_device:
self.b_exit = False
ret = self.obj_cam.MV_CC_StartGrabbing()
if ret != 0:
return ret
self.b_start_grabbing = True
print("start grabbing successfully!")
try:
thread_id = random.randint(1, 10000)
self.h_thread_handle = threading.Thread(target=CameraOperation.Work_thread, args=(self, winHandle))
self.h_thread_handle.start()
self.b_thread_closed = True
finally:
pass
return MV_OK
return MV_E_CALLORDER
# 停止取图
def Stop_grabbing(self):
if self.b_start_grabbing and self.b_open_device:
# 退出线程
if self.b_thread_closed:
Stop_thread(self.h_thread_handle)
self.b_thread_closed = False
ret = self.obj_cam.MV_CC_StopGrabbing()
if ret != 0:
return ret
print("stop grabbing successfully!")
self.b_start_grabbing = False
self.b_exit = True
return MV_OK
else:
return MV_E_CALLORDER
# 关闭相机
def Close_device(self):
if self.b_open_device:
# 退出线程
if self.b_thread_closed:
Stop_thread(self.h_thread_handle)
self.b_thread_closed = False
ret = self.obj_cam.MV_CC_CloseDevice()
if ret != 0:
return ret
# ch:销毁句柄 | Destroy handle
self.obj_cam.MV_CC_DestroyHandle()
self.b_open_device = False
self.b_start_grabbing = False
self.b_exit = True
print("close device successfully!")
return MV_OK
# 设置触发模式
def Set_trigger_mode(self, is_trigger_mode):
if not self.b_open_device:
return MV_E_CALLORDER
if not is_trigger_mode:
ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", 0)
if ret != 0:
return ret
else:
ret = self.obj_cam.MV_CC_SetEnumValue("TriggerMode", 1)
if ret != 0:
return ret
ret = self.obj_cam.MV_CC_SetEnumValue("TriggerSource", 7)
if ret != 0:
return ret
return MV_OK
# 软触发一次
def Trigger_once(self):
if self.b_open_device:
return self.obj_cam.MV_CC_SetCommandValue("TriggerSoftware")
# 获取参数
def Get_parameter(self):
if self.b_open_device:
stFloatParam_FrameRate = MVCC_FLOATVALUE()
memset(byref(stFloatParam_FrameRate), 0, sizeof(MVCC_FLOATVALUE))
stFloatParam_exposureTime = MVCC_FLOATVALUE()
memset(byref(stFloatParam_exposureTime), 0, sizeof(MVCC_FLOATVALUE))
stFloatParam_gain = MVCC_FLOATVALUE()
memset(byref(stFloatParam_gain), 0, sizeof(MVCC_FLOATVALUE))
ret = self.obj_cam.MV_CC_GetFloatValue("AcquisitionFrameRate", stFloatParam_FrameRate)
if ret != 0:
return ret
self.frame_rate = stFloatParam_FrameRate.fCurValue
ret = self.obj_cam.MV_CC_GetFloatValue("ExposureTime", stFloatParam_exposureTime)
if ret != 0:
return ret
self.exposure_time = stFloatParam_exposureTime.fCurValue
ret = self.obj_cam.MV_CC_GetFloatValue("Gain", stFloatParam_gain)
if ret != 0:
return ret
self.gain = stFloatParam_gain.fCurValue
return MV_OK
# 设置参数
def Set_parameter(self, frameRate, exposureTime, gain):
if '' == frameRate or '' == exposureTime or '' == gain:
print('show info', 'please type in the text box !')
return MV_E_PARAMETER
if self.b_open_device:
ret = self.obj_cam.MV_CC_SetEnumValue("ExposureAuto", 0)
time.sleep(0.2)
ret = self.obj_cam.MV_CC_SetFloatValue("ExposureTime", float(exposureTime))
if ret != 0:
print('show error', 'set exposure time fail! ret = ' + To_hex_str(ret))
return ret
ret = self.obj_cam.MV_CC_SetFloatValue("Gain", float(gain))
if ret != 0:
print('show error', 'set gain fail! ret = ' + To_hex_str(ret))
return ret
ret = self.obj_cam.MV_CC_SetFloatValue("AcquisitionFrameRate", float(frameRate))
if ret != 0:
print('show error', 'set acquistion frame rate fail! ret = ' + To_hex_str(ret))
return ret
print('show info', 'set parameter success!')
return MV_OK
# 取图线程函数
def Work_thread(self, winHandle):
stOutFrame = MV_FRAME_OUT()
memset(byref(stOutFrame), 0, sizeof(stOutFrame))
while True:
ret = self.obj_cam.MV_CC_GetImageBuffer(stOutFrame, 1000)
if 0 == ret:
# 拷贝图像和图像信息
if self.buf_save_image is None:
self.buf_save_image = (c_ubyte * stOutFrame.stFrameInfo.nFrameLen)()
self.st_frame_info = stOutFrame.stFrameInfo
# 获取缓存锁
self.buf_lock.acquire()
cdll.msvcrt.memcpy(byref(self.buf_save_image), stOutFrame.pBufAddr, self.st_frame_info.nFrameLen)
self.buf_lock.release()
print("get one frame: Width[%d], Height[%d], nFrameNum[%d]"
% (self.st_frame_info.nWidth, self.st_frame_info.nHeight, self.st_frame_info.nFrameNum))
# 释放缓存
self.obj_cam.MV_CC_FreeImageBuffer(stOutFrame)
else:
print("no data, ret = " + To_hex_str(ret))
continue
# 使用Display接口显示图像
stDisplayParam = MV_DISPLAY_FRAME_INFO()
memset(byref(stDisplayParam), 0, sizeof(stDisplayParam))
stDisplayParam.hWnd = int(winHandle)
stDisplayParam.nWidth = self.st_frame_info.nWidth
stDisplayParam.nHeight = self.st_frame_info.nHeight
stDisplayParam.enPixelType = self.st_frame_info.enPixelType
stDisplayParam.pData = self.buf_save_image
stDisplayParam.nDataLen = self.st_frame_info.nFrameLen
self.obj_cam.MV_CC_DisplayOneFrame(stDisplayParam)
# 是否退出
if self.b_exit:
if self.buf_save_image is not None:
del self.buf_save_image
break
# 存jpg图像
def Save_jpg(self):
if self.buf_save_image is None:
return
# 获取缓存锁
self.buf_lock.acquire()
file_path = str(self.st_frame_info.nFrameNum) + ".jpg"
c_file_path = file_path.encode('ascii')
stSaveParam = MV_SAVE_IMAGE_TO_FILE_PARAM_EX()
stSaveParam.enPixelType = self.st_frame_info.enPixelType # ch:相机对应的像素格式 | en:Camera pixel type
stSaveParam.nWidth = self.st_frame_info.nWidth # ch:相机对应的宽 | en:Width
stSaveParam.nHeight = self.st_frame_info.nHeight # ch:相机对应的高 | en:Height
stSaveParam.nDataLen = self.st_frame_info.nFrameLen
stSaveParam.pData = cast(self.buf_save_image, POINTER(c_ubyte))
stSaveParam.enImageType = MV_Image_Jpeg # ch:需要保存的图像类型 | en:Image format to save
stSaveParam.nQuality = 80
stSaveParam.pcImagePath = ctypes.create_string_buffer(c_file_path)
stSaveParam.iMethodValue = 1
ret = self.obj_cam.MV_CC_SaveImageToFileEx(stSaveParam)
self.buf_lock.release()
return ret
# 存BMP图像
def Save_Bmp(self):
if 0 == self.buf_save_image:
return
# 获取缓存锁
self.buf_lock.acquire()
file_path = str(self.st_frame_info.nFrameNum) + ".bmp"
c_file_path = file_path.encode('ascii')
stSaveParam = MV_SAVE_IMAGE_TO_FILE_PARAM_EX()
stSaveParam.enPixelType = self.st_frame_info.enPixelType # ch:相机对应的像素格式 | en:Camera pixel type
stSaveParam.nWidth = self.st_frame_info.nWidth # ch:相机对应的宽 | en:Width
stSaveParam.nHeight = self.st_frame_info.nHeight # ch:相机对应的高 | en:Height
stSaveParam.nDataLen = self.st_frame_info.nFrameLen
stSaveParam.pData = cast(self.buf_save_image, POINTER(c_ubyte))
stSaveParam.enImageType = MV_Image_Bmp # ch:需要保存的图像类型 | en:Image format to save
stSaveParam.pcImagePath = ctypes.create_string_buffer(c_file_path)
stSaveParam.iMethodValue = 1
ret = self.obj_cam.MV_CC_SaveImageToFileEx(stSaveParam)
self.buf_lock.release()
return ret

View File

@ -0,0 +1,82 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# \~chinese 设备类型定义 \~english Device Type Definition
MV_UNKNOW_DEVICE = 0x00000000 # < \~chinese 未知设备类型,保留意义 \~english Unknown Device Type, Reserved
MV_GIGE_DEVICE = 0x00000001 # < \~chinese GigE设备 \~english GigE Device
MV_1394_DEVICE = 0x00000002 # < \~chinese 1394-a/b 设备 \~english 1394-a/b Device
MV_USB_DEVICE = 0x00000004 # < \~chinese USB 设备 \~english USB Device
MV_CAMERALINK_DEVICE = 0x00000008 # < \~chinese CameraLink设备 \~english CameraLink Device
MV_VIR_GIGE_DEVICE = 0x00000010 # < \~chinese 虚拟GigE设备 \~english Virtual GigE Device
MV_VIR_USB_DEVICE = 0x00000020 # < \~chinese 虚拟USB设备 \~english Virtual USB Device
MV_GENTL_GIGE_DEVICE = 0x00000040 # < \~chinese 自研网卡下GigE设备 \~english GenTL GigE Device
MV_GENTL_CAMERALINK_DEVICE = 0x00000080 # < \~chinese CameraLink设备 \~english GenTL CameraLink Device
MV_GENTL_CXP_DEVICE = 0x00000100 # < \~chinese CoaXPress设备 \~english GenTL CoaXPress Device
MV_GENTL_XOF_DEVICE = 0x00000200 # < \~chinese XoF设备 \~english GenTL XoF Device
# \~chinese 采集卡类型 \~english Interface type
MV_GIGE_INTERFACE = 0x00000001 # < \~chinese GigE Vision采集卡 \~english GigE Vision interface
MV_CAMERALINK_INTERFACE = 0x00000004 # < \~chinese Camera Link采集卡 \~english Camera Link interface
MV_CXP_INTERFACE = 0x00000008 # < \~chinese CoaXPress采集卡 \~english CoaXPress interface
MV_XOF_INTERFACE = 0x00000010 # < \~chinese XoFLink采集卡 \~english XoFLink interface
INFO_MAX_BUFFER_SIZE = 64 # < \~chinese 最大的数据信息大小 \~english Maximum data information size
MV_MAX_TLS_NUM = 8 # < \~chinese 最多支持的传输层实例个数 \~english The maximum number of supported transport layer instances
MV_MAX_DEVICE_NUM = 256 # < \~chinese 最大支持的设备个数 \~english The maximum number of supported devices
MV_MAX_INTERFACE_NUM = 64 #< \~chinese 最大支持的采集卡数量 \~english The maximum number of Frame Grabber interface supported
MV_MAX_SERIAL_PORT_NUM = 64 # \~chinese 最大支持的串口数量 \~english The maximum number of serial port supported
MV_MAX_GENTL_IF_NUM = 256 # < \~chinese 最大支持的GenTL数量 \~english The maximum number of GenTL supported
MV_MAX_GENTL_DEV_NUM = 256 # < \~chinese 最大支持的GenTL设备数量 \~english The maximum number of GenTL devices supported
# \~chinese 设备的访问模式 \~english Device Access Mode
# \~chinese 独占权限其他APP只允许读CCP寄存器 \~english Exclusive authority, other APP is only allowed to read the CCP register
MV_ACCESS_Exclusive = 1
# \~chinese 可以从5模式下抢占权限然后以独占权限打开 \~english You can seize the authority from the 5 mode, and then open with exclusive authority
MV_ACCESS_ExclusiveWithSwitch = 2
# \~chinese 控制权限其他APP允许读所有寄存器 \~english Control authority, allows other APP reading all registers
MV_ACCESS_Control = 3
# \~chinese 可以从5的模式下抢占权限然后以控制权限打开 \~english You can seize the authority from the 5 mode, and then open with control authority
MV_ACCESS_ControlWithSwitch = 4
# \~chinese 以可被抢占的控制权限打开 \~english Open with seized control authority
MV_ACCESS_ControlSwitchEnable = 5
# \~chinese 可以从5的模式下抢占权限然后以可被抢占的控制权限打开 \~english You can seize the authority from the 5 mode, and then open with seized control authority
MV_ACCESS_ControlSwitchEnableWithKey = 6
# \~chinese 读模式打开设备,适用于控制权限下 \~english Open with read mode and is available under control authority
MV_ACCESS_Monitor = 7
MV_MATCH_TYPE_NET_DETECT = 0x00000001 # < \~chinese 网络流量和丢包信息 \~english Network traffic and packet loss information
MV_MATCH_TYPE_USB_DETECT = 0x00000002 # < \~chinese host接收到来自U3V设备的字节总数 \~english The total number of bytes host received from U3V device
# \~chinese GigEVision IP配置 \~english GigEVision IP Configuration
MV_IP_CFG_STATIC = 0x05000000 # < \~chinese 静态 \~english Static
MV_IP_CFG_DHCP = 0x06000000 # < \~chinese DHCP \~english DHCP
MV_IP_CFG_LLA = 0x04000000 # < \~chinese LLA \~english LLA
# \~chinese GigEVision网络传输模式 \~english GigEVision Net Transfer Mode
MV_NET_TRANS_DRIVER = 0x00000001 # < \~chinese 驱动 \~english Driver
MV_NET_TRANS_SOCKET = 0x00000002 # < \~chinese Socket \~english Socket
# \~chinese CameraLink波特率 \~english CameraLink Baud Rates (CLUINT32)
MV_CAML_BAUDRATE_9600 = 0x00000001 # < \~chinese 9600 \~english 9600
MV_CAML_BAUDRATE_19200 = 0x00000002 # < \~chinese 19200 \~english 19200
MV_CAML_BAUDRATE_38400 = 0x00000004 # < \~chinese 38400 \~english 38400
MV_CAML_BAUDRATE_57600 = 0x00000008 # < \~chinese 57600 \~english 57600
MV_CAML_BAUDRATE_115200 = 0x00000010 # < \~chinese 115200 \~english 115200
MV_CAML_BAUDRATE_230400 = 0x00000020 # < \~chinese 230400 \~english 230400
MV_CAML_BAUDRATE_460800 = 0x00000040 # < \~chinese 460800 \~english 460800
MV_CAML_BAUDRATE_921600 = 0x00000080 # < \~chinese 921600 \~english 921600
MV_CAML_BAUDRATE_AUTOMAX = 0x40000000 # < \~chinese 最大值 \~english Auto Max
# \~chinese 异常消息类型 \~english Exception message type
MV_EXCEPTION_DEV_DISCONNECT = 0x00008001 # < \~chinese 设备断开连接 \~english The device is disconnected
MV_EXCEPTION_VERSION_CHECK = 0x00008002 # < \~chinese SDK与驱动版本不匹配 \~english SDK does not match the driver version
MAX_EVENT_NAME_SIZE = 128 # < \~chinese 设备Event事件名称最大长度 \~english Max length of event name
MV_MAX_XML_SYMBOLIC_NUM = 64 # \~chinese 最大XML符号数 \~english Max XML Symbolic Number
MV_MAX_SYMBOLIC_LEN = 64 # \~chinese 最大枚举条目对应的符号长度 \~english Max Enum Entry Symbolic Number
MV_MAX_SPLIT_NUM = 8 # \~chinese 分时曝光时最多将源图像拆分的个数 \~english The maximum number of source image to be split in time-division exposure

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
MV_OK = 0x00000000 # < \~chinese 成功,无错误 \~english Successed, no error
# 通用错误码定义:范围0x80000000-0x800000FF
MV_E_HANDLE = 0x80000000 # < \~chinese 错误或无效的句柄 \~english Error or invalid handle
MV_E_SUPPORT = 0x80000001 # < \~chinese 不支持的功能 \~english Not supported function
MV_E_BUFOVER = 0x80000002 # < \~chinese 缓存已满 \~english Buffer overflow
MV_E_CALLORDER = 0x80000003 # < \~chinese 函数调用顺序错误 \~english Function calling order error
MV_E_PARAMETER = 0x80000004 # < \~chinese 错误的参数 \~english Incorrect parameter
MV_E_RESOURCE = 0x80000006 # < \~chinese 资源申请失败 \~english Applying resource failed
MV_E_NODATA = 0x80000007 # < \~chinese 无数据 \~english No data
MV_E_PRECONDITION = 0x80000008 # < \~chinese 前置条件有误,或运行环境已发生变化 \~english Precondition error, or running environment changed
MV_E_VERSION = 0x80000009 # < \~chinese 版本不匹配 \~english Version mismatches
MV_E_NOENOUGH_BUF = 0x8000000A # < \~chinese 传入的内存空间不足 \~english Insufficient memory
MV_E_ABNORMAL_IMAGE = 0x8000000B # < \~chinese 异常图像,可能是丢包导致图像不完整 \~english Abnormal image, maybe incomplete image because of lost packet
MV_E_LOAD_LIBRARY = 0x8000000C # < \~chinese 动态导入DLL失败 \~english Load library failed
MV_E_NOOUTBUF = 0x8000000D # < \~chinese 没有可输出的缓存 \~english No Avaliable Buffer
MV_E_ENCRYPT = 0x8000000E # < \~chinese 加密错误 \~english Encryption error
MV_E_OPENFILE = 0x8000000F # < \~chinese 打开文件出现错误 \~english open file error
MV_E_BUF_IN_USE = 0x80000010 # < \~chinese 缓存地址已使用 \~english Buffer already in use
MV_E_BUF_INVALID = 0x80000011 # < \~chinese 无效的缓存地址 \~english Buffer address invalid
MV_E_NOALIGN_BUF = 0x80000012 # < \~chinese 缓存对齐异常 \~english Buffer alignmenterror error
MV_E_NOENOUGH_BUF_NUM = 0x80000013 # < \~chinese 缓存个数不足 \~english Insufficient cache count
MV_E_PORT_IN_USE = 0x80000014 # < \~chinese 串口被占用 \~english Port is in use
MV_E_IMAGE_DECODEC = 0x80000015 # < \~chinese 解码错误(SDK校验图像异常)\~english Decoding error (SDK verification image exception)
MV_E_UNKNOW = 0x800000FF # < \~chinese 未知的错误 \~english Unknown error
# GenICam系列错误:范围0x80000100-0x800001FF
MV_E_GC_GENERIC = 0x80000100 # < \~chinese 通用错误 \~english General error
MV_E_GC_ARGUMENT = 0x80000101 # < \~chinese 参数非法 \~english Illegal parameters
MV_E_GC_RANGE = 0x80000102 # < \~chinese 值超出范围 \~english The value is out of range
MV_E_GC_PROPERTY = 0x80000103 # < \~chinese 属性 \~english Property
MV_E_GC_RUNTIME = 0x80000104 # < \~chinese 运行环境有问题 \~english Running environment error
MV_E_GC_LOGICAL = 0x80000105 # < \~chinese 逻辑错误 \~english Logical error
MV_E_GC_ACCESS = 0x80000106 # < \~chinese 节点访问条件有误 \~english Node accessing condition error
MV_E_GC_TIMEOUT = 0x80000107 # < \~chinese 超时 \~english Timeout
MV_E_GC_DYNAMICCAST = 0x80000108 # < \~chinese 转换异常 \~english Transformation exception
MV_E_GC_UNKNOW = 0x800001FF # < \~chinese GenICam未知错误 \~english GenICam unknown error
# GigE_STATUS对应的错误码:范围0x80000200-0x800002FF
MV_E_NOT_IMPLEMENTED = 0x80000200 # < \~chinese 命令不被设备支持 \~english The command is not supported by device
MV_E_INVALID_ADDRESS = 0x80000201 # < \~chinese 访问的目标地址不存在 \~english The target address being accessed does not exist
MV_E_WRITE_PROTECT = 0x80000202 # < \~chinese 目标地址不可写 \~english The target address is not writable
MV_E_ACCESS_DENIED = 0x80000203 # < \~chinese 设备无访问权限 \~english No permission
MV_E_BUSY = 0x80000204 # < \~chinese 设备忙,或网络断开 \~english Device is busy, or network disconnected
MV_E_PACKET = 0x80000205 # < \~chinese 网络包数据错误 \~english Network data packet error
MV_E_NETER = 0x80000206 # < \~chinese 网络相关错误 \~english Network error
MV_E_KEY_VERIFICATION = 0x8000020F # < \~chinese 秘钥校验错误 \~english SwitchKey error
MV_E_IP_CONFLICT = 0x80000221 # < \~chinese 设备IP冲突 \~english Device IP conflict
# USB_STATUS对应的错误码:范围0x80000300-0x800003FF
MV_E_USB_READ = 0x80000300 # < \~chinese 读usb出错 \~english Reading USB error
MV_E_USB_WRITE = 0x80000301 # < \~chinese 写usb出错 \~english Writing USB error
MV_E_USB_DEVICE = 0x80000302 # < \~chinese 设备异常 \~english Device exception
MV_E_USB_GENICAM = 0x80000303 # < \~chinese GenICam相关错误 \~english GenICam error
MV_E_USB_BANDWIDTH = 0x80000304 # < \~chinese 带宽不足 该错误码新增 \~english Insufficient bandwidth, this error code is newly added
MV_E_USB_DRIVER = 0x80000305 # < \~chinese 驱动不匹配或者未装驱动 \~english Driver mismatch or unmounted drive
MV_E_USB_UNKNOW = 0x800003FF # < \~chinese USB未知的错误 \~english USB unknown error
# 升级时对应的错误码:范围0x80000400-0x800004FF
MV_E_UPG_FILE_MISMATCH = 0x80000400 # < \~chinese 升级固件不匹配 \~english Firmware mismatches
MV_E_UPG_LANGUSGE_MISMATCH = 0x80000401 # < \~chinese 升级固件语言不匹配 \~english Firmware language mismatches
MV_E_UPG_CONFLICT = 0x80000402 # < \~chinese 升级冲突(设备已经在升级了再次请求升级即返回此错误) \~english Upgrading conflicted (repeated upgrading requests during device upgrade)
MV_E_UPG_INNER_ERR = 0x80000403 # < \~chinese 升级时设备内部出现错误 \~english Camera internal error during upgrade
MV_E_UPG_UNKNOW = 0x800004FF # < \~chinese 升级时未知错误 \~english Unknown error during upgrade

258
camera/PixelType_header.py Normal file
View File

@ -0,0 +1,258 @@
# -*- coding: utf-8 -*-
from ctypes import *
PixelType_Gvsp_BGR12_Packed = 36700187
PixelType_Gvsp_COORD3D_DEPTH_PLUS_MASK = -2112094207
PixelType_Gvsp_RGB10V1_Packed = 35651612
PixelType_Gvsp_BayerRG12_Packed = 17563691
PixelType_Gvsp_RGB12_Planar = 36700195
PixelType_Gvsp_YUV411_Packed = 34340894
PixelType_Gvsp_YUV444_Packed = 35127328
PixelType_Gvsp_BGR10_Packed = 36700185
PixelType_Gvsp_YCBCR422_8 = 34603067
PixelType_Gvsp_YCBCR709_422_8 = 34603073
PixelType_Gvsp_RGB12_Packed = 36700186
PixelType_Gvsp_Coord3D_ABC32f_Planar = 39846081
PixelType_Gvsp_Mono8_Signed = 17301506
PixelType_Gvsp_YUV422_YUYV_Packed = 34603058
PixelType_Gvsp_Mono4p = 17039417
PixelType_Gvsp_BayerGR12_Packed = 17563690
PixelType_Gvsp_Coord3D_ABC32 = -2107625471
PixelType_Gvsp_Coord3D_AB32f = -2109722622
PixelType_Gvsp_YCBCR709_411_8_CBYYCRYY = 34340930
PixelType_Gvsp_YCBCR709_422_8_CBYCRY = 34603077
PixelType_Gvsp_Coord3D_AB32 = -2109722621
PixelType_Gvsp_YCBCR601_422_8_CBYCRY = 34603076
PixelType_Gvsp_YCBCR709_8_CBYCR = 35127360
PixelType_Gvsp_Coord3D_AC32f = 36176066
PixelType_Gvsp_YCBCR8_CBYCR = 35127354
PixelType_Gvsp_YCBCR411_8_CBYYCRYY = 34340924
PixelType_Gvsp_Coord3D_ABC32f = 39846080
PixelType_Gvsp_YUV422_Packed = 34603039
PixelType_Gvsp_Coord3D_ABC16 = 36700345
PixelType_Gvsp_RGB10_Planar = 36700194
PixelType_Gvsp_RGB8_Planar = 35127329
PixelType_Gvsp_RGB10_Packed = 36700184
PixelType_Gvsp_Coord3D_C32 = -2128596986
PixelType_Gvsp_RGB16_Packed = 36700211
PixelType_Gvsp_YCBCR422_8_CBYCRY = 34603075
PixelType_Gvsp_YCBCR601_411_8_CBYYCRYY = 34340927
PixelType_Gvsp_BGRA8_Packed = 35651607
PixelType_Gvsp_Jpeg = -2145910783
PixelType_Gvsp_YCBCR601_422_8 = 34603070
PixelType_Gvsp_Coord3D_C32f = 18874559
PixelType_Gvsp_BayerGB12_Packed = 17563692
PixelType_Gvsp_BayerRG16 = 17825839
PixelType_Gvsp_BayerGB10_Packed = 17563688
PixelType_Gvsp_RGB12V1_Packed = 35913780
PixelType_Gvsp_RGB16_Planar = 36700196
PixelType_Gvsp_BGR565_Packed = 34603062
PixelType_Gvsp_Mono16 = 17825799
PixelType_Gvsp_BayerBG10 = 17825807
PixelType_Gvsp_Coord3D_AC32f_64 = 37748930
PixelType_Gvsp_RGBA8_Packed = 35651606
PixelType_Gvsp_Mono12 = 17825797
PixelType_Gvsp_Coord3D_A32f = 18874557
PixelType_Gvsp_YCBCR601_8_CBYCR = 35127357
PixelType_Gvsp_BayerGB16 = 17825840
PixelType_Gvsp_Coord3D_AC32 = -2109722620
PixelType_Gvsp_BayerBG16 = 17825841
PixelType_Gvsp_Coord3D_AC32f_Planar = 37748931
PixelType_Gvsp_BayerBG10_Packed = 17563689
PixelType_Gvsp_RGB8_Packed = 35127316
PixelType_Gvsp_BGR8_Packed = 35127317
PixelType_Gvsp_BayerGR16 = 17825838
PixelType_Gvsp_BayerGR10_Packed = 17563686
PixelType_Gvsp_Mono8 = 17301505
PixelType_Gvsp_Mono14 = 17825829
PixelType_Gvsp_BayerGB10 = 17825806
PixelType_Gvsp_Undefined = -1
PixelType_Gvsp_BayerRG8 = 17301513
PixelType_Gvsp_BayerGB12 = 17825810
PixelType_Gvsp_Mono12_Packed = 17563654
PixelType_Gvsp_BayerBG8 = 17301515
PixelType_Gvsp_BayerBG12_Packed = 17563693
PixelType_Gvsp_Mono1p = 16842807
PixelType_Gvsp_Mono2p = 16908344
PixelType_Gvsp_RGB565_Packed = 34603061
PixelType_Gvsp_RGB10V2_Packed = 35651613
PixelType_Gvsp_BayerRG12 = 17825809
PixelType_Gvsp_Mono10_Packed = 17563652
PixelType_Gvsp_BayerGR8 = 17301512
PixelType_Gvsp_BayerGB8 = 17301514
PixelType_Gvsp_BayerGR12 = 17825808
PixelType_Gvsp_BayerGR10 = 17825804
PixelType_Gvsp_BayerRG10 = 17825805
PixelType_Gvsp_BayerBG12 = 17825811
PixelType_Gvsp_Coord3D_A32 = -2128596987
PixelType_Gvsp_Mono10 = 17825795
PixelType_Gvsp_BayerRG10_Packed = 17563687
PixelType_Gvsp_YUV420SP_NV12 = 34373633
PixelType_Gvsp_YUV420SP_NV21 = 34373634
PixelType_Gvsp_Coord3D_C16 = 17825976
PixelType_Gvsp_BayerRBGG8 = 17301574
PixelType_Gvsp_HB_Mono8 = -2130182143
PixelType_Gvsp_HB_Mono10 = -2129657853
PixelType_Gvsp_HB_Mono10_Packed = -2129919996
PixelType_Gvsp_HB_Mono12 = -2129657851
PixelType_Gvsp_HB_Mono12_Packed = -2129919994
PixelType_Gvsp_HB_Mono16 = -2129657849
PixelType_Gvsp_HB_BayerGR8 = -2130182136
PixelType_Gvsp_HB_BayerRG8 = -2130182135
PixelType_Gvsp_HB_BayerGB8 = -2130182134
PixelType_Gvsp_HB_BayerBG8 = -2130182133
PixelType_Gvsp_HB_BayerRBGG8 = -2130182074
PixelType_Gvsp_HB_BayerGR10 = -2129657844
PixelType_Gvsp_HB_BayerRG10 = -2129657843
PixelType_Gvsp_HB_BayerGB10 = -2129657842
PixelType_Gvsp_HB_BayerBG10 = -2129657841
PixelType_Gvsp_HB_BayerGR12 = -2129657840
PixelType_Gvsp_HB_BayerRG12 = -2129657839
PixelType_Gvsp_HB_BayerGB12 = -2129657838
PixelType_Gvsp_HB_BayerBG12 = -2129657837
PixelType_Gvsp_HB_BayerGR10_Packed = -2129919962
PixelType_Gvsp_HB_BayerRG10_Packed = -2129919961
PixelType_Gvsp_HB_BayerGB10_Packed = -2129919960
PixelType_Gvsp_HB_BayerBG10_Packed = -2129919959
PixelType_Gvsp_HB_BayerGR12_Packed = -2129919958
PixelType_Gvsp_HB_BayerRG12_Packed = -2129919957
PixelType_Gvsp_HB_BayerGB12_Packed = -2129919956
PixelType_Gvsp_HB_BayerBG12_Packed = -2129919955
PixelType_Gvsp_HB_YUV422_Packed = -2112880609
PixelType_Gvsp_HB_YUV422_YUYV_Packed = -2112880590
PixelType_Gvsp_HB_RGB8_Packed = -2112356332
PixelType_Gvsp_HB_BGR8_Packed = -2112356331
PixelType_Gvsp_HB_RGBA8_Packed = -2111832042
PixelType_Gvsp_HB_BGRA8_Packed = -2111832041
PixelType_Gvsp_HB_RGB16_Packed = -2110783437
PixelType_Gvsp_HB_BGR16_Packed = -2110783413
PixelType_Gvsp_HB_RGBA16_Packed = -2109734812
PixelType_Gvsp_HB_BGRA16_Packed = -2109734831
__all__ = ['PixelType_Gvsp_BayerRG8',
'PixelType_Gvsp_YCBCR422_8',
'PixelType_Gvsp_Coord3D_ABC32',
'PixelType_Gvsp_Coord3D_AB32f',
'PixelType_Gvsp_COORD3D_DEPTH_PLUS_MASK',
'PixelType_Gvsp_RGB10_Packed',
'PixelType_Gvsp_RGB10V1_Packed',
'PixelType_Gvsp_RGB8_Planar',
'PixelType_Gvsp_RGBA8_Packed',
'PixelType_Gvsp_RGB8_Packed',
'PixelType_Gvsp_BayerBG12',
'PixelType_Gvsp_Coord3D_AC32f_Planar',
'PixelType_Gvsp_BayerBG10_Packed',
'PixelType_Gvsp_YCBCR709_422_8_CBYCRY',
'PixelType_Gvsp_Coord3D_A32f',
'PixelType_Gvsp_YUV411_Packed',
'PixelType_Gvsp_BayerBG12_Packed',
'PixelType_Gvsp_RGB16_Packed',
'PixelType_Gvsp_BayerRG12',
'PixelType_Gvsp_BayerRG10',
'PixelType_Gvsp_BayerRG16',
'PixelType_Gvsp_YCBCR709_411_8_CBYYCRYY',
'PixelType_Gvsp_BayerGB12_Packed',
'PixelType_Gvsp_Coord3D_AC32f',
'PixelType_Gvsp_BayerRG12_Packed',
'PixelType_Gvsp_Coord3D_AB32',
'PixelType_Gvsp_BGR12_Packed',
'PixelType_Gvsp_BayerGR10_Packed',
'PixelType_Gvsp_Coord3D_AC32',
'PixelType_Gvsp_RGB12_Planar',
'PixelType_Gvsp_YCBCR709_422_8',
'PixelType_Gvsp_BGR8_Packed',
'PixelType_Gvsp_Jpeg',
'PixelType_Gvsp_Coord3D_AC32f_64',
'PixelType_Gvsp_YUV422_Packed',
'PixelType_Gvsp_Mono8_Signed',
'PixelType_Gvsp_BayerBG10',
'PixelType_Gvsp_BayerBG16',
'PixelType_Gvsp_BayerGR8',
'PixelType_Gvsp_RGB16_Planar',
'PixelType_Gvsp_Mono4p',
'PixelType_Gvsp_BayerRG10_Packed',
'PixelType_Gvsp_Mono8',
'PixelType_Gvsp_BayerGR16',
'PixelType_Gvsp_BayerGR10',
'PixelType_Gvsp_BGRA8_Packed',
'PixelType_Gvsp_BayerGR12',
'PixelType_Gvsp_Mono12_Packed',
'PixelType_Gvsp_YCBCR709_8_CBYCR',
'PixelType_Gvsp_Coord3D_A32',
'PixelType_Gvsp_YCBCR601_422_8',
'PixelType_Gvsp_Coord3D_C32',
'PixelType_Gvsp_YCBCR411_8_CBYYCRYY',
'PixelType_Gvsp_Undefined',
'PixelType_Gvsp_BayerGR12_Packed',
'PixelType_Gvsp_YCBCR601_411_8_CBYYCRYY',
'PixelType_Gvsp_RGB10_Planar',
'PixelType_Gvsp_BayerGB16',
'PixelType_Gvsp_BayerGB10',
'PixelType_Gvsp_BayerGB12',
'PixelType_Gvsp_BGR565_Packed',
'PixelType_Gvsp_Mono1p',
'PixelType_Gvsp_Coord3D_ABC16',
'PixelType_Gvsp_YUV444_Packed',
'PixelType_Gvsp_YUV422_YUYV_Packed',
'PixelType_Gvsp_BayerBG8',
'PixelType_Gvsp_Coord3D_C32f',
'PixelType_Gvsp_BGR10_Packed',
'PixelType_Gvsp_BayerGB10_Packed',
'PixelType_Gvsp_Coord3D_ABC32f_Planar',
'PixelType_Gvsp_Coord3D_ABC32f',
'PixelType_Gvsp_YCBCR422_8_CBYCRY',
'PixelType_Gvsp_RGB12_Packed',
'PixelType_Gvsp_Mono12',
'PixelType_Gvsp_Mono10',
'PixelType_Gvsp_Mono16',
'PixelType_Gvsp_Mono2p',
'PixelType_Gvsp_Mono14',
'PixelType_Gvsp_RGB10V2_Packed',
'PixelType_Gvsp_RGB12V1_Packed',
'PixelType_Gvsp_Mono10_Packed',
'PixelType_Gvsp_YCBCR601_8_CBYCR',
'PixelType_Gvsp_BayerGB8',
'PixelType_Gvsp_YCBCR8_CBYCR',
'PixelType_Gvsp_RGB565_Packed',
'PixelType_Gvsp_YCBCR601_422_8_CBYCRY',
'PixelType_Gvsp_YUV420SP_NV12',
'PixelType_Gvsp_YUV420SP_NV21',
'PixelType_Gvsp_Coord3D_C16',
'PixelType_Gvsp_BayerRBGG8',
'PixelType_Gvsp_HB_Mono8',
'PixelType_Gvsp_HB_Mono10',
'PixelType_Gvsp_HB_Mono10_Packed',
'PixelType_Gvsp_HB_Mono12',
'PixelType_Gvsp_HB_Mono12_Packed',
'PixelType_Gvsp_HB_Mono16',
'PixelType_Gvsp_HB_BayerGR8',
'PixelType_Gvsp_HB_BayerRG8',
'PixelType_Gvsp_HB_BayerGB8',
'PixelType_Gvsp_HB_BayerBG8',
'PixelType_Gvsp_HB_BayerRBGG8',
'PixelType_Gvsp_HB_BayerGR10',
'PixelType_Gvsp_HB_BayerRG10',
'PixelType_Gvsp_HB_BayerGB10',
'PixelType_Gvsp_HB_BayerBG10',
'PixelType_Gvsp_HB_BayerGR12',
'PixelType_Gvsp_HB_BayerRG12',
'PixelType_Gvsp_HB_BayerGB12',
'PixelType_Gvsp_HB_BayerBG12',
'PixelType_Gvsp_HB_BayerGR10_Packed',
'PixelType_Gvsp_HB_BayerRG10_Packed',
'PixelType_Gvsp_HB_BayerGB10_Packed',
'PixelType_Gvsp_HB_BayerBG10_Packed',
'PixelType_Gvsp_HB_BayerGR12_Packed',
'PixelType_Gvsp_HB_BayerRG12_Packed',
'PixelType_Gvsp_HB_BayerGB12_Packed',
'PixelType_Gvsp_HB_BayerBG12_Packed',
'PixelType_Gvsp_HB_YUV422_Packed',
'PixelType_Gvsp_HB_YUV422_YUYV_Packed',
'PixelType_Gvsp_HB_RGB8_Packed',
'PixelType_Gvsp_HB_BGR8_Packed',
'PixelType_Gvsp_HB_RGBA8_Packed',
'PixelType_Gvsp_HB_BGRA8_Packed',
'PixelType_Gvsp_HB_RGB16_Packed',
'PixelType_Gvsp_HB_BGR16_Packed',
'PixelType_Gvsp_HB_RGBA16_Packed',
'PixelType_Gvsp_HB_BGRA16_Packed']

146
camera/PyUICBasicDemo.py Normal file
View File

@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'PyUICBasicDemo.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(747, 486)
self.centralWidget = QtWidgets.QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
self.ComboDevices = QtWidgets.QComboBox(self.centralWidget)
self.ComboDevices.setGeometry(QtCore.QRect(10, 20, 511, 22))
self.ComboDevices.setObjectName("ComboDevices")
self.widgetDisplay = QtWidgets.QWidget(self.centralWidget)
self.widgetDisplay.setGeometry(QtCore.QRect(10, 60, 511, 401))
self.widgetDisplay.setObjectName("widgetDisplay")
self.groupInit = QtWidgets.QGroupBox(self.centralWidget)
self.groupInit.setGeometry(QtCore.QRect(530, 20, 211, 101))
self.groupInit.setObjectName("groupInit")
self.gridLayoutWidget = QtWidgets.QWidget(self.groupInit)
self.gridLayoutWidget.setGeometry(QtCore.QRect(9, 19, 201, 81))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(11, 11, 11, 11)
self.gridLayout.setSpacing(6)
self.gridLayout.setObjectName("gridLayout")
self.bnClose = QtWidgets.QPushButton(self.gridLayoutWidget)
self.bnClose.setEnabled(False)
self.bnClose.setObjectName("bnClose")
self.gridLayout.addWidget(self.bnClose, 2, 2, 1, 1)
self.bnOpen = QtWidgets.QPushButton(self.gridLayoutWidget)
self.bnOpen.setObjectName("bnOpen")
self.gridLayout.addWidget(self.bnOpen, 2, 1, 1, 1)
self.bnEnum = QtWidgets.QPushButton(self.gridLayoutWidget)
self.bnEnum.setObjectName("bnEnum")
self.gridLayout.addWidget(self.bnEnum, 1, 1, 1, 2)
self.groupGrab = QtWidgets.QGroupBox(self.centralWidget)
self.groupGrab.setEnabled(False)
self.groupGrab.setGeometry(QtCore.QRect(530, 130, 211, 171))
self.groupGrab.setObjectName("groupGrab")
self.gridLayoutWidget_2 = QtWidgets.QWidget(self.groupGrab)
self.gridLayoutWidget_2.setGeometry(QtCore.QRect(9, 19, 202, 141))
self.gridLayoutWidget_2.setObjectName("gridLayoutWidget_2")
self.gridLayout_2 = QtWidgets.QGridLayout(self.gridLayoutWidget_2)
self.gridLayout_2.setContentsMargins(11, 11, 11, 11)
self.gridLayout_2.setSpacing(6)
self.gridLayout_2.setObjectName("gridLayout_2")
self.bnSaveImage = QtWidgets.QPushButton(self.gridLayoutWidget_2)
self.bnSaveImage.setEnabled(False)
self.bnSaveImage.setObjectName("bnSaveImage")
self.gridLayout_2.addWidget(self.bnSaveImage, 4, 0, 1, 2)
self.radioContinueMode = QtWidgets.QRadioButton(self.gridLayoutWidget_2)
self.radioContinueMode.setObjectName("radioContinueMode")
self.gridLayout_2.addWidget(self.radioContinueMode, 0, 0, 1, 1)
self.radioTriggerMode = QtWidgets.QRadioButton(self.gridLayoutWidget_2)
self.radioTriggerMode.setObjectName("radioTriggerMode")
self.gridLayout_2.addWidget(self.radioTriggerMode, 0, 1, 1, 1)
self.bnStop = QtWidgets.QPushButton(self.gridLayoutWidget_2)
self.bnStop.setEnabled(False)
self.bnStop.setObjectName("bnStop")
self.gridLayout_2.addWidget(self.bnStop, 2, 1, 1, 1)
self.bnStart = QtWidgets.QPushButton(self.gridLayoutWidget_2)
self.bnStart.setEnabled(False)
self.bnStart.setObjectName("bnStart")
self.gridLayout_2.addWidget(self.bnStart, 2, 0, 1, 1)
self.bnSoftwareTrigger = QtWidgets.QPushButton(self.gridLayoutWidget_2)
self.bnSoftwareTrigger.setEnabled(False)
self.bnSoftwareTrigger.setObjectName("bnSoftwareTrigger")
self.gridLayout_2.addWidget(self.bnSoftwareTrigger, 3, 0, 1, 2)
self.groupParam = QtWidgets.QGroupBox(self.centralWidget)
self.groupParam.setEnabled(False)
self.groupParam.setGeometry(QtCore.QRect(530, 310, 211, 151))
self.groupParam.setObjectName("groupParam")
self.gridLayoutWidget_3 = QtWidgets.QWidget(self.groupParam)
self.gridLayoutWidget_3.setGeometry(QtCore.QRect(10, 20, 201, 131))
self.gridLayoutWidget_3.setObjectName("gridLayoutWidget_3")
self.gridLayoutParam = QtWidgets.QGridLayout(self.gridLayoutWidget_3)
self.gridLayoutParam.setContentsMargins(11, 11, 11, 11)
self.gridLayoutParam.setSpacing(6)
self.gridLayoutParam.setObjectName("gridLayoutParam")
self.label_6 = QtWidgets.QLabel(self.gridLayoutWidget_3)
self.label_6.setObjectName("label_6")
self.gridLayoutParam.addWidget(self.label_6, 3, 0, 1, 1)
self.edtGain = QtWidgets.QLineEdit(self.gridLayoutWidget_3)
self.edtGain.setObjectName("edtGain")
self.gridLayoutParam.addWidget(self.edtGain, 1, 1, 1, 1)
self.label_5 = QtWidgets.QLabel(self.gridLayoutWidget_3)
self.label_5.setObjectName("label_5")
self.gridLayoutParam.addWidget(self.label_5, 1, 0, 1, 1)
self.label_4 = QtWidgets.QLabel(self.gridLayoutWidget_3)
self.label_4.setObjectName("label_4")
self.gridLayoutParam.addWidget(self.label_4, 0, 0, 1, 1)
self.edtExposureTime = QtWidgets.QLineEdit(self.gridLayoutWidget_3)
self.edtExposureTime.setObjectName("edtExposureTime")
self.gridLayoutParam.addWidget(self.edtExposureTime, 0, 1, 1, 1)
self.bnGetParam = QtWidgets.QPushButton(self.gridLayoutWidget_3)
self.bnGetParam.setObjectName("bnGetParam")
self.gridLayoutParam.addWidget(self.bnGetParam, 4, 0, 1, 1)
self.bnSetParam = QtWidgets.QPushButton(self.gridLayoutWidget_3)
self.bnSetParam.setObjectName("bnSetParam")
self.gridLayoutParam.addWidget(self.bnSetParam, 4, 1, 1, 1)
self.edtFrameRate = QtWidgets.QLineEdit(self.gridLayoutWidget_3)
self.edtFrameRate.setObjectName("edtFrameRate")
self.gridLayoutParam.addWidget(self.edtFrameRate, 3, 1, 1, 1)
self.gridLayoutParam.setColumnStretch(0, 2)
self.gridLayoutParam.setColumnStretch(1, 3)
MainWindow.setCentralWidget(self.centralWidget)
self.statusBar = QtWidgets.QStatusBar(MainWindow)
self.statusBar.setObjectName("statusBar")
MainWindow.setStatusBar(self.statusBar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.groupInit.setTitle(_translate("MainWindow", "初始化"))
self.bnClose.setText(_translate("MainWindow", "关闭设备"))
self.bnOpen.setText(_translate("MainWindow", "打开设备"))
self.bnEnum.setText(_translate("MainWindow", "查找设备"))
self.groupGrab.setTitle(_translate("MainWindow", "采集"))
self.bnSaveImage.setText(_translate("MainWindow", "保存图像"))
self.radioContinueMode.setText(_translate("MainWindow", "连续模式"))
self.radioTriggerMode.setText(_translate("MainWindow", "触发模式"))
self.bnStop.setText(_translate("MainWindow", "停止采集"))
self.bnStart.setText(_translate("MainWindow", "开始采集"))
self.bnSoftwareTrigger.setText(_translate("MainWindow", "软触发一次"))
self.groupParam.setTitle(_translate("MainWindow", "参数"))
self.label_6.setText(_translate("MainWindow", "帧率"))
self.edtGain.setText(_translate("MainWindow", "0"))
self.label_5.setText(_translate("MainWindow", "增益"))
self.label_4.setText(_translate("MainWindow", "曝光"))
self.edtExposureTime.setText(_translate("MainWindow", "0"))
self.bnGetParam.setText(_translate("MainWindow", "获取参数"))
self.bnSetParam.setText(_translate("MainWindow", "设置参数"))
self.edtFrameRate.setText(_translate("MainWindow", "0"))

261
camera/PyUICBasicDemo.ui Normal file
View File

@ -0,0 +1,261 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>747</width>
<height>486</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<widget class="QComboBox" name="ComboDevices">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>511</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QWidget" name="widgetDisplay" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>60</y>
<width>511</width>
<height>401</height>
</rect>
</property>
</widget>
<widget class="QGroupBox" name="groupInit">
<property name="geometry">
<rect>
<x>530</x>
<y>20</y>
<width>211</width>
<height>101</height>
</rect>
</property>
<property name="title">
<string>初始化</string>
</property>
<widget class="QWidget" name="gridLayoutWidget">
<property name="geometry">
<rect>
<x>9</x>
<y>19</y>
<width>201</width>
<height>81</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="2">
<widget class="QPushButton" name="bnClose">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>关闭设备</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="bnOpen">
<property name="text">
<string>打开设备</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QPushButton" name="bnEnum">
<property name="text">
<string>查找设备</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QGroupBox" name="groupGrab">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>530</x>
<y>130</y>
<width>211</width>
<height>171</height>
</rect>
</property>
<property name="title">
<string>采集</string>
</property>
<widget class="QWidget" name="gridLayoutWidget_2">
<property name="geometry">
<rect>
<x>9</x>
<y>19</y>
<width>202</width>
<height>141</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="bnSaveImage">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>保存图像</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="radioContinueMode">
<property name="text">
<string>连续模式</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="radioTriggerMode">
<property name="text">
<string>触发模式</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="bnStop">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>停止采集</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="bnStart">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>开始采集</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QPushButton" name="bnSoftwareTrigger">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>软触发一次</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QGroupBox" name="groupParam">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>530</x>
<y>310</y>
<width>211</width>
<height>151</height>
</rect>
</property>
<property name="title">
<string>参数</string>
</property>
<widget class="QWidget" name="gridLayoutWidget_3">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>201</width>
<height>131</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayoutParam" columnstretch="2,3">
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>帧率</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="edtGain">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>增益</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>曝光</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="edtExposureTime">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QPushButton" name="bnGetParam">
<property name="text">
<string>获取参数</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="bnSetParam">
<property name="text">
<string>设置参数</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="edtFrameRate">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<tabstops>
<tabstop>ComboDevices</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

19
config/app_config.json Normal file
View File

@ -0,0 +1,19 @@
{
"app": {
"name": "腾智微丝产线包装系统",
"version": "1.0.0",
"features": {
"enable_serial_ports": false,
"enable_keyboard_listener": false
}
},
"database": {
"type": "sqlite",
"path": "db/jtDB.db",
"host": "",
"port": "",
"user": "",
"password": "",
"name": ""
}
}

5
config/application.yaml Normal file
View File

@ -0,0 +1,5 @@
app:
debug: true
name: jt-mes-system
version: 1.0.0
description: 佳腾管理系统

52
dao/login_dao.py Normal file
View File

@ -0,0 +1,52 @@
from db.pgsql import PostgreSQL
from db.system_config_dao import SystemConfigDAO
def check_user_login(user_id: str, password: str) -> bool:
db = PostgreSQL()
try:
db.connect()
sql = "SELECT 1 FROM sys_user WHERE user_id=%s AND password=md5(%s)"
result = db.execute_query(sql, (user_id, password))
return bool(len(result) > 0)
finally:
db.disconnect()
def get_user_info(user_id: str):
db = PostgreSQL()
try:
db.connect()
sql = """
SELECT user_id, nick_name AS user_name, position_id,
f_getname('GETCORPEXP', '', '', org_id) AS corp_name,
org_id AS corp_id, default_org_id
FROM sys_user
WHERE user_id = %s
"""
result = db.execute_query(sql, (user_id,))
if result:
user_name = result[0]['user_name']
corp_name = result[0]['corp_name']
corp_id = result[0]['corp_id']
position_id = result[0]['position_id']
default_org_id = result[0]['default_org_id']
# 如果有默认账套ID使用默认账套ID替代登录账套ID
if default_org_id:
corp_id = default_org_id
# 查询默认账套的名称
corp_name_sql = """
SELECT corp_exp
FROM sys_org
WHERE org_id = %s
"""
corp_result = db.execute_query(corp_name_sql, (corp_id,))
if corp_result:
corp_name = corp_result[0]['corp_exp']
# 将用户的当前账套ID保存到内存中
SystemConfigDAO.save_user_selected_corp(user_id, corp_id)
return user_name, corp_name, corp_id, position_id
return None, None, None, None
finally:
db.disconnect()

BIN
db/jtDB.db Normal file

Binary file not shown.

1628
logs/app_2025-06-05.log Normal file

File diff suppressed because it is too large Load Diff

15
logs/app_2025-06-06.log Normal file
View File

@ -0,0 +1,15 @@
2025-06-06 15:33:51,679 - INFO - root - [<module>:98] - =================================================================
2025-06-06 15:33:51,679 - INFO - root - [<module>:99] - 应用程序启动
2025-06-06 15:33:51,679 - INFO - root - [<module>:100] - Python version: 3.10.9 (main, Mar 1 2023, 12:20:14) [Clang 14.0.6 ]
2025-06-06 15:33:51,679 - INFO - root - [<module>:101] - Working directory: /Users/meng/work/tengzhi/jiateng_ws
2025-06-06 15:33:51,679 - INFO - root - [<module>:102] - 日志文件: logs/app_2025-06-06.log
2025-06-06 15:33:51,934 - INFO - root - [main:125] - =====================================================
2025-06-06 15:33:51,935 - INFO - root - [main:126] - 应用程序启动
2025-06-06 15:33:51,935 - INFO - root - [main:127] - =====================================================
2025-06-06 15:33:51,935 - INFO - root - [load_config:34] - 已加载配置文件: config/app_config.json
2025-06-06 15:33:51,935 - INFO - root - [main:135] - 配置信息 - 启用串口: False, 启用键盘监听: False
2025-06-06 15:33:51,937 - INFO - root - [install_menu_translator:12] - 已安装菜单翻译器
2025-06-06 15:33:51,937 - INFO - root - [main:176] - 已设置中文翻译器,翻译路径: /Users/meng/anaconda3/lib/python3.10/site-packages/PySide6/Qt/translations加载状态: True
2025-06-06 15:33:56,785 - INFO - root - [handle_login:81] - 正在创建主窗口用户ID: system, 姓名: system, 公司: Default Corp
2025-06-06 15:33:56,832 - INFO - root - [__init__:44] - 主窗口已创建,用户: system
2025-06-06 15:33:56,863 - INFO - root - [handle_login:84] - 主窗口已显示(最大化)

172
logs/app_2025-06-07.log Normal file
View File

@ -0,0 +1,172 @@
2025-06-07 09:03:35,618 - INFO - root - [<module>:98] - =================================================================
2025-06-07 09:03:35,618 - INFO - root - [<module>:99] - 应用程序启动
2025-06-07 09:03:35,618 - INFO - root - [<module>:100] - Python version: 3.10.9 (main, Mar 1 2023, 12:20:14) [Clang 14.0.6 ]
2025-06-07 09:03:35,618 - INFO - root - [<module>:101] - Working directory: /Users/meng/work/tengzhi/jiateng_ws
2025-06-07 09:03:35,618 - INFO - root - [<module>:102] - 日志文件: logs/app_2025-06-07.log
2025-06-07 09:03:35,784 - INFO - root - [main:125] - =====================================================
2025-06-07 09:03:35,784 - INFO - root - [main:126] - 应用程序启动
2025-06-07 09:03:35,784 - INFO - root - [main:127] - =====================================================
2025-06-07 09:03:35,784 - INFO - root - [load_config:34] - 已加载配置文件: config/app_config.json
2025-06-07 09:03:35,784 - INFO - root - [main:135] - 配置信息 - 启用串口: False, 启用键盘监听: False
2025-06-07 09:03:35,786 - INFO - root - [install_menu_translator:12] - 已安装菜单翻译器
2025-06-07 09:03:35,786 - INFO - root - [main:176] - 已设置中文翻译器,翻译路径: /Users/meng/anaconda3/lib/python3.10/site-packages/PySide6/Qt/translations加载状态: True
2025-06-07 09:03:39,742 - INFO - root - [handle_login:81] - 正在创建主窗口用户ID: system, 姓名: system, 公司: Default Corp
2025-06-07 09:03:39,796 - CRITICAL - root - [handle_login:86] - 创建或显示主窗口时发生错误: name 'MvCamCtrldll' is not defined
Traceback (most recent call last):
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/login_widget.py", line 82, in handle_login
self.main_window = MainWindow(user_id, user_name, corp_name, corp_id, position_id)
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/main_window.py", line 37, in __init__
self.camera_display = CameraDisplayWidget()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_display_widget.py", line 37, in __init__
self.camera_manager = CameraManager.get_instance()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_manager.py", line 27, in get_instance
CameraManager._instance = CameraManager()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_manager.py", line 47, in __init__
MvCamera.MV_CC_Initialize()
File "/Users/meng/work/tengzhi/jiateng_ws/camera/MvCameraControl_class.py", line 50, in MV_CC_Initialize
MvCamCtrldll.MV_CC_Initialize.restype = c_int
NameError: name 'MvCamCtrldll' is not defined
2025-06-07 09:05:18,994 - INFO - root - [<module>:98] - =================================================================
2025-06-07 09:05:18,994 - INFO - root - [<module>:99] - 应用程序启动
2025-06-07 09:05:18,994 - INFO - root - [<module>:100] - Python version: 3.10.9 (main, Mar 1 2023, 12:20:14) [Clang 14.0.6 ]
2025-06-07 09:05:18,994 - INFO - root - [<module>:101] - Working directory: /Users/meng/work/tengzhi/jiateng_ws
2025-06-07 09:05:18,994 - INFO - root - [<module>:102] - 日志文件: logs/app_2025-06-07.log
2025-06-07 09:05:19,168 - INFO - root - [main:125] - =====================================================
2025-06-07 09:05:19,168 - INFO - root - [main:126] - 应用程序启动
2025-06-07 09:05:19,168 - INFO - root - [main:127] - =====================================================
2025-06-07 09:05:19,169 - INFO - root - [load_config:34] - 已加载配置文件: config/app_config.json
2025-06-07 09:05:19,169 - INFO - root - [main:135] - 配置信息 - 启用串口: False, 启用键盘监听: False
2025-06-07 09:05:19,169 - INFO - root - [install_menu_translator:12] - 已安装菜单翻译器
2025-06-07 09:05:19,169 - INFO - root - [main:176] - 已设置中文翻译器,翻译路径: /Users/meng/anaconda3/lib/python3.10/site-packages/PySide6/Qt/translations加载状态: True
2025-06-07 09:05:24,244 - INFO - root - [handle_login:81] - 正在创建主窗口用户ID: system, 姓名: system, 公司: Default Corp
2025-06-07 09:05:24,289 - CRITICAL - root - [handle_login:86] - 创建或显示主窗口时发生错误: name 'MvCamCtrldll' is not defined
Traceback (most recent call last):
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/login_widget.py", line 82, in handle_login
self.main_window = MainWindow(user_id, user_name, corp_name, corp_id, position_id)
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/main_window.py", line 37, in __init__
self.camera_display = CameraDisplayWidget()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_display_widget.py", line 37, in __init__
self.camera_manager = CameraManager.get_instance()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_manager.py", line 27, in get_instance
CameraManager._instance = CameraManager()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_manager.py", line 47, in __init__
MvCamera.MV_CC_Initialize()
File "/Users/meng/work/tengzhi/jiateng_ws/camera/MvCameraControl_class.py", line 50, in MV_CC_Initialize
MvCamCtrldll.MV_CC_Initialize.restype = c_int
NameError: name 'MvCamCtrldll' is not defined
2025-06-07 09:06:11,419 - INFO - root - [<module>:98] - =================================================================
2025-06-07 09:06:11,419 - INFO - root - [<module>:99] - 应用程序启动
2025-06-07 09:06:11,420 - INFO - root - [<module>:100] - Python version: 3.10.9 (main, Mar 1 2023, 12:20:14) [Clang 14.0.6 ]
2025-06-07 09:06:11,420 - INFO - root - [<module>:101] - Working directory: /Users/meng/work/tengzhi/jiateng_ws
2025-06-07 09:06:11,420 - INFO - root - [<module>:102] - 日志文件: logs/app_2025-06-07.log
2025-06-07 09:06:11,559 - INFO - root - [main:125] - =====================================================
2025-06-07 09:06:11,559 - INFO - root - [main:126] - 应用程序启动
2025-06-07 09:06:11,559 - INFO - root - [main:127] - =====================================================
2025-06-07 09:06:11,559 - INFO - root - [load_config:34] - 已加载配置文件: config/app_config.json
2025-06-07 09:06:11,559 - INFO - root - [main:135] - 配置信息 - 启用串口: False, 启用键盘监听: False
2025-06-07 09:06:11,560 - INFO - root - [install_menu_translator:12] - 已安装菜单翻译器
2025-06-07 09:06:11,560 - INFO - root - [main:176] - 已设置中文翻译器,翻译路径: /Users/meng/anaconda3/lib/python3.10/site-packages/PySide6/Qt/translations加载状态: True
2025-06-07 09:06:15,973 - INFO - root - [handle_login:81] - 正在创建主窗口用户ID: system, 姓名: system, 公司: Default Corp
2025-06-07 09:06:16,015 - CRITICAL - root - [handle_login:86] - 创建或显示主窗口时发生错误: name 'MvCamCtrldll' is not defined
Traceback (most recent call last):
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/login_widget.py", line 82, in handle_login
self.main_window = MainWindow(user_id, user_name, corp_name, corp_id, position_id)
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/main_window.py", line 37, in __init__
self.camera_display = CameraDisplayWidget()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_display_widget.py", line 37, in __init__
self.camera_manager = CameraManager.get_instance()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_manager.py", line 27, in get_instance
CameraManager._instance = CameraManager()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_manager.py", line 47, in __init__
MvCamera.MV_CC_Initialize()
File "/Users/meng/work/tengzhi/jiateng_ws/camera/MvCameraControl_class.py", line 50, in MV_CC_Initialize
MvCamCtrldll.MV_CC_Initialize.restype = c_int
NameError: name 'MvCamCtrldll' is not defined
2025-06-07 09:06:24,249 - INFO - root - [main:191] - 应用程序退出,退出码: 0
2025-06-07 09:07:01,441 - INFO - root - [<module>:98] - =================================================================
2025-06-07 09:07:01,441 - INFO - root - [<module>:99] - 应用程序启动
2025-06-07 09:07:01,441 - INFO - root - [<module>:100] - Python version: 3.10.9 (main, Mar 1 2023, 12:20:14) [Clang 14.0.6 ]
2025-06-07 09:07:01,441 - INFO - root - [<module>:101] - Working directory: /Users/meng/work/tengzhi/jiateng_ws
2025-06-07 09:07:01,441 - INFO - root - [<module>:102] - 日志文件: logs/app_2025-06-07.log
2025-06-07 09:07:01,585 - INFO - root - [main:125] - =====================================================
2025-06-07 09:07:01,585 - INFO - root - [main:126] - 应用程序启动
2025-06-07 09:07:01,585 - INFO - root - [main:127] - =====================================================
2025-06-07 09:07:01,585 - INFO - root - [load_config:34] - 已加载配置文件: config/app_config.json
2025-06-07 09:07:01,585 - INFO - root - [main:135] - 配置信息 - 启用串口: False, 启用键盘监听: False
2025-06-07 09:07:01,586 - INFO - root - [install_menu_translator:12] - 已安装菜单翻译器
2025-06-07 09:07:01,586 - INFO - root - [main:176] - 已设置中文翻译器,翻译路径: /Users/meng/anaconda3/lib/python3.10/site-packages/PySide6/Qt/translations加载状态: True
2025-06-07 09:07:05,135 - INFO - root - [handle_login:81] - 正在创建主窗口用户ID: system, 姓名: system, 公司: Default Corp
2025-06-07 09:07:05,179 - CRITICAL - root - [handle_login:86] - 创建或显示主窗口时发生错误: 'MainWindow' object has no attribute 'camera_display'
Traceback (most recent call last):
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/login_widget.py", line 82, in handle_login
self.main_window = MainWindow(user_id, user_name, corp_name, corp_id, position_id)
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/main_window.py", line 38, in __init__
self.material_content_layout.addWidget(self.camera_display)
AttributeError: 'MainWindow' object has no attribute 'camera_display'
2025-06-07 09:07:08,971 - INFO - root - [main:191] - 应用程序退出,退出码: 0
2025-06-07 09:07:27,619 - INFO - root - [<module>:98] - =================================================================
2025-06-07 09:07:27,619 - INFO - root - [<module>:99] - 应用程序启动
2025-06-07 09:07:27,619 - INFO - root - [<module>:100] - Python version: 3.10.9 (main, Mar 1 2023, 12:20:14) [Clang 14.0.6 ]
2025-06-07 09:07:27,619 - INFO - root - [<module>:101] - Working directory: /Users/meng/work/tengzhi/jiateng_ws
2025-06-07 09:07:27,619 - INFO - root - [<module>:102] - 日志文件: logs/app_2025-06-07.log
2025-06-07 09:07:27,757 - INFO - root - [main:125] - =====================================================
2025-06-07 09:07:27,757 - INFO - root - [main:126] - 应用程序启动
2025-06-07 09:07:27,757 - INFO - root - [main:127] - =====================================================
2025-06-07 09:07:27,758 - INFO - root - [load_config:34] - 已加载配置文件: config/app_config.json
2025-06-07 09:07:27,758 - INFO - root - [main:135] - 配置信息 - 启用串口: False, 启用键盘监听: False
2025-06-07 09:07:27,758 - INFO - root - [install_menu_translator:12] - 已安装菜单翻译器
2025-06-07 09:07:27,758 - INFO - root - [main:176] - 已设置中文翻译器,翻译路径: /Users/meng/anaconda3/lib/python3.10/site-packages/PySide6/Qt/translations加载状态: True
2025-06-07 09:07:31,806 - INFO - root - [handle_login:81] - 正在创建主窗口用户ID: system, 姓名: system, 公司: Default Corp
2025-06-07 09:07:31,858 - CRITICAL - root - [handle_login:86] - 创建或显示主窗口时发生错误: 'MainWindow' object has no attribute 'camera_display'
Traceback (most recent call last):
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/login_widget.py", line 82, in handle_login
self.main_window = MainWindow(user_id, user_name, corp_name, corp_id, position_id)
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/main_window.py", line 58, in __init__
self.connect_signals()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/main_window.py", line 83, in connect_signals
self.camera_display.signal_camera_status.connect(self.handle_camera_status)
AttributeError: 'MainWindow' object has no attribute 'camera_display'
2025-06-07 09:08:04,413 - INFO - root - [<module>:98] - =================================================================
2025-06-07 09:08:04,413 - INFO - root - [<module>:99] - 应用程序启动
2025-06-07 09:08:04,413 - INFO - root - [<module>:100] - Python version: 3.10.9 (main, Mar 1 2023, 12:20:14) [Clang 14.0.6 ]
2025-06-07 09:08:04,413 - INFO - root - [<module>:101] - Working directory: /Users/meng/work/tengzhi/jiateng_ws
2025-06-07 09:08:04,413 - INFO - root - [<module>:102] - 日志文件: logs/app_2025-06-07.log
2025-06-07 09:08:04,549 - INFO - root - [main:125] - =====================================================
2025-06-07 09:08:04,550 - INFO - root - [main:126] - 应用程序启动
2025-06-07 09:08:04,550 - INFO - root - [main:127] - =====================================================
2025-06-07 09:08:04,550 - INFO - root - [load_config:34] - 已加载配置文件: config/app_config.json
2025-06-07 09:08:04,550 - INFO - root - [main:135] - 配置信息 - 启用串口: False, 启用键盘监听: False
2025-06-07 09:08:04,550 - INFO - root - [install_menu_translator:12] - 已安装菜单翻译器
2025-06-07 09:08:04,550 - INFO - root - [main:176] - 已设置中文翻译器,翻译路径: /Users/meng/anaconda3/lib/python3.10/site-packages/PySide6/Qt/translations加载状态: True
2025-06-07 09:08:07,854 - INFO - root - [handle_login:81] - 正在创建主窗口用户ID: system, 姓名: system, 公司: Default Corp
2025-06-07 09:08:07,897 - CRITICAL - root - [handle_login:86] - 创建或显示主窗口时发生错误: name 'MvCamCtrldll' is not defined
Traceback (most recent call last):
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/login_widget.py", line 82, in handle_login
self.main_window = MainWindow(user_id, user_name, corp_name, corp_id, position_id)
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/main_window.py", line 37, in __init__
self.camera_display = CameraDisplayWidget()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_display_widget.py", line 37, in __init__
self.camera_manager = CameraManager.get_instance()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_manager.py", line 27, in get_instance
CameraManager._instance = CameraManager()
File "/Users/meng/work/tengzhi/jiateng_ws/widgets/camera_manager.py", line 47, in __init__
MvCamera.MV_CC_Initialize()
File "/Users/meng/work/tengzhi/jiateng_ws/camera/MvCameraControl_class.py", line 50, in MV_CC_Initialize
MvCamCtrldll.MV_CC_Initialize.restype = c_int
NameError: name 'MvCamCtrldll' is not defined
2025-06-07 09:08:10,965 - INFO - root - [main:191] - 应用程序退出,退出码: 0
2025-06-07 09:10:03,526 - INFO - root - [<module>:98] - =================================================================
2025-06-07 09:10:03,526 - INFO - root - [<module>:99] - 应用程序启动
2025-06-07 09:10:03,526 - INFO - root - [<module>:100] - Python version: 3.10.9 (main, Mar 1 2023, 12:20:14) [Clang 14.0.6 ]
2025-06-07 09:10:03,527 - INFO - root - [<module>:101] - Working directory: /Users/meng/work/tengzhi/jiateng_ws
2025-06-07 09:10:03,527 - INFO - root - [<module>:102] - 日志文件: logs/app_2025-06-07.log
2025-06-07 09:10:03,669 - INFO - root - [main:125] - =====================================================
2025-06-07 09:10:03,669 - INFO - root - [main:126] - 应用程序启动
2025-06-07 09:10:03,669 - INFO - root - [main:127] - =====================================================
2025-06-07 09:10:03,670 - INFO - root - [load_config:34] - 已加载配置文件: config/app_config.json
2025-06-07 09:10:03,670 - INFO - root - [main:135] - 配置信息 - 启用串口: False, 启用键盘监听: False
2025-06-07 09:10:03,670 - INFO - root - [install_menu_translator:12] - 已安装菜单翻译器
2025-06-07 09:10:03,670 - INFO - root - [main:176] - 已设置中文翻译器,翻译路径: /Users/meng/anaconda3/lib/python3.10/site-packages/PySide6/Qt/translations加载状态: True
2025-06-07 09:10:07,872 - INFO - root - [handle_login:81] - 正在创建主窗口用户ID: system, 姓名: system, 公司: Default Corp
2025-06-07 09:10:07,924 - INFO - root - [__init__:68] - 主窗口已创建,用户: system
2025-06-07 09:10:07,957 - INFO - root - [handle_login:84] - 主窗口已显示(最大化)

200
main.py Normal file
View File

@ -0,0 +1,200 @@
import sys
import logging
import os
import tempfile
import traceback
from logging.handlers import TimedRotatingFileHandler
from datetime import datetime
from PySide6.QtWidgets import QApplication, QMessageBox
from PySide6.QtCore import QTranslator, QLocale, QLibraryInfo
from widgets.login_widget import LoginWidget
from utils.menu_translator import MenuTranslator
from utils.config_loader import ConfigLoader
# 自定义异常处理器
def global_exception_handler(exctype, value, tb):
"""全局异常处理器,捕获未处理的异常并记录日志"""
error_msg = ''.join(traceback.format_exception(exctype, value, tb))
logging.critical(f"未捕获的异常: {error_msg}")
# 如果已经有QApplication实例显示错误对话框
if QApplication.instance():
QMessageBox.critical(None, "系统错误",
f"发生了未处理的错误:\n{value}\n\n请联系管理员并提供日志文件。")
# 调用原始的异常处理器
sys.__excepthook__(exctype, value, tb)
# 安装全局异常处理器
sys.excepthook = global_exception_handler
# 设置日志目录
log_dir = 'logs'
temp_log_dir = None
# 尝试使用应用程序目录
try:
if not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
# 测试目录是否可写
test_file = os.path.join(log_dir, 'test_write.tmp')
with open(test_file, 'w') as f:
f.write('test')
os.remove(test_file)
logging.basicConfig(level=logging.INFO, format='%(message)s')
logging.info(f"使用应用程序日志目录: {os.path.abspath(log_dir)}")
except (OSError, PermissionError) as e:
# 如果应用程序目录不可用或不可写,使用临时目录
temp_log_dir = os.path.join(tempfile.gettempdir(), 'app_logs')
log_dir = temp_log_dir
try:
if not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
logging.basicConfig(level=logging.INFO, format='%(message)s')
logging.info(f"无法使用应用程序日志目录,原因: {e}")
logging.info(f"使用临时日志目录: {log_dir}")
except Exception as e2:
# 如果临时目录也失败,只使用控制台输出
print(f"无法创建日志目录,将只使用控制台输出: {e2}")
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# 配置全局日志
# 使用日期命名日志文件
today = datetime.now().strftime('%Y-%m-%d')
log_file_path = os.path.join(log_dir, f'app_{today}.log')
try:
# 创建一个TimedRotatingFileHandler每天轮换一次日志文件
file_handler = TimedRotatingFileHandler(
log_file_path,
when='midnight', # 每天午夜轮换
interval=1, # 轮换间隔为1天
backupCount=30, # 保留30天的日志
encoding='utf-8' # 使用UTF-8编码
)
# 配置日志格式
log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - [%(funcName)s:%(lineno)d] - %(message)s')
file_handler.setFormatter(log_format)
# 控制台输出处理器
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(log_format)
# 配置根日志记录器
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO) # 将默认级别设为INFO减少DEBUG级别的输出
# 清除已有的handlers
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
root_logger.addHandler(file_handler)
root_logger.addHandler(console_handler)
logging.info("=================================================================")
logging.info("应用程序启动")
logging.info(f"Python version: {sys.version}")
logging.info(f"Working directory: {os.getcwd()}")
logging.info(f"日志文件: {log_file_path}")
except Exception as e:
# 如果文件处理器创建失败,仅使用控制台输出
logging.error(f"无法设置日志文件,将只使用控制台输出: {e}")
# 确保根logger至少有一个控制台处理器
root_logger = logging.getLogger()
if not root_logger.handlers:
console_handler = logging.StreamHandler(sys.stdout)
console_format = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - [%(funcName)s:%(lineno)d] - %(message)s')
console_handler.setFormatter(console_format)
root_logger.addHandler(console_handler)
logging.info("=================================================================")
logging.info("应用程序启动 (仅控制台日志)")
logging.info(f"Python version: {sys.version}")
logging.info(f"Working directory: {os.getcwd()}")
def main():
"""主程序入口"""
app = QApplication(sys.argv)
logging.info("=====================================================")
logging.info(" 应用程序启动 ")
logging.info("=====================================================")
try:
# 读取配置
config = ConfigLoader.get_instance()
# 打印关键配置信息
enable_serial_ports = config.get_value('app.features.enable_serial_ports', False)
enable_keyboard_listener = config.get_value('app.features.enable_keyboard_listener', False)
logging.info(f"配置信息 - 启用串口: {enable_serial_ports}, 启用键盘监听: {enable_keyboard_listener}")
# 设置中文翻译器
translator = QTranslator(app)
# 使用兼容新旧版本的方式获取翻译路径
try:
# 新版本API
translations_path = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)
except AttributeError:
# 旧版本API
translations_path = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
# 尝试加载翻译文件
translation_loaded = translator.load("qt_zh_CN", translations_path)
# 如果加载失败,尝试在可执行文件目录下查找翻译文件
if not translation_loaded and getattr(sys, 'frozen', False):
# 打包后的应用
app_dir = os.path.dirname(sys.executable)
possible_paths = [
os.path.join(app_dir, "translations"),
os.path.join(app_dir, "PySide6", "translations"),
os.path.join(app_dir, "Qt", "translations"),
]
for path in possible_paths:
if os.path.exists(path):
if translator.load("qt_zh_CN", path):
translation_loaded = True
translations_path = path
break
app.installTranslator(translator)
# 设置应用程序的本地化
locale = QLocale(QLocale.Chinese, QLocale.China)
QLocale.setDefault(locale)
# 安装菜单翻译器
MenuTranslator.install_menu_translator(app)
logging.info(f"已设置中文翻译器,翻译路径: {translations_path},加载状态: {translation_loaded}")
# 创建db目录如果不存在
os.makedirs('db', exist_ok=True)
# 检查数据库是否存在,如果不存在则初始化
if not os.path.exists('db/jtDB.db'):
from utils.init_db import init_database
init_database()
logging.info("初始化数据库完成")
login_widget = LoginWidget()
login_widget.show()
exit_code = app.exec()
logging.info(f"应用程序退出,退出码: {exit_code}")
sys.exit(exit_code)
except Exception as e:
logging.critical(f"严重错误: {e}", exc_info=True)
print(f"严重错误: {e}") # 确保即使日志系统故障也能看到错误
sys.exit(1)
if __name__ == "__main__":
main()

Binary file not shown.

Binary file not shown.

Binary file not shown.

85
ui/login_ui.py Normal file
View File

@ -0,0 +1,85 @@
from PySide6.QtWidgets import (
QWidget, QLabel, QLineEdit, QPushButton, QComboBox, QGridLayout, QHBoxLayout, QVBoxLayout
)
from PySide6.QtGui import QFont
from PySide6.QtCore import Qt
class LoginUI(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("用户登录")
self.resize(350, 220)
self.init_ui()
def init_ui(self):
font_title = QFont("微软雅黑", 18, QFont.Bold)
font_label = QFont("微软雅黑", 12)
font_input = QFont("微软雅黑", 12)
font_version = QFont("微软雅黑", 8)
# 标题
self.label_title = QLabel("腾龙集团MES管理系统")
self.label_title.setFont(font_title)
self.label_title.setStyleSheet("color: #1a237e;")
self.label_title.setAlignment(Qt.AlignCenter)
self.label_title.setFixedHeight(40)
# 工号
self.label_user = QLabel("工号:")
self.label_user.setFont(font_label)
self.label_user.setFixedWidth(50)
self.edit_user = QLineEdit()
self.edit_user.setFont(font_input)
self.edit_user.setFixedHeight(28)
# 密码
self.label_pwd = QLabel("密码:")
self.label_pwd.setFont(font_label)
self.label_pwd.setFixedWidth(50)
self.edit_pwd = QLineEdit()
self.edit_pwd.setFont(font_input)
self.edit_pwd.setEchoMode(QLineEdit.Password)
self.edit_pwd.setFixedHeight(28)
# 按钮
self.btn_login = QPushButton("登陆")
self.btn_close = QPushButton("关闭")
self.btn_login.setFixedWidth(90)
self.btn_close.setFixedWidth(90)
self.btn_login.setFixedHeight(30)
self.btn_close.setFixedHeight(30)
btn_layout = QHBoxLayout()
btn_layout.addStretch(1)
btn_layout.addWidget(self.btn_login)
btn_layout.addSpacing(30)
btn_layout.addWidget(self.btn_close)
btn_layout.addStretch(1)
# 版本标签
self.version_label = QLabel("版本: 1.0.0")
self.version_label.setFont(font_version)
self.version_label.setStyleSheet("color: #666666;")
self.version_label.setAlignment(Qt.AlignRight)
# 表单布局
form_layout = QGridLayout()
form_layout.setHorizontalSpacing(10)
form_layout.setVerticalSpacing(18)
form_layout.addWidget(self.label_user, 1, 0)
form_layout.addWidget(self.edit_user, 1, 1)
form_layout.addWidget(self.label_pwd, 2, 0)
form_layout.addWidget(self.edit_pwd, 2, 1)
# 主布局
main_layout = QVBoxLayout()
main_layout.addSpacing(8)
main_layout.addWidget(self.label_title)
main_layout.addSpacing(5)
main_layout.addLayout(form_layout)
main_layout.addSpacing(10)
main_layout.addLayout(btn_layout)
main_layout.addStretch(1)
main_layout.addWidget(self.version_label)
main_layout.addSpacing(5)
self.setLayout(main_layout)

742
ui/main_window_ui.py Normal file
View File

@ -0,0 +1,742 @@
from PySide6.QtWidgets import (
QMainWindow, QWidget, QLabel, QGridLayout, QVBoxLayout, QHBoxLayout,
QTableWidget, QTableWidgetItem, QHeaderView, QFrame, QSplitter,
QPushButton, QLineEdit, QAbstractItemView
)
from PySide6.QtGui import QFont, QAction, QBrush, QColor
from PySide6.QtCore import Qt
class MainWindowUI(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("腾智微丝产线包装系统")
self.resize(1200, 800)
self.init_ui()
def init_ui(self):
# 设置字体
self.title_font = QFont("微软雅黑", 16, QFont.Bold)
self.second_title_font = QFont("微软雅黑", 14, QFont.Bold)
self.normal_font = QFont("微软雅黑", 12)
self.small_font = QFont("微软雅黑", 9)
# 创建菜单栏
self.create_menu()
# 创建中央部件
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
# 创建主布局 - 水平分割
self.main_layout = QHBoxLayout(self.central_widget)
self.main_layout.setContentsMargins(5, 5, 5, 5)
self.main_layout.setSpacing(5)
# 创建左侧面板
self.left_panel = QWidget()
self.left_layout = QVBoxLayout(self.left_panel)
self.left_layout.setContentsMargins(0, 0, 0, 0)
self.left_layout.setSpacing(5)
self.create_left_panel()
# 创建右侧面板
self.right_panel = QWidget()
self.right_layout = QVBoxLayout(self.right_panel)
self.right_layout.setContentsMargins(0, 0, 0, 0)
self.right_layout.setSpacing(5)
self.create_right_panel()
# 添加左右面板到主布局
self.main_layout.addWidget(self.left_panel, 1) # 左侧面板占比较小
self.main_layout.addWidget(self.right_panel, 2) # 右侧面板占比较大
def create_menu(self):
# 创建菜单栏
self.menubar = self.menuBar()
# 用户操作菜单
self.user_menu = self.menubar.addMenu("用户操作页")
self.main_action = QAction("主页面", self)
self.user_menu.addAction(self.main_action)
# 系统设置菜单
self.system_menu = self.menubar.addMenu("系统设置")
self.settings_action = QAction("设置页面", self)
self.system_menu.addAction(self.settings_action)
def create_left_panel(self):
# 标题
self.title_label = QLabel("腾智微丝产线包装系统")
self.title_label.setFont(self.title_font)
self.title_label.setAlignment(Qt.AlignCenter)
self.title_label.setStyleSheet("color: #1a237e; padding: 10px; background-color: #f5f5f5; border-radius: 4px;")
self.left_layout.addWidget(self.title_label)
# 项目信息表格 - 使用QFrame包裹添加边框
self.project_frame = QFrame()
self.project_frame.setFrameShape(QFrame.StyledPanel)
self.project_frame.setLineWidth(1)
self.project_frame.setFixedHeight(150) # 调整这个值可以控制整体高度
self.project_layout = QVBoxLayout(self.project_frame)
self.project_layout.setContentsMargins(5, 5, 5, 5)
# 项目表格
self.project_table = QTableWidget(4, 5)
self.project_table.setHorizontalHeaderLabels(["项目", "用电", "数量", "产量", "开机率"])
self.project_table.setVerticalHeaderLabels(["当日", "当月", "当年", "累计"])
self.project_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.project_table.verticalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.project_table.setEditTriggers(QTableWidget.NoEditTriggers) # 设置为不可编辑
# 设置表格样式
self.project_table.setStyleSheet("""
QTableWidget {
border: none;
gridline-color: #dddddd;
}
QHeaderView::section {
background-color: #f0f0f0;
padding: 4px;
border: 1px solid #cccccc;
font-weight: bold;
}
""")
self.project_layout.addWidget(self.project_table)
self.left_layout.addWidget(self.project_frame)
# 任务信息区域 - 使用QFrame包裹添加边框
self.task_frame = QFrame()
self.task_frame.setFrameShape(QFrame.StyledPanel)
self.task_frame.setLineWidth(1)
self.task_frame.setFixedHeight(180)
self.task_layout = QVBoxLayout(self.task_frame)
self.task_layout.setContentsMargins(8, 8, 8, 8)
# 任务标签
self.task_label = QLabel("任务")
self.task_label.setFont(self.normal_font)
self.task_label.setAlignment(Qt.AlignLeft)
self.task_label.setStyleSheet("font-weight: bold; color: #333333;")
self.task_layout.addWidget(self.task_label)
# 任务表格 - 使用合并单元格实现一级二级标题
self.task_table = QTableWidget(3, 4) # 3行4列一级标题行、二级标题行、数据行
self.task_table.setEditTriggers(QTableWidget.NoEditTriggers) # 设置为不可编辑
self.task_table.horizontalHeader().setVisible(False)
self.task_table.verticalHeader().setVisible(False)
self.task_table.setShowGrid(True)
# 设置列宽均等
self.task_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
# 第一行:订单量和完成量 (一级标题)
self.task_table.setSpan(0, 0, 1, 2) # 订单量跨2列
self.task_table.setSpan(0, 2, 1, 2) # 完成量跨2列
order_item = QTableWidgetItem("订单量")
order_item.setTextAlignment(Qt.AlignCenter)
order_item.setFont(self.normal_font)
self.task_table.setItem(0, 0, order_item)
completed_item = QTableWidgetItem("完成量")
completed_item.setTextAlignment(Qt.AlignCenter)
completed_item.setFont(self.normal_font)
self.task_table.setItem(0, 2, completed_item)
# 第二行:二级标题
headers = ["总生产数量", "总生产公斤", "已完成数量", "已完成公斤"]
for col, header in enumerate(headers):
item = QTableWidgetItem(header)
item.setTextAlignment(Qt.AlignCenter)
item.setFont(self.small_font)
self.task_table.setItem(1, col, item)
# 设置行高
self.task_table.setRowHeight(0, 30) # 一级标题行高
self.task_table.setRowHeight(1, 30) # 二级标题行高
self.task_table.setRowHeight(2, 30) # 数据行高
self.task_layout.addWidget(self.task_table)
# 订单行
self.order_layout = QHBoxLayout()
self.order_label = QLabel("订单")
self.order_label.setFont(QFont("微软雅黑", 12, QFont.Bold))
self.order_label.setFixedHeight(30)
self.order_label.setStyleSheet("padding: 0 5px; color: #333333;")
self.order_edit = QLineEdit()
self.order_edit.setReadOnly(True)
self.order_edit.setFont(QFont("微软雅黑", 12))
self.order_edit.setText("ORD-2025-001") # 设置默认订单号
self.order_edit.setStyleSheet("background-color: #f9f9f9; border: 1px solid #cccccc; border-radius: 3px; padding: 2px 5px;")
self.order_layout.addWidget(self.order_label)
self.order_layout.addWidget(self.order_edit)
self.task_layout.addLayout(self.order_layout)
self.left_layout.addWidget(self.task_frame)
# 上料区 - 使用QFrame包裹添加边框
self.material_frame = QFrame()
self.material_frame.setFrameShape(QFrame.StyledPanel)
self.material_frame.setLineWidth(1)
self.material_frame.setFixedHeight(300) # 增加高度以匹配图片
self.material_frame.setStyleSheet("QFrame { background-color: #f8f8f8; }")
self.material_layout = QHBoxLayout(self.material_frame)
self.material_layout.setContentsMargins(0, 0, 0, 0)
self.material_layout.setSpacing(0)
# 上料区标签
self.material_label = QLabel("上料区")
self.material_label.setFont(self.normal_font)
self.material_label.setAlignment(Qt.AlignCenter)
self.material_label.setFixedWidth(100) # 设置固定宽度
self.material_label.setStyleSheet("background-color: #e0e0e0; border-right: 1px solid #cccccc; font-weight: bold;")
self.material_layout.addWidget(self.material_label)
# 上料区内容 - 这里可以添加更多控件
self.material_content = QWidget()
self.material_content.setStyleSheet("background-color: black;") # 黑色背景适合显示相机画面
self.material_content_layout = QVBoxLayout(self.material_content)
self.material_content_layout.setContentsMargins(0, 0, 0, 0) # 移除内边距以便相机画面填满
self.material_layout.addWidget(self.material_content)
self.left_layout.addWidget(self.material_frame)
# 托盘号行 - 使用QFrame包裹添加边框
self.tray_frame = QFrame()
self.tray_frame.setFrameShape(QFrame.StyledPanel)
self.tray_frame.setLineWidth(1)
self.tray_layout = QHBoxLayout(self.tray_frame)
self.tray_layout.setContentsMargins(0, 0, 0, 0)
self.tray_layout.setSpacing(0)
self.tray_label = QLabel("托盘号")
self.tray_label.setFont(self.normal_font)
self.tray_label.setAlignment(Qt.AlignCenter)
self.tray_label.setFixedWidth(100) # 设置固定宽度
self.tray_label.setStyleSheet("background-color: #e0e0e0; border-right: 1px solid #cccccc; font-weight: bold;")
self.tray_layout.addWidget(self.tray_label)
self.tray_edit = QLineEdit()
self.tray_edit.setReadOnly(True)
self.tray_edit.setStyleSheet("border: none; padding: 5px 10px;")
self.tray_edit.setFont(QFont("微软雅黑", 12))
self.tray_layout.addWidget(self.tray_edit)
self.left_layout.addWidget(self.tray_frame)
# 下料区 - 使用QFrame包裹添加边框
self.output_frame = QFrame()
self.output_frame.setFrameShape(QFrame.StyledPanel)
self.output_frame.setLineWidth(1)
self.output_frame.setFixedHeight(200) # 增加高度以匹配图片
self.output_frame.setStyleSheet("QFrame { background-color: #f8f8f8; }")
self.output_layout = QHBoxLayout(self.output_frame)
self.output_layout.setContentsMargins(0, 0, 0, 0)
self.output_layout.setSpacing(0)
# 下料区标签
self.output_label = QLabel("下料")
self.output_label.setFont(self.normal_font)
self.output_label.setAlignment(Qt.AlignCenter)
self.output_label.setFixedWidth(100) # 设置固定宽度
self.output_label.setStyleSheet("background-color: #e0e0e0; border-right: 1px solid #cccccc; font-weight: bold;")
self.output_layout.addWidget(self.output_label)
# 下料区内容 - 这里可以添加更多控件
self.output_content = QWidget()
self.output_content.setStyleSheet("background-color: white;")
self.output_content_layout = QVBoxLayout(self.output_content)
self.output_content_layout.setContentsMargins(10, 10, 10, 10)
self.output_layout.addWidget(self.output_content)
self.left_layout.addWidget(self.output_frame)
# 产线控制区 - 使用QFrame包裹添加边框
self.control_frame = QFrame()
self.control_frame.setFrameShape(QFrame.StyledPanel)
self.control_frame.setLineWidth(1)
self.control_layout = QHBoxLayout(self.control_frame)
self.control_layout.setContentsMargins(0, 0, 0, 0)
self.control_layout.setSpacing(0)
self.control_label = QLabel("产线")
self.control_label.setFont(self.normal_font)
self.control_label.setAlignment(Qt.AlignCenter)
self.control_label.setFixedWidth(100) # 设置固定宽度
self.control_label.setStyleSheet("background-color: #e0e0e0; border-right: 1px solid #cccccc; font-weight: bold;")
self.control_layout.addWidget(self.control_label)
# 按钮容器
self.button_container = QWidget()
self.button_layout = QGridLayout(self.button_container)
self.button_layout.setContentsMargins(10, 10, 10, 10)
self.button_layout.setSpacing(10) # 增加按钮间距
# 创建按钮并设置样式
button_style = """
QPushButton {
padding: 8px 16px;
font-weight: bold;
border-radius: 4px;
}
"""
self.input_button = QPushButton("上料")
self.input_button.setFont(self.normal_font)
self.input_button.setStyleSheet(button_style + "background-color: #e3f2fd; border: 1px solid #2196f3;")
self.output_button = QPushButton("下料")
self.output_button.setFont(self.normal_font)
self.output_button.setStyleSheet(button_style + "background-color: #fff8e1; border: 1px solid #ffc107;")
self.start_button = QPushButton("开始")
self.start_button.setFont(self.normal_font)
self.start_button.setStyleSheet(button_style + "background-color: #e8f5e9; border: 1px solid #4caf50;")
self.stop_button = QPushButton("暂停")
self.stop_button.setFont(self.normal_font)
self.stop_button.setStyleSheet(button_style + "background-color: #ffebee; border: 1px solid #f44336;")
# 使用网格布局排列按钮
self.button_layout.addWidget(self.input_button, 0, 0)
self.button_layout.addWidget(self.output_button, 0, 1)
self.button_layout.addWidget(self.start_button, 0, 2)
self.button_layout.addWidget(self.stop_button, 0, 3)
self.control_layout.addWidget(self.button_container)
self.left_layout.addWidget(self.control_frame)
# 添加弹性空间,确保控件紧凑排列在顶部
self.left_layout.addStretch()
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.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 setup_table_common(self, table, hide_headers=True):
"""设置表格的通用样式和属性
Args:
table: 要设置的QTableWidget对象
hide_headers: 是否隐藏默认的表头
"""
# 设置为不可编辑
table.setEditTriggers(QAbstractItemView.NoEditTriggers)
# 隐藏默认表头
if hide_headers:
table.horizontalHeader().setVisible(False)
table.verticalHeader().setVisible(False)
# 显示网格线
table.setShowGrid(True)
# 移除外边框
table.setFrameShape(QFrame.NoFrame)
# 设置表格样式
table.setStyleSheet("""
QTableWidget {
gridline-color: #dddddd;
border: none;
background-color: white;
}
QTableWidget::item {
border: none;
padding: 3px;
}
QTableWidget::item:selected {
background-color: #e0e0ff;
color: black;
}
""")
# 允许用户调整列宽
table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
table.horizontalHeader().setStretchLastSection(True)
return table
# 添加一个通用的表头项创建方法
def create_header_item(self, text, font=None, alignment=Qt.AlignCenter, bg_color="#f8f8f8"):
"""创建表头单元格项
Args:
text: 表头文本
font: 字体默认为None使用self.normal_font
alignment: 对齐方式
bg_color: 背景色
Returns:
QTableWidgetItem: 创建的表头项
"""
item = QTableWidgetItem(text)
item.setTextAlignment(alignment)
item.setFont(font or self.normal_font)
item.setBackground(QBrush(QColor(bg_color)))
return item
def create_process_table(self):
"""创建微丝产线表格,包含上料、检验、包装部分"""
# 创建微丝产线框架
self.process_frame = QFrame()
self.process_frame.setFrameShape(QFrame.Box)
self.process_frame.setLineWidth(1)
self.process_frame.setStyleSheet("QFrame { border: 1px solid #dddddd; }")
self.process_layout = QVBoxLayout(self.process_frame)
self.process_layout.setContentsMargins(0, 0, 0, 0)
self.process_layout.setSpacing(0)
# 微丝产线标题
self.process_title = QLabel("微丝产线")
self.process_title.setFont(self.second_title_font)
self.process_title.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
# 调整行高
self.process_title.setFixedHeight(40)
self.process_title.setStyleSheet("background-color: #f8f8f8; padding: 5px; border-bottom: 1px solid #dddddd;")
self.process_layout.addWidget(self.process_title)
# 创建表格内容区域
self.process_content = QWidget()
self.process_content_layout = QVBoxLayout(self.process_content)
self.process_content_layout.setContentsMargins(0, 0, 0, 0)
self.process_content_layout.setSpacing(0)
# 创建表格 - 支持动态配置检验列数
self.inspection_columns = 3 # 默认3列可动态配置
# TODO后续从数据库中读取
self.inspection_headers = ["外观", "线径", "电阻", "硬度", "强度"] # 默认检验标题
total_columns = 2 + self.inspection_columns + 2 # 上料2列 + 检验N列 + 包装2列
self.process_table = QTableWidget(8, total_columns) # 8行1行标题区域 + 1行列标题 + 6行数据
# 应用通用表格设置
self.setup_table_common(self.process_table)
# 设置行高
self.process_table.setRowHeight(0, 30) # 标题区域行高
self.process_table.setRowHeight(1, 30) # 列标题行高
# 设置数据行的行高
for row in range(2, 8): # 工序行
self.process_table.setRowHeight(row, 35)
# 设置列宽
self.set_process_table_column_widths()
# 创建表头 - 合并单元格
self.create_process_table_headers()
# 填充表格内容
self.fill_process_table_cells()
# 添加表格到布局
self.process_content_layout.addWidget(self.process_table)
self.process_layout.addWidget(self.process_content)
def create_record_table(self):
"""创建包装记录表格"""
# 创建包装记录框架
self.record_frame = QFrame()
self.record_frame.setFrameShape(QFrame.Box)
self.record_frame.setLineWidth(1)
self.record_frame.setStyleSheet("QFrame { border: 1px solid #dddddd; }") # 移除 border-top: none;
self.record_layout = QVBoxLayout(self.record_frame)
self.record_layout.setContentsMargins(0, 0, 0, 0)
self.record_layout.setSpacing(0)
# 包装记录标题
self.record_title = QLabel("包装记录")
self.record_title.setFont(self.second_title_font)
self.record_title.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
# 调整行高
self.record_title.setFixedHeight(40)
self.record_title.setStyleSheet("background-color: #f8f8f8; padding: 5px; border-bottom: 1px solid #dddddd;")
self.record_layout.addWidget(self.record_title)
# 创建表格
self.record_table = QTableWidget(14, 7) # 14行7列序号、订单、材质、规格、托号、轴包装号、重量
# 应用通用表格设置
self.setup_table_common(self.record_table)
# 设置列标题
record_headers = ["序号", "订单", "材质", "规格", "托号", "轴包装号", "重量"]
for col, header in enumerate(record_headers):
self.record_table.setItem(0, col, self.create_header_item(header))
# 设置行高
self.record_table.setRowHeight(0, 35) # 列标题行高
self.record_table.setRowHeight(13, 35) # 合计行高
# 设置数据行的行高
for row in range(1, 13): # 记录行
self.record_table.setRowHeight(row, 35)
# 设置列宽
column_widths = [70, 220, 170, 170, 170, 170, 170] # 各列的默认宽度
for col, width in enumerate(column_widths):
self.record_table.setColumnWidth(col, width)
# 添加表格到布局
self.record_layout.addWidget(self.record_table)
# 填充表格内容
self.fill_record_table_cells()
# 添加一个通用的单元格创建方法
def create_cell_item(self, text, alignment=Qt.AlignCenter):
"""创建表格单元格项
Args:
text: 单元格文本
alignment: 对齐方式
Returns:
QTableWidgetItem: 创建的单元格项
"""
item = QTableWidgetItem(str(text))
item.setTextAlignment(alignment)
return item
def fill_process_table_cells(self):
"""填充微丝产线表格单元格"""
# 工序工程数据
process_data = ["拉丝", "退火", "检验", "包装", "入库", "出库"]
# 填充工序数据
for row in range(6):
# 设置序号
self.process_table.setItem(row + 2, 0, self.create_cell_item(row + 1))
# 设置工序工程名称
self.process_table.setItem(row + 2, 1, self.create_cell_item(process_data[row]))
# 只为前3行设置数据
if row < 3:
# 检验区域 - 动态列
inspection_data = ["合格", f"{0.5 + row * 0.1:.1f}", f"{10 + row * 5}", f"{80 + row * 5}", f"{90 + row * 2}"]
for i in range(min(self.inspection_columns, len(inspection_data))):
self.process_table.setItem(row + 2, 2 + i, self.create_cell_item(inspection_data[i]))
# 包装区域 - 贴标和称重
packaging_start_col = 2 + self.inspection_columns
self.process_table.setItem(row + 2, packaging_start_col, self.create_cell_item("已完成"))
self.process_table.setItem(row + 2, packaging_start_col + 1, self.create_cell_item(f"{50 + row * 10}"))
def fill_record_table_cells(self):
"""填充包装记录表格单元格"""
# 填充序号列
for row in range(12):
self.record_table.setItem(row + 1, 0, self.create_cell_item(row + 1))
# 填充示例数据
record_data = [
["ORD-2025-001", "不锈钢", "0.5mm", "T001", "A001", "50kg"],
["ORD-2025-001", "不锈钢", "0.6mm", "T001", "A002", "55kg"],
["ORD-2025-001", "不锈钢", "0.7mm", "T001", "A003", "60kg"],
]
# 只填充前3行
for row in range(3):
for col, value in enumerate(record_data[row]):
self.record_table.setItem(row + 1, col + 1, self.create_cell_item(value))
# 设置合计行
self.record_table.setItem(13, 0, self.create_header_item("合计"))
# 轴数
self.record_table.setItem(13, 3, self.create_header_item("轴数"))
self.record_table.setItem(13, 4, self.create_cell_item("0"))
# 重量
self.record_table.setItem(13, 5, self.create_header_item("重量"))
self.record_table.setItem(13, 6, self.create_cell_item("0.0"))
def set_inspection_columns(self, columns, headers=None):
"""设置检验列数和标题
Args:
columns: 检验列数量
headers: 检验列标题列表如果为None则使用默认标题
"""
# 保存旧的列数
old_column_count = self.process_table.columnCount()
# 清除表头行的所有项目
if old_column_count > 0:
for c_idx in range(old_column_count):
# 第0行 - 主标题
item_r0 = self.process_table.takeItem(0, c_idx)
if item_r0:
del item_r0
# 第1行 - 子标题
item_r1 = self.process_table.takeItem(1, c_idx)
if item_r1:
del item_r1
# 更新检验列数
self.inspection_columns = columns
# 更新检验标题
if headers is not None and len(headers) >= columns:
self.inspection_headers = headers
elif len(self.inspection_headers) < columns:
# 如果当前标题不足,扩展标题列表
current_len = len(self.inspection_headers)
for i in range(current_len, columns):
self.inspection_headers.append(f"检验项{i+1}")
# 计算总列数
total_columns = 2 + self.inspection_columns + 2 # 上料2列 + 检验N列 + 包装2列
self.process_table.setColumnCount(total_columns)
# 重新设置列宽
self.set_process_table_column_widths()
# 重新创建表头
self.create_process_table_headers()
# 重新填充数据
self.fill_process_table_cells()
def create_process_table_headers(self):
"""创建微丝产线表格的表头,实现合并单元格"""
# 第一行:上料、检验、包装标题区域
# 上料区域2列
self.process_table.setSpan(0, 0, 1, 2)
self.process_table.setItem(0, 0, self.create_header_item("上料"))
# 检验区域(动态列数)
self.process_table.setSpan(0, 2, 1, self.inspection_columns)
self.process_table.setItem(0, 2, self.create_header_item("检验"))
# 包装区域2列
packaging_start_col = 2 + self.inspection_columns
self.process_table.setSpan(0, packaging_start_col, 1, 2)
self.process_table.setItem(0, packaging_start_col, self.create_header_item("包装"))
# 第二行:列标题
# 上料区域列标题
material_headers = ["序号", "工序工程"]
for col, header in enumerate(material_headers):
self.process_table.setItem(1, col, self.create_header_item(header))
# 检验区域列标题 - 可动态配置
for i in range(self.inspection_columns):
header_text = ""
if i < len(self.inspection_headers):
header_text = self.inspection_headers[i]
else:
header_text = f"检验项{i+1}" # 如果没有定义足够的标题,使用默认标题
self.process_table.setItem(1, 2 + i, self.create_header_item(header_text))
# 包装区域列标题
packaging_headers = ["贴标", "称重"]
for i, header in enumerate(packaging_headers):
self.process_table.setItem(1, packaging_start_col + i, self.create_header_item(header))
def set_process_table_column_widths(self):
"""设置微丝产线表格的列宽 - 支持动态配置检验列"""
# 上料区域列宽
self.process_table.setColumnWidth(0, 70) # 序号
self.process_table.setColumnWidth(1, 190) # 工序工程
# 检验区域列宽
for i in range(self.inspection_columns):
self.process_table.setColumnWidth(2 + i, 140) # 检验列
# 包装区域列宽
packaging_start_col = 2 + self.inspection_columns
self.process_table.setColumnWidth(packaging_start_col, 140) # 贴标
self.process_table.setColumnWidth(packaging_start_col + 1, 140) # 称重
def create_process_table_headers(self):
"""创建微丝产线表格的表头,实现合并单元格"""
# 第一行:上料、检验、包装标题区域
# 上料区域2列
self.process_table.setSpan(0, 0, 1, 2)
self.process_table.setItem(0, 0, self.create_header_item("上料"))
# 检验区域(动态列数)
self.process_table.setSpan(0, 2, 1, self.inspection_columns)
self.process_table.setItem(0, 2, self.create_header_item("检验"))
# 包装区域2列
packaging_start_col = 2 + self.inspection_columns
self.process_table.setSpan(0, packaging_start_col, 1, 2)
self.process_table.setItem(0, packaging_start_col, self.create_header_item("包装"))
# 第二行:列标题
# 上料区域列标题
material_headers = ["序号", "工序工程"]
for col, header in enumerate(material_headers):
self.process_table.setItem(1, col, self.create_header_item(header))
# 检验区域列标题 - 可动态配置
for i in range(self.inspection_columns):
header_text = ""
if i < len(self.inspection_headers):
header_text = self.inspection_headers[i]
else:
header_text = f"检验项{i+1}" # 如果没有定义足够的标题,使用默认标题
self.process_table.setItem(1, 2 + i, self.create_header_item(header_text))
# 包装区域列标题
packaging_headers = ["贴标", "称重"]
for i, header in enumerate(packaging_headers):
self.process_table.setItem(1, packaging_start_col + i, self.create_header_item(header))

369
ui/settings_ui.py Normal file
View File

@ -0,0 +1,369 @@
from PySide6.QtWidgets import (
QWidget, QLabel, QLineEdit, QPushButton, QComboBox, QGridLayout, QHBoxLayout, QVBoxLayout,
QTabWidget, QFrame, QFormLayout, QGroupBox, QRadioButton, QSpacerItem, QSizePolicy,
QTableWidget, QTableWidgetItem, QHeaderView, QSlider
)
from PySide6.QtGui import QFont
from PySide6.QtCore import Qt
class SettingsUI(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
def init_ui(self):
# 设置字体
self.title_font = QFont("微软雅黑", 14, QFont.Bold)
self.normal_font = QFont("微软雅黑", 10)
self.small_font = QFont("微软雅黑", 9)
# 创建主布局
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(10, 10, 10, 10)
# 创建标题
self.title_label = QLabel("系统设置")
self.title_label.setFont(self.title_font)
self.title_label.setAlignment(Qt.AlignCenter)
self.title_label.setStyleSheet("color: #1a237e; margin-bottom: 10px;")
self.main_layout.addWidget(self.title_label)
# 创建选项卡部件
self.tab_widget = QTabWidget()
self.tab_widget.setFont(self.normal_font)
self.main_layout.addWidget(self.tab_widget)
# 创建各个选项卡
self.create_camera_tab()
self.create_database_tab()
self.create_plc_tab()
self.create_push_tab()
self.create_auth_tab()
self.create_user_tab()
self.create_param_tab()
def create_camera_tab(self):
# 相机设置选项卡
self.camera_tab = QWidget()
self.camera_layout = QVBoxLayout(self.camera_tab)
self.camera_layout.setContentsMargins(20, 20, 20, 20)
# 相机选择区域
self.camera_select_group = QGroupBox("相机选择")
self.camera_select_group.setFont(self.normal_font)
self.camera_select_layout = QHBoxLayout()
self.camera_label = QLabel("相机设备:")
self.camera_label.setFont(self.normal_font)
self.camera_combo = QComboBox()
self.camera_combo.setFont(self.normal_font)
self.camera_combo.setMinimumWidth(300)
self.refresh_button = QPushButton("刷新设备")
self.refresh_button.setFont(self.normal_font)
self.camera_select_layout.addWidget(self.camera_label)
self.camera_select_layout.addWidget(self.camera_combo)
self.camera_select_layout.addWidget(self.refresh_button)
self.camera_select_layout.addStretch(1)
self.camera_select_group.setLayout(self.camera_select_layout)
self.camera_layout.addWidget(self.camera_select_group)
# 相机控制区域
self.camera_control_group = QGroupBox("相机控制")
self.camera_control_group.setFont(self.normal_font)
self.camera_control_layout = QHBoxLayout()
self.open_button = QPushButton("打开相机")
self.open_button.setFont(self.normal_font)
self.open_button.setFixedWidth(120)
self.close_button = QPushButton("关闭相机")
self.close_button.setFont(self.normal_font)
self.close_button.setFixedWidth(120)
self.test_button = QPushButton("测试预览")
self.test_button.setFont(self.normal_font)
self.test_button.setFixedWidth(120)
self.camera_control_layout.addWidget(self.open_button)
self.camera_control_layout.addWidget(self.close_button)
self.camera_control_layout.addWidget(self.test_button)
self.camera_control_layout.addStretch(1)
self.camera_control_group.setLayout(self.camera_control_layout)
self.camera_layout.addWidget(self.camera_control_group)
# 相机参数设置区域
self.camera_params_group = QGroupBox("相机参数")
self.camera_params_group.setFont(self.normal_font)
self.camera_params_layout = QGridLayout()
self.camera_params_layout.setColumnStretch(1, 1) # 让滑块列占据更多空间
# 曝光时间
self.exposure_label = QLabel("曝光时间:")
self.exposure_label.setFont(self.normal_font)
self.exposure_slider = QSlider(Qt.Horizontal)
self.exposure_slider.setMinimum(1000)
self.exposure_slider.setMaximum(50000)
self.exposure_slider.setValue(20000)
self.exposure_slider.setTickPosition(QSlider.TicksBelow)
self.exposure_slider.setTickInterval(5000)
self.exposure_value = QLabel("20000 μs")
self.exposure_value.setFont(self.normal_font)
self.exposure_value.setMinimumWidth(80)
# 增益
self.gain_label = QLabel("增益:")
self.gain_label.setFont(self.normal_font)
self.gain_slider = QSlider(Qt.Horizontal)
self.gain_slider.setMinimum(0)
self.gain_slider.setMaximum(100)
self.gain_slider.setValue(10)
self.gain_slider.setTickPosition(QSlider.TicksBelow)
self.gain_slider.setTickInterval(10)
self.gain_value = QLabel("10")
self.gain_value.setFont(self.normal_font)
self.gain_value.setMinimumWidth(80)
# 帧率
self.framerate_label = QLabel("帧率:")
self.framerate_label.setFont(self.normal_font)
self.framerate_slider = QSlider(Qt.Horizontal)
self.framerate_slider.setMinimum(1)
self.framerate_slider.setMaximum(30)
self.framerate_slider.setValue(15)
self.framerate_slider.setTickPosition(QSlider.TicksBelow)
self.framerate_slider.setTickInterval(5)
self.framerate_value = QLabel("15 fps")
self.framerate_value.setFont(self.normal_font)
self.framerate_value.setMinimumWidth(80)
# 添加到布局
self.camera_params_layout.addWidget(self.exposure_label, 0, 0)
self.camera_params_layout.addWidget(self.exposure_slider, 0, 1)
self.camera_params_layout.addWidget(self.exposure_value, 0, 2)
self.camera_params_layout.addWidget(self.gain_label, 1, 0)
self.camera_params_layout.addWidget(self.gain_slider, 1, 1)
self.camera_params_layout.addWidget(self.gain_value, 1, 2)
self.camera_params_layout.addWidget(self.framerate_label, 2, 0)
self.camera_params_layout.addWidget(self.framerate_slider, 2, 1)
self.camera_params_layout.addWidget(self.framerate_value, 2, 2)
self.camera_params_group.setLayout(self.camera_params_layout)
self.camera_layout.addWidget(self.camera_params_group)
# 相机设置按钮
self.camera_buttons_layout = QHBoxLayout()
self.get_params_button = QPushButton("获取参数")
self.get_params_button.setFont(self.normal_font)
self.get_params_button.setFixedWidth(120)
self.set_params_button = QPushButton("设置参数")
self.set_params_button.setFont(self.normal_font)
self.set_params_button.setFixedWidth(120)
self.save_camera_button = QPushButton("保存设置")
self.save_camera_button.setFont(self.normal_font)
self.save_camera_button.setFixedWidth(120)
self.camera_buttons_layout.addStretch(1)
self.camera_buttons_layout.addWidget(self.get_params_button)
self.camera_buttons_layout.addWidget(self.set_params_button)
self.camera_buttons_layout.addWidget(self.save_camera_button)
self.camera_layout.addLayout(self.camera_buttons_layout)
# 预览区域
self.preview_group = QGroupBox("相机预览")
self.preview_group.setFont(self.normal_font)
self.preview_layout = QVBoxLayout()
self.preview_frame = QFrame()
self.preview_frame.setFrameShape(QFrame.Box)
self.preview_frame.setLineWidth(1)
self.preview_frame.setMinimumHeight(200)
self.preview_frame.setStyleSheet("background-color: black;")
self.preview_layout.addWidget(self.preview_frame)
self.preview_group.setLayout(self.preview_layout)
self.camera_layout.addWidget(self.preview_group)
# 添加弹性空间
self.camera_layout.addStretch(1)
self.tab_widget.addTab(self.camera_tab, "相机设置")
def create_database_tab(self):
# 数据源设置选项卡
self.database_tab = QWidget()
self.database_layout = QVBoxLayout(self.database_tab)
self.database_layout.setContentsMargins(20, 20, 20, 20)
# 数据库类型选择
self.db_type_group = QGroupBox("数据库类型")
self.db_type_group.setFont(self.normal_font)
self.db_type_layout = QHBoxLayout()
self.sqlite_radio = QRadioButton("SQLite")
self.sqlite_radio.setFont(self.normal_font)
self.sqlite_radio.setChecked(True)
self.pgsql_radio = QRadioButton("PostgreSQL")
self.pgsql_radio.setFont(self.normal_font)
self.mysql_radio = QRadioButton("MySQL")
self.mysql_radio.setFont(self.normal_font)
self.db_type_layout.addWidget(self.sqlite_radio)
self.db_type_layout.addWidget(self.pgsql_radio)
self.db_type_layout.addWidget(self.mysql_radio)
self.db_type_layout.addStretch(1)
self.db_type_group.setLayout(self.db_type_layout)
self.database_layout.addWidget(self.db_type_group)
# 数据库连接设置
self.db_conn_group = QGroupBox("连接设置")
self.db_conn_group.setFont(self.normal_font)
self.db_conn_layout = QFormLayout()
self.host_label = QLabel("主机:")
self.host_label.setFont(self.normal_font)
self.host_input = QLineEdit()
self.host_input.setFont(self.normal_font)
self.host_input.setPlaceholderText("localhost")
self.user_label = QLabel("用户:")
self.user_label.setFont(self.normal_font)
self.user_input = QLineEdit()
self.user_input.setFont(self.normal_font)
self.password_label = QLabel("密码:")
self.password_label.setFont(self.normal_font)
self.password_input = QLineEdit()
self.password_input.setFont(self.normal_font)
self.password_input.setEchoMode(QLineEdit.Password)
self.database_label = QLabel("数据库:")
self.database_label.setFont(self.normal_font)
self.database_input = QLineEdit()
self.database_input.setFont(self.normal_font)
self.database_input.setPlaceholderText("db/jtDB.db")
self.port_label = QLabel("端口:")
self.port_label.setFont(self.normal_font)
self.port_input = QLineEdit()
self.port_input.setFont(self.normal_font)
self.desc_label = QLabel("说明:")
self.desc_label.setFont(self.normal_font)
self.desc_input = QLineEdit()
self.desc_input.setFont(self.normal_font)
self.db_conn_layout.addRow(self.host_label, self.host_input)
self.db_conn_layout.addRow(self.user_label, self.user_input)
self.db_conn_layout.addRow(self.password_label, self.password_input)
self.db_conn_layout.addRow(self.database_label, self.database_input)
self.db_conn_layout.addRow(self.port_label, self.port_input)
self.db_conn_layout.addRow(self.desc_label, self.desc_input)
self.db_conn_group.setLayout(self.db_conn_layout)
self.database_layout.addWidget(self.db_conn_group)
# 按钮区域
self.db_buttons_layout = QHBoxLayout()
self.test_conn_button = QPushButton("测试连接")
self.test_conn_button.setFont(self.normal_font)
self.test_conn_button.setFixedWidth(120)
self.save_db_button = QPushButton("保存设置")
self.save_db_button.setFont(self.normal_font)
self.save_db_button.setFixedWidth(120)
self.db_buttons_layout.addStretch(1)
self.db_buttons_layout.addWidget(self.test_conn_button)
self.db_buttons_layout.addSpacing(20)
self.db_buttons_layout.addWidget(self.save_db_button)
self.database_layout.addLayout(self.db_buttons_layout)
self.database_layout.addStretch(1)
self.tab_widget.addTab(self.database_tab, "数据源设置")
def create_plc_tab(self):
# PLC设置选项卡
self.plc_tab = QWidget()
self.plc_layout = QVBoxLayout(self.plc_tab)
self.plc_layout.setContentsMargins(20, 20, 20, 20)
# 占位标签
self.plc_placeholder = QLabel("PLC设置待实现")
self.plc_placeholder.setFont(self.normal_font)
self.plc_placeholder.setAlignment(Qt.AlignCenter)
self.plc_layout.addWidget(self.plc_placeholder)
self.plc_layout.addStretch(1)
self.tab_widget.addTab(self.plc_tab, "PLC设置")
def create_push_tab(self):
# 推送设置选项卡
self.push_tab = QWidget()
self.push_layout = QVBoxLayout(self.push_tab)
self.push_layout.setContentsMargins(20, 20, 20, 20)
# 占位标签
self.push_placeholder = QLabel("推送设置(待实现)")
self.push_placeholder.setFont(self.normal_font)
self.push_placeholder.setAlignment(Qt.AlignCenter)
self.push_layout.addWidget(self.push_placeholder)
self.push_layout.addStretch(1)
self.tab_widget.addTab(self.push_tab, "推送设置")
def create_auth_tab(self):
# 授权设置选项卡
self.auth_tab = QWidget()
self.auth_layout = QVBoxLayout(self.auth_tab)
self.auth_layout.setContentsMargins(20, 20, 20, 20)
# 占位标签
self.auth_placeholder = QLabel("授权设置(待实现)")
self.auth_placeholder.setFont(self.normal_font)
self.auth_placeholder.setAlignment(Qt.AlignCenter)
self.auth_layout.addWidget(self.auth_placeholder)
self.auth_layout.addStretch(1)
self.tab_widget.addTab(self.auth_tab, "授权设置")
def create_user_tab(self):
# 用户设置选项卡
self.user_tab = QWidget()
self.user_layout = QVBoxLayout(self.user_tab)
self.user_layout.setContentsMargins(20, 20, 20, 20)
# 占位标签
self.user_placeholder = QLabel("用户设置(待实现)")
self.user_placeholder.setFont(self.normal_font)
self.user_placeholder.setAlignment(Qt.AlignCenter)
self.user_layout.addWidget(self.user_placeholder)
self.user_layout.addStretch(1)
self.tab_widget.addTab(self.user_tab, "用户设置")
def create_param_tab(self):
# 参数配置选项卡
self.param_tab = QWidget()
self.param_layout = QVBoxLayout(self.param_tab)
self.param_layout.setContentsMargins(20, 20, 20, 20)
# 占位标签
self.param_placeholder = QLabel("参数配置(待实现)")
self.param_placeholder.setFont(self.normal_font)
self.param_placeholder.setAlignment(Qt.AlignCenter)
self.param_layout.addWidget(self.param_placeholder)
self.param_layout.addStretch(1)
self.tab_widget.addTab(self.param_tab, "参数配置")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

130
utils/config_loader.py Normal file
View File

@ -0,0 +1,130 @@
import os
import json
import logging
class ConfigLoader:
"""配置加载器,用于加载和管理应用配置"""
_instance = None
@classmethod
def get_instance(cls):
"""获取单例实例"""
if cls._instance is None:
cls._instance = ConfigLoader()
return cls._instance
def __init__(self):
"""初始化配置加载器"""
self.config = {}
self.config_file = os.path.join('config', 'app_config.json')
# 创建配置目录(如果不存在)
os.makedirs('config', exist_ok=True)
# 加载配置
self.load_config()
def load_config(self):
"""加载配置文件"""
try:
if os.path.exists(self.config_file):
with open(self.config_file, 'r', encoding='utf-8') as f:
self.config = json.load(f)
logging.info(f"已加载配置文件: {self.config_file}")
else:
# 创建默认配置
self.config = {
"app": {
"name": "腾智微丝产线包装系统",
"version": "1.0.0",
"features": {
"enable_serial_ports": False,
"enable_keyboard_listener": False
}
},
"database": {
"type": "sqlite",
"path": "db/jtDB.db",
"host": "",
"port": "",
"user": "",
"password": "",
"name": ""
}
}
# 保存默认配置
self.save_config()
logging.info(f"已创建默认配置文件: {self.config_file}")
except Exception as e:
logging.error(f"加载配置文件失败: {e}")
# 使用默认配置
self.config = {
"app": {
"name": "腾智微丝产线包装系统",
"version": "1.0.0",
"features": {
"enable_serial_ports": False,
"enable_keyboard_listener": False
}
}
}
def save_config(self):
"""保存配置到文件"""
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, indent=4, ensure_ascii=False)
logging.info(f"已保存配置文件: {self.config_file}")
return True
except Exception as e:
logging.error(f"保存配置文件失败: {e}")
return False
def get_value(self, key_path, default=None):
"""
获取配置值
Args:
key_path: 配置键路径例如 "app.features.enable_serial_ports"
default: 默认值如果配置项不存在则返回此值
Returns:
配置值或默认值
"""
keys = key_path.split('.')
value = self.config
try:
for key in keys:
value = value[key]
return value
except (KeyError, TypeError):
return default
def set_value(self, key_path, value):
"""
设置配置值
Args:
key_path: 配置键路径例如 "app.features.enable_serial_ports"
value: 要设置的值
Returns:
bool: 是否设置成功
"""
keys = key_path.split('.')
config = self.config
# 遍历路径中的所有键,除了最后一个
for key in keys[:-1]:
if key not in config:
config[key] = {}
config = config[key]
# 设置最后一个键的值
config[keys[-1]] = value
# 保存配置
return self.save_config()

46
utils/init_db.py Normal file
View File

@ -0,0 +1,46 @@
from sql_utils import SQLUtils
import datetime
def init_database():
db = SQLUtils('sqlite', database='db/jtDB.db')
# 创建用户表
create_table_sql = """
CREATE TABLE IF NOT EXISTS user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
create_time TIMESTAMP NOT NULL,
create_by VARCHAR(50) NOT NULL,
update_time TIMESTAMP,
update_by VARCHAR(50),
is_deleted BOOLEAN DEFAULT FALSE
);
"""
# 获取当前时间
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 插入系统用户
insert_system_user_sql = """
INSERT OR IGNORE INTO user (
username, password, create_time, create_by, is_deleted
) VALUES (
'system', '123456', ?, 'system', FALSE
);
"""
try:
db.begin_transaction()
db.execute_query(create_table_sql)
db.execute_query(insert_system_user_sql, (current_time,))
db.commit_transaction()
print("Database initialized successfully!")
except Exception as e:
db.rollback_transaction()
print(f"Error initializing database: {str(e)}")
finally:
db.close()
if __name__ == "__main__":
init_database()

19
utils/menu_translator.py Normal file
View File

@ -0,0 +1,19 @@
from PySide6.QtCore import QObject, QEvent
import logging
class MenuTranslator(QObject):
"""菜单翻译器,用于翻译右键菜单等动态创建的菜单"""
@staticmethod
def install_menu_translator(app):
"""安装菜单翻译器"""
translator = MenuTranslator()
app.installEventFilter(translator)
logging.info("已安装菜单翻译器")
def eventFilter(self, obj, event):
"""事件过滤器,用于捕获菜单显示事件并翻译菜单项"""
if event.type() == QEvent.ContextMenu:
# 这里可以添加右键菜单翻译逻辑
pass
return False

78
utils/sql_utils.py Normal file
View File

@ -0,0 +1,78 @@
import sys
try:
import psycopg2
except ImportError:
psycopg2 = None
try:
import sqlite3
except ImportError:
sqlite3 = None
class SQLUtils:
def __init__(self, db_type, **kwargs):
self.db_type = db_type.lower()
self.conn = None
self.cursor = None
self.kwargs = kwargs
self.connect()
def connect(self):
if self.db_type == 'pgsql' or self.db_type == 'postgresql':
if not psycopg2:
raise ImportError('psycopg2 is not installed')
self.conn = psycopg2.connect(**self.kwargs)
elif self.db_type == 'sqlite' or self.db_type == 'sqlite3':
if not sqlite3:
raise ImportError('sqlite3 is not installed')
self.conn = sqlite3.connect(self.kwargs.get('database', ':memory:'))
else:
raise ValueError(f'Unsupported db_type: {self.db_type}')
self.cursor = self.conn.cursor()
def execute_query(self, sql, params=None):
if params is None:
params = ()
self.cursor.execute(sql, params)
self.conn.commit()
def execute_update(self, sql, params=None):
try:
self.cursor.execute(sql,params)
self.conn.commit()
except Exception as e:
self.conn.rollback()
raise e
def begin_transaction(self) -> None:
"""开始事务"""
if self.db_type in ['sqlite', 'sqlite3']:
self.execute_query('BEGIN TRANSACTION')
else:
self.conn.autocommit = False
def commit_transaction(self) -> None:
"""提交事务"""
self.conn.commit()
if self.db_type not in ['sqlite', 'sqlite3']:
self.conn.autocommit = True
def rollback_transaction(self) -> None:
"""回滚事务"""
self.conn.rollback()
if self.db_type not in ['sqlite', 'sqlite3']:
self.conn.autocommit = True
def fetchone(self):
return self.cursor.fetchone()
def fetchall(self):
return self.cursor.fetchall()
def close(self):
if self.cursor:
self.cursor.close()
if self.conn:
self.conn.close()

262
utils/version_manager.py Normal file
View File

@ -0,0 +1,262 @@
import os
import json
import logging
import requests
import shutil
import tempfile
import zipfile
from pathlib import Path
import sys
import datetime
from PySide6.QtWidgets import QMessageBox, QProgressDialog
from PySide6.QtCore import Qt
# 版本检查服务器地址
VERSION_CHECK_URL = "http://your-server.com/api/version_check"
UPDATE_DOWNLOAD_URL = "http://your-server.com/api/download_update"
class VersionManager:
"""版本管理工具类"""
VERSION = "1.0.0"
@staticmethod
def get_current_version():
"""获取当前版本号"""
return VersionManager.VERSION
@staticmethod
def check_for_updates(parent_widget=None):
"""
检查是否有更新可用
Args:
parent_widget: 父窗口用于显示消息框
Returns:
dict: 包含更新信息的字典如果没有更新或检查失败则为None
"""
try:
current_version = VersionManager.get_current_version()
logging.info(f"正在检查更新,当前版本: {current_version}")
# 从服务器获取最新版本信息
response = requests.get(VERSION_CHECK_URL, params={"current_version": current_version}, timeout=5)
response.raise_for_status() # 如果HTTP请求返回了不成功的状态码将引发HTTPError异常
data = response.json()
if not data.get("success"):
logging.warning(f"版本检查失败: {data.get('message')}")
return None
if data.get("update_available"):
logging.info(f"发现新版本: {data.get('latest_version')}")
return {
"latest_version": data.get("latest_version"),
"update_url": data.get("update_url"),
"release_notes": data.get("release_notes"),
"is_mandatory": data.get("is_mandatory", False)
}
else:
logging.info("当前已是最新版本")
return None
except requests.RequestException as e:
logging.error(f"检查更新时网络错误: {e}")
if parent_widget:
QMessageBox.warning(parent_widget, "更新检查失败", f"无法连接到更新服务器: {e}")
return None
except Exception as e:
logging.error(f"检查更新时发生错误: {e}")
if parent_widget:
QMessageBox.warning(parent_widget, "更新检查失败", f"检查更新时发生错误: {e}")
return None
@staticmethod
def download_and_install_update(update_info, parent_widget=None):
"""
下载并安装更新
Args:
update_info: 包含更新信息的字典
parent_widget: 父窗口用于显示进度对话框
Returns:
bool: 更新是否成功
"""
if not update_info or not update_info.get("update_url"):
logging.error("无效的更新信息")
return False
update_url = update_info.get("update_url")
version = update_info.get("latest_version")
try:
# 创建进度对话框
progress = None
if parent_widget:
progress = QProgressDialog("正在下载更新...", "取消", 0, 100, parent_widget)
progress.setWindowTitle(f"下载更新 v{version}")
progress.setWindowModality(Qt.WindowModal)
progress.setAutoClose(True)
progress.setMinimumDuration(0)
progress.setValue(0)
progress.show()
# 下载更新文件
logging.info(f"开始下载更新: {update_url}")
# 使用临时目录
with tempfile.TemporaryDirectory() as temp_dir:
# 下载文件
update_file = os.path.join(temp_dir, "update.zip")
# 使用流式下载以显示进度
with requests.get(update_url, stream=True) as r:
r.raise_for_status()
total_size = int(r.headers.get('content-length', 0))
downloaded = 0
with open(update_file, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
if progress and progress.wasCanceled():
logging.info("用户取消了下载")
return False
if chunk:
f.write(chunk)
downloaded += len(chunk)
if total_size and progress:
percent = int((downloaded / total_size) * 100)
progress.setValue(percent)
if progress:
progress.setValue(100)
progress.setLabelText("正在安装更新...")
# 解压更新文件
logging.info("正在解压更新文件")
extract_dir = os.path.join(temp_dir, "extracted")
os.makedirs(extract_dir, exist_ok=True)
with zipfile.ZipFile(update_file, 'r') as zip_ref:
zip_ref.extractall(extract_dir)
# 获取应用程序根目录
app_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 备份当前版本
current_version = VersionManager.get_current_version()
backup_dir = os.path.join(app_dir, f"backup_v{current_version}")
logging.info(f"备份当前版本到: {backup_dir}")
if os.path.exists(backup_dir):
shutil.rmtree(backup_dir)
os.makedirs(backup_dir, exist_ok=True)
# 复制当前文件到备份目录
for item in os.listdir(app_dir):
if not item.startswith("backup_v"):
s = os.path.join(app_dir, item)
d = os.path.join(backup_dir, item)
if os.path.isdir(s):
shutil.copytree(s, d, dirs_exist_ok=True)
else:
shutil.copy2(s, d)
# 复制更新文件到应用目录
logging.info("正在安装更新文件")
for item in os.listdir(extract_dir):
s = os.path.join(extract_dir, item)
d = os.path.join(app_dir, item)
if os.path.isdir(s):
shutil.copytree(s, d, dirs_exist_ok=True)
else:
shutil.copy2(s, d)
# 更新版本文件
version_file = os.path.join(app_dir, "version.json")
with open(version_file, 'w') as f:
json.dump({
"version": version,
"updated_at": str(datetime.datetime.now()),
"release_notes": update_info.get("release_notes", "")
}, f, indent=4)
logging.info(f"更新完成,新版本: {version}")
if parent_widget:
QMessageBox.information(parent_widget, "更新完成",
f"已成功更新到版本 {version}\n应用程序需要重启才能应用更新。")
# 提示用户重启应用
if parent_widget and QMessageBox.question(
parent_widget, "重启应用",
"需要重启应用程序以完成更新。是否现在重启?",
QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
logging.info("用户选择重启应用")
# 重启应用
python = sys.executable
os.execl(python, python, *sys.argv)
return True
except Exception as e:
logging.error(f"下载或安装更新时发生错误: {e}", exc_info=True)
if parent_widget:
QMessageBox.critical(parent_widget, "更新失败", f"下载或安装更新时发生错误: {e}")
return False
finally:
if progress:
progress.close()
@staticmethod
def check_and_prompt_update(parent_widget=None):
"""检查更新并提示用户"""
# 这里只是一个示例,实际应该连接到服务器检查更新
logging.info(f"检查更新,当前版本: {VersionManager.VERSION}")
return False # 返回是否有更新
@staticmethod
def check_and_prompt_update(parent_widget):
"""
检查更新并提示用户
Args:
parent_widget: 父窗口
Returns:
bool: 是否有可用更新
"""
update_info = VersionManager.check_for_updates(parent_widget)
if not update_info:
return False
# 获取更新信息
latest_version = update_info.get("latest_version", "未知")
release_notes = update_info.get("release_notes", "无更新说明")
is_mandatory = update_info.get("is_mandatory", False)
message = f"发现新版本: {latest_version}\n\n"
message += "更新内容:\n"
message += release_notes
if is_mandatory:
message += "\n\n这是一个必须安装的更新。"
result = QMessageBox.information(parent_widget, "发现必要更新", message,
QMessageBox.Ok | QMessageBox.Cancel)
if result == QMessageBox.Ok:
return VersionManager.download_and_install_update(update_info, parent_widget)
else:
# 如果是必要更新但用户取消,则退出应用
if parent_widget:
QMessageBox.critical(parent_widget, "更新取消",
"这是一个必要更新,应用程序将退出。")
sys.exit(0)
else:
message += "\n\n是否现在更新?"
result = QMessageBox.question(parent_widget, "发现新版本", message,
QMessageBox.Yes | QMessageBox.No)
if result == QMessageBox.Yes:
return VersionManager.download_and_install_update(update_info, parent_widget)
return False

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,137 @@
import sys
import os
import logging
from ctypes import *
# 确定使用哪个UI框架
try:
from PySide6.QtWidgets import QWidget, QVBoxLayout, QFrame
from PySide6.QtCore import Qt, Signal
except ImportError:
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QFrame
from PyQt5.QtCore import Qt, pyqtSignal as Signal
# 添加相机模块路径
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "camera"))
# 导入相机相关模块
from camera.MvCameraControl_class import MvCamera
from camera.CamOperation_class import CameraOperation
from camera.MvErrorDefine_const import *
from camera.CameraParams_header import *
# 导入相机管理器
from widgets.camera_manager import CameraManager
class CameraDisplayWidget(QWidget):
"""相机显示组件,用于在主窗口显示相机图像"""
# 状态信号
signal_camera_status = Signal(bool, str) # 相机状态信号 (是否连接, 状态消息)
def __init__(self, parent=None):
super().__init__(parent)
# 获取相机管理器实例
self.camera_manager = CameraManager.get_instance()
# 初始化UI
self.init_ui()
# 设置显示事件处理
self.showEvent = self.on_show_event
# 设置大小变化事件处理
self.resizeEvent = self.on_resize_event
def init_ui(self):
"""初始化UI - 只包含相机显示框架"""
# 创建布局
layout = QVBoxLayout()
# 移除所有边距和间距
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# 创建相机画面显示框架
self.frame = QFrame()
# 去掉边框
self.frame.setFrameShape(QFrame.NoFrame)
# 设置黑色背景
self.frame.setStyleSheet("background-color: #000000;")
# 添加到布局
layout.addWidget(self.frame)
# 设置布局
self.setLayout(layout)
def start_display(self):
"""开始显示相机图像"""
if not self.camera_manager.isOpen:
logging.error("相机未打开,无法开始显示")
self.signal_camera_status.emit(False, "相机未打开")
return False
try:
# 获取窗口句柄
try:
# PySide6
window_id = int(self.frame.winId())
except:
try:
# PyQt5
window_id = self.frame.winId().__int__()
except:
# 其他情况
window_id = int(self.frame.winId())
# 开始取图
success = self.camera_manager.start_grabbing(window_id)
if success:
logging.info("相机画面显示开始")
self.signal_camera_status.emit(True, "")
else:
logging.error("开始显示相机画面失败")
self.signal_camera_status.emit(False, "开始显示失败")
return success
except Exception as e:
logging.error(f"开始显示时发生异常: {str(e)}")
self.signal_camera_status.emit(False, str(e))
return False
def stop_display(self):
"""停止显示相机图像"""
if not self.camera_manager.isGrabbing:
return True
try:
success = self.camera_manager.stop_grabbing()
if not success:
logging.error("停止显示失败")
return success
except Exception as e:
logging.error(f"停止显示时发生异常: {str(e)}")
return False
def on_show_event(self, event):
"""当组件显示时检查相机状态并自动开始显示"""
if self.camera_manager.isOpen and not self.camera_manager.isGrabbing:
self.start_display()
super().showEvent(event)
def on_resize_event(self, event):
"""处理大小变化事件,确保相机画面适配上料区"""
super().resizeEvent(event)
if self.camera_manager.isGrabbing:
# 停止当前显示
self.stop_display()
# 使用新尺寸重新开始显示
self.start_display()
def closeEvent(self, event):
"""窗口关闭事件"""
self.stop_display()
super().closeEvent(event)

354
widgets/camera_manager.py Normal file
View File

@ -0,0 +1,354 @@
import sys
import os
import logging
from ctypes import *
# 添加相机模块路径
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "camera"))
# 导入相机相关模块
from camera.MvCameraControl_class import MvCamera
from camera.CamOperation_class import CameraOperation
from camera.MvErrorDefine_const import *
from camera.CameraParams_header import *
from camera.CameraParams_const import *
class CameraManager:
"""相机管理器单例类,确保整个应用中只有一个相机实例"""
_instance = None
_initialized = False
@staticmethod
def get_instance():
"""获取相机管理器实例"""
if CameraManager._instance is None:
CameraManager._instance = CameraManager()
return CameraManager._instance
def __init__(self):
"""初始化相机管理器"""
if CameraManager._instance is not None:
raise Exception("相机管理器是单例类请使用get_instance()方法获取实例")
else:
CameraManager._instance = self
# 初始化变量
self.deviceList = None
self.cam = MvCamera()
self.nSelCamIndex = -1
self.obj_cam_operation = None
self.isOpen = False
self.isGrabbing = False
# 初始化SDK (只在第一次时初始化)
if not CameraManager._initialized:
MvCamera.MV_CC_Initialize()
CameraManager._initialized = True
logging.info("相机SDK已初始化")
def enum_devices(self):
"""枚举相机设备"""
try:
# 确保先关闭任何已打开的相机
if self.isOpen:
self.close_device()
# 枚举设备
self.deviceList = MV_CC_DEVICE_INFO_LIST()
n_layer_type = (MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE
| MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE)
ret = MvCamera.MV_CC_EnumDevices(n_layer_type, self.deviceList)
if ret != 0:
error_msg = f"枚举设备失败! 错误码: 0x{ret:x}"
logging.error(error_msg)
return None
if self.deviceList.nDeviceNum == 0:
logging.info("未找到相机设备")
return []
logging.info(f"找到 {self.deviceList.nDeviceNum} 个相机设备")
# 构造设备信息列表
devices_info = []
for i in range(0, self.deviceList.nDeviceNum):
mvcc_dev_info = cast(self.deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE:
# GigE相机
user_defined_name = ""
for per in mvcc_dev_info.SpecialInfo.stGigEInfo.chUserDefinedName:
if per == 0:
break
user_defined_name = user_defined_name + chr(per)
model_name = ""
for per in mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName:
if per == 0:
break
model_name = model_name + chr(per)
nip1 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24)
nip2 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16)
nip3 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8)
nip4 = (mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff)
ip = f"{nip1}.{nip2}.{nip3}.{nip4}"
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})"
}
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)
model_name = ""
for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chModelName:
if per == 0:
break
model_name = model_name + chr(per)
device_info = {
"index": i,
"type": "USB",
"name": user_defined_name,
"model": model_name,
"display": f"[{i}]USB: {user_defined_name} {model_name}"
}
devices_info.append(device_info)
else:
# 其他类型相机
device_info = {
"index": i,
"type": "Other",
"display": f"[{i}]Other"
}
devices_info.append(device_info)
return devices_info
except Exception as e:
error_msg = f"枚举设备时发生异常: {str(e)}"
logging.error(error_msg)
return None
def open_device(self, device_index):
"""打开相机设备
Args:
device_index: 设备索引
Returns:
bool: 是否成功打开设备
"""
# 检查是否已经打开
if self.isOpen:
logging.warning("相机已经打开!")
return False
# 检查设备索引是否有效
if device_index < 0 or (self.deviceList and device_index >= self.deviceList.nDeviceNum):
logging.error(f"无效的设备索引: {device_index}")
return False
try:
logging.debug(f"准备打开相机,设备索引: {device_index}")
self.nSelCamIndex = device_index
# 创建相机操作对象
self.obj_cam_operation = CameraOperation(self.cam, self.deviceList, self.nSelCamIndex)
ret = self.obj_cam_operation.Open_device()
if ret != 0:
error_msg = f"打开相机失败! 错误码: 0x{ret:x}"
logging.error(error_msg)
self.isOpen = False
return False
# 设置连续模式
self.obj_cam_operation.Set_trigger_mode(False)
# 获取参数
self.obj_cam_operation.Get_parameter()
self.isOpen = True
logging.info(f"相机已打开,设备索引: {device_index}")
return True
except Exception as e:
error_msg = f"打开相机时发生异常: {str(e)}"
logging.error(error_msg)
self.isOpen = False
return False
def close_device(self):
"""关闭相机设备
Returns:
bool: 是否成功关闭设备
"""
if not self.isOpen:
return True
try:
# 确保停止取图
if self.isGrabbing:
self.stop_grabbing()
# 关闭设备
ret = self.obj_cam_operation.Close_device()
if ret != 0:
error_msg = f"关闭相机失败! 错误码: 0x{ret:x}"
logging.error(error_msg)
return False
self.isOpen = False
logging.info("相机已关闭")
return True
except Exception as e:
error_msg = f"关闭相机时发生异常: {str(e)}"
logging.error(error_msg)
self.isOpen = False # 强制设置为关闭状态
return False
def start_grabbing(self, window_id):
"""开始取图
Args:
window_id: 显示窗口句柄
Returns:
bool: 是否成功开始取图
"""
if not self.isOpen:
logging.error("相机未打开,无法开始取图")
return False
if self.isGrabbing:
logging.warning("相机已经在取图")
return True
try:
ret = self.obj_cam_operation.Start_grabbing(window_id)
if ret != 0:
error_msg = f"开始取图失败! 错误码: 0x{ret:x}"
logging.error(error_msg)
return False
self.isGrabbing = True
logging.info("开始图像采集")
return True
except Exception as e:
error_msg = f"开始取图时发生异常: {str(e)}"
logging.error(error_msg)
return False
def stop_grabbing(self):
"""停止取图
Returns:
bool: 是否成功停止取图
"""
if not self.isOpen:
return True
if not self.isGrabbing:
return True
try:
ret = self.obj_cam_operation.Stop_grabbing()
if ret != 0:
error_msg = f"停止取图失败! 错误码: 0x{ret:x}"
logging.error(error_msg)
return False
self.isGrabbing = False
logging.info("停止图像采集")
return True
except Exception as e:
error_msg = f"停止取图时发生异常: {str(e)}"
logging.error(error_msg)
self.isGrabbing = False # 强制设置为非取图状态
return False
def get_parameters(self):
"""获取相机参数
Returns:
tuple: (曝光时间, 增益, 帧率) None (如果失败)
"""
if not self.isOpen:
logging.error("相机未打开,无法获取参数")
return None
try:
ret = self.obj_cam_operation.Get_parameter()
if ret != 0:
error_msg = f"获取相机参数失败! 错误码: 0x{ret:x}"
logging.error(error_msg)
return None
exposure_time = self.obj_cam_operation.exposure_time
gain = self.obj_cam_operation.gain
frame_rate = self.obj_cam_operation.frame_rate
logging.info(f"获取相机参数: 曝光={exposure_time}, 增益={gain}, 帧率={frame_rate}")
return (exposure_time, gain, frame_rate)
except Exception as e:
error_msg = f"获取相机参数时发生异常: {str(e)}"
logging.error(error_msg)
return None
def set_parameters(self, frame_rate, exposure_time, gain):
"""设置相机参数
Args:
frame_rate: 帧率
exposure_time: 曝光时间
gain: 增益
Returns:
bool: 是否成功设置参数
"""
if not self.isOpen:
logging.error("相机未打开,无法设置参数")
return False
try:
# 设置参数
ret = self.obj_cam_operation.Set_parameter(str(frame_rate), str(exposure_time), str(gain))
if ret != 0:
error_msg = f"设置相机参数失败! 错误码: 0x{ret:x}"
logging.error(error_msg)
return False
logging.info(f"设置相机参数: 曝光={exposure_time}, 增益={gain}, 帧率={frame_rate}")
return True
except Exception as e:
error_msg = f"设置相机参数时发生异常: {str(e)}"
logging.error(error_msg)
return False

View File

@ -0,0 +1,324 @@
import sys
import os
import logging
from ctypes import *
from math import log10
from camera.CameraParams_const import *
# 添加相机模块路径
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "camera"))
try:
from PySide6.QtWidgets import QWidget, QMessageBox
from PySide6.QtCore import Qt, Signal
USE_PYSIDE6 = True
except ImportError:
from PyQt5.QtWidgets import QWidget, QMessageBox
from PyQt5.QtCore import Qt
from PyQt5.QtCore import pyqtSignal as Signal
USE_PYSIDE6 = False
# 导入相机相关模块
from camera.MvCameraControl_class import MvCamera
from camera.CamOperation_class import CameraOperation
from camera.MvErrorDefine_const import *
from camera.CameraParams_header import *
from ui.settings_ui import SettingsUI
# 导入相机管理器
from widgets.camera_manager import CameraManager
class CameraSettingsWidget(SettingsUI):
"""相机设置控制器,管理相机设置并提供与主窗口相机显示部分的通信"""
# 定义信号
signal_camera_connection = Signal(bool, str) # 相机连接状态信号 (是否连接, 错误消息)
signal_camera_params_changed = Signal(float, float, float) # 相机参数变化信号 (曝光, 增益, 帧率)
signal_camera_error = Signal(str) # 相机错误信号
def __init__(self, parent=None):
super().__init__(parent)
# 获取相机管理器实例
self.camera_manager = CameraManager.get_instance()
# 初始化UI控件状态
self.update_controls()
# 连接信号和槽
self.connect_signals()
# 初始化相机参数范围
self.frame_rate_min = 1.0
self.frame_rate_max = 60.0
self.exposure_min = 20.0
self.exposure_max = 1000000.0
self.gain_min = 0.0
self.gain_max = 15.0
# 枚举设备
self.refresh_devices()
def connect_signals(self):
"""连接信号和槽"""
# 设备选择和刷新
self.refresh_button.clicked.connect(self.refresh_devices)
# 相机控制按钮
self.open_button.clicked.connect(self.open_camera)
self.close_button.clicked.connect(self.close_camera)
self.test_button.clicked.connect(self.test_camera)
# 参数滑块
self.exposure_slider.valueChanged.connect(self.update_exposure_value)
self.gain_slider.valueChanged.connect(self.update_gain_value)
self.framerate_slider.valueChanged.connect(self.update_frame_rate_value)
# 参数操作按钮
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()
# 枚举设备
devices_info = self.camera_manager.enum_devices()
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()
def get_selected_device_index(self):
"""获取当前选中的设备索引"""
if self.camera_combo.count() == 0:
return -1
return self.camera_combo.currentData()
def update_controls(self):
"""更新控件状态"""
is_open = self.camera_manager.isOpen
is_grabbing = self.camera_manager.isGrabbing
# 设备选择和刷新
self.camera_combo.setEnabled(not is_open)
self.refresh_button.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)
# 参数滑块
self.exposure_slider.setEnabled(is_open)
self.gain_slider.setEnabled(is_open)
self.framerate_slider.setEnabled(is_open)
# 参数操作按钮
self.get_params_button.setEnabled(is_open)
self.set_params_button.setEnabled(is_open)
self.save_camera_button.setEnabled(is_open)
def open_camera(self):
"""打开相机"""
if self.camera_manager.isOpen:
QMessageBox.warning(self, "错误", "相机已经打开!")
return
device_index = self.get_selected_device_index()
if device_index < 0:
QMessageBox.warning(self, "错误", "请先选择一个相机设备!")
return
success = self.camera_manager.open_device(device_index)
if success:
# 获取相机参数并更新UI
self.get_camera_params()
# 更新控件状态
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.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()
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)
# 增益
slider_value = int(((gain - self.gain_min) / (self.gain_max - self.gain_min)) * 100)
self.gain_slider.setValue(slider_value)
# 帧率
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, "错误", "相机参数设置失败!")
def save_camera_params(self):
"""保存相机参数"""
if not self.camera_manager.isOpen:
QMessageBox.warning(self, "错误", "请先打开相机!")
return
# 实现保存参数到配置文件的功能
# TODO: 待实现
QMessageBox.information(self, "提示", "参数保存功能尚未实现")
def closeEvent(self, event):
"""窗口关闭事件"""
# 确保关闭相机
if self.camera_manager.isOpen:
self.camera_manager.close_device()
# 处理事件
super().closeEvent(event)

298
widgets/camera_widget.py Normal file
View File

@ -0,0 +1,298 @@
import sys
import os
import logging
from ctypes import *
from camera.CameraParams_const import *
# 添加相机模块路径
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "camera"))
# 导入相机相关模块
from camera.MvCameraControl_class import MvCamera
from camera.CamOperation_class import CameraOperation
from camera.MvErrorDefine_const import *
from camera.CameraParams_header import * # 导入MV_CC_DEVICE_INFO_LIST等类型
# 尝试从PySide6导入如果失败则从PyQt5导入
try:
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QMessageBox, QFrame
from PySide6.QtCore import Signal
USE_PYSIDE6 = True
except ImportError:
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox, QMessageBox, QFrame
from PyQt5.QtCore import pyqtSignal as Signal
USE_PYSIDE6 = False
class CameraWidget(QWidget):
"""海康威视工业相机控制组件"""
# 定义信号
camera_connected = Signal(bool)
camera_error = Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
# 初始化相机变量
self.deviceList = None
self.cam = MvCamera()
self.nSelCamIndex = 0
self.obj_cam_operation = None
self.isOpen = False
self.isGrabbing = False
# 初始化UI
self.init_ui()
# 枚举相机设备
self.enum_devices()
def init_ui(self):
# 主布局
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(5, 5, 5, 5)
self.main_layout.setSpacing(5)
# 相机选择区域
self.device_layout = QHBoxLayout()
self.device_label = QLabel("相机:")
self.device_combo = QComboBox()
self.device_combo.setMinimumWidth(150)
self.refresh_button = QPushButton("刷新")
self.refresh_button.clicked.connect(self.enum_devices)
self.device_layout.addWidget(self.device_label)
self.device_layout.addWidget(self.device_combo)
self.device_layout.addWidget(self.refresh_button)
self.device_layout.addStretch()
# 相机控制区域
self.control_layout = QHBoxLayout()
self.open_button = QPushButton("打开相机")
self.open_button.clicked.connect(self.open_device)
self.close_button = QPushButton("关闭相机")
self.close_button.clicked.connect(self.close_device)
self.start_button = QPushButton("开始采集")
self.start_button.clicked.connect(self.start_grabbing)
self.stop_button = QPushButton("停止采集")
self.stop_button.clicked.connect(self.stop_grabbing)
self.control_layout.addWidget(self.open_button)
self.control_layout.addWidget(self.close_button)
self.control_layout.addWidget(self.start_button)
self.control_layout.addWidget(self.stop_button)
# 视频显示区域
self.display_frame = QFrame()
self.display_frame.setFrameShape(QFrame.Box)
self.display_frame.setLineWidth(1)
self.display_frame.setMinimumHeight(200)
self.display_frame.setStyleSheet("background-color: black;")
# 将所有组件添加到主布局
self.main_layout.addLayout(self.device_layout)
self.main_layout.addLayout(self.control_layout)
self.main_layout.addWidget(self.display_frame, 1) # 显示区域占据剩余空间
# 初始化按钮状态
self.update_controls()
def update_controls(self):
"""更新控件状态"""
has_devices = self.device_combo.count() > 0
self.open_button.setEnabled(has_devices and not self.isOpen)
self.close_button.setEnabled(self.isOpen)
self.start_button.setEnabled(self.isOpen and not self.isGrabbing)
self.stop_button.setEnabled(self.isOpen and self.isGrabbing)
self.device_combo.setEnabled(not self.isOpen)
self.refresh_button.setEnabled(not self.isOpen)
def enum_devices(self):
"""枚举相机设备"""
try:
# 初始化SDK
MvCamera.MV_CC_Initialize()
# 清空设备列表
self.device_combo.clear()
# 枚举设备
self.deviceList = MV_CC_DEVICE_INFO_LIST()
n_layer_type = (MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE
| MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE)
ret = MvCamera.MV_CC_EnumDevices(n_layer_type, self.deviceList)
if ret != 0:
error_msg = f"枚举设备失败! 错误码: 0x{ret:x}"
self.camera_error.emit(error_msg)
logging.error(error_msg)
return
if self.deviceList.nDeviceNum == 0:
logging.info("未找到相机设备")
return
logging.info(f"找到 {self.deviceList.nDeviceNum} 个相机设备")
# 将设备添加到下拉列表
for i in range(0, self.deviceList.nDeviceNum):
mvcc_dev_info = cast(self.deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents
if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE:
# GigE相机
nip1 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24)
nip2 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16)
nip3 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8)
nip4 = (mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff)
ip = f"{nip1}.{nip2}.{nip3}.{nip4}"
device_info = f"[{i}]GigE: {ip}"
self.device_combo.addItem(device_info)
elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE:
# USB相机
device_info = f"[{i}]USB"
self.device_combo.addItem(device_info)
else:
# 其他类型相机
device_info = f"[{i}]Other"
self.device_combo.addItem(device_info)
# 更新控件状态
self.update_controls()
except Exception as e:
error_msg = f"枚举设备时发生异常: {str(e)}"
self.camera_error.emit(error_msg)
logging.error(error_msg)
def get_selected_device_index(self):
"""获取选中的设备索引"""
if self.device_combo.count() == 0:
return -1
device_text = self.device_combo.currentText()
start_idx = device_text.find('[') + 1
end_idx = device_text.find(']')
if start_idx >= 0 and end_idx > start_idx:
return int(device_text[start_idx:end_idx])
return 0
def open_device(self):
"""打开相机设备"""
if self.isOpen:
QMessageBox.warning(self, "错误", "相机已经打开!")
return
try:
self.nSelCamIndex = self.get_selected_device_index()
if self.nSelCamIndex < 0:
QMessageBox.warning(self, "错误", "请选择一个相机!")
return
self.obj_cam_operation = CameraOperation(self.cam, self.deviceList, self.nSelCamIndex)
ret = self.obj_cam_operation.Open_device()
if ret != 0:
error_msg = f"打开相机失败! 错误码: 0x{ret:x}"
QMessageBox.warning(self, "错误", error_msg)
self.isOpen = False
logging.error(error_msg)
else:
# 设置连续模式
self.obj_cam_operation.Set_trigger_mode(False)
# 获取参数
self.obj_cam_operation.Get_parameter()
self.isOpen = True
self.camera_connected.emit(True)
logging.info("相机已打开")
self.update_controls()
except Exception as e:
error_msg = f"打开相机时发生异常: {str(e)}"
QMessageBox.warning(self, "错误", error_msg)
logging.error(error_msg)
self.isOpen = False
self.update_controls()
def close_device(self):
"""关闭相机设备"""
if not self.isOpen:
return
try:
if self.isGrabbing:
self.stop_grabbing()
ret = self.obj_cam_operation.Close_device()
if ret != 0:
error_msg = f"关闭相机失败! 错误码: 0x{ret:x}"
QMessageBox.warning(self, "错误", error_msg)
logging.error(error_msg)
else:
self.isOpen = False
self.camera_connected.emit(False)
logging.info("相机已关闭")
self.update_controls()
except Exception as e:
error_msg = f"关闭相机时发生异常: {str(e)}"
logging.error(error_msg)
self.isOpen = False
self.update_controls()
def start_grabbing(self):
"""开始取图"""
if not self.isOpen or self.isGrabbing:
return
try:
# 根据使用的框架不同,获取窗口句柄的方式不同
if USE_PYSIDE6:
window_id = int(self.display_frame.winId())
else:
window_id = self.display_frame.winId()
ret = self.obj_cam_operation.Start_grabbing(window_id)
if ret != 0:
error_msg = f"开始取图失败! 错误码: 0x{ret:x}"
QMessageBox.warning(self, "错误", error_msg)
logging.error(error_msg)
else:
self.isGrabbing = True
logging.info("开始图像采集")
self.update_controls()
except Exception as e:
error_msg = f"开始取图时发生异常: {str(e)}"
QMessageBox.warning(self, "错误", error_msg)
logging.error(error_msg)
self.isGrabbing = False
self.update_controls()
def stop_grabbing(self):
"""停止取图"""
if not self.isOpen or not self.isGrabbing:
return
try:
ret = self.obj_cam_operation.Stop_grabbing()
if ret != 0:
error_msg = f"停止取图失败! 错误码: 0x{ret:x}"
QMessageBox.warning(self, "错误", error_msg)
logging.error(error_msg)
else:
self.isGrabbing = False
logging.info("停止图像采集")
self.update_controls()
except Exception as e:
error_msg = f"停止取图时发生异常: {str(e)}"
logging.error(error_msg)
self.isGrabbing = False
self.update_controls()
def closeEvent(self, event):
"""窗口关闭事件"""
if self.isOpen:
self.close_device()
event.accept()

98
widgets/login_widget.py Normal file
View File

@ -0,0 +1,98 @@
from PySide6.QtWidgets import QMessageBox
from PySide6.QtCore import Qt
from ui.login_ui import LoginUI
from utils.version_manager import VersionManager
from widgets.main_window import MainWindow
from utils.sql_utils import SQLUtils
import logging
import threading
def check_user_login(user_id, password):
"""验证用户登录"""
try:
db = SQLUtils('sqlite', database='db/jtDB.db')
db.execute_query("SELECT id FROM user WHERE username = ? AND password = ? AND is_deleted = 0", (user_id, password))
result = db.fetchone()
db.close()
return result is not None
except Exception as e:
logging.error(f"登录验证出错: {e}")
return False
def get_user_info(user_id):
"""获取用户信息"""
try:
db = SQLUtils('sqlite', database='db/jtDB.db')
db.execute_query("SELECT username, 'Default Corp', 1, 1 FROM user WHERE username = ?", (user_id,))
result = db.fetchone()
db.close()
if result:
return result
return user_id, "未知公司", 0, 0
except Exception as e:
logging.error(f"获取用户信息出错: {e}")
return user_id, "未知公司", 0, 0
class LoginWidget(LoginUI):
def __init__(self, parent=None):
super().__init__(parent)
self.btn_login.clicked.connect(self.handle_login)
self.btn_close.clicked.connect(self.close)
# 添加回车键支持
self.edit_user.returnPressed.connect(self.handle_login)
self.edit_pwd.returnPressed.connect(self.handle_login)
# 显示版本号
self.version_label.setText(f"版本: {VersionManager.get_current_version()}")
# 在单独的线程中检查更新避免阻塞UI
# self.check_update_thread = threading.Thread(target=self.check_for_updates)
# self.check_update_thread.daemon = True
# self.check_update_thread.start()
def check_for_updates(self):
"""在后台线程中检查更新"""
try:
# 检查更新并提示用户
VersionManager.check_and_prompt_update(self)
except Exception as e:
logging.error(f"检查更新时发生错误: {e}")
# 登录按钮点击事件
def handle_login(self):
user_id = self.edit_user.text().strip()
password = self.edit_pwd.text().strip()
if not user_id or not password:
QMessageBox.warning(self, "提示", "请输入工号和密码!")
return
if check_user_login(user_id, password):
try:
user_name, corp_name, corp_id, position_id = get_user_info(user_id)
if not corp_name:
corp_name = "未知公司"
# 移除登录成功的弹框
# QMessageBox.information(self, "登录成功", f"欢迎 {user_name}")
self.hide()
# 使用异常处理确保主窗口创建失败不会导致整个应用程序崩溃
try:
import logging
logging.info(f"正在创建主窗口用户ID: {user_id}, 姓名: {user_name}, 公司: {corp_name}")
self.main_window = MainWindow(user_id, user_name, corp_name, corp_id, position_id)
self.main_window.showMaximized() # 窗口最大化显示
logging.info("主窗口已显示(最大化)")
except Exception as e:
logging.critical(f"创建或显示主窗口时发生错误: {e}", exc_info=True)
QMessageBox.critical(self, "系统错误", f"打开主窗口时发生错误: {e}\n请联系管理员")
self.show() # 如果主窗口创建失败,重新显示登录窗口
except Exception as e:
import logging
logging.critical(f"处理登录信息时发生错误: {e}", exc_info=True)
QMessageBox.critical(self, "系统错误", f"处理登录信息时发生错误: {e}\n请联系管理员")
self.show() # 确保用户可以重新尝试登录
else:
QMessageBox.warning(self, "登录失败", "工号或密码错误!")

213
widgets/main_window.py Normal file
View File

@ -0,0 +1,213 @@
import os
import sys
import logging
from datetime import datetime
# 导入PySide6
from PySide6.QtWidgets import QWidget, QMessageBox, QTableWidgetItem, QStackedWidget, QLabel
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QBrush, QColor
# 导入UI
from ui.main_window_ui import MainWindowUI
# 导入相机显示组件和设置组件
from widgets.camera_display_widget import CameraDisplayWidget
from widgets.camera_settings_widget import CameraSettingsWidget
class MainWindow(MainWindowUI):
"""主窗口"""
def __init__(self, user_id=None, user_name=None, corp_name=None, corp_id=None, position_id=None):
super().__init__()
self.user_id = user_id
self.user_name = user_name
self.corp_name = corp_name
self.corp_id = corp_id
self.position_id = position_id
# 设置窗口标题
if user_name and corp_name:
self.setWindowTitle(f"腾智微丝产线包装系统 - {user_name} ({corp_name})")
# # 创建相机显示组件并添加到上料区
# self.camera_display = CameraDisplayWidget()
# self.material_content_layout.addWidget(self.camera_display)
# 为下料区添加占位标签,确保它保持为空
self.output_placeholder = QLabel("下料区 - 暂无内容")
self.output_placeholder.setAlignment(Qt.AlignCenter)
self.output_placeholder.setStyleSheet("color: #888888; background-color: #f0f0f0;")
self.output_content_layout.addWidget(self.output_placeholder)
# 创建堆叠部件
self.stacked_widget = QStackedWidget()
self.stacked_widget.addWidget(self.central_widget) # 主页面
# 不在这里直接初始化相机设置组件
# 延迟创建保证创建的时候SettingsUI的所有控件都已经准备好
self.camera_settings = None
# 设置中央部件为堆叠部件
self.setCentralWidget(self.stacked_widget)
# 连接信号和槽
# self.connect_signals()
# 默认显示主页面
self.stacked_widget.setCurrentIndex(0)
# 初始化数据
self.initialize_data()
# 配置检验列为5列
self.set_inspection_columns(5)
logging.info(f"主窗口已创建,用户: {user_name}")
def connect_signals(self):
# 连接菜单动作
self.main_action.triggered.connect(self.show_main_page)
self.settings_action.triggered.connect(self.show_settings_page)
# 连接按钮事件
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.camera_display.signal_camera_status.connect(self.handle_camera_status)
def initialize_data(self):
"""初始化界面数据"""
# 设置订单和批号
self.order_edit.setText("ORD-2025-001")
self.tray_edit.setText("BAT-2025-001")
# 初始化项目表格数据示例
project_data = [
["100", "50", "200", "95%"],
["3000", "1500", "6000", "92%"],
["36000", "18000", "72000", "90%"],
["120000", "60000", "240000", "91%"]
]
for row in range(4):
for col in range(1, 5): # 从第2列开始跳过项目列
item = QTableWidgetItem(project_data[row][col-1])
# item.setTextAlignment(Qt.AlignCenter) # 设置文本居中对齐
self.project_table.setItem(row, col, item)
# 初始化任务表格数据示例 - 注意现在表格有3行第0行是一级标题第1行是二级标题第2行是数据行
# 数据应该填充在第2行索引为2
data = ["200", "95", "0", "0"] # 分别对应:总生产数量、总生产公斤、已完成数量、已完成公斤
for col, value in enumerate(data):
item = QTableWidgetItem(value)
item.setTextAlignment(Qt.AlignCenter) # 设置文本居中对齐
self.task_table.setItem(2, col, item)
def show_main_page(self):
self.stacked_widget.setCurrentWidget(self.central_widget)
# 如果相机已连接,直接开始显示相机画面
if self.camera_display.camera_manager.isOpen:
if not self.camera_display.camera_manager.isGrabbing:
self.camera_display.start_display()
logging.info("显示主页面")
def show_settings_page(self):
# 延迟创建相机设置组件
if self.camera_settings is None:
self.camera_settings = CameraSettingsWidget()
# 连接相机设置信号
self.camera_settings.signal_camera_connection.connect(self.handle_camera_connection)
self.camera_settings.signal_camera_params_changed.connect(self.handle_camera_params_changed)
self.camera_settings.signal_camera_error.connect(self.handle_camera_error)
# 添加到堆叠部件
self.stacked_widget.addWidget(self.camera_settings)
# 切换到设置页面
self.stacked_widget.setCurrentWidget(self.camera_settings)
logging.info("显示设置页面")
def handle_input(self):
"""处理上料按钮点击事件"""
logging.info("上料按钮被点击")
# 如果相机已经配置,则开始显示
if self.camera_display.camera_manager.isOpen and not self.camera_display.camera_manager.isGrabbing:
self.camera_display.start_display()
QMessageBox.information(self, "操作提示", "开始上料操作")
# 这里添加上料相关的业务逻辑
def handle_output(self):
"""处理下料按钮点击事件"""
logging.info("下料按钮被点击")
QMessageBox.information(self, "操作提示", "开始下料操作")
# 这里添加下料相关的业务逻辑
def handle_start(self):
"""处理开始按钮点击事件"""
logging.info("开始按钮被点击")
# 开始显示相机画面
if self.camera_display.camera_manager.isOpen:
self.camera_display.start_display()
QMessageBox.information(self, "操作提示", "生产线已启动")
# 这里添加启动生产线的业务逻辑
def handle_stop(self):
"""处理暂停按钮点击事件"""
logging.info("暂停按钮被点击")
# 停止显示相机画面
self.camera_display.stop_display()
QMessageBox.information(self, "操作提示", "生产线已暂停")
# 这里添加暂停生产线的业务逻辑
def handle_camera_status(self, is_connected, message):
"""处理相机状态变化"""
if is_connected:
logging.info("相机已连接并显示")
else:
logging.warning(f"相机显示问题: {message}")
def handle_camera_connection(self, is_connected, message):
"""处理相机连接状态变化"""
if is_connected:
logging.info("相机已连接")
# 如果当前在主页面,直接开始显示相机画面
if self.stacked_widget.currentWidget() == self.central_widget:
self.camera_display.start_display()
else:
if message:
logging.warning(f"相机连接失败: {message}")
else:
logging.info("相机已断开")
# 如果相机断开,确保停止显示
self.camera_display.stop_display()
def handle_camera_params_changed(self, exposure_time, gain, frame_rate):
"""处理相机参数变化"""
logging.info(f"相机参数已更新: 曝光={exposure_time:.1f}μs, 增益={gain:.1f}dB, 帧率={frame_rate:.1f}fps")
# 这里可以添加对相机参数变化的处理逻辑
def handle_camera_error(self, error_msg):
"""处理相机错误"""
logging.error(f"相机错误: {error_msg}")
QMessageBox.warning(self, "相机错误", error_msg)
def closeEvent(self, event):
"""窗口关闭事件"""
# 停止相机显示
self.camera_display.stop_display()
# 接受关闭事件
event.accept()

View File

@ -0,0 +1,57 @@
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()

140
widgets/settings_widget.py Normal file
View File

@ -0,0 +1,140 @@
from PySide6.QtWidgets import QMessageBox
import logging
from ui.settings_ui import SettingsUI
from utils.sql_utils import SQLUtils
class SettingsWidget(SettingsUI):
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
# 连接信号和槽
self.connect_signals()
# 初始化数据库类型UI状态
self.update_db_ui_state()
def connect_signals(self):
# 数据库类型选择
self.sqlite_radio.toggled.connect(self.update_db_ui_state)
self.pgsql_radio.toggled.connect(self.update_db_ui_state)
self.mysql_radio.toggled.connect(self.update_db_ui_state)
# 按钮动作
self.test_conn_button.clicked.connect(self.test_connection)
self.save_db_button.clicked.connect(self.save_db_settings)
def update_db_ui_state(self):
"""根据选择的数据库类型更新UI状态"""
if self.sqlite_radio.isChecked():
# SQLite模式下只需要数据库文件路径
self.host_input.setEnabled(False)
self.host_input.setText("")
self.user_input.setEnabled(False)
self.user_input.setText("")
self.password_input.setEnabled(False)
self.password_input.setText("")
self.port_input.setEnabled(False)
self.port_input.setText("")
self.database_input.setEnabled(True)
self.database_input.setText("db/jtDB.db")
elif self.pgsql_radio.isChecked():
# PostgreSQL模式下需要完整的连接信息
self.host_input.setEnabled(True)
self.host_input.setText("localhost")
self.user_input.setEnabled(True)
self.user_input.setText("postgres")
self.password_input.setEnabled(True)
self.password_input.setText("")
self.port_input.setEnabled(True)
self.port_input.setText("5432")
self.database_input.setEnabled(True)
self.database_input.setText("jtDB")
elif self.mysql_radio.isChecked():
# MySQL模式下需要完整的连接信息
self.host_input.setEnabled(True)
self.host_input.setText("localhost")
self.user_input.setEnabled(True)
self.user_input.setText("root")
self.password_input.setEnabled(True)
self.password_input.setText("")
self.port_input.setEnabled(True)
self.port_input.setText("3306")
self.database_input.setEnabled(True)
self.database_input.setText("jtDB")
def get_db_type(self):
"""获取当前选择的数据库类型"""
if self.sqlite_radio.isChecked():
return "sqlite"
elif self.pgsql_radio.isChecked():
return "pgsql"
elif self.mysql_radio.isChecked():
return "mysql"
return "sqlite" # 默认返回sqlite
def get_connection_params(self):
"""获取数据库连接参数"""
db_type = self.get_db_type()
params = {
"database": self.database_input.text().strip()
}
if db_type != "sqlite":
params.update({
"host": self.host_input.text().strip(),
"user": self.user_input.text().strip(),
"password": self.password_input.text().strip(),
"port": self.port_input.text().strip()
})
return db_type, params
def test_connection(self):
"""测试数据库连接"""
db_type, params = self.get_connection_params()
try:
# 创建数据库连接
db = SQLUtils(db_type, **params)
# 尝试执行简单查询
if db_type == "sqlite":
db.execute_query("SELECT sqlite_version();")
elif db_type == "pgsql":
db.execute_query("SELECT version();")
elif db_type == "mysql":
db.execute_query("SELECT version();")
result = db.fetchone()
db.close()
# 显示成功消息
QMessageBox.information(self, "连接成功", f"数据库连接测试成功!\n数据库版本: {result[0]}")
logging.info(f"数据库连接测试成功,类型: {db_type}, 版本: {result[0]}")
except Exception as e:
# 显示错误消息
QMessageBox.critical(self, "连接失败", f"数据库连接测试失败!\n错误: {str(e)}")
logging.error(f"数据库连接测试失败,类型: {db_type}, 错误: {str(e)}")
def save_db_settings(self):
"""保存数据库设置"""
db_type = self.get_db_type()
params = self.get_connection_params()[1]
desc = self.desc_input.text().strip()
# 这里应该将设置保存到配置文件中
# 为了简单起见,这里只显示一个消息框
settings_info = f"数据库类型: {db_type}\n"
for key, value in params.items():
if key != "password":
settings_info += f"{key}: {value}\n"
else:
settings_info += f"{key}: {'*' * len(value)}\n"
settings_info += f"说明: {desc}"
QMessageBox.information(self, "设置已保存", f"数据库设置已保存!\n\n{settings_info}")
logging.info(f"数据库设置已保存,类型: {db_type}")