其实这些代码大部分是从 chatgpt 中生成的。
骨骼命名
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