欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于Maya API和PySide2的插件开发——用TextBrowser实现文件信息的显示

程序员文章站 2022-07-12 13:51:18
...

做这个的主要目的是在maya里实现一个类似unity的prefab的一个节点预设库,来方便地创建、管理和使用提前制作好的节点,省去大量重复工作所耗费的时间。

程序前面的部分参考了一个视频:https://www.bilibili.com/video/BV1it411F7LN?p=1

需要import的内容:

# -*- coding: UTF-8 -*-

import maya.OpenMayaUI as mul
import maya.OpenMaya as om
from PySide2.QtWidgets import QMainWindow, QWidget
import maya.cmds as cmds
import shiboken2
from PySide2 import QtCore, QtGui, QtWidgets
import os
import glob
import shutil
import webbrowser
import time
import threading

同组同学在视频中程序的基础上实现了选择文件进行打开、导入的操作(下图)之后,我把视频里没用过的左下Node Types部分从原来的treewidget改成了textbrowser,来显示文件的路径、名称、创建日期和最近修改日期。
基于Maya API和PySide2的插件开发——用TextBrowser实现文件信息的显示
这个功能涉及到两个不同的窗口,在小窗口进行操作是主窗口是得不到信息的,我希望的是在打开小窗口之后主窗口不断的检测文件的选择是否有改动,以便实时更新显示的信息。于是我考虑了在打开小窗口的同时,再开一个线程,持续地每隔一定时间检测小窗口是否有变化。

- 判断选择的文件是否有改动

为了实现上述功能,我一共用了三个全局变量,分别是所选文件及路径present_file_path、路径是否有改动if_file_path_changed、小窗是否已关闭if_window_closed,初始值分别是空字符串、0和1。

检测是在右侧文件列表框的类中实现的。为了方便后面调用TextBrowser的方法,首先在类的初始化时将TextBrowser作为一个参数传到这个类里,并在初始化时实例化一个对象:

# 初始化加入textBrowser方便后面调用其函数
    def __init__(self, tb):
        super(TestGridLayout, self).__init__()
        self.maxColumnCount = 5
        self.setSpacing(10)
        self.setGeometry(QtCore.QRect(0, 0, 500, 500))
        self.nodeTypes_textBrowser = tb

因为只有在打开小窗之后才需要检测路径及改动,所以我在open_file()中添加了开新线程的代码,此时需要将if_window_closed的值同时改为0:

    def open_file(self):
        window2 = OpenImportDialog()
        window2.show()
        # 在另一个线程上不断检测选择的文件是否发生改变
        # if_window_closed的值改为0,表示有文件选择窗口生成,使主窗口准备获取文件路径
        global if_window_closed
        if_window_closed = 0
        t = threading.Thread(target=self.run, args=())
        t.setDaemon(True)  # 把子进程设置为守护线程,必须在start()之前设置
        t.start()

该线程运行的函数run同样定义在这个类里面(写在了open_file前面):

    def run(self):
        global if_file_path_changed
        global present_file_path
        global if_window_closed

        while if_window_closed == 0:
            while (if_file_path_changed == 0) and (if_window_closed == 0):
                time.sleep(1)

            if if_file_path_changed == 1:
                self.nodeTypes_textBrowser.showInfo(present_file_path)
                self.nodeTypes_textBrowser.moveCursor(self.nodeTypes_textBrowser.textCursor().End)
                if_file_path_changed = 0

这个函数用了两个循环:外层的循环用来判断小窗是否还存在,如果存在就继续检测,如果不存在则终止;内层的循环则是判断输入框里是否有改动,如果没有则每隔一秒检测一次,如果有就跳出这一层循环,将获取到的文件信息显示在文本框里并刷新到最底部,然后把if_file_path_changed的值重置成0,判断此时小窗有没有关闭,如果没有就继续检测。

- 显示信息

