This commit is contained in:
Jeffreytsai1004 2025-01-14 03:05:57 +08:00
parent 6cfb690ac5
commit 4e5c33f861
27 changed files with 2472 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,12 @@
{
"solver_expression": "(?P<prefix>[a-zA-Z0-9]+)?(?P<side>_[lr]{1}_)(?P<suffix>[a-zA-Z0-9_]+)",
"transform_expression": "(?P<prefix>[a-zA-Z0-9_]+)?(?P<side>_[lr]{1}_)(?P<suffix>[a-zA-Z0-9_]+)",
"left": {
"solver_syntax": "_l_",
"transform_syntax": "_l_"
},
"right": {
"solver_syntax": "_r_",
"transform_syntax": "_r_"
}
}

View File

@ -0,0 +1,30 @@
# Copyright Epic Games, Inc. All Rights Reserved.
from epic_pose_wrangler.model.api import RBFAPI
from epic_pose_wrangler.v1 import poseWranglerUI, poseWrangler
class UE4RBFAPI(RBFAPI):
VERSION = "1.0.0"
def __init__(self, view=False, parent=None, file_path=None):
super(UE4RBFAPI, self).__init__(view=view, parent=parent)
if view:
self._view = poseWranglerUI.PoseWrangler()
self._view.event_upgrade_dispatch.upgrade.connect(self._upgrade)
self._view.show(dockable=True)
@property
def view(self):
return self._view
@property
def api_module(self):
return poseWrangler
def _upgrade(self, file_path):
"""
Upgrade the scene to the new version
:param file_path :type str: file path to the exported scene data
"""
self._parent.upgrade(file_path, delete_file=True)

View File

@ -0,0 +1,147 @@
# Copyright Epic Games, Inc. All Rights Reserved.
def getPaletteString():
bgColorA = "rgb(45, 56, 78)"
scrollWidgetBgColor = "(35, 45, 55)"
bgHighlightColorA = "rgb(121, 94, 102)"
borderDarkColor = "rgb(21, 21, 21)"
buttonLightColor = "rgb(62, 91, 122)"
buttonDisabledColor = "rgb(52, 81, 112)"
buttonLightPressedColor = "rgb(32, 61, 92)"
fontDarkColor = "rgb(118, 93, 105)"
fontLightColor = "rgb(201, 201, 201)"
selectedTabColor = "rgb(253, 251, 212)"
notSelectedTabColor = "rgb(220, 188, 156)"
borderStr = "border: 1px groove " + borderDarkColor + ";"
paletteStr = str(
"QWidget {font-family: Trebuchet MS; font: bold 11px;}"
"QMainWindow{"
"color: " + bgColorA + ";"
"background: " + bgColorA + ";"
"border: 0px solid white;"
"font: " + fontLightColor + ";"
"}"
"QLabel{"
"color: " + fontLightColor + ";"
"}"
"QLineEdit{"
+ borderStr +
"color: " + fontDarkColor + ";"
"border-radius: 5px;"
"background: " + scrollWidgetBgColor + ";"
"color: " + fontLightColor + ";"
"}"
"QPushButton{"
"background: " + buttonLightColor + ";"
"color: " + fontLightColor + ";"
"border-radius: 3px;"
"padding: 8px;"
"margin: 1px;"
"}"
"QPushButton:pressed{"
"background: " + buttonLightPressedColor + ";"
"}"
"QPushButton:!pressed{"
"background: " + buttonLightColor + ";"
"}"
"QPushButton:disabled{"
"background: " + buttonDisabledColor + ";"
"color: " + fontDarkColor + ";"
"}"
"QCheckBox{"
"background-color: " + bgColorA + ";"
"}"
"QMenu{"
"background-color: " + bgColorA + ";"
"border: 1px solid " + borderDarkColor + ";"
"color: " + fontDarkColor + ";"
"}"
"QMenu::item{"
"color: " + fontLightColor + ";"
"}"
"QMenu::item:selected {"
"background-color: " + bgHighlightColorA + ";"
"}"
"QTreeWidget{"
"background-color: " + scrollWidgetBgColor + ";"
+ borderStr +
"color: " + fontDarkColor + ";"
"border-radius: 5px;"
"margin-top: 3px;"
"padding: 10px;"
"}"
"QTreeWidget::item{"
"color: " + fontLightColor + ";"
"}"
"QTreeWidget::item:selected {"
"background-color: " + bgHighlightColorA + ";"
"}"
"QListWidget{"
"background-color: " + scrollWidgetBgColor + ";"
+ borderStr +
"color: " + fontDarkColor + ";"
"border-radius: 5px;"
"margin-top: 3px;"
"padding: 10px;"
"}"
"QGroupBox{"
"background-color: " + bgColorA + ";"
+ borderStr +
"color: " + fontDarkColor + ";"
"border-radius: 5px;"
"margin-top: 10px;"
"padding: 10px;"
"}"
"QGroupBox::title{"
"subcontrol-origin: margin;"
"subcontrol-position: top center;"
"background-color: " + bgColorA + ";"
+ borderStr +
"color: " + fontLightColor + ";"
"border-radius: 3px;"
"padding: 1px 3px;"
"}"
"QComboBox{"
"background: " + buttonLightColor + ";"
"color: " + fontDarkColor + ";"
"border-radius: 4px;"
"}"
"QComboBox::drop-down {"
"subcontrol-origin: padding;"
"subcontrol-position: top right;"
"border-style: none;"
"}"
"QTabWidget::pane{"
"background: " + bgColorA + ";"
+ borderStr +
"color: " + fontDarkColor + ";"
"border-radius: 5px;"
"}"
"QTabWidget::tab-bar {"
"left: 5px;"
"padding:0px;"
"}"
"QTabBar::tab {"
"color: " + fontDarkColor + ";"
"padding:5px;"
"border-top-left-radius: 4px;"
"border-top-right-radius: 4px;"
"border: 1px solid " + borderDarkColor + ";"
"}"
"QTabBar::tab:selected{"
"background: " + selectedTabColor + ";"
"}"
"QTabBar::tab:!selected{"
"background: " + notSelectedTabColor + ";"
"}")
return paletteStr

