三维地形图计算软件(三)-原基于PYQT5+pyqtgraph旧代码

发布于:2024-11-29 ⋅ 阅读:(29) ⋅ 点赞:(0)

最先入手设计三维地形图及平基挖填方计算软件时,地形图的显示方案是:三维视图基于pyqtgraph.opengl显示和二维视图基于pyqtgraph的PlotWidget来显示地形地貌,作到一半时就发现,地形点过多时,将会造成系统卡顿(加载时主要是二维俯视图耗时太多,不要二维俯视图或优化二维显示视图应可以解决点多加载慢的问题),故就放弃了此方案。现将此方案当前完成的代码贴出来(只完成可以显示地形点线,也没测试过BUG,其他功能均没有),可以作为用pyqtgraph.opengl或pyqtgraph的PlotWidget库来显示三维或二维图形图像时的参考。

运行界面如下,

主窗口代码MainWindow.py

# -*- coding: utf-8 -*-
#MainWindow.py:地形土石方计算软件主窗体模块

import sys,os,time,math,copy,random
import PyQt5
from PyQt5 import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
#from PyQt5.QtCore import Qt, QEvent
from  g import *             #导入自定义的一些全局变量和函数
from mapData import *       #导入地形图数据处理类模块

from D3D2View import *     #导入可预览3D和2D的视图类(实例化对象:self.view3D)
from  QLabelEx import *            #导入自定义的彩色标签库
from  QTableWidgetEx import *      #导入自定义的表格控件库

#python -m PyQt5.uic.pyuic './ui/MainWindow.ui'  -o 'Ui_MainWindow.py'  #将Design设计的UI文件转换成中间窗体模块文件,复制此代码在终端中运行即可
from Ui_MainWindow import  Ui_MainWindow   #导入上面命令生成的中间界面模块
#因使用了自定义的扩展类,需要对生成的Ui_MainWindow.py模块中的局部代码进行手动调整,每编绎一次UI都要手动调一次
#1、将上面14-16行代码COPY至Ui_MainWindow.py文件中,
#2、将self.glWidget对象的类名改为自定义类名:GLWidget(如已提升忽略)
#3、将self.view3D对象名改成自定义的类名:D3Widget(如已提升忽略)
#4、
#5、
#定义主窗口类(继承自QMainWindow和设计器生成的Ui_MainWindow)
class terrainWindow(QMainWindow,Ui_MainWindow):  
    def __init__(self):
        super(terrainWindow, self).__init__()
        Ui_MainWindow.__init__(self) 
        self.setupUi(self)
        self.zRate=0.1  #控件为1-100  10为默认位置,向100变大会加大Z值的附加比率倍数
        self.setWindowTitle('土石方工程量计算软件-基于PYthon+QT5+pyqtgraph')
        self.menuType=0 #当前窗体右键菜单类型为0
        lstpath=['\\res','\\data','\\prj','\\ui']
        gf_InitPrgPath(lstpath)   #将本程序支持的所有目录加入环境变量,函数来自g.py模块
        self.initUi()        #初始化界面
        self.initOpenGL()    #初始化OPENGL

    #继续初始化窗口控件    
    def initUi(self):
        self.statusbar = self.statusBar()   # 创建状态栏
        self.statusbar.showMessage('准备')
        self.setContextMenuPolicy(Qt.CustomContextMenu)          
        self.initSignPlot()   #初始化所有信号槽

        head=['序号', '类型','X坐标', 'Y坐标', 'Z坐标', '其他说明']   #定义表格头
        self.tableOrg.initTable(1,len(head))     #先初始化一行
        self.tableOrg.initTableHead(head)        #初始化表格头
        self.tableEnd.initTable(1,len(head))    
        self.tableEnd.initTableHead(head)
        self.tableDesign.initTable(1,len(head))    
        self.tableDesign.initTableHead(head)
        self.bShow3Delement=[True,True,True,False]
        self.bShow2Delement=[True,True,True,False]
        self.chkPoint.setChecked(self.bShow3Delement[0])
        self.chkLine.setChecked(self.bShow3Delement[1])
        self.chkTxt.setChecked(self.bShow3Delement[2])
        self.chkPlan.setChecked(self.bShow3Delement[3]) 

        self.tBtn01_OpenOrgData.setIcon(QIcon('./res/01orgfile.ico'))
        self.tBtn02_OpenEndData.setIcon(QIcon('./res/02endfile.ico'))
        self.tBtn03_OpenDesignData.setIcon(QIcon('./res/03desfile.ico'))


        self.hSliderZ.setValue(self.zRate*100)

        self.threadMaxCount=2       #本程序最大可开多线程的数量   
        self.thread={}              #定义线程数组
        self.threadOpen=[]          #0索引起用,0对应首个线程
        for i in range(self.threadMaxCount):
            self.threadOpen.append(False)      #定义n个线程打开的状况,供计时器函数中取线程值时使用
            self.thread[i] = ThreadClass(parent=None,index=i)
            self.threadOpen[i]=False   #设置线程暂不打开,执行多线程计算时再打开
            #self.thread[i].start()
            #self.setThreadObj(i,self.label_Demo)  #向线程中传入标签控件实例对象
            self.thread[i].signal_ID.connect(self.trd_function)      #将线程1中的自定义信号signal_ID绑定槽函数self.trd_function

    #初始化信号槽定义
    def initSignPlot(self):
        self.customContextMenuRequested.connect(self.showMenu_test)    #创建一个右键菜单栏,对菜单项的显示绑定到槽函数
        #工具栏按纽类信号槽绑定
        self.tBtn01_OpenOrgData.clicked.connect(self.openOrgDatFile)   #打开原始测绘数据文件
        #当复选框状态改变时触发
        self.chkPoint.stateChanged.connect(self.on_chkPoint_stateChanged)
        self.chkLine.stateChanged.connect(self.on_chkLine_stateChanged)
        self.chkTxt.stateChanged.connect(self.on_chkTxt_stateChanged)
        self.chkPlan.stateChanged.connect(self.on_chkPlan_stateChanged)
        self.hSliderZ.valueChanged[int].connect(self.on_SliderZ_changed)

    #对OPENGL绘制3D图的窗体控件进一步进行初始化
    def initOpenGL(self):
        pass
   
######################################################################################
    def on_chkPoint_stateChanged(self):
        self.bShow3Delement[0]=self.chkPoint.isChecked()
        self.view3D.chgDrawParameter(self.bShow3Delement[0],self.bShow3Delement[1],self.bShow3Delement[2],self.bShow3Delement[3], self.zRate)
    def on_chkLine_stateChanged(self):
        self.bShow3Delement[1]=self.chkLine.isChecked()
        self.view3D.chgDrawParameter(self.bShow3Delement[0],self.bShow3Delement[1],self.bShow3Delement[2],self.bShow3Delement[3], self.zRate)
    def on_chkTxt_stateChanged(self):
        self.bShow3Delement[2]=self.chkTxt.isChecked()
        self.view3D.chgDrawParameter(self.bShow3Delement[0],self.bShow3Delement[1],self.bShow3Delement[2],self.bShow3Delement[3], self.zRate)
    def on_chkPlan_stateChanged(self):
        self.bShow3Delement[3]=self.chkPlan.isChecked()
        self.view3D.chgDrawParameter(self.bShow3Delement[0],self.bShow3Delement[1],self.bShow3Delement[2],self.bShow3Delement[3], self.zRate)
    def on_SliderZ_changed(self, value):
        self.zRate=value/100  #值在0.01~1间
        self.view3D.chgDrawParameter(self.bShow3Delement[0],self.bShow3Delement[1],self.bShow3Delement[2],self.bShow3Delement[3], self.zRate)
   
    #槽函数1:
    def openOrgDatFile(self):
        global g_appPath
        file, ok = QFileDialog.getOpenFileName(self, "打开", g_appPath+'/data/demo.dat', "测绘dat文件 (*.dat);所有文件 (*.*)")
        if(self.view3D.loadMapData(file,0)):  #参数1=0,表示导入的是原貌数据
            #更新表中的数据值
            rowNum=self.view3D.pointCount
            self.tableOrg.initTable(rowNum,5)
            self.tableOrg.fillTableAllData(self.view3D.mapOrgDat.d3_orgxyzs)
            if(len(self.view3D.mapOrgDat.lonePoint)>0):
                for row in self.view3D.mapOrgDat.lonePoint:
                    self.tableOrg.setTableRowBkCol(row,QColor(255,0,0))


#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
    def setThreadObj(self,index,obj):
        self.thread[index].setObj(obj)
    #绑定线程类中的signal_ID信号对应的槽函数,得到各线程中的变量ID的值(仅示例线程同窗体数据交互,同本示例无关)
    def trd_function(self,counter):
        ID = counter
        index = self.sender().index   #在槽函数中被调用,用于获取发出信号的对象,的索引号
        print(f'主窗体接收线程槽函数:{index} 返回整数值{ID}')
        if index == 0:
            pass       
        elif index == 1:
            pass
        elif index == 2:
            pass
        #......



#测试代码用的右键菜单
    def showMenu_test(self, point):
        menu = QMenu(self)   # 创建一个菜单
        act_test1 = menu.addAction("测试代码1") # 添加菜单项
        act_test2 = menu.addAction("测试代码2")
        act_test3 = menu.addAction("测试代码3")
        # 将菜单项与响应函数绑定
        act_test1.triggered.connect(self.actTest1)
        act_test2.triggered.connect(self.actTest2)
        act_test3.triggered.connect(self.actTest3)
        menu.exec(self.mapToGlobal(point))  # 在鼠标点击的位置显示菜单

    def actTest1(self):
        print("\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$测试代码1$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$4$$$$$$$$$$$$$$$$$$$$$$$$$")
        self.view3D.updateGrid()
    def actTest2(self):
         print("\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$测试代码1$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$4$$$$$$$$$$$$$$$$$$$$$$$$$")
    
    def actTest3(self):
        print("\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$测试代码1$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$4$$$$$$$$$$$$$$$$$$$$$$$$$")


#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
 
#自定义线程类(继承QT的多线程类QtCore.QThread,不是PYTHON的线程类)
class ThreadClass(QtCore.QThread):
    signal_ID = QtCore.pyqtSignal(int) #自定义线程中的信号,名称为signal_ID
    def __init__(self,parent=None,index=0):
        super(ThreadClass,self).__init__(parent)
        self.index = index
        self.is_running = True
        self.ID=0
    #重载开始线程对应的run函数:本例根据鼠标点击画板(标签控件)的次数来决定运行几个多线程DEMO
    def run(self):
        print(f'开始线程...:线程索引号:{self.index}')
        while(self.is_running):   #线程重复不断的循环来
            time.sleep(1)                 #1000毫秒间隔
            self.draw3d()

    def draw3d(self):
       pass 
   
    #停止指定线程
    def stop(self):
        self.is_running=False
        print('停止线程...',self.index)
        self.terminate()

    #线程中自定义函数供外部调用线程中的变量值
    def getID(self):
        return self.index,self.ID
    #在线程中导入需要操作的对象
    def setObj(self,frmobj):
        self.obj = frmobj
#########################################################################################
if __name__ == '__main__':
    app = QApplication(sys.argv)
    form = terrainWindow()
    form.show()
    sys.exit(app.exec())

用于显示3D和2D地形图的视类模块代码文件D3D2View.py

# -*- coding: utf-8 -*-
  
import sys
import copy
import numpy as np     #数据组用np
from PyQt5.QtWidgets import *
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout
from PyQt5.QtCore import QTimer
import pyqtgraph.opengl as gl
import pyqtgraph as pg
from scipy.spatial import Delaunay
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph import PlotWidget, PlotDataItem, ScatterPlotItem, mkPen, mkBrush
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import pyqtgraph as pg

from  g import *             #导入自定义的一些全局变更和函数
from  QLabelEx import *     #导入自定义的彩色标签库
from  QTableWidgetEx import *     #导入自定义的彩色标签库
from mapData import *       #导入数据类

from D3D2View import *     #导入可预览3D和2D的视图类(用于self.view3D)

#将地形数据以3D和俯视2D的方式分别显示在两个视口(上下方式)
#当前设计控件尺寸为:790*840,上半部份3D视口占
class D3Widget(QWidget):
    def __init__(self, parent=None):
        super(D3Widget, self).__init__(parent)
        self.showType=0   #当前视中显示的数据类别:-1=DEMO数据,0=原貌数据,1=成形貌数据 2=设计数据 3=原貌+成型数据混显
        self.demoDat=mapData(self.width,self.height,'DEMO数据')
        self.mapOrgDat=mapData(self.width,self.height,'原始地貌数据')
        self.mapEndDat=mapData(self.width,self.height,'成型地貌数据')
        self.mapDesignDat=mapData(self.width,self.height,'设计地貌数据')
        self.pointCount=0   #当前显示到视中的总点数
        self.pointR=5       #3D视中画点的半径
        self.lineWidth=1.5  #散点连线的线宽

        self.moveCount=0   #DEMO示例程序用
        self.moveN=0
        self.ChgDir=1
        self.timerDemo = QTimer(self)   #对DEMO设置一计时器,以查看动画效果
        self.bUpdataView=True           # DEMO全部数据加载完毕后,此值变为False,将不再重复加载
        self.bUpdataGrid=True           #如果需要重新加载数据,但不要更改显示比例时,此值为False                      
        #以下几个变量控制在界面中显示不,点、线、点号、面的状态,点较多时如卡顿应关闭部份元素的显示
        self.bDrawPoint=True      #是否在3D+2D视中画出线
        self.bDrawLine=True      #是否在3D+2D视中画出线
        self.bDrawPidTxt=True     #是否在3D+2D中画出点序号
        self.bDrawPlan=False     #是否在3D+2D视中画出面

        #定义3D视上的表格参数:0=X向尺寸,1=Y向尺寸,2=Z向尺寸,3=3D底面网格间距(Z最终要同地形数据Z坐标的最小值一致),3-5:网格三维方向间距
        #以下值在每次导入图据要在对应的导入数据后重新计算设置
        self.gridSize=np.array([100.0,100.0,100.0,10.0,10.0,10.0])
        self.maxX = self.maxY = self.maxZ = 1.0 
        self.minX = self.minY = self.minZ = -1.0
        self.rateX=self.rateY=1.0  
        self.rateZ = 1                   #在3D视图中各坐标的相对显示比例(如是加强显示某一方向的不同,可以加大此方向的比例值)
        #=np.array([self.rateX,self.rateY,self.rateZ])
        self.xRange=100 
        self.yRange=100
    
        self.pos3Ds = np.empty((1, 3))                       #散点数据:n行3列,共n个数据,先定义,后面根据导入的点不同重新初始化
        self.pos2Ds  = np.empty((1, 2))                       #散点数据:n行3列,共n个数据,先定义,后面根据导入的点不同重新初始化
        self.defPointcolor0 = np.array([[255, 0, 255, 255]])    #默认在Z0以下显示的点的RGB颜色
        self.defLinecolor0 = np.array([[0, 255, 255, 255]])     #默认在Z0以下显示的点连线的RGB颜色
        self.defPointcolor1 = np.array([[255, 0, 0, 255]])    #默认在Z0以上显示的点的RGB颜色
        self.defLinecolor1 = np.array([[0, 255, 0, 255]])     #默认在Z0以下显示的点连线的RGB颜色 
        self.def2DTxtCol = np.array([255, 0, 0])              #默认在2D视上车出的点号的颜色
        self.pointCols = np.empty((1,3))                    #点的颜色,有多少个点,就有多少个数组成员

        layout = QVBoxLayout(self)
        self.D3view = gl.GLViewWidget()        # 三维视图绑定到self.D3view上
        layout.addWidget(self.D3view, 30)       #2表示3Dview在layout占3/5高度
        self.d2txt=QLabel('2D俯视图')
        layout.addWidget(self.d2txt, 1)         #1表示3Dview在layout占2/5高度
        self.D2view = PlotWidget()             # 侧二维绘图(俯视图)
        layout.addWidget(self.D2view, 20)       #1表示3Dview在layout占2/5高度
        
        self.camerPos=np.array([100,45,45])    #初始化相机位置数据结构 
       
        self.loadMapData('',-1)                #因开始时还没有加载任何数据,先加载一DEMO数据


        #################################################################################33
        #定义信号槽函数
        self.timerDemo.timeout.connect(self.timeDrawView)     #绑定定时器与更新函数


    #主窗体调用设置一些绘图参数的改变
    def chgDrawParameter(self,bpoint,bline,btxt,bplan,zrate):
        self.bDrawPoint=bpoint
        self.bDrawLine=bline
        self.bDrawPidTxt=btxt
        self.bDrawPlan=bplan
        if(self.showType==-1):
            if(self.bUpdataGrid):
               self.bUpdataGrid==False
            self.bUpdataView=True   
            self.showDemo1()
            
        else:
            self.reDrawView()
    #再初始化窗体上的显示方式:可重复加载的界面变化  
    def updateGrid(self):
        grid = gl.GLGridItem()  # 设置网格
        grid.setSpacing(self.gridSize[3], self.gridSize[4], self.gridSize[5])  #网格间距
        grid.setSize(self.gridSize[0], self.gridSize[1], self.gridSize[2])  #网格三维尺寸,
        if(self.showType!=-1):
            grid.translate(0, 0, self.gridSize[5])  #将网格调到Z最小值的位置 

        #参数:distance:相机镜头到观察点的距离。elevation:相机的俯仰角。 azimuth:相机的方位角。  fov:相机的视野角度。
        #self.camerPos=np.array([100,45,45])    #初始化相机位置数据结构 
        
        self.D3view.addItem(grid)                            #加入网格到控件视图中
        self.D3view.setCameraPosition(distance=self.camerPos[0], elevation=self.camerPos[1], azimuth=self.camerPos[2])   #设置相机位置  
        self.D3view.update()

        self.D2view.setXRange(-self.xRange , self.xRange )     #2D视图中的对象在此平面视图中的竖直方向相对比例:为0时,将是一条水平线(竖直向的全部图元压缩到此线上),数值表示当前控件水平方向X标尺的刻度,值越大,初始图象在此方向越小
        self.D2view.setYRange(-self.yRange, self.yRange)       #2D视图中的对象在此平面视图中的水平方向相对比例:为0时,将是一条竖向线(水平向的全部图元压缩到此线上),数值表示当前控件竖直方向Y标尺的刻度,值越大,初始图象在此方向越小
        self.D2view.update()

    #设置当前相机的3D观查位置
    def setCamerPosition(self,distance=100, elevation=45, azimuth=45):
        self.camerPos=np.array([distance,elevation,azimuth])   
    
    # 更新散点坐标和在两个视图上刷新显示(计时器绑定的此槽函数)
    def timeDrawView(self,showScale=None):
        self.moveCount+=1
        dirN=np.array([1,0,0])
        if(self.moveCount%50==0):
            self.ChgDir+=1
            if(self.ChgDir>4):self.ChgDir=1
        if(self.ChgDir==1 or self.ChgDir==4):
            dirN[0]=1
        elif(self.ChgDir==2 or self.ChgDir==3):
            dirN[0]=-1

        if(self.showType==-1):  #因DEMO要演示动画效果,需要在计时器定时不修改数据并更新到视中
            self.pos3Ds = self.pos3Ds + dirN      #*self.xyzRate)   #每次更新后散点 所有点的X坐标都加1,即沿X方向飞行
            if(self.bDrawPoint):
                self.glPoints.setData(pos=self.pos3Ds)               #散点集合
            if(self.bDrawLine):
                line3D = [self.pos3Ds[i] for i in self.pointLines[0]]   #三维散点重新连线,对DEMO,只有0序号的首尾相接数据
                self.line3Ds.setData(pos=np.array(line3D))              #更新连线数据
                
            #以下处理二维视图中的数据
            pos2D = self.pos3Ds[:, :2]   # 只保留二维坐标,将变化后的二维坐标存到一二维数据中
            if(self.bDrawPoint):
                self.glPoints2D.setData(pos=pos2D, brush=[mkBrush(color) for color in self.pointCols])
            if(self.bDrawLine):
                line2D = [pos2D[i] for i in self.pointLines[0]]   #更新二维散点连线
                self.line2Ds.setData(np.array(line2D))           #更新二维散点连线
                self.D2view.addItem(self.line2Ds)
            """
            #画出点序号
            if(self.bDrawPidTxt):   #暂只对2D图起作用,不对3D图起作用
                tid=0
                plotItem = self.D2view.getPlotItem()    #获取绘图区域
                plot=plotItem.plot()
                for point in  pos2D:
                    tx=point[0]
                    ty=point[1]
                    textItem = pg.TextItem(str(tid), self.def2DTxtCol)
                    textItem.setPos(tx, ty)       #设置文本位置
                    plotItem.addItem(textItem)     #将文本添加到绘图区
                    tid+=1
            """
        else:
            print('本函数只对定时器相关的DEMO数据用,暂不对其他加载点作定时作理')


    #按当前数据点重新画3D及2D到两个视中
    def reDrawView(self):
        """
        lstmatrix = self.D3view.viewMatrix()   # 获取3D视图的当前变换矩阵
        #得到两个视图的当前比例因子并在视图上显示出来
        lst3d = [0.,1.,2.,3.,4.,5.,6.,7.,8.,9.,10.,11.,12.,13.,14.,15.]
        lst3d = lstmatrix.copyDataTo()
        #print(f'当前3D预览视的比例:sx={lst3d[0]},sx={lst3d[1]},sz={lst3d[2]}')
        t2d = self.D2view.plotItem.getAxis('top').scale
        b2d = self.D2view.plotItem.getAxis('bottom').scale
        l2d = self.D2view.plotItem.getAxis('left').scale
        r2d = self.D2view.plotItem.getAxis('right').scale
        #print(f'当前2D预览视的比例:top={t2d},bottom={b2d},left={l2d},right={r2d}')
        """     
        #以下处理三维对象中的数据,因是动画,需要反复更新数据
        if(self.showType==-1):  #是导入的DEMO数据,此DEMO有动画显示,已打开打开计时器
            print('DEMO示例数据在timeDrawView中处理,不在本函数中处理')
            return
        else:  #非DEMO数据,只在用时调用一次
            self.D3view.clear()    #清除原3D视图上显示的点等对象
            self.D2view.clear()    #清除原2D视图上显示的点等对象
            #处理self.zRate变化后对Z向的陡缓显示,因点多时会对系统影响大,暂不支持,需要改时到mapData.py,更改self.zScale的值,当前用的是0.1值

            if(self.bDrawPoint): #是否画点
                #处理3D显示的散点
                gf_setStartTime()
                self.glPoints = gl.GLScatterPlotItem(pos=np.array([[0, 0, 0]] * self.pointCount), size=self.pointR, 
                                                            color=np.array(self.pointCols)/255.0)    # 初始化三维散点,散点颜色按设置列表,/255表示将RGB换算OPENGL中用的RGB比值
                self.glPoints.setData(pos=self.pos3Ds)               #将当前散点集合加入视中显示
                self.D3view.addItem(self.glPoints)                   #此方法,是增加点,如原有点没有作删除,会同新增加的点一同存在 
                gf_getRunTime('加载3D视图中画点用时为')  

                #处理2D显示的散点
                gf_setStartTime()
                self.glPoints2D = ScatterPlotItem(pos= self.pos2Ds, size=self.pointR,  # 初始化二维散点连线Item的颜色(全为0,在self.reDrawView()中再赋值)
                                                    pen=[mkPen(color) for color in self.pointCols],
                                                    brush=[mkBrush(color) for color in self.pointCols])   
                self.D2view.addItem(self.glPoints2D)    #二维散点加入到视
                gf_getRunTime('加载2D视图中画点用时为') 
            if(self.bDrawLine):
                gf_setStartTime()
                if(self.bDrawLine):
                    for line3D  in self.line3Ds: 
                        self.D3view.addItem(line3D)
                gf_getRunTime('加载3D视图中画线用时为')   

                gf_setStartTime() 
                d2n=0
                for line2D in self.line2Ds:                #此段代码用时最多,原因不详 
                    #此代码耗时太长,暂屏蔽,屏蔽后将不画线了self.D2view.addItem(line2D)            #将当前处理的连线数据加入到2D视的连线集合中,以使视可以显现出来   
                    d2n+=1
                print(f'加载2D视图中画线代码(耗时太长,原因不详)循环总数={d2n}')
                gf_getRunTime('加载2D视图中画线用时为')

            #处理在2D视上是否要画出原貌点序号
            if(self.bDrawPidTxt):   #暂只对2D图起作用,不对3D图起作用
                gf_setStartTime()
                tid=0
                plotItem = self.D2view.getPlotItem()    #获取绘图区域
                plot=plotItem.plot()
                for point in self.pos2Ds:
                    tx=point[0]
                    ty=point[1]
                    textItem = pg.TextItem(str(tid), self.def2DTxtCol)
                    textItem.setPos(tx, ty)       #设置文本位置
                    plotItem.addItem(textItem)     #将文本添加到绘图区
                    tid+=1
                gf_getRunTime('加载2D视图中画点号文本用时为')
        if(self.bUpdataGrid):
            self.updateGrid()    
    #打开数据文件导入数据:type=0:原始地貌文件   type=1:完成地貌文件  type=2,设计地形文件 type=-1Demo数据文件
    def loadMapData(self,mapDataFile,type=0):
        self.showType=type
        pg.setConfigOptions(antialias=True)  # 开启反锯齿
        if(type==-1):
            print('导入DEMO数据')
            self.pointR=15
            self.demoDat.loadMapData(mapDataFile,type)
            #根据导入的数据,初始化视图上的有关数据
            #重新设置2D视图上的网格尺寸
            self.gridSize=[100.0,100.0,100.0,10.0,10.0,10.0]
            #重新设置XYZ方向的最大最小值(在显示的视中)
            self.maxX = self.maxY = self.maxZ = 1.0 
            self.minX = self.minY = self.minZ = -1.0
            #重新设置XYZ向比例                                       
            #self.xyzRate[[0,1,2]]=self.rateX,self.rateY,self.rateZ 此功能已移入mapData.py模块中了
            #重新设置2D视图上的标尺比例
            self.xRange=20    #在2D视,此图越小,图像显示的越大
            self.yRange=20
            self.camerPos=np.array([100,45,45]) #仍采用默认值
            if(self.bUpdataGrid):
                self.updateGrid()  #更新3D 2D视基本框架
            self.showDemo1()   #显示DEMO1的数据到视图

        elif(type==0):
            print(f'导入原貌测绘数据{mapDataFile}')
            self.pointR=5     #此值也应用DAT的点范围动态变化
            if(self.timerDemo!=None):
                self.timerDemo.stop()
            #self.D3view.clear()    #清除原3D视图上显示的点等对象
            #self.D2view.clear()    #清除原2D视图上显示的点等对象
            self.mapOrgDat.loadMapData(mapDataFile,type)
            self.pointCount=self.mapOrgDat.pointCount
            self.pos3Ds,self.pointLines,self.pointCols=self.mapOrgDat.getMapData()
            self.pos2Ds=self.pos3Ds[:, :2]   #同时更新2D数据
            #根据导入的数据,初始化视图上的有关数据
            #重新设置2D视图上的网格尺寸及视距
            self.gridSize=self.mapOrgDat.gridSize
            self.xRange=100*self.mapOrgDat.xScale       #使网格可在正确的显示
            self.yRange=100*self.mapOrgDat.yScale
            self.camerPos[0]=100*self.mapOrgDat.zRate   #调整相机位置,以使3D图在初次显示时可尽可能大的显示
            """
            #重新设置XYZ方向的最大最小值
            self.maxX = self.mapOrgDat.maxX
            self.maxY = self.mapOrgDat.maxY
            self.maxZ = self.mapOrgDat.maxZ
            self.minX = -1*self.mapOrgDat.maxX
            self.minY = -1*self.mapOrgDat.maxY
            self.minZ = -1*self.mapOrgDat.maxZ
            #重新设置XYZ向不等比的拉伸倍率
            self.rateX=self.mapOrgDat.xScale
            self.rateY=self.mapOrgDat.yScale
            self.xRange=100*self.rateX
            self.yRange=100*self.rateY
            self.rateZ=self.mapOrgDat.zRate         #将Z向加大,以放大高程变化的3D显示 
            """
            #以下对3D图数据初始化
            gf_setStartTime()
            if(self.glPoints!=None):
                pass
            #处理3D显示的线,散点处理已移入reDrawView函数中了
            self.line3Ds = []
            for lineID in self.pointLines:
                #构建3D视中的连线
                line3D = [self.pos3Ds[i] for i in lineID]   #三维散点重新连线
                line_3Ds = gl.GLLinePlotItem(pos=np.array([[0, 0, 0]]), color=(self.defLinecolor1/255.0).repeat(len(self.pos3Ds),axis = 0))  # 初始化三维散点连线color=(1.0, 1.0, 0.0, 1)          
                line_3Ds.setData(pos=np.array(line3D))     #更新首尾相连线数据 
                self.line3Ds.append(line_3Ds)
            gf_getRunTime('3D点及线处理时间为')  
#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$测试: 构建三角形面片
            """
            # 添加面
            # 生成三维散点数据
            np.random.seed(0)
            points = np.random.rand(10, 3)

            # 构建三角形面片()
            triangles = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 0, 1]])

            # 创建图形对象
            fig = plt.figure()
            ax = fig.add_subplot(111, projection='3d')

            # 绘制三角形面片
            poly = Poly3DCollection(points[triangles], alpha=0.25)
            ax.add_collection3d(poly)

            # 绘制散点
            ax.scatter(points[:, 0], points[:, 1], points[:, 2], c='r')

            # 设置坐标轴标签
            ax.set_xlabel('X')
            ax.set_ylabel('Y')
            ax.set_zlabel('Z')
            #plt.show()


            # 将Matplotlib图表转换为QImage
            fig1 = plt.gcf()
            fig1.canvas.draw()
            image = np.frombuffer(fig1.canvas.tostring_rgb(), dtype=np.uint8).reshape(fig1.canvas.get_width_height()[::-1] + (3,))
            w,h=fig1.canvas.get_width_height()
            qimage = QtGui.QImage(image, w, h, QtGui.QImage.Format_RGB888)
            
           
            # 创建一个图像项并设置其尺寸
            img = gl.GLImageItem(qimage)
            #img.setScale(1.0)
            # 将图像添加到OpenGL场景中
            self.D3view.addItem(img)
            """
            """
            faces = np.random.normal(size=(100, 3), scale=1e-1)
            faceColors = np.ones((10, 3))  # 颜色为白色
            mesh = gl.GLMeshPlotItem(vertexes=faces, faces=np.arange(0, 100, 4), faceColors=faceColors, smooth=False)
            vw.addItem(mesh)
            """
                        
#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$43
            #以下对二维图进行数据初始化 
            #处理线
            gf_setStartTime()
            self.line2Ds = []
            for lineID in self.pointLines:
                #self.glPoints2D=np.arange(0,len(pos2Ds))                     #测试,将全部点连接完OK
                self.glPoints2D=lineID
                line_2d = [self.pos2Ds[i] for i in lineID]               #转换对应下标的点为相应的XY坐标(nx2维数组)
                line_2ds= PlotDataItem(np.array([0, 0]), pen=mkPen(self.defLinecolor1[0]),
                                        width=self.lineWidth, antialias=False)
                line_2ds.setData(np.array(line_2d))                             #二维散点连线数组数据更新入上面建立的对象
                self.line2Ds.append(line_2ds)
            gf_getRunTime('2D点处理时间为')
            self.reDrawView()  #更新数据到视中
            return True
        elif(type==1):
            print('导入成形地貌测绘数据')
            bOk=self.mapEndDat.loadMapData(mapDataFile,type)
            self.pointCount=self.mapEndDat.pointCount

        elif(type==2):
            print('导入设计地貌测绘数据')
            return self.mapDesignDat.loadMapData(mapDataFile,type)
    
    #若无数据加载,显示DEMO数据
    def showDemo1(self):     
        if(self.bUpdataView):
            self.D3view.clear()    #清除原3D视图上显示的点等对象
            self.D2view.clear()    #清除原2D视图上显示的点等对象
            if(self.timerDemo!=None):
                self.timerDemo.stop()
            self.showType=-1
            self.rateZ = 1 
            self.moveCount=0   #DEMO示例程序用
            self.moveN=0
            self.ChgDir=1
            self.pointCount=self.demoDat.pointCount
            self.pos3Ds,self.pointLines,self.pointCols=self.demoDat.getMapData()  #对DEMO数据self.pointLines只有0序号数组,首尾相接一数据
                 #以下对3D图数据初始化
            self.glPoints = gl.GLScatterPlotItem(pos=np.array([[0, 0, 0]] * self.pointCount), size=self.pointR, 
                                                        color=np.array(self.pointCols)/ 255)  # 初始化三维散点Item,散点颜色按设置列表,/255表示将RGB换算OPENGL中用的RGB比值
            if(self.bDrawPoint):
                self.D3view.addItem(self.glPoints)   
            
            self.line3Ds = gl.GLLinePlotItem(pos=np.array([0, 0, 0]), color=(1.0, 1.0, 0.0, 1))  # 初始化三维散点连线Item  
            if(self.bDrawLine):
                self.D3view.addItem(self.line3Ds)   #将连线对象列表加入到视中
    
            #以下对二维图进行数据初始化 
            self.glPoints2D = ScatterPlotItem(pos=np.array([[0, 0]] * self.pointCount), size=self.pointR,  # 初始化二维散点连线Item的颜色
                                                pen=[mkPen(color) for color in self.pointCols],
                                                brush=[mkBrush(color) for color in self.pointCols])   
            if(self.bDrawPoint):
                self.D2view.addItem(self.glPoints2D)  

            self.line2Ds = PlotDataItem(np.array([0, 0]), pen=mkPen(self.defPointcolor1[0]),
                                        width=self.lineWidth, antialias=False)  # 初始化二维散点连线Item
            if(self.bUpdataGrid):
                self.updateGrid()
            self.bUpdataView=False     
            self.timeDrawView()                                   #刷新显示的初始化数据到视图上
            self.timerDemo.start(20)                              #设置定时器周期为500ms,无计时器时,显示的只是初始化数据
        self.bUpdataView=False                                    #防止点击切换点线面显示时,不重复加载上述代码
      

用于处理地形数据的类模块代码文件mapData.py

#mapData.py  处理测绘数据,并将测绘数据加工成3D显示用数据和计算用计算数据
import sys
import math
from math import *
from  g import *             #导入自定义的一些全局变量和函数
import numpy as np          #数据组用库
from scipy.spatial import Delaunay
from PyQt5.QtWidgets import  QMessageBox

