MetaBox/Scripts/Animation/epic_pose_wrangler/v1/poseWrangler.py
2025-01-14 03:05:57 +08:00

994 lines
41 KiB
Python

# Copyright Epic Games, Inc. All Rights Reserved.
import traceback
import math
import json
from PySide2 import QtWidgets
import maya.cmds as cmds
import maya.api.OpenMaya as api
class UE4PoseDriver(object):
'''
TODO:
- add a transform to an existing pose driver
- I was told that the Maya implementation only works with one driver,
not multiple transforms, this reflects that
- properly serialize the UI driving mode when opening the UI based on connections into the drivers
- validate the node network on load, knowing that a driven has driven, etc.. (low prio)
'''
def __init__(self, existing_interpolator=None):
self.name = None
self.solver = None
self.decompose_mx_list = []
self.copied_trs_map = {}
if existing_interpolator:
if cmds.nodeType(existing_interpolator) == 'UE4RBFSolverNode':
self.name = existing_interpolator
self.solver = existing_interpolator
else:
print('Node', existing_interpolator, 'is not of type UE4RBFSolverNode.')
return
## General utils pulled from utils lib
#################################################################################
def attrExists(self, attr):
if '.' in attr:
node, att = attr.split('.')
return cmds.attributeQuery(att, node=node, ex=1)
else:
cmds.warning('attrExists: No attr passed in: ' + attr)
return False
def msgConnect(self, attribFrom, attribTo, debug=0):
objFrom, attFrom = attribFrom.split('.')
objTo, attTo = attribTo.split('.')
objTo, attTo = attribTo.split('.')
if debug: print('msgConnect>>> Locals:', locals())
if not self.attrExists(attribFrom):
cmds.addAttr(objFrom, longName=attFrom, attributeType='message')
if not self.attrExists(attribTo):
cmds.addAttr(objTo, longName=attTo, attributeType='message')
# check that both atts, if existing are msg atts
for a in (attribTo, attribFrom):
if cmds.getAttr(a, type=1) != 'message':
cmds.warning('msgConnect: Attr, ' + a + ' is not a message attribute. CONNECTION ABORTED.')
return False
try:
return cmds.connectAttr(attribFrom, attribTo, f=True)
except Exception as e:
print(traceback.format_exc())
return False
def break_all_connections(self, node):
destination_conns = cmds.listConnections(node, plugs=True, connections=True, source=False) or []
for i in range(0, len(destination_conns), 2):
cmds.disconnectAttr(destination_conns[i], destination_conns[i + 1])
source_conns = cmds.listConnections(node, plugs=True, connections=True, destination=False) or []
for i in range(0, len(source_conns), 2):
# we have to flip these because the output is always node centric and not connection centric
cmds.disconnectAttr(source_conns[i + 1], source_conns[i])
def get_dag_dict(self, dagpose_node):
dag_dict = {}
if cmds.nodeType(dagpose_node) == 'dagPose':
for idx in range(1, cmds.getAttr(dagpose_node + '.members', size=True)):
conns = cmds.listConnections(dagpose_node + '.members[' + str(idx) + ']')
if conns:
# because it's a message there could be more than one incoming, but Maya doesn't do that
# set the value for the name key to the int socket
# also this check for length is because of the garbage menber plugs for parents
if len(conns) == 1:
dag_dict[conns[0]] = idx
return dag_dict
else:
cmds.error('Requires dagPose node.')
return False
def get_local_matrix(self, node, invertJointOrient=False):
local_mx = cmds.xform(node, m=1, q=1)
# ignore code below for now - that code will take joint orient into account
return local_mx
if invertJointOrient:
# if it's a joint, lets remove the joint orient from the local matrix
if cmds.nodeType(node) == "joint":
selList = api.MGlobal.getSelectionListByName(node)
dagPath = selList.getDagPath(0)
parentMatrix = dagPath.exclusiveMatrix()
worldMatrix = dagPath.inclusiveMatrix()
jointOrient = cmds.getAttr(node + ".jointOrient")[0]
x = math.radians(jointOrient[0])
y = math.radians(jointOrient[1])
z = math.radians(jointOrient[2])
joEuler = api.MEulerRotation(x, y, z)
joMatrix = joEuler.asMatrix()
localMatrix = worldMatrix * parentMatrix.inverse()
orientLocalMatrix = localMatrix * joMatrix.inverse()
# combine the orient local matrix and the translation which shouldn't be changed based on the orientation
for i in range(12):
localMatrix[i] = orientLocalMatrix[i]
# set the local matrix to this
local_mx = localMatrix
return local_mx
def create_matrix_node(self, node, mx_node_name, invertJointOrient=False):
local_mx = self.get_local_matrix(node, invertJointOrient=invertJointOrient)
world_mx = cmds.xform(node, m=1, ws=1, q=1)
mx_node = cmds.createNode('network', name=mx_node_name)
cmds.addAttr(mx_node, at='matrix', longName='outputLocalMatrix')
cmds.addAttr(mx_node, at='matrix', longName='outputWorldMatrix')
cmds.addAttr(mx_node, dt='string', longName='matrix_node_ver')
self.msgConnect(node + '.mx_pose', mx_node + '.driven_transform')
cmds.setAttr(mx_node + '.outputLocalMatrix', local_mx, type='matrix')
cmds.setAttr(mx_node + '.outputWorldMatrix', world_mx, type='matrix')
return mx_node
## Methods used by class
#################################################################################
def init_pose_driver_system(self, existing_solver_node):
# check that it's a solver node
if cmds.nodeType(existing_solver_node) == 'UE4RBFSolverNode':
self.solver = existing_solver_node
def create_pose_driver_system(self, name, input_xform, driven_xforms):
solver = self.create_UE4RBFSolverNode(name, input_xform, driven_xforms)
def create_UE4RBFSolverNode(self, name, input_xform, driven_xforms):
try:
cmds.undoInfo(openChunk=True, undoName='create pose interpolator')
# create the interpolator (will be your API)
self.solver = cmds.createNode('UE4RBFSolverNode', name=name + '_UE4RBFSolver')
self.name = self.solver
# Set the default values for the solver node
cmds.setAttr("{}.radius".format(self.solver), 50)
# connect the world matrix from the driving xform to the interp
cmds.connectAttr(input_xform + '.matrix', self.solver + '.inputs[0]')
# connect the default pose into the first target (Nate said to do this)
default_mx_node = self.create_matrix_node(input_xform, input_xform + '_base_pose', invertJointOrient=True)
cmds.connectAttr(default_mx_node + '.outputLocalMatrix', self.solver + '.targets[0].targetValues[0]')
# connect the driver to the interp for the property to query
self.msgConnect(input_xform + '.ue4_rbf_solver', self.solver + '.driver')
# store the driven nodes for the property query
for node in driven_xforms:
self.msgConnect(self.solver + '.driven_transforms', node + '.ue4_rbf_solver')
for node in driven_xforms:
blender_node_name = node + '_ue4PoseBlender'
if not cmds.objExists(blender_node_name):
print("node: " + blender_node_name + " didn't exist!")
# make matrix node
mx_node = self.create_matrix_node(node, node + '_base_pose', invertJointOrient=True)
print("mx node")
print(mx_node)
blender_node = cmds.createNode('UE4PoseBlenderNode', name=blender_node_name)
# connect the base pose, it doesn't make much sense copying the same mx into
# all three slots on this node (base_pose, in_matrix, pose) -but I was told to by the author
cmds.connectAttr(mx_node + '.outputLocalMatrix', blender_node + '.basePose')
cmds.connectAttr(mx_node + '.outputLocalMatrix', blender_node + '.inMatrix')
self.msgConnect(node + '.blenderNode', blender_node + '.drivenTransform')
cmds.connectAttr(mx_node + '.outputLocalMatrix', blender_node + '.poses[0]')
# connect ourtput, remember that this bone could already be driven
next_pose_weight_idx = cmds.getAttr(blender_node + '.weights', size=True)
cmds.connectAttr(
self.solver + '.outputs[0]',
blender_node + '.weights[' + str(next_pose_weight_idx) + ']'
)
# wire up the pose representation
self.msgConnect(self.solver + '.stored_pose_base_pose', mx_node + '.ue4_rbf_solver')
else:
print("node: " + blender_node_name + " did exist!")
blender_node = node + '_ue4PoseBlender'
# TODO: check that this node is valid and has a base pose connected
self.msgConnect(self.solver + '.pose_blenders', blender_node + '.ue4_rbf_solver')
self.msgConnect(self.solver + '.stored_pose_base_pose', default_mx_node + '.ue4_rbf_solver')
except Exception as e:
print(traceback.format_exc())
return False
finally:
cmds.undoInfo(closeChunk=True)
def add_pose(self, pose_name, debug_mode=0):
'''
Because the user has selected what xforms are driven on creation of the solver, this function does
not need to know anything but what we will call the pose, everything else is derived from metadata.
'''
try:
cmds.undoInfo(openChunk=True, undoName='create pose')
self.is_driving(False)
if pose_name:
if debug_mode:
# I was storing transforms as locators for testing
pose_loc = cmds.spaceLocator(name=pose_name)[0]
cmds.delete(cmds.parentConstraint(self.driving_transform, pose_loc))
# wire up the world mx of the driver in it's current position to the solver
mx_node_driver = self.create_matrix_node(
self.driving_transform,
self.driving_transform + '_' + pose_name + '_pose',
invertJointOrient=True
)
next_target_index = cmds.getAttr(self.solver + '.targets', size=True)
cmds.connectAttr(
mx_node_driver + '.outputLocalMatrix',
self.solver + '.targets[' + str(next_target_index) + '].targetValues[0]'
)
# get the next output weight
next_output = cmds.getAttr(self.solver + '.outputs', size=True)
# hook that up to existing
for node in self.driven_transforms:
# make matrix node
mx_node = self.create_matrix_node(node, pose_name + '_' + node + '_mx_pose', invertJointOrient=True)
blender_node = cmds.listConnections(node + '.blenderNode')[0]
next_pose_idx = cmds.getAttr(blender_node + '.poses', size=True)
cmds.connectAttr(
mx_node + '.outputLocalMatrix',
blender_node + '.poses[' + str(next_pose_idx) + ']'
)
# connect to weight
next_pose_weight_idx = cmds.getAttr(blender_node + '.weights', size=True)
cmds.connectAttr(
self.solver + '.outputs[' + str(next_output) + ']',
blender_node + '.weights[' + str(next_pose_weight_idx) + ']'
)
# wire up the pose representation
self.msgConnect(self.solver + '.stored_pose_' + pose_name, mx_node + '.ue4_rbf_solver')
driving_xform_mx = \
cmds.listConnections(self.solver + '.targets[' + str(next_target_index) + '].targetValues[0]')[
0]
self.msgConnect(self.solver + '.stored_pose_' + pose_name, driving_xform_mx + '.ue4_rbf_solver')
self.is_driving(True)
except Exception as e:
print(traceback.format_exc())
finally:
cmds.undoInfo(closeChunk=True)
def update_pose(self, pose_name):
try:
cmds.undoInfo(openChunk=True, undoName='enable pose ' + pose_name)
if pose_name in self.pose_dict:
for mx_node in self.pose_dict[pose_name]:
driven_xform = cmds.listConnections(mx_node + '.driven_transform')[0]
# local_mx = cmds.xform(driven_xform, m=1, q=1)
local_mx = self.get_local_matrix(driven_xform, invertJointOrient=True)
cmds.setAttr(mx_node + '.outputLocalMatrix', local_mx, type='matrix')
except Exception as e:
print(traceback.format_exc())
finally:
cmds.undoInfo(closeChunk=True)
def delete_pose(self, pose_name):
try:
cmds.undoInfo(openChunk=True, undoName='enable pose ' + pose_name)
if pose_name in self.pose_dict:
for mx_node in self.pose_dict[pose_name]:
print("mx node")
print(mx_node)
# this will also remove the pose array instance which keeps the pose computing
conn = cmds.listConnections(mx_node + ".outputLocalMatrix", d=1, s=0, p=1)
print(conn)
if conn:
index = int(conn[0].split("[")[-1].split("]")[0])
# this means the conn is a poseBlender
if ".poses[" in conn[0]:
cmds.removeMultiInstance(conn[0], b=1)
poseBlender = conn[0].split(".")[0]
cmds.removeMultiInstance(poseBlender + ".weights[" + str(index) + "]", b=1)
print("pose blender:")
print(poseBlender)
print("solver")
print(self.solver)
cmds.removeMultiInstance(self.solver + ".outputs[" + str(index) + "]", b=1)
cmds.removeMultiInstance(self.solver + ".targets[" + str(index) + "]", b=1)
print("index:")
print(index)
# driven = cmds.listConnections(mx_node + ".driven_transform")[0]
# blender_node_name = driven + '_ue4PoseBlender'
# cmds.removeMultiInstance(blender_node_name + ".weights[" + str(index) + "]", b=1)
cmds.delete(mx_node)
cmds.deleteAttr(self.name + '.stored_pose_' + pose_name)
except Exception as e:
print(traceback.format_exc())
finally:
cmds.undoInfo(closeChunk=True)
def assume_pose(self, pose_name):
try:
cmds.undoInfo(openChunk=True, undoName='enable pose ' + pose_name)
if pose_name in self.pose_dict:
for mx in self.pose_dict[pose_name]:
driven_xform = cmds.listConnections(mx + '.driven_transform')[0]
mx = cmds.getAttr(mx + '.outputLocalMatrix')
"""
matrix = api.MMatrix(mx)
matrixFn = api.MTransformationMatrix(matrix)
euler = matrixFn.rotation()
rotX = math.degrees(euler[0])
rotY = math.degrees(euler[1])
rotZ = math.degrees(euler[2])
translation = matrixFn.translation(api.MSpace.kWorld)
scale = matrixFn.scale(api.MSpace.kWorld)
try:
cmds.setAttr(driven_xform + ".translate", translation[0], translation[1], translation[2])
except:
pass
try:
cmds.setAttr(driven_xform + ".rotate", rotX, rotY, rotZ)
except:
pass
try:
cmds.setAttr(driven_xform + ".scale", scale[0], scale[1], scale[2])
except:
pass
"""
# replace this to take joint orient into account
cmds.xform(driven_xform, m=mx)
except Exception as e:
print(traceback.format_exc())
finally:
cmds.undoInfo(closeChunk=True)
def bake_poses_to_timeline(self, start_frame=0, suppress=False, anim_layer=None):
"""
Bakes the poses to the timeline and sets the time range to the given animation.
:param start_frame: start frame of he baked animation
:param suppress: A bool to suppress the warning dialogue. Meant to be used outside of the UI
:anim_layer: if given the animations will be baked on that layer.If the layer doesnt exist we will create one.
"""
bake_bool = True
pose_list = []
if not anim_layer:
anim_layer = "BaseAnimation"
if not cmds.animLayer(anim_layer, query=True, exists=True):
cmds.animLayer(anim_layer)
try:
cmds.autoKeyframe(e=1, st=0)
if not suppress:
ret = QtWidgets.QMessageBox.warning(
None, "WARNING: DESTRUCTIVE FUNCTION",
"This will bake the poses to the timeline, change your time range, and delete inputs on driving and driven transforms.\n" + \
"Do you want this to happen?",
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
QtWidgets.QMessageBox.Cancel
)
if ret == QtWidgets.QMessageBox.StandardButton.Ok:
bake_bool = True
cmds.undoInfo(openChunk=True, undoName='Bake poses to timeline')
else:
bake_bool = False
if bake_bool:
# begin printing UE4 pose list
# print('<- UE4 Pose List ---------------------->')
i = start_frame
for p in self.pose_dict:
driven_xforms = []
for mx_node in self.pose_dict[p]:
driven_xforms.extend(cmds.listConnections(mx_node + '.driven_transform'))
# let's key it on the previous and next frames before we pose it
cmds.select(driven_xforms)
cmds.animLayer(anim_layer, addSelectedObjects=True, e=True)
cmds.setKeyframe(driven_xforms, t=[(i - 1), (i + 1)], animLayer=anim_layer)
# assume the pose
self.assume_pose(p)
cmds.setKeyframe(driven_xforms, t=[i], animLayer=anim_layer)
# print(out to UE4 pose list)
print(p)
pose_list.append(p)
# increment to next keyframe
i += 1
# print('<- UE4 Pose List ---------------------->')
# set the range to the number of keyframes
cmds.playbackOptions(minTime=0, maxTime=i, animationStartTime=0, animationEndTime=i - 1)
cmds.dgdirty(a=1)
return pose_list
cmds.dgdirty(a=1)
except Exception as e:
print(traceback.format_exc())
finally:
cmds.undoInfo(closeChunk=True)
def is_driving(self, state):
try:
cmds.undoInfo(openChunk=True, undoName='Change driving state')
# state is True or False
if state:
for node in self.pose_blenders:
# let's get the driven transform
driven_transform = cmds.listConnections(node + '.drivenTransform')[0]
# we have to decompose the out matrix in order to connect it to the driven transform
mx_decompose = cmds.createNode('decomposeMatrix', name=node + '_mx_decompose')
# we hookup the output mx of the pose driver to the input of the decompose
cmds.connectAttr(node + '.outMatrix', mx_decompose + '.inputMatrix')
# and the TRS out of the decompose back into the original driven xform
cmds.connectAttr(mx_decompose + '.outputTranslate', driven_transform + '.translate', f=True)
cmds.connectAttr(mx_decompose + '.outputRotate', driven_transform + '.rotate', f=True)
cmds.connectAttr(mx_decompose + '.outputScale', driven_transform + '.scale', f=True)
else:
for node in self.pose_blenders:
print("debugging:")
print(node)
mx_decompose_node = cmds.listConnections(node + '.outMatrix')
print(mx_decompose_node)
while (mx_decompose_node):
self.break_all_connections(mx_decompose_node[0])
cmds.delete(mx_decompose_node[0])
mx_decompose_node = cmds.listConnections(node + '.outMatrix')
except Exception as e:
print(traceback.format_exc())
finally:
cmds.undoInfo(closeChunk=True)
def delete(self):
"""function that deletes the node"""
for mx_nodes in self.pose_dict.values():
for mx_node in mx_nodes:
cmds.delete(mx_node)
cmds.delete(self.pose_blenders)
cmds.delete(self.solver)
def add_driven(self, driven):
try:
cmds.undoInfo(openChunk=True, undoName='add driven')
self.msgConnect(self.solver + '.driven_transforms', driven + '.ue4_rbf_solver')
blender_node_name = driven + '_ue4PoseBlender'
mx_node = self.create_matrix_node(driven, driven + '_base_pose', invertJointOrient=True)
blender_node = cmds.createNode('UE4PoseBlenderNode', name=blender_node_name)
cmds.connectAttr(mx_node + '.outputLocalMatrix', blender_node + '.basePose')
cmds.connectAttr(mx_node + '.outputLocalMatrix', blender_node + '.inMatrix')
self.msgConnect(driven + '.blenderNode', blender_node + '.drivenTransform')
cmds.connectAttr(mx_node + '.outputLocalMatrix', blender_node + '.poses[0]')
cmds.connectAttr(self.solver + '.outputs[0]', blender_node + '.weights[0]')
self.msgConnect(self.solver + '.stored_pose_base_pose', mx_node + '.ue4_rbf_solver')
self.msgConnect(self.solver + '.pose_blenders', blender_node + '.ue4_rbf_solver')
output_size = cmds.getAttr(self.solver + '.outputs', size=True)
for i in range(output_size):
cmds.connectAttr(
self.solver + ".outputs[" + str(i) + "]", blender_node + ".weights[" + str(i) + "]",
f=1
)
for pose_name in self.pose_dict.keys():
other_mx_node = self.pose_dict[pose_name][0]
conns = cmds.listConnections(other_mx_node + ".outputLocalMatrix", d=1, s=0, p=1)
index = None
for conn in conns:
if "poses" in conn and "PoseBlender" in conn:
index = int(conn.split("[")[-1].split("]")[0])
mx_node = self.create_matrix_node(driven, pose_name + '_' + driven + '_mx_pose', invertJointOrient=True)
cmds.connectAttr(mx_node + '.outputLocalMatrix', blender_node + '.poses[' + str(index) + ']', f=1)
self.msgConnect(self.solver + '.stored_pose_' + pose_name, mx_node + '.ue4_rbf_solver')
except Exception as e:
print(traceback.format_exc())
return False
finally:
cmds.undoInfo(closeChunk=True)
def copy_driven_trs(self):
"""copies the driven TRS so we can paste it across other poses"""
for driven in self.driven_transforms:
translate = cmds.getAttr(driven + ".translate")[0]
rotate = cmds.getAttr(driven + ".rotate")[0]
scale = cmds.getAttr(driven + ".scale")[0]
self.copied_trs_map[driven] = {"translate": translate, "rotate": rotate, "scale": scale}
print("Copied driven TRS successfully.")
def paste_driven_trs(self, mult=1.0):
"""pastes the driven trs and multiplies it"""
for driven, data in self.copied_trs_map.items():
translate = data['translate']
rotate = data['rotate']
scale = list(data['scale'])
scale[0] = ((scale[0] - 1.0) * mult) + 1.0
scale[1] = ((scale[1] - 1.0) * mult) + 1.0
scale[2] = ((scale[2] - 1.0) * mult) + 1.0
try:
cmds.setAttr(driven + ".translate", translate[0] * mult, translate[1] * mult, translate[2] * mult)
except:
traceback.print_exc()
try:
cmds.setAttr(driven + ".rotate", rotate[0] * mult, rotate[1] * mult, rotate[2] * mult)
except:
traceback.print_exc()
try:
cmds.setAttr(driven + ".scale", scale[0], scale[1], scale[2])
except:
traceback.print_exc()
print("Pasted driven TRS successfully.")
def zero_base_pose(self):
"""zero's out the base pose"""
self.assume_pose("base_pose")
self.is_driving(False)
for d in self.driven_transforms:
cmds.setAttr(d + ".translate", 0.0, 0.0, 0.0)
cmds.setAttr(d + ".rotate", 0.0, 0.0, 0.0)
cmds.setAttr(d + ".scale", 1.0, 1.0, 1.0)
self.update_pose("base_pose")
self.is_driving(True)
## Class properties
#################################################################################
@property
def is_enabled(self):
found_conns = False
for node in self.pose_blenders:
mx_decompose_node = cmds.listConnections(node + '.outMatrix')
if mx_decompose_node:
found_conns = True
return found_conns
@property
def driven_transforms(self):
"""gets the driven transforms"""
if cmds.attributeQuery("driven_transforms", n=self.solver, ex=1):
xforms = cmds.listConnections(self.solver + '.driven_transforms')
if xforms:
return xforms
return []
@driven_transforms.setter
def driven_transforms(self, xforms):
pass
@property
def driving_transform(self):
"""gets the driving transform"""
if cmds.attributeQuery("driver", n=self.solver, ex=1):
transform = cmds.listConnections(self.solver + '.driver')[0]
if transform:
return transform
return []
@driving_transform.setter
def driving_transform(self, transform):
self.msgConnect(transform + '.ue4_rbf_solver', self.solver + '.driver')
@property
def base_dagPose(self):
xforms = cmds.listConnections(self.solver + '.driven_transforms')
if xforms:
return xforms
else:
return []
@base_dagPose.setter
def base_dagPose(self, dp):
pass
@property
def pose_blenders(self):
if not cmds.attributeQuery("pose_blenders", ex=1, n=self.solver):
return []
xforms = cmds.listConnections(self.solver + '.pose_blenders')
if xforms:
return xforms
else:
return []
@pose_blenders.setter
def poses(self, pose_list):
# nuke poses connections
for item in pose_list:
self.msgConnect(self.solver + '.pose_blenders', item + '.ue4_rbf_solver')
@property
def pose_dict(self):
pose_dict = {}
attrs = cmds.listAttr(self.solver, st='stored_pose_*')
if attrs:
for attr in attrs:
pose_name = attr.replace('stored_pose_', '')
pose_matrices = cmds.listConnections(self.solver + '.' + attr)
pose_dict[pose_name] = pose_matrices
return pose_dict
def zero_all_base_poses():
"""zero's all driver bases poses"""
nodes = cmds.ls(type='UE4RBFSolverNode')
for node in nodes:
driver_obj = UE4PoseDriver(existing_interpolator=node)
driver_obj.zero_base_pose()
def export_drivers(drivers, file_path):
output_data = {"drivers": {}}
for driver in drivers:
driver_data = {}
driver_obj = UE4PoseDriver(existing_interpolator=driver)
solver = driver_obj.solver
driver_name = driver_obj.name.replace("_UE4RBFSolver", "")
driver_data['name'] = driver_name
driver_data['solver_settings'] = {}
solver_attrs = cmds.listAttr(solver, k=1)
for solver_attr in solver_attrs:
try:
value = cmds.getAttr(solver + "." + solver_attr)
except:
continue
driver_data['solver_settings'][solver_attr] = value
driver_data['driver_transform'] = driver_obj.driving_transform
driver_data['driven_transforms'] = driver_obj.driven_transforms
"""
driver_data['driven_off_transforms'] = []
for transform in driver_obj.driven_transforms:
parent = cmds.listRelatives(transform, p=1)
if parent:
"""
driver_data['pose_data'] = {}
for pose, mx_list in driver_obj.pose_dict.items():
pose_data = {
"pose": pose, "local_matrix_map": {}, "world_matrix_map": {}, "driven_trs": {},
"driving_trs": {}
}
driver_obj.assume_pose(pose)
driving_transforn = driver_obj.driving_transform
translate = cmds.getAttr(driving_transforn + ".translate")[0]
rotate = cmds.getAttr(driving_transforn + ".rotate")[0]
scale = cmds.getAttr(driving_transforn + ".scale")[0]
pose_data["driving_trs"][driving_transforn] = {"translate": translate, "rotate": rotate, "scale": scale}
for driven in driver_obj.driven_transforms:
translate = cmds.getAttr(driven + ".translate")[0]
rotate = cmds.getAttr(driven + ".rotate")[0]
scale = cmds.getAttr(driven + ".scale")[0]
pose_data["driven_trs"][driven] = {"translate": translate, "rotate": rotate, "scale": scale}
for mx in mx_list:
local_matrix = cmds.getAttr(mx + ".outputLocalMatrix")
world_matrix = cmds.getAttr(mx + ".outputWorldMatrix")
pose_data['local_matrix_map'][mx] = local_matrix
pose_data['world_matrix_map'][mx] = world_matrix
driver_data['pose_data'][pose] = pose_data
output_data['drivers'][driver_name] = driver_data
# zero the pose
driver_obj.assume_pose("base_pose")
with open(file_path, 'w') as outfile:
json.dump(output_data, outfile, sort_keys=1, indent=4, separators=(",", ":"))
print("Successfuly export pose data to : " + file_path)
def import_drivers(file_path, driverFilter=None):
"""imports the drivers"""
with open(file_path) as json_file:
data = json.load(json_file)
for driver, driver_data in data['drivers'].items():
if driverFilter:
if not driver in driverFilter:
continue
print("driver:")
print(driver)
driving_transform = driver_data['driver_transform']
driven_transforms = driver_data['driven_transforms']
print("driving: ")
print(driving_transform)
print("driven: ")
print(driven_transforms)
# if the driver exists, then just update it
if (cmds.objExists(driver + "_UE4RBFSolver")):
pose_driver_obj = UE4PoseDriver(existing_interpolator=driver + "_UE4RBFSolver")
else:
print("Solver NOT found!")
pose_driver_obj = UE4PoseDriver()
pose_driver_obj.create_pose_driver_system(driver, driving_transform, driven_transforms)
# pose_driver_obj.is_driving(True)#delete this one
pose_driver_obj.is_driving(False)
for attr, value in driver_data['solver_settings'].items():
cmds.setAttr(pose_driver_obj.solver + "." + attr, value)
for pose, pose_data in driver_data['pose_data'].items():
if pose == "base_pose":
continue
if not pose in pose_driver_obj.pose_dict.keys():
pose_driver_obj.add_pose(pose.replace("_pose", ""))
local_matrix_map = pose_data['local_matrix_map']
world_matrix_map = pose_data['local_matrix_map']
for mx_node, local_matrix in local_matrix_map.items():
if mx_node.startswith(driving_transform):
translate = cmds.getAttr(driving_transform + ".translate")[0]
local_matrix[12] = translate[0]
local_matrix[13] = translate[1]
local_matrix[14] = translate[2]
cmds.setAttr(mx_node + ".outputLocalMatrix", local_matrix, type="matrix")
for mx_node, world_matrix in world_matrix_map.items():
if mx_node.startswith(driving_transform):
translate = cmds.getAttr(driving_transform + ".translate")[0]
world_matrix[12] = translate[0]
world_matrix[13] = translate[1]
world_matrix[14] = translate[2]
cmds.setAttr(mx_node + ".outputWorldMatrix", world_matrix, type="matrix")
pose_driver_obj.is_driving(True)
def mirror_all_drivers():
nodes = cmds.ls(type='UE4RBFSolverNode')
for node in nodes:
if "_l_" not in node:
continue
mirror_pose_driver(node)
print("-----------------------------------------------------------")
print("--------successfuly mirrored node: " + node + "-------")
print("-------------------------------------------------------------")
def mirror_pose_driver(source_pose_driver, pose=None):
source_syntax = "_l_"
target_syntax = "_r_"
target_pose_driver = None
if "_r_" in source_pose_driver:
source_syntax = "_r_"
target_syntax = "_l_"
target_pose_driver = source_pose_driver.replace(source_syntax, target_syntax)
source_driver_obj = UE4PoseDriver(existing_interpolator=source_pose_driver)
poses = source_driver_obj.pose_dict.keys()
driven = source_driver_obj.driven_transforms
driving = source_driver_obj.driving_transform
target_driving = driving.replace(source_syntax, target_syntax)
target_driver_obj = None
if not cmds.objExists(target_pose_driver):
target_driven = []
for d in driven:
target_driven.append(d.replace(source_syntax, target_syntax))
target_driver_obj = UE4PoseDriver()
target_driver_obj.create_pose_driver_system(
target_pose_driver.replace("_UE4RBFSolver", ""), target_driving,
target_driven
)
else:
target_driver_obj = UE4PoseDriver(existing_interpolator=target_pose_driver)
# make sure the solver settings match
solver_attrs = cmds.listAttr(source_driver_obj.solver, k=1)
for solver_attr in solver_attrs:
try:
value = cmds.getAttr(source_driver_obj.solver + "." + solver_attr)
except:
continue
cmds.setAttr(target_driver_obj.solver + "." + solver_attr, value)
for p in poses:
if pose and not pose == p:
continue
# don't mirror the base pose
if "base_pose" in p:
continue
target_pose = p.replace(source_syntax, target_syntax)
source_driver_obj.assume_pose(p)
target_driver_obj.is_driving(False)
# if the pose doesn't exist, lets add it
if target_pose not in target_driver_obj.pose_dict.keys():
print("didn't find pose: " + target_pose)
# mirror_transforms([driving], rotation=True, position=False)
rotate = cmds.getAttr(driving + ".rotate")[0]
cmds.setAttr(target_driving + ".rotate", rotate[0], rotate[1], rotate[2])
mirror_transforms(driven)
target_driver_obj.add_pose(target_pose)
else:
target_driver_obj.assume_pose(target_pose)
mirror_transforms(driven)
target_driver_obj.update_pose(target_pose)
target_driver_obj.is_driving(True)
print("Successfully mirrored pose: " + p + " to " + target_pose)
# if no pose was specified, then lets default back to base at the end
if not pose:
source_driver_obj.assume_pose("base_pose")
target_driver_obj.assume_pose("base_pose")
def mirror_transforms(transforms, position=True, rotation=True, scale=True):
"""mirrors the transform"""
for transform in transforms:
source_transform = transform
source_syntax = "_l_"
target_syntax = "_r_"
if "_r_" in source_transform:
source_syntax = "_r_"
target_syntax = "_l_"
target_transform = source_transform.replace(source_syntax, target_syntax)
source_parent_mat = api.MMatrix()
values = cmds.getAttr(source_transform + ".parentMatrix")
index = 0
for i in range(4):
for j in range(4):
source_parent_mat.setElement(i, j, values[index])
index += 1
target_parent_mat = api.MMatrix()
values = cmds.getAttr(target_transform + ".parentMatrix")
index = 0
for i in range(4):
for j in range(4):
target_parent_mat.setElement(i, j, values[index])
index += 1
if position:
pos = cmds.getAttr(source_transform + ".translate")[0]
pos = api.MVector(pos[0], pos[1], pos[2])
pos = _mirror_position(parent_matrix=source_parent_mat, m_parent_matrix=target_parent_mat, pos=pos)
try:
cmds.setAttr(target_transform + ".translate", pos[0], pos[1], pos[2])
except:
pass
if rotation:
rot = cmds.getAttr(source_transform + ".rotate")[0]
rot = api.MVector(rot[0], rot[1], rot[2])
rot = _mirror_rotation(parent_matrix=source_parent_mat, m_parent_matrix=target_parent_mat, rot=rot)
try:
cmds.setAttr(target_transform + ".rotate", rot[0], rot[1], rot[2])
except:
pass
# scale we assume the axis correlate and mirror
if scale:
scale = cmds.getAttr(source_transform + ".scale")[0]
cmds.setAttr(target_transform + ".scale", scale[0], scale[1], scale[2])
def _mirror_position(parent_matrix, m_parent_matrix, pos):
"""this mirrors the position"""
driver_mat_fn = api.MTransformationMatrix(api.MMatrix.kIdentity)
driver_mat_fn.setTranslation(pos, api.MSpace.kWorld)
driver_mat = driver_mat_fn.asMatrix()
scale_matrix_fn = api.MTransformationMatrix(api.MMatrix.kIdentity)
scale_matrix_fn.setScale([-1.0, 1.0, 1.0], api.MSpace.kWorld)
scale_matrix = scale_matrix_fn.asMatrix()
pos_matrix = driver_mat * parent_matrix
pos_matrix = pos_matrix * scale_matrix
pos_matrix = pos_matrix * m_parent_matrix.inverse()
mat_fn = api.MTransformationMatrix(pos_matrix)
m_pos = mat_fn.translation(api.MSpace.kWorld)
return m_pos
def _mirror_rotation(parent_matrix, m_parent_matrix, rot):
"""this mirrors the rotation"""
driver_mat_fn = api.MTransformationMatrix(api.MMatrix.kIdentity)
# set the values to radians
rot[0] = math.radians(rot[0])
rot[1] = math.radians(rot[1])
rot[2] = math.radians(rot[2])
euler = api.MEulerRotation(rot[0], rot[1], rot[2])
driver_mat_fn.setRotation(euler)
driver_matrix = driver_mat_fn.asMatrix()
world_matrix = driver_matrix * parent_matrix
rot_matrix = parent_matrix.inverse() * world_matrix
rot_matrix_fn = api.MTransformationMatrix(rot_matrix)
rot = rot_matrix_fn.rotation(asQuaternion=True)
rot.x = rot.x * -1.0
rot.w = rot.w * -1.0
rot_matrix = rot.asMatrix()
final_rot_matrix = m_parent_matrix * rot_matrix * m_parent_matrix.inverse()
rot_matrix_fn = api.MTransformationMatrix(final_rot_matrix)
rot = rot_matrix_fn.rotation(asQuaternion=False)
m_rot = api.MVector()
m_rot[0] = math.degrees(rot[0])
m_rot[1] = math.degrees(rot[1])
m_rot[2] = math.degrees(rot[2])
return m_rot