View File

@ -0,0 +1,993 @@
# 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

View File

@ -0,0 +1,658 @@
# Copyright Epic Games, Inc. All Rights Reserved.
from PySide2 import QtWidgets
from PySide2 import QtCore
from PySide2 import QtUiTools
import os
import maya.cmds as cmds
import maya.OpenMaya as OpenMaya
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
from epic_pose_wrangler.log import LOG
from epic_pose_wrangler.view import log_widget
from epic_pose_wrangler.v1 import poseWrangler, upgrade
from epic_pose_wrangler.v1 import palette
class EventUpgrade(QtCore.QObject):
upgrade = QtCore.Signal(str)
class PoseWrangler(MayaQWidgetDockableMixin, QtWidgets.QMainWindow):
"""
class for the pose wranglerUI
"""
ui_name = "poseWranglerWindow"
def __init__(self, parent=None):
super(PoseWrangler, self).__init__(parent)
self.event_upgrade_dispatch = EventUpgrade()
# Load the dependent plugin
plugin_versions = [{
"name": "MayaUE4RBFPlugin_{}".format(cmds.about(version=True)),
"node_name": "UE4RBFSolverNode"
},
{
"name": "MayaUE4RBFPlugin{}".format(cmds.about(version=True)),
"node_name": "UE4RBFSolverNode"
},
{"name": "MayaUERBFPlugin".format(cmds.about(version=True)), "node_name": "UERBFSolverNode"}]
QtCore.QSettings.setPath(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, os.environ['LOCALAPPDATA'])
self._settings = QtCore.QSettings(
QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, "Epic Games",
"PoseWrangler"
)
self._settings.setFallbacksEnabled(False)
for plugin_version in plugin_versions:
plugin_name = plugin_version['name']
node_name = plugin_version['node_name']
if cmds.pluginInfo(plugin_name, q=True, loaded=True):
self._node_name = node_name
break
else:
try:
cmds.loadPlugin(plugin_name)
self._node_name = node_name
break
except RuntimeError as e:
pass
else:
raise RuntimeError("Unable to load valid RBF plugin version")
# Load the UI file
file_path = os.path.dirname(__file__) + "/poseWranglerUI.ui"
if os.path.exists(file_path):
ui_file = QtCore.QFile(file_path)
# Attempt to open and load the UI
try:
ui_file.open(QtCore.QFile.ReadOnly)
loader = QtUiTools.QUiLoader()
self.win = loader.load(ui_file)
finally:
# Always close the UI file regardless of loader result
ui_file.close()
else:
raise ValueError('UI File does not exist on disk at path: {}'.format(file_path))
self.setWindowTitle("Pose Wrangler")
# Embed the UI window inside this widget
self.setCentralWidget(self.win)
# buttons
self.win.add_pose_BTN.pressed.connect(self.add_pose)
# refresh the driver cmd list
self.load_drivers()
# hook up utils UI
self.win.bake_poses_BTN.pressed.connect(self.bake_poses)
self.win.pose_LIST.itemSelectionChanged.connect(self.pose_changed)
self.win.edit_pose_BTN.pressed.connect(self.edit_pose)
self.win.delete_pose_BTN.pressed.connect(self.delete_pose)
self.win.driver_LIST.itemSelectionChanged.connect(self.driver_changed)
self.win.driver_LIST.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.win.driver_LIST.customContextMenuRequested.connect(self.driver_popup)
self.win.driven_transforms_LIST.itemSelectionChanged.connect(self.driven_changed)
self.win.select_driver_BTN.pressed.connect(self.select_driver)
self.win.add_driven_BTN.pressed.connect(self.add_new_driven)
self.win.mirror_pose_BTN.pressed.connect(self.mirror_pose)
self.win.copy_driven_trs_BTN.pressed.connect(self.copy_driven_trs)
self.win.paste_driven_trs_BTN.pressed.connect(self.paste_driven_trs)
self.win.create_driver_BTN.pressed.connect(self.create_driver)
self.win.delete_driver_BTN.pressed.connect(self.delete_driver)
# setup the 'driving enabled' check box functionality
self.win.toggle_edit_BTN.clicked.connect(self.toggle_edit)
self.win.refresh_BTN.clicked.connect(self.load_drivers)
self.win.driver_transform_LINE.setReadOnly(True)
self.win.upgrade_scene_BTN.pressed.connect(self._upgrade_scene)
self._log_widget = log_widget.LogWidget()
self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self._log_widget.log_dock)
LOG.addHandler(self._log_widget)
# refresh the tree
self.load_poses()
self.set_stylesheet()
self._driver = None
def set_stylesheet(self):
"""set the theming and styling here"""
self.setStyleSheet(palette.getPaletteString())
def driver_popup(self, point):
"""add right click menu"""
selected_solvers = self.get_selected_solvers()
solver = None
if selected_solvers:
solver = selected_solvers[-1]
"""--------------------------EDIT----------------------------"""
edit_action = QtWidgets.QAction("Edit", self)
edit_action.triggered.connect(self.edit_driver)
enable_action = QtWidgets.QAction("Finish Editing (Enable)", self)
enable_action.triggered.connect(self.enable_driver)
"""--------------------------SELECTION----------------------------"""
select_driver_action = QtWidgets.QAction("Select Driver Transform", self)
select_driver_action.triggered.connect(self.select_driver)
select_solver_action = QtWidgets.QAction("Select Solver Node", self)
select_solver_action.triggered.connect(self.select_solver)
select_driven_action = QtWidgets.QAction("Select Driven Transform(s)", self)
select_driven_action.triggered.connect(self.select_driven)
"""--------------------------MIRROR----------------------------"""
mirror_driver_action = QtWidgets.QAction("Mirror Selected Drivers", self)
mirror_driver_action.triggered.connect(self.mirror_driver)
"""----------------------IMPORT/EXPORT----------------------------"""
export_driver_action = QtWidgets.QAction("Export Selected Drivers", self)
export_driver_action.triggered.connect(self.export_driver)
import_driver_action = QtWidgets.QAction("Import Driver(s)", self)
import_driver_action.triggered.connect(self.import_drivers)
export_all_action = QtWidgets.QAction("Export All Drivers", self)
export_all_action.triggered.connect(self.export_all)
"""--------------------------ADD----------------------------"""
add_driven_action = QtWidgets.QAction("Add Driven Transform(s)", self)
add_driven_action.triggered.connect(self.add_new_driven)
"""--------------------------UTILITIES----------------------------"""
zero_base_pose_action = QtWidgets.QAction("Zero Base Pose Transforms", self)
zero_base_pose_action.triggered.connect(self.zero_base_poses)
menu = QtWidgets.QMenu("Options:", self.win.driver_LIST)
select_menu = QtWidgets.QMenu("Select:", menu)
select_menu.addAction(select_driver_action)
select_menu.addAction(select_driven_action)
select_menu.addAction(select_solver_action)
mirror_menu = QtWidgets.QMenu("Mirror:", menu)
mirror_menu.addAction(mirror_driver_action)
import_export_menu = QtWidgets.QMenu("Import/Export:", menu)
if solver:
import_export_menu.addAction(export_driver_action)
import_export_menu.addAction(export_all_action)
import_export_menu.addSeparator()
import_export_menu.addAction(import_driver_action)
add_menu = QtWidgets.QMenu("Add:", menu)
add_menu.addAction(add_driven_action)
utilities_menu = QtWidgets.QMenu("Utilities:", menu)
utilities_menu.addAction(zero_base_pose_action)
if solver:
# if the solver is enabled show the edit and otherwise show the enable
if solver.is_enabled:
menu.addAction(edit_action)
else:
menu.addAction(enable_action)
menu.addSeparator()
menu.addMenu(select_menu)
menu.addMenu(add_menu)
menu.addMenu(mirror_menu)
menu.addMenu(utilities_menu)
menu.addMenu(import_export_menu)
menu.popup(self.win.driver_LIST.mapToGlobal(point))
def get_selected_solvers(self):
"""gets the selected solvers"""
selected_items = self.win.driver_LIST.selectedItems()
selected_solvers = []
if selected_items:
for item in selected_items:
solver = item.data(QtCore.Qt.UserRole)
selected_solvers.append(solver)
return selected_solvers
def get_selected_poses(self):
"""gets the selected poses"""
selected_items = self.win.pose_LIST.selectedItems()
selected_poses = []
if selected_items:
for item in selected_items:
pose = item.text()
selected_poses.append(pose)
return selected_poses
def add_pose(self):
"""add a new pose to the driver"""
selected_solvers = self.get_selected_solvers()
if not selected_solvers:
OpenMaya.MGlobal.displayError('PoseWrangler: You must have a driver selected!')
return
solver = selected_solvers[-1]
pose_name, ok = QtWidgets.QInputDialog.getText(self, 'text', 'Pose Name:')
if pose_name:
solver.add_pose(pose_name)
# self.win.edit_pose_BTN.setEnabled(True)
self.load_poses()
else:
cmds.warning('PoseWrangler: You must enter a pose name to add a pose.')
def edit_pose(self):
"""updates the current pose"""
poses = self.get_selected_poses()
if not poses:
return
pose = poses[-1]
selected_solvers = self.get_selected_solvers()
if not selected_solvers:
OpenMaya.MGlobal.displayError('PoseWrangler: You must have a driver selected!')
return
solver = selected_solvers[-1]
solver.update_pose(pose)
def delete_pose(self):
"""deletes the selected pose"""
poses = self.get_selected_poses()
if not poses:
return
pose = poses[-1]
selected_solvers = self.get_selected_solvers()
if not selected_solvers:
OpenMaya.MGlobal.displayError('PoseWrangler: You must have a driver selected!')
return
solver = selected_solvers[-1]
solver.delete_pose(pose)
self.load_poses()
def driver_changed(self):
"""function that gets called with the driver/solver is clicked"""
selected_solvers = self.get_selected_solvers()
self.win.driven_transforms_LIST.clear()
if selected_solvers:
solver = selected_solvers[-1]
driven_transforms = solver.driven_transforms
if driven_transforms:
for transform in driven_transforms:
item = QtWidgets.QListWidgetItem(transform)
self.win.driven_transforms_LIST.addItem(item)
else:
self.win.driver_transform_LINE.setText("")
self.refresh_ui_state()
self.load_poses()
def driven_changed(self):
"""select the driven transforms when picked in the UI"""
selected_items = self.win.driven_transforms_LIST.selectedItems()
cmds.select(cl=1)
for item in selected_items:
cmds.select(item.text(), add=1)
def select_driver(self):
"""selects the driver(s)"""
cmds.select(cl=1)
selected_solvers = self.get_selected_solvers()
for solver in selected_solvers:
cmds.select(solver.driving_transform, add=1)
def select_driven(self):
"""selects the driven"""
cmds.select(cl=1)
selected_solvers = self.get_selected_solvers()
for solver in selected_solvers:
cmds.select(solver.driven_transforms, add=1)
def select_solver(self):
"""selects the solver DG node"""
cmds.select(cl=1)
selected_solvers = self.get_selected_solvers()
for solver in selected_solvers:
cmds.select(solver.name, add=1)
def bake_poses(self):
"""bakes the poses to the timeline"""
selected_solvers = self.get_selected_solvers()
if selected_solvers:
for solver in selected_solvers:
solver.bake_poses_to_timeline()
def add_new_driven(self):
"""adds new driven transforms to the selected drivers"""
selected_items = self.win.driver_LIST.selectedItems()
sl = cmds.ls(sl=1)
if not sl:
OpenMaya.MGlobal.displayError("No driven object selected, please select a driven object to add")
selected_solvers = self.get_selected_solvers()
if selected_solvers:
for solver in selected_solvers:
for s in sl:
solver.add_driven(s)
self.load_drivers(selected_items[-1].text())
def edit_driver(self):
"""set the drivers into edit mode"""
selected_solvers = self.get_selected_solvers()
if selected_solvers:
for solver in selected_solvers:
solver.is_driving(False)
self.refresh_ui_state()
def enable_driver(self):
"""enables the drivers into finishes edit mode"""
selected_solvers = self.get_selected_solvers()
if selected_solvers:
for solver in selected_solvers:
solver.is_driving(True)
self.refresh_ui_state()
def toggle_edit(self):
"""toggle the edit"""
selected_solvers = self.get_selected_solvers()
if selected_solvers:
solver = selected_solvers[-1]
if solver.is_enabled:
solver.is_driving(False)
else:
solver.is_driving(True)
self.refresh_ui_state()
def refresh_ui_state(self):
"""refresh the UI state"""
selected_solvers = self.get_selected_solvers()
if selected_solvers:
solver = selected_solvers[-1]
self.win.driver_transform_LINE.setText(solver.driving_transform)
# enable buttons that should be available when the driver is selected
self.win.toggle_edit_BTN.setEnabled(True)
self.win.delete_driver_BTN.setEnabled(True)
if solver.is_enabled:
self.win.toggle_edit_BTN.setText("EDIT")
else:
self.win.toggle_edit_BTN.setText("FINISH EDITING (ENABLE)")
self.win.add_pose_BTN.setEnabled(True)
self.win.add_driven_BTN.setEnabled(True)
self.win.select_driver_BTN.setEnabled(True)
self.win.copy_driven_trs_BTN.setEnabled(True)
self.win.paste_driven_trs_BTN.setEnabled(True)
selected_poses = self.get_selected_poses()
if selected_poses:
self.win.edit_pose_BTN.setEnabled(True)
self.win.delete_pose_BTN.setEnabled(True)
self.win.mirror_pose_BTN.setEnabled(True)
else:
self.win.add_pose_BTN.setEnabled(False)
self.win.add_driven_BTN.setEnabled(False)
self.win.toggle_edit_BTN.setEnabled(False)
self.win.delete_driver_BTN.setEnabled(False)
self.win.driver_transform_LINE.setText("")
self.win.select_driver_BTN.setEnabled(False)
# pose buttons
self.win.edit_pose_BTN.setEnabled(False)
self.win.delete_pose_BTN.setEnabled(False)
self.win.mirror_pose_BTN.setEnabled(False)
self.win.copy_driven_trs_BTN.setEnabled(False)
self.win.paste_driven_trs_BTN.setEnabled(False)
for i in range(self.win.driver_LIST.count()):
item = self.win.driver_LIST.item(i)
solver = item.data(QtCore.Qt.UserRole)
if not solver.is_enabled:
item.setText(solver.name + " (Editing)")
else:
item.setText(solver.name)
def pose_changed(self):
"""called when the pose selection is changed"""
selected_poses = self.get_selected_poses()
if selected_poses:
selected_pose = selected_poses[-1]
selected_solvers = self.get_selected_solvers()
solver = selected_solvers[-1]
if selected_pose in solver.pose_dict.keys():
solver.assume_pose(selected_pose)
else:
cmds.warning('Pose ' + selected_pose + ' not found in pose dictionary')
self.refresh_ui_state()
def load_drivers(self, selected=None):
"""loads all the RBF node drivers into the driver list widget"""
self.win.driver_LIST.clear()
drivers = [node for node in cmds.ls(type=self._node_name)]
selected_item = None
for driver in drivers:
item = QtWidgets.QListWidgetItem(driver)
solver = poseWrangler.UE4PoseDriver(existing_interpolator=driver)
item.setData(QtCore.Qt.UserRole, solver)
if selected and selected == driver:
selected_item = item
self.win.driver_LIST.addItem(item)
self.win.driver_LIST.sortItems(QtCore.Qt.AscendingOrder)
if selected_item:
self.win.driver_LIST.setCurrentItem(selected_item)
self.refresh_ui_state()
self.load_poses()
def load_poses(self):
"""loads the poses for the current driver"""
self.win.pose_LIST.clear()
selected_solvers = self.get_selected_solvers()
if selected_solvers:
solver = selected_solvers[-1]
for target in solver.pose_dict.keys() or []:
item = QtWidgets.QListWidgetItem()
item.setText(target)
item.setData(QtCore.Qt.UserRole, target)
self.win.pose_LIST.addItem(item)
# sort the items
self.win.pose_LIST.sortItems(QtCore.Qt.AscendingOrder)
def mirror_pose(self):
"""mirrors the selected pose for the current driver"""
selected_solvers = self.get_selected_solvers()
if not selected_solvers:
return
solver = selected_solvers[-1]
selected_poses = self.get_selected_poses()
if selected_poses:
for selected_pose in selected_poses:
pose_dict = solver.pose_dict
if selected_pose in pose_dict.keys():
poseWrangler.mirror_pose_driver(
solver.name, pose=selected_pose
)
else:
cmds.warning('Pose ' + selected_pose + ' not found in pose dictionary')
def mirror_driver(self):
"""mirrors all poses for the selected drivers"""
selected_solvers = self.get_selected_solvers()
for solver in selected_solvers:
poseWrangler.mirror_pose_driver(solver.name)
self.load_drivers()
def import_drivers(self, file_path=""):
"""imports the drivers"""
path = file_path or QtWidgets.QFileDialog.getOpenFileName(
self, "Pose Wrangler Format",
"", "JSON (*.json)"
)[0]
if path == "":
return
if not os.path.isfile(path):
OpenMaya.MGlobal.displayError(path + " is not a valid file.")
return
poseWrangler.import_drivers(path)
self.load_drivers()
self.load_poses()
def export_driver(self):
"""exports the selected drivers"""
selected_solvers = self.get_selected_solvers()
solver_names = []
for solver in selected_solvers:
solver_names.append(solver.name)
self._export(solver_names)
def export_all(self):
"""exports all rbf nodes"""
nodes = cmds.ls(type=self._node_name)
if not nodes:
return
self._export(nodes)
def _export(self, drivers):
"""exports the drivers passed in"""
file_path = QtWidgets.QFileDialog.getSaveFileName(None, "Pose Wrangler Format", "", "*.json")[0]
if file_path == "":
return
# do the export
poseWrangler.export_drivers(drivers, file_path)
def create_driver(self):
"""creates a new driver"""
sel = cmds.ls(sl=1)
if not sel:
OpenMaya.MGlobal.displayError(
'PoseWrangler: You must select a driving transform to CREATE a pose interpolator'
)
return
# takes all driven and the driving input is last
interp_name, ok = QtWidgets.QInputDialog.getText(self, 'text', 'Driver Name:')
if interp_name:
driver = poseWrangler.UE4PoseDriver()
driver.create_pose_driver_system(interp_name, sel[-1], sel[0:-1])
self._current_solver = driver
# refresh the combobox and set this interpolator as current
self.load_drivers(selected=self._current_solver.name if self._current_solver else None)
def delete_driver(self):
"""deletes the selected drivers"""
selected_solvers = self.get_selected_solvers()
if selected_solvers:
for solver in selected_solvers:
solver.delete()
self.load_drivers()
self.load_poses()
def copy_driven_trs(self):
"""copies the driven TRS for pasting in different poses"""
selected_solvers = self.get_selected_solvers()
if selected_solvers:
for solver in selected_solvers:
solver.copy_driven_trs()
def paste_driven_trs(self):
"""pastes the driven TRS and lets you multiply it"""
mult = self.paste_mult_DSPN.value()
selected_solvers = self.get_selected_solvers()
if selected_solvers:
for solver in selected_solvers:
solver.paste_driven_trs(mult=mult)
def zero_base_poses(self):
"""zeros out the selected solver base poses"""
selected_solvers = self.get_selected_solvers()
if selected_solvers:
for solver in selected_solvers:
solver.zero_base_pose()
def _upgrade_scene(self):
file_path = upgrade.upgrade_scene(clear_scene=True)
LOG.info("Successfully Exported Current Scene")
self.event_upgrade_dispatch.upgrade.emit(file_path)
self.close()
def showUI():
"""show the UI"""
pose_wrangler_widget = PoseWrangler()
# show the UI
pose_wrangler_widget.show(dockable=True)

