Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Human Ik Mapper - Batch bake #374

Merged
merged 4 commits into from
Jan 5, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 183 additions & 12 deletions release/scripts/mgear/animbits/humanIkMapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import mgear
from mgear.core import callbackManager
from mgear.core import widgets as mwgt
from mgear.core.utils import one_undo
from mgear.core.utils import one_undo, viewport_off
from mgear.vendor.Qt import QtCore, QtWidgets, QtGui

from mgear.rigbits.mirror_controls import MirrorController
Expand All @@ -30,6 +30,9 @@ def maya_main_window():


class HumanIKMapper:

# Hard coded values for bone names in Human Ik

HEAD_NAMES = [
"Head",
"Neck",
Expand Down Expand Up @@ -110,11 +113,15 @@ class HumanIKMapper:
"LeafLeftLegRoll5",
]


CHAR_NAME = "MGearIKHuman"

# Dictionary containing all relevant info; [bone] = {target:'', sub_ik:''}

char_config = {}

@classmethod
def set_character(cls):
def initialize_character(cls):
selection = cmds.ls(sl=1)
if not selection:
cmds.error("Must have reference bone selected")
Expand All @@ -123,6 +130,8 @@ def set_character(cls):

pm.mel.HIKCharacterControlsTool()

# creates a set with current HIKCharNodes, creates a new node and subtracts sets members to get new node

tmp = set(pm.ls(type="HIKCharacterNode"))
pm.mel.hikCreateDefinition()
hikChar = list(set(pm.ls(type="HIKCharacterNode")) - tmp)[0]
Expand All @@ -148,6 +157,7 @@ def is_initialized(cls):
def set_list_of_bones_from_selection(
cls, bones_list, ctrls, do_mirror=False
):
# mirror logic
if do_mirror:
if "Left" in bones_list[0]:
bones_list.extend(
Expand Down Expand Up @@ -183,11 +193,14 @@ def set_list_of_bones_from_selection(
ctrl, hikChar, pm.mel.hikGetNodeIdFromName(bone), 0
)

pm.mel.hikUpdateDefinitionUI()
pm.evalDeferred("pm.mel.hikUpdateDefinitionUI()")
return

@classmethod
def get_locked_ctrls(cls, ctrl_list):
"""
Receives a list of controllers and returns the controllers that have a locked attribute
"""
locked_ctrls = []
for ctrl in ctrl_list:
attrs = []
Expand Down Expand Up @@ -216,16 +229,20 @@ def unlock_ctrls_srt(cls, ctrl_list):
@classmethod
def refresh_char_configuration(cls):
# TODO: Check if character exists on scene
# clears current char config
cls.char_config = {}

hik_count = pm.mel.hikGetNodeCount()
hikChar = pm.mel.hikGetCurrentCharacter()

for i in range(hik_count):
bone_name = pm.mel.GetHIKNodeName(i)
bone_target = pm.mel.hikGetSkNode(hikChar, i)

if bone_target:
cls.char_config[bone_name] = {"target": bone_target}
cls.char_config[bone_name]["sub_ik"] = []

if pm.attributeQuery("sub_ik", node=bone_target, exists=True):
connections = cmds.listConnections(
"{}.sub_ik".format(bone_target),
Expand Down Expand Up @@ -369,6 +386,7 @@ def sub_iks_binding(cls, activate=True):
pm.delete(constraint)

@classmethod
@viewport_off
def bake(cls):
current_ik_char = pm.mel.hikGetCurrentCharacter()
attrs_string = " ".join(cls.get_sub_ik_bake_attrs())
Expand Down Expand Up @@ -416,6 +434,53 @@ def get_sub_ik_bake_attrs(cls):
return attrs


@classmethod
@one_undo
def batch_bake(cls, file_list):

curr_character = pm.mel.hikGetCurrentCharacter()
# pm.mel.hikSetCurrentCharacter(hikChar)
existing_ik_humans = set(pm.ls(type="HIKCharacterNode"))

HumanIKMapper.refresh_char_configuration()

# TODO: determine first and last frame, change anim layer name to fbx name,
sub_ik_ctls = [cls.char_config[bone]["sub_ik"] for bone in cls.char_config if
cls.char_config[bone]["sub_ik"]]
sub_ik_constraints = [cmds.parentConstraint(ctl, query=True) for ctl in sub_ik_ctls]
print(sub_ik_constraints)

for file in file_list:
ref_node = cmds.file(file, r=True, namespace=":", type="FBX")
file_ik_human = list(set(pm.ls(type="HIKCharacterNode")) - existing_ik_humans)[0]
existing_ik_humans = set(pm.ls(type="HIKCharacterNode"))

frame_range = "{0}:{1}".format(pm.playbackOptions(q=True, min=True), pm.playbackOptions(q=True, max=True))
# pm.evalDeferred(deferred_cmd)
pm.evalDeferred("from mgear.animbits.humanIkMapper import HumanIKMapper \n"
"HumanIKMapper.deferred_bake(\"{0}\", \"{1}\")".format(file_ik_human, frame_range))

@classmethod
def deferred_bake(cls, ikhuman, frame_range):
# updates src
HumanIKMapper.sub_iks_binding(True)

pm.optionMenuGrp("hikSourceList", edit=True, value=" {0}".format(ikhuman))
pm.mel.hikUpdateCurrentSourceFromUI()

sub_ik_ctls = [cls.char_config[bone]["sub_ik"] for bone in HumanIKMapper.char_config if HumanIKMapper.char_config[bone]["sub_ik"]]
sub_ik_constraints = [cmds.parentConstraint(ctl, query=True) for ctl in sub_ik_ctls]
# print("sub ik ctls = {1}, \n subik constraints = {1}".format(sub_ik_ctls, sub_ik_constraints))

pm.mel.hikBakeCharacterPre("{0}".format(pm.mel.hikGetCurrentCharacter()))
pm.select(HumanIKMapper.get_sub_ik_bake_attrs(), add=1)
pm.bakeResults(pm.ls(sl=1), bakeOnOverrideLayer=True, simulation=True, t=frame_range, sampleBy=1)
pm.mel.hikBakeCharacterPost("{0}".format(pm.mel.hikGetCurrentCharacter()))
if sub_ik_constraints:
pm.delete(sub_ik_constraints)
pm.rename("BakeResults", ikhuman)


class HumanIKMapperUI(MayaQWidgetDockableMixin, QtWidgets.QDialog):
def __init__(self, parent=None):
super(HumanIKMapperUI, self).__init__(parent)
Expand All @@ -440,13 +505,18 @@ def __init__(self, parent=None):
@QtCore.Slot()
def adjustSize(self, *args, **kwargs):
# Needed to mark the adjustSize method as slot
self.setup_tab.resize(self.setup_tab.minimumSizeHint())
self.setup_tab.adjustSize()
self.resize(self.minimumSizeHint())
super(HumanIKMapperUI, self).adjustSize()

def create_actions(self):
self.import_action = QtWidgets.QAction("Import")
self.export_action = QtWidgets.QAction("Export")

self.bake_action = QtWidgets.QAction("Bake")
self.export_batch_bake_action = QtWidgets.QAction("Export Batch Bake Config")
self.import_batch_bake_action = QtWidgets.QAction("Import Batch Bake Config")


def create_widgets(self):

Expand All @@ -456,6 +526,9 @@ def create_widgets(self):
self.file_menu.addAction(self.export_action)
self.bake_menu = self.menu_bar.addMenu("Bake")
self.bake_menu.addAction(self.bake_action)
self.bake_menu.addSeparator()
self.bake_menu.addAction(self.export_batch_bake_action)
self.bake_menu.addAction(self.import_batch_bake_action)

self.initialize_btn = QtWidgets.QPushButton("Initialize")

Expand Down Expand Up @@ -525,19 +598,45 @@ def create_widgets(self):
QtWidgets.QHeaderView.Stretch
)

# bake tab
self.bake_paths_lw = QtWidgets.QListWidget()
self.bake_paths_lw.setSelectionMode(QtWidgets.QListWidget.ExtendedSelection)
add_remove_font = QtGui.QFont()
add_remove_font.setPointSize(25)
self.add_bake_path_btn = QtWidgets.QPushButton("+")
self.add_bake_path_btn.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
self.add_bake_path_btn.setFont(add_remove_font)
self.remove_bake_path_btn = QtWidgets.QPushButton("-")
self.remove_bake_path_btn.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum)
self.remove_bake_path_btn.setFont(add_remove_font)
self.batch_bake_btn = QtWidgets.QPushButton("Batch Bake")