DATFILE_LISTNUM=5  #dat文件的每行文本包含的数据数量
class mapData():
    def __init__(self,width=0,height=0,type=0):
        self.bhasDat=False  #无导入数据时,采用DEMO数据在视图中显示
        self.pointCount=0   #当前加载的数据的总点数
        self.gridSize=[10,10,1,0.1,0.1,0.1]  #3D底图上的网格尺寸
        self.defPointcolor0 = np.array([[255, 0, 0, 255]])    #默认在Z0以下显示的点的RGB颜色
        self.defLinecolor0 = np.array([[0, 255, 255, 255]])     #默认在Z0以下显示的点连线的RGB颜色
        self.defPointcolor1 = np.array([[255, 0, 0, 255]])    #默认在Z0以上显示的点的RGB颜色
        self.defLinecolor1 = np.array([[0, 255, 255, 255]])     #默认在Z0以下显示的点连线的RGB颜色 

        self.width=width    #显示这些数据的窗体控件当前宽度,用于计算显示比例
        self.height=height  #显示这些数据的窗体控件当前高度,用于计算显示比例
        self.datType=type  #测绘数据分以下几种类型,0=原始地貌测绘数据,1=完成貌测绘数据,2=设计地形数据 -1=DEMO数据
        self.maxX = self.maxY = self.maxZ = 1.0
        self.minX = self.minY = self.minZ = -1.0
        self.xScale=self.yScale=1.0
        self.zScale = 1.0                      #在3D视图中各坐标的相对显示比例(如是加强显示某一方向的不同,可以加大此方向的比例值)
        self.zRate  = 0.15                     #因XY在3D面分配到-1至1,Z如也在-1到1中分配,将会使Z值很陡峭,
        self.trangeCount = 0  #本数据对应的三角形总数量

        self.oneorgdata=np.array([[0.,0.,0.,0.,0.]],float)         #对应self.d3_orgxyzs中的一个数据形状
        self.d3_orgxyzs=self.oneorgdata.repeat(1,axis = 0)        #用于在视中绘制3D图象的所有点数据(未加工转换成坐标前的数据),此数据格式 [(id,部位类型,x1, y1, z1), (id,部位类型,x2, y2, z2),.....]
        self.d3_3xyz= np.array([0.0,0.0,0.0])                     #每个三角形的三个数据(对应于self.d3_xyzs的一个数据)
        self.d3_4xyz= np.array([0.0,0.0,0.0,0.0])                 #每个四边的四个数据
        self.d3_positionCol=np.empty((1, 4),float)                #用于在视中绘制3D图象的所有顶点的着色器,数据格式[R,G,B,H],值为0-1间,为各色彩的比例
        self.d3_shadeCol=np.empty((1, 4),float)                   #用于在视中绘制3D图象的所有三角形区域片段的着色器,数据格式[R,G,B,H],值为0-1间,为各色彩的比例
        self.d3_lightCol=np.empty((1, 4),float)                   #用于在视中绘制3D图象的所有三角形区域灯光的着色器,数据格式[R,G,B,H],值为0-1间,为各色彩的比例
        self.d3_toyCol=np.empty((1, 4),float)                     #用于在视中绘制3D图象的所有三角形区域光源的着色器,数据格式[R,G,B,H],值为0-1间,为各色彩的比例

        self.d3_3xyzs = np.empty((1, 3),float)                     #用于在视中绘制3D图象的所有点数据(加工后的数据),此数据格式同上,但数据值均已处理在-1到1之间的浮点数了
        self.pointLines = [[]]                                     # 散点连线规则(每一个[]内的数据首位相接,但[]分隔开的不首位相接,中[1,2,3],[80,81],表示1连2连3,80连81,共3条线)
        self.lonePoint= []                                         #连成三角形后,没有被使用的孤点集合(点的序号)
        #定义三维数组,每行保存一个完整的三角型数据(真实数据):0= 三角形顶点1坐标    1=三角形顶点2坐标  2=三角形顶点2坐标   3=三角形对应的顶点着色器   4=三角形面域着色器      5=三角形区域灯光的着色器     6=光源的着色器                 7=扩展1                       8=扩展1
        self.trangeSpape=np.array([[[float,float,float,float],[float,float,float,float],[float,float,float,float],[float,float,float,float],[float,float,float,float],[float,float,float,float],[float,float,float,float],[float,float,float,float],[float,float,float,float]]])
        self.trangles=np.array(self.trangeSpape)                   #所有显示的三角形数据总集合,导入文件数据后确定数量
        self.tranglesID=np.empty((1,3),int)                        #本数组对应self.tranglesr的二维,保存三角形的三个点序号下标数
    ######################################################################

    #导入测绘数据
    def loadMapData(self,mapDataFile,type=0):
        gf_setStartTime()
        self.datType=type
        if(self.datType==-1):
            print('当前为DEMO演示程序,不用打开文件,直接设置DEMO演示数据')
            self.setDemo1Data()
            return
        s=''
        lstRowData=[]
        lstData=[]
        index=0
        if(os.path.exists(mapDataFile)):
            rf = open(mapDataFile,'r',encoding='utf-8')
            lindS=''
            for lineS in rf.readlines():
                lstRowData.clear()
                s=lineS.strip()
                s=s.replace("\n","")  #将从文件中读出的\n删除,此语句可能会报异常
                s=s.replace("\r","")  #将从文件中读出的\r删除,此语句可能会报异常
                if(s==''):continue  #去空行
                lstRowData = s.split(',')
                if(len(lstRowData)!=DATFILE_LISTNUM):continue  #去格式不对的行(要求每行5个数据,4个逗号)
                lstRowData[0]=index  #重新为数据编号
                if(len(str(lstRowData[1]))!=0): 
                    lstRowData[1]=1 #对第二个参数不为空时,改其值为1,否则为0
                else:
                    lstRowData[1]=0
                lstRowData[2]=float(lstRowData[2])   #dat文件中的坐标x坐标
                lstRowData[3]=float(lstRowData[3])   #dat文件中的坐标y坐标
                lstRowData[4]=float(lstRowData[4])   #dat文件中的坐标z坐标
                lstData.append(copy.deepcopy(lstRowData))
                index+=1
            rf.close
            self.pointCount=len(lstData)          #记录本数据文件的总坐标点数
            self.d3_orgxyzs=np.array(lstData)     #保存全部的np原始数据
            #开始处理原始数据
            self.makeMapData()  
            print(f'导入测绘数据文件"{mapDataFile}"成功!')  
            gf_getRunTime('导入数据文件{mapDataFile}用时为')
            return True
        return False
    
    #得到原始数据的值
    def getOrgPointValue(self,id):
        if(id>=0 and id<self.pointCount):
            return int(self.d3_orgxyzs[id][1])
        return -1
    #得到一个三角形数据的默认值数组(三维)
    def getdefOnenageles(self):
        #定义三维数组,每行保存一个完整的三角型数据(真实数据):0-2=三角形顶点坐标     3=三角形对应的顶点着色器   4=三角形面域着色器      5=三角形区域灯光的着色器     6=光源的着色器                 7=扩展1                       8=扩展1
        onetrnageles=np.array([[[0.,0.,0.,0.],[0.,0.,0.,0.],[0.,0.,0.,0.],self.defPointcolor0[0],self.defLinecolor0[0],[0.,0.,0.,0.],[0.,0.,0.,0.],[0.,0.,0.,0.],[0.,0.,0.,0.]]])
        return onetrnageles
    #将导入的数据从self.d3_orgxyzs处理成所有np结构数据
    def makeMapData(self):
        self.minX=np.amin(self.d3_orgxyzs[:,2])  
        self.maxX=np.amax(self.d3_orgxyzs[:,2])
        self.minY=np.amin(self.d3_orgxyzs[:,3])
        self.maxY=np.amax(self.d3_orgxyzs[:,3])
        self.minZ=np.amin(self.d3_orgxyzs[:,4])
        self.maxZ=np.amax(self.d3_orgxyzs[:,4])
        self.xyzMin=np.array([self.minX,self.minY,self.minZ])                 #真实坐标的最小值结构
        self.xyzMax=np.array([self.maxY,self.maxY,self.maxY])                 #真实坐标的最大值结构
        self.xScale=2/(self.maxX-self.minX)
        self.yScale=2/(self.maxY-self.minY)
        self.zRate=sqrt(((self.maxZ-self.minZ)/(self.maxX-self.minX))**2+((self.maxZ-self.minZ)/(self.maxY-self.minY)**2))
        print(f'当前导入的数据的Z向附加比例self.zRate={self.zRate}')
        self.zScale=2/(self.maxZ-self.minZ)*self.zRate     #在-1到1中分配Z将会使三角面很陡峭,附加一消陡系数self.zRate

        self.gridSize[0]=(self.maxX-self.minX)*self.xScale*1.5
        self.gridSize[1]=(self.maxY-self.minY)*self.yScale*1.5
        self.gridSize[2]=(self.maxZ-self.minZ)*self.zScale*1.5
        self.gridSize[3]=5*self.xScale
        self.gridSize[4]=5*self.yScale
        self.gridSize[5]=-0.5*(self.maxZ-self.minZ)*self.zScale/self.zRate   #此参数要调整到最小那个坐标值就同网格的底面,在dD3D2View中还用一类似设置比例也控制Z向显示

        self.xyzToGL=np.array([self.xScale,self.yScale,self.zScale]) #真实坐标转换为GL绘图坐标np结构

        #真实坐标转换成视图显示坐标
        self.pxyzs=self.d3_orgxyzs[:, [2,3,4]]
        self.d3_3xyzs=(-1+(self.pxyzs-self.xyzMin)*self.xyzToGL)   #此代码计算是否正确,应复核,显示倍率在D3C2View中定义     
       
        #因本程序主要计算方格网的方量,故用二维点来连成三角网即可    
        #points=输入散点,furthest_site=True时,计算最远点incremental=True时,允许增量添加点, qhull_options=qhull参数,具体可参考qhull
        #tri3D=Delaunay(self.d3_3xyzs)       #此行仅为测试,程序不使用3D下的三角网,创建3D的Delaunay三角部分
        pos2Ds = self.d3_3xyzs[:, :2]       # 只保留二维坐标,将变化后的二维坐标存到一二维数据中

        #tri2D=tri3D.simplices[:, :3]      #trangle3pID = tri2D    #self.tranglesID=tri2D.copy()             #测试:用三维的三角形取值来画二维连点三角形:三角形太多不可用
        tri2D=Delaunay(pos2Ds,False,True)  #创建2DDelaunay三角部分:示例文件44个点生成72个2D三角形存在外围局部点并没有组网????????????????????????????
        trangle3pID = tri2D.simplices                         #当前值为三角形三个顶点的序号下标数,因绘图是三个点画两根线,存在可能差一根线不绘,有些线又重复绘的现象.需要在下面再处理,后写入self.pointLines
        self.tranglesID=tri2D.simplices.copy()                #对应self.trangles中的局部数据的二维,只保存点的下标数,每个下标数对应三个坐标通过函数来转换,

        rowID=0
        print(f'优化前三角形数量={len(self.tranglesID)}')
        self.pointLines.clear()  #清空连线点集
        onetrnageles=self.getdefOnenageles()                            #np.empty((1,9,4),float) #一个三角形的全部数据结构
        self.trangles=self.getdefOnenageles().copy()                    #重新定三维空数组(三角形总数,9,4)
        dic2w={}     #用于记录点一点画线的次数,key为小点到大点号n_m:num
        dicpoint={}  #记录点对外画线次数,判断是不还有孤点没组成三角形,有的话提示用户通加插点的方式完成处理
        self.lonePoint.clear()
        for p3s in trangle3pID:     #得到每个三角形三个点下标
            #Pa = trangle3pID.take(indices = i,axis = 1)    #按列抽取数据:当前是点的下标值(每个下标值对应三个坐标XYZ,需转换)
            xyz1 = [self.pxyzs[i] for i in p3s]              #转换对应下标的点为相应的XYZ坐标(nx2维数组)
            xyz2=np.array(xyz1)
            new_column = np.array([[p3s[0]],[p3s[1]],[p3s[2]]])
            xyz=np.c_[xyz2,new_column]  #为同self.trangles一致,增加一列无效数据(存的是点序号)
            id1=p3s[0]
            id2=p3s[1]
            id3=p3s[2]
            p1=self.getOrgPointValue(id1)
            p2=self.getOrgPointValue(id2)
            p3=self.getOrgPointValue(id3)
            if(p1==1 and p2==1 and p3==1):    #点是否是外围点,如三个都是外围点时,不画此线,也不建立此三角形
                continue 
            pstr1=str((min(id1,id2)))+'_'+str((max(id1,id2)))
            pstr2=str((min(id1,id3)))+'_'+str((max(id1,id3)))
            pstr3=str((min(id3,id2)))+'_'+str((max(id3,id2)))
            v1=dic2w.get(pstr1,None)
            v2=dic2w.get(pstr2,None)
            v3=dic2w.get(pstr3,None)
            if(v1==None):
                dic2w[pstr1]=1
                oneline=[id1,id2]
                self.pointLines.append(oneline)
                dicpoint[id1]=dicpoint.get(id1,0)+1
                dicpoint[id2]=dicpoint.get(id2,0)+1
            if(v2==None): 
                dic2w[pstr2]=1
                oneline=[id1,id3]
                self.pointLines.append(oneline)   
                dicpoint[id1]=dicpoint.get(id1,0)+1
                dicpoint[id3]=dicpoint.get(id3,0)+1
            if(v3==None): 
                dic2w[pstr3]=1
                oneline=[id2,id3]
                self.pointLines.append(oneline)
                dicpoint[id2]=dicpoint.get(id2,0)+1
                dicpoint[id3]=dicpoint.get(id3,0)+1
            if(rowID!=0):
                self.trangles = np.vstack((self.trangles, onetrnageles))  #增加一行
            #将本三角形更新到self.trangles数据结构中
            self.trangles[rowID][0]=xyz[0].copy()
            self.trangles[rowID][1]=xyz[1].copy()
            self.trangles[rowID][2]=xyz[2].copy()          
            rowID+=1
        print(f'优化前三角形数量={rowID}')
        for n in range(self.pointCount):
            if(dicpoint.get(n,0)==0):
                self.lonePoint.append(n)
        if(len(self.lonePoint)>0):
           reply = QMessageBox.information(None,'警告',f"当前地形测绘点中中存在没有连成三角形的孤点,请判断是否有计算用途,如需要计算,可以在点边插入附加点来处理\n,孤点序列{self.lonePoint}",QMessageBox.Yes | QMessageBox.No,QMessageBox.Yes)
      

        #测试行self.pointLines = self.tranglesID.tolist()  #测试加工前后的三角网变化,还未对trangle3pID进行加工,有漏画线的现象(self.pointLines原类型为列表,此行后变成了np,要用tolist转换,否则后面要出错)
        
        self.d3_positionCol = self.defPointcolor1.repeat(self.pointCount,axis=0) #对非DEMO状态,点的颜色采用默认色,未作不同类型不同颜色的扩展

    #设置当前数据为DEMO1类型数据 
    def setDemo1Data(self):
        self.pointCount=18  #本DEMO对应18个示例点
        del self.d3_3xyzs  #先删除,再重新定义
        self.d3_3xyzs = np.empty((self.pointCount, 3))   #散点数据:18行3列,共18个数据
        self.d3_3xyzs[:] = [(0, 2, 5), (0, -2, 5), (2, 0, 7), (5, 0, 6), (8, 3, 4), (7, 5, 2), (6, 7, 0), (8, -3, 4),
                       (7, -5, 2), (6, -7, 0), (12, 3, 4), (11, 5, 2), (10, 7, 0), (12, -3, 4),
                       (11, -5, 2), (10, -7, 0), (12, 0, 4), (13, 0, 1)]  # 初始化散点坐标
        self.pointLines = [[0, 2, 1, 2, 3, 4, 5, 6, 5, 4, 7, 8, 9, 8, 7, 3, 7, 13, 14, 15, 14, 
                              13, 16, 17, 16, 10, 4, 10, 11, 12]]  # 散点连线规则(首尾相接,且只有第一组数据)
        #本DEMO对应的18个点的颜色结构
        del self.d3_positionCol
        #对DEMO,设置每个点的颜色均不相同
        self.d3_positionCol = [(10, 36, 45, 255), (32, 120, 186, 255), (50, 25, 28, 255), (79, 182, 62, 255), (90, 27, 32, 255),
                      (48, 129, 196, 255), (255, 127, 0, 255), (255, 119, 129, 255), (58, 157, 47, 255), 
                      (124, 85, 147, 255), (38, 123, 191, 255), (235, 154, 100, 255), (207, 179, 205, 255),
                      (127, 185, 158, 255), (235, 40, 50, 255), (250, 180, 108, 255), (250, 200, 120, 255), 
                      (250, 220, 140, 255)]  # 散点颜色
        #self.d3_positionCol=self.self.defPointcolor0.repeat(self.pointCount,axis = 0)   #也可用一种颜色复制18个self.defPointcolor0 = np.array([[0, 0, 255, 255]])   #默认在Z0以下显示的点的RGB颜色

    #得到当前数据    
    def getMapData(self):    
        return self.d3_3xyzs,self.pointLines,self.d3_positionCol

另外三个模块文件

Ui_MainWindow.py:主窗口ui文件编绎而成的py界面代码文件

 -*- coding: utf-8 -*-

# Form implementation generated from reading ui file './ui/MainWindow.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# 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

