Updated
This commit is contained in:
830
Scripts/Animation/epic_pose_wrangler/v2/main.py
Normal file
830
Scripts/Animation/epic_pose_wrangler/v2/main.py
Normal file
@ -0,0 +1,830 @@
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from maya.api import OpenMaya as om
|
||||
|
||||
from epic_pose_wrangler.log import LOG
|
||||
from epic_pose_wrangler.model import mirror_mapping, settings, exceptions
|
||||
from epic_pose_wrangler.model.api import RBFAPI
|
||||
from epic_pose_wrangler.v2.model import api, base_action, base_extension, pose_blender, context, utils
|
||||
from epic_pose_wrangler.v2.model import exceptions as api_exceptions
|
||||
|
||||
class UERBFAPI(RBFAPI):
|
||||
"""
|
||||
Main entry point for interacting with the UERBFSolverNode and UEPoseBlenderNode
|
||||
|
||||
>>> from epic_pose_wrangler.v2 import main
|
||||
>>> rbf_api = main.UERBFAPI(view=False)
|
||||
>>> rbf_api.create_rbf_solver(solver_name="ExampleSolver", drivers=['leg_l'])
|
||||
"""
|
||||
VERSION = "2.0.0"
|
||||
|
||||
def __init__(self, view=False, parent=None, file_path=None):
|
||||
super(UERBFAPI, self).__init__(view=view, parent=parent)
|
||||
|
||||
self._view = None
|
||||
|
||||
# Set up a default mirror mapping
|
||||
self._mirror_mapping = mirror_mapping.MirrorMapping()
|
||||
# Instantiate a settings manager to restore settings from previous sessions
|
||||
self._settings_manager = settings.SettingsManager()
|
||||
# Empty var to store the current solver for convenience so that you don't have to pass the solver through
|
||||
# every time you call a function
|
||||
self._current_solver = None
|
||||
# Empty list to store any core and custom actions used to extend the functionality of PoseWrangler
|
||||
self._extensions = []
|
||||
|
||||
# If view is requested, import and build the UI. We use a local import so that any QtWidget dependencies
|
||||
# won't be loaded if the user is running from mayapy
|
||||
if view:
|
||||
from epic_pose_wrangler.v2.view import pose_wrangler_window
|
||||
self._view = pose_wrangler_window.PoseWranglerWindow()
|
||||
# Connect up the views events to the appropriate functions
|
||||
self._setup_view_events()
|
||||
|
||||
# Load the contents of the current scene
|
||||
LOG.info("Loading PoseWrangler...")
|
||||
self.load()
|
||||
# If a file path is specified, deserialize and load it
|
||||
if file_path:
|
||||
self.deserialize_from_file(file_path=file_path)
|
||||
|
||||
@property
|
||||
def extensions(self):
|
||||
"""
|
||||
:return: list of pose wrangler extensions currently loaded
|
||||
:rtype: list[pose_wrangler.v2.model.base_extension.PoseWranglerExtension]
|
||||
"""
|
||||
return self._extensions
|
||||
|
||||
@property
|
||||
def view(self):
|
||||
"""
|
||||
:return: reference to the ui QWidget
|
||||
:rtype: QtWidgets.QWidget or None
|
||||
"""
|
||||
return self._view
|
||||
|
||||
@property
|
||||
def current_solver(self):
|
||||
"""
|
||||
:return: reference to the current solver
|
||||
:rtype: api.RBFNode or None
|
||||
"""
|
||||
return self._current_solver
|
||||
|
||||
@current_solver.setter
|
||||
def current_solver(self, solver):
|
||||
# If the specified solver is not an RBFNode class, raise exception
|
||||
if not isinstance(solver, api.RBFNode):
|
||||
raise api_exceptions.InvalidSolverError(
|
||||
"Solver is not a valid {node_type} node".format(node_type=api.RBFNode)
|
||||
)
|
||||
# Set the current solver
|
||||
self._current_solver = solver
|
||||
solvers = self.rbf_solvers
|
||||
for action in self._extensions:
|
||||
action.on_context_changed(
|
||||
context.PoseWranglerContext(current_solver=solver, solvers=solvers)
|
||||
)
|
||||
# If we are displaying the view, update it with the current selection
|
||||
if self._view:
|
||||
# Generate a kwarg dict.
|
||||
kwargs = {
|
||||
"solver": solver,
|
||||
# We want to store a reference to the MObject in case the name of the node is changed and the
|
||||
# user doesn't refresh the UI
|
||||
"drivers": {driver: om.MGlobal.getSelectionListByName(driver).getDependNode(0) for driver in
|
||||
solver.drivers()},
|
||||
# We want to display both the driven transforms and connected blendshapes.
|
||||
# NOTE: Blendshapes will only be found if connected up via this API. For more info see:
|
||||
# self.create_blendshape or self.add_blendshape
|
||||
"driven_transforms": {
|
||||
'transform': solver.driven_nodes(pose_blender.UEPoseBlenderNode.node_type),
|
||||
'blendshape': {mesh: solver.get_pose_for_blendshape_mesh(mesh) for mesh in
|
||||
solver.driven_nodes(type='blendShape')}
|
||||
},
|
||||
"poses": solver.poses()
|
||||
}
|
||||
# Load the solver settings in the view with the given kwargs
|
||||
self._view.load_solver_settings(**kwargs)
|
||||
|
||||
@property
|
||||
def mirror_mapping(self):
|
||||
"""
|
||||
:return: reference to the currently loaded mirror mapping
|
||||
:rtype: mirror_mapping.MirrorMapping object
|
||||
"""
|
||||
return self._mirror_mapping
|
||||
|
||||
@property
|
||||
def rbf_solvers(self):
|
||||
"""
|
||||
:return: list of rbf solvers in the scene
|
||||
:rtype: list
|
||||
"""
|
||||
return api.RBFNode.find_all()
|
||||
|
||||
# ================================================ Solvers ======================================================= #
|
||||
|
||||
def create_rbf_solver(self, solver_name, drivers=None):
|
||||
"""
|
||||
Create an rbf solver node with the given name and the specified driver transforms
|
||||
|
||||
:param solver_name: name of the solver node
|
||||
:type solver_name: str
|
||||
:param drivers: list of driver transform node names
|
||||
:type drivers: list
|
||||
:return: RBFNode ref
|
||||
:rtype: api.RBFNode
|
||||
"""
|
||||
# If no drivers are specified, grab the current selection
|
||||
drivers = drivers or utils.get_selection(_type='transform')
|
||||
LOG.debug("Creating RBF solver '{name}' with drivers: {drivers}".format(name=solver_name, drivers=drivers))
|
||||
# Create the solver
|
||||
solver = api.RBFNode.create(solver_name)
|
||||
# If drivers have been specified, add them
|
||||
if drivers:
|
||||
self.add_drivers(drivers=drivers, solver=solver)
|
||||
# If we are in UI mode, add the solver to the view
|
||||
if self._view:
|
||||
self._view.add_rbf_solver(solver)
|
||||
# Set the current solver
|
||||
self.current_solver = solver
|
||||
# Set the current solver to edit mode
|
||||
self.edit_solver(edit=True, solver=solver)
|
||||
# Return the new solver
|
||||
return solver
|
||||
|
||||
def delete_rbf_solver(self, solver=None):
|
||||
"""
|
||||
Delete the specified solver
|
||||
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# If no solver is specified, grab the current solver
|
||||
solver = solver or self._current_solver
|
||||
# Delete the solver
|
||||
solver.delete()
|
||||
# If we are using the UI, delete the solver
|
||||
if self._view:
|
||||
self._view.delete_solver(solver)
|
||||
# If the current solver is this solver, set current to None
|
||||
if self._current_solver == solver:
|
||||
self._current_solver = None
|
||||
|
||||
def edit_solver(self, edit=True, solver=None):
|
||||
"""
|
||||
Edit or finish editing the specified solver. Enables pose creation/driven node changes via the ui
|
||||
|
||||
:param edit: set edit mode on or off
|
||||
:type edit: bool
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# If no solver is specified, grab the current solver
|
||||
solver = solver or self._current_solver
|
||||
# Edit the solver
|
||||
solver.edit_solver(edit=edit)
|
||||
# If we have a ui, update the edit mode status
|
||||
if self._view:
|
||||
self._view.edit_solver(solver=solver, edit=edit)
|
||||
LOG.debug("Setting edit status: {status} for solver: {solver}".format(status=edit, solver=solver))
|
||||
self.current_solver = solver
|
||||
|
||||
def mirror_rbf_solver(self, solver=None):
|
||||
"""
|
||||
Mirror the current solver
|
||||
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
:return: mirrored solver reference
|
||||
:rtype: api.RBFNode
|
||||
"""
|
||||
# If no solver is specified, grab the current solver
|
||||
solver = solver or self._current_solver
|
||||
mirrored_solver = solver.mirror(self.mirror_mapping)
|
||||
if self._view:
|
||||
self._load_view()
|
||||
|
||||
# Set the current solver to the new solver
|
||||
self.current_solver = mirrored_solver
|
||||
# Return the new solver
|
||||
return mirrored_solver
|
||||
|
||||
def get_rbf_solver_by_name(self, solver_name):
|
||||
"""
|
||||
Searches the scene for an rbf solver with the given name. Case insensitive
|
||||
|
||||
:param solver_name: Solver node name
|
||||
:type solver_name: str
|
||||
:return: found node or None
|
||||
:rtype: api.RBFNode or None
|
||||
"""
|
||||
for solver in api.RBFNode.find_all():
|
||||
if str(solver).lower() == solver_name.lower():
|
||||
return solver
|
||||
|
||||
# ================================================ Drivers ======================================================= #
|
||||
|
||||
def add_drivers(self, drivers=None, solver=None):
|
||||
"""
|
||||
Add the specified drivers to the specified solver
|
||||
|
||||
:param drivers: list of transform nodes
|
||||
:type drivers: list
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# If no solver is specified, grab the current solver
|
||||
solver = solver or self._current_solver
|
||||
# If we already have more than one pose, we can't add a new driver (would require manually updating every pose)
|
||||
if solver.num_poses() > 1:
|
||||
raise api_exceptions.InvalidPoseIndex("Too many poses have been created, unable to add a new driver.")
|
||||
# Check if we have a default pose
|
||||
if solver.has_pose(pose_name='default'):
|
||||
# Delete the current rest pose if found
|
||||
solver.delete_pose(pose_name='default')
|
||||
# Add new drivers
|
||||
solver.add_driver(transform_nodes=drivers)
|
||||
# Create the new rest pose with all the drivers
|
||||
solver.add_pose_from_current(pose_name='default')
|
||||
# Set the current solver
|
||||
self.current_solver = solver
|
||||
|
||||
def remove_drivers(self, drivers, solver=None):
|
||||
"""
|
||||
Remove the specified drivers from the specified solver
|
||||
|
||||
:param drivers: list of driver transform nodes
|
||||
:type drivers: list
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Get the solver if it hasn't been specified
|
||||
solver = solver or self._current_solver
|
||||
# Remove the drivers from the solver
|
||||
solver.remove_drivers(drivers)
|
||||
# Have to re-wrap solver due to a bug with targets array indexing
|
||||
self.current_solver = api.RBFNode(str(solver))
|
||||
|
||||
# ================================================ Driven ======================================================== #
|
||||
|
||||
def add_driven_transforms(self, driven_nodes=None, solver=None, edit=False):
|
||||
"""
|
||||
Add driven transforms to the specified solver
|
||||
|
||||
:param driven_nodes: list of transform nodes
|
||||
:type driven_nodes: list
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
:param edit: should this transform not be connected to the pose blender output upon creation
|
||||
:type edit: bool
|
||||
"""
|
||||
# Get the solver if it hasn't been specified
|
||||
solver = solver or self._current_solver
|
||||
solver.add_driven_transforms(driven_nodes, edit=edit)
|
||||
# Update the current solver
|
||||
self.current_solver = solver
|
||||
|
||||
def remove_driven(self, driven_nodes, solver=None):
|
||||
"""
|
||||
Remove driven transforms from the specified solver
|
||||
|
||||
:param driven_nodes: list of transform nodes
|
||||
:type driven_nodes: list
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Get the solver if it hasn't been specified
|
||||
solver = solver or self._current_solver
|
||||
solver.remove_driven_transforms(driven_nodes)
|
||||
# Update the current solver
|
||||
self.current_solver = solver
|
||||
|
||||
# ============================================== Blendshapes ===================================================== #
|
||||
|
||||
def add_blendshape(self, pose_name, mesh_name, base_mesh, solver=None):
|
||||
"""
|
||||
Add an existing blendshape for the current pose
|
||||
|
||||
:param pose_name: name of the pose the blendshape is associated with
|
||||
:type pose_name: str
|
||||
:param mesh_name: name of the existing blendshape mesh
|
||||
:type mesh_name: str
|
||||
:param base_mesh: name of the mesh the blendshape mesh is derived from
|
||||
:type base_mesh: str
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Add the blendshape
|
||||
solver.add_existing_blendshape(pose_name, mesh_name, base_mesh)
|
||||
self.current_solver = solver
|
||||
|
||||
def create_blendshape(self, pose_name, mesh_name=None, edit=False, solver=None):
|
||||
"""
|
||||
Create a new blendshape for the given pose and mesh
|
||||
|
||||
:param pose_name: name of the pose to create this blendshape for
|
||||
:type pose_name: str
|
||||
:param mesh_name: name of the mesh to create the blendshape from
|
||||
:type mesh_name: str
|
||||
:param edit: should this blendshape be edited straight away
|
||||
:type edit: bool
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
:return: name of the newly created blendshape mesh
|
||||
:rtype: str
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Create a new blendshape mesh at the specified pose
|
||||
blendshape_mesh_name = solver.create_blendshape(pose_name, mesh_name)
|
||||
# If we are editing, change the mode
|
||||
if edit:
|
||||
self.edit_blendshape(pose_name=pose_name, edit=True, solver=solver)
|
||||
else:
|
||||
# If we aren't editing, update the current solver
|
||||
self.current_solver = solver
|
||||
# Return the name of the new blendshape mesh
|
||||
return blendshape_mesh_name
|
||||
|
||||
def delete_blendshape(self, pose_name, solver=None):
|
||||
"""
|
||||
Delete the blendshape associated with the specified pose
|
||||
|
||||
:param pose_name: name of the pose to delete blendshapes for
|
||||
:type pose_name: str
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Delete the blendshape mesh at the specified pose
|
||||
solver.delete_blendshape(pose_name, delete_mesh=True)
|
||||
# Set the current solver
|
||||
self.current_solver = solver
|
||||
|
||||
def edit_blendshape(self, pose_name, edit=True, solver=None):
|
||||
"""
|
||||
Edit or finish editing the blendshape associated with the specified pose name
|
||||
|
||||
:param pose_name: name of the pose the blendshape is associated with
|
||||
:type pose_name: str
|
||||
:param edit: True = enable editing, False = finish editing
|
||||
:type edit: bool
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Edit or finish editing the blendshape
|
||||
solver.edit_blendshape(pose_name, edit=edit)
|
||||
# Set the current solver
|
||||
self.current_solver = solver
|
||||
# If we have a ui, update the blendshapes status
|
||||
if self._view:
|
||||
self._view.edit_blendshape(pose_name, edit=edit)
|
||||
|
||||
def isolate_blendshape(self, pose_name, isolate=True, solver=None):
|
||||
"""
|
||||
Isolate the blendshape associated with the specified pose name, disabling all other blendshapes.
|
||||
|
||||
:param pose_name: name of the pose the blendshape is associated with
|
||||
:type pose_name: str
|
||||
:param isolate: True = isolate the blendshape, False = reconnect all disconnected blendshapes
|
||||
:type isolate: bool
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Isolate or un-isolate the blendshape
|
||||
solver.isolate_blendshape(pose_name=pose_name, isolate=isolate)
|
||||
# Set the current solver
|
||||
self.current_solver = solver
|
||||
|
||||
# ================================================= Poses ======================================================== #
|
||||
|
||||
def create_pose(self, pose_name, solver=None):
|
||||
"""
|
||||
Create a new pose for the specified solver
|
||||
|
||||
:param pose_name: name of the new pose
|
||||
:type pose_name: str
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Create a new pose from the current position
|
||||
solver.add_pose_from_current(pose_name=pose_name)
|
||||
# Set the current solver
|
||||
self.current_solver = solver
|
||||
|
||||
def delete_pose(self, pose_name, solver=None):
|
||||
"""
|
||||
Remove a pose from the given solver
|
||||
|
||||
:param pose_name: name of the pose to remove
|
||||
:type pose_name: str
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Delete the pose
|
||||
solver.delete_pose(pose_name)
|
||||
# Set the current solver
|
||||
self.current_solver = solver
|
||||
|
||||
def go_to_pose(self, pose_name, solver=None):
|
||||
"""
|
||||
Move the driver/driven transforms to the given pose
|
||||
|
||||
:param pose_name: name of the pose
|
||||
:type pose_name: str
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Go to the pose
|
||||
solver.go_to_pose(pose_name=pose_name)
|
||||
|
||||
def mirror_pose(self, pose_name, solver=None):
|
||||
"""
|
||||
Mirror a pose to the mirror of the current solver
|
||||
|
||||
:param pose_name: name of the pose
|
||||
:type pose_name: str
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Mirror the pose
|
||||
solver.mirror_pose(pose_name=pose_name, mirror_mapping=self.mirror_mapping)
|
||||
# Reload all the solvers
|
||||
self.load()
|
||||
# Set the current solver
|
||||
self.current_solver = solver
|
||||
|
||||
def mute_pose(self, pose_name, mute=True, solver=None):
|
||||
"""
|
||||
Mute or unmute the specified pose, removing all influences of the pose from the solver.
|
||||
NOTE: This will affect the solver radius if automatic radius is enabled.
|
||||
|
||||
:param pose_name: name of the pose
|
||||
:type pose_name: str
|
||||
:param mute: mute or unmute the pose
|
||||
:type mute: bool
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Mute or unmute the pose
|
||||
solver.mute_pose(pose_name=pose_name, mute=mute)
|
||||
# Set the current solver
|
||||
self.current_solver = solver
|
||||
|
||||
def rename_pose(self, pose_name, new_pose_name, solver=None):
|
||||
"""
|
||||
Rename a pose on the given solver
|
||||
|
||||
:param pose_name: name of the pose
|
||||
:type pose_name: str
|
||||
:param new_pose_name: new name of the pose
|
||||
:type new_pose_name: str
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Get the current pose index
|
||||
pose_index = solver.pose_index(pose_name=pose_name)
|
||||
# Rename the pose at the given index
|
||||
solver.rename_pose(pose_index=pose_index, pose_name=new_pose_name)
|
||||
# Update the current solver
|
||||
self.current_solver = solver
|
||||
|
||||
def update_pose(self, pose_name, solver=None):
|
||||
"""
|
||||
Update the pose for the given solver
|
||||
|
||||
:param pose_name: name of the pose to update
|
||||
:type pose_name: str
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
"""
|
||||
# Ensure we have a solver
|
||||
solver = solver or self._current_solver
|
||||
# Update the pose with the current driver/driven positions
|
||||
solver.add_pose_from_current(pose_name, update=True)
|
||||
LOG.info("Updated {pose_name}".format(pose_name=pose_name))
|
||||
|
||||
self.current_solver = solver
|
||||
|
||||
# ================================================== IO ========================================================== #
|
||||
|
||||
def deserialize_from_file(self, file_path, solver_names=None):
|
||||
"""
|
||||
Deserialize solvers from a specific file.
|
||||
|
||||
:param file_path: json file to load
|
||||
:type file_path: str
|
||||
"""
|
||||
# Check the path exists
|
||||
if not os.path.exists(file_path):
|
||||
raise exceptions.PoseWranglerIOError(
|
||||
"Unable to deserialize from {file_path}, path does not exist".format(file_path=file_path)
|
||||
)
|
||||
if solver_names is None:
|
||||
solver_names = []
|
||||
# Load the json file and deserialize
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.loads(f.read())
|
||||
self.deserialize(data, solver_names=solver_names)
|
||||
LOG.debug("Successfully loaded solvers: {solvers}".format(solvers=solver_names or list(data.keys())))
|
||||
LOG.info(
|
||||
"Successfully loaded {num_solvers} solver(s) from {file_path}".format(
|
||||
num_solvers=len(data),
|
||||
file_path=file_path
|
||||
)
|
||||
)
|
||||
|
||||
def serialize_to_file(self, file_path, solvers=None):
|
||||
"""
|
||||
Serialize the specified solvers to a file
|
||||
|
||||
:param file_path: json file to serialize
|
||||
:type file_path: str
|
||||
:param solvers: list of api.RBFNode to serialize
|
||||
:type solvers: list
|
||||
"""
|
||||
# Check that the directory exists before writing to it
|
||||
if not os.path.exists(os.path.dirname(file_path)):
|
||||
os.makedirs(file_path)
|
||||
# Dump the serialized data to the json file
|
||||
with open(file_path, 'w') as f:
|
||||
data = self.serialize(solvers=solvers)
|
||||
LOG.debug("Successfully serialized solvers: {solvers}".format(solvers=list(data.keys())))
|
||||
f.write(json.dumps(data))
|
||||
LOG.info(
|
||||
"Successfully exported {num_solvers} solver(s) to {file_path}".format(
|
||||
num_solvers=len(data),
|
||||
file_path=file_path
|
||||
)
|
||||
)
|
||||
|
||||
def deserialize(self, data, solver_names=None):
|
||||
"""
|
||||
Deserialize and load the solvers from the data specified
|
||||
|
||||
:param data: serialized solver data
|
||||
:type data: dict
|
||||
:param solver_names: list of solver names to load from the data
|
||||
:type solver_names: list, optional
|
||||
"""
|
||||
for solver_name, solver_data in data.items():
|
||||
if solver_names and solver_name in solver_names or not solver_names:
|
||||
api.RBFNode.create_from_data(solver_data)
|
||||
self.load()
|
||||
|
||||
def serialize(self, solvers=None):
|
||||
"""
|
||||
Serialize the specified solvers
|
||||
|
||||
:param solvers: list of api.RBFNode to serialize
|
||||
:type solvers: list
|
||||
:return: serialized solver data
|
||||
:rtype: dict
|
||||
"""
|
||||
return {str(solver): solver.data() for solver in solvers or self.rbf_solvers}
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
Load the default pose wrangler settings
|
||||
"""
|
||||
# Set the mirror mapping from user settings
|
||||
self.set_mirror_mapping()
|
||||
self._load_extensions()
|
||||
|
||||
if self._view:
|
||||
self._load_view()
|
||||
|
||||
# =============================================== Utilities ====================================================== #
|
||||
|
||||
def get_context(self):
|
||||
"""
|
||||
Get the current solver context
|
||||
|
||||
:return: pose wrangler context containing the current solver and all rbf solvers
|
||||
:rtype: context.PoseWranglerContext
|
||||
"""
|
||||
return context.PoseWranglerContext(current_solver=self.current_solver, solvers=self.rbf_solvers)
|
||||
|
||||
def get_ui_context(self):
|
||||
"""
|
||||
If the ui is available, return the ui context
|
||||
|
||||
:return: ui context containing the current state of the ui
|
||||
:rtype: ui_context.PoseWranglerUIContext or None
|
||||
"""
|
||||
if self._view:
|
||||
return self._view.get_context()
|
||||
|
||||
def get_extension_by_type(self, class_ref):
|
||||
"""
|
||||
Get a reference to one of the loaded extensions from a class type
|
||||
|
||||
:param class_ref: reference to an extension class
|
||||
:type class_ref: base_extension.PoseWranglerExtension
|
||||
:return: reference to a loaded extension if one is loaded
|
||||
:rtype: base_extension.PoseWranglerExtension instance or None
|
||||
"""
|
||||
extensions = [extension for extension in self._extensions if isinstance(extension, class_ref)]
|
||||
if not extensions:
|
||||
return
|
||||
return extensions[0]
|
||||
|
||||
def set_mirror_mapping(self, path=None):
|
||||
"""
|
||||
Set the mirror mapping from a file
|
||||
|
||||
:param path: path to json mirror mapping file
|
||||
:type path: str
|
||||
"""
|
||||
# If no path is specified, get the path stored in the settings manager
|
||||
if path is None:
|
||||
path = self._settings_manager.get_setting("MirrorMappingFile")
|
||||
if path:
|
||||
# If the path doesnt exist, raise an exception
|
||||
if not os.path.exists(path):
|
||||
raise exceptions.InvalidMirrorMapping("Unable to find mapping file: {file}".format(file=path))
|
||||
# Set the new mirror mapping
|
||||
self._mirror_mapping = mirror_mapping.MirrorMapping(file_path=path)
|
||||
self._settings_manager.set_setting("MirrorMappingFile", path)
|
||||
# If we are using the UI, update the mirror mapping file path
|
||||
if self._view:
|
||||
self._view.update_mirror_mapping_file(self._mirror_mapping.file_path)
|
||||
|
||||
def get_solver_edit_status(self, solver):
|
||||
"""
|
||||
Check if the current solver is in 'Edit' mode
|
||||
|
||||
:param solver: solver reference
|
||||
:type solver: api.RBFNode
|
||||
:return: True = in edit mode, False = not in edit mode
|
||||
:rtype: bool
|
||||
"""
|
||||
# Get the solver if it hasn't been specified
|
||||
solver = solver or self._current_solver
|
||||
return solver.get_solver_edit_status()
|
||||
|
||||
def _get_valid_actions(self, ui_context):
|
||||
"""
|
||||
Gets the valid actions base on the ui context
|
||||
:param ui_context :type PoseWranglerUIContext: current selection state of the UI
|
||||
"""
|
||||
if not self._view:
|
||||
return
|
||||
# Find all the available actions
|
||||
found_actions = []
|
||||
# Iterate through all the modules currently loaded
|
||||
for module in list(sys.modules.values()):
|
||||
try:
|
||||
# Find all the classes in the module that inherit from BaseAction
|
||||
found_actions.extend(
|
||||
[obj for name, obj in inspect.getmembers(module, inspect.isclass) if
|
||||
issubclass(
|
||||
obj, base_action.BaseAction
|
||||
) and obj != base_action.BaseAction
|
||||
and obj not in found_actions]
|
||||
)
|
||||
except ImportError:
|
||||
continue
|
||||
|
||||
# Create the actions
|
||||
self._view.valid_actions.clear()
|
||||
self._view.valid_actions.extend([action(api=self) for action in found_actions])
|
||||
|
||||
def _load_extensions(self):
|
||||
"""
|
||||
Loads any core and custom extensions found in the sys.modules
|
||||
"""
|
||||
# Find all the available extensions
|
||||
found_extensions = []
|
||||
# Iterate through all the modules currently loaded
|
||||
for module in list(sys.modules.values()):
|
||||
try:
|
||||
# Find all the classes in the module that inherit from BaseAction
|
||||
found_extensions.extend(
|
||||
[obj for name, obj in inspect.getmembers(module, inspect.isclass) if
|
||||
issubclass(
|
||||
obj, base_extension.PoseWranglerExtension
|
||||
) and obj != base_extension.PoseWranglerExtension
|
||||
and obj not in found_extensions]
|
||||
)
|
||||
except ImportError:
|
||||
continue
|
||||
|
||||
# Create the extensions
|
||||
self._extensions = [extension(display_view=bool(self._view), api=self) for extension in found_extensions]
|
||||
|
||||
# If we are using the UI, show the actions
|
||||
if self._view:
|
||||
self._view.display_extensions(self._extensions)
|
||||
|
||||
def _load_view(self):
|
||||
"""
|
||||
Refresh the UI with the latest solvers
|
||||
"""
|
||||
if not self._view:
|
||||
return
|
||||
# If we have a ui, clear it before we load fresh data
|
||||
self._view.clear()
|
||||
# Bool to keep track if we have a solver that has edits to it
|
||||
existing_edit = None
|
||||
# Iterate through all the solvers in the scene
|
||||
for solver in self.rbf_solvers:
|
||||
# Get the solvers edit status
|
||||
edit = self.get_solver_edit_status(solver)
|
||||
# If we have an existing edit and this solver has also been edited
|
||||
if existing_edit is not None and edit:
|
||||
# Finish editing this solver
|
||||
self.edit_solver(edit=False, solver=solver)
|
||||
# Update the edit status accordingly
|
||||
edit = False
|
||||
# Otherwise if no existing edit and we have an edit
|
||||
elif edit:
|
||||
# Store the existing edit
|
||||
existing_edit = solver
|
||||
# Add the solver to the view with its edit status
|
||||
self._view.add_rbf_solver(solver, edit=edit)
|
||||
|
||||
def _set_current_solver(self, solver):
|
||||
"""
|
||||
UI Event to update the current solver
|
||||
:param solver :type api.RBFNode: solver reference
|
||||
"""
|
||||
self.current_solver = solver
|
||||
|
||||
# noinspection DuplicatedCode
|
||||
def _setup_view_events(self):
|
||||
"""
|
||||
Connect up all of the ui signals to their corresponding functions
|
||||
"""
|
||||
# Only works if we have a view
|
||||
if not self._view:
|
||||
return
|
||||
# Connect up all the ui events
|
||||
# Solver Events
|
||||
self._view.event_create_solver.connect(self.create_rbf_solver)
|
||||
self._view.event_delete_solver.connect(self.delete_rbf_solver)
|
||||
self._view.event_edit_solver.connect(self.edit_solver)
|
||||
self._view.event_mirror_solver.connect(self.mirror_rbf_solver)
|
||||
self._view.event_refresh_solvers.connect(self._load_view)
|
||||
self._view.event_set_current_solver.connect(self._set_current_solver)
|
||||
# Driver Events
|
||||
self._view.event_add_drivers.connect(self.add_drivers)
|
||||
self._view.event_remove_drivers.connect(self.remove_drivers)
|
||||
self._view.event_export_drivers.connect(self.serialize_to_file)
|
||||
self._view.event_import_drivers.connect(self.deserialize_from_file)
|
||||
# Driven Events
|
||||
self._view.event_add_driven.connect(self.add_driven_transforms)
|
||||
self._view.event_remove_driven.connect(self.remove_driven)
|
||||
# Pose Events
|
||||
self._view.event_add_pose.connect(self.create_pose)
|
||||
self._view.event_delete_pose.connect(self.delete_pose)
|
||||
self._view.event_go_to_pose.connect(self.go_to_pose)
|
||||
self._view.event_mirror_pose.connect(self.mirror_pose)
|
||||
self._view.event_rename_pose.connect(self.rename_pose)
|
||||
self._view.event_update_pose.connect(self.update_pose)
|
||||
self._view.event_mute_pose.connect(self.mute_pose)
|
||||
# Blendshape Events
|
||||
self._view.event_create_blendshape.connect(self.create_blendshape)
|
||||
self._view.event_add_blendshape.connect(self.add_blendshape)
|
||||
self._view.event_edit_blendshape.connect(self.edit_blendshape)
|
||||
# Utility Events
|
||||
self._view.event_get_valid_actions.connect(self._get_valid_actions)
|
||||
self._view.event_select.connect(utils.set_selection)
|
||||
self._view.event_set_mirror_mapping.connect(self.set_mirror_mapping)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import ctypes
|
||||
|
||||
from Qt import QtWidgets
|
||||
|
||||
myappid = 'EpicGames.PoseWrangler'
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
tool = UERBFAPI(view=True)
|
||||
app.exec_()
|
Reference in New Issue
Block a user