如何在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
相关产品推荐
相关产品推荐