#下面三行是在将ui设计文件编绎成py代码文件后,从MainWindwo.py中COPY过来的
from D3D2View import *     #导入可预览3D和2D的视图类(用于self.view3D)
from  QLabelEx import *            #导入自定义的彩色标签库
from  QTableWidgetEx import *      #导入自定义的表格控件库

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1920, 990)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
        self.tabWidget.setGeometry(QtCore.QRect(1049, 30, 841, 880))
        self.tabWidget.setTabShape(QtWidgets.QTabWidget.Triangular)
        self.tabWidget.setIconSize(QtCore.QSize(32, 32))
        self.tabWidget.setObjectName("tabWidget")
        self.tab_orgData = QtWidgets.QWidget()
        self.tab_orgData.setObjectName("tab_orgData")
        self.labInfo1 = QtWidgets.QLabel(self.tab_orgData)
        self.labInfo1.setGeometry(QtCore.QRect(10, 840, 621, 20))
        self.labInfo1.setFrameShape(QtWidgets.QFrame.Box)
        self.labInfo1.setObjectName("labInfo1")
        self.tableOrg = QTableWidgetEx(self.tab_orgData)
        self.tableOrg.setGeometry(QtCore.QRect(10, 10, 821, 821))
        self.tableOrg.setObjectName("tableOrg")
        self.tableOrg.setColumnCount(0)
        self.tableOrg.setRowCount(0)
        self.tabWidget.addTab(self.tab_orgData, "")
        self.tab_enddata = QtWidgets.QWidget()
        self.tab_enddata.setObjectName("tab_enddata")
        self.labInfo2 = QtWidgets.QLabel(self.tab_enddata)
        self.labInfo2.setGeometry(QtCore.QRect(10, 840, 821, 16))
        self.labInfo2.setFrameShape(QtWidgets.QFrame.Box)
        self.labInfo2.setObjectName("labInfo2")
        self.tableEnd = QTableWidgetEx(self.tab_enddata)
        self.tableEnd.setGeometry(QtCore.QRect(10, 10, 821, 831))
        self.tableEnd.setObjectName("tableEnd")
        self.tableEnd.setColumnCount(0)
        self.tableEnd.setRowCount(0)
        self.tabWidget.addTab(self.tab_enddata, "")
        self.tabDesignData = QtWidgets.QWidget()
        self.tabDesignData.setObjectName("tabDesignData")
        self.labInfo3 = QtWidgets.QLabel(self.tabDesignData)
        self.labInfo3.setGeometry(QtCore.QRect(0, 840, 831, 16))
        self.labInfo3.setFrameShape(QtWidgets.QFrame.Box)
        self.labInfo3.setObjectName("labInfo3")
        self.tableDesign = QTableWidgetEx(self.tabDesignData)
        self.tableDesign.setGeometry(QtCore.QRect(10, 10, 821, 831))
        self.tableDesign.setObjectName("tableDesign")
        self.tableDesign.setColumnCount(0)
        self.tableDesign.setRowCount(0)
        self.tabWidget.addTab(self.tabDesignData, "")
        self.show3DView = QtWidgets.QStackedWidget(self.centralwidget)
        self.show3DView.setGeometry(QtCore.QRect(9, 39, 1031, 871))
        self.show3DView.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.show3DView.setObjectName("show3DView")
        self.page = QtWidgets.QWidget()
        self.page.setObjectName("page")
        self.view3D = D3Widget(self.page)
        self.view3D.setGeometry(QtCore.QRect(0, 18, 1031, 840))
        self.view3D.setObjectName("view3D")
        self.lab3DView = QtWidgets.QLabel(self.page)
        self.lab3DView.setGeometry(QtCore.QRect(0, 0, 111, 16))
        self.lab3DView.setObjectName("lab3DView")
        self.chkPoint = QtWidgets.QCheckBox(self.page)
        self.chkPoint.setGeometry(QtCore.QRect(150, 0, 91, 19))
        self.chkPoint.setObjectName("chkPoint")
        self.chkLine = QtWidgets.QCheckBox(self.page)
        self.chkLine.setGeometry(QtCore.QRect(280, 0, 91, 19))
        self.chkLine.setObjectName("chkLine")
        self.chkPlan = QtWidgets.QCheckBox(self.page)
        self.chkPlan.setGeometry(QtCore.QRect(560, 0, 91, 19))
        self.chkPlan.setObjectName("chkPlan")
        self.hSliderZ = QtWidgets.QSlider(self.page)
        self.hSliderZ.setGeometry(QtCore.QRect(680, 0, 191, 22))
        self.hSliderZ.setMinimum(1)
        self.hSliderZ.setMaximum(100)
        self.hSliderZ.setProperty("value", 50)
        self.hSliderZ.setOrientation(QtCore.Qt.Horizontal)
        self.hSliderZ.setTickPosition(QtWidgets.QSlider.TicksBelow)
        self.hSliderZ.setObjectName("hSliderZ")
        self.chkTxt = QtWidgets.QCheckBox(self.page)
        self.chkTxt.setGeometry(QtCore.QRect(430, 0, 91, 19))
        self.chkTxt.setObjectName("chkTxt")
        self.Btn_Next = QtWidgets.QPushButton(self.page)
        self.Btn_Next.setGeometry(QtCore.QRect(970, 0, 31, 16))
        self.Btn_Next.setIconSize(QtCore.QSize(32, 17))
        self.Btn_Next.setObjectName("Btn_Next")
        self.show3DView.addWidget(self.page)
        self.page_2 = QtWidgets.QWidget()
        self.page_2.setObjectName("page_2")
        self.labgl = QtWidgets.QLabel(self.page_2)
        self.labgl.setGeometry(QtCore.QRect(10, 50, 91, 16))
        self.labgl.setObjectName("labgl")
        self.show3DView.addWidget(self.page_2)
        self.tBtn01_OpenOrgData = QtWidgets.QPushButton(self.centralwidget)
        self.tBtn01_OpenOrgData.setGeometry(QtCore.QRect(1, 0, 32, 32))
        self.tBtn01_OpenOrgData.setText("")
        self.tBtn01_OpenOrgData.setObjectName("tBtn01_OpenOrgData")
        self.tBtn02_OpenEndData = QtWidgets.QPushButton(self.centralwidget)
        self.tBtn02_OpenEndData.setGeometry(QtCore.QRect(34, 0, 32, 32))
        self.tBtn02_OpenEndData.setText("")
        self.tBtn02_OpenEndData.setObjectName("tBtn02_OpenEndData")
        self.tBtn03_OpenDesignData = QtWidgets.QPushButton(self.centralwidget)
        self.tBtn03_OpenDesignData.setGeometry(QtCore.QRect(68, 0, 32, 32))
        self.tBtn03_OpenDesignData.setText("")
        self.tBtn03_OpenDesignData.setObjectName("tBtn03_OpenDesignData")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1920, 26))
        self.menubar.setObjectName("menubar")
        self.menu = QtWidgets.QMenu(self.menubar)
        self.menu.setObjectName("menu")
        self.menuLoadFile = QtWidgets.QMenu(self.menu)
        self.menuLoadFile.setObjectName("menuLoadFile")
        self.menuMakeCountFile = QtWidgets.QMenu(self.menu)
        self.menuMakeCountFile.setObjectName("menuMakeCountFile")
        self.menu_2 = QtWidgets.QMenu(self.menubar)
        self.menu_2.setObjectName("menu_2")
        self.menu_3 = QtWidgets.QMenu(self.menubar)
        self.menu_3.setObjectName("menu_3")
        self.menu_4 = QtWidgets.QMenu(self.menubar)
        self.menu_4.setObjectName("menu_4")
        self.menu_5 = QtWidgets.QMenu(self.menubar)
        self.menu_5.setObjectName("menu_5")
        self.menu_6 = QtWidgets.QMenu(self.menubar)
        self.menu_6.setObjectName("menu_6")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.actLoadOrgData = QtWidgets.QAction(MainWindow)
        self.actLoadOrgData.setObjectName("actLoadOrgData")
        self.actLoadEndData = QtWidgets.QAction(MainWindow)
        self.actLoadEndData.setObjectName("actLoadEndData")
        self.actAppendData = QtWidgets.QAction(MainWindow)
        self.actAppendData.setObjectName("actAppendData")
        self.actionDraw3D = QtWidgets.QAction(MainWindow)
        self.actionDraw3D.setObjectName("actionDraw3D")
        self.actCount = QtWidgets.QAction(MainWindow)
        self.actCount.setObjectName("actCount")
        self.actMakeOrgDataFile = QtWidgets.QAction(MainWindow)
        self.actMakeOrgDataFile.setObjectName("actMakeOrgDataFile")
        self.actMakeEndDataFile = QtWidgets.QAction(MainWindow)
        self.actMakeEndDataFile.setObjectName("actMakeEndDataFile")
        self.actExit = QtWidgets.QAction(MainWindow)
        self.actExit.setObjectName("actExit")
        self.actDelData = QtWidgets.QAction(MainWindow)
        self.actDelData.setObjectName("actDelData")
        self.actTools = QtWidgets.QAction(MainWindow)
        self.actTools.setObjectName("actTools")
        self.actHelp = QtWidgets.QAction(MainWindow)
        self.actHelp.setObjectName("actHelp")
        self.actAbout = QtWidgets.QAction(MainWindow)
        self.actAbout.setObjectName("actAbout")
        self.menuLoadFile.addAction(self.actLoadOrgData)
        self.menuLoadFile.addAction(self.actLoadEndData)
        self.menuMakeCountFile.addAction(self.actMakeOrgDataFile)
        self.menuMakeCountFile.addAction(self.actMakeEndDataFile)
        self.menu.addAction(self.menuLoadFile.menuAction())
        self.menu.addAction(self.menuMakeCountFile.menuAction())
        self.menu.addAction(self.actExit)
        self.menu_2.addAction(self.actAppendData)
        self.menu_2.addAction(self.actDelData)
        self.menu_3.addAction(self.actionDraw3D)
        self.menu_4.addAction(self.actCount)
        self.menu_5.addAction(self.actTools)
        self.menu_6.addAction(self.actHelp)
        self.menu_6.addAction(self.actAbout)
        self.menubar.addAction(self.menu.menuAction())
        self.menubar.addAction(self.menu_2.menuAction())
        self.menubar.addAction(self.menu_3.menuAction())
        self.menubar.addAction(self.menu_4.menuAction())
        self.menubar.addAction(self.menu_5.menuAction())
        self.menubar.addAction(self.menu_6.menuAction())

        self.retranslateUi(MainWindow)
        self.tabWidget.setCurrentIndex(0)
        self.show3DView.setCurrentIndex(0)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.labInfo1.setText(_translate("MainWindow", "原始地貌测绘数据"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_orgData), _translate("MainWindow", "原始地貌测绘数据"))
        self.labInfo2.setText(_translate("MainWindow", "成型貌测绘数据"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_enddata), _translate("MainWindow", "成型貌测绘数据"))
        self.labInfo3.setText(_translate("MainWindow", "设计成型数据"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabDesignData), _translate("MainWindow", "设计数据"))
        self.view3D.setToolTip(_translate("MainWindow", "设置三维Z坐标显示比例,调整显示的陡缓程度"))
        self.lab3DView.setText(_translate("MainWindow", "3D地形预览视图"))
        self.chkPoint.setText(_translate("MainWindow", "显示点"))
        self.chkLine.setText(_translate("MainWindow", "显示线"))
        self.chkPlan.setText(_translate("MainWindow", "显示面"))
        self.chkTxt.setText(_translate("MainWindow", "显示点号"))
        self.Btn_Next.setToolTip(_translate("MainWindow", "下一页面"))
        self.Btn_Next.setText(_translate("MainWindow", "->"))
        self.labgl.setText(_translate("MainWindow", "3D编辑视图"))
        self.menu.setTitle(_translate("MainWindow", "文件"))
        self.menuLoadFile.setTitle(_translate("MainWindow", "导入测绘数据"))
        self.menuMakeCountFile.setTitle(_translate("MainWindow", "生成计算用三角网数据"))
        self.menu_2.setTitle(_translate("MainWindow", "数据"))
        self.menu_3.setTitle(_translate("MainWindow", "绘图处理"))
        self.menu_4.setTitle(_translate("MainWindow", "工程应用"))
        self.menu_5.setTitle(_translate("MainWindow", "工具"))
        self.menu_6.setTitle(_translate("MainWindow", "帮助"))
        self.actLoadOrgData.setText(_translate("MainWindow", "导入原始地貌数据"))
        self.actLoadOrgData.setIconText(_translate("MainWindow", "导入原始地貌数据"))
        self.actLoadOrgData.setToolTip(_translate("MainWindow", "导入原始地貌数据"))
        self.actLoadEndData.setText(_translate("MainWindow", "导入完成貌原始数据"))
        self.actAppendData.setText(_translate("MainWindow", "插入三角网顶点数据"))
        self.actionDraw3D.setText(_translate("MainWindow", "刷新绘图"))
        self.actCount.setText(_translate("MainWindow", "计算当前三角网土石方"))
        self.actMakeOrgDataFile.setText(_translate("MainWindow", "生成原三角网数据文件"))
        self.actMakeEndDataFile.setText(_translate("MainWindow", "生成完成貌三角网数据文件"))
        self.actExit.setText(_translate("MainWindow", "退出"))
        self.actDelData.setText(_translate("MainWindow", "删除选中的三角网"))
        self.actTools.setText(_translate("MainWindow", "打开工具箱"))
        self.actHelp.setText(_translate("MainWindow", "帮助"))
        self.actAbout.setText(_translate("MainWindow", "关于"))

QLabelEx.py:QT标签控件扩展模块文件

#模块名:QLabelEx.py:将QT5/6的标签、编辑框、按纽等常规控件类再扩展对应的子类,更方便快速使用
#包含类名: QLabelEx: 继承QtLable类的扩展类:支持低代码显示图片,动画等功能
#          QLabelExLstImg: 继承QLabelEx类的扩展类,用于可以定时显示指定的图象列表
#          QLabelExBtn: 继承QLabelEx类的扩展类,用于模仿图象按纽,移入移出单击控件时可以用不同的透明图象显示
#          QLabelExBtnCheck: 继承QLabelEx类的扩展类,用于模仿check多选图象按纽
#           QLabelExBtnRadio: 继承QLabelEx类的扩展类,用于模仿Radio单选图象按纽
#           : 
import os,sys 
import PyQt5
from PyQt5 import *
from PyQt5 import QtCore   
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtGui import QMovie
from PyQt5.QtCore import QByteArray
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest

import time
import math
import copy 
import random

lst_ImgExName=['BMP','JPG','JPEG','PNG','TIF','TIFF','TGA','WMF','SVG','HEIF','RAW','WEBP']
lst_MovExName=['GIF','AVI','MPEG','MP4','MOV','MKV','WMV','FLV','RMVB','RM','RAM']
lst_AlignType=['TL','TC','TR','CL','CC','CR','DL','DC','DR']
#########################################################################################################################
#重载标签类,标签可透明显示图像,用于在窗体上加载小分部图像
class QLabelEx(QLabel):  
    objcount=0   # 
    signal_Leftclicked = QtCore.pyqtSignal(object)        #自定信号,标签被左键单击,传回参数:控件对象本身
    signal_Rightclicked = QtCore.pyqtSignal(object)       #自定信号,标签被右键单击,传回参数:控件对象本身
    signal_Midclicked = QtCore.pyqtSignal(object)        #自定信号,标签被中键单击,传回参数:控件对象本身
    signal_LeftDropRelease = QtCore.pyqtSignal(object)    #自定信号,标签被左键拖动后释放,传回参数:控件对象本身

    #初始化对角需传递的参数为  父类,创建矩形,内容,     控件透明度      字体                     字体颜色           背景颜色                    
    def __init__(self,     parent,x,y,w,h,text='',transt=1.0,font=QFont('宋体', 11),fcolor=QColor(0,0,0)):  
        super(QLabel, self).__init__(parent)
        self.type='TXT'   #标签控件的类型,'TXT'=纯文本标签,‘IMG'=可显示图片标签 'MOV':可播放动画标签
        self.setGeometry(x,y,w,h)
        self.ctlRect=QRect(x,y,w,h)     #控件的矩形区域
        self.imgRect=QRect()            #如果控件加载了图象或视频自身尺寸的矩形区域
        self.bDrawRect = False          #是否在标签控件外边画出矩形框        
        self.rectCol=QColor(255,0,0)    #画矩形边框的颜色
        self.rectPenWidth=2             #画矩形边框的线宽度
        self.bChgCtlRect=False          #如果self.ctlRect不能满足文字、图象的矩形区域时,是否允许控件变化其矩形来适应文字或图象要求的矩形区域
        self.move_Flag = False           #标签控件是否可以主窗体上拖动:对窗体元素,应设置为False
        self.bZoomImgSize=True          #控件的矩形区同图象的矩形区不相符时,是否允许图象或视频自动缩放以适应控件矩形区
        self.setScaledContents(self.bZoomImgSize)  # 设置标签的图片,设置True时图片自适应控件,为False时,只显示控件范围图片
        self.setAutoFillBackground(False) #不允许自动填充背景底色
        self.text=text       #标签是文本类型时显示的内容
        self.drawText=text   #标签是图片或视频类型时显示的内容
        self.alignFlags=Qt.AlignTop | Qt.AlignLeft   #对齐方式
        self.bDrawTxt = False   #显示图片的同时,是否将self.drawText画到图象上
        self.fontCol=fcolor   #字体颜色
        self.bkCol=QColor(255,255,255)     #如设置不透明时的标签背景颜色
        self.setFont(font)
        palette = QPalette()
        palette.setColor(QPalette.WindowText, self.fontCol) #设置字体颜色
  
        self.setPalette(palette)
        self.SetTransparent(transt)             #设置控件的透明度,1=不透明,0=完全透明
        self.setText(text)
        self.global_X=self.gobal_Y=0               #标签相对屏幕左上点(0,0)的坐标
        self.startPoint=QPoint()                    #鼠标在标签控件上压下开始的坐标点
        self.endPoint=QPoint()                      #鼠标在标签控件上压下结束时的坐标点
        self.mouse_X=self.mouse_Y=0                #鼠标在标签控件上相对标签控件范围的坐标
        self.origin_x=self.origin_y=0
        self.globalmouse_X=self.globalmouse_Y=0   #鼠标在标签控件上相对屏幕左上点(0,0)的坐标
        self.oldPos=QPoint()                       #移动前标签控件的位置
      
        self.curImgfilename=''
        self.curMovFileName=''
        self.curData=None     #当标签是加载的图片或动画时,将文件同容加载到内存中再显示,避免频繁读写文件
        self.image=QImage()
        self.curRotAngle=0.0 #图片当前旋转角度(角度,非弧度,顺时针为正)
        
        self.gifSpeed=200  #当前要播放的GIF动画的速度

        self.drawtxtX=self.drawtxtY=0
    #如要要不透明的标签,设置标签背景色
    def setBkCol(self,bkcol=QColor(255,255,255)):
        self.bkCol=bkcol
        palette = QPalette()
        self.setAutoFillBackground(True)          
        palette.setColor(QPalette.Background, self.bkCol)
        self.setPalette(palette)

    #设置标签中的文字/图片/GIF动画对齐方式Qt.AlignLeft:左对齐Qt.AlignRight:右对齐 Qt.AlignTop:顶部对齐Qt.AlignBottom:底部对齐Qt.AlignHCenter:水平居中Qt.AlignVCenter:垂直居中Qt.AlignCenter:同时水平和垂直居中
    def SetAlign(self,at='TL'):  #
        at=at.upper()
        self.alignFlags=Qt.AlignTop | Qt.AlignLeft
        if(at=='TL'): self.alignFlags=Qt.AlignTop | Qt.AlignLeft
        elif(at=='TC'): self.alignFlags=Qt.AlignTop | Qt.AlignHCenter
        elif(at=='TR'): self.alignFlags=Qt.AlignTop | Qt.AlignRight
        elif(at=='CL'): self.alignFlags=Qt.AlignVCenter | Qt.AlignLeft
        elif(at=='CC'): self.alignFlags=Qt.AlignVCenter | Qt.AlignHCenter
        elif(at=='CR'): self.alignFlags=Qt.AlignVCenter | Qt.AlignRight
        elif(at=='DL'): self.alignFlags=Qt.AlignBottom | Qt.AlignLeft
        elif(at=='DC'): self.alignFlags=Qt.AlignBottom | Qt.AlignHCenter
        elif(at=='DR'): self.alignFlags=Qt.AlignBottom | Qt.AlignRight
        else:self.alignFlags=Qt.AlignVCenter | Qt.AlignLeft
        self.setAlignment(self.alignFlags)
        self.setText(self.text)  #有时并没有出现对齐效果,只能采用先清除再重加载的方式

    #旋转控件中的图片一指定的角度:角度为正东向,向顺时针旋转的角度为正,反之为负(非弧度)
    def RotateImg(self,angle): 
        if(self.type=='IMG' and self.curData!=None):
            transform = QTransform()  
            transform.rotate(angle)     
            self.image=self.image.transformed(transform);             
            self.setPixmap(QPixmap.fromImage(self.image))  # 显示图片到Qlabel控件
            if(self.bChgCtlRect):   #为真时,旋转后同时调整控件大小
                self.resize(self.image.width(),self.image.height())

    #设置标签控件在加载图片时,控件尺寸同图片尺寸不符时,是否允许控件调整自身的矩形区域,以适应1:1的图象显示
    def ObjToImgSize(self):
        self.setScaledContents(self.bZoomImgSize)  #不允许自适应控件,只1:1显示到控件中,同时调整控件大小
        if(self.bChgCtlRect):   #只有先设置此属性为真时,才允许变化控件尺寸
            if(self.curData!=None):  
                image= QImage.fromData(self.curData)
                self.resize(image.width(),image.height()) #用下行后用设置参数中的矩形,用本行就是图片本身的尺寸
                self.ctlRect=QRect(self.x(),self.y(),self.width(),self.height())

    #设置标签加载的文件名称,可以是图片也可以是动画GIF或视频
    def LoadFile(self,filename=''):
        if(os.path.exists(filename)):
            file_extension = str(filename.split(".")[-1]).upper()
            bOK=False
            for exname in lst_ImgExName:
                if file_extension == exname:
                    self.type='IMG'
                    bOK=True
                    break
            for exname in lst_MovExName:
                if file_extension == exname:
                    self.type='MOV'
                    bOK=True
                    break
            if (bOK):
                with open(filename, 'rb') as f:
                    self.curData = f.read()
                self.image= QImage.fromData(self.curData)    
                self.curMovFileName=filename
                self.RefreshLable()
            else:
                print(f'没有找到对应扩展名: {file_extension} 的分类')
                self.type='TXT'
                self.ReshowText(self.text)
            self.ObjToImgSize()
        else:
            self.type='TXT'
            self.ReshowText(self.text)

    #清除图象,重新显示标签的文本
    def ReshowText(self,txt):
        self.text=txt
        self.clear()
        self.type='TXT'
        self.setText(txt)
    #设置显示图片的同时,画到标签控件上的文本,传入文本为空时,同标签控件初始化时的字符串一致,图形模式下,不调用此函数,默认不会绘出文本
    def setDrawText(self,txt,x=0,y=0):
        self.bDrawTxt=True
        if(len(txt)==0):
            self.drawText=self.text
        else:
            self.drawText=txt
        self.drawtxtX=x
        self.drawtxtY=y
    #重新显示标签(在用了LoadFile后)
    def RefreshLable(self):    #如果图片被调整乱了,且不想要控件尺寸同图片尺寸
        self.setScaledContents(self.bZoomImgSize)  #不允许自适应控件,只1:1显示到控件中,同时调整控件大小
        if(self.type=='IMG'):
            self.image= QImage.fromData(self.curData)
            self.setPixmap(QPixmap.fromImage(self.image))  # 显示图片到Qlabel控件
            self.imgRect=QRect(self.x(),self.y(),self.image.width(),self.image.height())
            if(self.bChgCtlRect):
                self.resize(self.image.width(),self.image.height()) #用下行后用设置参数中的矩形,用本行就是图片本身的尺寸             
            
        elif(self.type=='MOV'):
            """
            #用内存文件来播放GIF没成功??
            #从bytes创建一个QBuffer对象
            buffer=QBuffer()
            buffer.open(QBuffer.ReadOnly)
            buffer.write(self.curData)
            #self.movie = QMovie(self)
            #self.movie.setDevice(QByteArray(self.curData))

            # 使用QMovie来播放GIF
            #self.movie = QMovie(buffer)
            #self.movie.setSpeed(10)
            # 将movie应用到label上
            self.setMovie(self.movie)
            self.total_frame = self.movie.frameCount()
            self.gifSpeed=self.movie.speed()
            print(f'当前GIF文件=’{self.curMovFileName}‘,总帧数={self.total_frame},默认正常播放速度={self.gifSpeed}')
            self.set_GifSpeed(2.0)   #设置播放动画GIF的整速度:方法接受的是每1000毫秒播放的帧数比例,如是1:表示,一秒显示全部帧数,0.5表示一秒显示半数的帧数。
            self.movie.start()
            #self.setLabelLayer(True)
            """
            self.movie = QMovie(self.curMovFileName)
            # 将movie应用到label上
            self.setMovie(self.movie)
            self.total_frame = self.movie.frameCount()
            self.gifSpeed=self.movie.speed()
            #print(f'当前GIF文件=’{self.curMovFileName}‘,总帧数={self.total_frame},默认正常播放速度={self.gifSpeed}')
            self.set_GifSpeed(self.gifSpeed)   #设置播放动画GIF的整速度:方法接受的是每1000毫秒播放的帧数比例,如是1:表示,一秒显示全部帧数,0.5表示一秒显示半数的帧数。
            self.movie.start()
            #"""
        else:    #self.type=='TXT'
            pass 

    #设置播放GIF动画的速度:  interval值哦本准备播放GIF的默认播放速度的倍数,如当前GIF默认播放速度为100
    def set_GifSpeed(self,interval=100.0):
        self.gifSpeed=interval
        if(self.type=='MOV' and self.movie!=None):
            self.movie.setSpeed(interval)  # 设置播放速度
    #设置标签控件的透明程度:对文字及图片均有效
    def SetTransparent(self,trans):
        if trans>1:trans=1
        elif trans<0:trans=0
        self.Transparent=trans
        opacity_effect = QGraphicsOpacityEffect(parent=self)
        opacity_effect.setOpacity(trans)  # 设置透明度
        self.setGraphicsEffect(opacity_effect)  # 将透明度效果应用到标签上
        #self.setWindowOpacity(self.Transparent)
    #设置本标签对象是在最上方还是在最下方    
    def setLabelLayer(self,bTop=True):
        if(bTop):self.raise_()
        else:self.lower()
    #设置标签显示的文本的字体
    def setTextFont(self,fontname='宋体',fontsize=11,bBold=False,bItalic=False,bUnderline=False):
        font = QFont()
        font.setFamily(fontname)       # 设置字体名称
        font.setPointSize(fontsize)    # 设置字体大小
        font.setBold(bBold)            # 设置字体加粗
        font.setItalic(False)
        font.setUnderline(False)
        self.setFont(font)
    #设置标签显示的文本的字体
    def setTextCol(self,fcol=QColor(0,0,0)):
        self.setStyleSheet(f"QLabel {{ color: {fcol.name()}; }}")
     #设置标签显示的文本的字体
    def setTextBkCol(self,bkcol=QColor(255,255,255)):
        if( not self.bTransparent):   #对非透明模式才支持设置标签背景颜色
            self.setAutoFillBackground(True)  # 确保背景自动填充
            palette = self.palette()
            palette.setColor(QPalette.Window, bkcol)  
            self.setPalette(palette)
    #得到标签矩形中心位置 
    def getObjRect(self):
        size = self.geometry()
        self.centerX=size.x()+size.width()/2
        self.centerY=size.y()+size.height()/2
        return size.x(),size.y(),size.width(),size.height()
    
    #鼠标按下事件重载   
    def mousePressEvent(self, event):  
        self.startPoint=event.pos()
        self.oldPos=QPoint(event.globalX(),event.globalY())                         
        # 核心部分: 当鼠标点击是左键 并且 在top控件内点击时候触发 
        if (event.button() == Qt.LeftButton and self.move_Flag):    #and self.top.underMouse():
            self.setCursor(Qt.OpenHandCursor)    #移动时设置成手型光标
            # 但判断条件满足时候, 把拖动标识位设定为真
            #self.move_Flag = True
            self.globalmouse_X = event.globalX()
            self.globalmouse_Y = event.globalY()
            # 获取窗体当前坐标
            self.origin_x = self.x()
            self.origin_y = self.y()
        
     #鼠标移动事件重载          
    def mouseMoveEvent(self, event):  
        # 拖动标识位设定为真时, 进入移动事件
        if self.move_Flag:
            # 计算鼠标移动的x,y位移
            move_x = event.globalX() - self.globalmouse_X
            move_y = event.globalY() - self.globalmouse_Y
            # 计算窗体更新后的坐标:更新后的坐标 = 原本的坐标 + 鼠标的位移
            dest_x = self.origin_x + move_x
            dest_y = self.origin_y + move_y
            # 移动本标签控件
            size = self.geometry()
            self.move(dest_x, dest_y)
            self.ctlRect=QRect(self.x(),self.y(),self.width(),self.height())
            self.setLabelLayer(True)    #拖动的标签控件角色在最顶端显示
            
    # 鼠标左键释放        
    def mouseReleaseEvent(self, event):
        self.endPoint=event.pos()
        newPos=QPoint(event.globalX(),event.globalY())  
        if (event.button() == Qt.LeftButton and self.move_Flag): 
            self.setCursor(Qt.ArrowCursor) # 设定鼠标为普通状态: 箭头
        if(self.move_Flag==False):   #非对象拖动状态,鼠标在控件区域移动一位置
            if(abs(self.endPoint.x()-self.startPoint.x())<2 and abs(self.endPoint.y()-self.startPoint.y())<2 ):  #判断是否单击
                if(event.button() == Qt.LeftButton):
                    print('非拖动状态下:是左键单击不是拖动')
                    self.signal_Leftclicked.emit(self)
                elif(event.button() == Qt.RightButton):
                    print('非拖动状态下:是右键单击不是拖动')
                    self.signal_Rightclicked.emit(self)
                elif(event.button() == Qt.MidButton):
                    print('非拖动状态下:是中键单击不是拖动')
                    self.signal_Midclicked.emit(self)
            else:
                print('非拖动状态下,鼠标在控件上移动了一位置')
        else:   #拖动对象状态,除非一点也没拖动,否则不对单位处理
            if(abs(self.oldPos.x()-newPos.x())<2 and abs(self.oldPos.y()-newPos.y())<2 ):
                print('虽是拖动状态但是并没拖动对象')
                if(event.button() == Qt.LeftButton):
                    print('拖动状态下:是左键单击不是拖动')
                    self.signal_Leftclicked.emit(self)
                elif(event.button() == Qt.RightButton):
                    print('拖动状态下:是右键单击不是拖动')
                    self.signal_Rightclicked.emit(self)
                elif(event.button() == Qt.MidButton):
                    print('拖动状态下:是中键单击不是拖动')
                    self.signal_Midclicked.emit(self)
            else:   #拖动对象移动了位置 
                print('拖动状态下:左键拖动控件移动了位置')
                self.signal_LeftDropRelease.emit(self)
                     
    #重载绘图函数:
    def paintEvent(self, event):
        if (self.bDrawRect):  #标签控件是否绘制边框架
            self.DrawObjRect(self.rectCol,self.rectPenWidth)   #为控件画出外边框
        if(self.bDrawTxt): #是否在标签控件上画出文本
            pen = QPen()                    # 创建画笔对象
            painter = QPainter(self)        # 此QPainter只能在paintEvent中定义,不能定义成类的self成员对象,也不能在其地方(如其他窗口,线程中)定义,否则没有绘画功能显示
            #绘制
            pen.setColor(self.fontCol)                 
            painter.drawText(self.drawtxtX,self.drawtxtY,self.width(),self.height(),self.alignFlags,self.drawText) 
        return super().paintEvent(event)                #调用主窗口的重绘事件,不用不会加载动画只显示第一帖,用了动画加载正常,但又多了一静态图第一帖
        
    #画出当前控件的矩形框(用于对象被选择时)
    def DrawObjRect(self,pencol,penwidth):
        self.rectCol=pencol
        self.rectPenWidth=penwidth
        if(self.bDrawRect):
            pen = QPen()                    # 创建画笔对象
            brush = QBrush()                # 创建画刷对象
            painter = QPainter(self)        # 此QPainter只能在paintEvent中定义,不能定义成类的self成员对象,也不能在其地方(如其他窗口,线程中)定义,否则没有绘画功能显示
            #绘制
            pen.setColor(pencol)                 
            pen.setStyle(Qt.SolidLine)               
            pen.setWidth(penwidth)          # 设置画笔宽度
            painter.setPen(pen)             # 设置画笔
            self.pixmap = QPixmap.fromImage(self.image)
           #painter.drawPixmap(0, 0, self.pixmap)
            painter.drawRect(0,0,self.width(),self.height()) 

