mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge branch 'develop' into bugfix/houdini_license_validator_missing_families
This commit is contained in:
commit
4da20799d7
122 changed files with 1902 additions and 484 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -35,6 +35,7 @@ body:
|
|||
label: Version
|
||||
description: What version are you running? Look to OpenPype Tray
|
||||
options:
|
||||
- 3.17.7-nightly.2
|
||||
- 3.17.7-nightly.1
|
||||
- 3.17.6
|
||||
- 3.17.6-nightly.3
|
||||
|
|
@ -134,7 +135,6 @@ body:
|
|||
- 3.15.2-nightly.5
|
||||
- 3.15.2-nightly.4
|
||||
- 3.15.2-nightly.3
|
||||
- 3.15.2-nightly.2
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ from .entities import (
|
|||
get_thumbnail_id_from_source,
|
||||
|
||||
get_workfile_info,
|
||||
|
||||
get_asset_name_identifier,
|
||||
)
|
||||
|
||||
from .entity_links import (
|
||||
|
|
@ -108,4 +110,6 @@ __all__ = (
|
|||
"get_linked_representation_id",
|
||||
|
||||
"create_project",
|
||||
|
||||
"get_asset_name_identifier",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,3 +4,22 @@ if not AYON_SERVER_ENABLED:
|
|||
from .mongo.entities import *
|
||||
else:
|
||||
from .server.entities import *
|
||||
|
||||
|
||||
def get_asset_name_identifier(asset_doc):
|
||||
"""Get asset name identifier by asset document.
|
||||
|
||||
This function is added because of AYON implementation where name
|
||||
identifier is not just a name but full path.
|
||||
|
||||
Asset document must have "name" key, and "data.parents" when in AYON mode.
|
||||
|
||||
Args:
|
||||
asset_doc (dict[str, Any]): Asset document.
|
||||
"""
|
||||
|
||||
if not AYON_SERVER_ENABLED:
|
||||
return asset_doc["name"]
|
||||
parents = list(asset_doc["data"]["parents"])
|
||||
parents.append(asset_doc["name"])
|
||||
return "/" + "/".join(parents)
|
||||
|
|
|
|||
|
|
@ -182,6 +182,19 @@ def get_asset_by_name(project_name, asset_name, fields=None):
|
|||
return None
|
||||
|
||||
|
||||
def _folders_query(project_name, con, fields, **kwargs):
|
||||
if fields is None or "tasks" in fields:
|
||||
folders = get_folders_with_tasks(
|
||||
con, project_name, fields=fields, **kwargs
|
||||
)
|
||||
|
||||
else:
|
||||
folders = con.get_folders(project_name, fields=fields, **kwargs)
|
||||
|
||||
for folder in folders:
|
||||
yield folder
|
||||
|
||||
|
||||
def get_assets(
|
||||
project_name,
|
||||
asset_ids=None,
|
||||
|
|
@ -201,20 +214,39 @@ def get_assets(
|
|||
fields = folder_fields_v3_to_v4(fields, con)
|
||||
kwargs = dict(
|
||||
folder_ids=asset_ids,
|
||||
folder_names=asset_names,
|
||||
parent_ids=parent_ids,
|
||||
active=active,
|
||||
fields=fields
|
||||
)
|
||||
if not asset_names:
|
||||
for folder in _folders_query(project_name, con, fields, **kwargs):
|
||||
yield convert_v4_folder_to_v3(folder, project_name)
|
||||
return
|
||||
|
||||
if fields is None or "tasks" in fields:
|
||||
folders = get_folders_with_tasks(con, project_name, **kwargs)
|
||||
new_asset_names = set()
|
||||
folder_paths = set()
|
||||
for name in asset_names:
|
||||
if "/" in name:
|
||||
folder_paths.add(name)
|
||||
else:
|
||||
new_asset_names.add(name)
|
||||
|
||||
else:
|
||||
folders = con.get_folders(project_name, **kwargs)
|
||||
yielded_ids = set()
|
||||
if folder_paths:
|
||||
for folder in _folders_query(
|
||||
project_name, con, fields, folder_paths=folder_paths, **kwargs
|
||||
):
|
||||
yielded_ids.add(folder["id"])
|
||||
yield convert_v4_folder_to_v3(folder, project_name)
|
||||
|
||||
for folder in folders:
|
||||
yield convert_v4_folder_to_v3(folder, project_name)
|
||||
if not new_asset_names:
|
||||
return
|
||||
|
||||
for folder in _folders_query(
|
||||
project_name, con, fields, folder_names=new_asset_names, **kwargs
|
||||
):
|
||||
if folder["id"] not in yielded_ids:
|
||||
yielded_ids.add(folder["id"])
|
||||
yield convert_v4_folder_to_v3(folder, project_name)
|
||||
|
||||
|
||||
def get_archived_assets(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import collections
|
||||
import json
|
||||
|
||||
import six
|
||||
from ayon_api.graphql import GraphQlQuery, FIELD_VALUE, fields_to_dict
|
||||
|
||||
from .constants import DEFAULT_FOLDER_FIELDS
|
||||
|
|
@ -84,12 +87,12 @@ def get_folders_with_tasks(
|
|||
for folder. All possible folder fields are returned if 'None'
|
||||
is passed.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: Queried folder entities.
|
||||
Yields:
|
||||
Dict[str, Any]: Queried folder entities.
|
||||
"""
|
||||
|
||||
if not project_name:
|
||||
return []
|
||||
return
|
||||
|
||||
filters = {
|
||||
"projectName": project_name
|
||||
|
|
@ -97,25 +100,25 @@ def get_folders_with_tasks(
|
|||
if folder_ids is not None:
|
||||
folder_ids = set(folder_ids)
|
||||
if not folder_ids:
|
||||
return []
|
||||
return
|
||||
filters["folderIds"] = list(folder_ids)
|
||||
|
||||
if folder_paths is not None:
|
||||
folder_paths = set(folder_paths)
|
||||
if not folder_paths:
|
||||
return []
|
||||
return
|
||||
filters["folderPaths"] = list(folder_paths)
|
||||
|
||||
if folder_names is not None:
|
||||
folder_names = set(folder_names)
|
||||
if not folder_names:
|
||||
return []
|
||||
return
|
||||
filters["folderNames"] = list(folder_names)
|
||||
|
||||
if parent_ids is not None:
|
||||
parent_ids = set(parent_ids)
|
||||
if not parent_ids:
|
||||
return []
|
||||
return
|
||||
if None in parent_ids:
|
||||
# Replace 'None' with '"root"' which is used during GraphQl
|
||||
# query for parent ids filter for folders without folder
|
||||
|
|
@ -147,10 +150,10 @@ def get_folders_with_tasks(
|
|||
|
||||
parsed_data = query.query(con)
|
||||
folders = parsed_data["project"]["folders"]
|
||||
if active is None:
|
||||
return folders
|
||||
return [
|
||||
folder
|
||||
for folder in folders
|
||||
if folder["active"] is active
|
||||
]
|
||||
for folder in folders:
|
||||
if active is not None and folder["active"] is not active:
|
||||
continue
|
||||
folder_data = folder.get("data")
|
||||
if isinstance(folder_data, six.string_types):
|
||||
folder["data"] = json.loads(folder_data)
|
||||
yield folder
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ class HostBase(object):
|
|||
if project_name:
|
||||
items.append(project_name)
|
||||
if asset_name:
|
||||
items.append(asset_name)
|
||||
items.append(asset_name.lstrip("/"))
|
||||
if task_name:
|
||||
items.append(task_name)
|
||||
if items:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from openpype import AYON_SERVER_ENABLED
|
||||
import openpype.hosts.aftereffects.api as api
|
||||
from openpype.client import get_asset_by_name
|
||||
from openpype.pipeline import (
|
||||
|
|
@ -43,6 +44,14 @@ class AEWorkfileCreator(AutoCreator):
|
|||
task_name = context.get_current_task_name()
|
||||
host_name = context.host_name
|
||||
|
||||
existing_asset_name = None
|
||||
if existing_instance is not None:
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_asset_name = existing_instance.get("folderPath")
|
||||
|
||||
if existing_asset_name is None:
|
||||
existing_asset_name = existing_instance["asset"]
|
||||
|
||||
if existing_instance is None:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
|
|
@ -50,10 +59,13 @@ class AEWorkfileCreator(AutoCreator):
|
|||
project_name, host_name
|
||||
)
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
"variant": self.default_variant
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
data.update(self.get_dynamic_data(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name, None
|
||||
|
|
@ -68,7 +80,7 @@ class AEWorkfileCreator(AutoCreator):
|
|||
new_instance.data_to_store())
|
||||
|
||||
elif (
|
||||
existing_instance["asset"] != asset_name
|
||||
existing_asset_name != asset_name
|
||||
or existing_instance["task"] != task_name
|
||||
):
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
|
@ -76,6 +88,10 @@ class AEWorkfileCreator(AutoCreator):
|
|||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
)
|
||||
existing_instance["asset"] = asset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_instance["folderPath"] = asset_name
|
||||
else:
|
||||
existing_instance["asset"] = asset_name
|
||||
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import os
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from openpype.client import get_asset_name_identifier
|
||||
from openpype.pipeline.create import get_subset_name
|
||||
|
||||
|
||||
|
|
@ -48,9 +50,11 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
|
|||
asset_entity = context.data["assetEntity"]
|
||||
project_entity = context.data["projectEntity"]
|
||||
|
||||
asset_name = get_asset_name_identifier(asset_entity)
|
||||
|
||||
instance_data = {
|
||||
"active": True,
|
||||
"asset": asset_entity["name"],
|
||||
"asset": asset_name,
|
||||
"task": task,
|
||||
"frameStart": context.data['frameStart'],
|
||||
"frameEnd": context.data['frameEnd'],
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ from typing import Dict, List, Optional
|
|||
|
||||
import bpy
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.pipeline import (
|
||||
Creator,
|
||||
CreatedInstance,
|
||||
LoaderPlugin,
|
||||
get_current_task_name,
|
||||
)
|
||||
from openpype.lib import BoolDef
|
||||
|
||||
|
|
@ -225,7 +225,12 @@ class BaseCreator(Creator):
|
|||
bpy.context.scene.collection.children.link(instances)
|
||||
|
||||
# Create asset group
|
||||
name = prepare_scene_name(instance_data["asset"], subset_name)
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = instance_data["folderPath"]
|
||||
else:
|
||||
asset_name = instance_data["asset"]
|
||||
|
||||
name = prepare_scene_name(asset_name, subset_name)
|
||||
if self.create_as_asset_group:
|
||||
# Create instance as empty
|
||||
instance_node = bpy.data.objects.new(name=name, object_data=None)
|
||||
|
|
@ -281,7 +286,14 @@ class BaseCreator(Creator):
|
|||
|
||||
Args:
|
||||
update_list(List[UpdateData]): Changed instances
|
||||
and their changes, as a list of tuples."""
|
||||
and their changes, as a list of tuples.
|
||||
"""
|
||||
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name_key = "folderPath"
|
||||
else:
|
||||
asset_name_key = "asset"
|
||||
|
||||
for created_instance, changes in update_list:
|
||||
data = created_instance.data_to_store()
|
||||
node = created_instance.transient_data["instance_node"]
|
||||
|
|
@ -295,11 +307,12 @@ class BaseCreator(Creator):
|
|||
|
||||
# Rename the instance node in the scene if subset or asset changed
|
||||
if (
|
||||
"subset" in changes.changed_keys
|
||||
or "asset" in changes.changed_keys
|
||||
"subset" in changes.changed_keys
|
||||
or asset_name_key in changes.changed_keys
|
||||
):
|
||||
asset_name = data[asset_name_key]
|
||||
name = prepare_scene_name(
|
||||
asset=data["asset"], subset=data["subset"]
|
||||
asset=asset_name, subset=data["subset"]
|
||||
)
|
||||
node.name = name
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import bpy
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.pipeline import CreatedInstance, AutoCreator
|
||||
from openpype.client import get_asset_by_name
|
||||
from openpype.hosts.blender.api.plugin import BaseCreator
|
||||
|
|
@ -24,7 +25,7 @@ class CreateWorkfile(BaseCreator, AutoCreator):
|
|||
|
||||
def create(self):
|
||||
"""Create workfile instances."""
|
||||
current_instance = next(
|
||||
existing_instance = next(
|
||||
(
|
||||
instance for instance in self.create_context.instances
|
||||
if instance.creator_identifier == self.identifier
|
||||
|
|
@ -37,16 +38,27 @@ class CreateWorkfile(BaseCreator, AutoCreator):
|
|||
task_name = self.create_context.get_current_task_name()
|
||||
host_name = self.create_context.host_name
|
||||
|
||||
if not current_instance:
|
||||
existing_asset_name = None
|
||||
if existing_instance is not None:
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_asset_name = existing_instance.get("folderPath")
|
||||
|
||||
if existing_asset_name is None:
|
||||
existing_asset_name = existing_instance["asset"]
|
||||
|
||||
if not existing_instance:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
task_name, task_name, asset_doc, project_name, host_name
|
||||
)
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
"variant": task_name,
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
data.update(
|
||||
self.get_dynamic_data(
|
||||
task_name,
|
||||
|
|
@ -54,7 +66,7 @@ class CreateWorkfile(BaseCreator, AutoCreator):
|
|||
asset_doc,
|
||||
project_name,
|
||||
host_name,
|
||||
current_instance,
|
||||
existing_instance,
|
||||
)
|
||||
)
|
||||
self.log.info("Auto-creating workfile instance...")
|
||||
|
|
@ -65,17 +77,21 @@ class CreateWorkfile(BaseCreator, AutoCreator):
|
|||
current_instance.transient_data["instance_node"] = instance_node
|
||||
self._add_instance_to_context(current_instance)
|
||||
elif (
|
||||
current_instance["asset"] != asset_name
|
||||
or current_instance["task"] != task_name
|
||||
existing_asset_name != asset_name
|
||||
or existing_instance["task"] != task_name
|
||||
):
|
||||
# Update instance context if it's different
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
task_name, task_name, asset_doc, project_name, host_name
|
||||
)
|
||||
current_instance["asset"] = asset_name
|
||||
current_instance["task"] = task_name
|
||||
current_instance["subset"] = subset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_instance["folderPath"] = asset_name
|
||||
else:
|
||||
existing_instance["asset"] = asset_name
|
||||
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
|
||||
def collect_instances(self):
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = f"{instance.name}.abc"
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
filename = f"{instance_name}.abc"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
# Perform extraction
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ class ExtractAnimationABC(
|
|||
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = f"{instance.name}.abc"
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
filename = f"{instance_name}.abc"
|
||||
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
# Perform extraction
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
# Define extract output file path
|
||||
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = f"{instance.name}.blend"
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
filename = f"{instance_name}.blend"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
# Perform extraction
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ class ExtractBlendAnimation(
|
|||
# Define extract output file path
|
||||
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = f"{instance.name}.blend"
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
filename = f"{instance_name}.blend"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
# Perform extraction
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = f"{instance.name}.abc"
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
filename = f"{instance_name}.abc"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
# Perform extraction
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = f"{instance.name}.fbx"
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
filename = f"{instance_name}.fbx"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
# Perform extraction
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = f"{instance.name}.fbx"
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
filename = f"{instance_name}.fbx"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
# Perform extraction
|
||||
|
|
|
|||
|
|
@ -145,7 +145,10 @@ class ExtractAnimationFBX(
|
|||
|
||||
root.select_set(True)
|
||||
armature.select_set(True)
|
||||
fbx_filename = f"{instance.name}_{armature.name}.fbx"
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
fbx_filename = f"{instance_name}_{armature.name}.fbx"
|
||||
filepath = os.path.join(stagingdir, fbx_filename)
|
||||
|
||||
override = plugin.create_blender_context(
|
||||
|
|
@ -178,7 +181,7 @@ class ExtractAnimationFBX(
|
|||
pair[1].user_clear()
|
||||
bpy.data.actions.remove(pair[1])
|
||||
|
||||
json_filename = f"{instance.name}.json"
|
||||
json_filename = f"{instance_name}.json"
|
||||
json_path = os.path.join(stagingdir, json_filename)
|
||||
|
||||
json_dict = {
|
||||
|
|
|
|||
|
|
@ -224,7 +224,11 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
json_data.append(json_element)
|
||||
|
||||
json_filename = "{}.json".format(instance.name)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
json_filename = f"{instance_name}.json"
|
||||
|
||||
json_path = os.path.join(stagingdir, json_filename)
|
||||
|
||||
with open(json_path, "w+") as file:
|
||||
|
|
|
|||
|
|
@ -51,7 +51,10 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
# get output path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = instance.name
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
filename = f"{asset_name}_{subset}"
|
||||
|
||||
path = os.path.join(stagingdir, filename)
|
||||
|
||||
self.log.debug(f"Outputting images to {path}")
|
||||
|
|
|
|||
|
|
@ -27,7 +27,10 @@ class ExtractThumbnail(publish.Extractor):
|
|||
self.log.debug("Extracting capture..")
|
||||
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = instance.name
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
filename = f"{asset_name}_{subset}"
|
||||
|
||||
path = os.path.join(stagingdir, filename)
|
||||
|
||||
self.log.debug(f"Outputting images to {path}")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
|
||||
from openpype.client import get_asset_name_identifier
|
||||
|
||||
|
||||
class CollectCelactionInstances(pyblish.api.ContextPlugin):
|
||||
""" Adds the celaction render instances """
|
||||
|
|
@ -17,8 +19,10 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
|
|||
asset_entity = context.data["assetEntity"]
|
||||
project_entity = context.data["projectEntity"]
|
||||
|
||||
asset_name = get_asset_name_identifier(asset_entity)
|
||||
|
||||
shared_instance_data = {
|
||||
"asset": asset_entity["name"],
|
||||
"asset": asset_name,
|
||||
"frameStart": asset_entity["data"]["frameStart"],
|
||||
"frameEnd": asset_entity["data"]["frameEnd"],
|
||||
"handleStart": asset_entity["data"]["handleStart"],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import pyblish.api
|
||||
|
||||
from openpype.client import get_asset_name_identifier
|
||||
import openpype.hosts.flame.api as opfapi
|
||||
from openpype.hosts.flame.otio import flame_export
|
||||
from openpype.pipeline.create import get_subset_name
|
||||
|
|
@ -33,13 +34,15 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin):
|
|||
project_settings=context.data["project_settings"]
|
||||
)
|
||||
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
|
||||
# adding otio timeline to context
|
||||
with opfapi.maintained_segment_selection(sequence) as selected_seg:
|
||||
otio_timeline = flame_export.create_otio_timeline(sequence)
|
||||
|
||||
instance_data = {
|
||||
"name": subset_name,
|
||||
"asset": asset_doc["name"],
|
||||
"asset": asset_name,
|
||||
"subset": subset_name,
|
||||
"family": "workfile",
|
||||
"families": []
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from openpype.hosts.fusion.api import (
|
||||
get_current_comp
|
||||
)
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import get_asset_by_name
|
||||
from openpype.pipeline import (
|
||||
AutoCreator,
|
||||
|
|
@ -68,6 +69,13 @@ class FusionWorkfileCreator(AutoCreator):
|
|||
task_name = self.create_context.get_current_task_name()
|
||||
host_name = self.create_context.host_name
|
||||
|
||||
if existing_instance is None:
|
||||
existing_instance_asset = None
|
||||
elif AYON_SERVER_ENABLED:
|
||||
existing_instance_asset = existing_instance["folderPath"]
|
||||
else:
|
||||
existing_instance_asset = existing_instance["asset"]
|
||||
|
||||
if existing_instance is None:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
|
|
@ -75,10 +83,13 @@ class FusionWorkfileCreator(AutoCreator):
|
|||
project_name, host_name
|
||||
)
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
"variant": self.default_variant
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
data.update(self.get_dynamic_data(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name, None
|
||||
|
|
@ -91,7 +102,7 @@ class FusionWorkfileCreator(AutoCreator):
|
|||
self._add_instance_to_context(new_instance)
|
||||
|
||||
elif (
|
||||
existing_instance["asset"] != asset_name
|
||||
existing_instance_asset != asset_name
|
||||
or existing_instance["task"] != task_name
|
||||
):
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
|
@ -99,6 +110,9 @@ class FusionWorkfileCreator(AutoCreator):
|
|||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
)
|
||||
existing_instance["asset"] = asset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_instance["folderPath"] = asset_name
|
||||
else:
|
||||
existing_instance["asset"] = asset_name
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import qargparse
|
|||
from openpype.settings import get_current_project_settings
|
||||
from openpype.lib import Logger
|
||||
from openpype.pipeline import LoaderPlugin, LegacyCreator
|
||||
from openpype.pipeline.context_tools import get_current_project_asset
|
||||
from openpype.pipeline.load import get_representation_path_from_context
|
||||
from . import lib
|
||||
|
||||
|
|
@ -32,7 +31,7 @@ def load_stylesheet():
|
|||
class CreatorWidget(QtWidgets.QDialog):
|
||||
|
||||
# output items
|
||||
items = dict()
|
||||
items = {}
|
||||
|
||||
def __init__(self, name, info, ui_inputs, parent=None):
|
||||
super(CreatorWidget, self).__init__(parent)
|
||||
|
|
@ -494,9 +493,8 @@ class ClipLoader:
|
|||
joint `data` key with asset.data dict into the representation
|
||||
|
||||
"""
|
||||
asset_name = self.context["representation"]["context"]["asset"]
|
||||
asset_doc = get_current_project_asset(asset_name)
|
||||
log.debug("__ asset_doc: {}".format(pformat(asset_doc)))
|
||||
|
||||
asset_doc = self.context["asset"]
|
||||
self.data["assetData"] = asset_doc["data"]
|
||||
|
||||
def _make_track_item(self, source_bin_item, audio=False):
|
||||
|
|
@ -644,8 +642,8 @@ class PublishClip:
|
|||
Returns:
|
||||
hiero.core.TrackItem: hiero track item object with pype tag
|
||||
"""
|
||||
vertical_clip_match = dict()
|
||||
tag_data = dict()
|
||||
vertical_clip_match = {}
|
||||
tag_data = {}
|
||||
types = {
|
||||
"shot": "shot",
|
||||
"folder": "folder",
|
||||
|
|
@ -707,9 +705,10 @@ class PublishClip:
|
|||
self._create_parents()
|
||||
|
||||
def convert(self):
|
||||
|
||||
# solve track item data and add them to tag data
|
||||
self._convert_to_tag_data()
|
||||
tag_hierarchy_data = self._convert_to_tag_data()
|
||||
|
||||
self.tag_data.update(tag_hierarchy_data)
|
||||
|
||||
# if track name is in review track name and also if driving track name
|
||||
# is not in review track name: skip tag creation
|
||||
|
|
@ -723,16 +722,23 @@ class PublishClip:
|
|||
if self.rename:
|
||||
# rename track item
|
||||
self.track_item.setName(new_name)
|
||||
self.tag_data["asset"] = new_name
|
||||
self.tag_data["asset_name"] = new_name
|
||||
else:
|
||||
self.tag_data["asset"] = self.ti_name
|
||||
self.tag_data["asset_name"] = self.ti_name
|
||||
self.tag_data["hierarchyData"]["shot"] = self.ti_name
|
||||
|
||||
# AYON unique identifier
|
||||
folder_path = "/{}/{}".format(
|
||||
tag_hierarchy_data["hierarchy"],
|
||||
self.tag_data["asset_name"]
|
||||
)
|
||||
self.tag_data["folderPath"] = folder_path
|
||||
if self.tag_data["heroTrack"] and self.review_layer:
|
||||
self.tag_data.update({"reviewTrack": self.review_layer})
|
||||
else:
|
||||
self.tag_data.update({"reviewTrack": None})
|
||||
|
||||
# TODO: remove debug print
|
||||
log.debug("___ self.tag_data: {}".format(
|
||||
pformat(self.tag_data)
|
||||
))
|
||||
|
|
@ -891,7 +897,7 @@ class PublishClip:
|
|||
tag_hierarchy_data = hero_data
|
||||
|
||||
# add data to return data dict
|
||||
self.tag_data.update(tag_hierarchy_data)
|
||||
return tag_hierarchy_data
|
||||
|
||||
def _solve_tag_hierarchy_data(self, hierarchy_formatting_data):
|
||||
""" Solve tag data from hierarchy data and templates. """
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import json
|
|||
|
||||
import pyblish.api
|
||||
|
||||
from openpype.client import get_asset_name_identifier
|
||||
|
||||
|
||||
class CollectFrameTagInstances(pyblish.api.ContextPlugin):
|
||||
"""Collect frames from tags.
|
||||
|
|
@ -99,6 +101,9 @@ class CollectFrameTagInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
# first collect all available subset tag frames
|
||||
subset_data = {}
|
||||
context_asset_doc = context.data["assetEntity"]
|
||||
context_asset_name = get_asset_name_identifier(context_asset_doc)
|
||||
|
||||
for tag_data in sequence_tags:
|
||||
frame = int(tag_data["start"])
|
||||
|
||||
|
|
@ -115,7 +120,7 @@ class CollectFrameTagInstances(pyblish.api.ContextPlugin):
|
|||
subset_data[subset] = {
|
||||
"frames": [frame],
|
||||
"format": tag_data["format"],
|
||||
"asset": context.data["assetEntity"]["name"]
|
||||
"asset": context_asset_name
|
||||
}
|
||||
return subset_data
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import pyblish
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.pipeline.editorial import is_overlapping_otio_ranges
|
||||
|
||||
from openpype.hosts.hiero import api as phiero
|
||||
from openpype.hosts.hiero.api.otio import hiero_export
|
||||
import hiero
|
||||
|
||||
import hiero
|
||||
# # developer reload modules
|
||||
from pprint import pformat
|
||||
|
||||
|
|
@ -80,25 +83,24 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
if k not in ("id", "applieswhole", "label")
|
||||
})
|
||||
|
||||
asset = tag_data["asset"]
|
||||
asset, asset_name = self._get_asset_data(tag_data)
|
||||
|
||||
subset = tag_data["subset"]
|
||||
|
||||
# insert family into families
|
||||
family = tag_data["family"]
|
||||
families = [str(f) for f in tag_data["families"]]
|
||||
families.insert(0, str(family))
|
||||
|
||||
# form label
|
||||
label = asset
|
||||
if asset != clip_name:
|
||||
label = "{} -".format(asset)
|
||||
if asset_name != clip_name:
|
||||
label += " ({})".format(clip_name)
|
||||
label += " {}".format(subset)
|
||||
label += " {}".format("[" + ", ".join(families) + "]")
|
||||
|
||||
data.update({
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"label": label,
|
||||
"asset": asset,
|
||||
"asset_name": asset_name,
|
||||
"item": track_item,
|
||||
"families": families,
|
||||
"publish": tag_data["publish"],
|
||||
|
|
@ -176,9 +178,9 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
})
|
||||
|
||||
def create_shot_instance(self, context, **data):
|
||||
subset = "shotMain"
|
||||
master_layer = data.get("heroTrack")
|
||||
hierarchy_data = data.get("hierarchyData")
|
||||
asset = data.get("asset")
|
||||
item = data.get("item")
|
||||
clip_name = item.name()
|
||||
|
||||
|
|
@ -189,23 +191,21 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
return
|
||||
|
||||
asset = data["asset"]
|
||||
subset = "shotMain"
|
||||
asset_name = data["asset_name"]
|
||||
|
||||
# insert family into families
|
||||
family = "shot"
|
||||
|
||||
# form label
|
||||
label = asset
|
||||
if asset != clip_name:
|
||||
label = "{} -".format(asset)
|
||||
if asset_name != clip_name:
|
||||
label += " ({}) ".format(clip_name)
|
||||
label += " {}".format(subset)
|
||||
label += " [{}]".format(family)
|
||||
|
||||
data.update({
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"label": label,
|
||||
"subset": subset,
|
||||
"asset": asset,
|
||||
"family": family,
|
||||
"families": []
|
||||
})
|
||||
|
|
@ -215,7 +215,33 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
self.log.debug(
|
||||
"_ instance.data: {}".format(pformat(instance.data)))
|
||||
|
||||
def _get_asset_data(self, data):
|
||||
folder_path = data.pop("folderPath", None)
|
||||
|
||||
if data.get("asset_name"):
|
||||
asset_name = data["asset_name"]
|
||||
else:
|
||||
asset_name = data["asset"]
|
||||
|
||||
# backward compatibility for clip tags
|
||||
# which are missing folderPath key
|
||||
# TODO remove this in future versions
|
||||
if not folder_path:
|
||||
hierarchy_path = data["hierarchy"]
|
||||
folder_path = "/{}/{}".format(
|
||||
hierarchy_path,
|
||||
asset_name
|
||||
)
|
||||
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset = folder_path
|
||||
else:
|
||||
asset = asset_name
|
||||
|
||||
return asset, asset_name
|
||||
|
||||
def create_audio_instance(self, context, **data):
|
||||
subset = "audioMain"
|
||||
master_layer = data.get("heroTrack")
|
||||
|
||||
if not master_layer:
|
||||
|
|
@ -230,23 +256,21 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
return
|
||||
|
||||
asset = data["asset"]
|
||||
subset = "audioMain"
|
||||
asset_name = data["asset_name"]
|
||||
|
||||
# insert family into families
|
||||
family = "audio"
|
||||
|
||||
# form label
|
||||
label = asset
|
||||
if asset != clip_name:
|
||||
label = "{} -".format(asset)
|
||||
if asset_name != clip_name:
|
||||
label += " ({}) ".format(clip_name)
|
||||
label += " {}".format(subset)
|
||||
label += " [{}]".format(family)
|
||||
|
||||
data.update({
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"label": label,
|
||||
"subset": subset,
|
||||
"asset": asset,
|
||||
"family": family,
|
||||
"families": ["clip"]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from qtpy.QtGui import QPixmap
|
|||
|
||||
import hiero.ui
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.hosts.hiero.api.otio import hiero_export
|
||||
|
||||
|
||||
|
|
@ -17,9 +18,11 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
order = pyblish.api.CollectorOrder - 0.491
|
||||
|
||||
def process(self, context):
|
||||
|
||||
asset = context.data["asset"]
|
||||
subset = "workfile"
|
||||
asset_name = asset
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = asset_name.split("/")[-1]
|
||||
|
||||
active_timeline = hiero.ui.activeSequence()
|
||||
project = active_timeline.project()
|
||||
fps = active_timeline.framerate().toFloat()
|
||||
|
|
@ -27,7 +30,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
# adding otio timeline to context
|
||||
otio_timeline = hiero_export.create_otio_timeline()
|
||||
|
||||
# get workfile thumnail paths
|
||||
# get workfile thumbnail paths
|
||||
tmp_staging = tempfile.mkdtemp(prefix="pyblish_tmp_")
|
||||
thumbnail_name = "workfile_thumbnail.png"
|
||||
thumbnail_path = os.path.join(tmp_staging, thumbnail_name)
|
||||
|
|
@ -49,8 +52,8 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
}
|
||||
|
||||
# get workfile paths
|
||||
curent_file = project.path()
|
||||
staging_dir, base_name = os.path.split(curent_file)
|
||||
current_file = project.path()
|
||||
staging_dir, base_name = os.path.split(current_file)
|
||||
|
||||
# creating workfile representation
|
||||
workfile_representation = {
|
||||
|
|
@ -59,13 +62,16 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
'files': base_name,
|
||||
"stagingDir": staging_dir,
|
||||
}
|
||||
|
||||
family = "workfile"
|
||||
instance_data = {
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"asset": asset,
|
||||
"subset": "{}{}".format(asset, subset.capitalize()),
|
||||
"label": "{} - {}Main".format(
|
||||
asset, family),
|
||||
"name": "{}_{}".format(asset_name, family),
|
||||
"asset": context.data["asset"],
|
||||
# TODO use 'get_subset_name'
|
||||
"subset": "{}{}Main".format(asset_name, family.capitalize()),
|
||||
"item": project,
|
||||
"family": "workfile",
|
||||
"family": family,
|
||||
"families": [],
|
||||
"representations": [workfile_representation, thumb_representation]
|
||||
}
|
||||
|
|
@ -78,7 +84,7 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
"activeProject": project,
|
||||
"activeTimeline": active_timeline,
|
||||
"otioTimeline": otio_timeline,
|
||||
"currentFile": curent_file,
|
||||
"currentFile": current_file,
|
||||
"colorspace": self.get_colorspace(project),
|
||||
"fps": fps
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from pyblish import api
|
||||
from openpype.client import get_assets
|
||||
|
||||
from openpype.client import get_assets, get_asset_name_identifier
|
||||
|
||||
|
||||
class CollectAssetBuilds(api.ContextPlugin):
|
||||
|
|
@ -19,10 +20,13 @@ class CollectAssetBuilds(api.ContextPlugin):
|
|||
def process(self, context):
|
||||
project_name = context.data["projectName"]
|
||||
asset_builds = {}
|
||||
for asset in get_assets(project_name):
|
||||
if asset["data"]["entityType"] == "AssetBuild":
|
||||
self.log.debug("Found \"{}\" in database.".format(asset))
|
||||
asset_builds[asset["name"]] = asset
|
||||
for asset_doc in get_assets(project_name):
|
||||
if asset_doc["data"].get("entityType") != "AssetBuild":
|
||||
continue
|
||||
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
self.log.debug("Found \"{}\" in database.".format(asset_doc))
|
||||
asset_builds[asset_name] = asset_doc
|
||||
|
||||
for instance in context:
|
||||
if instance.data["family"] != "clip":
|
||||
|
|
@ -50,9 +54,7 @@ class CollectAssetBuilds(api.ContextPlugin):
|
|||
# Collect asset builds.
|
||||
data = {"assetbuilds": []}
|
||||
for name in asset_names:
|
||||
data["assetbuilds"].append(
|
||||
asset_builds[name]
|
||||
)
|
||||
data["assetbuilds"].append(asset_builds[name])
|
||||
self.log.debug(
|
||||
"Found asset builds: {}".format(data["assetbuilds"])
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ from abc import (
|
|||
)
|
||||
import six
|
||||
import hou
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.pipeline import (
|
||||
CreatorError,
|
||||
LegacyCreator,
|
||||
|
|
@ -142,12 +144,13 @@ class HoudiniCreatorBase(object):
|
|||
|
||||
@staticmethod
|
||||
def create_instance_node(
|
||||
node_name, parent,
|
||||
node_type="geometry"):
|
||||
asset_name, node_name, parent, node_type="geometry"
|
||||
):
|
||||
# type: (str, str, str) -> hou.Node
|
||||
"""Create node representing instance.
|
||||
|
||||
Arguments:
|
||||
asset_name (str): Asset name.
|
||||
node_name (str): Name of the new node.
|
||||
parent (str): Name of the parent node.
|
||||
node_type (str, optional): Type of the node.
|
||||
|
|
@ -182,8 +185,13 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase):
|
|||
if node_type is None:
|
||||
node_type = "geometry"
|
||||
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = instance_data["folderPath"]
|
||||
else:
|
||||
asset_name = instance_data["asset"]
|
||||
|
||||
instance_node = self.create_instance_node(
|
||||
subset_name, "/out", node_type)
|
||||
asset_name, subset_name, "/out", node_type)
|
||||
|
||||
self.customize_node_look(instance_node)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ class CreateHDA(plugin.HoudiniCreator):
|
|||
icon = "gears"
|
||||
maintain_selection = False
|
||||
|
||||
def _check_existing(self, subset_name):
|
||||
def _check_existing(self, asset_name, subset_name):
|
||||
# type: (str) -> bool
|
||||
"""Check if existing subset name versions already exists."""
|
||||
# Get all subsets of the current asset
|
||||
project_name = self.project_name
|
||||
asset_doc = get_asset_by_name(
|
||||
project_name, self.data["asset"], fields=["_id"]
|
||||
project_name, asset_name, fields=["_id"]
|
||||
)
|
||||
subset_docs = get_subsets(
|
||||
project_name, asset_ids=[asset_doc["_id"]], fields=["name"]
|
||||
|
|
@ -35,7 +35,8 @@ class CreateHDA(plugin.HoudiniCreator):
|
|||
return subset_name.lower() in existing_subset_names_low
|
||||
|
||||
def create_instance_node(
|
||||
self, node_name, parent, node_type="geometry"):
|
||||
self, asset_name, node_name, parent, node_type="geometry"
|
||||
):
|
||||
|
||||
parent_node = hou.node("/obj")
|
||||
if self.selected_nodes:
|
||||
|
|
@ -61,7 +62,7 @@ class CreateHDA(plugin.HoudiniCreator):
|
|||
hda_file_name="$HIP/{}.hda".format(node_name)
|
||||
)
|
||||
hda_node.layoutChildren()
|
||||
elif self._check_existing(node_name):
|
||||
elif self._check_existing(asset_name, node_name):
|
||||
raise plugin.OpenPypeCreatorError(
|
||||
("subset {} is already published with different HDA"
|
||||
"definition.").format(node_name))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Creator plugin for creating workfiles."""
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.hosts.houdini.api import plugin
|
||||
from openpype.hosts.houdini.api.lib import read, imprint
|
||||
from openpype.hosts.houdini.api.pipeline import CONTEXT_CONTAINER
|
||||
|
|
@ -30,16 +31,27 @@ class CreateWorkfile(plugin.HoudiniCreatorBase, AutoCreator):
|
|||
task_name = self.create_context.get_current_task_name()
|
||||
host_name = self.host_name
|
||||
|
||||
if current_instance is None:
|
||||
current_instance_asset = None
|
||||
elif AYON_SERVER_ENABLED:
|
||||
current_instance_asset = current_instance["folderPath"]
|
||||
else:
|
||||
current_instance_asset = current_instance["asset"]
|
||||
|
||||
if current_instance is None:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
variant, task_name, asset_doc, project_name, host_name
|
||||
)
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
"variant": variant
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
|
||||
data.update(
|
||||
self.get_dynamic_data(
|
||||
variant, task_name, asset_doc,
|
||||
|
|
@ -51,15 +63,18 @@ class CreateWorkfile(plugin.HoudiniCreatorBase, AutoCreator):
|
|||
)
|
||||
self._add_instance_to_context(current_instance)
|
||||
elif (
|
||||
current_instance["asset"] != asset_name
|
||||
or current_instance["task"] != task_name
|
||||
current_instance_asset != asset_name
|
||||
or current_instance["task"] != task_name
|
||||
):
|
||||
# Update instance context if is not the same
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
variant, task_name, asset_doc, project_name, host_name
|
||||
)
|
||||
current_instance["asset"] = asset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
current_instance["folderPath"] = asset_name
|
||||
else:
|
||||
current_instance["asset"] = asset_name
|
||||
current_instance["task"] = task_name
|
||||
current_instance["subset"] = subset_name
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import pyblish.api
|
||||
|
||||
from openpype.client import get_subset_by_name, get_asset_by_name
|
||||
from openpype.client import (
|
||||
get_subset_by_name,
|
||||
get_asset_by_name,
|
||||
get_asset_name_identifier,
|
||||
)
|
||||
import openpype.lib.usdlib as usdlib
|
||||
|
||||
|
||||
|
|
@ -51,8 +55,9 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin):
|
|||
self.log.debug("Add bootstrap for: %s" % bootstrap)
|
||||
|
||||
project_name = instance.context.data["projectName"]
|
||||
asset = get_asset_by_name(project_name, instance.data["asset"])
|
||||
assert asset, "Asset must exist: %s" % asset
|
||||
asset_name = instance.data["asset"]
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
assert asset_doc, "Asset must exist: %s" % asset_name
|
||||
|
||||
# Check which are not about to be created and don't exist yet
|
||||
required = {"shot": ["usdShot"], "asset": ["usdAsset"]}.get(bootstrap)
|
||||
|
|
@ -67,19 +72,21 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin):
|
|||
required += list(layers)
|
||||
|
||||
self.log.debug("Checking required bootstrap: %s" % required)
|
||||
for subset in required:
|
||||
if self._subset_exists(project_name, instance, subset, asset):
|
||||
for subset_name in required:
|
||||
if self._subset_exists(
|
||||
project_name, instance, subset_name, asset_doc
|
||||
):
|
||||
continue
|
||||
|
||||
self.log.debug(
|
||||
"Creating {0} USD bootstrap: {1} {2}".format(
|
||||
bootstrap, asset["name"], subset
|
||||
bootstrap, asset_name, subset_name
|
||||
)
|
||||
)
|
||||
|
||||
new = instance.context.create_instance(subset)
|
||||
new.data["subset"] = subset
|
||||
new.data["label"] = "{0} ({1})".format(subset, asset["name"])
|
||||
new = instance.context.create_instance(subset_name)
|
||||
new.data["subset"] = subset_name
|
||||
new.data["label"] = "{0} ({1})".format(subset_name, asset_name)
|
||||
new.data["family"] = "usd.bootstrap"
|
||||
new.data["comment"] = "Automated bootstrap USD file."
|
||||
new.data["publishFamilies"] = ["usd"]
|
||||
|
|
@ -91,21 +98,23 @@ class CollectUsdBootstrap(pyblish.api.InstancePlugin):
|
|||
for key in ["asset"]:
|
||||
new.data[key] = instance.data[key]
|
||||
|
||||
def _subset_exists(self, project_name, instance, subset, asset):
|
||||
def _subset_exists(self, project_name, instance, subset_name, asset_doc):
|
||||
"""Return whether subset exists in current context or in database."""
|
||||
# Allow it to be created during this publish session
|
||||
context = instance.context
|
||||
|
||||
asset_doc_name = get_asset_name_identifier(asset_doc)
|
||||
for inst in context:
|
||||
if (
|
||||
inst.data["subset"] == subset
|
||||
and inst.data["asset"] == asset["name"]
|
||||
inst.data["subset"] == subset_name
|
||||
and inst.data["asset"] == asset_doc_name
|
||||
):
|
||||
return True
|
||||
|
||||
# Or, if they already exist in the database we can
|
||||
# skip them too.
|
||||
if get_subset_by_name(
|
||||
project_name, subset, asset["_id"], fields=["_id"]
|
||||
project_name, subset_name, asset_doc["_id"], fields=["_id"]
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -54,12 +54,13 @@ class ValidateSubsetName(pyblish.api.InstancePlugin,
|
|||
rop_node = hou.node(instance.data["instance_node"])
|
||||
|
||||
# Check subset name
|
||||
asset_doc = instance.data["assetEntity"]
|
||||
subset_name = get_subset_name(
|
||||
family=instance.data["family"],
|
||||
variant=instance.data["variant"],
|
||||
task_name=instance.data["task"],
|
||||
asset_doc=instance.data["assetEntity"],
|
||||
dynamic_data={"asset": instance.data["asset"]}
|
||||
asset_doc=asset_doc,
|
||||
dynamic_data={"asset": asset_doc["name"]}
|
||||
)
|
||||
|
||||
if instance.data.get("subset") != subset_name:
|
||||
|
|
@ -76,12 +77,13 @@ class ValidateSubsetName(pyblish.api.InstancePlugin,
|
|||
rop_node = hou.node(instance.data["instance_node"])
|
||||
|
||||
# Check subset name
|
||||
asset_doc = instance.data["assetEntity"]
|
||||
subset_name = get_subset_name(
|
||||
family=instance.data["family"],
|
||||
variant=instance.data["variant"],
|
||||
task_name=instance.data["task"],
|
||||
asset_doc=instance.data["assetEntity"],
|
||||
dynamic_data={"asset": instance.data["asset"]}
|
||||
asset_doc=asset_doc,
|
||||
dynamic_data={"asset": asset_doc["name"]}
|
||||
)
|
||||
|
||||
instance.data["subset"] = subset_name
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ class RenderSettings(object):
|
|||
def set_default_renderer_settings(self, renderer=None):
|
||||
"""Set basic settings based on renderer."""
|
||||
# Not all hosts can import this module.
|
||||
from maya import cmds
|
||||
import maya.mel as mel
|
||||
from maya import cmds # noqa: F401
|
||||
import maya.mel as mel # noqa: F401
|
||||
|
||||
if not renderer:
|
||||
renderer = cmds.getAttr(
|
||||
|
|
@ -126,6 +126,10 @@ class RenderSettings(object):
|
|||
"""Sets settings for Arnold."""
|
||||
from mtoa.core import createOptions # noqa
|
||||
from mtoa.aovs import AOVInterface # noqa
|
||||
# Not all hosts can import this module.
|
||||
from maya import cmds # noqa: F401
|
||||
import maya.mel as mel # noqa: F401
|
||||
|
||||
createOptions()
|
||||
render_settings = self._project_settings["maya"]["RenderSettings"]
|
||||
arnold_render_presets = render_settings["arnold_renderer"] # noqa
|
||||
|
|
@ -172,6 +176,10 @@ class RenderSettings(object):
|
|||
|
||||
def _set_redshift_settings(self, width, height):
|
||||
"""Sets settings for Redshift."""
|
||||
# Not all hosts can import this module.
|
||||
from maya import cmds # noqa: F401
|
||||
import maya.mel as mel # noqa: F401
|
||||
|
||||
render_settings = self._project_settings["maya"]["RenderSettings"]
|
||||
redshift_render_presets = render_settings["redshift_renderer"]
|
||||
|
||||
|
|
@ -224,6 +232,10 @@ class RenderSettings(object):
|
|||
|
||||
def _set_renderman_settings(self, width, height, aov_separator):
|
||||
"""Sets settings for Renderman"""
|
||||
# Not all hosts can import this module.
|
||||
from maya import cmds # noqa: F401
|
||||
import maya.mel as mel # noqa: F401
|
||||
|
||||
rman_render_presets = (
|
||||
self._project_settings
|
||||
["maya"]
|
||||
|
|
@ -285,6 +297,11 @@ class RenderSettings(object):
|
|||
def _set_vray_settings(self, aov_separator, width, height):
|
||||
# type: (str, int, int) -> None
|
||||
"""Sets important settings for Vray."""
|
||||
# Not all hosts can import this module.
|
||||
from maya import cmds # noqa: F401
|
||||
import maya.mel as mel # noqa: F401
|
||||
|
||||
|
||||
settings = cmds.ls(type="VRaySettingsNode")
|
||||
node = settings[0] if settings else cmds.createNode("VRaySettingsNode")
|
||||
render_settings = self._project_settings["maya"]["RenderSettings"]
|
||||
|
|
@ -357,6 +374,10 @@ class RenderSettings(object):
|
|||
|
||||
@staticmethod
|
||||
def _set_global_output_settings():
|
||||
# Not all hosts can import this module.
|
||||
from maya import cmds # noqa: F401
|
||||
import maya.mel as mel # noqa: F401
|
||||
|
||||
# enable animation
|
||||
cmds.setAttr("defaultRenderGlobals.outFormatControl", 0)
|
||||
cmds.setAttr("defaultRenderGlobals.animation", 1)
|
||||
|
|
@ -364,6 +385,10 @@ class RenderSettings(object):
|
|||
cmds.setAttr("defaultRenderGlobals.extensionPadding", 4)
|
||||
|
||||
def _additional_attribs_setter(self, additional_attribs):
|
||||
# Not all hosts can import this module.
|
||||
from maya import cmds # noqa: F401
|
||||
import maya.mel as mel # noqa: F401
|
||||
|
||||
for item in additional_attribs:
|
||||
attribute, value = item
|
||||
attribute = str(attribute) # ensure str conversion from settings
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import six
|
|||
from maya import cmds
|
||||
from maya.app.renderSetup.model import renderSetup
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.lib import BoolDef, Logger
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.pipeline import (
|
||||
|
|
@ -449,14 +450,16 @@ class RenderlayerCreator(NewCreator, MayaCreatorBase):
|
|||
# this instance will not have the `instance_node` data yet
|
||||
# until it's been saved/persisted at least once.
|
||||
project_name = self.create_context.get_current_project_name()
|
||||
|
||||
asset_name = self.create_context.get_current_asset_name()
|
||||
instance_data = {
|
||||
"asset": self.create_context.get_current_asset_name(),
|
||||
"task": self.create_context.get_current_task_name(),
|
||||
"variant": layer.name(),
|
||||
}
|
||||
asset_doc = get_asset_by_name(project_name,
|
||||
instance_data["asset"])
|
||||
if AYON_SERVER_ENABLED:
|
||||
instance_data["folderPath"] = asset_name
|
||||
else:
|
||||
instance_data["asset"] = asset_name
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
layer.name(),
|
||||
instance_data["task"],
|
||||
|
|
|
|||
|
|
@ -45,10 +45,14 @@ class CreateMultishotLayout(plugin.MayaCreator):
|
|||
above is done.
|
||||
"""
|
||||
|
||||
current_folder = get_folder_by_name(
|
||||
project_name=get_current_project_name(),
|
||||
folder_name=get_current_asset_name(),
|
||||
)
|
||||
project_name = get_current_project_name()
|
||||
folder_path = get_current_asset_name()
|
||||
if "/" in folder_path:
|
||||
current_folder = get_folder_by_path(project_name, folder_path)
|
||||
else:
|
||||
current_folder = get_folder_by_name(
|
||||
project_name, folder_name=folder_path
|
||||
)
|
||||
|
||||
current_path_parts = current_folder["path"].split("/")
|
||||
|
||||
|
|
@ -154,7 +158,7 @@ class CreateMultishotLayout(plugin.MayaCreator):
|
|||
# Create layout instance by the layout creator
|
||||
|
||||
instance_data = {
|
||||
"asset": shot["name"],
|
||||
"folderPath": shot["path"],
|
||||
"variant": layout_creator.get_default_variant()
|
||||
}
|
||||
if layout_task:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import json
|
|||
|
||||
from maya import cmds
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.hosts.maya.api import (
|
||||
lib,
|
||||
plugin
|
||||
|
|
@ -43,7 +44,11 @@ class CreateReview(plugin.MayaCreator):
|
|||
members = cmds.ls(selection=True)
|
||||
|
||||
project_name = self.project_name
|
||||
asset_doc = get_asset_by_name(project_name, instance_data["asset"])
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = instance_data["folderPath"]
|
||||
else:
|
||||
asset_name = instance_data["asset"]
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
task_name = instance_data["task"]
|
||||
preset = lib.get_capture_preset(
|
||||
task_name,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class CreateUnrealSkeletalMesh(plugin.MayaCreator):
|
|||
# We reorganize the geometry that was originally added into the
|
||||
# set into either 'joints_SET' or 'geometry_SET' based on the
|
||||
# joint_hints from project settings
|
||||
members = cmds.sets(instance_node, query=True)
|
||||
members = cmds.sets(instance_node, query=True) or []
|
||||
cmds.sets(clear=instance_node)
|
||||
|
||||
geometry_set = cmds.sets(name="geometry_SET", empty=True)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Creator plugin for creating workfiles."""
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.pipeline import CreatedInstance, AutoCreator
|
||||
from openpype.client import get_asset_by_name
|
||||
from openpype.client import get_asset_by_name, get_asset_name_identifier
|
||||
from openpype.hosts.maya.api import plugin
|
||||
from maya import cmds
|
||||
|
||||
|
|
@ -29,16 +30,27 @@ class CreateWorkfile(plugin.MayaCreatorBase, AutoCreator):
|
|||
task_name = self.create_context.get_current_task_name()
|
||||
host_name = self.create_context.host_name
|
||||
|
||||
if current_instance is None:
|
||||
current_instance_asset = None
|
||||
elif AYON_SERVER_ENABLED:
|
||||
current_instance_asset = current_instance["folderPath"]
|
||||
else:
|
||||
current_instance_asset = current_instance["asset"]
|
||||
|
||||
if current_instance is None:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
variant, task_name, asset_doc, project_name, host_name
|
||||
)
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
"variant": variant
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
|
||||
data.update(
|
||||
self.get_dynamic_data(
|
||||
variant, task_name, asset_doc,
|
||||
|
|
@ -50,15 +62,20 @@ class CreateWorkfile(plugin.MayaCreatorBase, AutoCreator):
|
|||
)
|
||||
self._add_instance_to_context(current_instance)
|
||||
elif (
|
||||
current_instance["asset"] != asset_name
|
||||
or current_instance["task"] != task_name
|
||||
current_instance_asset != asset_name
|
||||
or current_instance["task"] != task_name
|
||||
):
|
||||
# Update instance context if is not the same
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
variant, task_name, asset_doc, project_name, host_name
|
||||
)
|
||||
current_instance["asset"] = asset_name
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
|
||||
if AYON_SERVER_ENABLED:
|
||||
current_instance["folderPath"] = asset_name
|
||||
else:
|
||||
current_instance["asset"] = asset_name
|
||||
current_instance["task"] = task_name
|
||||
current_instance["subset"] = subset_name
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from maya import cmds, mel
|
|||
import pyblish.api
|
||||
|
||||
from openpype.client import get_subset_by_name
|
||||
from openpype.pipeline import legacy_io, KnownPublishError
|
||||
from openpype.pipeline import KnownPublishError
|
||||
from openpype.hosts.maya.api import lib
|
||||
|
||||
|
||||
|
|
@ -116,10 +116,10 @@ class CollectReview(pyblish.api.InstancePlugin):
|
|||
instance.data['remove'] = True
|
||||
|
||||
else:
|
||||
task = legacy_io.Session["AVALON_TASK"]
|
||||
legacy_subset_name = task + 'Review'
|
||||
project_name = instance.context.data["projectName"]
|
||||
asset_doc = instance.context.data['assetEntity']
|
||||
project_name = legacy_io.active_project()
|
||||
task = instance.context.data["task"]
|
||||
legacy_subset_name = task + 'Review'
|
||||
subset_doc = get_subset_by_name(
|
||||
project_name,
|
||||
legacy_subset_name,
|
||||
|
|
|
|||
|
|
@ -13,16 +13,6 @@ from openpype.hosts.maya.api.lib import (
|
|||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def renamed(original_name, renamed_name):
|
||||
# type: (str, str) -> None
|
||||
try:
|
||||
cmds.rename(original_name, renamed_name)
|
||||
yield
|
||||
finally:
|
||||
cmds.rename(renamed_name, original_name)
|
||||
|
||||
|
||||
class ExtractUnrealSkeletalMeshAbc(publish.Extractor):
|
||||
"""Extract Unreal Skeletal Mesh as FBX from Maya. """
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ class ExtractUnrealSkeletalMeshFbx(publish.Extractor):
|
|||
original_parent = to_extract[0].split("|")[1]
|
||||
|
||||
parent_node = instance.data.get("asset")
|
||||
# this needs to be done for AYON
|
||||
# WARNING: since AYON supports duplicity of asset names,
|
||||
# this needs to be refactored throughout the pipeline.
|
||||
parent_node = parent_node.split("/")[-1]
|
||||
|
||||
renamed_to_extract = []
|
||||
for node in to_extract:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import pyblish.api
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
import openpype.hosts.maya.api.action
|
||||
from openpype.pipeline.publish import (
|
||||
RepairAction,
|
||||
|
|
@ -66,12 +67,16 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin,
|
|||
def repair(cls, instance):
|
||||
context_asset = cls.get_context_asset(instance)
|
||||
instance_node = instance.data["instance_node"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name_attr = "folderPath"
|
||||
else:
|
||||
asset_name_attr = "asset"
|
||||
cmds.setAttr(
|
||||
"{}.asset".format(instance_node),
|
||||
"{}.{}".format(instance_node, asset_name_attr),
|
||||
context_asset,
|
||||
type="string"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_context_asset(instance):
|
||||
return instance.context.data["assetEntity"]["name"]
|
||||
return instance.context.data["asset"]
|
||||
|
|
|
|||
|
|
@ -67,13 +67,15 @@ class ValidateModelName(pyblish.api.InstancePlugin,
|
|||
regex = cls.top_level_regex
|
||||
r = re.compile(regex)
|
||||
m = r.match(top_group)
|
||||
project_name = instance.context.data["projectName"]
|
||||
current_asset_name = instance.context.data["asset"]
|
||||
if m is None:
|
||||
cls.log.error("invalid name on: {}".format(top_group))
|
||||
cls.log.error("name doesn't match regex {}".format(regex))
|
||||
invalid.append(top_group)
|
||||
else:
|
||||
if "asset" in r.groupindex:
|
||||
if m.group("asset") != legacy_io.Session["AVALON_ASSET"]:
|
||||
if m.group("asset") != current_asset_name:
|
||||
cls.log.error("Invalid asset name in top level group.")
|
||||
return top_group
|
||||
if "subset" in r.groupindex:
|
||||
|
|
@ -81,7 +83,7 @@ class ValidateModelName(pyblish.api.InstancePlugin,
|
|||
cls.log.error("Invalid subset name in top level group.")
|
||||
return top_group
|
||||
if "project" in r.groupindex:
|
||||
if m.group("project") != legacy_io.Session["AVALON_PROJECT"]:
|
||||
if m.group("project") != project_name:
|
||||
cls.log.error("Invalid project name in top level group.")
|
||||
return top_group
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class ValidateShaderName(pyblish.api.InstancePlugin,
|
|||
|
||||
descendants = cmds.ls(descendants, noIntermediate=True, long=True)
|
||||
shapes = cmds.ls(descendants, type=["nurbsSurface", "mesh"], long=True)
|
||||
asset_name = instance.data.get("asset", None)
|
||||
asset_name = instance.data.get("asset")
|
||||
|
||||
# Check the number of connected shadingEngines per shape
|
||||
regex_compile = re.compile(cls.regex)
|
||||
|
|
|
|||
|
|
@ -102,7 +102,8 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin,
|
|||
|
||||
cl_r = re.compile(regex_collision)
|
||||
|
||||
mesh_name = "{}{}".format(instance.data["asset"],
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
mesh_name = "{}{}".format(asset_name,
|
||||
instance.data.get("variant", []))
|
||||
|
||||
for obj in collision_set:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from collections import defaultdict
|
|||
|
||||
import maya.cmds as cmds
|
||||
|
||||
from openpype.client import get_assets
|
||||
from openpype.client import get_assets, get_asset_name_identifier
|
||||
from openpype.pipeline import (
|
||||
remove_container,
|
||||
registered_host,
|
||||
|
|
@ -128,7 +128,8 @@ def create_items_from_nodes(nodes):
|
|||
|
||||
project_name = get_current_project_name()
|
||||
asset_ids = set(id_hashes.keys())
|
||||
asset_docs = get_assets(project_name, asset_ids, fields=["name"])
|
||||
fields = {"_id", "name", "data.parents"}
|
||||
asset_docs = get_assets(project_name, asset_ids, fields=fields)
|
||||
asset_docs_by_id = {
|
||||
str(asset_doc["_id"]): asset_doc
|
||||
for asset_doc in asset_docs
|
||||
|
|
@ -156,8 +157,9 @@ def create_items_from_nodes(nodes):
|
|||
namespace = get_namespace_from_node(node)
|
||||
namespaces.add(namespace)
|
||||
|
||||
label = get_asset_name_identifier(asset_doc)
|
||||
asset_view_items.append({
|
||||
"label": asset_doc["name"],
|
||||
"label": label,
|
||||
"asset": asset_doc,
|
||||
"looks": looks,
|
||||
"namespaces": namespaces
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from collections import defaultdict
|
|||
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
from openpype.client import get_asset_name_identifier
|
||||
from openpype.tools.utils.models import TreeModel
|
||||
from openpype.tools.utils.lib import (
|
||||
preserve_expanded_rows,
|
||||
|
|
@ -126,7 +127,7 @@ class AssetOutliner(QtWidgets.QWidget):
|
|||
asset_namespaces = defaultdict(set)
|
||||
for item in items:
|
||||
asset_id = str(item["asset"]["_id"])
|
||||
asset_name = item["asset"]["name"]
|
||||
asset_name = get_asset_name_identifier(item["asset"])
|
||||
asset_namespaces[asset_name].add(item.get("namespace"))
|
||||
|
||||
if asset_name in assets:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from collections import OrderedDict
|
|||
import nuke
|
||||
from qtpy import QtCore, QtWidgets
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import (
|
||||
get_project,
|
||||
get_asset_by_name,
|
||||
|
|
@ -1107,7 +1108,9 @@ def format_anatomy(data):
|
|||
Return:
|
||||
path (str)
|
||||
'''
|
||||
anatomy = Anatomy()
|
||||
|
||||
project_name = get_current_project_name()
|
||||
anatomy = Anatomy(project_name)
|
||||
log.debug("__ anatomy.templates: {}".format(anatomy.templates))
|
||||
|
||||
padding = None
|
||||
|
|
@ -1125,8 +1128,10 @@ def format_anatomy(data):
|
|||
file = script_name()
|
||||
data["version"] = get_version_from_path(file)
|
||||
|
||||
project_name = anatomy.project_name
|
||||
asset_name = data["asset"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = data["folderPath"]
|
||||
else:
|
||||
asset_name = data["asset"]
|
||||
task_name = data["task"]
|
||||
host_name = get_current_host_name()
|
||||
context_data = get_template_data_with_names(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import re
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
import openpype.hosts.photoshop.api as api
|
||||
from openpype.client import get_asset_by_name
|
||||
from openpype.lib import prepare_template_data
|
||||
|
|
@ -43,6 +44,14 @@ class PSAutoCreator(AutoCreator):
|
|||
asset_name = context.get_current_asset_name()
|
||||
task_name = context.get_current_task_name()
|
||||
host_name = context.host_name
|
||||
|
||||
if existing_instance is None:
|
||||
existing_instance_asset = None
|
||||
elif AYON_SERVER_ENABLED:
|
||||
existing_instance_asset = existing_instance["folderPath"]
|
||||
else:
|
||||
existing_instance_asset = existing_instance["asset"]
|
||||
|
||||
if existing_instance is None:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
|
|
@ -50,10 +59,13 @@ class PSAutoCreator(AutoCreator):
|
|||
project_name, host_name
|
||||
)
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
"variant": self.default_variant
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
data.update(self.get_dynamic_data(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name, None
|
||||
|
|
@ -70,7 +82,7 @@ class PSAutoCreator(AutoCreator):
|
|||
new_instance.data_to_store())
|
||||
|
||||
elif (
|
||||
existing_instance["asset"] != asset_name
|
||||
existing_instance_asset != asset_name
|
||||
or existing_instance["task"] != task_name
|
||||
):
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
|
@ -78,7 +90,10 @@ class PSAutoCreator(AutoCreator):
|
|||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
)
|
||||
existing_instance["asset"] = asset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_instance["folderPath"] = asset_name
|
||||
else:
|
||||
existing_instance["asset"] = asset_name
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from openpype.pipeline import CreatedInstance
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.lib import BoolDef
|
||||
import openpype.hosts.photoshop.api as api
|
||||
from openpype.hosts.photoshop.lib import PSAutoCreator, clean_subset_name
|
||||
|
|
@ -37,6 +38,13 @@ class AutoImageCreator(PSAutoCreator):
|
|||
host_name = context.host_name
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
||||
if existing_instance is None:
|
||||
existing_instance_asset = None
|
||||
elif AYON_SERVER_ENABLED:
|
||||
existing_instance_asset = existing_instance["folderPath"]
|
||||
else:
|
||||
existing_instance_asset = existing_instance["asset"]
|
||||
|
||||
if existing_instance is None:
|
||||
subset_name = self.get_subset_name(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
|
|
@ -44,9 +52,12 @@ class AutoImageCreator(PSAutoCreator):
|
|||
)
|
||||
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
|
||||
if not self.active_on_create:
|
||||
data["active"] = False
|
||||
|
|
@ -62,15 +73,17 @@ class AutoImageCreator(PSAutoCreator):
|
|||
new_instance.data_to_store())
|
||||
|
||||
elif ( # existing instance from different context
|
||||
existing_instance["asset"] != asset_name
|
||||
existing_instance_asset != asset_name
|
||||
or existing_instance["task"] != task_name
|
||||
):
|
||||
subset_name = self.get_subset_name(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
)
|
||||
|
||||
existing_instance["asset"] = asset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_instance["folderPath"] = asset_name
|
||||
else:
|
||||
existing_instance["asset"] = asset_name
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import pyblish.api
|
||||
|
||||
from openpype.client import get_asset_name_identifier
|
||||
from openpype.hosts.photoshop import api as photoshop
|
||||
from openpype.pipeline.create import get_subset_name
|
||||
|
||||
|
|
@ -27,7 +28,7 @@ class CollectAutoImage(pyblish.api.ContextPlugin):
|
|||
task_name = context.data["task"]
|
||||
host_name = context.data["hostName"]
|
||||
asset_doc = context.data["assetEntity"]
|
||||
asset_name = asset_doc["name"]
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
|
||||
auto_creator = proj_settings.get(
|
||||
"photoshop", {}).get(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ Provides:
|
|||
"""
|
||||
import pyblish.api
|
||||
|
||||
from openpype.client import get_asset_name_identifier
|
||||
from openpype.hosts.photoshop import api as photoshop
|
||||
from openpype.pipeline.create import get_subset_name
|
||||
|
||||
|
|
@ -65,7 +66,8 @@ class CollectAutoReview(pyblish.api.ContextPlugin):
|
|||
task_name = context.data["task"]
|
||||
host_name = context.data["hostName"]
|
||||
asset_doc = context.data["assetEntity"]
|
||||
asset_name = asset_doc["name"]
|
||||
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
|
||||
subset_name = get_subset_name(
|
||||
family,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
|
||||
from openpype.client import get_asset_name_identifier
|
||||
from openpype.hosts.photoshop import api as photoshop
|
||||
from openpype.pipeline.create import get_subset_name
|
||||
|
||||
|
|
@ -69,8 +70,8 @@ class CollectAutoWorkfile(pyblish.api.ContextPlugin):
|
|||
task_name = context.data["task"]
|
||||
host_name = context.data["hostName"]
|
||||
asset_doc = context.data["assetEntity"]
|
||||
asset_name = asset_doc["name"]
|
||||
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
subset_name = get_subset_name(
|
||||
family,
|
||||
variant,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import re
|
||||
import uuid
|
||||
import copy
|
||||
|
||||
import qargparse
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
from openpype.settings import get_current_project_settings
|
||||
from openpype.pipeline.context_tools import get_current_project_asset
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
LoaderPlugin,
|
||||
|
|
@ -18,7 +19,7 @@ from .menu import load_stylesheet
|
|||
class CreatorWidget(QtWidgets.QDialog):
|
||||
|
||||
# output items
|
||||
items = dict()
|
||||
items = {}
|
||||
|
||||
def __init__(self, name, info, ui_inputs, parent=None):
|
||||
super(CreatorWidget, self).__init__(parent)
|
||||
|
|
@ -100,7 +101,7 @@ class CreatorWidget(QtWidgets.QDialog):
|
|||
self.close()
|
||||
|
||||
def value(self, data, new_data=None):
|
||||
new_data = new_data or dict()
|
||||
new_data = new_data or {}
|
||||
for k, v in data.items():
|
||||
new_data[k] = {
|
||||
"target": None,
|
||||
|
|
@ -289,7 +290,7 @@ class Spacer(QtWidgets.QWidget):
|
|||
class ClipLoader:
|
||||
|
||||
active_bin = None
|
||||
data = dict()
|
||||
data = {}
|
||||
|
||||
def __init__(self, loader_obj, context, **options):
|
||||
""" Initialize object
|
||||
|
|
@ -386,8 +387,8 @@ class ClipLoader:
|
|||
joint `data` key with asset.data dict into the representation
|
||||
|
||||
"""
|
||||
asset_name = self.context["representation"]["context"]["asset"]
|
||||
self.data["assetData"] = get_current_project_asset(asset_name)["data"]
|
||||
|
||||
self.data["assetData"] = copy.deepcopy(self.context["asset"]["data"])
|
||||
|
||||
def load(self, files):
|
||||
"""Load clip into timeline
|
||||
|
|
@ -587,8 +588,8 @@ class PublishClip:
|
|||
Returns:
|
||||
hiero.core.TrackItem: hiero track item object with openpype tag
|
||||
"""
|
||||
vertical_clip_match = dict()
|
||||
tag_data = dict()
|
||||
vertical_clip_match = {}
|
||||
tag_data = {}
|
||||
types = {
|
||||
"shot": "shot",
|
||||
"folder": "folder",
|
||||
|
|
@ -664,15 +665,23 @@ class PublishClip:
|
|||
new_name = self.tag_data.pop("newClipName")
|
||||
|
||||
if self.rename:
|
||||
self.tag_data["asset"] = new_name
|
||||
self.tag_data["asset_name"] = new_name
|
||||
else:
|
||||
self.tag_data["asset"] = self.ti_name
|
||||
self.tag_data["asset_name"] = self.ti_name
|
||||
|
||||
# AYON unique identifier
|
||||
folder_path = "/{}/{}".format(
|
||||
self.tag_data["hierarchy"],
|
||||
self.tag_data["asset_name"]
|
||||
)
|
||||
self.tag_data["folder_path"] = folder_path
|
||||
|
||||
# create new name for track item
|
||||
if not lib.pype_marker_workflow:
|
||||
# create compound clip workflow
|
||||
lib.create_compound_clip(
|
||||
self.timeline_item_data,
|
||||
self.tag_data["asset"],
|
||||
self.tag_data["asset_name"],
|
||||
self.mp_folder
|
||||
)
|
||||
|
||||
|
|
@ -764,7 +773,7 @@ class PublishClip:
|
|||
# increasing steps by index of rename iteration
|
||||
self.count_steps *= self.rename_index
|
||||
|
||||
hierarchy_formatting_data = dict()
|
||||
hierarchy_formatting_data = {}
|
||||
_data = self.timeline_item_default_data.copy()
|
||||
if self.ui_inputs:
|
||||
# adding tag metadata from ui
|
||||
|
|
@ -853,8 +862,7 @@ class PublishClip:
|
|||
"parents": self.parents,
|
||||
"hierarchyData": hierarchy_formatting_data,
|
||||
"subset": self.subset,
|
||||
"family": self.subset_family,
|
||||
"families": ["clip"]
|
||||
"family": self.subset_family
|
||||
}
|
||||
|
||||
def _convert_to_entity(self, key):
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class ExtractWorkfile(publish.Extractor):
|
|||
|
||||
resolve_workfile_ext = ".drp"
|
||||
drp_file_name = name + resolve_workfile_ext
|
||||
|
||||
drp_file_path = os.path.normpath(
|
||||
os.path.join(staging_dir, drp_file_name))
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from openpype.hosts.resolve.api.lib import (
|
|||
get_publish_attribute,
|
||||
get_otio_clip_instance_data,
|
||||
)
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
|
||||
|
||||
class PrecollectInstances(pyblish.api.ContextPlugin):
|
||||
|
|
@ -29,7 +30,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
for timeline_item_data in selected_timeline_items:
|
||||
|
||||
data = dict()
|
||||
data = {}
|
||||
timeline_item = timeline_item_data["clip"]["item"]
|
||||
|
||||
# get pype tag data
|
||||
|
|
@ -60,24 +61,24 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
if k not in ("id", "applieswhole", "label")
|
||||
})
|
||||
|
||||
asset = tag_data["asset"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset = tag_data["folder_path"]
|
||||
else:
|
||||
asset = tag_data["asset_name"]
|
||||
|
||||
subset = tag_data["subset"]
|
||||
|
||||
# insert family into families
|
||||
family = tag_data["family"]
|
||||
families = [str(f) for f in tag_data["families"]]
|
||||
families.insert(0, str(family))
|
||||
|
||||
data.update({
|
||||
"name": "{} {} {}".format(asset, subset, families),
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"label": "{} {}".format(asset, subset),
|
||||
"asset": asset,
|
||||
"item": timeline_item,
|
||||
"families": families,
|
||||
"publish": get_publish_attribute(timeline_item),
|
||||
"fps": context.data["fps"],
|
||||
"handleStart": handle_start,
|
||||
"handleEnd": handle_end,
|
||||
"newAssetPublishing": True
|
||||
"newAssetPublishing": True,
|
||||
"families": ["clip"],
|
||||
})
|
||||
|
||||
# otio clip data
|
||||
|
|
@ -135,7 +136,8 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
family = "shot"
|
||||
|
||||
data.update({
|
||||
"name": "{} {} {}".format(asset, subset, family),
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"label": "{} {}".format(asset, subset),
|
||||
"subset": subset,
|
||||
"asset": asset,
|
||||
"family": family,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import pyblish.api
|
||||
from pprint import pformat
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.pipeline import get_current_asset_name
|
||||
|
||||
from openpype.hosts.resolve import api as rapi
|
||||
from openpype.hosts.resolve.otio import davinci_export
|
||||
|
||||
|
|
@ -13,9 +15,12 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
order = pyblish.api.CollectorOrder - 0.5
|
||||
|
||||
def process(self, context):
|
||||
current_asset_name = asset_name = get_current_asset_name()
|
||||
|
||||
asset = get_current_asset_name()
|
||||
subset = "workfile"
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = current_asset_name.split("/")[-1]
|
||||
|
||||
subset = "workfileMain"
|
||||
project = rapi.get_current_project()
|
||||
fps = project.GetSetting("timelineFrameRate")
|
||||
video_tracks = rapi.get_video_track_names()
|
||||
|
|
@ -24,9 +29,10 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
otio_timeline = davinci_export.create_otio_timeline(project)
|
||||
|
||||
instance_data = {
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"asset": asset,
|
||||
"subset": "{}{}".format(asset, subset.capitalize()),
|
||||
"name": "{}_{}".format(asset_name, subset),
|
||||
"label": "{} {}".format(current_asset_name, subset),
|
||||
"asset": current_asset_name,
|
||||
"subset": subset,
|
||||
"item": project,
|
||||
"family": "workfile",
|
||||
"families": []
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@ class CollectHarmonyScenes(pyblish.api.InstancePlugin):
|
|||
# updating hierarchy data
|
||||
anatomy_data_new.update({
|
||||
"asset": asset_data["name"],
|
||||
"folder": {
|
||||
"name": asset_data["name"],
|
||||
},
|
||||
"task": {
|
||||
"name": task,
|
||||
"type": task_type,
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ class CollectHarmonyZips(pyblish.api.InstancePlugin):
|
|||
anatomy_data_new.update(
|
||||
{
|
||||
"asset": asset_data["name"],
|
||||
"folder": {
|
||||
"name": asset_data["name"],
|
||||
},
|
||||
"task": {
|
||||
"name": task,
|
||||
"type": task_type,
|
||||
|
|
|
|||
|
|
@ -170,7 +170,8 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
|
||||
parent = substance_painter.ui.get_main_window()
|
||||
|
||||
menu = QtWidgets.QMenu("OpenPype")
|
||||
tab_menu_label = os.environ.get("AVALON_LABEL") or "AYON"
|
||||
menu = QtWidgets.QMenu(tab_menu_label)
|
||||
|
||||
action = menu.addAction("Create...")
|
||||
action.triggered.connect(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Creator plugin for creating workfiles."""
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.pipeline import CreatedInstance, AutoCreator
|
||||
from openpype.client import get_asset_by_name
|
||||
|
||||
|
|
@ -41,6 +42,13 @@ class CreateWorkfile(AutoCreator):
|
|||
if instance.creator_identifier == self.identifier
|
||||
), None)
|
||||
|
||||
if current_instance is None:
|
||||
current_instance_asset = None
|
||||
elif AYON_SERVER_ENABLED:
|
||||
current_instance_asset = current_instance["folderPath"]
|
||||
else:
|
||||
current_instance_asset = current_instance["asset"]
|
||||
|
||||
if current_instance is None:
|
||||
self.log.info("Auto-creating workfile instance...")
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
|
@ -48,22 +56,28 @@ class CreateWorkfile(AutoCreator):
|
|||
variant, task_name, asset_doc, project_name, host_name
|
||||
)
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
"variant": variant
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
current_instance = self.create_instance_in_context(subset_name,
|
||||
data)
|
||||
elif (
|
||||
current_instance["asset"] != asset_name
|
||||
or current_instance["task"] != task_name
|
||||
current_instance_asset != asset_name
|
||||
or current_instance["task"] != task_name
|
||||
):
|
||||
# Update instance context if is not the same
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
variant, task_name, asset_doc, project_name, host_name
|
||||
)
|
||||
current_instance["asset"] = asset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
current_instance["folderPath"] = asset_name
|
||||
else:
|
||||
current_instance["asset"] = asset_name
|
||||
current_instance["task"] = task_name
|
||||
current_instance["subset"] = subset_name
|
||||
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@ class ShotMetadataSolver:
|
|||
try:
|
||||
# format to new shot name
|
||||
return shot_rename_template.format(**data)
|
||||
except KeyError as _E:
|
||||
except KeyError as _error:
|
||||
raise CreatorError((
|
||||
"Make sure all keys in settings are correct:: \n\n"
|
||||
f"From template string {shot_rename_template} > "
|
||||
f"`{_E}` has no equivalent in \n"
|
||||
f"`{_error}` has no equivalent in \n"
|
||||
f"{list(data.keys())} input formatting keys!"
|
||||
))
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ class ShotMetadataSolver:
|
|||
"at your project settings..."
|
||||
))
|
||||
|
||||
# QUESTION:how to refactory `match[-1]` to some better way?
|
||||
# QUESTION:how to refactor `match[-1]` to some better way?
|
||||
output_data[token_key] = match[-1]
|
||||
|
||||
return output_data
|
||||
|
|
@ -130,10 +130,10 @@ class ShotMetadataSolver:
|
|||
parent_token["name"]: parent_token["value"].format(**data)
|
||||
for parent_token in hierarchy_parents
|
||||
}
|
||||
except KeyError as _E:
|
||||
except KeyError as _error:
|
||||
raise CreatorError((
|
||||
"Make sure all keys in settings are correct : \n"
|
||||
f"`{_E}` has no equivalent in \n{list(data.keys())}"
|
||||
f"`{_error}` has no equivalent in \n{list(data.keys())}"
|
||||
))
|
||||
|
||||
_parent_tokens_type = {
|
||||
|
|
@ -147,10 +147,10 @@ class ShotMetadataSolver:
|
|||
try:
|
||||
parent_name = _parent.format(
|
||||
**_parent_tokens_formatting_data)
|
||||
except KeyError as _E:
|
||||
except KeyError as _error:
|
||||
raise CreatorError((
|
||||
"Make sure all keys in settings are correct : \n\n"
|
||||
f"`{_E}` from template string "
|
||||
f"`{_error}` from template string "
|
||||
f"{shot_hierarchy['parents_path']}, "
|
||||
f" has no equivalent in \n"
|
||||
f"{list(_parent_tokens_formatting_data.keys())} parents"
|
||||
|
|
@ -319,8 +319,16 @@ class ShotMetadataSolver:
|
|||
tasks = self._generate_tasks_from_settings(
|
||||
project_doc)
|
||||
|
||||
# generate hierarchy path from parents
|
||||
hierarchy_path = self._create_hierarchy_path(parents)
|
||||
if hierarchy_path:
|
||||
folder_path = f"/{hierarchy_path}/{shot_name}"
|
||||
else:
|
||||
folder_path = f"/{shot_name}"
|
||||
|
||||
return shot_name, {
|
||||
"hierarchy": self._create_hierarchy_path(parents),
|
||||
"hierarchy": hierarchy_path,
|
||||
"folderPath": folder_path,
|
||||
"parents": parents,
|
||||
"tasks": tasks
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import (
|
||||
get_assets,
|
||||
get_subsets,
|
||||
get_last_versions,
|
||||
get_asset_name_identifier,
|
||||
)
|
||||
from openpype.lib.attribute_definitions import (
|
||||
FileDef,
|
||||
|
|
@ -114,7 +116,10 @@ class SettingsCreator(TrayPublishCreator):
|
|||
|
||||
# Fill 'version_to_use' if version control is enabled
|
||||
if self.allow_version_control:
|
||||
asset_name = data["asset"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = data["folderPath"]
|
||||
else:
|
||||
asset_name = data["asset"]
|
||||
subset_docs_by_asset_id = self._prepare_next_versions(
|
||||
[asset_name], [subset_name])
|
||||
version = subset_docs_by_asset_id[asset_name].get(subset_name)
|
||||
|
|
@ -162,10 +167,10 @@ class SettingsCreator(TrayPublishCreator):
|
|||
asset_docs = get_assets(
|
||||
self.project_name,
|
||||
asset_names=asset_names,
|
||||
fields=["_id", "name"]
|
||||
fields=["_id", "name", "data.parents"]
|
||||
)
|
||||
asset_names_by_id = {
|
||||
asset_doc["_id"]: asset_doc["name"]
|
||||
asset_doc["_id"]: get_asset_name_identifier(asset_doc)
|
||||
for asset_doc in asset_docs
|
||||
}
|
||||
subset_docs = list(get_subsets(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ production type `ociolook`. All files are published as representation.
|
|||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import get_asset_by_name
|
||||
from openpype.lib.attribute_definitions import (
|
||||
FileDef, EnumDef, TextDef, UISeparatorDef
|
||||
|
|
@ -54,8 +55,12 @@ This creator publishes color space look file (LUT).
|
|||
# this should never happen
|
||||
raise CreatorError("Missing files from representation")
|
||||
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = instance_data["folderPath"]
|
||||
else:
|
||||
asset_name = instance_data["asset"]
|
||||
asset_doc = get_asset_by_name(
|
||||
self.project_name, instance_data["asset"])
|
||||
self.project_name, asset_name)
|
||||
|
||||
subset_name = self.get_subset_name(
|
||||
variant=instance_data["variant"],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
from copy import deepcopy
|
||||
import opentimelineio as otio
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import (
|
||||
get_asset_by_name,
|
||||
get_project
|
||||
|
|
@ -101,14 +102,23 @@ class EditorialShotInstanceCreator(EditorialClipInstanceCreatorBase):
|
|||
label = "Editorial Shot"
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
attr_defs = [
|
||||
TextDef(
|
||||
"asset_name",
|
||||
label="Asset name",
|
||||
instance_attributes = []
|
||||
if AYON_SERVER_ENABLED:
|
||||
instance_attributes.append(
|
||||
TextDef(
|
||||
"folderPath",
|
||||
label="Folder path"
|
||||
)
|
||||
)
|
||||
]
|
||||
attr_defs.extend(CLIP_ATTR_DEFS)
|
||||
return attr_defs
|
||||
else:
|
||||
instance_attributes.append(
|
||||
TextDef(
|
||||
"shotName",
|
||||
label="Shot name"
|
||||
)
|
||||
)
|
||||
instance_attributes.extend(CLIP_ATTR_DEFS)
|
||||
return instance_attributes
|
||||
|
||||
|
||||
class EditorialPlateInstanceCreator(EditorialClipInstanceCreatorBase):
|
||||
|
|
@ -214,8 +224,11 @@ or updating already created. Publishing will create OTIO file.
|
|||
i["family"] for i in self._creator_settings["family_presets"]
|
||||
]
|
||||
}
|
||||
# Create otio editorial instance
|
||||
asset_name = instance_data["asset"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = instance_data["folderPath"]
|
||||
else:
|
||||
asset_name = instance_data["asset"]
|
||||
|
||||
asset_doc = get_asset_by_name(self.project_name, asset_name)
|
||||
|
||||
if pre_create_data["fps"] == "from_selection":
|
||||
|
|
@ -595,19 +608,23 @@ or updating already created. Publishing will create OTIO file.
|
|||
Returns:
|
||||
str: label string
|
||||
"""
|
||||
shot_name = instance_data["shotName"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = instance_data["creator_attributes"]["folderPath"]
|
||||
else:
|
||||
asset_name = instance_data["creator_attributes"]["shotName"]
|
||||
|
||||
variant_name = instance_data["variant"]
|
||||
family = preset["family"]
|
||||
|
||||
# get variant name from preset or from inharitance
|
||||
# get variant name from preset or from inheritance
|
||||
_variant_name = preset.get("variant") or variant_name
|
||||
|
||||
# subset name
|
||||
subset_name = "{}{}".format(
|
||||
family, _variant_name.capitalize()
|
||||
)
|
||||
label = "{}_{}".format(
|
||||
shot_name,
|
||||
label = "{} {}".format(
|
||||
asset_name,
|
||||
subset_name
|
||||
)
|
||||
|
||||
|
|
@ -666,7 +683,10 @@ or updating already created. Publishing will create OTIO file.
|
|||
}
|
||||
)
|
||||
|
||||
self._validate_name_uniqueness(shot_name)
|
||||
# It should be validated only in openpype since we are supporting
|
||||
# publishing to AYON with folder path and uniqueness is not an issue
|
||||
if not AYON_SERVER_ENABLED:
|
||||
self._validate_name_uniqueness(shot_name)
|
||||
|
||||
timing_data = self._get_timing_data(
|
||||
otio_clip,
|
||||
|
|
@ -677,35 +697,43 @@ or updating already created. Publishing will create OTIO file.
|
|||
|
||||
# create creator attributes
|
||||
creator_attributes = {
|
||||
"asset_name": shot_name,
|
||||
"Parent hierarchy path": shot_metadata["hierarchy"],
|
||||
|
||||
"workfile_start_frame": workfile_start_frame,
|
||||
"fps": fps,
|
||||
"handle_start": int(handle_start),
|
||||
"handle_end": int(handle_end)
|
||||
}
|
||||
# add timing data
|
||||
creator_attributes.update(timing_data)
|
||||
|
||||
# create shared new instance data
|
||||
# create base instance data
|
||||
base_instance_data = {
|
||||
"shotName": shot_name,
|
||||
"variant": variant_name,
|
||||
|
||||
# HACK: just for temporal bug workaround
|
||||
# TODO: should loockup shot name for update
|
||||
"asset": parent_asset_name,
|
||||
"task": "",
|
||||
|
||||
"newAssetPublishing": True,
|
||||
|
||||
# parent time properties
|
||||
"trackStartFrame": track_start_frame,
|
||||
"timelineOffset": timeline_offset,
|
||||
"isEditorial": True,
|
||||
|
||||
# creator_attributes
|
||||
"creator_attributes": creator_attributes
|
||||
}
|
||||
# update base instance data with context data
|
||||
# and also update creator attributes with context data
|
||||
if AYON_SERVER_ENABLED:
|
||||
# TODO: this is here just to be able to publish
|
||||
# to AYON with folder path
|
||||
creator_attributes["folderPath"] = shot_metadata.pop("folderPath")
|
||||
base_instance_data["folderPath"] = parent_asset_name
|
||||
else:
|
||||
creator_attributes.update({
|
||||
"shotName": shot_name,
|
||||
"Parent hierarchy path": shot_metadata["hierarchy"]
|
||||
})
|
||||
|
||||
base_instance_data["asset"] = parent_asset_name
|
||||
# add creator attributes to shared instance data
|
||||
base_instance_data["creator_attributes"] = creator_attributes
|
||||
# add hierarchy shot metadata
|
||||
base_instance_data.update(shot_metadata)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import copy
|
|||
import os
|
||||
import re
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import get_asset_name_identifier
|
||||
from openpype.lib import (
|
||||
FileDef,
|
||||
BoolDef,
|
||||
|
|
@ -64,8 +66,13 @@ class BatchMovieCreator(TrayPublishCreator):
|
|||
subset_name, task_name = self._get_subset_and_task(
|
||||
asset_doc, data["variant"], self.project_name)
|
||||
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
|
||||
instance_data["task"] = task_name
|
||||
instance_data["asset"] = asset_doc["name"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
instance_data["folderPath"] = asset_name
|
||||
else:
|
||||
instance_data["asset"] = asset_name
|
||||
|
||||
# Create new instance
|
||||
new_instance = CreatedInstance(self.family, subset_name,
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ class CollectSequenceFrameData(
|
|||
return
|
||||
|
||||
# editorial would fail since they might not be in database yet
|
||||
is_editorial = instance.data.get("isEditorial")
|
||||
if is_editorial:
|
||||
self.log.debug("Instance is Editorial. Skipping.")
|
||||
new_asset_publishing = instance.data.get("newAssetPublishing")
|
||||
if new_asset_publishing:
|
||||
self.log.debug("Instance is creating new asset. Skipping.")
|
||||
return
|
||||
|
||||
frame_data = self.get_frame_data_from_repre_sequence(instance)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ from pprint import pformat
|
|||
import pyblish.api
|
||||
import opentimelineio as otio
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
|
||||
|
||||
class CollectShotInstance(pyblish.api.InstancePlugin):
|
||||
""" Collect shot instances
|
||||
|
|
@ -119,8 +121,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin):
|
|||
frame_end = _cr_attrs["frameEnd"]
|
||||
frame_dur = frame_end - frame_start
|
||||
|
||||
return {
|
||||
"asset": _cr_attrs["asset_name"],
|
||||
data = {
|
||||
"fps": float(_cr_attrs["fps"]),
|
||||
"handleStart": _cr_attrs["handle_start"],
|
||||
"handleEnd": _cr_attrs["handle_end"],
|
||||
|
|
@ -133,6 +134,12 @@ class CollectShotInstance(pyblish.api.InstancePlugin):
|
|||
"sourceOut": _cr_attrs["sourceOut"],
|
||||
"workfileFrameStart": workfile_start_frame
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["asset"] = _cr_attrs["folderPath"]
|
||||
else:
|
||||
data["asset"] = _cr_attrs["shotName"]
|
||||
|
||||
return data
|
||||
|
||||
def _solve_hierarchy_context(self, instance):
|
||||
""" Adding hierarchy data to context shared data.
|
||||
|
|
@ -148,7 +155,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin):
|
|||
else {}
|
||||
)
|
||||
|
||||
name = instance.data["asset"]
|
||||
asset_name = instance.data["asset"]
|
||||
|
||||
# get handles
|
||||
handle_start = int(instance.data["handleStart"])
|
||||
|
|
@ -170,7 +177,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin):
|
|||
|
||||
parents = instance.data.get('parents', [])
|
||||
|
||||
actual = {name: in_info}
|
||||
actual = {asset_name: in_info}
|
||||
|
||||
for parent in reversed(parents):
|
||||
parent_name = parent["entity_name"]
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ class ValidateFrameRange(OptionalPyblishPluginMixin,
|
|||
return
|
||||
|
||||
# editorial would fail since they might not be in database yet
|
||||
is_editorial = instance.data.get("isEditorial")
|
||||
if is_editorial:
|
||||
self.log.debug("Instance is Editorial. Skipping.")
|
||||
new_asset_publishing = instance.data.get("newAssetPublishing")
|
||||
if new_asset_publishing:
|
||||
self.log.debug("Instance is creating new asset. Skipping.")
|
||||
return
|
||||
|
||||
if (self.skip_timelines_check and
|
||||
|
|
@ -41,6 +41,7 @@ class ValidateFrameRange(OptionalPyblishPluginMixin,
|
|||
for pattern in self.skip_timelines_check)):
|
||||
self.log.info("Skipping for {} task".format(instance.data["task"]))
|
||||
|
||||
asset_doc = instance.data["assetEntity"]
|
||||
asset_data = asset_doc["data"]
|
||||
frame_start = asset_data["frameStart"]
|
||||
frame_end = asset_data["frameEnd"]
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ Todos:
|
|||
import collections
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from openpype.client import get_asset_by_name
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import get_asset_by_name, get_asset_name_identifier
|
||||
from openpype.lib import (
|
||||
prepare_template_data,
|
||||
AbstractAttrDef,
|
||||
|
|
@ -784,18 +785,25 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator):
|
|||
project_name,
|
||||
host_name=self.create_context.host_name,
|
||||
)
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
if existing_instance is not None:
|
||||
existing_instance["asset"] = asset_doc["name"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_instance["folderPath"] = asset_name
|
||||
else:
|
||||
existing_instance["asset"] = asset_name
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
return existing_instance
|
||||
|
||||
instance_data: dict[str, str] = {
|
||||
"asset": asset_doc["name"],
|
||||
"task": task_name,
|
||||
"family": creator.family,
|
||||
"variant": variant
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
instance_data["folderPath"] = asset_name
|
||||
else:
|
||||
instance_data["asset"] = asset_name
|
||||
pre_create_data: dict[str, str] = {
|
||||
"group_id": group_id,
|
||||
"mark_for_review": mark_for_review
|
||||
|
|
@ -820,6 +828,8 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator):
|
|||
for layer_name in render_pass["layer_names"]:
|
||||
render_pass_by_layer_name[layer_name] = render_pass
|
||||
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
|
||||
for layer in layers:
|
||||
layer_name = layer["name"]
|
||||
variant = layer_name
|
||||
|
|
@ -838,17 +848,25 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator):
|
|||
)
|
||||
|
||||
if render_pass is not None:
|
||||
render_pass["asset"] = asset_doc["name"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
render_pass["folderPath"] = asset_name
|
||||
else:
|
||||
render_pass["asset"] = asset_name
|
||||
|
||||
render_pass["task"] = task_name
|
||||
render_pass["subset"] = subset_name
|
||||
continue
|
||||
|
||||
instance_data: dict[str, str] = {
|
||||
"asset": asset_doc["name"],
|
||||
"task": task_name,
|
||||
"family": creator.family,
|
||||
"variant": variant
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
instance_data["folderPath"] = asset_name
|
||||
else:
|
||||
instance_data["asset"] = asset_name
|
||||
|
||||
pre_create_data: dict[str, Any] = {
|
||||
"render_layer_instance_id": render_layer_instance.id,
|
||||
"layer_names": [layer_name],
|
||||
|
|
@ -882,9 +900,13 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator):
|
|||
|
||||
def create(self, subset_name, instance_data, pre_create_data):
|
||||
project_name: str = self.create_context.get_current_project_name()
|
||||
asset_name: str = instance_data["asset"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name: str = instance_data["folderPath"]
|
||||
else:
|
||||
asset_name: str = instance_data["asset"]
|
||||
task_name: str = instance_data["task"]
|
||||
asset_doc: dict[str, Any] = get_asset_by_name(project_name, asset_name)
|
||||
asset_doc: dict[str, Any] = get_asset_by_name(
|
||||
project_name, asset_name)
|
||||
|
||||
render_layers_by_group_id: dict[int, CreatedInstance] = {}
|
||||
render_passes_by_render_layer_id: dict[int, list[CreatedInstance]] = (
|
||||
|
|
@ -1061,7 +1083,6 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator):
|
|||
host_name
|
||||
)
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
"variant": self.default_variant,
|
||||
"creator_attributes": {
|
||||
|
|
@ -1073,6 +1094,10 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator):
|
|||
self.default_pass_name
|
||||
)
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
if not self.active_on_create:
|
||||
data["active"] = False
|
||||
|
||||
|
|
@ -1101,8 +1126,14 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator):
|
|||
asset_name = create_context.get_current_asset_name()
|
||||
task_name = create_context.get_current_task_name()
|
||||
|
||||
existing_name = None
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_name = existing_instance.get("folderPath")
|
||||
if existing_name is None:
|
||||
existing_name = existing_instance["asset"]
|
||||
|
||||
if (
|
||||
existing_instance["asset"] != asset_name
|
||||
existing_name != asset_name
|
||||
or existing_instance["task"] != task_name
|
||||
):
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
|
@ -1114,7 +1145,10 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator):
|
|||
host_name,
|
||||
existing_instance
|
||||
)
|
||||
existing_instance["asset"] = asset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_instance["folderPath"] = asset_name
|
||||
else:
|
||||
existing_instance["asset"] = asset_name
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import get_asset_by_name
|
||||
from openpype.pipeline import CreatedInstance
|
||||
from openpype.hosts.tvpaint.api.plugin import TVPaintAutoCreator
|
||||
|
|
@ -33,6 +34,13 @@ class TVPaintReviewCreator(TVPaintAutoCreator):
|
|||
asset_name = create_context.get_current_asset_name()
|
||||
task_name = create_context.get_current_task_name()
|
||||
|
||||
if existing_instance is None:
|
||||
existing_asset_name = None
|
||||
elif AYON_SERVER_ENABLED:
|
||||
existing_asset_name = existing_instance["folderPath"]
|
||||
else:
|
||||
existing_asset_name = existing_instance["asset"]
|
||||
|
||||
if existing_instance is None:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
|
|
@ -43,10 +51,14 @@ class TVPaintReviewCreator(TVPaintAutoCreator):
|
|||
host_name
|
||||
)
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
"variant": self.default_variant
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
|
||||
if not self.active_on_create:
|
||||
data["active"] = False
|
||||
|
||||
|
|
@ -59,7 +71,7 @@ class TVPaintReviewCreator(TVPaintAutoCreator):
|
|||
self._add_instance_to_context(new_instance)
|
||||
|
||||
elif (
|
||||
existing_instance["asset"] != asset_name
|
||||
existing_asset_name != asset_name
|
||||
or existing_instance["task"] != task_name
|
||||
):
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
|
@ -71,6 +83,9 @@ class TVPaintReviewCreator(TVPaintAutoCreator):
|
|||
host_name,
|
||||
existing_instance
|
||||
)
|
||||
existing_instance["asset"] = asset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_instance["folderPath"] = asset_name
|
||||
else:
|
||||
existing_instance["asset"] = asset_name
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import get_asset_by_name
|
||||
from openpype.pipeline import CreatedInstance
|
||||
from openpype.hosts.tvpaint.api.plugin import TVPaintAutoCreator
|
||||
|
|
@ -29,6 +30,13 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator):
|
|||
asset_name = create_context.get_current_asset_name()
|
||||
task_name = create_context.get_current_task_name()
|
||||
|
||||
if existing_instance is None:
|
||||
existing_asset_name = None
|
||||
elif AYON_SERVER_ENABLED:
|
||||
existing_asset_name = existing_instance["folderPath"]
|
||||
else:
|
||||
existing_asset_name = existing_instance["asset"]
|
||||
|
||||
if existing_instance is None:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
|
|
@ -39,10 +47,13 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator):
|
|||
host_name
|
||||
)
|
||||
data = {
|
||||
"asset": asset_name,
|
||||
"task": task_name,
|
||||
"variant": self.default_variant
|
||||
}
|
||||
if AYON_SERVER_ENABLED:
|
||||
data["folderPath"] = asset_name
|
||||
else:
|
||||
data["asset"] = asset_name
|
||||
|
||||
new_instance = CreatedInstance(
|
||||
self.family, subset_name, data, self
|
||||
|
|
@ -53,7 +64,7 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator):
|
|||
self._add_instance_to_context(new_instance)
|
||||
|
||||
elif (
|
||||
existing_instance["asset"] != asset_name
|
||||
existing_asset_name != asset_name
|
||||
or existing_instance["task"] != task_name
|
||||
):
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
|
|
@ -65,6 +76,9 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator):
|
|||
host_name,
|
||||
existing_instance
|
||||
)
|
||||
existing_instance["asset"] = asset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
existing_instance["folderPath"] = asset_name
|
||||
else:
|
||||
existing_instance["asset"] = asset_name
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import pyblish.api
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.pipeline import (
|
||||
PublishXmlValidationError,
|
||||
OptionalPyblishPluginMixin,
|
||||
|
|
@ -24,12 +25,19 @@ class FixAssetNames(pyblish.api.Action):
|
|||
old_instance_items = list_instances()
|
||||
new_instance_items = []
|
||||
for instance_item in old_instance_items:
|
||||
instance_asset_name = instance_item.get("asset")
|
||||
if AYON_SERVER_ENABLED:
|
||||
instance_asset_name = instance_item.get("folderPath")
|
||||
else:
|
||||
instance_asset_name = instance_item.get("asset")
|
||||
|
||||
if (
|
||||
instance_asset_name
|
||||
and instance_asset_name != context_asset_name
|
||||
):
|
||||
instance_item["asset"] = context_asset_name
|
||||
if AYON_SERVER_ENABLED:
|
||||
instance_item["folderPath"] = context_asset_name
|
||||
else:
|
||||
instance_item["asset"] = context_asset_name
|
||||
new_instance_items.append(instance_item)
|
||||
write_instances(new_instance_items)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from abc import ABCMeta, abstractmethod
|
|||
import six
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED, PACKAGE_DIR
|
||||
from openpype.client import get_asset_name_identifier
|
||||
from openpype.settings import (
|
||||
get_system_settings,
|
||||
get_project_settings,
|
||||
|
|
@ -1728,7 +1729,9 @@ def prepare_context_environments(data, env_group=None, modules_manager=None):
|
|||
"AVALON_APP_NAME": app.full_name
|
||||
}
|
||||
if asset_doc:
|
||||
context_env["AVALON_ASSET"] = asset_doc["name"]
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
context_env["AVALON_ASSET"] = asset_name
|
||||
|
||||
if task_name:
|
||||
context_env["AVALON_TASK"] = task_name
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from .interfaces import (
|
|||
)
|
||||
|
||||
from .base import (
|
||||
AYONAddon,
|
||||
OpenPypeModule,
|
||||
OpenPypeAddOn,
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ __all__ = (
|
|||
"ISettingsChangeListener",
|
||||
"IHostAddon",
|
||||
|
||||
"AYONAddon",
|
||||
"OpenPypeModule",
|
||||
"OpenPypeAddOn",
|
||||
|
||||
|
|
|
|||
8
openpype/modules/asset_reporter/__init__.py
Normal file
8
openpype/modules/asset_reporter/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from .module import (
|
||||
AssetReporterAction
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"AssetReporterAction",
|
||||
)
|
||||
27
openpype/modules/asset_reporter/module.py
Normal file
27
openpype/modules/asset_reporter/module.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import os.path
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.modules import OpenPypeModule, ITrayAction
|
||||
from openpype.lib import run_detached_process, get_openpype_execute_args
|
||||
|
||||
|
||||
class AssetReporterAction(OpenPypeModule, ITrayAction):
|
||||
|
||||
label = "Asset Usage Report"
|
||||
name = "asset_reporter"
|
||||
|
||||
def tray_init(self):
|
||||
pass
|
||||
|
||||
def initialize(self, modules_settings):
|
||||
self.enabled = not AYON_SERVER_ENABLED
|
||||
|
||||
def on_action_trigger(self):
|
||||
args = get_openpype_execute_args()
|
||||
args += ["run",
|
||||
os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"window.py")]
|
||||
|
||||
print(" ".join(args))
|
||||
run_detached_process(args)
|
||||
418
openpype/modules/asset_reporter/window.py
Normal file
418
openpype/modules/asset_reporter/window.py
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
"""Tool for generating asset usage report.
|
||||
|
||||
This tool is used to generate asset usage report for a project.
|
||||
It is using links between published version to find out where
|
||||
the asset is used.
|
||||
|
||||
"""
|
||||
|
||||
import csv
|
||||
import time
|
||||
|
||||
import appdirs
|
||||
import qtawesome
|
||||
from pymongo.collection import Collection
|
||||
from qtpy import QtCore, QtWidgets
|
||||
from qtpy.QtGui import QClipboard, QColor
|
||||
|
||||
from openpype import style
|
||||
from openpype.client import OpenPypeMongoConnection
|
||||
from openpype.lib import JSONSettingRegistry
|
||||
from openpype.tools.utils import PlaceholderLineEdit, get_openpype_qt_app
|
||||
from openpype.tools.utils.constants import PROJECT_NAME_ROLE
|
||||
from openpype.tools.utils.models import ProjectModel, ProjectSortFilterProxy
|
||||
|
||||
|
||||
class AssetReporterRegistry(JSONSettingRegistry):
|
||||
"""Class handling OpenPype general settings registry.
|
||||
|
||||
This is used to store last selected project.
|
||||
|
||||
Attributes:
|
||||
vendor (str): Name used for path construction.
|
||||
product (str): Additional name used for path construction.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.vendor = "ynput"
|
||||
self.product = "openpype"
|
||||
name = "asset_usage_reporter"
|
||||
path = appdirs.user_data_dir(self.product, self.vendor)
|
||||
super(AssetReporterRegistry, self).__init__(name, path)
|
||||
|
||||
|
||||
class OverlayWidget(QtWidgets.QFrame):
|
||||
"""Overlay widget for choosing project.
|
||||
|
||||
This code is taken from the Tray Publisher tool.
|
||||
"""
|
||||
project_selected = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, publisher_window):
|
||||
super(OverlayWidget, self).__init__(publisher_window)
|
||||
self.setObjectName("OverlayFrame")
|
||||
|
||||
middle_frame = QtWidgets.QFrame(self)
|
||||
middle_frame.setObjectName("ChooseProjectFrame")
|
||||
|
||||
content_widget = QtWidgets.QWidget(middle_frame)
|
||||
|
||||
header_label = QtWidgets.QLabel("Choose project", content_widget)
|
||||
header_label.setObjectName("ChooseProjectLabel")
|
||||
# Create project models and view
|
||||
projects_model = ProjectModel()
|
||||
projects_proxy = ProjectSortFilterProxy()
|
||||
projects_proxy.setSourceModel(projects_model)
|
||||
projects_proxy.setFilterKeyColumn(0)
|
||||
|
||||
projects_view = QtWidgets.QListView(content_widget)
|
||||
projects_view.setObjectName("ChooseProjectView")
|
||||
projects_view.setModel(projects_proxy)
|
||||
projects_view.setEditTriggers(
|
||||
QtWidgets.QAbstractItemView.NoEditTriggers
|
||||
)
|
||||
|
||||
confirm_btn = QtWidgets.QPushButton("Confirm", content_widget)
|
||||
cancel_btn = QtWidgets.QPushButton("Cancel", content_widget)
|
||||
cancel_btn.setVisible(False)
|
||||
btns_layout = QtWidgets.QHBoxLayout()
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(cancel_btn, 0)
|
||||
btns_layout.addWidget(confirm_btn, 0)
|
||||
|
||||
txt_filter = PlaceholderLineEdit(content_widget)
|
||||
txt_filter.setPlaceholderText("Quick filter projects..")
|
||||
txt_filter.setClearButtonEnabled(True)
|
||||
txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"),
|
||||
QtWidgets.QLineEdit.LeadingPosition)
|
||||
|
||||
content_layout = QtWidgets.QVBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
content_layout.setSpacing(20)
|
||||
content_layout.addWidget(header_label, 0)
|
||||
content_layout.addWidget(txt_filter, 0)
|
||||
content_layout.addWidget(projects_view, 1)
|
||||
content_layout.addLayout(btns_layout, 0)
|
||||
|
||||
middle_layout = QtWidgets.QHBoxLayout(middle_frame)
|
||||
middle_layout.setContentsMargins(30, 30, 10, 10)
|
||||
middle_layout.addWidget(content_widget)
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout(self)
|
||||
main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
main_layout.addStretch(1)
|
||||
main_layout.addWidget(middle_frame, 2)
|
||||
main_layout.addStretch(1)
|
||||
|
||||
projects_view.doubleClicked.connect(self._on_double_click)
|
||||
confirm_btn.clicked.connect(self._on_confirm_click)
|
||||
cancel_btn.clicked.connect(self._on_cancel_click)
|
||||
txt_filter.textChanged.connect(self._on_text_changed)
|
||||
|
||||
self._projects_view = projects_view
|
||||
self._projects_model = projects_model
|
||||
self._projects_proxy = projects_proxy
|
||||
self._cancel_btn = cancel_btn
|
||||
self._confirm_btn = confirm_btn
|
||||
self._txt_filter = txt_filter
|
||||
|
||||
self._publisher_window = publisher_window
|
||||
self._project_name = None
|
||||
|
||||
def showEvent(self, event):
|
||||
self._projects_model.refresh()
|
||||
# Sort projects after refresh
|
||||
self._projects_proxy.sort(0)
|
||||
|
||||
setting_registry = AssetReporterRegistry()
|
||||
try:
|
||||
project_name = str(setting_registry.get_item("project_name"))
|
||||
except ValueError:
|
||||
project_name = None
|
||||
|
||||
if project_name:
|
||||
index = None
|
||||
src_index = self._projects_model.find_project(project_name)
|
||||
if src_index is not None:
|
||||
index = self._projects_proxy.mapFromSource(src_index)
|
||||
|
||||
if index is not None:
|
||||
selection_model = self._projects_view.selectionModel()
|
||||
selection_model.select(
|
||||
index,
|
||||
QtCore.QItemSelectionModel.SelectCurrent
|
||||
)
|
||||
self._projects_view.setCurrentIndex(index)
|
||||
|
||||
self._cancel_btn.setVisible(self._project_name is not None)
|
||||
super(OverlayWidget, self).showEvent(event)
|
||||
|
||||
def _on_double_click(self):
|
||||
self.set_selected_project()
|
||||
|
||||
def _on_confirm_click(self):
|
||||
self.set_selected_project()
|
||||
|
||||
def _on_cancel_click(self):
|
||||
self._set_project(self._project_name)
|
||||
|
||||
def _on_text_changed(self):
|
||||
self._projects_proxy.setFilterRegularExpression(
|
||||
self._txt_filter.text())
|
||||
|
||||
def set_selected_project(self):
|
||||
index = self._projects_view.currentIndex()
|
||||
|
||||
if project_name := index.data(PROJECT_NAME_ROLE):
|
||||
self._set_project(project_name)
|
||||
|
||||
def _set_project(self, project_name):
|
||||
self._project_name = project_name
|
||||
self.setVisible(False)
|
||||
self.project_selected.emit(project_name)
|
||||
|
||||
setting_registry = AssetReporterRegistry()
|
||||
setting_registry.set_item("project_name", project_name)
|
||||
|
||||
|
||||
class AssetReporterWindow(QtWidgets.QDialog):
|
||||
default_width = 1000
|
||||
default_height = 800
|
||||
_content = None
|
||||
|
||||
def __init__(self, parent=None, controller=None, reset_on_show=None):
|
||||
super(AssetReporterWindow, self).__init__(parent)
|
||||
|
||||
self._result = {}
|
||||
self.setObjectName("AssetReporterWindow")
|
||||
|
||||
self.setWindowTitle("Asset Usage Reporter")
|
||||
|
||||
if parent is None:
|
||||
on_top_flag = QtCore.Qt.WindowStaysOnTopHint
|
||||
else:
|
||||
on_top_flag = QtCore.Qt.Dialog
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowTitleHint
|
||||
| QtCore.Qt.WindowMaximizeButtonHint
|
||||
| QtCore.Qt.WindowMinimizeButtonHint
|
||||
| QtCore.Qt.WindowCloseButtonHint
|
||||
| on_top_flag
|
||||
)
|
||||
self.table = QtWidgets.QTableWidget(self)
|
||||
self.table.setColumnCount(3)
|
||||
self.table.setColumnWidth(0, 400)
|
||||
self.table.setColumnWidth(1, 300)
|
||||
self.table.setHorizontalHeaderLabels(["Subset", "Used in", "Version"])
|
||||
|
||||
# self.text_area = QtWidgets.QTextEdit(self)
|
||||
self.copy_button = QtWidgets.QPushButton('Copy to Clipboard', self)
|
||||
self.save_button = QtWidgets.QPushButton('Save to CSV File', self)
|
||||
|
||||
self.copy_button.clicked.connect(self.copy_to_clipboard)
|
||||
self.save_button.clicked.connect(self.save_to_file)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(self.table)
|
||||
# layout.addWidget(self.text_area)
|
||||
layout.addWidget(self.copy_button)
|
||||
layout.addWidget(self.save_button)
|
||||
|
||||
self.resize(self.default_width, self.default_height)
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
overlay_widget = OverlayWidget(self)
|
||||
overlay_widget.project_selected.connect(self._on_project_select)
|
||||
self._overlay_widget = overlay_widget
|
||||
|
||||
def _on_project_select(self, project_name: str):
|
||||
"""Generate table when project is selected.
|
||||
|
||||
This will generate the table and fill it with data.
|
||||
Source data are held in memory in `_result` attribute that
|
||||
is used to transform them into clipboard or csv file.
|
||||
"""
|
||||
self._project_name = project_name
|
||||
self.process()
|
||||
if not self._result:
|
||||
self.set_content("no result generated")
|
||||
return
|
||||
|
||||
rows = sum(len(value) for key, value in self._result.items())
|
||||
self.table.setRowCount(rows)
|
||||
|
||||
row = 0
|
||||
content = []
|
||||
for key, value in self._result.items():
|
||||
item = QtWidgets.QTableWidgetItem(key)
|
||||
# this doesn't work as it is probably overriden by stylesheet?
|
||||
# item.setBackground(QColor(32, 32, 32))
|
||||
self.table.setItem(row, 0, item)
|
||||
for source in value:
|
||||
self.table.setItem(
|
||||
row, 1, QtWidgets.QTableWidgetItem(source["name"]))
|
||||
self.table.setItem(
|
||||
row, 2, QtWidgets.QTableWidgetItem(
|
||||
str(source["version"])))
|
||||
row += 1
|
||||
|
||||
# generate clipboard content
|
||||
content.append(key)
|
||||
content.extend(
|
||||
f"\t{source['name']} (v{source['version']})" for source in value # noqa: E501
|
||||
)
|
||||
self.set_content("\n".join(content))
|
||||
|
||||
def copy_to_clipboard(self):
|
||||
clipboard = QtWidgets.QApplication.clipboard()
|
||||
clipboard.setText(self._content, QClipboard.Clipboard)
|
||||
|
||||
def save_to_file(self):
|
||||
file_name, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save File')
|
||||
if file_name:
|
||||
self._write_csv(file_name)
|
||||
|
||||
def set_content(self, content):
|
||||
self._content = content
|
||||
|
||||
def get_content(self):
|
||||
return self._content
|
||||
|
||||
def _resize_overlay(self):
|
||||
self._overlay_widget.resize(
|
||||
self.width(),
|
||||
self.height()
|
||||
)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super(AssetReporterWindow, self).resizeEvent(event)
|
||||
self._resize_overlay()
|
||||
|
||||
def _get_subset(self, version_id, project: Collection):
|
||||
pipeline = [
|
||||
{
|
||||
"$match": {
|
||||
"_id": version_id
|
||||
},
|
||||
}, {
|
||||
"$lookup": {
|
||||
"from": project.name,
|
||||
"localField": "parent",
|
||||
"foreignField": "_id",
|
||||
"as": "parents"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
result = project.aggregate(pipeline)
|
||||
doc = next(result)
|
||||
# print(doc)
|
||||
return {
|
||||
"name": f'{"/".join(doc["parents"][0]["data"]["parents"])}/{doc["parents"][0]["name"]}/{doc["name"]}', # noqa: E501
|
||||
"family": doc["data"].get("family") or doc["data"].get("families")[0] # noqa: E501
|
||||
}
|
||||
|
||||
def process(self):
|
||||
"""Generate asset usage report data.
|
||||
|
||||
This is the main method of the tool. It is using MongoDB
|
||||
aggregation pipeline to find all published versions that
|
||||
are used as input for other published versions. Then it
|
||||
generates a map of assets and their usage.
|
||||
|
||||
"""
|
||||
start = time.perf_counter()
|
||||
project = self._project_name
|
||||
|
||||
# get all versions of published workfiles that has non-empty
|
||||
# inputLinks and connect it with their respective documents
|
||||
# using ID.
|
||||
pipeline = [
|
||||
{
|
||||
"$match": {
|
||||
"data.inputLinks": {
|
||||
"$exists": True,
|
||||
"$ne": []
|
||||
},
|
||||
"data.families": {"$in": ["workfile"]}
|
||||
}
|
||||
}, {
|
||||
"$lookup": {
|
||||
"from": project,
|
||||
"localField": "data.inputLinks.id",
|
||||
"foreignField": "_id",
|
||||
"as": "linked_docs"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
client = OpenPypeMongoConnection.get_mongo_client()
|
||||
db = client["avalon"]
|
||||
|
||||
result = db[project].aggregate(pipeline)
|
||||
|
||||
asset_map = []
|
||||
# this is creating the map - for every workfile and its linked
|
||||
# documents, create a dictionary with "source" and "refs" keys
|
||||
# and resolve the subset name and version from the document
|
||||
for doc in result:
|
||||
source = {
|
||||
"source": self._get_subset(doc["parent"], db[project]),
|
||||
}
|
||||
source["source"].update({"version": doc["name"]})
|
||||
refs = []
|
||||
version = '<unknown>'
|
||||
for linked in doc["linked_docs"]:
|
||||
try:
|
||||
version = f'v{linked["name"]}'
|
||||
except KeyError:
|
||||
if linked["type"] == "hero_version":
|
||||
version = "hero"
|
||||
finally:
|
||||
refs.append({
|
||||
"subset": self._get_subset(
|
||||
linked["parent"], db[project]),
|
||||
"version": version
|
||||
})
|
||||
|
||||
source["refs"] = refs
|
||||
asset_map.append(source)
|
||||
|
||||
grouped = {}
|
||||
|
||||
# this will group the assets by subset name and version
|
||||
for asset in asset_map:
|
||||
for ref in asset["refs"]:
|
||||
key = f'{ref["subset"]["name"]} ({ref["version"]})'
|
||||
if key in grouped:
|
||||
grouped[key].append(asset["source"])
|
||||
else:
|
||||
grouped[key] = [asset["source"]]
|
||||
self._result = grouped
|
||||
|
||||
end = time.perf_counter()
|
||||
|
||||
print(f"Finished in {end - start:0.4f} seconds", 2)
|
||||
|
||||
def _write_csv(self, file_name: str) -> None:
|
||||
"""Write CSV file with results."""
|
||||
with open(file_name, "w", newline="") as csvfile:
|
||||
writer = csv.writer(csvfile, delimiter=";")
|
||||
writer.writerow(["Subset", "Used in", "Version"])
|
||||
for key, value in self._result.items():
|
||||
writer.writerow([key, "", ""])
|
||||
for source in value:
|
||||
writer.writerow(["", source["name"], source["version"]])
|
||||
|
||||
|
||||
def main():
|
||||
app_instance = get_openpype_qt_app()
|
||||
window = AssetReporterWindow()
|
||||
window.show()
|
||||
app_instance.exec_()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Base class for Pype Modules."""
|
||||
"""Base class for AYON addons."""
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
|
|
@ -11,6 +11,7 @@ import platform
|
|||
import threading
|
||||
import collections
|
||||
import traceback
|
||||
|
||||
from uuid import uuid4
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
|
@ -29,9 +30,12 @@ from openpype.settings import (
|
|||
|
||||
from openpype.settings.lib import (
|
||||
get_studio_system_settings_overrides,
|
||||
load_json_file
|
||||
load_json_file,
|
||||
)
|
||||
from openpype.settings.ayon_settings import (
|
||||
is_dev_mode_enabled,
|
||||
get_ayon_settings,
|
||||
)
|
||||
from openpype.settings.ayon_settings import is_dev_mode_enabled
|
||||
|
||||
from openpype.lib import (
|
||||
Logger,
|
||||
|
|
@ -47,11 +51,11 @@ from .interfaces import (
|
|||
ITrayService
|
||||
)
|
||||
|
||||
# Files that will be always ignored on modules import
|
||||
# Files that will be always ignored on addons import
|
||||
IGNORED_FILENAMES = (
|
||||
"__pycache__",
|
||||
)
|
||||
# Files ignored on modules import from "./openpype/modules"
|
||||
# Files ignored on addons import from "./openpype/modules"
|
||||
IGNORED_DEFAULT_FILENAMES = (
|
||||
"__init__.py",
|
||||
"base.py",
|
||||
|
|
@ -59,8 +63,8 @@ IGNORED_DEFAULT_FILENAMES = (
|
|||
"example_addons",
|
||||
"default_modules",
|
||||
)
|
||||
# Modules that won't be loaded in AYON mode from "./openpype/modules"
|
||||
# - the same modules are ignored in "./server_addon/create_ayon_addons.py"
|
||||
# Addons that won't be loaded in AYON mode from "./openpype/modules"
|
||||
# - the same addons are ignored in "./server_addon/create_ayon_addons.py"
|
||||
IGNORED_FILENAMES_IN_AYON = {
|
||||
"ftrack",
|
||||
"shotgrid",
|
||||
|
|
@ -68,6 +72,10 @@ IGNORED_FILENAMES_IN_AYON = {
|
|||
"slack",
|
||||
"kitsu",
|
||||
}
|
||||
IGNORED_HOSTS_IN_AYON = {
|
||||
"flame",
|
||||
"harmony",
|
||||
}
|
||||
|
||||
|
||||
# Inherit from `object` for Python 2 hosts
|
||||
|
|
@ -466,7 +474,7 @@ def _load_ayon_addons(openpype_modules, modules_key, log):
|
|||
attr = getattr(mod, attr_name)
|
||||
if (
|
||||
inspect.isclass(attr)
|
||||
and issubclass(attr, OpenPypeModule)
|
||||
and issubclass(attr, AYONAddon)
|
||||
):
|
||||
imported_modules.append(mod)
|
||||
break
|
||||
|
|
@ -536,6 +544,11 @@ def _load_modules():
|
|||
addons_dir = os.path.join(os.path.dirname(current_dir), "addons")
|
||||
module_dirs.append(addons_dir)
|
||||
|
||||
ignored_host_names = set(IGNORED_HOSTS_IN_AYON)
|
||||
ignored_current_dir_filenames = set(IGNORED_DEFAULT_FILENAMES)
|
||||
if AYON_SERVER_ENABLED:
|
||||
ignored_current_dir_filenames |= IGNORED_FILENAMES_IN_AYON
|
||||
|
||||
processed_paths = set()
|
||||
for dirpath in frozenset(module_dirs):
|
||||
# Skip already processed paths
|
||||
|
|
@ -551,9 +564,6 @@ def _load_modules():
|
|||
|
||||
is_in_current_dir = dirpath == current_dir
|
||||
is_in_host_dir = dirpath == hosts_dir
|
||||
ignored_current_dir_filenames = set(IGNORED_DEFAULT_FILENAMES)
|
||||
if AYON_SERVER_ENABLED:
|
||||
ignored_current_dir_filenames |= IGNORED_FILENAMES_IN_AYON
|
||||
|
||||
for filename in os.listdir(dirpath):
|
||||
# Ignore filenames
|
||||
|
|
@ -566,6 +576,12 @@ def _load_modules():
|
|||
):
|
||||
continue
|
||||
|
||||
if (
|
||||
is_in_host_dir
|
||||
and filename in ignored_host_names
|
||||
):
|
||||
continue
|
||||
|
||||
fullpath = os.path.join(dirpath, filename)
|
||||
basename, ext = os.path.splitext(filename)
|
||||
|
||||
|
|
@ -633,26 +649,22 @@ def _load_modules():
|
|||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class OpenPypeModule:
|
||||
"""Base class of pype module.
|
||||
class AYONAddon(object):
|
||||
"""Base class of AYON addon.
|
||||
|
||||
Attributes:
|
||||
id (UUID): Module's id.
|
||||
enabled (bool): Is module enabled.
|
||||
name (str): Module name.
|
||||
manager (ModulesManager): Manager that created the module.
|
||||
id (UUID): Addon object id.
|
||||
enabled (bool): Is addon enabled.
|
||||
name (str): Addon name.
|
||||
|
||||
Args:
|
||||
manager (ModulesManager): Manager object who discovered addon.
|
||||
settings (dict[str, Any]): AYON settings.
|
||||
"""
|
||||
|
||||
# Disable by default
|
||||
enabled = False
|
||||
enabled = True
|
||||
_id = None
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self):
|
||||
"""Module's name."""
|
||||
pass
|
||||
|
||||
def __init__(self, manager, settings):
|
||||
self.manager = manager
|
||||
|
||||
|
|
@ -662,22 +674,45 @@ class OpenPypeModule:
|
|||
|
||||
@property
|
||||
def id(self):
|
||||
"""Random id of addon object.
|
||||
|
||||
Returns:
|
||||
str: Object id.
|
||||
"""
|
||||
|
||||
if self._id is None:
|
||||
self._id = uuid4()
|
||||
return self._id
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def initialize(self, module_settings):
|
||||
"""Initialization of module attributes.
|
||||
def name(self):
|
||||
"""Addon name.
|
||||
|
||||
It is not recommended to override __init__ that's why specific method
|
||||
was implemented.
|
||||
Returns:
|
||||
str: Addon name.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def connect_with_modules(self, enabled_modules):
|
||||
"""Connect with other enabled modules."""
|
||||
def initialize(self, settings):
|
||||
"""Initialization of module attributes.
|
||||
|
||||
It is not recommended to override __init__ that's why specific method
|
||||
was implemented.
|
||||
|
||||
Args:
|
||||
settings (dict[str, Any]): Settings.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def connect_with_modules(self, enabled_addons):
|
||||
"""Connect with other enabled addons.
|
||||
|
||||
Args:
|
||||
enabled_addons (list[AYONAddon]): Addons that are enabled.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
|
@ -685,6 +720,9 @@ class OpenPypeModule:
|
|||
"""Get global environments values of module.
|
||||
|
||||
Environment variables that can be get only from system settings.
|
||||
|
||||
Returns:
|
||||
dict[str, str]: Environment variables.
|
||||
"""
|
||||
|
||||
return {}
|
||||
|
|
@ -697,7 +735,7 @@ class OpenPypeModule:
|
|||
|
||||
Args:
|
||||
application (Application): Application that is launched.
|
||||
env (dict): Current environment variables.
|
||||
env (dict[str, str]): Current environment variables.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
@ -713,7 +751,8 @@ class OpenPypeModule:
|
|||
to receive from 'host' object.
|
||||
|
||||
Args:
|
||||
host (ModuleType): Access to installed/registered host object.
|
||||
host (Union[ModuleType, HostBase]): Access to installed/registered
|
||||
host object.
|
||||
host_name (str): Name of host.
|
||||
project_name (str): Project name which is main part of host
|
||||
context.
|
||||
|
|
@ -727,47 +766,66 @@ class OpenPypeModule:
|
|||
The best practise is to create click group for whole module which is
|
||||
used to separate commands.
|
||||
|
||||
class MyPlugin(OpenPypeModule):
|
||||
...
|
||||
def cli(self, module_click_group):
|
||||
module_click_group.add_command(cli_main)
|
||||
Example:
|
||||
class MyPlugin(AYONAddon):
|
||||
...
|
||||
def cli(self, module_click_group):
|
||||
module_click_group.add_command(cli_main)
|
||||
|
||||
|
||||
@click.group(<module name>, help="<Any help shown in cmd>")
|
||||
def cli_main():
|
||||
pass
|
||||
@click.group(<module name>, help="<Any help shown in cmd>")
|
||||
def cli_main():
|
||||
pass
|
||||
|
||||
@cli_main.command()
|
||||
def mycommand():
|
||||
print("my_command")
|
||||
@cli_main.command()
|
||||
def mycommand():
|
||||
print("my_command")
|
||||
|
||||
Args:
|
||||
module_click_group (click.Group): Group to which can be added
|
||||
commands.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class OpenPypeModule(AYONAddon):
|
||||
"""Base class of OpenPype module.
|
||||
|
||||
Instead of 'AYONAddon' are passed in module settings.
|
||||
|
||||
Args:
|
||||
manager (ModulesManager): Manager object who discovered addon.
|
||||
settings (dict[str, Any]): OpenPype settings.
|
||||
"""
|
||||
|
||||
# Disable by default
|
||||
enabled = False
|
||||
|
||||
|
||||
class OpenPypeAddOn(OpenPypeModule):
|
||||
# Enable Addon by default
|
||||
enabled = True
|
||||
|
||||
def initialize(self, module_settings):
|
||||
"""Initialization is not be required for most of addons."""
|
||||
pass
|
||||
|
||||
|
||||
class ModulesManager:
|
||||
"""Manager of Pype modules helps to load and prepare them to work.
|
||||
|
||||
Args:
|
||||
modules_settings(dict): To be able create module manager with specified
|
||||
data. For settings changes callbacks and testing purposes.
|
||||
system_settings (Optional[dict[str, Any]]): OpenPype system settings.
|
||||
ayon_settings (Optional[dict[str, Any]]): AYON studio settings.
|
||||
"""
|
||||
|
||||
# Helper attributes for report
|
||||
_report_total_key = "Total"
|
||||
_system_settings = None
|
||||
_ayon_settings = None
|
||||
|
||||
def __init__(self, _system_settings=None):
|
||||
def __init__(self, system_settings=None, ayon_settings=None):
|
||||
self.log = logging.getLogger(self.__class__.__name__)
|
||||
|
||||
self._system_settings = _system_settings
|
||||
self._system_settings = system_settings
|
||||
self._ayon_settings = ayon_settings
|
||||
|
||||
self.modules = []
|
||||
self.modules_by_id = {}
|
||||
|
|
@ -789,8 +847,9 @@ class ModulesManager:
|
|||
default (Any): Default output if module is not available.
|
||||
|
||||
Returns:
|
||||
Union[OpenPypeModule, None]: Module found by name or None.
|
||||
Union[AYONAddon, None]: Module found by name or None.
|
||||
"""
|
||||
|
||||
return self.modules_by_name.get(module_name, default)
|
||||
|
||||
def get_enabled_module(self, module_name, default=None):
|
||||
|
|
@ -804,7 +863,7 @@ class ModulesManager:
|
|||
not enabled.
|
||||
|
||||
Returns:
|
||||
Union[OpenPypeModule, None]: Enabled module found by name or None.
|
||||
Union[AYONAddon, None]: Enabled module found by name or None.
|
||||
"""
|
||||
|
||||
module = self.get(module_name)
|
||||
|
|
@ -819,11 +878,20 @@ class ModulesManager:
|
|||
|
||||
import openpype_modules
|
||||
|
||||
self.log.debug("*** Pype modules initialization.")
|
||||
self.log.debug("*** {} initialization.".format(
|
||||
"AYON addons"
|
||||
if AYON_SERVER_ENABLED
|
||||
else "OpenPype modules"
|
||||
))
|
||||
# Prepare settings for modules
|
||||
system_settings = getattr(self, "_system_settings", None)
|
||||
system_settings = self._system_settings
|
||||
if system_settings is None:
|
||||
system_settings = get_system_settings()
|
||||
|
||||
ayon_settings = self._ayon_settings
|
||||
if AYON_SERVER_ENABLED and ayon_settings is None:
|
||||
ayon_settings = get_ayon_settings()
|
||||
|
||||
modules_settings = system_settings["modules"]
|
||||
|
||||
report = {}
|
||||
|
|
@ -836,12 +904,13 @@ class ModulesManager:
|
|||
for name in dir(module):
|
||||
modules_item = getattr(module, name, None)
|
||||
# Filter globals that are not classes which inherit from
|
||||
# OpenPypeModule
|
||||
# AYONAddon
|
||||
if (
|
||||
not inspect.isclass(modules_item)
|
||||
or modules_item is AYONAddon
|
||||
or modules_item is OpenPypeModule
|
||||
or modules_item is OpenPypeAddOn
|
||||
or not issubclass(modules_item, OpenPypeModule)
|
||||
or not issubclass(modules_item, AYONAddon)
|
||||
):
|
||||
continue
|
||||
|
||||
|
|
@ -866,10 +935,14 @@ class ModulesManager:
|
|||
module_classes.append(modules_item)
|
||||
|
||||
for modules_item in module_classes:
|
||||
is_openpype_module = issubclass(modules_item, OpenPypeModule)
|
||||
settings = (
|
||||
modules_settings if is_openpype_module else ayon_settings
|
||||
)
|
||||
name = modules_item.__name__
|
||||
try:
|
||||
name = modules_item.__name__
|
||||
# Try initialize module
|
||||
module = modules_item(self, modules_settings)
|
||||
module = modules_item(self, settings)
|
||||
# Store initialized object
|
||||
self.modules.append(module)
|
||||
self.modules_by_id[module.id] = module
|
||||
|
|
@ -924,8 +997,9 @@ class ModulesManager:
|
|||
"""Enabled modules initialized by the manager.
|
||||
|
||||
Returns:
|
||||
list: Initialized and enabled modules.
|
||||
list[AYONAddon]: Initialized and enabled modules.
|
||||
"""
|
||||
|
||||
return [
|
||||
module
|
||||
for module in self.modules
|
||||
|
|
@ -1108,7 +1182,7 @@ class ModulesManager:
|
|||
host_name (str): Host name for which is found host module.
|
||||
|
||||
Returns:
|
||||
OpenPypeModule: Found host module by name.
|
||||
AYONAddon: Found host module by name.
|
||||
None: There was not found module inheriting IHostAddon which has
|
||||
host name set to passed 'host_name'.
|
||||
"""
|
||||
|
|
@ -1129,12 +1203,11 @@ class ModulesManager:
|
|||
inheriting 'IHostAddon'.
|
||||
"""
|
||||
|
||||
host_names = {
|
||||
return {
|
||||
module.host_name
|
||||
for module in self.get_enabled_modules()
|
||||
if isinstance(module, IHostAddon)
|
||||
}
|
||||
return host_names
|
||||
|
||||
def print_report(self):
|
||||
"""Print out report of time spent on modules initialization parts.
|
||||
|
|
@ -1290,6 +1363,10 @@ class TrayModulesManager(ModulesManager):
|
|||
callback can be defined with `doubleclick_callback` attribute.
|
||||
|
||||
Missing feature how to define default callback.
|
||||
|
||||
Args:
|
||||
addon (AYONAddon): Addon object.
|
||||
callback (FunctionType): Function callback.
|
||||
"""
|
||||
callback_name = "_".join([module.name, callback.__name__])
|
||||
if callback_name not in self.doubleclick_callbacks:
|
||||
|
|
@ -1310,11 +1387,17 @@ class TrayModulesManager(ModulesManager):
|
|||
self.tray_menu(tray_menu)
|
||||
|
||||
def get_enabled_tray_modules(self):
|
||||
output = []
|
||||
for module in self.modules:
|
||||
if module.enabled and isinstance(module, ITrayModule):
|
||||
output.append(module)
|
||||
return output
|
||||
"""Enabled tray modules.
|
||||
|
||||
Returns:
|
||||
list[AYONAddon]: Enabled addons that inherit from tray interface.
|
||||
"""
|
||||
|
||||
return [
|
||||
module
|
||||
for module in self.modules
|
||||
if module.enabled and isinstance(module, ITrayModule)
|
||||
]
|
||||
|
||||
def restart_tray(self):
|
||||
if self.tray_manager:
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
|||
cls.group = settings.get("group", cls.group)
|
||||
cls.strict_error_checking = settings.get("strict_error_checking",
|
||||
cls.strict_error_checking)
|
||||
cls.jobInfo = settings.get("jobInfo", cls.jobInfo)
|
||||
cls.pluginInfo = settings.get("pluginInfo", cls.pluginInfo)
|
||||
|
||||
def get_job_info(self):
|
||||
job_info = DeadlineJobInfo(Plugin="MayaBatch")
|
||||
|
|
@ -648,7 +650,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
|||
return job_info, attr.asdict(plugin_info)
|
||||
|
||||
def _get_arnold_render_payload(self, data):
|
||||
|
||||
from maya import cmds
|
||||
# Job Info
|
||||
job_info = copy.deepcopy(self.job_info)
|
||||
job_info.Name = self._job_info_label("Render")
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ class CreatePublishRoyalRenderJob(pyblish.api.InstancePlugin,
|
|||
|
||||
environment = RREnvList({
|
||||
"AVALON_PROJECT": anatomy_data["project"]["name"],
|
||||
"AVALON_ASSET": anatomy_data["asset"],
|
||||
"AVALON_ASSET": instance.context.data["asset"],
|
||||
"AVALON_TASK": anatomy_data["task"]["name"],
|
||||
"OPENPYPE_USERNAME": anatomy_data["user"]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ class TimersManager(
|
|||
return {
|
||||
"project_name": project_name,
|
||||
"asset_id": str(asset_doc["_id"]),
|
||||
"asset_name": asset_doc["name"],
|
||||
"asset_name": asset_name,
|
||||
"task_name": task_name,
|
||||
"task_type": task_type,
|
||||
"hierarchy": hierarchy_items
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from openpype.client import (
|
|||
get_asset_by_id,
|
||||
get_asset_by_name,
|
||||
version_is_latest,
|
||||
get_asset_name_identifier,
|
||||
get_ayon_server_api_connection,
|
||||
)
|
||||
from openpype.lib.events import emit_event
|
||||
|
|
@ -44,7 +45,7 @@ from . import (
|
|||
|
||||
_is_installed = False
|
||||
_process_id = None
|
||||
_registered_root = {"_": ""}
|
||||
_registered_root = {"_": {}}
|
||||
_registered_host = {"_": None}
|
||||
# Keep modules manager (and it's modules) in memory
|
||||
# - that gives option to register modules' callbacks
|
||||
|
|
@ -85,15 +86,22 @@ def register_root(path):
|
|||
|
||||
|
||||
def registered_root():
|
||||
"""Return currently registered root"""
|
||||
root = _registered_root["_"]
|
||||
if root:
|
||||
return root
|
||||
"""Return registered roots from current project anatomy.
|
||||
|
||||
root = legacy_io.Session.get("AVALON_PROJECTS")
|
||||
if root:
|
||||
return os.path.normpath(root)
|
||||
return ""
|
||||
Consider this does return roots only for current project and current
|
||||
platforms, only if host was installer using 'install_host'.
|
||||
|
||||
Deprecated:
|
||||
Please use project 'Anatomy' to get roots. This function is still used
|
||||
at current core functions of load logic, but that will change
|
||||
in future and this function will be removed eventually. Using this
|
||||
function at new places can cause problems in the future.
|
||||
|
||||
Returns:
|
||||
dict[str, str]: Root paths.
|
||||
"""
|
||||
|
||||
return _registered_root["_"]
|
||||
|
||||
|
||||
def install_host(host):
|
||||
|
|
@ -592,14 +600,12 @@ def compute_session_changes(
|
|||
Dict[str, str]: Changes in the Session dictionary.
|
||||
"""
|
||||
|
||||
changes = {}
|
||||
|
||||
# Get asset document and asset
|
||||
if not asset_doc:
|
||||
task_name = None
|
||||
asset_name = None
|
||||
else:
|
||||
asset_name = asset_doc["name"]
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
|
||||
# Detect any changes compared session
|
||||
mapping = {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,12 @@ from contextlib import contextmanager
|
|||
import pyblish.logic
|
||||
import pyblish.api
|
||||
|
||||
from openpype.client import get_assets, get_asset_by_name
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import (
|
||||
get_assets,
|
||||
get_asset_by_name,
|
||||
get_asset_name_identifier,
|
||||
)
|
||||
from openpype.settings import (
|
||||
get_system_settings,
|
||||
get_project_settings
|
||||
|
|
@ -922,9 +927,19 @@ class CreatedInstance:
|
|||
self._orig_data = copy.deepcopy(data)
|
||||
|
||||
# Pop family and subset to prevent unexpected changes
|
||||
# TODO change to 'productType' and 'productName' in AYON
|
||||
data.pop("family", None)
|
||||
data.pop("subset", None)
|
||||
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = data.pop("asset", None)
|
||||
if "folderPath" not in data:
|
||||
data["folderPath"] = asset_name
|
||||
|
||||
elif "folderPath" in data:
|
||||
asset_name = data.pop("folderPath").split("/")[-1]
|
||||
if "asset" not in data:
|
||||
data["asset"] = asset_name
|
||||
|
||||
# QUESTION Does it make sense to have data stored as ordered dict?
|
||||
self._data = collections.OrderedDict()
|
||||
|
|
@ -1268,6 +1283,8 @@ class CreatedInstance:
|
|||
def has_set_asset(self):
|
||||
"""Asset name is set in data."""
|
||||
|
||||
if AYON_SERVER_ENABLED:
|
||||
return "folderPath" in self._data
|
||||
return "asset" in self._data
|
||||
|
||||
@property
|
||||
|
|
@ -2003,8 +2020,14 @@ class CreateContext:
|
|||
project_name,
|
||||
self.host_name
|
||||
)
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name_key = "folderPath"
|
||||
else:
|
||||
asset_name_key = "asset"
|
||||
|
||||
instance_data = {
|
||||
"asset": asset_doc["name"],
|
||||
asset_name_key: asset_name,
|
||||
"task": task_name,
|
||||
"family": creator.family,
|
||||
"variant": variant
|
||||
|
|
@ -2229,34 +2252,51 @@ class CreateContext:
|
|||
task_names_by_asset_name = {}
|
||||
for instance in instances:
|
||||
task_name = instance.get("task")
|
||||
asset_name = instance.get("asset")
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = instance.get("folderPath")
|
||||
else:
|
||||
asset_name = instance.get("asset")
|
||||
if asset_name:
|
||||
task_names_by_asset_name[asset_name] = set()
|
||||
if task_name:
|
||||
task_names_by_asset_name[asset_name].add(task_name)
|
||||
|
||||
asset_names = [
|
||||
asset_names = {
|
||||
asset_name
|
||||
for asset_name in task_names_by_asset_name.keys()
|
||||
if asset_name is not None
|
||||
]
|
||||
}
|
||||
fields = {"name", "data.tasks"}
|
||||
if AYON_SERVER_ENABLED:
|
||||
fields |= {"data.parents"}
|
||||
asset_docs = list(get_assets(
|
||||
self.project_name,
|
||||
asset_names=asset_names,
|
||||
fields=["name", "data.tasks"]
|
||||
fields=fields
|
||||
))
|
||||
|
||||
task_names_by_asset_name = {}
|
||||
asset_docs_by_name = collections.defaultdict(list)
|
||||
for asset_doc in asset_docs:
|
||||
asset_name = asset_doc["name"]
|
||||
asset_name = get_asset_name_identifier(asset_doc)
|
||||
tasks = asset_doc.get("data", {}).get("tasks") or {}
|
||||
task_names_by_asset_name[asset_name] = set(tasks.keys())
|
||||
asset_docs_by_name[asset_doc["name"]].append(asset_doc)
|
||||
|
||||
for instance in instances:
|
||||
if not instance.has_valid_asset or not instance.has_valid_task:
|
||||
continue
|
||||
|
||||
asset_name = instance["asset"]
|
||||
if AYON_SERVER_ENABLED:
|
||||
asset_name = instance["folderPath"]
|
||||
if asset_name and "/" not in asset_name:
|
||||
asset_docs = asset_docs_by_name.get(asset_name)
|
||||
if len(asset_docs) == 1:
|
||||
asset_name = get_asset_name_identifier(asset_docs[0])
|
||||
instance["folderPath"] = asset_name
|
||||
else:
|
||||
asset_name = instance["asset"]
|
||||
|
||||
if asset_name not in task_names_by_asset_name:
|
||||
instance.set_asset_invalid(True)
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import collections
|
||||
|
||||
from openpype.client import get_assets, get_subsets, get_last_versions
|
||||
from openpype.client import (
|
||||
get_assets,
|
||||
get_subsets,
|
||||
get_last_versions,
|
||||
get_asset_name_identifier,
|
||||
)
|
||||
|
||||
|
||||
def get_last_versions_for_instances(
|
||||
|
|
@ -52,10 +57,10 @@ def get_last_versions_for_instances(
|
|||
asset_docs = get_assets(
|
||||
project_name,
|
||||
asset_names=subset_names_by_asset_name.keys(),
|
||||
fields=["name", "_id"]
|
||||
fields=["name", "_id", "data.parents"]
|
||||
)
|
||||
asset_names_by_id = {
|
||||
asset_doc["_id"]: asset_doc["name"]
|
||||
asset_doc["_id"]: get_asset_name_identifier(asset_doc)
|
||||
for asset_doc in asset_docs
|
||||
}
|
||||
if not asset_names_by_id:
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ def install():
|
|||
|
||||
session = session_data_from_environment(context_keys=True)
|
||||
|
||||
session["schema"] = "openpype:session-3.0"
|
||||
session["schema"] = "openpype:session-4.0"
|
||||
try:
|
||||
schema.validate(session)
|
||||
except schema.ValidationError as e:
|
||||
|
|
|
|||
|
|
@ -62,8 +62,6 @@ def auto_reconnect(func):
|
|||
|
||||
|
||||
SESSION_CONTEXT_KEYS = (
|
||||
# Root directory of projects on disk
|
||||
"AVALON_PROJECTS",
|
||||
# Name of current Project
|
||||
"AVALON_PROJECT",
|
||||
# Name of current Asset
|
||||
|
|
|
|||
61
openpype/pipeline/schema/session-4.0.json
Normal file
61
openpype/pipeline/schema/session-4.0.json
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:session-4.0",
|
||||
"description": "The Avalon environment",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"AVALON_PROJECT"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"AVALON_PROJECT": {
|
||||
"description": "Name of project",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "Hulk"
|
||||
},
|
||||
"AVALON_ASSET": {
|
||||
"description": "Name of asset",
|
||||
"type": "string",
|
||||
"pattern": "^[\\/\\w]*$",
|
||||
"example": "Bruce"
|
||||
},
|
||||
"AVALON_TASK": {
|
||||
"description": "Name of task",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "modeling"
|
||||
},
|
||||
"AVALON_APP": {
|
||||
"description": "Name of host",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "maya"
|
||||
},
|
||||
"AVALON_DB": {
|
||||
"description": "Name of database",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "avalon",
|
||||
"default": "avalon"
|
||||
},
|
||||
"AVALON_LABEL": {
|
||||
"description": "Nice name of Avalon, used in e.g. graphical user interfaces",
|
||||
"type": "string",
|
||||
"example": "MyLabel",
|
||||
"default": "Avalon"
|
||||
},
|
||||
"AVALON_TIMEOUT": {
|
||||
"description": "Wherever there is a need for a timeout, this is the default value.",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]*$",
|
||||
"default": "1000",
|
||||
"example": "1000"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,8 @@ import pyblish.api
|
|||
from openpype.client import (
|
||||
get_assets,
|
||||
get_subsets,
|
||||
get_last_versions
|
||||
get_last_versions,
|
||||
get_asset_name_identifier,
|
||||
)
|
||||
from openpype.pipeline.version_start import get_versioning_start
|
||||
|
||||
|
|
@ -60,6 +61,9 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
self.log.debug("Querying asset documents for instances.")
|
||||
|
||||
context_asset_doc = context.data.get("assetEntity")
|
||||
context_asset_name = None
|
||||
if context_asset_doc:
|
||||
context_asset_name = get_asset_name_identifier(context_asset_doc)
|
||||
|
||||
instances_with_missing_asset_doc = collections.defaultdict(list)
|
||||
for instance in context:
|
||||
|
|
@ -68,15 +72,15 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
|
||||
# There is possibility that assetEntity on instance is already set
|
||||
# which can happen in standalone publisher
|
||||
if (
|
||||
instance_asset_doc
|
||||
and instance_asset_doc["name"] == _asset_name
|
||||
):
|
||||
continue
|
||||
if instance_asset_doc:
|
||||
instance_asset_name = get_asset_name_identifier(
|
||||
instance_asset_doc)
|
||||
if instance_asset_name == _asset_name:
|
||||
continue
|
||||
|
||||
# Check if asset name is the same as what is in context
|
||||
# - they may be different, e.g. in NukeStudio
|
||||
if context_asset_doc and context_asset_doc["name"] == _asset_name:
|
||||
if context_asset_name and context_asset_name == _asset_name:
|
||||
instance.data["assetEntity"] = context_asset_doc
|
||||
|
||||
else:
|
||||
|
|
@ -93,7 +97,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
|
||||
asset_docs = get_assets(project_name, asset_names=asset_names)
|
||||
asset_docs_by_name = {
|
||||
asset_doc["name"]: asset_doc
|
||||
get_asset_name_identifier(asset_doc): asset_doc
|
||||
for asset_doc in asset_docs
|
||||
}
|
||||
|
||||
|
|
@ -183,35 +187,29 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
self.log.debug("Storing anatomy data to instance data.")
|
||||
|
||||
project_doc = context.data["projectEntity"]
|
||||
context_asset_doc = context.data.get("assetEntity")
|
||||
|
||||
project_task_types = project_doc["config"]["tasks"]
|
||||
|
||||
for instance in context:
|
||||
asset_doc = instance.data.get("assetEntity")
|
||||
anatomy_updates = {
|
||||
"asset": instance.data["asset"],
|
||||
"folder": {
|
||||
"name": instance.data["asset"],
|
||||
},
|
||||
"family": instance.data["family"],
|
||||
"subset": instance.data["subset"],
|
||||
}
|
||||
|
||||
# Hierarchy
|
||||
asset_doc = instance.data.get("assetEntity")
|
||||
if (
|
||||
asset_doc
|
||||
and (
|
||||
not context_asset_doc
|
||||
or asset_doc["_id"] != context_asset_doc["_id"]
|
||||
)
|
||||
):
|
||||
if asset_doc:
|
||||
parents = asset_doc["data"].get("parents") or list()
|
||||
parent_name = project_doc["name"]
|
||||
if parents:
|
||||
parent_name = parents[-1]
|
||||
anatomy_updates["hierarchy"] = "/".join(parents)
|
||||
anatomy_updates["parent"] = parent_name
|
||||
|
||||
hierarchy = "/".join(parents)
|
||||
anatomy_updates.update({
|
||||
"asset": asset_doc["name"],
|
||||
"hierarchy": hierarchy,
|
||||
"parent": parent_name,
|
||||
"folder": {
|
||||
"name": asset_doc["name"],
|
||||
},
|
||||
})
|
||||
|
||||
# Task
|
||||
task_type = None
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from openpype.client import (
|
|||
get_subsets,
|
||||
get_last_versions,
|
||||
get_representations,
|
||||
get_asset_name_identifier,
|
||||
)
|
||||
from openpype.pipeline.load import get_representation_path_with_anatomy
|
||||
|
||||
|
|
@ -121,12 +122,13 @@ class CollectAudio(pyblish.api.ContextPlugin):
|
|||
asset_docs = get_assets(
|
||||
project_name,
|
||||
asset_names=asset_names,
|
||||
fields=["_id", "name"]
|
||||
fields=["_id", "name", "data.parents"]
|
||||
)
|
||||
|
||||
asset_id_by_name = {}
|
||||
for asset_doc in asset_docs:
|
||||
asset_id_by_name[asset_doc["name"]] = asset_doc["_id"]
|
||||
asset_id_by_name = {
|
||||
get_asset_name_identifier(asset_doc): asset_doc["_id"]
|
||||
for asset_doc in asset_docs
|
||||
}
|
||||
asset_ids = set(asset_id_by_name.values())
|
||||
|
||||
# Query subsets with name define by 'audio_subset_name' attr
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.host import IPublishHost
|
||||
from openpype.pipeline import legacy_io, registered_host
|
||||
from openpype.pipeline.create import CreateContext
|
||||
|
|
@ -38,6 +39,8 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin):
|
|||
|
||||
for created_instance in create_context.instances:
|
||||
instance_data = created_instance.data_to_store()
|
||||
if AYON_SERVER_ENABLED:
|
||||
instance_data["asset"] = instance_data.pop("folderPath")
|
||||
if instance_data["active"]:
|
||||
thumbnail_path = thumbnail_paths_by_instance_id.get(
|
||||
created_instance.id
|
||||
|
|
|
|||
|
|
@ -69,9 +69,9 @@ class CollectResourcesPath(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
# editorial would fail since they might not be in database yet
|
||||
is_editorial = instance.data.get("isEditorial")
|
||||
if is_editorial:
|
||||
self.log.debug("Instance is Editorial. Skipping.")
|
||||
new_asset_publishing = instance.data.get("newAssetPublishing")
|
||||
if new_asset_publishing:
|
||||
self.log.debug("Instance is creating new asset. Skipping.")
|
||||
return
|
||||
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from ayon_api import slugify_string
|
|||
from ayon_api.entity_hub import EntityHub
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
from openpype.client import get_assets
|
||||
from openpype.client import get_assets, get_asset_name_identifier
|
||||
from openpype.pipeline.template_data import (
|
||||
get_asset_template_data,
|
||||
get_task_template_data,
|
||||
|
|
@ -58,7 +58,7 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin):
|
|||
project_name, asset_names=instances_by_asset_name.keys()
|
||||
)
|
||||
asset_docs_by_name = {
|
||||
asset_doc["name"]: asset_doc
|
||||
get_asset_name_identifier(asset_doc): asset_doc
|
||||
for asset_doc in asset_docs
|
||||
}
|
||||
for asset_name, instances in instances_by_asset_name.items():
|
||||
|
|
@ -191,15 +191,15 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin):
|
|||
"""
|
||||
|
||||
# filter only the active publishing instances
|
||||
active_folder_names = set()
|
||||
active_folder_paths = set()
|
||||
for instance in context:
|
||||
if instance.data.get("publish") is not False:
|
||||
active_folder_names.add(instance.data.get("asset"))
|
||||
active_folder_paths.add(instance.data.get("asset"))
|
||||
|
||||
active_folder_names.discard(None)
|
||||
active_folder_paths.discard(None)
|
||||
|
||||
self.log.debug("Active folder names: {}".format(active_folder_names))
|
||||
if not active_folder_names:
|
||||
self.log.debug("Active folder paths: {}".format(active_folder_paths))
|
||||
if not active_folder_paths:
|
||||
return None
|
||||
|
||||
project_item = None
|
||||
|
|
@ -230,12 +230,13 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin):
|
|||
if not children_context:
|
||||
continue
|
||||
|
||||
for asset_name, asset_info in children_context.items():
|
||||
for asset, asset_info in children_context.items():
|
||||
if (
|
||||
asset_name not in active_folder_names
|
||||
asset not in active_folder_paths
|
||||
and not asset_info.get("childs")
|
||||
):
|
||||
continue
|
||||
asset_name = asset.split("/")[-1]
|
||||
item_id = uuid.uuid4().hex
|
||||
new_item = copy.deepcopy(asset_info)
|
||||
new_item["name"] = asset_name
|
||||
|
|
@ -252,7 +253,7 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin):
|
|||
items_by_id[item_id] = new_item
|
||||
parent_id_by_item_id[item_id] = parent_id
|
||||
|
||||
if asset_name in active_folder_names:
|
||||
if asset in active_folder_paths:
|
||||
valid_ids.add(item_id)
|
||||
hierarchy_queue.append((item_id, new_children_context))
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from pprint import pformat
|
|||
|
||||
import pyblish.api
|
||||
|
||||
from openpype.client import get_assets
|
||||
from openpype.client import get_assets, get_asset_name_identifier
|
||||
|
||||
|
||||
class ValidateEditorialAssetName(pyblish.api.ContextPlugin):
|
||||
|
|
@ -34,8 +34,11 @@ class ValidateEditorialAssetName(pyblish.api.ContextPlugin):
|
|||
self.log.debug("__ db_assets: {}".format(db_assets))
|
||||
|
||||
asset_db_docs = {
|
||||
str(e["name"]): [str(p) for p in e["data"]["parents"]]
|
||||
for e in db_assets}
|
||||
get_asset_name_identifier(asset_doc): list(
|
||||
asset_doc["data"]["parents"]
|
||||
)
|
||||
for asset_doc in db_assets
|
||||
}
|
||||
|
||||
self.log.debug("__ project_entities: {}".format(
|
||||
pformat(asset_db_docs)))
|
||||
|
|
|
|||
|
|
@ -940,6 +940,23 @@ def _convert_photoshop_project_settings(ayon_settings, output):
|
|||
output["photoshop"] = ayon_photoshop
|
||||
|
||||
|
||||
def _convert_substancepainter_project_settings(ayon_settings, output):
|
||||
if "substancepainter" not in ayon_settings:
|
||||
return
|
||||
|
||||
ayon_substance_painter = ayon_settings["substancepainter"]
|
||||
_convert_host_imageio(ayon_substance_painter)
|
||||
if "shelves" in ayon_substance_painter:
|
||||
shelves_items = ayon_substance_painter["shelves"]
|
||||
new_shelves_items = {
|
||||
item["name"]: item["value"]
|
||||
for item in shelves_items
|
||||
}
|
||||
ayon_substance_painter["shelves"] = new_shelves_items
|
||||
|
||||
output["substancepainter"] = ayon_substance_painter
|
||||
|
||||
|
||||
def _convert_tvpaint_project_settings(ayon_settings, output):
|
||||
if "tvpaint" not in ayon_settings:
|
||||
return
|
||||
|
|
@ -1398,6 +1415,7 @@ def convert_project_settings(ayon_settings, default_settings):
|
|||
_convert_nuke_project_settings(ayon_settings, output)
|
||||
_convert_hiero_project_settings(ayon_settings, output)
|
||||
_convert_photoshop_project_settings(ayon_settings, output)
|
||||
_convert_substancepainter_project_settings(ayon_settings, output)
|
||||
_convert_tvpaint_project_settings(ayon_settings, output)
|
||||
_convert_traypublisher_project_settings(ayon_settings, output)
|
||||
_convert_webpublisher_project_settings(ayon_settings, output)
|
||||
|
|
@ -1573,3 +1591,18 @@ def get_ayon_system_settings(default_values):
|
|||
return convert_system_settings(
|
||||
ayon_settings, default_values, addon_versions
|
||||
)
|
||||
|
||||
|
||||
def get_ayon_settings(project_name=None):
|
||||
"""AYON studio settings.
|
||||
|
||||
Raw AYON settings values.
|
||||
|
||||
Args:
|
||||
project_name (Optional[str]): Project name.
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: AYON settings.
|
||||
"""
|
||||
|
||||
return _AyonSettingsCache.get_value_by_project(project_name)
|
||||
|
|
|
|||
|
|
@ -210,5 +210,8 @@
|
|||
"darwin": "",
|
||||
"linux": ""
|
||||
}
|
||||
},
|
||||
"asset_reporter": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,6 +355,20 @@
|
|||
{
|
||||
"type": "dynamic_schema",
|
||||
"name": "system_settings/modules"
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "asset_reporter",
|
||||
"label": "Asset Usage Reporter",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ def save_studio_settings(data):
|
|||
clear_metadata_from_settings(new_data)
|
||||
|
||||
changes = calculate_changes(old_data, new_data)
|
||||
modules_manager = ModulesManager(_system_settings=new_data)
|
||||
modules_manager = ModulesManager(new_data)
|
||||
|
||||
warnings = []
|
||||
for module in modules_manager.get_enabled_modules():
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue