MetaWhiz/scripts/utils/maya_utils.py
2025-04-17 13:00:39 +08:00

551 lines
19 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Tool - Maya Utilities
Provides utilities for working with Maya
"""
import os
import sys
import json
import maya.cmds as cmds
import maya.mel as mel
# Import configuration
import config
class MayaUtils:
"""Utilities for working with Maya"""
def __init__(self):
"""Initialize Maya utilities"""
# Ensure required plugins are loaded
self._load_required_plugins()
def _load_required_plugins(self):
"""Load required Maya plugins"""
required_plugins = ["fbxmaya", "matrixNodes"]
for plugin in required_plugins:
if not cmds.pluginInfo(plugin, query=True, loaded=True):
try:
cmds.loadPlugin(plugin)
print(f"Loaded plugin: {plugin}")
except Exception as e:
cmds.warning(f"Error loading plugin: {plugin}, Error: {str(e)}")
def export_fbx(self, file_path):
"""
Export the current scene or selection as an FBX file
Args:
file_path (str): Path to save the FBX file
Returns:
bool: True if successful, False otherwise
"""
try:
# Ensure FBX plugin is loaded
if not cmds.pluginInfo("fbxmaya", query=True, loaded=True):
cmds.loadPlugin("fbxmaya")
# Get selection
selection = cmds.ls(selection=True)
# Set FBX export options
mel.eval("FBXResetExport")
# Set export options
mel.eval("FBXExportFileVersion -v FBX201900")
mel.eval("FBXExportUpAxis y")
mel.eval("FBXExportShapes -v true")
mel.eval("FBXExportScaleFactor 1.0")
mel.eval("FBXExportInAscii -v false")
mel.eval("FBXExportConstraints -v false")
mel.eval("FBXExportLights -v false")
mel.eval("FBXExportSkins -v true")
mel.eval("FBXExportSmoothingGroups -v true")
mel.eval("FBXExportSmoothMesh -v true")
mel.eval("FBXExportEmbeddedTextures -v false")
mel.eval("FBXExportCameras -v false")
mel.eval("FBXExportBakeComplexAnimation -v true")
mel.eval("FBXExportBakeComplexStart -v 0")
mel.eval("FBXExportBakeComplexEnd -v 100")
mel.eval("FBXExportBakeComplexStep -v 1")
mel.eval("FBXExportBakeResampleAnimation -v true")
mel.eval("FBXExportAnimationOnly -v false")
mel.eval("FBXExportQuaternion -v euler")
mel.eval("FBXExportAxisConversionMethod \"none\"")
mel.eval("FBXExportApplyConstantKeyReducer -v false")
mel.eval("FBXExportUseSceneName -v false")
mel.eval("FBXExportGenerateLog -v false")
# Export FBX
if selection:
# Export selected objects
result = mel.eval(f'FBXExport -f "{file_path}" -s')
else:
# Export entire scene
result = mel.eval(f'FBXExport -f "{file_path}"')
if result:
print(f"Successfully exported FBX file: {file_path}")
return True
else:
cmds.warning(f"Failed to export FBX file: {file_path}")
return False
except Exception as e:
cmds.warning(f"Error exporting FBX file: {str(e)}")
return False
def import_fbx(self, file_path):
"""
Import an FBX file into the current scene
Args:
file_path (str): Path to the FBX file
Returns:
bool: True if successful, False otherwise
"""
try:
# Ensure FBX plugin is loaded
if not cmds.pluginInfo("fbxmaya", query=True, loaded=True):
cmds.loadPlugin("fbxmaya")
# Set FBX import options
mel.eval("FBXResetImport")
# Set import options
mel.eval("FBXImportMode -v add")
mel.eval("FBXImportMergeAnimationLayers -v false")
mel.eval("FBXImportProtectDrivenKeys -v true")
mel.eval("FBXImportConvertDeformingNullsToJoint -v false")
mel.eval("FBXImportMergeBackNullPivots -v false")
mel.eval("FBXImportSetLockedAttribute -v true")
mel.eval("FBXImportConstraints -v false")
mel.eval("FBXImportCameras -v false")
mel.eval("FBXImportLights -v false")
mel.eval("FBXImportSkins -v true")
mel.eval("FBXImportShapes -v true")
mel.eval("FBXImportUnlockNormals -v false")
mel.eval("FBXImportForcedFileAxis \"y\"")
# Import FBX
result = mel.eval(f'FBXImport -f "{file_path}"')
if result:
print(f"Successfully imported FBX file: {file_path}")
return True
else:
cmds.warning(f"Failed to import FBX file: {file_path}")
return False
except Exception as e:
cmds.warning(f"Error importing FBX file: {str(e)}")
return False
def save_scene(self, file_path):
"""
Save the current scene to a Maya file
Args:
file_path (str): Path to save the Maya file
Returns:
bool: True if successful, False otherwise
"""
try:
# Determine file type
if file_path.lower().endswith(".ma"):
file_type = "mayaAscii"
else:
file_type = "mayaBinary"
# Save scene
cmds.file(rename=file_path)
cmds.file(save=True, type=file_type)
print(f"Successfully saved scene: {file_path}")
return True
except Exception as e:
cmds.warning(f"Error saving scene: {str(e)}")
return False
def load_scene(self, file_path):
"""
Load a Maya scene file
Args:
file_path (str): Path to the Maya file
Returns:
bool: True if successful, False otherwise
"""
try:
# Check if current scene has changes
if cmds.file(query=True, modified=True):
# Ask user to save changes
result = cmds.confirmDialog(
title="Save Changes",
message="Current scene has unsaved changes. Save?",
button=["Save", "Don't Save", "Cancel"],
defaultButton="Save",
cancelButton="Cancel",
dismissString="Cancel"
)
if result == "Save":
cmds.file(save=True)
elif result == "Don't Save":
return False
# Load scene
cmds.file(file_path, open=True, force=True)
print(f"Successfully loaded scene: {file_path}")
return True
except Exception as e:
cmds.warning(f"Error loading scene: {str(e)}")
return False
def create_reference(self, file_path, namespace=None):
"""
Create a reference to a Maya file
Args:
file_path (str): Path to the Maya file
namespace (str, optional): Namespace for the reference
Returns:
bool: True if successful, False otherwise
"""
try:
# Create reference
if namespace:
cmds.file(file_path, reference=True, namespace=namespace)
else:
cmds.file(file_path, reference=True)
print(f"Successfully created reference: {file_path}")
return True
except Exception as e:
cmds.warning(f"Error creating reference: {str(e)}")
return False
def remove_reference(self, reference_node):
"""
Remove a reference
Args:
reference_node (str): Name of the reference node
Returns:
bool: True if successful, False otherwise
"""
try:
# Remove reference
cmds.file(referenceNode=reference_node, removeReference=True)
print(f"Successfully removed reference: {reference_node}")
return True
except Exception as e:
cmds.warning(f"Error removing reference: {str(e)}")
return False
def get_scene_info(self):
"""
Get information about the current scene
Returns:
dict: Dictionary containing scene information
"""
try:
info = {}
# Get scene path
scene_path = cmds.file(query=True, sceneName=True)
info["scene_path"] = scene_path
# Get scene name
scene_name = os.path.basename(scene_path) if scene_path else "untitled"
info["scene_name"] = scene_name
# Get scene statistics
info["mesh_count"] = len(cmds.ls(type="mesh"))
info["joint_count"] = len(cmds.ls(type="joint"))
info["blendshape_count"] = len(cmds.ls(type="blendShape"))
info["skincluster_count"] = len(cmds.ls(type="skinCluster"))
# Get selection
info["selection"] = cmds.ls(selection=True)
return info
except Exception as e:
cmds.warning(f"Error getting scene information: {str(e)}")
return {}
def create_locator(self, name, position=(0, 0, 0)):
"""
Create a locator
Args:
name (str): Name for the locator
position (tuple, optional): Position for the locator
Returns:
str: Name of the created locator
"""
try:
# Create locator
locator = cmds.spaceLocator(name=name)[0]
# Set position
cmds.setAttr(f"{locator}.translate", *position)
return locator
except Exception as e:
cmds.warning(f"Error creating locator: {str(e)}")
return None
def create_group(self, name, children=None):
"""
Create a group
Args:
name (str): Name for the group
children (list, optional): List of objects to parent to the group
Returns:
str: Name of the created group
"""
try:
# Create group
group = cmds.group(empty=True, name=name)
# Parent children
if children:
cmds.parent(children, group)
return group
except Exception as e:
cmds.warning(f"Error creating group: {str(e)}")
return None
def create_control(self, name, shape="circle", radius=1.0, color=None):
"""
Create a control curve
Args:
name (str): Name for the control
shape (str, optional): Shape of the control
radius (float, optional): Radius of the control
color (int, optional): Color index for the control
Returns:
str: Name of the created control
"""
try:
# Create control based on shape
control = None
if shape == "circle":
control = cmds.circle(name=name, radius=radius, normal=(0, 1, 0), constructionHistory=False)[0]
elif shape == "square":
points = [
(-radius, 0, -radius),
(radius, 0, -radius),
(radius, 0, radius),
(-radius, 0, radius),
(-radius, 0, -radius)
]
control = cmds.curve(name=name, degree=1, point=points)
elif shape == "cube":
points = [
(-radius, -radius, -radius),
(radius, -radius, -radius),
(radius, -radius, radius),
(-radius, -radius, radius),
(-radius, -radius, -radius),
(-radius, radius, -radius),
(radius, radius, -radius),
(radius, -radius, -radius),
(radius, radius, -radius),
(radius, radius, radius),
(radius, -radius, radius),
(radius, radius, radius),
(-radius, radius, radius),
(-radius, -radius, radius),
(-radius, radius, radius),
(-radius, radius, -radius)
]
control = cmds.curve(name=name, degree=1, point=points)
elif shape == "sphere":
# Create circles in different planes
circle1 = cmds.circle(radius=radius, normal=(1, 0, 0), constructionHistory=False)[0]
circle2 = cmds.circle(radius=radius, normal=(0, 1, 0), constructionHistory=False)[0]
circle3 = cmds.circle(radius=radius, normal=(0, 0, 1), constructionHistory=False)[0]
# Combine circles
control = cmds.group(empty=True, name=name)
cmds.parent(
cmds.listRelatives(circle1, shapes=True),
cmds.listRelatives(circle2, shapes=True),
cmds.listRelatives(circle3, shapes=True),
control,
shape=True,
relative=True
)
# Delete original circles
cmds.delete(circle1, circle2, circle3)
else:
# Default to circle
control = cmds.circle(name=name, radius=radius, normal=(0, 1, 0), constructionHistory=False)[0]
# Set color
if color is not None:
shapes = cmds.listRelatives(control, shapes=True)
for shape in shapes:
cmds.setAttr(f"{shape}.overrideEnabled", 1)
cmds.setAttr(f"{shape}.overrideColor", color)
return control
except Exception as e:
cmds.warning(f"Error creating control: {str(e)}")
return None
def create_joint(self, name, position=(0, 0, 0), parent=None):
"""
Create a joint
Args:
name (str): Name for the joint
position (tuple, optional): Position for the joint
parent (str, optional): Parent for the joint
Returns:
str: Name of the created joint
"""
try:
# Select parent if provided
if parent:
cmds.select(parent)
else:
cmds.select(clear=True)
# Create joint
joint = cmds.joint(name=name, position=position)
return joint
except Exception as e:
cmds.warning(f"Error creating joint: {str(e)}")
return None
def set_keyframe(self, obj, attribute, value, time):
"""
Set a keyframe
Args:
obj (str): Name of the object
attribute (str): Name of the attribute
value (float): Value to set
time (float): Time to set the keyframe
Returns:
bool: True if successful, False otherwise
"""
try:
# Set attribute value
cmds.setAttr(f"{obj}.{attribute}", value)
# Set keyframe
cmds.setKeyframe(obj, attribute=attribute, time=time)
return True
except Exception as e:
cmds.warning(f"Error setting keyframe: {str(e)}")
return False
def bake_animation(self, objects, start_frame, end_frame, step=1):
"""
Bake animation
Args:
objects (list): List of objects to bake
start_frame (float): Start frame
end_frame (float): End frame
step (float, optional): Frame step
Returns:
bool: True if successful, False otherwise
"""
try:
# Bake animation
cmds.bakeResults(
objects,
simulation=True,
time=(start_frame, end_frame),
sampleBy=step,
disableImplicitControl=True,
preserveOutsideKeys=True,
sparseAnimCurveBake=False,
removeBakedAttributeFromLayer=False,
bakeOnOverrideLayer=False,
minimizeRotation=True,
controlPoints=False
)
return True
except Exception as e:
cmds.warning(f"Error baking animation: {str(e)}")
return False
def clean_scene(self):
"""
Clean the scene by removing unused nodes
Returns:
bool: True if successful, False otherwise
"""
try:
# Delete unused nodes
mel.eval("MLdeleteUnused")
# Delete turtle nodes if they exist
if cmds.pluginInfo("Turtle", query=True, loaded=True):
turtle_nodes = cmds.ls("*turtle*")
if turtle_nodes:
cmds.delete(turtle_nodes)
# Delete unknown nodes
unknown_nodes = cmds.ls(type="unknown")
if unknown_nodes:
cmds.delete(unknown_nodes)
# Delete unused animation curves
unused_anim_curves = []
for curve in cmds.ls(type=["animCurveTA", "animCurveTL", "animCurveTU"]):
if not cmds.connectionInfo(f"{curve}.output", destinationFromSource=True):
unused_anim_curves.append(curve)
if unused_anim_curves:
cmds.delete(unused_anim_curves)
# Delete empty groups
empty_groups = []
for transform in cmds.ls(type="transform"):
if not cmds.listRelatives(transform, children=True) and not cmds.listRelatives(transform, shapes=True):
empty_groups.append(transform)
if empty_groups:
cmds.delete(empty_groups)
# Optimize scene
mel.eval("OptimizeScene")
return True
except Exception as e:
cmds.warning(f"Error cleaning scene: {str(e)}")
return False