[小脚本] maya 命令行常用操作

文章介绍了使用Python脚本在Maya中进行骨骼重命名、层级结构操作(如添加后缀)、属性复制(如左手旋转到右手),以及一个批量处理框架,用于从原始FBX文件中提取特定骨骼并导出重定向的动画。
摘要由CSDN通过智能技术生成

其实这些代码大部分是从 chatgpt 中生成的。

动画数据传递

把一个带有动画数据的fbx (A) 中的旋转赋值到另一个fbx模型(B)中生成新动画。前提是这两个动画的骨骼定义和初始姿态是一样的,只是骨长和平移不一样。

** 为什么不能直接拖入,因为maya中直接拖入会把骨长也给改变了 **,现在就是要保证骨骼不变,只传递系数

首先将A的旋转数据提取出来

import maya.cmds as cmds
import os

# 定义文件路径
file_path = 'bone_rots.txt'

def save_rotations():
    # 获取所有骨骼
    all_bones = cmds.ls(type='joint', long=True)
    frame_range = cmds.playbackOptions(query=True, min=True), cmds.playbackOptions(query=True, max=True)

    # 保存旋转数据
    rotations = {}

    for frame in range(int(frame_range[0]), int(frame_range[1]) + 1):
        cmds.currentTime(frame)
        for bone in all_bones:
            rotation = cmds.xform(bone, query=True, rotation=True, worldSpace=True)
            if frame not in rotations:
                rotations[frame] = {}
            rotations[frame][bone] = rotation

    # 写入文件
    with open(file_path, 'w') as f:
        for frame, bones in rotations.items():
            for bone, rotation in bones.items():
                f.write(f"{frame}: {bone}: {rotation}\n")

    print(f"旋转数据已保存到 {file_path}。")

# 调用保存函数
save_rotations()

然后新建场景导入文件 B,加载动画系数产生动画


# 定义文件路径
file_path = 'bone_rots.txt'
def load_rotations():
    if not os.path.exists(file_path):
        print(f"文件 {file_path} 不存在。")
        return

    rotations = {}
    
    # 读取旋转数据
    with open(file_path, 'r') as f:
        for line in f:
            frame, bone, rotation = line.strip().split(': ')
            frame = int(frame)
            rotation = eval(rotation)  # 将字符串转换为列表
            if frame not in rotations:
                rotations[frame] = {}
            rotations[frame][bone] = rotation

    # 在新场景中应用旋转数据
    for frame, bones in rotations.items():
        cmds.currentTime(frame)
        for bone, rotation in bones.items():
            if cmds.objExists(bone):
                cmds.xform(bone, rotation=rotation, worldSpace=True)
                cmds.setKeyframe(bone, attribute='rotate', t=frame)

    print("旋转数据已加载并赋回骨骼。")

# 调用加载函数
load_rotations()

骨骼命名

import maya.cmds as cmds

def rename_bones():
    selected_bones = cmds.ls(type="joint")  # 获取选中的骨骼

    for bone in selected_bones:
        if "_" in bone:
            new_name = bone.split("_")[0]  # 获取下划线前面的部分作为新的骨骼名称
            cmds.rename(bone, new_name)  # 重命名骨骼

rename_bones()

层级 rename

import maya.cmds as cmds

def rename_bones_with_suffix(bone, suffix):
    children = cmds.listRelatives(bone, children=True, type="joint") or []  # 获取骨骼的子骨骼

    for child in children:
        rename_bones_with_suffix(child, suffix)  # 递归调用,处理子骨骼
        
    new_name = bone + suffix  # 添加结尾字符串
    cmds.rename(bone, new_name)  # 重命名骨骼
    print("将骨骼 {} 重命名为 {}".format(bone, new_name))

selected_bones = cmds.ls(selection=True, type="joint")  # 获取选中的骨骼

for bone in selected_bones:
    rename_bones_with_suffix(bone, "_R")

属性赋值

左手旋转复制到右手上

import maya.cmds as cmds

def flip_z_rotation(source_bone, target_bone):
    # 获取源骨骼的旋转值
    source_rotation = cmds.getAttr(source_bone + ".rotate")[0]
    
    # 翻转旋转值的Z轴
    flipped_rotation = [source_rotation[0], source_rotation[1], -source_rotation[2]]
    
    # 将翻转后的旋转值赋值给目标骨骼
    cmds.setAttr(target_bone + ".rotate", flipped_rotation[0], flipped_rotation[1], flipped_rotation[2], type="double3")
    
    print("将骨骼 {} 的旋转值翻转并赋值到骨骼 {}".format(source_bone, target_bone))

all_bones = cmds.ls(type="joint")  # 获取所有骨骼

for bone in all_bones:
    if "_L" in bone:
        # 替换"_L"为"_R"的骨骼
        target_bone = bone.replace("_L", "_R")
        
        # 检查替换后的骨骼是否存在
        if cmds.objExists(target_bone):
            flip_z_rotation(bone, target_bone)

控制台调用批量处理

自己的批量处理框架

# -*- coding: utf-8 -*-

import os
import json
import math
import errno
import numpy as np    

import maya.cmds as cmds
import pymel.core as pm
import pymel.core.nodetypes as nt
import pymel.core.datatypes as dt

from maya import mel 

