Skip to content

Commit

Permalink
Merge pull request #74 from ynput/enhancement/AY-6487_Load-Layout-fro…
Browse files Browse the repository at this point in the history
…m-JSON

Implementation of layout loader from Unreal to Maya
  • Loading branch information
antirotor authored Nov 5, 2024
2 parents ffe9aaa + 78b7588 commit 815a5bc
Show file tree
Hide file tree
Showing 2 changed files with 253 additions and 1 deletion.
251 changes: 251 additions & 0 deletions client/ayon_maya/plugins/load/load_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
from maya import cmds
import math
import json
import collections
import ayon_api
from ayon_maya.api import plugin
from ayon_maya.api.lib import (
unique_namespace,
get_container_members,
get_highest_in_hierarchy
)
from ayon_core.pipeline import (
load_container,
discover_loader_plugins,
loaders_from_representation,
get_current_project_name
)
from maya.api import OpenMaya as om
from ayon_maya.api.pipeline import containerise


class LayoutLoader(plugin.Loader):
"""Layout Loader (json)"""

product_types = {"layout"}
representations = {"json"}

label = "Load Layout"
order = -10
icon = "code-fork"
color = "orange"

def _get_repre_entities_by_version_id(self, data):
version_ids = {
element.get("version")
for element in data
}
version_ids.discard(None)

output = collections.defaultdict(list)
if not version_ids:
return output

project_name = get_current_project_name()
repre_entities = ayon_api.get_representations(
project_name,
representation_names={"fbx", "abc"},
version_ids=version_ids,
fields={"id", "versionId", "name"}
)
for repre_entity in repre_entities:
version_id = repre_entity["versionId"]
output[version_id].append(repre_entity)
return output

@staticmethod
def _get_loader(loaders, product_type):
name = ""
if product_type in {
"rig", "model", "camera",
"animation", "staticMesh",
"skeletalMesh"}:
name = "ReferenceLoader"

if name == "":
return None

for loader in loaders:
if loader.__name__ == name:
return loader

return None

def get_asset(self, containers):
# TODO: Improve this logic to support multiples of same asset
# and to avoid bugs with containers getting renamed by artists
# Find container names that starts with 'instance name'
containers = [con for con in containers]
# Get the highest root node from the loaded container
for container in containers:
members = get_container_members(container)
transforms = cmds.ls(members, transforms=True)
roots = get_highest_in_hierarchy(transforms)
root = next(iter(roots), None)
if root is not None:
return root

def _process_element(self, element, repre_entities_by_version_id):
repre_id = None
repr_format = None
version_id = element.get("version")
if version_id:
repre_entities = repre_entities_by_version_id[version_id]
if not repre_entities:
self.log.error(
"No valid representation found for version"
f" {version_id}")
return
# always use the first representation to load
# If reference is None, this element is skipped, as it cannot be
# imported in Maya, repre_entities must always be the first one
repre_entity = repre_entities[0]
repre_id = repre_entity["id"]
repr_format = repre_entity["name"]

# If reference is None, this element is skipped, as it cannot be
# imported in Maya
if not repr_format:
self.log.warning(f"Representation name not defined for element: {element}")
return


instance_name: str = element['instance_name']
all_loaders = discover_loader_plugins()
product_type = element.get("product_type")
if product_type is None:
product_type = element.get("family")
loaders = loaders_from_representation(
all_loaders, repre_id)

loader = self._get_loader(loaders, product_type)

if not loader:
self.log.error(
f"No valid loader found for {repre_id}")
return
options = {
# "asset_dir": asset_dir
}
assets = load_container(
loader,
repre_id,
namespace=instance_name,
options=options
)

self.set_transformation(assets, element)
return assets

def set_transformation(self, assets, element):
asset = self.get_asset(assets)
unreal_import = True if "unreal" in element.get("host", []) else False
if unreal_import:
transform = element["transform"]
self._set_transformation(asset, transform)
else:
transform = element["transform_matrix"]
rotation = element["rotation"]
# flatten matrix to a list
maya_transform_matrix = [element for row in transform for element in row]
self._convert_transformation_matrix(asset, maya_transform_matrix, rotation)

def _set_transformation(self, asset, transform):
translation = [
transform["translation"]["x"],
transform["translation"]["z"],
transform["translation"]["y"]
]

rotation = [
math.degrees(transform["rotation"]["x"]),
-math.degrees(transform["rotation"]["z"]),
math.degrees(transform["rotation"]["y"]),
]
scale = [
transform["scale"]["x"],
transform["scale"]["z"],
transform["scale"]["y"]
]
cmds.xform(
asset,
translation=translation,
rotation=rotation,
scale=scale
)

def _convert_transformation_matrix(self, asset, transform, rotation):
"""Convert matrix to list of transformation matrix for Unreal Engine import.
Args:
transform (list): Transformations of the asset
rotation (list): Rotations of the asset
Returns:
List[om.MMatrix]: List of transformation matrix of the asset
"""
transform_mm = om.MMatrix(transform)
convert_transform = om.MTransformationMatrix(transform_mm)
converted_rotation = om.MEulerRotation(
math.radians(rotation["x"]), math.radians(rotation["y"]), math.radians(rotation["z"])
)
convert_transform.setRotation(converted_rotation)
cmds.xform(asset, matrix=convert_transform.asMatrix())

def load(self, context, name, namespace, options):
path = self.filepath_from_context(context)
self.log.info(f">>> loading json [ {path} ]")
with open(path, "r") as fp:
data = json.load(fp)

# get the list of representations by using version id
repre_entities_by_version_id = self._get_repre_entities_by_version_id(
data
)
assets = []
for element in data:
elements = self._process_element(element, repre_entities_by_version_id)
assets.extend(elements)

folder_name = context["folder"]["name"]
namespace = namespace or unique_namespace(
folder_name + "_",
prefix="_" if folder_name[0].isdigit() else "",
suffix="_",
)

return containerise(
name=name,
namespace=namespace,
nodes=assets,
context=context,
loader=self.__class__.__name__)

def update(self, container, context):
repre_entity = context["representation"]
path = self.filepath_from_context(context)
self.log.info(f">>> loading json [ {path} ]")
with open(path, "r") as fp:
data = json.load(fp)
# TODO: Supports to load non-existing containers
for element in data:
self.set_transformation(container["nodes"], element)
# Update metadata
node = container["objectName"]
cmds.setAttr("{}.representation".format(node),
repre_entity["id"],
type="string")

def switch(self, container, context):
self.update(container, context)

def remove(self, container):
members = cmds.sets(container['objectName'], query=True)
cmds.lockNode(members, lock=False)
cmds.delete([container['objectName']] + members)
# Clean up the namespace
try:
cmds.namespace(removeNamespace=container['namespace'],
deleteNamespaceContent=True)
except RuntimeError:
pass
3 changes: 2 additions & 1 deletion client/ayon_maya/plugins/publish/extract_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ def process(self, instance):
"{}.namespace".format(container)),
"representation": str(representation_id),
"version": str(version_id),
"extension": repre_context["ext"]
"extension": repre_context["ext"],
"host": self.hosts
}

local_matrix = cmds.xform(asset, query=True, matrix=True)
Expand Down

0 comments on commit 815a5bc

Please sign in to comment.