Merge branch 'develop' into bugfix/houdini_license_validator_missing_families

This commit is contained in:
Kayla Man 2023-11-24 21:38:29 +08:00 committed by GitHub
commit 4da20799d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
122 changed files with 1902 additions and 484 deletions

View file

@ -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

View file

@ -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",
)

View file

@ -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)

View file

@ -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(

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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'],

View file

@ -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

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 = {

View file

@ -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:

View 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}")

View file

@ -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}")

View file

@ -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"],

View file

@ -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": []

View file

@ -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

View file

@ -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. """

View file

@ -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

View file

@ -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"]
})

View file

@ -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
}

View file

@ -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"])
)

View file

@ -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)

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"],

View file

@ -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:

View file

@ -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,

View file

@ -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)

View file

@ -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

View file

@ -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,

View file

@ -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. """

View file

@ -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:

View file

@ -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"]

View file

@ -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

View file

@ -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)

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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(

View file

@ -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,

View file

@ -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,

View file

@ -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):

View file

@ -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))

View file

@ -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,

View file

@ -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": []

View file

@ -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,

View file

@ -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,

View file

@ -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(

View file

@ -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

View file

@ -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
}

View file

@ -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(

View file

@ -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"],

View file

@ -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)

View file

@ -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,

View file

@ -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)

View file

@ -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"]

View file

@ -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"]

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -10,6 +10,7 @@ from .interfaces import (
)
from .base import (
AYONAddon,
OpenPypeModule,
OpenPypeAddOn,
@ -35,6 +36,7 @@ __all__ = (
"ISettingsChangeListener",
"IHostAddon",
"AYONAddon",
"OpenPypeModule",
"OpenPypeAddOn",

View file

@ -0,0 +1,8 @@
from .module import (
AssetReporterAction
)
__all__ = (
"AssetReporterAction",
)

View 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)

View 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()

View file

@ -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:

View file

@ -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")

View file

@ -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"]
})

View file

@ -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

View file

@ -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 = {

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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

View 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"
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"]

View file

@ -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))

View file

@ -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)))

View file

@ -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)

View file

@ -210,5 +210,8 @@
"darwin": "",
"linux": ""
}
},
"asset_reporter": {
"enabled": false
}
}

View file

@ -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"
}
]
}
]
}

View file

@ -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