#定义可用计时器播放连续图片以形成动画的标签扩展类    
class QLabelExLstImg(QLabelEx):
    def __init__(self, parent,x,y,w,h,text='',transt=1.0,font=QFont('宋体', 11),fcolor=QColor(0,0,0)):  
        super().__init__(parent,x,y,w,h,text,transt,font,fcolor)
        self.type='IMG'
        self.memImgFile=[]
        self.curtimespeed=100
        self.curindex = 0
        self.curPlayCount=0
        self.imgPlayCount = 0 #图象列表播放遍数,0=循环播放,大于0时为播放的遍数后,自动停止到最后一帖位置
        self.bStop=False      #指定遍数放完后,此开关为True
        # 创建计时器,设置时间间隔为100毫秒(1秒)
        self.timer = QTimer()
        # 计时器信号连接到timeout_slot槽函数
        self.timer.timeout.connect(self.time_objmove_slot)
        self.timer.start(self.curtimespeed)  # 开始计时器:

    #设置要播放的图片列表,及图象列表间播放的间隔
    def setLstImg(self,lstfilename,timesleep=50,playcount=0):
        self.bStop=False
        self.curPlayCount=0
        self.lstImgFile=lstfilename
        self.curtimespeed=timesleep
        self.imgPlayCount = playcount
        self.makeMemFile()
    #创建图片的内存文件
    def makeMemFile(self):
        self.memImgFile.clear()
        for imgFilename in self.lstImgFile:
            # 打开图片
            if(os.path.exists(imgFilename)):
                with open(imgFilename, 'rb') as f:
                    img = f.read()
                    self.memImgFile.append(img)

    #得到对象的文件内存数据:序号
    def getImgData(self,index):  
        count=len(self.memImgFile)
        if(count>0 and index>=0 and index<count):
            return self.memImgFile[index]
        else:
            print(f'返回一空图象{index},{count}')
            return None
        
    #显示内存文件数据:序号    
    def showMemImg(self,index):
        if(self.getImgData(index)!=None):
            self.curData = self.getImgData(index)
            self.RefreshLable()

    #在标签控件中播放下一帧
    def next_frame(self):
        count=len(self.memImgFile)
        if(count==0 or self.bStop):return
        if(self.curindex>=(count-1)): #已循环一遍
            if(self.imgPlayCount>=0):
                self.curPlayCount+=1
                if(self.imgPlayCount>0 and self.curPlayCount>=self.imgPlayCount):
                    self.curPlayCount = self.imgPlayCount+1
                    self.curData = self.getImgData(count-1)
                    self.bStop=True
                    return
                self.curData = self.getImgData(self.curindex)
                self.curindex=0
        else:
            self.curData = self.getImgData(self.curindex)
            self.curindex = (self.curindex + 1) % count 
        self.timer.setInterval(self.curtimespeed)

    #对象计时器来显示动画效果
    def time_objmove_slot(self):
        self.next_frame()   #得到下一帖图象
        #移动前对图象进行方向进行处理
        if (self.curData==None):   # 没图片,则不执行任何操作
            return
        size = self.geometry()
        fx = size.x()+size.width()/2 #对象矩形中心的坐标
        fy = size.y()+size.height()/2 
        if(self.type=='IMG'):  #对GIF,计时器会会造成播放过快无法控制
            self.RefreshLable()
       
#定义标签类按纽扩展类      
class QLabelExBtn(QLabelEx):
    def __init__(self, parent,x,y,w,h,text='',transt=1.0,font=QFont('宋体', 11),fcolor=QColor(0,0,0)):  
        super().__init__(parent,x,y,w,h,text,transt,font,fcolor)
        self.type='IMG'
        self.memImgFile=[]
        self.bEnable=True
    #设置要图片列表:0=按纽常规状态下的图片,1=鼠标移入控件时的图片,2=鼠标点击按纽时的图片,3=按纽失效时的图片
    def setBtnImg(self,lstfilename):
        self.lstImgFile=lstfilename
        self.makeMemFile()
    #创建图片的内存文件
    def makeMemFile(self):
        self.memImgFile.clear()
        for imgFilename in self.lstImgFile:
            # 打开图片
            if(os.path.exists(imgFilename)):
                with open(imgFilename, 'rb') as f:
                    img = f.read()
                    self.memImgFile.append(img)

    #得到对象的文件内存数据:序号
    def getImgData(self,index):  
        count=len(self.memImgFile)
        if(count>0 and index>=0 and index<count):
            return self.memImgFile[index]
        else:
            print(f'返回一空图象{index},{count}')
            return None
        
    #显示内存文件数据:序号    
    def showMemImg(self,index):
        if(self.getImgData(index)!=None):
            self.curData = self.getImgData(index)
            self.RefreshLable()
    #移入控件事件
    def enterEvent(self, event):     
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件                   
        self.showMemImg(1)
    #移出控件事件
    def leaveEvent(self,event):   
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件                      
        self.showMemImg(0)

    #鼠标按下事件重载   
    def mousePressEvent(self, event): 
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件
        if (event.button() == Qt.LeftButton): 
            self.showMemImg(2)
        return super().mousePressEvent(event)
      #鼠标移动事件重载          
    def mouseMoveEvent(self, event):  
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件
        return super().mouseMoveEvent(event)
     # 鼠标左键释放        
    def mouseReleaseEvent(self, event):
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件
        return super().mouseReleaseEvent(event)
    def setBtnEnable(self,enable):
        self.bEnable = enable
        print(f'self.bEnable={self.bEnable}')
        if(enable):self.showMemImg(0)
        else:self.showMemImg(3)
    
#定义标签CHECK类按纽扩展类    
class QLabelExBtnCheck(QLabelEx):
    def __init__(self, parent,x,y,w,h,text='',transt=1.0,font=QFont('宋体', 11),fcolor=QColor(0,0,0)):  
        super().__init__(parent,x,y,w,h,text,transt,font,fcolor)
        self.type='IMG'
        self.groupID=0  #check按纽所属组数
        self.move_Flag=False  #控件不可在主窗体上被拖动
        self.memImgFile=[]
        self.bEnable=True     #是否可用
        self.bChecked=False   #是否被选中
    #设置按纽所属组
    def setBtnGroup(self,group):
        self.groupID=group
    #设置要图片列表:0=按纽未被选中时的图片,1=按纽被选中时的图片,2=按纽未被选中失效时的图片,3=按纽被选中失效时的图片
    def setBtnImg(self,lstfilename):
        self.lstImgFile=lstfilename
        self.makeMemFile()
    #创建图片的内存文件
    def makeMemFile(self):
        self.memImgFile.clear()
        for imgFilename in self.lstImgFile:
            # 打开图片
            if(os.path.exists(imgFilename)):
                with open(imgFilename, 'rb') as f:
                    img = f.read()
                    self.memImgFile.append(img)

    #得到对象的文件内存数据:序号
    def getImgData(self,index):  
        count=len(self.memImgFile)
        if(count>0 and index>=0 and index<count):
            return self.memImgFile[index]
        else:
            print(f'返回一空图象{index},{count}')
            return None
        
    #显示内存文件数据:序号    
    def showMemImg(self,index):
        if(self.getImgData(index)!=None):
            self.curData = self.getImgData(index)
            self.RefreshLable()

    #鼠标按下事件重载   
    def mousePressEvent(self, event): 
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件
        if (event.button() == Qt.LeftButton): 
            self.bChecked=not self.bChecked
            if(self.bChecked):
                self.showMemImg(1)
            else:
                self.showMemImg(0)
        return super().mousePressEvent(event)
      #鼠标移动事件重载          
    def mouseMoveEvent(self, event):  
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件
        return super().mouseMoveEvent(event)
     # 鼠标左键释放        
    def mouseReleaseEvent(self, event):
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件
        return super().mouseReleaseEvent(event)
    def setBtnEnable(self,enable):
        self.bEnable = enable
        if(enable):
            if(self.bChecked):
                self.showMemImg(1)
            else:
                self.showMemImg(0)
        else:
            if(self.bChecked):
                self.showMemImg(2)
            else:
                self.showMemImg(3)
#定义标签Radio类按纽扩展类    
class QLabelExBtnRadio(QLabelEx):
    lst_btnRadio=[]    #定义到类构造外,的有类成员共用,每增加一个类成员实例化对象,同时加到此列表中以处理类似radio单选按纽的效果
    signal_RadioBntSelected = QtCore.pyqtSignal(int,object)        #自定信号,radio类按纽被单击时,通知主窗口,所有本组中的其他同类radio按结全部去除被选择状态,传回参数:所属组号
    def __init__(self, parent,x,y,w,h,text='',transt=1.0,font=QFont('宋体', 11),fcolor=QColor(0,0,0)):  
        super().__init__(parent,x,y,w,h,text,transt,font,fcolor)
        self.type='IMG'
        self.groupID=0  #check按纽所属组数
        self.ID=0
        self.move_Flag=False  #控件不可在主窗体上被拖动
        self.memImgFile=[]
        self.bEnable=True     #是否可用
        self.bChecked=False   #是否被选中
        QLabelExBtnRadio.lst_btnRadio.append(self)  #单选按纽本身加入公用列表
    #设置按纽所属组和在组中的编号ID
    def setBtnGroup(self,group,ID):
        self.groupID=group
        self.ID=ID
    #设置要图片列表:0=按纽未被选中时的图片,1=按纽被选中时的图片,2=按纽未被选中失效时的图片,3=按纽被选中失效时的图片
    def setBtnImg(self,lstfilename):
        self.lstImgFile=lstfilename
        self.makeMemFile()
    #创建图片的内存文件
    def makeMemFile(self):
        self.memImgFile.clear()
        for imgFilename in self.lstImgFile:
            # 打开图片
            if(os.path.exists(imgFilename)):
                with open(imgFilename, 'rb') as f:
                    img = f.read()
                    self.memImgFile.append(img)

    #得到对象的文件内存数据:序号
    def getImgData(self,index):  
        count=len(self.memImgFile)
        if(count>0 and index>=0 and index<count):
            return self.memImgFile[index]
        else:
            print(f'返回一空图象{index},{count}')
            return None
        
    #显示内存文件数据:序号    
    def showMemImg(self,index):
        if(self.getImgData(index)!=None):
            self.curData = self.getImgData(index)
            self.RefreshLable()

    #鼠标按下事件重载   
    def mousePressEvent(self, event): 
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件
        if (event.button() == Qt.LeftButton):  
            self.setBtnChecked(True)  #本对象被选中,同组内的其他成员去除选中状态,一组成员中只能有一个被选中
            for obj in QLabelExBtnRadio.lst_btnRadio:
                if(self.groupID==obj.groupID and self.ID!=obj.ID):  #是同一组其他对象
                    obj.setBtnChecked(False)

            self.signal_RadioBntSelected.emit(self.groupID,self)
        return super().mousePressEvent(event)   #不调用父类的按下事件
      #鼠标移动事件重载          
    def mouseMoveEvent(self, event):  
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件
        return super().mouseMoveEvent(event)
     # 鼠标左键释放        
    def mouseReleaseEvent(self, event):
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件
        return super().mouseReleaseEvent(event)
    def setBtnEnable(self,enable):
        self.bEnable = enable
        if(enable):
            if(self.bChecked):
                self.showMemImg(1)
            else:
                self.showMemImg(0)
        else:
            if(self.bChecked):
                self.showMemImg(2)
            else:
                self.showMemImg(3)
    #设置Radio按纽的显示状态
    def setBtnChecked(self,checked):
        if(not self.bEnable): return     #当按纽状记为不可用时,不接受鼠标事件
        self.bChecked = checked
        if(self.bChecked):
            self.showMemImg(1)
        else:
            self.showMemImg(0)

