Skip to content

Commit

Permalink
Merge pull request #308 from mgear-dev/dev/117_fbx_exporter
Browse files Browse the repository at this point in the history
Feedback check in for #117
  • Loading branch information
miquelcampos committed Oct 17, 2023
2 parents 862e6ab + 027b076 commit 2fa84f1
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 115 deletions.
144 changes: 144 additions & 0 deletions release/scripts/mgear/core/animLayers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import maya.cmds as cmds
import maya.api.OpenMaya as om

def animation_layer_exists(layer_name):
"""Checks ig animation layer exists.
:param str layer_name: Name of the layer that will be checked.
:return: True if the animation layer exists
:rtype: bool
"""
is_anim_layer = False

if not layer_name:
return False
exists = cmds.objExists(layer_name)

if exists:
is_anim_layer = cmds.nodeType(layer_name) == "animLayer"

return exists and is_anim_layer


def base_animation_layer_name():
"""Finds the name of the base animation layer, as this base layer might not
be named the default "BaseAnimation".
:return: name of the base animation layer.
:rtype: str
"""
if animation_layer_exists("BaseAnimation"):
return "BaseAnimation"

# BaseAnimation Layer might have been renamed, perform dg lookup.
nodes = find_anim_layer_base_nodes()

# No nodes found
if len(nodes) == 0:
return ""
return om.MFnDependencyNode(nodes[0]).name()


def find_anim_layer_base_nodes():
"""Finds the animation base layer, as this base layer might not
be named the default "BaseAnimation".
:return: list of Maya Objects.
:rtype: list[om.MObject]
"""
anim_layer_nodes = []

# Create an iterator to iterate through all nodes
it = om.MItDependencyNodes(om.MFn.kAnimLayer)

while not it.isDone():
# Get the animation layer node
m_obj = it.thisNode()

layer_dg = om.MFnDependencyNode(m_obj)
child_plug_obj = layer_dg.findPlug("childrenLayers", False)
child_plug = om.MPlug(child_plug_obj)

# Animation layer has connected children, then it is the
# "BaseAnimation" layer.
if child_plug.numConnectedElements() > 0:
# Append the animation layer node to the list
anim_layer_nodes.append(m_obj)

# Move to the next node
it.next()

return anim_layer_nodes


def all_anim_layers_ordered(include_base_animation=True):
"""Recursive function that returns all available animation layers within current scene.
:return: list of animation layers.
:rtype: list[str]
"""

def _add_node_recursive(layer_node):
all_layers.append(layer_node)
child_layers = (
cmds.animLayer(layer_node, query=True, children=True) or list()
)
for child_layer in child_layers:
_add_node_recursive(child_layer)

all_layers = list()
root_layer = cmds.animLayer(query=True, root=True)
if not root_layer:
return all_layers
_add_node_recursive(root_layer)

if not include_base_animation:
if "BaseAnimation" in all_layers:
all_layers.remove("BaseAnimation")

return all_layers


def get_layer_weights():
"""
Gets all the animation layer weights.
:return: Dictionary with the name of the animation layer, followed by the weight.
:rtype: dict
"""
anim_layer_weights = {}
anim_layers = all_anim_layers_ordered(include_base_animation=False)

for anim_layer in anim_layers:
anim_layer_weights[anim_layer] = cmds.animLayer(anim_layer, query=True, weight=True)

return anim_layer_weights


def set_layer_weights(anim_layer_weights):
"""
Sets the animation layer weights.
:param dict anim_layer_weights: Dictionary containing all the animation layer names, and the weights to be set for each anim layer name.
"""
for name, weight in anim_layer_weights.items():
cmds.animLayer(name, edit=True, weight=weight)


def set_layer_weight(name, value=1.0, toggle_other_off=False, include_base=False):
"""
Set a specific AnimationLayers weight.
:param str name: Name of the animation layer to have its weight modified.
:param float value: weight of the animation layer
:param bool toggle_other_off: Turn all other layers off
:param bool include_base: include the base animation layer when toggling off layers.
"""
anim_layers = all_anim_layers_ordered(include_base_animation=include_base)

if toggle_other_off:
for layer_name in anim_layers:
cmds.animLayer(layer_name, edit=True, weight=0.0)

cmds.animLayer(name, edit=True, weight=value)

39 changes: 32 additions & 7 deletions release/scripts/mgear/shifter/game_tools_fbx/anim_clip_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from mgear.vendor.Qt import QtWidgets, QtCore, QtGui

from mgear.core import pyqt
from mgear.core import pyqt, utils as coreUtils, animLayers
from mgear.shifter.game_tools_fbx import fbx_export_node, utils


Expand Down Expand Up @@ -171,7 +171,7 @@ def set_transparent_button(button):
self._clip_name_lineedit.setStatusTip("Clip Name")
clip_name_layout.addWidget(self._clip_name_lineedit)

