其实这些代码大部分是从 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