You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Maya UI脚本中触发独立Maya批处理脚本执行?

Maya脚本调用独立批处理的实现方案

问题说明

开发Autodesk Maya相关脚本时,需实现以下功能:

  • 已有Maya内UI交互脚本:包含动画文件夹选择框、「Set Path」按钮和「Batch Fix」批处理按钮。
  • 已有Maya独立模式批处理脚本:批量处理指定文件夹内FBX文件,删除指定关节的translateXYZ关键帧并重新导出FBX。
  • 需要实现:点击UI中的「Batch Fix」按钮时,调用并启动该独立批处理脚本。

原脚本代码

UI脚本(AnimJntClnup.py)

import maya.cmds as cmds
from PySide2 import QtCore, QtGui, QtWidgets
import os
import maya.mel as mel


presetPathName = "C:/Users/Valeriya/Documents/maya/scripts/CHanimExport/UMA_Male_Rig_AnimationExport.fbxexportpreset"
USERAPPDIR = cmds.internalVar(userAppDir=True)
DIRECTORY = os.path.join(USERAPPDIR, 'processedAnimation')

# 补充缺失的关节列表
joint_list = ['Leg_L','Knee_L','Foot_L','Toe_L']


def load_fbx_export_preset(presetPathName):
    mel.eval('loadPlugin "fbxmaya";')
    mel.eval('FBXLoadExportPresetFile -f "{}";'.format(presetPathName))


class batch_cleanup_UI(QtWidgets.QDialog):

    def __init__(self):
        super(batch_cleanup_UI, self).__init__()
        self.anim_folder_path = None
        self.export_dir = DIRECTORY
        self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
        self.setup_ui()

    def setup_ui(self):
        self.setWindowTitle("Batch Joint CleanUp")
        self.setObjectName("BatchJointCleanUpID")
        self.setMinimumSize(400, 100)
        self.setMaximumSize(800, 200)
        self.resize(400, 100)
        
        self.main_layout = QtWidgets.QVBoxLayout()
        self.main_layout.setAlignment(QtCore.Qt.AlignTop)
        self.main_layout.setContentsMargins(3, 3, 3, 3)
        self.main_layout.setSpacing(5)
        self.setLayout(self.main_layout)

        self.text_field_layout = QtWidgets.QHBoxLayout()
        self.main_layout.addLayout(self.text_field_layout)

        self.batch_btn_layout = QtWidgets.QVBoxLayout()
        self.main_layout.addLayout(self.batch_btn_layout)

    # Text Line
        self.nameLabel = QtWidgets.QLabel(self)
        self.nameLabel.setText('Animation Folder:')
        self.text_field_layout.addWidget(self.nameLabel)
        self.saveNameField = QtWidgets.QLineEdit()
        self.text_field_layout.addWidget(self.saveNameField)
        self.btn_set_path = QtWidgets.QPushButton("Set Path")
        self.btn_set_path.clicked.connect(self.btn_set_path_clicked)
        self.text_field_layout.addWidget(self.btn_set_path)

    # Batch Btn 
        self.btn_batch_process = QtWidgets.QPushButton("Batch Fix")
        self.btn_batch_process.clicked.connect(self.btn_batch_process_clicked)
        self.batch_btn_layout.addWidget(self.btn_batch_process)

    def btn_set_path_clicked(self):
        anim_folder_path = cmds.fileDialog2(dialogStyle=2, fileMode=3)
        if anim_folder_path:
            selected_folder_path = anim_folder_path[0]
            self.saveNameField.setText(selected_folder_path)
            self.anim_folder_path = selected_folder_path  # Set the attribute here
        return anim_folder_path
    
    @staticmethod
    def createDirectory(directory=DIRECTORY):
        '''
        Creates a given Directory if it doesn't exist
        Args:
            directory(str):The directory to create
        '''
        if not os.path.exists(directory):
            os.mkdir(directory)
        return directory


    def btn_batch_process_clicked(self):
        if not self.anim_folder_path:
            cmds.warning("Please set the animation folder path.")
            return

        fbx_files = [f for f in os.listdir(self.anim_folder_path) if f.endswith('.fbx')]
        if not fbx_files:
            cmds.warning("No FBX files found in the specified folder.")
            return

        for fbx_file in fbx_files:
            full_path = os.path.join(self.anim_folder_path, fbx_file)
            self.open_fbx_file(full_path)

    def open_fbx_file(self, file_path):
        # Load the FBX export preset
        load_fbx_export_preset(presetPathName)

        # Import the FBX file
        cmds.file(file_path, i=True, type="FBX", ignoreVersion=True, ra=True, mergeNamespacesOnClash=False, namespace=":")

        # Perform additional operations if needed
        file_short_name = os.path.basename(file_path)
        file_name = file_short_name.split(".")[0]

        # Open Files in a given folder
        for joint in joint_list:
            cmds.cutKey(joint, attribute=['translateX', 'translateY', 'translateZ'], option="keys")

        # Save the file to overwrite it in the same folder
        cmds.select('ParentForExportDelete', hierarchy=True)
        nameExport = os.path.join(self.export_dir, file_name + '.fbx')
        cmds.FBXExport(s=True, f=nameExport, force=True, options=True)