def create_layout(self):
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(2, 2, 2, 2)
main_layout.setMenuBar(self.menu_bar)

self.tabs = QtWidgets.QTabWidget()
self.tabs.minimumSizeHint(

)
main_layout.addWidget(self.tabs)
self.setup_tab = QtWidgets.QWidget()
self.setup_tab.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
self.setup_layout = QtWidgets.QVBoxLayout(self.setup_tab)
self.tabs.addTab(self.setup_tab, "Setup")


self.configure_collapsible = mwgt.CollapsibleWidget(
"Configuration", expanded=True
)
# self.configure_collapsible.header_wgt.setFixedHeight(18)
self.configure_collapsible.setSizePolicy(
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum
)

main_layout.addWidget(self.configure_collapsible)
self.setup_layout.addWidget(self.configure_collapsible)

configure_gb = QtWidgets.QGroupBox("Configure")
self.configure_collapsible.addWidget(configure_gb)
configure_layout = QtWidgets.QVBoxLayout()
Expand Down Expand Up @@ -577,13 +676,12 @@ def create_layout(self):
mirror_layout.addWidget(self.mirror_checkbox)
configure_layout.addLayout(mirror_layout)

# main_layout.addStretch()

self.instructions_collapsible = mwgt.CollapsibleWidget(
"Instructions", expanded=False
)
main_layout.addWidget(self.instructions_collapsible)
# self.instructions_collapsible.header_wgt.setFixedHeight(18)
self.setup_layout.addWidget(self.instructions_collapsible)
self.instructions_collapsible.header_wgt.setFixedHeight(18)
self.instructions_collapsible.setSizePolicy(
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum
)
Expand All @@ -592,8 +690,8 @@ def create_layout(self):
self.mapping_collapsible = mwgt.CollapsibleWidget(
"Mapping", expanded=False
)
main_layout.addWidget(self.mapping_collapsible)
# self.mapping_collapsible.header_wgt.setFixedHeight(18)
self.setup_layout.addWidget(self.mapping_collapsible)
self.mapping_collapsible.header_wgt.setFixedHeight(18)
self.mapping_collapsible.setSizePolicy(
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding
)
Expand All @@ -604,15 +702,37 @@ def create_layout(self):
self.mapping_collapsible.addLayout(mapping_buttons)
self.mapping_collapsible.addWidget(self.mapping_table)