View File

@ -0,0 +1,550 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>970</width>
<height>860</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QWidget" name="upgrade_container_WGT" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="upgrade_LBL">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>This scene is using an out of date version of PoseWrangler. Please upgrade your scene to use the latest features and functionality.</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignRight">
<widget class="QPushButton" name="upgrade_scene_BTN">
<property name="text">
<string>Upgrade Scene</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>RBF POSE DRIVERS:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="driver_LIST">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ContiguousSelection</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>RMB Pose Drivers for options</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="toggle_edit_BTN">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>EDIT</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="create_driver_BTN">
<property name="text">
<string>CREATE DRIVER</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="delete_driver_BTN">
<property name="font">
<font>
<pointsize>8</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>DELETE DRIVER(S)</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="refresh_BTN">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>REFRESH</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>Select Driven joints then the Driver joint for Create Driver</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>POSES:</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="pose_LIST">
<property name="selectionMode">
<enum>QAbstractItemView::ContiguousSelection</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="add_pose_BTN">
<property name="enabled">
<bool>true</bool>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>ADD POSE</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="edit_pose_BTN">
<property name="enabled">
<bool>true</bool>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>EDIT POSE</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="delete_pose_BTN">
<property name="enabled">
<bool>true</bool>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>DELETE POSE</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mirror_pose_BTN">
<property name="enabled">
<bool>true</bool>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>MIRROR POSE</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>DRIVER TRANSFORM:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="driver_transform_LINE"/>
</item>
<item>
<widget class="QPushButton" name="select_driver_BTN">
<property name="text">
<string>SELECT</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>DRIVEN TRANSFORM(S)</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="driven_transforms_LIST">
<property name="selectionMode">
<enum>QAbstractItemView::ContiguousSelection</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="add_driven_BTN">
<property name="text">
<string>ADD DRIVEN TRANSFORM(S)</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>COPY/PASTE DRIVEN TRS:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="copy_driven_trs_BTN">
<property name="enabled">
<bool>true</bool>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>COPY</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="paste_driven_trs_BTN">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>PASTE</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="maximumSize">
<size>
<width>35</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>MULT</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="paste_mult_DSPN">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="maximum">
<double>5.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="pose_utils_BOX">
<property name="enabled">
<bool>true</bool>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="title">
<string>Utilities:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QPushButton" name="bake_poses_BTN">
<property name="enabled">
<bool>true</bool>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>BAKE POSES TO TIMELINE</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="copy_pose_driver_BTN">
<property name="enabled">
<bool>true</bool>
</property>
<property name="font">
<font>
<pointsize>8</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>COPY UE4 POSE DRIVER</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<action name="connectSkinNode_action">
<property name="text">
<string>Connect Selected Skin Nodes</string>
</property>
</action>
<action name="createGeometry_action">
<property name="text">
<string>Create Geometry From SkinNode</string>
</property>
</action>
<action name="unbindGeometry_action">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Unbind Geometry When Saving</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,77 @@
# Copyright Epic Games, Inc. All Rights Reserved.
import collections
import json
import os
import tempfile
import uuid
from maya import cmds
from epic_pose_wrangler.log import LOG
from epic_pose_wrangler.v1 import poseWrangler
def upgrade_scene(clear_scene=True):
drivers = cmds.ls(type='UE4RBFSolverNode')
output_data = collections.OrderedDict()
for driver in drivers:
driver_data = collections.OrderedDict()
driver_obj = poseWrangler.UE4PoseDriver(existing_interpolator=driver)
solver = driver_obj.solver
driver_name = driver_obj.name.replace("_UE4RBFSolver", "_UERBFSolver")
driver_data['solver_name'] = driver_name
solver_attrs = cmds.listAttr(solver, keyable=True)
for solver_attr in solver_attrs:
try:
value = cmds.getAttr(solver + "." + solver_attr)
except:
continue
driver_data[solver_attr] = value
driver_data['drivers'] = [driver_obj.driving_transform]
driver_data['driven_transforms'] = driver_obj.driven_transforms
driver_data['mode'] = cmds.getAttr("{driver}.mode".format(driver=driver))
driver_data['radius'] = cmds.getAttr("{driver}.radius".format(driver=driver))
driver_data['weightThreshold'] = cmds.getAttr("{driver}.weightThreshold".format(driver=driver))
driver_data['automaticRadius'] = cmds.getAttr("{driver}.automaticRadius".format(driver=driver))
driver_data['distanceMethod'] = cmds.getAttr("{driver}.distanceMethod".format(driver=driver))
driver_data['poses'] = collections.OrderedDict()
driver_transform = driver_obj.driving_transform
sorted_poses = sorted([p for p in driver_obj.pose_dict.keys()])
for pose in sorted_poses:
mx_list = driver_obj.pose_dict[pose]
driver_obj.assume_pose(pose)
pose_data = {
"drivers": [cmds.xform(driver_transform, query=True, matrix=True, objectSpace=True)],
"driven": {transform: cmds.xform(transform, query=True, matrix=True, objectSpace=True)
for transform in driver_obj.driven_transforms},
"function_type": "DefaultFunctionType",
"scale_factor": 1.0,
"distance_method": "DefaultMethod"
}
# Changing default pose name
if pose == 'base_pose':
pose = 'default'
driver_data['poses'][pose] = pose_data
output_data[driver_name] = driver_data
# zero the pose
driver_obj.assume_pose("base_pose")
temp_dir = tempfile.gettempdir()
file_path = os.path.join(temp_dir, "pose_wrangler-{uuid}.json".format(uuid=uuid.uuid4()))
with open(file_path, 'w') as outfile:
json.dump(output_data, outfile, indent=4, separators=(",", ":"))
LOG.debug("Writing scene to {file_path}".format(file_path=file_path))
if clear_scene:
cmds.delete(cmds.ls(type='UE4RBFSolverNode'))
cmds.delete(cmds.ls(type='UE4PoseBlenderNode'))
cmds.delete(cmds.ls('*_pose', type='network'))
for joint in cmds.ls(type='joint'):
for attr in ['mx_pose', 'ue4_rbf_solver', 'blenderNode']:
if cmds.attributeQuery(attr, node=joint, exists=True):
cmds.deleteAttr("{joint}.{attr}".format(joint=joint, attr=attr))
return file_path

View File

@ -0,0 +1,5 @@
from .extensions import (
bake_poses,
copy_paste_trs,
generate_inbetweens
)