这里用到了另一个函数showInfo,它是定义在TextBrowser的类里面的,还用到了两个获取时间的函数,分别是获取最近修改的时间和文件创建的时间:

# 两个获取时间的函数
def fileAlteredTime(file):
    t1 = time.ctime(os.path.getmtime(file))
    return t1


def fileCreatedTime(file):
    t2 = time.ctime(os.path.getctime(file))
    return t2


# TextBrowser的类,定义了一个显示时间的函数
class TestTextBrowser(QtWidgets.QTextBrowser):
    def __init__(self, parent=None):
        super(TestTextBrowser, self).__init__(parent)

    def showInfo(self, text):
        self.append('====================')
        self.append('File:')
        self.append(text)
        self.append('Created Time:')
        self.append(fileCreatedTime(text))
        self.append('Altered Time:')
        self.append(fileAlteredTime(text))

showInfo这个函数通过不断添加信息,然后每次添加完都将滑块拉到最底(其实是把光标移动到内容结尾)来保证每次看到的都是最新选择的文件的信息。

刷新到最底的一行代码self.nodeTypes_textBrowser.moveCursor(self.nodeTypes_textBrowser.textCursor().End)在后面的一个函数run里面。

- 控制循环检测

新线程中的循环不能一直进行下去,需要在合适的时候开始和停止,控制它的改变全局变量的部分全都在小窗口的OpenImportDialog类中。当输入框里的信息发生变化的时候才需要发信号给主窗口进行显示,因此我把全局变量的赋值放在了显示路径文本的代码后面:

    def show_file_select_dialog(self):
        file_path, self.selected_filter = QtWidgets.QFileDialog.getOpenFileName(
            self, "Select File", "", self.FILE_FILTERS, self.selected_filter)
        if file_path:
            self.filepath_le.setText(file_path)
#6
            # 将全局变量if_file_path_changed的值改为1,来通知主窗口已选取文件或选取的文件发生改变
            # 同时将文件路径赋给present_file_path
            global if_file_path_changed
            global present_file_path
            if_file_path_changed = 1
            present_file_path = file_path

而这个线程只需要在小窗存在的时候进行。小窗如果通过任何方式关闭了,也就意味着选定的文件不再会有新的改动,这时候就可以停止检测。所以在Apply和Close对应的函数后面都应该对全局变量进行修改:

    def open_file(self, file_path):
        force = self.force_cb.isChecked()
        if not force and cmds.file(q=True, modified=True):
            result = QtWidgets.QMessageBox.question(
                self, "Modified", "Current scene has unsaved changes.Continue?")
            if result == QtWidgets.QMessageBox.StandardButton.Yes:
                force = True
            else:
                return

        cmds.file(file_path, open=True, ignoreVersion=True, force=force)
#7
        # 窗口关闭后修改全局变量,停止检测
        global if_window_closed
        if_window_closed = 1

    def import_file(self, file_path):
        cmds.file(file_path, i=True, ignoreVersion=True)
        # 窗口关闭后修改全局变量,停止检测
        global if_window_closed
        if_window_closed = 1

    def reference_file(self, file_path):
        cmds.file(file_path, reference=True, ignoreVersion=True)
        # 窗口关闭后修改全局变量,停止检测
        global if_window_closed
        if_window_closed = 1

至于关闭窗口,原本用的方法是在创建连接和函数里把Close按钮和close()函数联系起来,但这样不方便在关闭的同时修改变量,所以我新写了下面这个函数

    def closeWindow(self):
        global if_window_closed
        if_window_closed = 1
        self.close()

并且把create_connections()(OpenImportDialog类里面的一个函数)中的
self.close_btn.clicked.connect(self.close)
改成了
self.close_btn.clicked.connect(self.closeWindow)

- 完成

下面是两个效果图
基于Maya API和PySide2的插件开发——用TextBrowser实现文件信息的显示
基于Maya API和PySide2的插件开发——用TextBrowser实现文件信息的显示