Merge pull request #5710 from ynput/feature/OP-6460_maya-multishot-layout

This commit is contained in:
Ondřej Samohel 2023-10-16 10:11:02 +02:00 committed by GitHub
commit d5e6025b15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 222 additions and 0 deletions

View file

@ -0,0 +1,211 @@
from ayon_api import (
get_folder_by_name,
get_folder_by_path,
get_folders,
)
from maya import cmds # noqa: F401
from openpype import AYON_SERVER_ENABLED
from openpype.client import get_assets
from openpype.hosts.maya.api import plugin
from openpype.lib import BoolDef, EnumDef, TextDef
from openpype.pipeline import (
Creator,
get_current_asset_name,
get_current_project_name,
)
from openpype.pipeline.create import CreatorError
class CreateMultishotLayout(plugin.MayaCreator):
"""Create a multi-shot layout in the Maya scene.
This creator will create a Camera Sequencer in the Maya scene based on
the shots found under the specified folder. The shots will be added to
the sequencer in the order of their clipIn and clipOut values. For each
shot a Layout will be created.
"""
identifier = "io.openpype.creators.maya.multishotlayout"
label = "Multi-shot Layout"
family = "layout"
icon = "project-diagram"
def get_pre_create_attr_defs(self):
# Present artist with a list of parents of the current context
# to choose from. This will be used to get the shots under the
# selected folder to create the Camera Sequencer.
"""
Todo: `get_folder_by_name` should be switched to `get_folder_by_path`
once the fork to pure AYON is done.
Warning: this will not work for projects where the asset name
is not unique across the project until the switch mentioned
above is done.
"""
current_folder = get_folder_by_name(
project_name=get_current_project_name(),
folder_name=get_current_asset_name(),
)
current_path_parts = current_folder["path"].split("/")
# populate the list with parents of the current folder
# this will create menu items like:
# [
# {
# "value": "",
# "label": "project (shots directly under the project)"
# }, {
# "value": "shots/shot_01", "label": "shot_01 (current)"
# }, {
# "value": "shots", "label": "shots"
# }
# ]
# add the project as the first item
items_with_label = [
{
"label": f"{self.project_name} "
"(shots directly under the project)",
"value": ""
}
]
# go through the current folder path and add each part to the list,
# but mark the current folder.
for part_idx, part in enumerate(current_path_parts):
label = part
if label == current_folder["name"]:
label = f"{label} (current)"
value = "/".join(current_path_parts[:part_idx + 1])
items_with_label.append({"label": label, "value": value})
return [
EnumDef("shotParent",
default=current_folder["name"],
label="Shot Parent Folder",
items=items_with_label,
),
BoolDef("groupLoadedAssets",
label="Group Loaded Assets",
tooltip="Enable this when you want to publish group of "
"loaded asset",
default=False),
TextDef("taskName",
label="Associated Task Name",
tooltip=("Task name to be associated "
"with the created Layout"),
default="layout"),
]
def create(self, subset_name, instance_data, pre_create_data):
shots = list(
self.get_related_shots(folder_path=pre_create_data["shotParent"])
)
if not shots:
# There are no shot folders under the specified folder.
# We are raising an error here but in the future we might
# want to create a new shot folders by publishing the layouts
# and shot defined in the sequencer. Sort of editorial publish
# in side of Maya.
raise CreatorError((
"No shots found under the specified "
f"folder: {pre_create_data['shotParent']}."))
# Get layout creator
layout_creator_id = "io.openpype.creators.maya.layout"
layout_creator: Creator = self.create_context.creators.get(
layout_creator_id)
if not layout_creator:
raise CreatorError(
f"Creator {layout_creator_id} not found.")
# Get OpenPype style asset documents for the shots
op_asset_docs = get_assets(
self.project_name, [s["id"] for s in shots])
asset_docs_by_id = {doc["_id"]: doc for doc in op_asset_docs}
for shot in shots:
# we are setting shot name to be displayed in the sequencer to
# `shot name (shot label)` if the label is set, otherwise just
# `shot name`. So far, labels are used only when the name is set
# with characters that are not allowed in the shot name.
if not shot["active"]:
continue
# get task for shot
asset_doc = asset_docs_by_id[shot["id"]]
tasks = asset_doc.get("data").get("tasks").keys()
layout_task = None
if pre_create_data["taskName"] in tasks:
layout_task = pre_create_data["taskName"]
shot_name = f"{shot['name']}%s" % (
f" ({shot['label']})" if shot["label"] else "")
cmds.shot(sequenceStartTime=shot["attrib"]["clipIn"],
sequenceEndTime=shot["attrib"]["clipOut"],
shotName=shot_name)
# Create layout instance by the layout creator
instance_data = {
"asset": shot["name"],
"variant": layout_creator.get_default_variant()
}
if layout_task:
instance_data["task"] = layout_task
layout_creator.create(
subset_name=layout_creator.get_subset_name(
layout_creator.get_default_variant(),
self.create_context.get_current_task_name(),
asset_doc,
self.project_name),
instance_data=instance_data,
pre_create_data={
"groupLoadedAssets": pre_create_data["groupLoadedAssets"]
}
)
def get_related_shots(self, folder_path: str):
"""Get all shots related to the current asset.
Get all folders of type Shot under specified folder.
Args:
folder_path (str): Path of the folder.
Returns:
list: List of dicts with folder data.
"""
# if folder_path is None, project is selected as a root
# and its name is used as a parent id
parent_id = self.project_name
if folder_path:
current_folder = get_folder_by_path(
project_name=self.project_name,
folder_path=folder_path,
)
parent_id = current_folder["id"]
# get all child folders of the current one
return get_folders(
project_name=self.project_name,
parent_ids=[parent_id],
fields=[
"attrib.clipIn", "attrib.clipOut",
"attrib.frameStart", "attrib.frameEnd",
"name", "label", "path", "folderType", "id"
]
)
# blast this creator if Ayon server is not enabled
if not AYON_SERVER_ENABLED:
del CreateMultishotLayout

View file

@ -1,6 +1,7 @@
from pydantic import Field
from ayon_server.settings import BaseSettingsModel
from ayon_server.settings import task_types_enum
class CreateLookModel(BaseSettingsModel):
@ -120,6 +121,16 @@ class CreateVrayProxyModel(BaseSettingsModel):
default_factory=list, title="Default Products")
class CreateMultishotLayout(BasicCreatorModel):
shotParent: str = Field(title="Shot Parent Folder")
groupLoadedAssets: bool = Field(title="Group Loaded Assets")
task_type: list[str] = Field(
title="Task types",
enum_resolver=task_types_enum
)
task_name: str = Field(title="Task name (regex)")
class CreatorsModel(BaseSettingsModel):
CreateLook: CreateLookModel = Field(
default_factory=CreateLookModel,