完成视频集成
This commit is contained in:
commit
bd30815b59
362
camera/BasicDemo.py
Normal file
362
camera/BasicDemo.py
Normal 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()
|
||||
376
camera/CamOperation_class.py
Normal file
376
camera/CamOperation_class.py
Normal 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
|
||||
|
||||
82
camera/CameraParams_const.py
Normal file
82
camera/CameraParams_const.py
Normal 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
|
||||
1289
camera/CameraParams_header.py
Normal file
1289
camera/CameraParams_header.py
Normal file
File diff suppressed because it is too large
Load Diff
1104
camera/MvCameraControl_class.py
Normal file
1104
camera/MvCameraControl_class.py
Normal file
File diff suppressed because it is too large
Load Diff
67
camera/MvErrorDefine_const.py
Normal file
67
camera/MvErrorDefine_const.py
Normal 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
258
camera/PixelType_header.py
Normal 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
146
camera/PyUICBasicDemo.py
Normal 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
261
camera/PyUICBasicDemo.ui
Normal 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>
|
||||
BIN
camera/__pycache__/CamOperation_class.cpython-310.pyc
Normal file
BIN
camera/__pycache__/CamOperation_class.cpython-310.pyc
Normal file
Binary file not shown.
BIN
camera/__pycache__/CameraParams_const.cpython-310.pyc
Normal file
BIN
camera/__pycache__/CameraParams_const.cpython-310.pyc
Normal file
Binary file not shown.
BIN
camera/__pycache__/CameraParams_header.cpython-310.pyc
Normal file
BIN
camera/__pycache__/CameraParams_header.cpython-310.pyc
Normal file
Binary file not shown.
BIN
camera/__pycache__/MvCameraControl_class.cpython-310.pyc
Normal file
BIN
camera/__pycache__/MvCameraControl_class.cpython-310.pyc
Normal file
Binary file not shown.
BIN
camera/__pycache__/MvErrorDefine_const.cpython-310.pyc
Normal file
BIN
camera/__pycache__/MvErrorDefine_const.cpython-310.pyc
Normal file
Binary file not shown.
BIN
camera/__pycache__/PixelType_header.cpython-310.pyc
Normal file
BIN
camera/__pycache__/PixelType_header.cpython-310.pyc
Normal file
Binary file not shown.
19
config/app_config.json
Normal file
19
config/app_config.json
Normal 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
5
config/application.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
app:
|
||||
debug: true
|
||||
name: jt-mes-system
|
||||
version: 1.0.0
|
||||
description: 佳腾管理系统
|
||||
52
dao/login_dao.py
Normal file
52
dao/login_dao.py
Normal 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
BIN
db/jtDB.db
Normal file
Binary file not shown.
1628
logs/app_2025-06-05.log
Normal file
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
15
logs/app_2025-06-06.log
Normal 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
172
logs/app_2025-06-07.log
Normal 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
200
main.py
Normal 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()
|
||||
BIN
ui/__pycache__/login_ui.cpython-310.pyc
Normal file
BIN
ui/__pycache__/login_ui.cpython-310.pyc
Normal file
Binary file not shown.
BIN
ui/__pycache__/main_window_ui.cpython-310.pyc
Normal file
BIN
ui/__pycache__/main_window_ui.cpython-310.pyc
Normal file
Binary file not shown.
BIN
ui/__pycache__/settings_ui.cpython-310.pyc
Normal file
BIN
ui/__pycache__/settings_ui.cpython-310.pyc
Normal file
Binary file not shown.
85
ui/login_ui.py
Normal file
85
ui/login_ui.py
Normal 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
742
ui/main_window_ui.py
Normal 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
369
ui/settings_ui.py
Normal 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, "参数配置")
|
||||
BIN
utils/__pycache__/config_loader.cpython-310.pyc
Normal file
BIN
utils/__pycache__/config_loader.cpython-310.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/menu_translator.cpython-310.pyc
Normal file
BIN
utils/__pycache__/menu_translator.cpython-310.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/sql_utils.cpython-310.pyc
Normal file
BIN
utils/__pycache__/sql_utils.cpython-310.pyc
Normal file
Binary file not shown.
BIN
utils/__pycache__/version_manager.cpython-310.pyc
Normal file
BIN
utils/__pycache__/version_manager.cpython-310.pyc
Normal file
Binary file not shown.
130
utils/config_loader.py
Normal file
130
utils/config_loader.py
Normal 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
46
utils/init_db.py
Normal 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
19
utils/menu_translator.py
Normal 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
78
utils/sql_utils.py
Normal 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
262
utils/version_manager.py
Normal 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
|
||||
BIN
widgets/__pycache__/camera_display_widget.cpython-310.pyc
Normal file
BIN
widgets/__pycache__/camera_display_widget.cpython-310.pyc
Normal file
Binary file not shown.
BIN
widgets/__pycache__/camera_manager.cpython-310.pyc
Normal file
BIN
widgets/__pycache__/camera_manager.cpython-310.pyc
Normal file
Binary file not shown.
BIN
widgets/__pycache__/camera_settings_widget.cpython-310.pyc
Normal file
BIN
widgets/__pycache__/camera_settings_widget.cpython-310.pyc
Normal file
Binary file not shown.
BIN
widgets/__pycache__/login_widget.cpython-310.pyc
Normal file
BIN
widgets/__pycache__/login_widget.cpython-310.pyc
Normal file
Binary file not shown.
BIN
widgets/__pycache__/main_window.cpython-310.pyc
Normal file
BIN
widgets/__pycache__/main_window.cpython-310.pyc
Normal file
Binary file not shown.
137
widgets/camera_display_widget.py
Normal file
137
widgets/camera_display_widget.py
Normal 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
354
widgets/camera_manager.py
Normal 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
|
||||
324
widgets/camera_settings_widget.py
Normal file
324
widgets/camera_settings_widget.py
Normal 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
298
widgets/camera_widget.py
Normal 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
98
widgets/login_widget.py
Normal 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
213
widgets/main_window.py
Normal 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()
|
||||
57
widgets/settings_dialog.py
Normal file
57
widgets/settings_dialog.py
Normal 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
140
widgets/settings_widget.py
Normal 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}")
|
||||
Loading…
Reference in New Issue
Block a user