QTableWidgetEx.py:QT表格控件扩展模块文件

#模块名:QTableWidgetEx.py:将QT5/6的表格控件窗体扩展子类
#包含类名:
import os,sys 
import PyQt5
from PyQt5 import *
from PyQt5 import QtCore   
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtGui import QMovie
from PyQt5.QtCore import QByteArray
from PyQt5.QtWidgets import QApplication, QTableWidget, QHeaderView, QApplication, QTableWidgetItem
from PyQt5.QtGui import QBrush, QColor
from PyQt5.QtCore import Qt
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
import numpy as np          #数据组用库
import time
import math
import copy 
import random
import g 

lst_AlignType=['TL','TC','TR','CL','CC','CR','DL','DC','DR']

#将颜色字符串转为RGB
def color_string_to_rgb(color_string):
    color = QColor(color_string)
    return color.getRgb()
      
#########################################################################################################################
#重载表格类
class QTableWidgetEx(QTableWidget):  
    #__init__构造函数之前定义的变量为本类的公用全局变量,所有该类的实例化对象都可操作这些变量,并公和。用法:QTableWidgetEx.变量名,
    DEF_STR='默认'
    bItemCopy=False   #单元格是否已被执行COPY
    copyItems=[]
    copyItemData=[]
    
    MAX_ROWCOUNT=100000   #最大支持行数:注意,如果表格中设置单元格为控件时过多,会造成运行卡顿,
    MAX_COLCOUNT=100      #最大支持列数
    #定义表格控件要用的的组合框等下拉列表要用到的数据,为单元格列表对象的第2个元素值         
    def __init__(self,     parent):  
        super(QTableWidget, self).__init__(parent)
        self.menuType=0             #当前右键菜单的弹出类型
        self.curRowCount=0          #表格当前的总行数
        self.curColCount=0          #表格当前的总列数
        self.type=0   #表格类型:0=普通表格 
        self.itemData=[]

    #初始始全表头    
    def initTableHead(self,lstHead):
        self.header_field = lstHead      #表头字段:支持动态增加
        self.setHorizontalHeaderLabels(self.header_field)                      # 设置行表头字段
        self.setTableHeader(QFrame.Box,QFrame.Sunken,'black','powderblue')      #设置表格头内容和样式

    #对表格进一步初始化初始化对角需传递的参数为父类,  行数,列数, 表头,行宽,列宽,     字体               字体颜色       是否交锚行
    def initTable(self,rowNum,colNum,Width=20,colWidth=50,font=QFont('宋体', 11),fcolor=QColor(0,0,0),bAlRow=True):
        #self.clear()
        self.curRowCount=rowNum          #表格当前的总行数
        self.curColCount=colNum          #表格当前的总列数
      
        self.setRowCount(rowNum)       # 表格行数
        self.setColumnCount(colNum)    #表格列数
        self.itemData.clear()
        rowid=0
        for row in range(rowNum): 
            onerow=[rowid,'','x','y','z']    
            self.itemData.append(onerow) 
            rowid+=1
            for col in range(colNum):
                item = QTableWidgetItem('')
                self.setItem(row,col,item) 
               
        self.alignFlags=Qt.AlignVCenter | Qt.AlignLeft   #对齐方式:竖向居中,前后居左
        self.fontCol=fcolor   #字体颜色
        self.bkCol=QColor(255,255,255)     #如设置不透明时的标签背景颜色
        self.setFont(font)
        palette = QPalette()
        palette.setColor(QPalette.WindowText, self.fontCol) #设置字体颜色
        self.setPalette(palette)

        self.setAlternatingRowColors(bAlRow)     # 交替行颜色
 
    #设置表格控件外观类型
    def setTableFrmType(self,shape=QFrame.Box,shadow=QFrame.Sunken,linewidth=1):   
        self.setFrameShape(shape)       # 设置表格框架样式
        self.setFrameShadow(shadow)     # 设置边框的样式
        self.setLineWidth(linewidth)    # 设置边框的线条宽度

    # 设置第0列为复选框列
    def setTableHeader(self,shape=QFrame.Box,shadow=QFrame.Sunken,textCol='black',bkCol='White'):
        self.curRowCount = self.rowCount()
        headlen=len(self.header_field)
        header = QHeaderViewEx(self,0)            # 实例化自定义表头,传入表格对象本身
        self.setHorizontalHeader(header)                         # 设置表头
        self.setHorizontalHeaderLabels(self.header_field)        # 设置行表头字段
        self.setColumnWidth(0,80)                                # 设置第0列宽度
        header.setHeaderType(shape,shadow,1,textCol,bkCol)       # 设置表头显示类型
        g.gf_setObjCol(header,'QHeaderViewEx','blue','powderblue')                     # 用g.py中的全局函数来改表头颜色也可成功

    #设置标签中的文字/图片/GIF动画对齐方式Qt.AlignLeft:左对齐Qt.AlignRight:右对齐 Qt.AlignTop:顶部对齐Qt.AlignBottom:底部对齐Qt.AlignHCenter:水平居中Qt.AlignVCenter:垂直居中Qt.AlignCenter:同时水平和垂直居中
    def SetAlign(self,at='TL'):  #
        at=at.upper()
        self.alignFlags=Qt.AlignTop | Qt.AlignLeft
        if(at=='TL'): self.alignFlags=Qt.AlignTop | Qt.AlignLeft
        elif(at=='TC'): self.alignFlags=Qt.AlignTop | Qt.AlignHCenter
        elif(at=='TR'): self.alignFlags=Qt.AlignTop | Qt.AlignRight
        elif(at=='CL'): self.alignFlags=Qt.AlignVCenter | Qt.AlignLeft
        elif(at=='CC'): self.alignFlags=Qt.AlignVCenter | Qt.AlignHCenter
        elif(at=='CR'): self.alignFlags=Qt.AlignVCenter | Qt.AlignRight
        elif(at=='DL'): self.alignFlags=Qt.AlignBottom | Qt.AlignLeft
        elif(at=='DC'): self.alignFlags=Qt.AlignBottom | Qt.AlignHCenter
        elif(at=='DR'): self.alignFlags=Qt.AlignBottom | Qt.AlignRight
        else:self.alignFlags=Qt.AlignVCenter | Qt.AlignLeft
        self.setAlignment(self.alignFlags)
        self.setText(self.text)  #有时并没有出现对齐效果,只能采用先清除再重加载的方式

       
     #清除表格单元格的内容: 
    def clearCell(self, row, col):
        if(row>0 and row< (self.curRowCount-1)   and col>0 and col< (self.curColCount-1)):
            item = self.item(row, col)  # 清除指定行列的单元格内容
            if(item is not None):  
                item.setText('')             #非控件单元格不作移除,但要删除内容 
                self.itemData[row,col]=''     #同时清除绑定的数据列表数据
                #print(f'{row},{col}')
    #重新填充表格的全部数据
    def fillTableAllData(self,lstData):  #lstdata应是二维数组
        if(len(lstData)<=0):return
        rowlen=len(lstData)
        collen=len(lstData[0])
        self.itemData.clear()
        rowid=0
        for row in range(rowlen):
            onerow=[rowid,'','x','y','z']
            self.itemData.append(onerow) 
            for col in range(collen):
                itemtxt=str(lstData[row][col])
                self.itemData[row][col]=itemtxt
            rowid+=1
        self.updateAllDataToTable()   

    #设置指定单元格的数据:控件类时,单元格原内容为None,非控件类,原单元格要初始化。
    def setItemData(self,row,col,itemStr,bNew=False):
        if(row>self.curRowCount-1 or row<0 and col>self.curColCount and col<0) : 
             print('行或列号有误')
             return
        self.itemData[row][col]=itemStr
        if bNew: itemtext=''
        item = self.item(row,col)
        if(item is None):
            newitem = QTableWidgetItem(itemStr)
            self.setItem(row,col,newitem)          #!!!!重要:必须用此行代码才真正初始化了非控件类的单元格,否则单元格可以录入数据,但实际值为None
        else:
           item.setText(itemStr)

    #设置一行表格数据(从self.itemData中的数据反映到表格中),如果bNew=True,表示为新建,数据采用默认,反之采用当前self.itemData的数据来写入表格单元格
    def setOneRowTableData(self,row,bNew=False):
        if(row>self.curRowCount-1 or row<0):
            print('行号错误') 
            return
        lstRow=self.itemData[row]
        col=0
        for OneItem in lstRow:  #
            self.setItemData(row,col,OneItem,bNew)
            col+=1

    #用当前self.itemData数据重新刷新设置表格中的数据
    def updateAllDataToTable(self):
        for row in range(self.curRowCount):
            self.setOneRowTableData(row)   
    
    #将当前表格中的数据回传到对应的self.itemData数据列表中存储
    def saveAllTableToItemData(self):
        if(self.curRowCount>0 and self.curColCount>0):
            for row in range(self.curRowCount):
                for col in range(self.curColCount):
                    txt=self.getItemText(row,col)
                    self.itemData[row][col]=txt

    #保存指定行列中的文本到self.itemData数据列表中
    def saveTableToItemData(self,row,col):
        if(self.curRowCount>0 and self.curColCount>0):
            if(row<self.curRowCount  and col<self.curColCount):
                txt=self.getItemText(row,col)
                self.itemData[row][col]=txt
    
    #设置表格整行背景色(对组合框等控件无效,如要效果,需对组合框控件重载继承类)
    def setTableRowBkCol(self,row,bkcol=QColor(255,255,255)):
        for colIndex in range(self.curColCount):
            self.setItemBkCol(row,colIndex,bkcol)
    #设置表格整列背景色(对组合框等控件无效,如要效果,需对组合框控件重载继承类)
    def setTableColBkCol(self,column,bkcol=QColor(255,255,255)):
        for rowIndex in range(self.curRowCount):
            self.setItemBkCol(rowIndex,column,bkcol)
    #设置指定单元格的背景色(对组合框等控件无效,如要效果,需对组合框控件重载继承类)
    def setItemBkCol(self,row,column, bkcol=QColor(255,255,255)):
        item = self.item(row,column)
        if(item is not None):
            item.setBackground(QColor(bkcol)) 
    #得到指定单元格中的文本(是控件的要用其他方法得到)
    def getItemText(self, row, col):
        txt=''
        if(row<self.curRowCount or col<self.curColCount):  
            item = self.item(row, col)
            txt=item.text()
            return txt
        else:return None
    #得到指定行全部的数据
    def getRowItems(self,row,bCopy=False):
        if(row>self.curRowCount-1):return None
        lstoneRow=[]
        item_one=''
        for col in range(self.curColCount):
            item_one=self.itemData[row][col]
            if(not bCopy):
                item_one=''  #非COPY模式下,内容为空
            lstoneRow.append(item_one)
        return  lstoneRow
        
    #得到指定列的全部数据列表
    def getColItems(self,col,bCopy=False):  
        if(col>self.curColCount-1):return None
        lstoneCol=[]
        item_one=''
        for row in range(self.curRowCount):
            item_one=self.itemData[row][col]
            if(not bCopy):
                item_one=''  #非COPY模式下,内容为空
            lstoneCol.append(item_one)
        return  lstoneCol

    #在当前行前插入一行数据
    def InsertItemRow(self,row,bCopy=False):
        if(row>self.curRowCount-1 or row<0):
            return None
        lstItems=self.getRowItems(row-1,bCopy)     #得到当行指定行的上一行的数据类型(内容为空)
        self.insertRow(row)                  #在指定行位置上插入一空行
        self.curRowCount+=1
        self.itemData.insert(row,lstItems)   #设置绑定的数据列表也插入一行
        self.setOneRowTableData(row)         #刷新新插入的一行数据内容
    
    #在表格末尾增加一行数据
    def AppendItemRow(self):
        lstItems=self.getRowItems(self.curRowCount-1)  #得到最后一行的数据
        self.insertRow(self.curRowCount)
        self.curRowCount+=1
        self.itemData.append(copy.deepcopy(lstItems))
        self.setOneRowTableData(self.curRowCount-1)  #刷新最后一行数据显示

    #在当前列前插入一列数据
    def InsertItemCol(self,col,bCopy=False):
        if(col>self.curColCount-1 or col<0):
            return None
        lstItems=self.getColItems(col-1)     #得到当前列前一列的数据类型(内容为空或复制了前一列)
        self.insertColumn(col)               #在指定列位置上插入一空列
        for ci in range(self.curRowCount):
            self.itemData[ci].insert(col,lstItems[ci])   #设置绑定的数据列表也插入一列
            self.setItemData(ci,col,lstItems[ci])
        self.curColCount+=1

    #删除指定行的数据
    def DelOneRowItem(self,row):
        if(row>self.curRowCount-1 or row<0):
            return None
        self.removeRow(row)
        self.itemData[row].pop(row)
        self.curRowCount-=1
    #删除指定列数据
    def DelOneColItem(self,col):
        if(col>self.curColCount-1 or col<0):
            return None
        self.removeColumn(col)
        for ci in range(self.curRowCount):
            self.itemData[ci].pop(col)
        self.curColCount-=1
    #允许、禁止表格可以被编辑
    def setTableEdited(self,bEdit):
        if(bEdit):
            self.setEditTriggers(QTableWidget.NoEditTriggers)  # 禁止表格内编辑
        else:
            pass

    #设置单元格左端显示为图标
    def setItemIco(self,row,col,icofile):
        icon = QIcon(icofile)             
        item = QTableWidgetItem()
        item.setIcon(icon)
        self.setItem(row, col, item)

    #得到当前选择的表格单元格:
    def getSelectedeItems(self):
        selectedItems = self.selectedItems()
        if not selectedItems:   return None 
        rowMin,colMin,rowMax,colMax=self.getSelectedRanges()
        selLstItems=[]
        selLstItemData=[]
        for row in range(rowMin,rowMax+1):
            selRowItems=[]     #一行单元格
            selRowItemData=[]  #一行数据所对应的数据
            for col in range(colMin,colMax+1):
                self.saveTableToItemData(row,col) #先将表格中显示的文本更新回绑定的数据中(因有此单元格是用了控件,并不是用的默认的,如在控件扣相关事件中处理了更新数据,此行就没必要了)
                item=self.item(row,col)           #表格对象
                selRowItems.append(item)
                itemdata=self.itemData[row][col]  #表格对象绑定的数据
                selRowItemData.append(itemdata)
            selLstItems.append(selRowItems)
            selLstItemData.append(selRowItemData)
        print(f'当前得到的表格数据共{rowMax-rowMin+1}行,{colMax-colMin+1}列')
        return selLstItems,selLstItemData
        
    #得到当前选择的表格区域
    def getSelectedRanges(self):
        # 获取所有选中的项目
        selected_items = self.selectedItems()
        # 如果没有选中的项目,返回空区域
        if not selected_items:
            return -1,-1,-1,-1
        # 获取选中项目的最小和最大行和列索引
        top_left_cell = selected_items[0]
        bottom_right_cell = selected_items[-1]
        top_row = top_left_cell.row()
        left_column = top_left_cell.column()
        bottom_row = bottom_right_cell.row()
        right_column = bottom_right_cell.column()
        print(f'当前选择表格左上行号列号={top_row}, {left_column}, 右下行列号={bottom_row}, {right_column}')
        # 返回选中区域
        return (top_row, left_column, bottom_row, right_column)

    #为表格控件增加右键菜单
    def contextMenuEvent(self, event):
        menu = QMenu(self)       # 创建一个菜单
        if(self.menuType==0) :   #0类型的菜单
            # 添加动作到菜单
            act_copyItem = menu.addAction("复制单元格")   #支持单元格为一区域
            act_pasteItem = menu.addAction("粘贴单元格")
            act_cutItem = menu.addAction("剪切单元格")
            act_delItem = menu.addAction("清除单元格")
            act_initItem = menu.addAction("初始化单元格")
            menu.addSeparator()
            act_insertRow = menu.addAction("插入一行数据")
            act_insertCol = menu.addAction("插入一列数据")
            act_appendRow = menu.addAction("表格未尾增加一行数据")
            act_delOneRow = menu.addAction("删除当前行数据")
            act_delOneCol = menu.addAction("删除当前列数据")
            menu.addSeparator()
     
        elif(self.menuType==1):
            act_point2Num = menu.addAction("设置小数点位数为2位")
            act_point3Num = menu.addAction("设置小数点位数为3位")

        # 将菜单显示在鼠标点击的位置
        action = menu.exec(event.globalPos())
        # 处理动作:需要用异常处理代码
        #try:
        if True:
            top_row, left_column, bottom_row, right_column = self.getSelectedRanges()  #得到当前选择的行列号
            if action == act_copyItem:
                print("复制单元格")
                selLstItems,selLstItemData = self.getSelectedeItems()
                if selLstItems==None: return
                QTableWidgetEx.copyItems=selLstItems
                QTableWidgetEx.copyItemData=copy.deepcopy(selLstItemData)
                QTableWidgetEx.bItemCopy=True

            elif action == act_pasteItem:
                print("粘贴单元格")
                if(QTableWidgetEx.bItemCopy):
                    copyRow=0
                    for row in range(top_row,bottom_row+1):
                        copyCol=0
                        for col in range(left_column,right_column+1):
                            if copyCol>(len(QTableWidgetEx.copyItems[0])-1):  
                                break   #复制的对象列数据不足时,退出列不再复制
                            print(f'copyrow={copyRow},copycol={copyCol}')
                            item=QTableWidgetEx.copyItems[copyRow][copyCol]
                            itemData=QTableWidgetEx.copyItemData[copyRow][copyCol]
                            #self.clearCell(row,col)
                            self.setItemData(row,col,itemData)
                            copyCol+=1
                        copyRow+=1
                        if copyRow>(len(QTableWidgetEx.copyItems)-1):  
                            copyRow=0   #复制的对象行数不足时,循环再从0开始叠加 
                            continue

            elif action == act_cutItem:
                print("剪切单元格")
                selLstItems,selLstItemData = self.getSelectedeItems()
                if selLstItems==None: return
                QTableWidgetEx.copyItems=selLstItems
                QTableWidgetEx.copyItemData=copy.deepcopy(selLstItemData)   #剪切前将数据先COPY备用
                QTableWidgetEx.bItemCopy=True
                top_row, left_column, bottom_row, right_column=self.getSelectedRanges()  #得到当前选择的行列号
                #清除选定的表格内容
                for row in range(top_row,bottom_row+1):
                    for col in range(left_column,right_column+1):
                        self.clearCell(row,col)

            elif action == act_delItem:
                print("清除单元格")
                for row in range(top_row,bottom_row+1):
                    for col in range(left_column,right_column+1):
                        self.clearCell(row,col)
            elif action == act_initItem:
                print("初始化单元格")
                for row in range(top_row,bottom_row+1):
                    for col in range(left_column,right_column+1):
                        item=self.item(row,col)
                        obj=self.cellWidget(row, col)
                        if(item is  None and obj is not None):  #单元格没用被初始化且单元格位置上没有覆盖控件时才进行初始化
                            self.setItem(row,col,None)
            elif action == act_insertRow:
                print("插入一行数据")
                currow=self.currentRow()
                if(currow>0):
                    self.InsertItemRow(currow)
            elif action == act_insertCol:
                print("插入一列数据")
                curcol=self.currentColumn()
                if(curcol>0):
                    self.InsertItemCol(curcol,True)
            elif action == act_appendRow:
                print("表格未尾增加一行数据")
                self.AppendItemRow()
            elif action == act_delOneRow:
                print("删除当前行数据")
                currow=self.currentRow()
                if(currow>0):
                    self.DelOneRowItem(currow)
            elif action == act_delOneCol:
                print("删除当前列数据")
                curcol=self.currentColumn()
                if(curcol>0):
                    self.DelOneColItem(curcol)
            else:
                print("其他类型。。。。。")

        #except Exception as e:  # 如果发生异常,执行这里的代码
        #    reply = QMessageBox.question(self, '信息',  
        #                                       "表格还没有被初始化,不能执行下面的代码,请在主窗口中调用相关初始化代码后再执行相关操作?", 
        #                                        QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        #finally:   #无论是否发生异常,都会执行finally中的代码
        #    pass