def SetFPS(fps):
    unit = 'ntscf'
    if fps == 15:
        unit = 'game'
    elif fps == 24:
        unit = 'film'
    elif fps == 25:
        unit = 'pal'
    elif fps == 30:
        unit = 'ntsc'
    elif fps == 48:
        unit = 'show'
    elif fps == 50:
        unit = 'palf'
    elif fps == 60:
        unit = 'ntscf'
    else:
        unit = str(fps)+'fps'

    cmds.currentUnit( time=unit )
    fps = mel.eval('currentTimeUnitToFPS')
    # 等价的
    #mel.eval("currentUnit -time ntsc;")
    return fps

class Cls_get_pure_bone:
    def __init__(self, bones, dstRoot=None, fps=30):
        
        self.use_joints = bones
        self.dstRoot = dstRoot
        self.fps = fps    

    def setup(self):
        cmds.file(new=True, force=True)
        if self.fps == 30:
            mel.eval("currentUnit -time ntsc;")
            self._import_fps = False
        else:
            SetFPS(self.fps)
            self._import_fps = False
            #self._import_fps = True
        

    def run(self, animFbx, outFn, targetOnly=True):
        
        # import source animation
        pm.importFile(
            animFbx, 
            type='FBX',
            importFrameRate=self._import_fps,
            importTimeRange='override'
        )

        startFrame = pm.playbackOptions(animationStartTime=True, q=True)
        endFrame   = pm.playbackOptions(animationEndTime=True, q=True)
    
        def string_array_contains(array, string):
            for item in array:
                if item == string:
                    return True  # 包含指定字符串
            return False  # 不包含指定字符串

        all_joints=cmds.ls(type="joint")
        # 遍历所有骨骼
        for joint in all_joints:
            # 检查当前骨骼是否在给定的骨骼列表中
            if not string_array_contains(self.use_joints, joint):
            # 检查当前骨骼是否在给, joint):
                # 删除不在列表中的骨骼
                if cmds.objExists(joint):
                    print("删除骨骼 " + joint)
                    cmds.delete(joint)
                else:
                    print("骨骼 " + joint + " 不存在")
       
        pm.playbackOptions(animationStartTime=startFrame, e=True)
        pm.playbackOptions(minTime=startFrame, e=True)
        pm.playbackOptions(animationEndTime=endFrame, e=True)
        pm.playbackOptions(maxTime=endFrame, e=True)
        
        # export retargeted anim
        if targetOnly:
            pm.select(self.dstRoot)
            pm.exportSelected(outFn, type='FBX export', force=True)
        else:
            pm.exportAll(outFn, type='FBX export', force=True) 
        return outFn  

 
if __name__ == '__main__':
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('--inputAnimFile', type=str, default=None, help='Input Anim Fbx')
    
    parser.add_argument('--inputFolder', type=str, default=None, help='Input Fbx Directory, batch mode')
    parser.add_argument('--outputFolder', type=str, default=None, help='Output Fbx Directory')
    parser.add_argument('--resume', action='store_true', help='skip exist or redo')
    parser.add_argument('--targetOnly', action='store_true', help='only keep target char')
 
    parser.add_argument('--dstRoot', type=str, default='')
    parser.add_argument('--fps', type=int, default=30)

    args = parser.parse_args()
    
    file_path = "scripts/data/coco_bone.txt"

    # 打开文件
    with open(file_path, "r") as file:
        # 按行读取文件内容
        bones = [ e.strip() for e in file.readlines() ]
    
    # list input files
    if args.inputFolder is None:
        inputFiles = [args.inputAnimFile]
        inputFolder = os.path.dirname(args.inputAnimFile)
    else:
        from pathlib import Path
        if not os.path.isdir(args.inputFolder):
            # regex pattern in arg
            pattern = os.path.basename(args.inputFolder)
            inputFolder = os.path.dirname(args.inputFolder)
        else:
            # directory
            inputFolder = args.inputFolder
            # pattern = r'**/*_anim.fbx'
            pattern = r'**/*.fbx'
            
        inputFiles = sorted([str(f) for f in Path(r'{0}'.format(inputFolder)).glob(pattern)])
        
    
    if args.outputFolder is None:
        outputFolder = inputFolder
    else:
        outputFolder = args.outputFolder
    
    cls = Cls_get_pure_bone(
        bones,
        dstRoot=args.dstRoot,
        fps=args.fps
    )
    
    cls.setup()

    for inputFile in inputFiles:
        relpath  = os.path.relpath(os.path.dirname(inputFile), inputFolder)        
        
        fout  = os.path.join(outputFolder, relpath) + f"/{os.path.basename(inputFile)}"
        
        ddir=fout.rsplit("/",1)[0]
        if not os.path.exists(ddir):
            os.makedirs(ddir)
        
        print('-- work on file: {0}'.format(inputFile))
        if args.resume and os.path.exists(fout):
            print('-- skip {0}'.format(inputFile))
            continue
        
        cls.run(inputFile, fout)
        

调用方式

@echo off
setlocal

rem 

"C:\Program Files\Autodesk\Maya2022\bin\mayapy.exe" /path/get_pure_bone.py ^
    --inputFolder  \path\fbx_org ^
    --dstRoot DeformationSystem ^
    --outputFolder \path\fbx_org_clear ^
    --targetOnly --fps 30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值