def clean_ui():
    if cmds.window("BatchJointCleanUpID", exists=True):
        cmds.deleteUI("BatchJointCleanUpID", window=True)

    if cmds.windowPref("BatchJointCleanUpID", exists=True):
        cmds.windowPref("BatchJointCleanUpID", remove=True)

clean_ui()
win = batch_cleanup_UI()
win.show()

独立批处理脚本(原版本)

import os
from maya.standalone import standalone
from maya.cmds import cmds
import maya.mel as mel
from AnimJntClnup import batch_cleanup_UI


joint_list = ['Leg_L','Knee_L','Foot_L','Toe_L']


standalone.initialize(name="python")
path = batch_cleanup_UI.self.anim_folder_path

for f in files:
    cmds.file(f, open=True, type='FBX', force=True)
    #cmds.file(f, open=True)

    file_short_name = os.path.basename(f)
    file_name = file_short_name.split(".")[0]

    #Open Files in a given folder


    for joint in joint_list:


        cmds.cutKey( joint, attribute=['translateX', 'translateY', 'translateZ'], option="keys" )


    # Save the file to overwrite it
    nameExport = path + '/' + file_name + '.fbx'
    mel.eval('file -force -options "fbx" -type "FBX export" -pr -ea "{}";'.format(nameExport))

    print("/n/n/nOutput:/n")
    print("/n" + file_name + "=>" + "Saved here => " + nameExport)


#print(file_name)

standalone.uninitialize()

修改后的实现方案

1. 修正UI脚本的批处理触发逻辑

在UI脚本中添加subprocess模块调用,通过Maya独立解释器启动批处理脚本,并传递路径参数:

# 在UI脚本顶部新增导入
import subprocess

# 修改btn_batch_process_clicked方法
def btn_batch_process_clicked(self):
    if not self.anim_folder_path:
        cmds.warning("Please set the animation folder path.")
        return

    # 创建导出目录
    self.createDirectory(self.export_dir)

    # 获取Maya独立Python解释器路径
    maya_python = os.path.join(os.environ.get('MAYA_LOCATION'), 'bin', 'mayapy.exe')
    # 替换为你的独立脚本实际路径
    standalone_script = "C:/Users/Valeriya/Documents/maya/scripts/CHanimExport/standalone_batch_fix.py"

    # 启动独立脚本,传递源文件夹和导出文件夹参数
    subprocess.Popen([
        maya_python,
        standalone_script,
        self.anim_folder_path,
        self.export_dir
    ])
    cmds.info("批处理已在独立模式启动,可查看控制台输出进度")

2. 重构独立批处理脚本

修改独立脚本,通过命令行参数接收路径,修复原脚本的逻辑错误:

import os
import sys
from maya.standalone import standalone
import maya.cmds as cmds
import maya.mel as mel

joint_list = ['Leg_L','Knee_L','Foot_L','Toe_L']

def main():
    # 检查命令行参数是否齐全
    if len(sys.argv) < 3:
        print("使用方式: mayapy standalone_batch_fix.py <源文件夹路径> <导出文件夹路径>")
        sys.exit(1)
    
    source_folder = sys.argv[1]
    export_folder = sys.argv[2]

    # 初始化Maya独立环境
    standalone.initialize(name="python")
    # 加载FBX插件
    mel.eval('loadPlugin "fbxmaya";')

    # 获取所有FBX文件
    fbx_files = [os.path.join(source_folder, f) for f in os.listdir(source_folder) if f.endswith('.fbx')]
    if not fbx_files:
        print("指定文件夹中未找到FBX文件")
        standalone.uninitialize()
        sys.exit(0)

    # 批量处理每个FBX
    for f in fbx_files:
        # 打开FBX文件
        cmds.file(f, open=True, type='FBX', force=True, ignoreVersion=True, ra=True)
        
        file_short_name = os.path.basename(f)
        file_name = file_short_name.split(".")[0]

        # 删除指定关节的translate关键帧
        for joint in joint_list:
            if cmds.objExists(joint):
                cmds.cutKey(joint, attribute=['translateX', 'translateY', 'translateZ'], option="keys")
            else:
                print(f"文件{f}中未找到关节{joint}")

        # 导出处理后的FBX
        export_path = os.path.join(export_folder, f"{file_name}_fixed.fbx")
        cmds.FBXExport(s=True, f=export_path, force=True)
        print(f"\n输出结果:\n{file_name} => 已保存至 => {export_path}")

    # 卸载Maya独立环境
    standalone.uninitialize()

if __name__ == "__main__":
    main()

关键实现要点

  • 参数传递:通过命令行参数传递路径,避免独立脚本直接访问Maya进程内的UI实例(跨进程无法共享对象)。
  • 环境隔离:使用mayapy.exe启动独立脚本,完全隔离Maya主进程,避免批处理操作影响当前场景。
  • 错误处理:增加参数检查、关节存在性判断,提升批处理稳定性。
  • 路径兼容性:使用os.path模块处理路径,避免硬编码导致的跨平台问题。

内容的提问来源于stack exchange,提问作者Valeriya Fedoseeva

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 09:30:55