###############################################################################
#定自义扩展表格头类:
class QHeaderViewEx(QHeaderView):
    def __init__(self,parent,headType=0,orientation=Qt.Horizontal):
        super(QHeaderViewEx, self).__init__(orientation, parent)
        self.isOn = False
        self.table=parent     #表格本身对象为开表头的父类

    def paintSection(self, painter, rect, logicalIndex):
        painter.save()
        super(QHeaderViewEx, self).paintSection(painter, rect, logicalIndex)
        painter.restore()
        
    def mousePressEvent(self, event):
        clickColNum=-1  #当前鼠标单击的列上的控件位置
        index = self.logicalIndexAt(event.pos())
        super(QHeaderViewEx, self).mousePressEvent(event)

    # 自定义信号当单击表头复选框选择全部行时 select_all_clicked 的槽方法
    def change_state(self, isOn,clickColID):
       pass

    # 设置表头为彩色:仅示例,不太好看
    def setHeaderType(self,shape=QFrame.Box,shadow=QFrame.Sunken,linewidth=1,txtCol='black',bkCol='white'):
        self.setFrameShape(shape)       # 设置表格框架样式
        self.setFrameShadow(shadow)     # 设置边框的样式
        self.setLineWidth(linewidth)    # 设置边框的线条宽度
        g.gf_setObjCol(self,'QHeaderViewEx',txtCol,bkCol)                     # 用g.py中的全局函数来改表头颜色也可成功

        
#########################################################################################################################
#重载标签类,标签可透明显示图像,用于在窗体上加载小分部图像
lst_ImgExName=['BMP','JPG','JPEG','PNG','TIF','TIFF','TGA','WMF','SVG','HEIF','RAW','WEBP']
lst_MovExName=['GIF','AVI','MPEG','MP4','MOV','MKV','WMV','FLV','RMVB','RM','RAM']
lst_AlignType=['TL','TC','TR','CL','CC','CR','DL','DC','DR']
class QLabelEx1(QLabel):  
    objcount=0   # 
    signal_Leftclicked = QtCore.pyqtSignal(object)        #自定信号,标签被左键单击,传回参数:控件对象本身
    signal_Rightclicked = QtCore.pyqtSignal(object)       #自定信号,标签被右键单击,传回参数:控件对象本身
    signal_Midclicked = QtCore.pyqtSignal(object)        #自定信号,标签被中键单击,传回参数:控件对象本身
    signal_LeftDropRelease = QtCore.pyqtSignal(object)    #自定信号,标签被左键拖动后释放,传回参数:控件对象本身

    #初始化对角需传递的参数为  父类,创建矩形,内容,     控件透明度      字体                     字体颜色           背景颜色                    
    def __init__(self,parent,text='',transt=1.0,font=QFont('宋体', 11),fcolor=QColor(0,0,0)):  
        super(QLabel, self).__init__(parent)
        self.type='TXT'   #标签控件的类型,'TXT'=纯文本标签,‘IMG'=可显示图片标签 'MOV':可播放动画标签
        #self.setGeometry(x,y,w,h)
        #self.ctlRect=QRect(x,y,w,h)     #控件的矩形区域
        self.imgRect=QRect()            #如果控件加载了图象或视频自身尺寸的矩形区域
        self.bDrawRect = False           #是否在标签控件外边画出矩形框        
        self.rectCol=QColor(255,0,0)    #画矩形边框的颜色
        self.rectPenWidth=2             #画矩形边框的线宽度
        self.bChgCtlRect=False          #如果self.ctlRect不能满足文字、图象的矩形区域时,是否允许控件变化其矩形来适应文字或图象要求的矩形区域
        self.move_Flag = False           #标签控件是否可以主窗体上拖动:对窗体元素,应设置为False
        self.bZoomImgSize=True          #控件的矩形区同图象的矩形区不相符时,是否允许图象或视频自动缩放以适应控件矩形区
        self.setScaledContents(self.bZoomImgSize)  # 设置标签的图片,设置True时图片自适应控件,为False时,只显示控件范围图片
        self.setAutoFillBackground(False) #不允许自动填充背景底色
        self.text=text       #标签是文本类型时显示的内容
        self.drawText=text   #标签是图片或视频类型时显示的内容
        self.alignFlags=Qt.AlignTop | Qt.AlignLeft   #对齐方式
        self.bDrawTxt = False   #显示图片的同时,是否将self.drawText画到图象上
        self.fontCol=fcolor   #字体颜色
        self.bkCol=QColor(255,255,255)     #如设置不透明时的标签背景颜色
        self.setFont(font)
        palette = QPalette()
        palette.setColor(QPalette.WindowText, self.fontCol) #设置字体颜色
  
        self.setPalette(palette)
        self.SetTransparent(transt)             #设置控件的透明度,1=不透明,0=完全透明
        self.setText(text)
        self.global_X=self.gobal_Y=0               #标签相对屏幕左上点(0,0)的坐标
        self.startPoint=QPoint()                    #鼠标在标签控件上压下开始的坐标点
        self.endPoint=QPoint()                      #鼠标在标签控件上压下结束时的坐标点
        self.mouse_X=self.mouse_Y=0                #鼠标在标签控件上相对标签控件范围的坐标
        self.origin_x=self.origin_y=0
        self.globalmouse_X=self.globalmouse_Y=0   #鼠标在标签控件上相对屏幕左上点(0,0)的坐标
        self.oldPos=QPoint()                       #移动前标签控件的位置
      
        self.curImgfilename=''
        self.curMovFileName=''
        self.curData=None     #当标签是加载的图片或动画时,将文件同容加载到内存中再显示,避免频繁读写文件
        self.image=QImage()
        self.curRotAngle=0.0 #图片当前旋转角度(角度,非弧度,顺时针为正)
        
        self.gifSpeed=200  #当前要播放的GIF动画的速度

        self.drawtxtX=self.drawtxtY=0
    #如要要不透明的标签,设置标签背景色
    def setBkCol(self,bkcol=QColor(255,255,255)):
        self.bkCol=bkcol
        palette = QPalette()
        self.setAutoFillBackground(True)          
        palette.setColor(QPalette.Background, self.bkCol)
        self.setPalette(palette)

    #设置标签中的文字/图片/GIF动画对齐方式Qt.AlignLeft:左对齐Qt.AlignRight:右对齐 Qt.AlignTop:顶部对齐Qt.AlignBottom:底部对齐Qt.AlignHCenter:水平居中Qt.AlignVCenter:垂直居中Qt.AlignCenter:同时水平和垂直居中
    def SetAlign(self,at='TL'):  #
        at=at.upper()
        self.alignFlags=Qt.AlignTop | Qt.AlignLeft
        if(at=='TL'): self.alignFlags=Qt.AlignTop | Qt.AlignLeft
        elif(at=='TC'): self.alignFlags=Qt.AlignTop | Qt.AlignHCenter
        elif(at=='TR'): self.alignFlags=Qt.AlignTop | Qt.AlignRight
        elif(at=='CL'): self.alignFlags=Qt.AlignVCenter | Qt.AlignLeft
        elif(at=='CC'): self.alignFlags=Qt.AlignVCenter | Qt.AlignHCenter
        elif(at=='CR'): self.alignFlags=Qt.AlignVCenter | Qt.AlignRight
        elif(at=='DL'): self.alignFlags=Qt.AlignBottom | Qt.AlignLeft
        elif(at=='DC'): self.alignFlags=Qt.AlignBottom | Qt.AlignHCenter
        elif(at=='DR'): self.alignFlags=Qt.AlignBottom | Qt.AlignRight
        else:self.alignFlags=Qt.AlignVCenter | Qt.AlignLeft
        self.setAlignment(self.alignFlags)
        self.setText(self.text)  #有时并没有出现对齐效果,只能采用先清除再重加载的方式

    #旋转控件中的图片一指定的角度:角度为正东向,向顺时针旋转的角度为正,反之为负(非弧度)
    def RotateImg(self,angle): 
        if(self.type=='IMG' and self.curData!=None):
            transform = QTransform()  
            transform.rotate(angle)     
            self.image=self.image.transformed(transform);             
            self.setPixmap(QPixmap.fromImage(self.image))  # 显示图片到Qlabel控件
            if(self.bChgCtlRect):   #为真时,旋转后同时调整控件大小
                self.resize(self.image.width(),self.image.height())

    #设置标签控件在加载图片时,控件尺寸同图片尺寸不符时,是否允许控件调整自身的矩形区域,以适应1:1的图象显示
    def ObjToImgSize(self):
        self.setScaledContents(self.bZoomImgSize)  #不允许自适应控件,只1:1显示到控件中,同时调整控件大小
        if(self.bChgCtlRect):   #只有先设置此属性为真时,才允许变化控件尺寸
            if(self.curData!=None):  
                image= QImage.fromData(self.curData)
                self.resize(image.width(),image.height()) #用下行后用设置参数中的矩形,用本行就是图片本身的尺寸
                self.ctlRect=QRect(self.x(),self.y(),self.width(),self.height())

    #设置标签加载的文件名称,可以是图片也可以是动画GIF或视频
    def LoadFile(self,filename=''):
        if(os.path.exists(filename)):
            file_extension = str(filename.split(".")[-1]).upper()
            bOK=False
            for exname in lst_ImgExName:
                if file_extension == exname:
                    self.type='IMG'
                    bOK=True
                    break
            for exname in lst_MovExName:
                if file_extension == exname:
                    self.type='MOV'
                    bOK=True
                    break
            if (bOK):
                with open(filename, 'rb') as f:
                    self.curData = f.read()
                self.image= QImage.fromData(self.curData)    
                self.curMovFileName=filename
                self.RefreshLable()
            else:
                print(f'没有找到对应扩展名: {file_extension} 的分类')
                self.type='TXT'
                self.ReshowText(self.text)
            self.ObjToImgSize()
        else:
            self.type='TXT'
            self.ReshowText(self.text)

    #清除图象,重新显示标签的文本
    def ReshowText(self,txt):
        self.text=txt
        self.clear()
        self.type='TXT'
        self.setText(txt)
    #设置显示图片的同时,画到标签控件上的文本,传入文本为空时,同标签控件初始化时的字符串一致,图形模式下,不调用此函数,默认不会绘出文本
    def setDrawText(self,txt,x=0,y=0):
        self.bDrawTxt=True
        if(len(txt)==0):
            self.drawText=self.text
        else:
            self.drawText=txt
        self.drawtxtX=x
        self.drawtxtY=y
    #重新显示标签(在用了LoadFile后)
    def RefreshLable(self):    #如果图片被调整乱了,且不想要控件尺寸同图片尺寸
        self.setScaledContents(self.bZoomImgSize)  #不允许自适应控件,只1:1显示到控件中,同时调整控件大小
        if(self.type=='IMG'):
            self.image= QImage.fromData(self.curData)
            self.setPixmap(QPixmap.fromImage(self.image))  # 显示图片到Qlabel控件
            self.imgRect=QRect(self.x(),self.y(),self.image.width(),self.image.height())
            if(self.bChgCtlRect):
                self.resize(self.image.width(),self.image.height()) #用下行后用设置参数中的矩形,用本行就是图片本身的尺寸             
            
        elif(self.type=='MOV'):
            self.movie = QMovie(self.curMovFileName)
            # 将movie应用到label上
            self.setMovie(self.movie)
            self.total_frame = self.movie.frameCount()
            self.gifSpeed=self.movie.speed()
            #print(f'当前GIF文件=’{self.curMovFileName}‘,总帧数={self.total_frame},默认正常播放速度={self.gifSpeed}')
            self.set_GifSpeed(self.gifSpeed)   #设置播放动画GIF的整速度:方法接受的是每1000毫秒播放的帧数比例,如是1:表示,一秒显示全部帧数,0.5表示一秒显示半数的帧数。
            self.movie.start()
            #"""
        else:    #self.type=='TXT'
            pass 

    #设置播放GIF动画的速度:  interval值哦本准备播放GIF的默认播放速度的倍数,如当前GIF默认播放速度为100
    def set_GifSpeed(self,interval=100.0):
        self.gifSpeed=interval
        if(self.type=='MOV' and self.movie!=None):
            self.movie.setSpeed(interval)  # 设置播放速度
    #设置标签控件的透明程度:对文字及图片均有效
    def SetTransparent(self,trans):
        if trans>1:trans=1
        elif trans<0:trans=0
        self.Transparent=trans
        opacity_effect = QGraphicsOpacityEffect(parent=self)
        opacity_effect.setOpacity(trans)  # 设置透明度
        self.setGraphicsEffect(opacity_effect)  # 将透明度效果应用到标签上
        #self.setWindowOpacity(self.Transparent)
    #设置本标签对象是在最上方还是在最下方    
    def setLabelLayer(self,bTop=True):
        if(bTop):self.raise_()
        else:self.lower()
    #设置标签显示的文本的字体
    def setTextFont(self,fontname='宋体',fontsize=11,bBold=False,bItalic=False,bUnderline=False):
        font = QFont()
        font.setFamily(fontname)       # 设置字体名称
        font.setPointSize(fontsize)    # 设置字体大小
        font.setBold(bBold)            # 设置字体加粗
        font.setItalic(False)
        font.setUnderline(False)
        self.setFont(font)
    #设置标签显示的文本的字体
    def setTextCol(self,fcol=QColor(0,0,0)):
        self.setStyleSheet(f"QLabel {{ color: {fcol.name()}; }}")
     #设置标签显示的文本的字体
    def setTextBkCol(self,bkcol=QColor(255,255,255)):
        if( not self.bTransparent):   #对非透明模式才支持设置标签背景颜色
            self.setAutoFillBackground(True)  # 确保背景自动填充
            palette = self.palette()
            palette.setColor(QPalette.Window, bkcol)  
            self.setPalette(palette)
    #得到标签矩形中心位置 
    def getObjRect(self):
        size = self.geometry()
        self.centerX=size.x()+size.width()/2
        self.centerY=size.y()+size.height()/2
        return size.x(),size.y(),size.width(),size.height()
    
    #鼠标按下事件重载   
    def mousePressEvent(self, event):  
        self.startPoint=event.pos()
        self.oldPos=QPoint(event.globalX(),event.globalY())                         
        # 核心部分: 当鼠标点击是左键 并且 在top控件内点击时候触发 
        if (event.button() == Qt.LeftButton and self.move_Flag):    #and self.top.underMouse():
            self.setCursor(Qt.OpenHandCursor)    #移动时设置成手型光标
            # 但判断条件满足时候, 把拖动标识位设定为真
            #self.move_Flag = True
            self.globalmouse_X = event.globalX()
            self.globalmouse_Y = event.globalY()
            # 获取窗体当前坐标
            self.origin_x = self.x()
            self.origin_y = self.y()
        
     #鼠标移动事件重载          
    def mouseMoveEvent(self, event):  
        # 拖动标识位设定为真时, 进入移动事件
        if self.move_Flag:
            # 计算鼠标移动的x,y位移
            move_x = event.globalX() - self.globalmouse_X
            move_y = event.globalY() - self.globalmouse_Y
            # 计算窗体更新后的坐标:更新后的坐标 = 原本的坐标 + 鼠标的位移
            dest_x = self.origin_x + move_x
            dest_y = self.origin_y + move_y
            # 移动本标签控件
            size = self.geometry()
            self.move(dest_x, dest_y)
            self.ctlRect=QRect(self.x(),self.y(),self.width(),self.height())
            self.setLabelLayer(True)    #拖动的标签控件角色在最顶端显示
            
    # 鼠标左键释放        
    def mouseReleaseEvent(self, event):
        self.endPoint=event.pos()
        newPos=QPoint(event.globalX(),event.globalY())  
        if (event.button() == Qt.LeftButton and self.move_Flag): 
            self.setCursor(Qt.ArrowCursor) # 设定鼠标为普通状态: 箭头
        if(self.move_Flag==False):   #非对象拖动状态,鼠标在控件区域移动一位置
            if(abs(self.endPoint.x()-self.startPoint.x())<2 and abs(self.endPoint.y()-self.startPoint.y())<2 ):  #判断是否单击
                if(event.button() == Qt.LeftButton):
                    print('非拖动状态下:是左键单击不是拖动')
                    self.signal_Leftclicked.emit(self)
                elif(event.button() == Qt.RightButton):
                    print('非拖动状态下:是右键单击不是拖动')
                    self.signal_Rightclicked.emit(self)
                elif(event.button() == Qt.MidButton):
                    print('非拖动状态下:是中键单击不是拖动')
                    self.signal_Midclicked.emit(self)
            else:
                print('非拖动状态下,鼠标在控件上移动了一位置')
        else:   #拖动对象状态,除非一点也没拖动,否则不对单位处理
            if(abs(self.oldPos.x()-newPos.x())<2 and abs(self.oldPos.y()-newPos.y())<2 ):
                print('虽是拖动状态但是并没拖动对象')
                if(event.button() == Qt.LeftButton):
                    print('拖动状态下:是左键单击不是拖动')
                    self.signal_Leftclicked.emit(self)
                elif(event.button() == Qt.RightButton):
                    print('拖动状态下:是右键单击不是拖动')
                    self.signal_Rightclicked.emit(self)
                elif(event.button() == Qt.MidButton):
                    print('拖动状态下:是中键单击不是拖动')
                    self.signal_Midclicked.emit(self)
            else:   #拖动对象移动了位置 
                print('拖动状态下:左键拖动控件移动了位置')
                self.signal_LeftDropRelease.emit(self)
                     
    #重载绘图函数:
    def paintEvent(self, event):
        if (self.bDrawRect):  #标签控件是否绘制边框架
            self.DrawObjRect(self.rectCol,self.rectPenWidth)   #为控件画出外边框
        if(self.bDrawTxt): #是否在标签控件上画出文本
            pen = QPen()                    # 创建画笔对象
            painter = QPainter(self)        # 此QPainter只能在paintEvent中定义,不能定义成类的self成员对象,也不能在其地方(如其他窗口,线程中)定义,否则没有绘画功能显示
            #绘制
            pen.setColor(self.fontCol)                 
            painter.drawText(self.drawtxtX,self.drawtxtY,self.width(),self.height(),self.alignFlags,self.drawText) 
        return super().paintEvent(event)                #调用主窗口的重绘事件,不用不会加载动画只显示第一帖,用了动画加载正常,但又多了一静态图第一帖
        
    #画出当前控件的矩形框(用于对象被选择时)
    def DrawObjRect(self,pencol,penwidth):
        self.rectCol=pencol
        self.rectPenWidth=penwidth
        if(self.bDrawRect):
            pen = QPen()                    # 创建画笔对象
            brush = QBrush()                # 创建画刷对象
            painter = QPainter(self)        # 此QPainter只能在paintEvent中定义,不能定义成类的self成员对象,也不能在其地方(如其他窗口,线程中)定义,否则没有绘画功能显示
            #绘制
            pen.setColor(pencol)                 
            pen.setStyle(Qt.SolidLine)               
            pen.setWidth(penwidth)          # 设置画笔宽度
            painter.setPen(pen)             # 设置画笔
            self.pixmap = QPixmap.fromImage(self.image)
           #painter.drawPixmap(0, 0, self.pixmap)
            painter.drawRect(0,0,self.width(),self.height()) 

