Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Maya: Refactor and implement fixes for Load Placeholder #436

Merged
Merged
20 changes: 20 additions & 0 deletions client/ayon_core/hosts/maya/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -4212,3 +4212,23 @@ def create_rig_animation_instance(
variant=namespace,
pre_create_data={"use_selection": True}
)


def get_node_index_under_parent(node: str) -> int:
"""Return the index of a DAG node under its parent.

Arguments:
node (str): A DAG Node path.

Returns:
int: The DAG node's index under its parents or world

"""
node = cmds.ls(node, long=True)[0] # enforce long names
parent = node.rsplit("|", 1)[0]
if not parent:
return cmds.ls(assemblies=True, long=True).index(node)
else:
return cmds.listRelatives(parent,
children=True,
fullPath=True).index(node)
164 changes: 162 additions & 2 deletions client/ayon_core/hosts/maya/api/workfile_template_builder.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

from maya import cmds

from ayon_core.pipeline import (
Expand All @@ -8,13 +10,15 @@
)
from ayon_core.pipeline.workfile.workfile_template_builder import (
TemplateAlreadyImported,
AbstractTemplateBuilder
AbstractTemplateBuilder,
PlaceholderPlugin,
PlaceholderItem,
)
from ayon_core.tools.workfile_template_build import (
WorkfileBuildPlaceholderDialog,
)

from .lib import get_main_window
from .lib import read, imprint, get_main_window

PLACEHOLDER_SET = "PLACEHOLDERS_SET"

Expand Down Expand Up @@ -86,6 +90,162 @@ def import_template(self, path):
return True


class MayaPlaceholderPlugin(PlaceholderPlugin):
"""Base Placeholder Plugin for Maya with one unified cache.

Creates a locator as placeholder node, which during populate provide
all of its attributes defined on the locator's transform in
`placeholder.data` and where `placeholder.scene_identifier` is the
full path to the node.

Inherited classes must still implement `populate_placeholder`

"""

use_selection_as_parent = True
item_class = PlaceholderItem

def _create_placeholder_name(self, placeholder_data):
return self.identifier.replace(".", "_")

def _collect_scene_placeholders(self):
nodes_by_identifier = self.builder.get_shared_populate_data(
"placeholder_nodes"
)
if nodes_by_identifier is None:
# Cache placeholder data to shared data
nodes = cmds.ls("*.plugin_identifier", long=True, objectsOnly=True)

nodes_by_identifier = {}
for node in nodes:
identifier = cmds.getAttr("{}.plugin_identifier".format(node))
nodes_by_identifier.setdefault(identifier, []).append(node)

# Set the cache
self.builder.set_shared_populate_data(
"placeholder_nodes", nodes_by_identifier
)

return nodes_by_identifier

def create_placeholder(self, placeholder_data):

parent = None
if self.use_selection_as_parent:
selection = cmds.ls(selection=True)
if len(selection) > 1:
raise ValueError(
"More than one node is selected. "
"Please select only one to define the parent."
)
parent = selection[0] if selection else None

placeholder_data["plugin_identifier"] = self.identifier
placeholder_name = self._create_placeholder_name(placeholder_data)

placeholder = cmds.spaceLocator(name=placeholder_name)[0]
if parent:
placeholder = cmds.parent(placeholder, selection[0])[0]

self.imprint(placeholder, placeholder_data)

def update_placeholder(self, placeholder_item, placeholder_data):
node_name = placeholder_item.scene_identifier

changed_values = {}
for key, value in placeholder_data.items():
if value != placeholder_item.data.get(key):
changed_values[key] = value

# Delete attributes to ensure we imprint new data with correct type
for key in changed_values.keys():
placeholder_item.data[key] = value
if cmds.attributeQuery(key, node=node_name, exists=True):
attribute = "{}.{}".format(node_name, key)
cmds.deleteAttr(attribute)

self.imprint(node_name, changed_values)

def collect_placeholders(self):
placeholders = []
nodes_by_identifier = self._collect_scene_placeholders()
for node in nodes_by_identifier.get(self.identifier, []):
# TODO do data validations and maybe upgrades if they are invalid
placeholder_data = self.read(node)
placeholders.append(
self.item_class(scene_identifier=node,
data=placeholder_data,
plugin=self)
)

return placeholders

def post_placeholder_process(self, placeholder, failed):
"""Cleanup placeholder after load of its corresponding representations.

Hide placeholder, add them to placeholder set.
Used only by PlaceholderCreateMixin and PlaceholderLoadMixin

Args:
placeholder (PlaceholderItem): Item which was just used to load
representation.
failed (bool): Loading of representation failed.
"""
# Hide placeholder and add them to placeholder set
node = placeholder.scene_identifier

# If we just populate the placeholders from current scene, the
# placeholder set will not be created so account for that.
if not cmds.objExists(PLACEHOLDER_SET):
cmds.sets(name=PLACEHOLDER_SET, empty=True)

cmds.sets(node, addElement=PLACEHOLDER_SET)
cmds.hide(node)
cmds.setAttr("{}.hiddenInOutliner".format(node), True)

def delete_placeholder(self, placeholder):
"""Remove placeholder if building was successful

Used only by PlaceholderCreateMixin and PlaceholderLoadMixin.
"""
node = placeholder.scene_identifier

# To avoid that deleting a placeholder node will have Maya delete
# any objectSets the node was a member of we will first remove it
# from any sets it was a member of. This way the `PLACEHOLDERS_SET`
# will survive long enough
sets = cmds.listSets(o=node) or []
for object_set in sets:
cmds.sets(node, remove=object_set)

cmds.delete(node)

def imprint(self, node, data):
"""Imprint call for placeholder node"""

# Complicated data that can't be represented as flat maya attributes
# we write to json strings, e.g. multiselection EnumDef
for key, value in data.items():
if isinstance(value, (list, tuple, dict)):
data[key] = "JSON::{}".format(json.dumps(value))

imprint(node, data)

def read(self, node):
"""Read call for placeholder node"""

data = read(node)

# Complicated data that can't be represented as flat maya attributes
# we read from json strings, e.g. multiselection EnumDef
for key, value in data.items():
if isinstance(value, str) and value.startswith("JSON::"):
value = value[len("JSON::"):] # strip of JSON:: prefix
data[key] = json.loads(value)

return data


def build_workfile_template(*args):
builder = MayaTemplateBuilder(registered_host())
builder.build_template()
Expand Down
Loading
Loading