# batch bake

self.batch_bake_tab = QtWidgets.QWidget()
self.batch_bake_tab.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)
batch_bake_vlayout = QtWidgets.QVBoxLayout(self.batch_bake_tab)
paths_hlayout = QtWidgets.QHBoxLayout()
path_buttons_vlayout = QtWidgets.QVBoxLayout()

path_buttons_vlayout.addWidget(self.add_bake_path_btn)
path_buttons_vlayout.addWidget(self.remove_bake_path_btn)

paths_hlayout.addWidget(self.bake_paths_lw)
paths_hlayout.addLayout(path_buttons_vlayout)

batch_bake_vlayout.addLayout(paths_hlayout)
batch_bake_vlayout.addWidget(self.batch_bake_btn)

self.tabs.addTab(self.batch_bake_tab, "Batch Bake")

def create_connections(self):

self.export_action.triggered.connect(
HumanIKMapper.export_char_configuration
)
self.import_action.triggered.connect(self.import_config)
self.bake_action.triggered.connect(HumanIKMapper.bake)
self.export_batch_bake_action.triggered.connect(self.export_batch_bake_config)
self.import_batch_bake_action.triggered.connect(self.import_batch_bake_config)


self.initialize_btn.clicked.connect(HumanIKMapper.set_character)
self.initialize_btn.clicked.connect(HumanIKMapper.initialize_character)

self.head_btn.clicked.connect(
self.display_list_mb_cb(HumanIKMapper.HEAD_NAMES)
Expand Down Expand Up @@ -699,7 +819,14 @@ def create_connections(self):
self._mapping_menu
)

# batch bake path

self.add_bake_path_btn.clicked.connect(self.add_batch_bake_paths)
self.remove_bake_path_btn.clicked.connect(self.remove_batch_bake_paths)
self.batch_bake_btn.clicked.connect(self.batch_bake)

def deferred_resize(self):
# print("calling deferred resize")
QtCore.QMetaObject.invokeMethod(
self, "adjustSize", QtCore.Qt.QueuedConnection
)
Expand Down Expand Up @@ -741,6 +868,49 @@ def clear_sub_ik(self, bone_target):
HumanIKMapper.clear_sub_ik(bone_target)
self.update_mapping()

def add_batch_bake_paths(self):
fbx_filter = "*.fbx"
paths = cmds.fileDialog2(fileFilter=fbx_filter, fileMode=4)

self.bake_paths_lw.addItems(paths)

def remove_batch_bake_paths(self):
selected_items = self.bake_paths_lw.selectedItems()

for item in selected_items:
self.bake_paths_lw.takeItem(self.bake_paths_lw.row(item))

def batch_bake(self):
files = []
for i in range(self.bake_paths_lw.count()):
files.append(self.bake_paths_lw.item(i).text())
print(files)
HumanIKMapper.batch_bake(files)

def export_batch_bake_config(self):
files = []
for i in range(self.bake_paths_lw.count()):
files.append(self.bake_paths_lw.item(i).text())

file_path = pm.fileDialog2(fileMode=0, fileFilter="*.bkcn")
if not file_path:
return
file_path = file_path[0]
data_string = json.dumps(files, indent=4)
with open(file_path, "w") as fp:
fp.write(data_string)
print(file_path)

def import_batch_bake_config(self):
file_path = pm.fileDialog2(fileMode=1, fileFilter="*.bkcn")
if not file_path:
return
file_path = file_path[0]
with open(file_path, "r") as fp:
data_string = json.load(fp)
self.bake_paths_lw.clear()
self.bake_paths_lw.addItems(data_string)

def _group_in_hlayout(self, *args):
h_layout = QtWidgets.QHBoxLayout()
for i in args:
Expand Down Expand Up @@ -918,7 +1088,8 @@ def create_connections(self):


def show(*args):
mgear.core.pyqt.showDialog(HumanIKMapperUI, dockable=True)
return mgear.core.pyqt.showDialog(HumanIKMapperUI, dockable=True)



if __name__ == "__main__":
Expand Down
Loading