全局变量及全局函数模块g.py

#g.py:全局变量及全局函数模块,将常用的一个通用变量,全局变量,通用函数在此模块中定义,可直接使用,如报错可使用g.变量或g.模块名也行。
#定义一全局模块,定整个应用程序用的全局变量和全局函数
import os,sys       #导入系统模块
import time
import math
import copy 
import random
import PyQt5
from PyQt5 import *
from PyQt5 import QtCore  
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
##########################################################################
#定义全局变更
g_appPath=sys.path[0]                  #应用程序主程序目录,要在所有模块使用
g_MAX_ID=10000                #定义最大ID号的合局变量,此变更在外部引用此变量并设置别名时,初使值会同它一样,但实际各是各的值了,互不关联,除非直接引用
                              #在其他模块要使用此全局变量,用  模块名.变量名      #在本模块中的函数及类中要使用此变量,需加上语句:global 变量名,否则函数体内的同名变量只是函数中的一私有变量
g_startTime=0.0
#PYTHONT或PYQT5/6支持的可用文字表示的颜色列表
dic_Cols={"lightpink":[[255,192,193],0xFFB6C1,"浅粉红色"],
        "pink":[[255,192,203],0xFFC0CB,"粉红色"],
        "crimson":[[220,20,60],0xDC143C,"猩红色"],
        "lavenderblush":[[255,240,245],0xFFF0F5,"脸红的淡紫色"],
        "palevioletred":[[219,112,147],0xDB7093,"苍白的紫罗兰红色"],
        "hotpink":[[255,105,180],0xFF69B4,"热情的粉红"],
        "deeppink":[[255,20,147],0xFF1493,"深粉色"],
        "mediumvioletred":[[199,21,133],0xC71585,"适中的紫罗兰红色"],
        "orchid":[[218,112,214],0xDA70D6,"兰花的紫色"],
        "thistle":[[216,191,216],0xD8BFD8,"蓟色"],
        "plum":[[221,160,221],0xDDA0DD,"李子色"],
        "violet":[[238,130,238],0xEE82EE,"紫罗兰色"],
        "magenta":[[255,0,255],0xFF00FF,"洋红色"],
        "fuchsia":[[255,0,255],0xFF00FF,"灯笼海棠(紫红色)"],
        "darkmagenta":[[139,0,139],0x8B008B,"深洋红色"],
        "purple":[[128,0,128],0x800080,"紫色"],
        "mediumorchid":[[186,85,211],0xBA55D3,"适中的兰花紫色"],
        "darkviolet":[[148,0,211],0x9400D3,"深紫罗兰色"],
        "darkorchid":[[153,50,204],0x9932CC,"深兰花紫"],
        "indigo":[[75,0,130],0x4B0082,"靛青色"],
        "blueviolet":[[138,43,226],0x8A2BE2,"深紫罗兰的蓝色"],
        "mediumpurple":[[147,112,219],0x9370DB,"适中的紫色"],
        "mediumslateblue":[[123,104,238],0x7B68EE,"适中的板岩暗蓝灰色"],
        "slateblue":[[106,90,205],0x6A5ACD,"板岩暗蓝灰色"],
        "darkslateblue":[[72,61,139],0x483D8B,"深岩暗蓝灰色"],
        "lavender":[[230,230,250],0xE6E6FA,"熏衣草花的淡紫色"],
        "ghostwhite":[[248,248,255],0xF8F8FF,"幽灵的白色"],
        "blue":[[0,0,255],0x0000FF,"纯蓝色"],
        "mediumblue":[[0,0,205],0x0000CD,"适中的蓝色"],
        "midnightblue":[[25,25,112],0x191970,"午夜的蓝色"],
        "darkblue":[[0,0,139],0x00008B,"深蓝色"],
        "navy":[[0,0,128],0x000080,"海军蓝色"],
        "royalblue":[[65,105,225],0x4169E1,"皇军蓝色"],
        "cornflowerblue":[[100,149,237],0x6495ED,"矢车菊的蓝色"],
        "lightsteelblue":[[176,196,222],0xB0C4DE,"淡钢蓝色"],
        "lightslategray":[[119,136,153],0x778899,"浅石板灰色"],
        "slategray":[[112,128,144],0x708090,"石板灰色"],
        "dodgerblue":[[30,144,255],0x1E90FF,"道奇蓝色"],
        "aliceblue":[[240,248,255],0xF0F8FF,"爱丽丝蓝"],
        "steelblue":[[70,130,180],0x4682B4,"钢蓝"],
        "lightskyblue":[[135,206,250],0x87CEFA,"淡蓝色"],
        "skyblue":[[135,206,235],0x87CEEB,"天蓝色"],
        "deepskyblue":[[0,191,255],0x00BFFF,"深天蓝"],
        "lightblue":[[173,216,230],0xADD8E6,"淡蓝"],
        "powderblue":[[176,224,230],0xB0E0E6,"火药蓝"],
        "cadetblue":[[95,158,160],0x5F9EA0,"军校蓝"],
        "azure":[[240,255,255],0xF0FFFF,"蔚蓝色"],
        "lightcyan":[[225,255,255],0xE1FFFF,"淡青色"],
        "paleturquoise":[[175,238,238],0xAFEEEE,"苍白的绿宝石"],
        "cyan":[[0,255,255],0x00FFFF,"青色"],
        "aqua":[[0,255,255],0x00FFFF,"水绿色"],
        "darkturquoise":[[0,206,209],0x00CED1,"深绿宝石色"],
        "darkslategray":[[47,79,79],0x2F4F4F,"深石板灰色"],
        "darkcyan":[[0,139,139],0x008B8B,"深青色"],
        "teal":[[0,128,128],0x008080,"水鸭色"],
        "mediumturquoise":[[72,209,204],0x48D1CC,"适中的绿宝石色"],
        "lightseagreen":[[32,178,170],0x20B2AA,"浅海洋绿色"],
        "turquoise":[[64,224,208],0x40E0D0,"绿宝石色"],
        "aquamarine":[[127,255,170],0x7FFFAA,"绿玉、碧绿色"],
        "mediumaquamarine":[[0,250,154],0x00FA9A,"适中的碧绿色"],
        "mediumspringgreen":[[245,255,250],0xF5FFFA,"适中的春天绿色"],
        "mintcream":[[0,255,127],0x00FF7F,"薄荷奶油色"],
        "springgreen":[[60,179,113],0x3CB371,"春天的绿色"],
        "seagreen":[[46,139,87],0x2E8B57,"海洋绿色"],
        "honeydew":[[240,255,240],0xF0FFF0,"蜂蜜色"],
        "lightgreen":[[144,238,144],0x90EE90,"淡绿色"],
        "palegreen":[[152,251,152],0x98FB98,"苍白的绿色"],
        "darkseagreen":[[143,188,143],0x8FBC8F,"深海洋绿"],
        "limegreen":[[50,205,50],0x32CD32,"酸橙绿"],
        "lime":[[0,255,0],0x00FF00,"酸橙色"],
        "forestgreen":[[34,139,34],0x228B22,"森林绿"],
        "green":[[0,128,0],0x008000,"纯绿"],
        "darkgreen":[[0,100,0],0x006400,"深绿色"],
        "chartreuse":[[127,255,0],0x7FFF00,"查特酒绿"],
        "lawngreen":[[124,252,0],0x7CFC00,"草坪绿"],
        "greenyellow":[[173,255,47],0xADFF2F,"绿黄色"],
        "olivedrab":[[85,107,47],0x556B2F,"橄榄土褐色"],
        "beige":[[107,142,35],0x6B8E23,"米色(浅褐色)"],
        "lightgoldenrodyellow":[[250,250,210],0xFAFAD2,"浅秋麒麟黄"],
        "ivory":[[255,255,240],0xFFFFF0,"象牙色"],
        "lightyellow":[[255,255,224],0xFFFFE0,"浅黄色"],
        "yellow":[[255,255,0],0xFFFF00,"纯黄"],
        "olive":[[128,128,0],0x808000,"橄榄色"],
        "darkkhaki":[[189,183,107],0xBDB76B,"深卡其布色"],
        "lemonchiffon":[[255,250,205],0xFFFACD,"柠檬薄纱色"],
        "palegoldenrod":[[238,232,170],0xEEE8AA,"灰秋麒麟色"],
        "khaki":[[240,230,140],0xF0E68C,"卡其布色"],
        "gold":[[255,215,0],0xFFD700,"金色"],
        "cornsilk":[[255,248,220],0xFFF8DC,"玉米色"],
        "goldenrod":[[218,165,32],0xDAA520,"秋麒麟色"],
        "floralwhite":[[255,250,240],0xFFFAF0,"花的白色"],
        "oldlace":[[253,245,230],0xFDF5E6,"老饰带色"],
        "wheat":[[245,222,179],0xF5DEB3,"小麦色色"],
        "moccasin":[[255,228,181],0xFFE4B5,"鹿皮鞋色"],
        "orange":[[255,165,0],0xFFA500,"橙色"],
        "papayawhip":[[255,239,213],0xFFEFD5,"番木瓜色"],
        "blanchedalmond":[[255,235,205],0xFFEBCD,"漂白的杏仁色"],
        "navajowhite":[[255,222,173],0xFFDEAD,"纳瓦尔白色"],
        "antiquewhite":[[250,235,215],0xFAEBD7,"古代的白色"],
        "tan":[[210,180,140],0xD2B48C,"晒黑色"],
        "burlywood":[[222,184,135],0xDEB887,"结实的树色"],
        "bisque":[[255,228,196],0xFFE4C4,"(浓汤)乳脂,番茄色"],
        "darkorange":[[255,140,0],0xFF8C00,"深橙色"],
        "linen":[[250,240,230],0xFAF0E6,"亚麻布色"],
        "peru":[[205,133,63],0xCD853F,"秘鲁色"],
        "peachpuff":[[255,218,185],0xFFDAB9,"桃色"],
        "sandybrown":[[244,164,96],0xF4A460,"沙棕色"],
        "chocolate":[[210,105,30],0xD2691E,"巧克力色"],
        "saddlebrown":[[139,69,19],0x8B4513,"马鞍棕色"],
        "seashell":[[255,245,238],0xFFF5EE,"海贝壳色"],
        "sienna":[[160,82,45],0xA0522D,"黄土赭色"],
        "lightsalmon":[[255,160,122],0xFFA07A,"浅鲜肉(鲑鱼)色"],
        "coral":[[255,127,80],0xFF7F50,"珊瑚色"],
        "orangered":[[255,69,0],0xFF4500,"橙红色"],
        "darksalmon":[[233,150,122],0xE9967A,"深鲜肉(鲑鱼)色"],
        "tomato":[[255,99,71],0xFF6347,"番茄色"],
        "mistyrose":[[255,228,225],0xFFE4E1,"薄雾玫瑰色"],
        "salmon":[[250,128,114],0xFA8072,"鲜肉(鲑鱼)色"],
        "snow":[[255,250,250],0xFFFAFA,"白雪色"],
        "lightcoral":[[240,128,128],0xF08080,"淡珊瑚色"],
        "rosybrown":[[188,143,143],0xBC8F8F,"玫瑰棕色"],
        "indianred":[[205,92,92],0xCD5C5C,"印度红色"],
        "red":[[255,0,0],0xFF0000,"纯红色"],
        "brown":[[165,42,42],0xA52A2A,"棕色"],
        "firebrick":[[178,34,34],0xB22222,"耐火砖色"],
        "darkred":[[139,0,0],0x8B0000,"深红色"],
        "maroon":[[128,0,0],0x800000,"栗色"],
        "white":[[255,255,255],0xFFFFFF,"纯白色"],
        "whitesmoke":[[245,245,245],0xF5F5F5,"白烟色"],
        "gainsboro":[[220,220,220],0xDCDCDC,"亮灰色"],
        "lightgrey":[[211,211,211],0xD3D3D3,"浅灰色"],
        "silver":[[192,192,192],0xC0C0C0,"银白色"],
        "darkgray":[[169,169,169],0xA9A9A9,"深灰色"],
        "gray":[[128,128,128],0x808080,"灰色"],
        "dimgray":[[105,105,105],0x696969,"暗淡灰"],
        "black":[[0,0,0],0x000000,"纯黑色"]
}

# 设置对象文本及背景颜色,调用此函数时要确传入的控件对象支持.setStyleSheet方法
def gf_setObjCol(obj,objClassName,txtCol='black',bkCol='white'):
    stylestr=objClassName+"::section {background-color: "+bkCol+"; color: "+txtCol+"};} " 
    try:
        obj.setStyleSheet(stylestr)
    except Exception as e:  # 如果发生异常,执行这里的代码
         print('你所调用的控件对象不支持setStyleSheet方法')    

##########################################################################
#示例定义应用程序的一些基本设置参数字典,因是定义的字典,如果采用函数传递字典参数有调用函数的模块公用此字典中的数据(列表、元组类似,其他变量不公用,函数传递参数量会建副本)
g_dic_set={'AppName':'我的PYTHON程序',
           'AppVer':'0.0.1.0',
            'frmWidth':800,
            'frmHeight':600,
            'saveTime':1800,
            'defTempFile':'tmp.bak'
            
            }

#初始化应用程序:将应用程序的所有子目录全部加入到工作路径中,程序运时时,就不会出现找不到模块的问题了
#例lstPath=['\\MyModules','\\Data','\\Res','\\Frm'],或lstPath=['D:\\MyPrg\\MyModules','D:\\MyPrg\\Data','D:\\MyPrg\\Res','D:\\MyPrg\\Frm']
def gf_InitPrgPath(lstPath,bAbsPath=True):   #bAbsPath=True:表示传递的lstPath为相对路径,否则表示传递的是绝对路径
    global g_apppath
    lst_modlist=sys.path
    print(f"当前模块可查找的目录列表是:\t{lst_modlist}\n")
    if(bAbsPath):
        for path in lstPath:
            sys.path.append(f"{os.path.dirname(os.path.abspath(__file__))}{path}")
    else:  #传入的是绝对路径时
        for path in lstPath:
            sys.path.append(f"{path}") 
    print(f"增加路径后,当前模块可查找的目录列表是:\t{lst_modlist}\n")
    g_appPath=sys.path[0]        #主运行目录位于数组列表的0位,不可更改
    print(f"当前程序所在的目录是:\t{g_appPath}\n")

#得到模块中的全局变量值
def gf_getAppPath():
    return g_appPath

#得到PYQT5/6颜色字符串转换成QColor颜色对象
def gf_getColRgb(colstr,defcol=QColor(0,0,0)):
    lst_col = dic_Col.get(colstr,defcol) #用get函数来得到对应字典key的值,如果没有,得到默认值,防报错
    return lst_col[1]    #返回lst_col[2]结果一样

#设置表格显示类型:在主窗口处调用此函数  
    #框架形状:self.setFrameShape(..)
    # QFrame.NoFrame=什么都没画, QFrame.Box=围绕其内容绘制一个框(需要设置外线和中线的宽度),QFrame.Panel=绘制一个面板,使内容显得凸起或凹陷(设置中线宽度没用)
    #         QFrame.HLine=绘制一条没有框架的水平线(用作分隔符)QFrame.VLine=绘制一条无框架的垂直线(用作分隔符)
    #         QFrame.StyledPanel=样式化的平面框架,
    #         QFrame.WinPanel=绘制一个可以像Windows中那样凸起或凹陷的矩形面板 
    #框架阴影:self.setFrameShadow(..)
    #        QFrame.Plain=框架和内容与周围环境呈现水平;(没有任何3D效果)
    #        QFrame.Raised=框架和内容出现; 使用当前颜色组的浅色和深色绘制3D凸起线
    #        QFrame.Sunken=框架和内容出现凹陷; 使用当前颜色组的浅色和深色绘制3D凹陷线
    #框架线宽:setLineWidth(int width)=设置外线宽度    lineWidth() =获取外线宽度
    #       setMidLineWidth(int width) =设置中线宽度(部分框架形状,设置框架中线宽度是没用的)midLineWidth()=获取中线宽度
    #       frameWidth() =获取总宽度(2*外线宽度 + 中线宽度).个人理解:总线宽 = 外线宽度 + 中线宽度 + 内线宽度(内线宽度 = 外线宽度,类似于内部阴影)
def gf_setFrmaeype(ctlobj,shape=QFrame.Box,shadow=QFrame.Sunken,linewidth=1):   
        ctlobj.setFrameShape(shape)       # 设置表格框架样式
        ctlobj.setFrameShadow(shadow)     # 设置边框的样式
        ctlobj.setLineWidth(linewidth)    # 设置边框的线条宽度

#得到文件的路径,文件名,扩展名三个返回参数,bUpper=返回值时是否全转为大写,pathEndBackslash=返回前是否为路径值尾部加一反斜杠
def gf_getFilePathName(filePathName,bUpper=False,pathEndBackslash=True):
    bExist=os.path.exists(filePathName)
    file_path, file_name = os.path.split(filePathName)  #file_path的值没有最后一个反斜杠,使用时要单独在其末尾加上一个反斜杠再加文件名
    file_ext = os.path.splitext(filePathName)[1] 
    #或file_ext = str(filePathName.split(".")[-1]).upper()  #得到扩展名(同时转大写)
    if(bUpper): #是否转大写
        file_path=file_path.upper()
        file_name=file_name.upper()
        file_ext=file_ext.upper()
    if(pathEndBackslash): #为防止目录再组合文件形成新的绝对路径时,忘记对路径尾部加上反斜杠,通过此参数来加上
        file_path=file_path+"\\"
    return file_path,file_name,file_ext

###################################################################################
#设置及得到程序指定代码段的运行时间
def gf_setStartTime():
    global g_startTime
    g_startTime=time.time()
def gf_getRunTime(info=''):
    endTime=time.time()
    print(f'{info}:{round((endTime-g_startTime),4)}秒')
    return endTime-g_startTime

以上全部完整文件在顶部链接应可以免费下载得到