self._anim_layer_combo = QtWidgets.QComboBox()
self._anim_layer_combo = AnimationLayerCB()
self._anim_layer_combo.setStatusTip("Animation Layer")
clip_name_layout.addWidget(self._anim_layer_combo)

Expand Down Expand Up @@ -253,7 +253,7 @@ def refresh(self):
with pyqt.block_signals(self._anim_layer_combo):
self._anim_layer_combo.clear()
# TODO: Maybe we should filter display layers that are set with override mode?
anim_layers = utils.all_anim_layers_ordered()
anim_layers = animLayers.all_anim_layers_ordered()
self._anim_layer_combo.addItems(["None"] + anim_layers)
self._anim_layer_combo.setCurrentText(
anim_clip_data.get("anim_layer", "None")
Expand Down Expand Up @@ -293,7 +293,7 @@ def _on_update_anim_clip(self):
anim_clip_data = fbx_export_node.FbxExportNode.ANIM_CLIP_DATA.copy()
anim_clip_data["title"] = self._clip_name_lineedit.text()
anim_clip_data["enabled"] = self._export_checkbox.isChecked()
# anim_clip_data["frame_rate"] = self._frame_rate_combo.currentText()
anim_clip_data["frame_rate"] = coreUtils.get_frame_rate()
anim_clip_data["start_frame"] = int(self._start_frame_box.text())
anim_clip_data["end_frame"] = int(self._end_frame_box.text())
anim_layer = self._anim_layer_combo.currentText()
Expand Down Expand Up @@ -329,12 +329,15 @@ def _on_set_range_button_clicked(self):
self._end_frame_box.setText(max_frame)

def _on_play_button_clicked(self):
start_time = str(int(cmds.playbackOptions(query=True, ast=True)))
end_time = str(int(cmds.playbackOptions(query=True, aet=True)))
start_time = str(int(cmds.playbackOptions(query=True, min=True)))
end_time = str(int(cmds.playbackOptions(query=True, max=True)))
anim_start_time = str(int(cmds.playbackOptions(query=True, ast=True)))
anim_end_time = str(int(cmds.playbackOptions(query=True, aet=True)))
start_frame = self._start_frame_box.text()
end_frame = self._end_frame_box.text()

if not (start_frame == start_time and end_frame == end_time):
if not (start_frame == start_time and end_frame == end_time) or \
not (start_frame == anim_start_time and end_frame == anim_end_time):
cmds.playbackOptions(
animationStartTime=start_frame,
minTime=start_frame,
Expand Down Expand Up @@ -379,3 +382,25 @@ def _on_custom_context_menu_requested(self, pos):
utils.open_mgear_playblast_folder
)
context_menu.exec_(self.mapToGlobal(pos))


class AnimationLayerCB(QtWidgets.QComboBox):
"""
Custom overloaded QComboBox, this will automatically refresh the combobox everytime
it shows the values. Keep the Combobox up to date with the AnimationLayers available.
"""
def __init__(self, parent=None):
super(AnimationLayerCB, self).__init__(parent=parent)

def showPopup(self):
super(AnimationLayerCB, self).showPopup()
currentText = self.currentText()

self.clear()

anim_layers = animLayers.all_anim_layers_ordered()
self.addItems(["None"] + anim_layers)

self.setCurrentText(currentText)

# TODO: Could to a check here to see if the layer still exists, else add a warning.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class FbxExportNode(object):
ANIM_CLIP_DATA = {
"title": "Untitled",
"enabled": True,
"frame_rate": "30 fps",
"frame_rate": "",
"start_frame": int(pm.playbackOptions(min=True, query=True)),
"end_frame": int(pm.playbackOptions(max=True, query=True)),
}
Expand Down
22 changes: 20 additions & 2 deletions release/scripts/mgear/shifter/game_tools_fbx/fbx_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def closeEvent(self, event):
super(FBXExporter, self).closeEvent(event)

def dockCloseEventTriggered(self):
# FIXME: Move the save_data before calling the super close. Check all saving works!
super(FBXExporter, self).dockCloseEventTriggered()
self._save_data_to_export_node()

Expand Down Expand Up @@ -701,8 +702,25 @@ def export_animation_clips(self):

export_config = self._get_current_tool_data()
anim_clip_data = export_node.get_animation_clips(joint_root)
export_config["anim_clips"] = anim_clip_data
utils.export_animation_clip(export_config)

# Stores the selected objects, before performing the export.
# These objects will be selected again, upon completion of
# exporting.
original_selection = cmds.ls(selection=True)

# Exports each clip
for clip_data in anim_clip_data:

if not clip_data["enabled"]:
# skip disabled clips.
continue

result = utils.export_animation_clip(export_config, clip_data)
if not result:
print("\t!!! >>> Failed to export clip: {}".format(clip_data["title"]))

if original_selection:
pm.select(original_selection)

return True

Expand Down
Loading

0 comments on commit 2fa84f1

Please sign in to comment.