Files
Nexus/2025/scripts/animation_tools/animation_retargeting_tool.py
2025-11-30 14:49:16 +08:00

722 lines
30 KiB
Python

'''
Name: animation_retargeting_tool
Description: Transfer animation data between rigs or transfer raw mocap from a skeleton to a custom rig.
Author: Joar Engberg 2021
Installation:
Add animation_retargeting_tool.py to your Maya scripts folder (Username\Documents\maya\scripts).
To start the tool within Maya, run these this lines of code from the Maya script editor or add them to a shelf button:
import animation_retargeting_tool
animation_retargeting_tool.start()
'''
from collections import OrderedDict
import os
import sys
import webbrowser
import maya.mel
import maya.cmds as cmds
from functools import partial
import maya.OpenMayaUI as omui
maya_version = int(cmds.about(version=True))
if maya_version < 2025:
from shiboken2 import wrapInstance
from PySide2 import QtCore, QtGui, QtWidgets
else:
from shiboken6 import wrapInstance
from PySide6 import QtCore, QtGui, QtWidgets
def maya_main_window():
# Return the Maya main window as QMainWindow
main_window = omui.MQtUtil.mainWindow()
if sys.version_info.major >= 3:
return wrapInstance(int(main_window), QtWidgets.QWidget)
else:
return wrapInstance(long(main_window), QtWidgets.QWidget) # type: ignore
class RetargetingTool(QtWidgets.QDialog):
'''
Retargeting tool class
'''
WINDOW_TITLE = "Animation Retargeting Tool"
def __init__(self):
super(RetargetingTool, self).__init__(maya_main_window())
self.script_job_ids = []
self.connection_ui_widgets = []
self.color_counter = 0
self.maya_color_index = OrderedDict([(13, "red"), (18, "cyan"), (14, "lime"), (17, "yellow")])
self.cached_connect_nodes = []
self.setWindowTitle(self.WINDOW_TITLE)
self.setWindowFlags(self.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint)
self.resize(400, 300)
self.create_ui_widgets()
self.create_ui_layout()
self.create_ui_connections()
self.create_script_jobs()
if cmds.about(macOS=True):
self.setWindowFlags(QtCore.Qt.Tool)
def create_ui_widgets(self):
self.refresh_button = QtWidgets.QPushButton(QtGui.QIcon(":refresh.png"), "")
self.simple_conn_button = QtWidgets.QPushButton("Create Connection")
self.ik_conn_button = QtWidgets.QPushButton("Create IK Connection")
self.bake_button = QtWidgets.QPushButton("Bake Animation")
self.bake_button.setStyleSheet("background-color: lightgreen; color: black")
self.batch_bake_button = QtWidgets.QPushButton("Batch Bake And Export ...")
self.help_button = QtWidgets.QPushButton("?")
self.help_button.setFixedWidth(25)
self.rot_checkbox = QtWidgets.QCheckBox("Rotation")
self.pos_checkbox = QtWidgets.QCheckBox("Translation")
self.mo_checkbox = QtWidgets.QCheckBox("Maintain Offset")
self.snap_checkbox = QtWidgets.QCheckBox("Align To Position")
def create_ui_layout(self):
horizontal_layout_1 = QtWidgets.QHBoxLayout()
horizontal_layout_1.addWidget(self.pos_checkbox)
horizontal_layout_1.addWidget(self.rot_checkbox)
horizontal_layout_1.addWidget(self.snap_checkbox)
horizontal_layout_1.addStretch()
horizontal_layout_1.addWidget(self.help_button)
horizontal_layout_2 = QtWidgets.QHBoxLayout()
horizontal_layout_2.addWidget(self.simple_conn_button)
horizontal_layout_2.addWidget(self.ik_conn_button)
horizontal_layout_3 = QtWidgets.QHBoxLayout()
horizontal_layout_3.addWidget(self.batch_bake_button)
horizontal_layout_3.addWidget(self.bake_button)
connection_list_widget = QtWidgets.QWidget()
self.connection_layout = QtWidgets.QVBoxLayout(connection_list_widget)
self.connection_layout.setContentsMargins(2, 2, 2, 2)
self.connection_layout.setSpacing(3)
self.connection_layout.setAlignment(QtCore.Qt.AlignTop)
list_scroll_area = QtWidgets.QScrollArea()
list_scroll_area.setWidgetResizable(True)
list_scroll_area.setWidget(connection_list_widget)
separator_line = QtWidgets.QFrame(parent=None)
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(10, 10, 10, 10)
main_layout.addWidget(list_scroll_area)
main_layout.addLayout(horizontal_layout_1)
main_layout.addLayout(horizontal_layout_2)
main_layout.addWidget(separator_line)
main_layout.addLayout(horizontal_layout_3)
def create_ui_connections(self):
self.simple_conn_button.clicked.connect(self.create_connection_node)
self.ik_conn_button.clicked.connect(self.create_ik_connection_node)
self.refresh_button.clicked.connect(self.refresh_ui_list)
self.bake_button.clicked.connect(self.bake_animation_confirm)
self.batch_bake_button.clicked.connect(self.open_batch_window)
self.help_button.clicked.connect(self.help_dialog)
self.rot_checkbox.setChecked(True)
self.pos_checkbox.setChecked(True)
self.snap_checkbox.setChecked(True)
def create_script_jobs(self):
self.script_job_ids.append(cmds.scriptJob(event=["SelectionChanged", partial(self.refresh_ui_list)]))
self.script_job_ids.append(cmds.scriptJob(event=["NameChanged", partial(self.refresh_ui_list)]))
def kill_script_jobs(self):
for id in self.script_job_ids:
if cmds.scriptJob(exists=id):
cmds.scriptJob(kill=id)
else:
pass
def refresh_ui_list(self):
self.clear_list()
connect_nodes_in_scene = RetargetingTool.get_connect_nodes()
self.cached_connect_nodes = connect_nodes_in_scene
for node in connect_nodes_in_scene:
connection_ui_item = ListItemWidget(parent_instance=self, connection_node=node)
self.connection_layout.addWidget(connection_ui_item)
self.connection_ui_widgets.append(connection_ui_item)
def clear_list(self):
self.connection_ui_widgets = []
while self.connection_layout.count() > 0:
connection_ui_item = self.connection_layout.takeAt(0)
if connection_ui_item.widget():
connection_ui_item.widget().deleteLater()
def showEvent(self, event):
self.refresh_ui_list()
def closeEvent(self, event):
self.kill_script_jobs()
self.clear_list()
def create_connection_node(self):
try:
selected_joint = cmds.ls(selection=True)[0]
selected_ctrl = cmds.ls(selection=True)[1]
except:
return cmds.warning("No selections!")
if self.snap_checkbox.isChecked() == True:
cmds.matchTransform(selected_ctrl, selected_joint, pos=True)
else:
pass
if self.rot_checkbox.isChecked() == True and self.pos_checkbox.isChecked() == False:
suffix = "_ROT"
elif self.pos_checkbox.isChecked() == True and self.rot_checkbox.isChecked() == False:
suffix = "_TRAN"
else:
suffix = "_TRAN_ROT"
locator = self.create_ctrl_sphere(selected_joint+suffix)
# Add message attr
cmds.addAttr(locator, longName="ConnectNode", attributeType="message")
cmds.addAttr(selected_ctrl, longName="ConnectedCtrl", attributeType="message")
cmds.connectAttr(locator+".ConnectNode",selected_ctrl+".ConnectedCtrl")
cmds.parent(locator, selected_joint)
cmds.xform(locator, rotation=(0, 0, 0))
cmds.xform(locator, translation=(0, 0, 0))
# Select the type of constraint based on the ui checkboxes
if self.rot_checkbox.isChecked() == True and self.pos_checkbox.isChecked() == True:
cmds.parentConstraint(locator, selected_ctrl, maintainOffset=True)
elif self.rot_checkbox.isChecked() == True and self.pos_checkbox.isChecked() == False:
cmds.orientConstraint(locator, selected_ctrl, maintainOffset=True)
elif self.pos_checkbox.isChecked() == True and self.rot_checkbox.isChecked() == False:
cmds.pointConstraint(locator, selected_ctrl, maintainOffset=True)
else:
cmds.warning("Select translation and/or rotation!")
cmds.delete(locator)
cmds.deleteAttr(selected_ctrl, at="ConnectedCtrl")
self.refresh_ui_list()
def create_ik_connection_node(self):
try:
selected_joint = cmds.ls(selection=True)[0]
selected_ctrl = cmds.ls(selection=True)[1]
except:
return cmds.warning("No selections!")
self.rot_checkbox.setChecked(True)
self.pos_checkbox.setChecked(True)
if self.snap_checkbox.isChecked() == True:
cmds.matchTransform(selected_ctrl, selected_joint, pos=True)
else:
pass
tran_locator = self.create_ctrl_sphere(selected_joint+"_TRAN")
cmds.parent(tran_locator, selected_joint)
cmds.xform(tran_locator, rotation=(0, 0, 0))
cmds.xform(tran_locator, translation=(0, 0, 0))
rot_locator = self.create_ctrl_locator(selected_joint+"_ROT")
# Add message attributes and connect them
cmds.addAttr(tran_locator, longName="ConnectNode", attributeType="message")
cmds.addAttr(rot_locator, longName="ConnectNode", attributeType="message")
cmds.addAttr(selected_ctrl, longName="ConnectedCtrl", attributeType="message")
cmds.connectAttr(tran_locator+".ConnectNode",selected_ctrl+".ConnectedCtrl")
cmds.parent(rot_locator, tran_locator)
cmds.xform(rot_locator, rotation=(0, 0, 0))
cmds.xform(rot_locator, translation=(0, 0, 0))
joint_parent = cmds.listRelatives(selected_joint, parent=True)[0]
cmds.parent(tran_locator, joint_parent)
cmds.makeIdentity(tran_locator, apply=True, translate=True)
cmds.orientConstraint(selected_joint, tran_locator, maintainOffset=False)
cmds.parentConstraint(rot_locator, selected_ctrl, maintainOffset=True)
# Lock and hide attributes
cmds.setAttr(rot_locator+".tx", lock=True, keyable=False)
cmds.setAttr(rot_locator+".ty", lock=True, keyable=False)
cmds.setAttr(rot_locator+".tz", lock=True, keyable=False)
cmds.setAttr(tran_locator+".rx", lock=True, keyable=False)
cmds.setAttr(tran_locator+".ry", lock=True, keyable=False)
cmds.setAttr(tran_locator+".rz", lock=True, keyable=False)
self.refresh_ui_list()
def scale_ctrl_shape(self, controller, size):
cmds.select(self.get_cvs(controller), replace=True)
cmds.scale(size, size, size)
cmds.select(clear=True)
def get_cvs(self, object):
children = cmds.listRelatives(object, type="shape", children=True)
ctrl_vertices = []
for c in children:
spans = int(cmds.getAttr(c+".spans")) + 1
vertices = "{shape}.cv[0:{count}]".format(shape=c, count=spans)
ctrl_vertices.append(vertices)
return ctrl_vertices
def create_ctrl_locator(self, ctrl_shape_name):
curves = []
curves.append(cmds.curve(degree=1, p=[(0, 0, 1), (0, 0, -1)], k=[0,1]))
curves.append(cmds.curve(degree=1, p=[(1, 0, 0), (-1, 0, 0)], k=[0,1]))
curves.append(cmds.curve(degree=1, p=[(0, 1, 0), (0, -1, 0)], k=[0,1]))
locator = self.combine_shapes(curves, ctrl_shape_name)
cmds.setAttr(locator+".overrideEnabled", 1)
cmds.setAttr(locator+".overrideColor", list(self.maya_color_index.keys())[self.color_counter])
return locator
def create_ctrl_sphere(self, ctrl_shape_name):
circles = []
for n in range(0, 5):
circles.append(cmds.circle(normal=(0,0,0), center=(0,0,0))[0])
cmds.rotate(0, 45, 0, circles[0])
cmds.rotate(0, -45, 0, circles[1])
cmds.rotate(0, -90, 0, circles[2])
cmds.rotate(90, 0, 0, circles[3])
sphere = self.combine_shapes(circles, ctrl_shape_name)
cmds.setAttr(sphere+".overrideEnabled", 1)
cmds.setAttr(sphere+".overrideColor", list(self.maya_color_index.keys())[self.color_counter])
self.scale_ctrl_shape(sphere, 0.5)
return sphere
def combine_shapes(self, shapes, ctrl_shape_name):
shape_nodes = cmds.listRelatives(shapes, shapes=True)
output_node = cmds.group(empty=True, name=ctrl_shape_name)
cmds.makeIdentity(shapes, apply=True, translate=True, rotate=True, scale=True)
cmds.parent(shape_nodes, output_node, shape=True, relative=True)
cmds.delete(shape_nodes, constructionHistory=True)
cmds.delete(shapes)
return output_node
def bake_animation_confirm(self):
confirm = cmds.confirmDialog(title="Confirm", message="Baking the animation will delete all the connection nodes. Do you wish to proceed?", button=["Yes","No"], defaultButton="Yes", cancelButton="No")
if confirm == "Yes":
progress_dialog = QtWidgets.QProgressDialog("Baking animation", None, 0, -1, self)
progress_dialog.setWindowFlags(progress_dialog.windowFlags() ^ QtCore.Qt.WindowCloseButtonHint)
progress_dialog.setWindowFlags(progress_dialog.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint)
progress_dialog.setWindowTitle("Progress...")
progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
progress_dialog.show()
QtCore.QCoreApplication.processEvents()
# Bake animation
self.bake_animation()
progress_dialog.close()
if confirm == "No":
pass
self.refresh_ui_list()
def help_dialog(self):
confirm = cmds.confirmDialog(
title="How to use",
message="To create a connection simply select the driver and then the driven and click 'Create connection'. For IK hands and IK feet controllers you can use 'Create IK Connection' for more complex retargeting. \n \nAs an example: if you want to transfer animation from a skeleton to a rig, first select the animated joint and then select the controller before you create a connection.",
button=["How to use the retargeting tool (Youtube)", "How to use the batch exporter (Youtube)", "Cancel"],
defaultButton="Cancel",
cancelButton="Cancel",
dismissString="Cancel")
if confirm == "How to use the retargeting tool (Youtube)":
webbrowser.open_new("https://youtu.be/x2-agPVfinc")
elif confirm == "How to use the batch exporter (Youtube)":
webbrowser.open_new("https://youtu.be/KOURUtN36ko")
def open_batch_window(self):
try:
self.settings_window.close()
self.settings_window.deleteLater()
except:
pass
self.settings_window = BatchExport()
self.settings_window.show()
@classmethod
def bake_animation(cls):
if len(cls.get_connected_ctrls()) == 0:
cmds.warning("No connections found in scene!")
if len(cls.get_connected_ctrls()) != 0:
time_min = cmds.playbackOptions(query=True, min=True)
time_max = cmds.playbackOptions(query=True, max=True)
# Bake the animation
cmds.refresh(suspend=True)
cmds.bakeResults(cls.get_connected_ctrls(), t=(time_min, time_max), sb=1, at=["rx","ry","rz","tx","ty","tz"], hi="none")
cmds.refresh(suspend=False)
# Delete the connect nodes
for node in cls.get_connect_nodes():
try:
cmds.delete(node)
except:
pass
# Remove the message attribute from the controllers
for ctrl in cls.get_connected_ctrls():
try:
cmds.deleteAttr(ctrl, attribute="ConnectedCtrl")
except:
pass
@classmethod
def get_connect_nodes(cls):
connect_nodes_in_scene = []
for i in cmds.ls():
if cmds.attributeQuery("ConnectNode", node=i, exists=True) == True:
connect_nodes_in_scene.append(i)
else:
pass
return connect_nodes_in_scene
@classmethod
def get_connected_ctrls(cls):
connected_ctrls_in_scene = []
for i in cmds.ls():
if cmds.attributeQuery("ConnectedCtrl", node=i, exists=True) == True:
connected_ctrls_in_scene.append(i)
else:
pass
return connected_ctrls_in_scene
class ListItemWidget(QtWidgets.QWidget):
'''
UI list item class.
When a new List Item is created it gets added to the connection_list_widget in the RetargetingTool class.
'''
def __init__(self, connection_node, parent_instance):
super(ListItemWidget, self).__init__()
self.connection_node = connection_node
self.main = parent_instance
self.setFixedHeight(26)
self.create_ui_widgets()
self.create_ui_layout()
self.create_ui_connections()
# If there is already connection nodes in the scene update the color counter
try:
current_override = cmds.getAttr(self.connection_node+".overrideColor")
self.main.color_counter = self.main.maya_color_index.keys().index(current_override)
except:
pass
def create_ui_widgets(self):
self.color_button = QtWidgets.QPushButton()
self.color_button.setFixedSize(20, 20)
self.color_button.setStyleSheet("background-color:" + self.get_current_color())
self.sel_button = QtWidgets.QPushButton()
self.sel_button.setStyleSheet("background-color: #707070")
self.sel_button.setText("Select")
self.sel_button.setFixedWidth(80)
self.del_button = QtWidgets.QPushButton()
self.del_button.setStyleSheet("background-color: #707070")
self.del_button.setText("Delete")
self.del_button.setFixedWidth(80)
self.transform_name_label = QtWidgets.QLabel(self.connection_node)
self.transform_name_label.setAlignment(QtCore.Qt.AlignCenter)
self.transform_name_label.setStyleSheet("color: darkgray")
for selected in cmds.ls(selection=True):
if selected == self.connection_node:
self.transform_name_label.setStyleSheet("color: white")
def create_ui_layout(self):
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(5, 5, 20, 0)
main_layout.addWidget(self.color_button)
main_layout.addWidget(self.transform_name_label)
main_layout.addWidget(self.sel_button)
main_layout.addWidget(self.del_button)
def create_ui_connections(self):
self.sel_button.clicked.connect(self.select_connection_node)
self.del_button.clicked.connect(self.delete_connection_node)
self.color_button.clicked.connect(self.set_color)
def select_connection_node(self):
cmds.select(self.connection_node)
for widget in self.main.connection_ui_widgets:
widget.transform_name_label.setStyleSheet("color: darkgray")
self.transform_name_label.setStyleSheet("color: white")
def delete_connection_node(self):
try:
for attr in cmds.listConnections(self.connection_node, destination=True):
if cmds.attributeQuery("ConnectedCtrl", node=attr, exists=True):
cmds.deleteAttr(attr, at="ConnectedCtrl")
except:
pass
cmds.delete(self.connection_node)
self.main.refresh_ui_list()
def set_color(self):
# Set the color on the connection node and button
connection_nodes = self.main.cached_connect_nodes
color = list(self.main.maya_color_index.keys())
if self.main.color_counter < 3:
self.main.color_counter += 1
else:
self.main.color_counter = 0
for node in connection_nodes:
cmds.setAttr(node+".overrideEnabled", 1)
cmds.setAttr(node+".overrideColor", color[self.main.color_counter])
for widget in self.main.connection_ui_widgets:
widget.color_button.setStyleSheet("background-color:"+self.get_current_color())
def get_current_color(self):
current_color_index = cmds.getAttr(self.connection_node+".overrideColor")
color_name = self.main.maya_color_index.get(current_color_index, "grey")
return color_name
class BatchExport(QtWidgets.QDialog):
'''
Batch exporter class
'''
WINDOW_TITLE = "Batch Exporter"
def __init__(self):
super(BatchExport, self).__init__(maya_main_window())
self.setWindowTitle(self.WINDOW_TITLE)
self.setWindowFlags(self.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint)
self.resize(400, 250)
self.animation_clip_paths = []
self.output_folder = ""
if cmds.about(macOS=True):
self.setWindowFlags(QtCore.Qt.Tool)
self.create_ui()
self.create_connections()
def create_ui(self):
self.file_list_widget = QtWidgets.QListWidget()
self.remove_selected_button = QtWidgets.QPushButton("Remove Selected")
self.remove_selected_button.setFixedHeight(24)
self.load_anim_button = QtWidgets.QPushButton("Load Animations")
self.load_anim_button.setFixedHeight(24)
self.export_button = QtWidgets.QPushButton("Batch Export Animations")
self.export_button.setStyleSheet("background-color: lightgreen; color: black")
self.connection_file_line = QtWidgets.QLineEdit()
self.connection_file_line.setToolTip("Enter the file path to the connection rig file. A file which contains a rig with connections.")
self.connection_filepath_button = QtWidgets.QPushButton()
self.connection_filepath_button.setIcon(QtGui.QIcon(":fileOpen.png"))
self.connection_filepath_button.setFixedSize(24, 24)
self.export_selected_label = QtWidgets.QLabel("Export Selected (Optional):")
self.export_selected_line = QtWidgets.QLineEdit()
self.export_selected_line.setToolTip("Enter the name(s) of the nodes that should be exported. Leave blank to export all.")
self.export_selected_button = QtWidgets.QPushButton()
self.export_selected_button.setIcon(QtGui.QIcon(":addClip.png"))
self.export_selected_button.setFixedSize(24, 24)
self.output_filepath_button = QtWidgets.QPushButton()
self.output_filepath_button.setIcon(QtGui.QIcon(":fileOpen.png"))
self.file_type_combo = QtWidgets.QComboBox()
self.file_type_combo.addItems([".fbx", ".ma"])
horizontal_layout_1 = QtWidgets.QHBoxLayout()
horizontal_layout_1.addWidget(QtWidgets.QLabel("Connection Rig File:"))
horizontal_layout_1.addWidget(self.connection_file_line)
horizontal_layout_1.addWidget(self.connection_filepath_button)
horizontal_layout_2 = QtWidgets.QHBoxLayout()
horizontal_layout_2.addWidget(self.load_anim_button)
horizontal_layout_2.addWidget(self.remove_selected_button)
horizontal_layout_3 = QtWidgets.QHBoxLayout()
horizontal_layout_3.addWidget(QtWidgets.QLabel("Output File Type:"))
horizontal_layout_3.addWidget(self.file_type_combo)
horizontal_layout_3.addWidget(self.export_button)
horizontal_layout_4 = QtWidgets.QHBoxLayout()
horizontal_layout_4.addWidget(self.export_selected_label)
horizontal_layout_4.addWidget(self.export_selected_line)
horizontal_layout_4.addWidget(self.export_selected_button)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(self.file_list_widget)
main_layout.addLayout(horizontal_layout_2)
main_layout.addLayout(horizontal_layout_1)
main_layout.addLayout(horizontal_layout_4)
main_layout.addLayout(horizontal_layout_3)
def create_connections(self):
self.connection_filepath_button.clicked.connect(self.connection_filepath_dialog)
self.load_anim_button.clicked.connect(self.animation_filepath_dialog)
self.export_button.clicked.connect(self.batch_action)
self.export_selected_button.clicked.connect(self.add_selected_action)
self.remove_selected_button.clicked.connect(self.remove_selected_item)
def connection_filepath_dialog(self):
file_path = QtWidgets.QFileDialog.getOpenFileName(self, "Select Connection Rig File", "", "Maya ACSII (*.ma);;All files (*.*)")
if file_path[0]:
self.connection_file_line.setText(file_path[0])
def output_filepath_dialog(self):
folder_path = QtWidgets.QFileDialog.getExistingDirectory(self, "Select export folder path", "")
if folder_path:
self.output_folder = folder_path
return True
else:
return False
def animation_filepath_dialog(self):
file_paths = QtWidgets.QFileDialog.getOpenFileNames(self, "Select Animation Clips", "", "FBX (*.fbx);;All files (*.*)")
file_path_list = file_paths[0]
if file_path_list[0]:
for i in file_path_list:
self.file_list_widget.addItem(i)
for i in range(0, self.file_list_widget.count()):
self.file_list_widget.item(i).setTextColor(QtGui.QColor("white"))
def add_selected_action(self):
selection = cmds.ls(selection=True)
if len(selection) > 1:
text_string = "["
for i in selection:
text_string += '"{}", '.format(i)
text_string = text_string[:-2]
text_string += "]"
elif selection[0]:
text_string = "{}".format(selection[0])
else:
pass
self.export_selected_line.setText(text_string)
def remove_selected_item(self):
try:
selected_items = self.file_list_widget.selectedItems()
for item in selected_items:
self.file_list_widget.takeItem(self.file_list_widget.row(item))
except:
pass
def batch_action(self):
if self.connection_file_line.text() == "":
cmds.warning("Connection file textfield is empty. Add a connection rig file to be able to export. This file should contain the rig and connections to a skeleton.")
elif self.file_list_widget.count() == 0:
cmds.warning("Animation clip list is empty. Add animation clips to the list to be able to export!")
else:
confirm_dialog = self.output_filepath_dialog()
if confirm_dialog == True:
self.bake_export()
else:
pass
def bake_export(self):
self.animation_clip_paths = []
for i in range(self.file_list_widget.count()):
self.animation_clip_paths.append(self.file_list_widget.item(i).text())
number_of_operations = len(self.animation_clip_paths) * 3
current_operation = 0
progress_dialog = QtWidgets.QProgressDialog("Preparing", "Cancel", 0, number_of_operations, self)
progress_dialog.setWindowFlags(progress_dialog.windowFlags() ^ QtCore.Qt.WindowCloseButtonHint)
progress_dialog.setWindowFlags(progress_dialog.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint)
progress_dialog.setValue(0)
progress_dialog.setWindowTitle("Progress...")
progress_dialog.setWindowModality(QtCore.Qt.WindowModal)
progress_dialog.show()
QtCore.QCoreApplication.processEvents()
export_result = []
for i, path in enumerate(self.animation_clip_paths):
# Import connection file and animation clip
progress_dialog.setLabelText("Baking and exporting {} of {}".format(i + 1, len(self.animation_clip_paths)))
self.file_list_widget.item(i).setTextColor(QtGui.QColor("yellow"))
cmds.file(new=True, force=True)
cmds.file(self.connection_file_line.text(), open=True)
maya.mel.eval('FBXImportMode -v "exmerge";')
maya.mel.eval('FBXImport -file "{}";'.format(path))
current_operation += 1
progress_dialog.setValue(current_operation)
# Bake animation
RetargetingTool.bake_animation()
current_operation += 1
progress_dialog.setValue(current_operation)
# Export animation
output_path = self.output_folder + "/" + os.path.splitext(os.path.basename(path))[0]
if self.file_type_combo.currentText() == ".fbx":
output_path += ".fbx"
cmds.file(rename=output_path)
if self.export_selected_line.text() != "":
cmds.select(self.export_selected_line.text(), replace=True)
maya.mel.eval('FBXExport -f "{}" -s'.format(output_path))
else:
maya.mel.eval('FBXExport -f "{}"'.format(output_path))
elif self.file_type_combo.currentText() == ".ma":
output_path += ".ma"
cmds.file(rename=output_path)
if self.export_selected_line.text() != "":
cmds.select(self.export_selected_line.text(), replace=True)
cmds.file(exportSelected=True, type="mayaAscii")
else:
cmds.file(exportAll=True, type="mayaAscii")
current_operation += 1
progress_dialog.setValue(current_operation)
if os.path.exists(output_path):
self.file_list_widget.item(i).setTextColor(QtGui.QColor("lime"))
export_result.append("Sucessfully exported: "+output_path)
else:
self.file_list_widget.item(i).setTextColor(QtGui.QColor("red"))
export_result.append("Failed exporting: "+output_path)
print("------")
for i in export_result:
print(i)
print("------")
progress_dialog.setValue(number_of_operations)
progress_dialog.close()
def start():
global retarget_tool_ui
try:
retarget_tool_ui.close()
retarget_tool_ui.deleteLater()
except:
pass
retarget_tool_ui = RetargetingTool()
retarget_tool_ui.show()
if __name__ == "__main__":
start()