Merge branch 'develop' into enhancement/OP-6458-ue_building_shot_structure_from_db

This commit is contained in:
Simone Barbieri 2024-03-01 10:56:53 +00:00
commit c6fe13d7fb
643 changed files with 8631 additions and 17132 deletions

View file

@ -0,0 +1,28 @@
name: Sync Issues to ClickUp [trigger]
on:
workflow_dispatch:
inputs:
issue-number:
required: true
issues:
types: [labeled]
jobs:
call-ci-tools-issue-sync:
if: github.event.inputs.issue-number != '' || github.event_name == 'issues' && contains(github.event.issue.labels.*.name, 'backlog')
uses: ynput/ci-tools/.github/workflows/issue_to_clickup_ref.yml@main
with:
# issue number should be taken either from inputs or from the event
issue-number: ${{ github.event.inputs.issue-number || github.event.issue.number }}
repo-owner: ${{ github.event.repository.owner.login }}
repo-name: ${{ github.event.repository.name }}
secrets:
token: ${{ secrets.YNPUT_BOT_TOKEN }}
cu_api_key: ${{ secrets.CLICKUP_API_KEY }}
cu_team_id: ${{ secrets.CLICKUP_TEAM_ID }}
cu_folder_id: ${{ secrets.CLICKUP_FOLDER_ID }}
cu_list_id: ${{ secrets.CLICKUP_LIST_ID }}
cu_field_domain_id: ${{ secrets.CLICKUP_DOMAIN_FIELD_ID }}
cu_field_type_id: ${{ secrets.CLICKUP_ISSUETYPE_FIELD_ID }}

View file

@ -15,13 +15,9 @@ from abc import ABCMeta, abstractmethod
import six import six
import appdirs import appdirs
from ayon_core.lib import Logger from ayon_core.lib import Logger, is_dev_mode_enabled
from ayon_core.client import get_ayon_server_api_connection from ayon_core.client import get_ayon_server_api_connection
from ayon_core.settings import get_system_settings from ayon_core.settings import get_studio_settings
from ayon_core.settings.ayon_settings import (
is_dev_mode_enabled,
get_ayon_settings,
)
from .interfaces import ( from .interfaces import (
IPluginPaths, IPluginPaths,
@ -648,7 +644,6 @@ class AddonsManager:
def __init__(self, settings=None, initialize=True): def __init__(self, settings=None, initialize=True):
self._settings = settings self._settings = settings
self._system_settings = None
self._addons = [] self._addons = []
self._addons_by_id = {} self._addons_by_id = {}
@ -738,14 +733,9 @@ class AddonsManager:
# Prepare settings for addons # Prepare settings for addons
settings = self._settings settings = self._settings
if settings is None: if settings is None:
settings = get_ayon_settings() settings = get_studio_settings()
# OpenPype settings modules_settings = {}
system_settings = self._system_settings
if system_settings is None:
system_settings = get_system_settings()
modules_settings = system_settings["modules"]
report = {} report = {}
time_start = time.time() time_start = time.time()

View file

@ -106,7 +106,7 @@ class Commands:
context = get_global_context() context = get_global_context()
env = get_app_environments_for_context( env = get_app_environments_for_context(
context["project_name"], context["project_name"],
context["asset_name"], context["folder_path"],
context["task_name"], context["task_name"],
app_full_name, app_full_name,
launch_type=LaunchTypes.farm_publish, launch_type=LaunchTypes.farm_publish,

View file

@ -27,7 +27,8 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook):
"tvpaint", "tvpaint",
"substancepainter", "substancepainter",
"aftereffects", "aftereffects",
"wrap" "wrap",
"openrv"
} }
launch_types = {LaunchTypes.local} launch_types = {LaunchTypes.local}

View file

@ -54,7 +54,7 @@ class CopyTemplateWorkfile(PreLaunchHook):
self.log.info("Last workfile does not exist.") self.log.info("Last workfile does not exist.")
project_name = self.data["project_name"] project_name = self.data["project_name"]
asset_name = self.data["asset_name"] asset_name = self.data["folder_path"]
task_name = self.data["task_name"] task_name = self.data["task_name"]
host_name = self.application.host_name host_name = self.application.host_name

View file

@ -22,7 +22,7 @@ class GlobalHostDataHook(PreLaunchHook):
app = self.launch_context.application app = self.launch_context.application
temp_data = EnvironmentPrepData({ temp_data = EnvironmentPrepData({
"project_name": self.data["project_name"], "project_name": self.data["project_name"],
"asset_name": self.data["asset_name"], "folder_path": self.data["folder_path"],
"task_name": self.data["task_name"], "task_name": self.data["task_name"],
"app": app, "app": app,
@ -66,7 +66,7 @@ class GlobalHostDataHook(PreLaunchHook):
project_doc = get_project(project_name) project_doc = get_project(project_name)
self.data["project_doc"] = project_doc self.data["project_doc"] = project_doc
asset_name = self.data.get("asset_name") asset_name = self.data.get("folder_path")
if not asset_name: if not asset_name:
self.log.warning( self.log.warning(
"Asset name was not set. Skipping asset document query." "Asset name was not set. Skipping asset document query."

View file

@ -19,6 +19,7 @@ class OCIOEnvHook(PreLaunchHook):
"nuke", "nuke",
"hiero", "hiero",
"resolve", "resolve",
"openrv"
} }
launch_types = set() launch_types = set()
@ -27,10 +28,10 @@ class OCIOEnvHook(PreLaunchHook):
template_data = get_template_data_with_names( template_data = get_template_data_with_names(
project_name=self.data["project_name"], project_name=self.data["project_name"],
asset_name=self.data["asset_name"], asset_name=self.data["folder_path"],
task_name=self.data["task_name"], task_name=self.data["task_name"],
host_name=self.host_name, host_name=self.host_name,
system_settings=self.data["system_settings"] settings=self.data["project_settings"]
) )
config_data = get_imageio_config( config_data = get_imageio_config(

View file

@ -181,6 +181,10 @@ class HostDirmap(object):
exclude_locals=False, exclude_locals=False,
cached=False) cached=False)
# TODO implement
# Dirmap is dependent on 'get_site_local_overrides' which
# is not implemented in AYON. The mapping should be received
# from sitesync addon.
active_overrides = get_site_local_overrides( active_overrides = get_site_local_overrides(
project_name, active_site) project_name, active_site)
remote_overrides = get_site_local_overrides( remote_overrides = get_site_local_overrides(

View file

@ -134,12 +134,12 @@ class HostBase(object):
Returns: Returns:
Dict[str, Union[str, None]]: Context with 3 keys 'project_name', Dict[str, Union[str, None]]: Context with 3 keys 'project_name',
'asset_name' and 'task_name'. All of them can be 'None'. 'folder_path' and 'task_name'. All of them can be 'None'.
""" """
return { return {
"project_name": self.get_current_project_name(), "project_name": self.get_current_project_name(),
"asset_name": self.get_current_asset_name(), "folder_path": self.get_current_asset_name(),
"task_name": self.get_current_task_name() "task_name": self.get_current_task_name()
} }
@ -161,7 +161,7 @@ class HostBase(object):
# Use current context to fill the context title # Use current context to fill the context title
current_context = self.get_current_context() current_context = self.get_current_context()
project_name = current_context["project_name"] project_name = current_context["project_name"]
asset_name = current_context["asset_name"] asset_name = current_context["folder_path"]
task_name = current_context["task_name"] task_name = current_context["task_name"]
items = [] items = []
if project_name: if project_name:

View file

@ -128,7 +128,7 @@ def set_settings(frames, resolution, comp_ids=None, print_msg=True):
current_context = get_current_context() current_context = get_current_context()
asset_doc = get_asset_by_name(current_context["project_name"], asset_doc = get_asset_by_name(current_context["project_name"],
current_context["asset_name"]) current_context["folder_path"])
settings = get_asset_settings(asset_doc) settings = get_asset_settings(asset_doc)
msg = '' msg = ''

View file

@ -9,6 +9,8 @@ from ayon_core.pipeline import (
register_loader_plugin_path, register_loader_plugin_path,
register_creator_plugin_path, register_creator_plugin_path,
AVALON_CONTAINER_ID, AVALON_CONTAINER_ID,
AVALON_INSTANCE_ID,
AYON_INSTANCE_ID,
) )
from ayon_core.hosts.aftereffects.api.workfile_template_builder import ( from ayon_core.hosts.aftereffects.api.workfile_template_builder import (
AEPlaceholderLoadPlugin, AEPlaceholderLoadPlugin,
@ -142,7 +144,9 @@ class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
layers_meta = stub.get_metadata() layers_meta = stub.get_metadata()
for instance in layers_meta: for instance in layers_meta:
if instance.get("id") == "pyblish.avalon.instance": if instance.get("id") in {
AYON_INSTANCE_ID, AVALON_INSTANCE_ID
}:
instances.append(instance) instances.append(instance)
return instances return instances

View file

@ -11,7 +11,7 @@ from ayon_core.pipeline import (
from ayon_core.hosts.aftereffects.api.pipeline import cache_and_get_instances from ayon_core.hosts.aftereffects.api.pipeline import cache_and_get_instances
from ayon_core.hosts.aftereffects.api.lib import set_settings from ayon_core.hosts.aftereffects.api.lib import set_settings
from ayon_core.lib import prepare_template_data from ayon_core.lib import prepare_template_data
from ayon_core.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS from ayon_core.pipeline.create import PRODUCT_NAME_ALLOWED_SYMBOLS
class RenderCreator(Creator): class RenderCreator(Creator):
@ -22,7 +22,7 @@ class RenderCreator(Creator):
""" """
identifier = "render" identifier = "render"
label = "Render" label = "Render"
family = "render" product_type = "render"
description = "Render creator" description = "Render creator"
create_allow_context_change = True create_allow_context_change = True
@ -31,7 +31,7 @@ class RenderCreator(Creator):
mark_for_review = True mark_for_review = True
force_setting_values = True force_setting_values = True
def create(self, subset_name_from_ui, data, pre_create_data): def create(self, product_name, data, pre_create_data):
stub = api.get_stub() # only after After Effects is up stub = api.get_stub() # only after After Effects is up
try: try:
@ -58,33 +58,37 @@ class RenderCreator(Creator):
len(comps) > 1) len(comps) > 1)
for comp in comps: for comp in comps:
composition_name = re.sub( composition_name = re.sub(
"[^{}]+".format(SUBSET_NAME_ALLOWED_SYMBOLS), "[^{}]+".format(PRODUCT_NAME_ALLOWED_SYMBOLS),
"", "",
comp.name comp.name
) )
if use_composition_name: if use_composition_name:
if "{composition}" not in subset_name_from_ui.lower(): if "{composition}" not in product_name.lower():
subset_name_from_ui += "{Composition}" product_name += "{Composition}"
dynamic_fill = prepare_template_data({"composition": dynamic_fill = prepare_template_data({"composition":
composition_name}) composition_name})
subset_name = subset_name_from_ui.format(**dynamic_fill) comp_product_name = product_name.format(**dynamic_fill)
data["composition_name"] = composition_name data["composition_name"] = composition_name
else: else:
subset_name = subset_name_from_ui comp_product_name = re.sub(
subset_name = re.sub(r"\{composition\}", '', subset_name, r"\{composition\}",
flags=re.IGNORECASE) "",
product_name,
flags=re.IGNORECASE
)
for inst in self.create_context.instances: for inst in self.create_context.instances:
if subset_name == inst.subset_name: if comp_product_name == inst.product_name:
raise CreatorError("{} already exists".format( raise CreatorError("{} already exists".format(
inst.subset_name)) inst.product_name))
data["members"] = [comp.id] data["members"] = [comp.id]
data["orig_comp_name"] = composition_name data["orig_comp_name"] = composition_name
new_instance = CreatedInstance(self.family, subset_name, data, new_instance = CreatedInstance(
self) self.product_type, comp_product_name, data, self
)
if "farm" in pre_create_data: if "farm" in pre_create_data:
use_farm = pre_create_data["farm"] use_farm = pre_create_data["farm"]
new_instance.creator_attributes["farm"] = use_farm new_instance.creator_attributes["farm"] = use_farm
@ -96,7 +100,7 @@ class RenderCreator(Creator):
new_instance.data_to_store()) new_instance.data_to_store())
self._add_instance_to_context(new_instance) self._add_instance_to_context(new_instance)
stub.rename_item(comp.id, subset_name) stub.rename_item(comp.id, comp_product_name)
if self.force_setting_values: if self.force_setting_values:
set_settings(True, True, [comp.id], print_msg=False) set_settings(True, True, [comp.id], print_msg=False)
@ -107,7 +111,7 @@ class RenderCreator(Creator):
"selected by default.", "selected by default.",
default=True, label="Use selection"), default=True, label="Use selection"),
BoolDef("use_composition_name", BoolDef("use_composition_name",
label="Use composition name in subset"), label="Use composition name in product"),
UISeparatorDef(), UISeparatorDef(),
BoolDef("farm", label="Render on farm"), BoolDef("farm", label="Render on farm"),
BoolDef( BoolDef(
@ -133,9 +137,14 @@ class RenderCreator(Creator):
def collect_instances(self): def collect_instances(self):
for instance_data in cache_and_get_instances(self): for instance_data in cache_and_get_instances(self):
# legacy instances have family=='render' or 'renderLocal', use them # legacy instances have product_type=='render' or 'renderLocal', use them
creator_id = (instance_data.get("creator_identifier") or creator_id = instance_data.get("creator_identifier")
instance_data.get("family", '').replace("Local", '')) if not creator_id:
# NOTE this is for backwards compatibility but probably can be
# removed
creator_id = instance_data.get("family", "")
creator_id = creator_id.replace("Local", "")
if creator_id == self.identifier: if creator_id == self.identifier:
instance_data = self._handle_legacy(instance_data) instance_data = self._handle_legacy(instance_data)
instance = CreatedInstance.from_existing( instance = CreatedInstance.from_existing(
@ -147,10 +156,10 @@ class RenderCreator(Creator):
for created_inst, _changes in update_list: for created_inst, _changes in update_list:
api.get_stub().imprint(created_inst.get("instance_id"), api.get_stub().imprint(created_inst.get("instance_id"),
created_inst.data_to_store()) created_inst.data_to_store())
subset_change = _changes.get("subset") name_change = _changes.get("productName")
if subset_change: if name_change:
api.get_stub().rename_item(created_inst.data["members"][0], api.get_stub().rename_item(created_inst.data["members"][0],
subset_change.new_value) name_change.new_value)
def remove_instances(self, instances): def remove_instances(self, instances):
"""Removes metadata and renames to original comp name if available.""" """Removes metadata and renames to original comp name if available."""
@ -183,33 +192,34 @@ class RenderCreator(Creator):
def get_detail_description(self): def get_detail_description(self):
return """Creator for Render instances return """Creator for Render instances
Main publishable item in AfterEffects will be of `render` family. Main publishable item in AfterEffects will be of `render` product type.
Result of this item (instance) is picture sequence or video that could Result of this item (instance) is picture sequence or video that could
be a final delivery product or loaded and used in another DCCs. be a final delivery product or loaded and used in another DCCs.
Select single composition and create instance of 'render' family or Select single composition and create instance of 'render' product type
turn off 'Use selection' to create instance for all compositions. or turn off 'Use selection' to create instance for all compositions.
'Use composition name in subset' allows to explicitly add composition 'Use composition name in product' allows to explicitly add composition
name into created subset name. name into created product name.
Position of composition name could be set in Position of composition name could be set in
`project_settings/global/tools/creator/subset_name_profiles` with some `project_settings/global/tools/creator/product_name_profiles` with
form of '{composition}' placeholder. some form of '{composition}' placeholder.
Composition name will be used implicitly if multiple composition should Composition name will be used implicitly if multiple composition should
be handled at same time. be handled at same time.
If {composition} placeholder is not us 'subset_name_profiles' If {composition} placeholder is not us 'product_name_profiles'
composition name will be capitalized and set at the end of subset name composition name will be capitalized and set at the end of
if necessary. product name if necessary.
If composition name should be used, it will be cleaned up of characters If composition name should be used, it will be cleaned up of characters
that would cause an issue in published file names. that would cause an issue in published file names.
""" """
def get_dynamic_data(self, variant, task_name, asset_doc, def get_dynamic_data(
project_name, host_name, instance): self, project_name, asset_doc, task_name, variant, host_name, instance
):
dynamic_data = {} dynamic_data = {}
if instance is not None: if instance is not None:
composition_name = instance.get("composition_name") composition_name = instance.get("composition_name")
@ -234,9 +244,9 @@ class RenderCreator(Creator):
instance_data["task"] = self.create_context.get_current_task_name() instance_data["task"] = self.create_context.get_current_task_name()
if not instance_data.get("creator_attributes"): if not instance_data.get("creator_attributes"):
is_old_farm = instance_data["family"] != "renderLocal" is_old_farm = instance_data.get("family") != "renderLocal"
instance_data["creator_attributes"] = {"farm": is_old_farm} instance_data["creator_attributes"] = {"farm": is_old_farm}
instance_data["family"] = self.family instance_data["productType"] = self.product_type
if instance_data["creator_attributes"].get("mark_for_review") is None: if instance_data["creator_attributes"].get("mark_for_review") is None:
instance_data["creator_attributes"]["mark_for_review"] = True instance_data["creator_attributes"]["mark_for_review"] = True

View file

@ -9,7 +9,7 @@ from ayon_core.hosts.aftereffects.api.pipeline import cache_and_get_instances
class AEWorkfileCreator(AutoCreator): class AEWorkfileCreator(AutoCreator):
identifier = "workfile" identifier = "workfile"
family = "workfile" product_type = "workfile"
default_variant = "Main" default_variant = "Main"
@ -20,9 +20,9 @@ class AEWorkfileCreator(AutoCreator):
for instance_data in cache_and_get_instances(self): for instance_data in cache_and_get_instances(self):
creator_id = instance_data.get("creator_identifier") creator_id = instance_data.get("creator_identifier")
if creator_id == self.identifier: if creator_id == self.identifier:
subset_name = instance_data["subset"] product_name = instance_data["productName"]
instance = CreatedInstance( instance = CreatedInstance(
self.family, subset_name, instance_data, self self.product_type, product_name, instance_data, self
) )
self._add_instance_to_context(instance) self._add_instance_to_context(instance)
@ -33,7 +33,7 @@ class AEWorkfileCreator(AutoCreator):
def create(self, options=None): def create(self, options=None):
existing_instance = None existing_instance = None
for instance in self.create_context.instances: for instance in self.create_context.instances:
if instance.family == self.family: if instance.product_type == self.product_type:
existing_instance = instance existing_instance = instance
break break
@ -49,9 +49,12 @@ class AEWorkfileCreator(AutoCreator):
if existing_instance is None: if existing_instance is None:
asset_doc = get_asset_by_name(project_name, asset_name) asset_doc = get_asset_by_name(project_name, asset_name)
subset_name = self.get_subset_name( product_name = self.get_product_name(
self.default_variant, task_name, asset_doc, project_name,
project_name, host_name asset_doc,
task_name,
self.default_variant,
host_name,
) )
data = { data = {
"folderPath": asset_name, "folderPath": asset_name,
@ -59,12 +62,16 @@ class AEWorkfileCreator(AutoCreator):
"variant": self.default_variant, "variant": self.default_variant,
} }
data.update(self.get_dynamic_data( data.update(self.get_dynamic_data(
self.default_variant, task_name, asset_doc, project_name,
project_name, host_name, None asset_doc,
task_name,
self.default_variant,
host_name,
None,
)) ))
new_instance = CreatedInstance( new_instance = CreatedInstance(
self.family, subset_name, data, self self.product_type, product_name, data, self
) )
self._add_instance_to_context(new_instance) self._add_instance_to_context(new_instance)
@ -76,10 +83,13 @@ class AEWorkfileCreator(AutoCreator):
or existing_instance["task"] != task_name or existing_instance["task"] != task_name
): ):
asset_doc = get_asset_by_name(project_name, asset_name) asset_doc = get_asset_by_name(project_name, asset_name)
subset_name = self.get_subset_name( product_name = self.get_product_name(
self.default_variant, task_name, asset_doc, project_name,
project_name, host_name asset_doc,
task_name,
self.default_variant,
host_name,
) )
existing_instance["folderPath"] = asset_name existing_instance["folderPath"] = asset_name
existing_instance["task"] = task_name existing_instance["task"] = task_name
existing_instance["subset"] = subset_name existing_instance["productName"] = product_name

View file

@ -11,7 +11,7 @@ from ayon_core.hosts.aftereffects.api.lib import (
class BackgroundLoader(api.AfterEffectsLoader): class BackgroundLoader(api.AfterEffectsLoader):
""" """
Load images from Background family Load images from Background product type
Creates for each background separate folder with all imported images Creates for each background separate folder with all imported images
from background json AND automatically created composition with layers, from background json AND automatically created composition with layers,
each layer for separate image. each layer for separate image.
@ -56,16 +56,21 @@ class BackgroundLoader(api.AfterEffectsLoader):
self.__class__.__name__ self.__class__.__name__
) )
def update(self, container, representation): def update(self, container, context):
""" Switch asset or change version """ """ Switch asset or change version """
stub = self.get_stub() stub = self.get_stub()
context = representation.get("context", {}) asset_doc = context["asset"]
subset_doc = context["subset"]
repre_doc = context["representation"]
folder_name = asset_doc["name"]
product_name = subset_doc["name"]
_ = container.pop("layer") _ = container.pop("layer")
# without iterator number (_001, 002...) # without iterator number (_001, 002...)
namespace_from_container = re.sub(r'_\d{3}$', '', namespace_from_container = re.sub(r'_\d{3}$', '',
container["namespace"]) container["namespace"])
comp_name = "{}_{}".format(context["asset"], context["subset"]) comp_name = "{}_{}".format(folder_name, product_name)
# switching assets # switching assets
if namespace_from_container != comp_name: if namespace_from_container != comp_name:
@ -73,11 +78,11 @@ class BackgroundLoader(api.AfterEffectsLoader):
existing_items = [layer.name for layer in items] existing_items = [layer.name for layer in items]
comp_name = get_unique_layer_name( comp_name = get_unique_layer_name(
existing_items, existing_items,
"{}_{}".format(context["asset"], context["subset"])) "{}_{}".format(folder_name, product_name))
else: # switching version - keep same name else: # switching version - keep same name
comp_name = container["namespace"] comp_name = container["namespace"]
path = get_representation_path(representation) path = get_representation_path(repre_doc)
layers = get_background_layers(path) layers = get_background_layers(path)
comp = stub.reload_background(container["members"][1], comp = stub.reload_background(container["members"][1],
@ -85,8 +90,8 @@ class BackgroundLoader(api.AfterEffectsLoader):
layers) layers)
# update container # update container
container["representation"] = str(representation["_id"]) container["representation"] = str(repre_doc["_id"])
container["name"] = context["subset"] container["name"] = product_name
container["namespace"] = comp_name container["namespace"] = comp_name
container["members"] = comp.members container["members"] = comp.members
@ -104,5 +109,5 @@ class BackgroundLoader(api.AfterEffectsLoader):
stub.imprint(layer.id, {}) stub.imprint(layer.id, {})
stub.delete_item(layer.id) stub.delete_item(layer.id)
def switch(self, container, representation): def switch(self, container, context):
self.update(container, representation) self.update(container, context)

View file

@ -64,31 +64,36 @@ class FileLoader(api.AfterEffectsLoader):
self.__class__.__name__ self.__class__.__name__
) )
def update(self, container, representation): def update(self, container, context):
""" Switch asset or change version """ """ Switch asset or change version """
stub = self.get_stub() stub = self.get_stub()
layer = container.pop("layer") layer = container.pop("layer")
context = representation.get("context", {}) asset_doc = context["asset"]
subset_doc = context["subset"]
repre_doc = context["representation"]
folder_name = asset_doc["name"]
product_name = subset_doc["name"]
namespace_from_container = re.sub(r'_\d{3}$', '', namespace_from_container = re.sub(r'_\d{3}$', '',
container["namespace"]) container["namespace"])
layer_name = "{}_{}".format(context["asset"], context["subset"]) layer_name = "{}_{}".format(folder_name, product_name)
# switching assets # switching assets
if namespace_from_container != layer_name: if namespace_from_container != layer_name:
layers = stub.get_items(comps=True) layers = stub.get_items(comps=True)
existing_layers = [layer.name for layer in layers] existing_layers = [layer.name for layer in layers]
layer_name = get_unique_layer_name( layer_name = get_unique_layer_name(
existing_layers, existing_layers,
"{}_{}".format(context["asset"], context["subset"])) "{}_{}".format(folder_name, product_name))
else: # switching version - keep same name else: # switching version - keep same name
layer_name = container["namespace"] layer_name = container["namespace"]
path = get_representation_path(representation) path = get_representation_path(repre_doc)
# with aftereffects.maintained_selection(): # TODO # with aftereffects.maintained_selection(): # TODO
stub.replace_item(layer.id, path, stub.LOADED_ICON + layer_name) stub.replace_item(layer.id, path, stub.LOADED_ICON + layer_name)
stub.imprint( stub.imprint(
layer.id, {"representation": str(representation["_id"]), layer.id, {"representation": str(repre_doc["_id"]),
"name": context["subset"], "name": product_name,
"namespace": layer_name} "namespace": layer_name}
) )
@ -103,5 +108,5 @@ class FileLoader(api.AfterEffectsLoader):
stub.imprint(layer.id, {}) stub.imprint(layer.id, {})
stub.delete_item(layer.id) stub.delete_item(layer.id)
def switch(self, container, representation): def switch(self, container, context):
self.update(container, representation) self.update(container, context)

View file

@ -60,8 +60,8 @@ class CollectAERender(publish.AbstractCollectRender):
if not inst.data.get("active", True): if not inst.data.get("active", True):
continue continue
family = inst.data["family"] product_type = inst.data["productType"]
if family not in ["render", "renderLocal"]: # legacy if product_type not in ["render", "renderLocal"]: # legacy
continue continue
comp_id = int(inst.data["members"][0]) comp_id = int(inst.data["members"][0])
@ -81,29 +81,32 @@ class CollectAERender(publish.AbstractCollectRender):
fps = comp_info.frameRate fps = comp_info.frameRate
# TODO add resolution when supported by extension # TODO add resolution when supported by extension
task_name = inst.data.get("task") # legacy task_name = inst.data.get("task")
render_q = CollectAERender.get_stub().get_render_info(comp_id) render_q = CollectAERender.get_stub().get_render_info(comp_id)
if not render_q: if not render_q:
raise ValueError("No file extension set in Render Queue") raise ValueError("No file extension set in Render Queue")
render_item = render_q[0] render_item = render_q[0]
product_type = "render"
instance_families = inst.data.get("families", []) instance_families = inst.data.get("families", [])
subset_name = inst.data["subset"] instance_families.append(product_type)
product_name = inst.data["productName"]
instance = AERenderInstance( instance = AERenderInstance(
family="render", productType=product_type,
family=product_type,
families=instance_families, families=instance_families,
version=version, version=version,
time="", time="",
source=current_file, source=current_file,
label="{} - {}".format(subset_name, family), label="{} - {}".format(product_name, product_type),
subset=subset_name, productName=product_name,
asset=inst.data["asset"], folderPath=inst.data["folderPath"],
task=task_name, task=task_name,
attachTo=False, attachTo=False,
setMembers='', setMembers='',
publish=True, publish=True,
name=subset_name, name=product_name,
resolutionWidth=render_item.width, resolutionWidth=render_item.width,
resolutionHeight=render_item.height, resolutionHeight=render_item.height,
pixelAspect=1, pixelAspect=1,
@ -175,8 +178,8 @@ class CollectAERender(publish.AbstractCollectRender):
version_str = "v{:03d}".format(render_instance.version) version_str = "v{:03d}".format(render_instance.version)
if "#" not in file_name: # single frame (mov)W if "#" not in file_name: # single frame (mov)W
path = os.path.join(base_dir, "{}_{}_{}.{}".format( path = os.path.join(base_dir, "{}_{}_{}.{}".format(
render_instance.asset, render_instance.folderPath,
render_instance.subset, render_instance.productName,
version_str, version_str,
ext ext
)) ))
@ -184,8 +187,8 @@ class CollectAERender(publish.AbstractCollectRender):
else: else:
for frame in range(start, end + 1): for frame in range(start, end + 1):
path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format( path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format(
render_instance.asset, render_instance.folderPath,
render_instance.subset, render_instance.productName,
version_str, version_str,
str(frame).zfill(self.padding_width), str(frame).zfill(self.padding_width),
ext ext

View file

@ -3,7 +3,7 @@ Requires:
None None
Provides: Provides:
instance -> family ("review") instance -> families ("review")
""" """
import pyblish.api import pyblish.api

View file

@ -2,9 +2,6 @@ import os
import pyblish.api import pyblish.api
from ayon_core.client import get_asset_name_identifier
from ayon_core.pipeline.create import get_subset_name
class CollectWorkfile(pyblish.api.ContextPlugin): class CollectWorkfile(pyblish.api.ContextPlugin):
""" Adds the AE render instances """ """ Adds the AE render instances """
@ -15,86 +12,24 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
default_variant = "Main" default_variant = "Main"
def process(self, context): def process(self, context):
existing_instance = None workfile_instance = None
for instance in context: for instance in context:
if instance.data["family"] == "workfile": if instance.data["productType"] == "workfile":
self.log.debug("Workfile instance found, won't create new") self.log.debug("Workfile instance found")
existing_instance = instance workfile_instance = instance
break break
current_file = context.data["currentFile"] current_file = context.data["currentFile"]
staging_dir = os.path.dirname(current_file) staging_dir = os.path.dirname(current_file)
scene_file = os.path.basename(current_file) scene_file = os.path.basename(current_file)
if existing_instance is None: # old publish if workfile_instance is None:
instance = self._get_new_instance(context, scene_file) self.log.debug("Workfile instance not found. Skipping")
else: return
instance = existing_instance
# creating representation # creating representation
representation = { workfile_instance.data["representations"].append({
'name': 'aep', "name": "aep",
'ext': 'aep', "ext": "aep",
'files': scene_file, "files": scene_file,
"stagingDir": staging_dir, "stagingDir": staging_dir,
}
if not instance.data.get("representations"):
instance.data["representations"] = []
instance.data["representations"].append(representation)
instance.data["publish"] = instance.data["active"] # for DL
def _get_new_instance(self, context, scene_file):
task = context.data["task"]
version = context.data["version"]
asset_entity = context.data["assetEntity"]
project_entity = context.data["projectEntity"]
asset_name = get_asset_name_identifier(asset_entity)
instance_data = {
"active": True,
"asset": asset_name,
"task": task,
"frameStart": context.data['frameStart'],
"frameEnd": context.data['frameEnd'],
"handleStart": context.data['handleStart'],
"handleEnd": context.data['handleEnd'],
"fps": asset_entity["data"]["fps"],
"resolutionWidth": asset_entity["data"].get(
"resolutionWidth",
project_entity["data"]["resolutionWidth"]),
"resolutionHeight": asset_entity["data"].get(
"resolutionHeight",
project_entity["data"]["resolutionHeight"]),
"pixelAspect": 1,
"step": 1,
"version": version
}
# workfile instance
family = "workfile"
subset = get_subset_name(
family,
self.default_variant,
context.data["anatomyData"]["task"]["name"],
context.data["assetEntity"],
context.data["anatomyData"]["project"]["name"],
host_name=context.data["hostName"],
project_settings=context.data["project_settings"]
)
# Create instance
instance = context.create_instance(subset)
# creating instance data
instance.data.update({
"subset": subset,
"label": scene_file,
"family": family,
"families": [family],
"representations": list()
}) })
instance.data.update(instance_data)
return instance

View file

@ -3,9 +3,9 @@
<error id="main"> <error id="main">
<title>Subset context</title> <title>Subset context</title>
<description> <description>
## Invalid subset context ## Invalid product context
Context of the given subset doesn't match your current scene. Context of the given product doesn't match your current scene.
### How to repair? ### How to repair?
@ -15,7 +15,7 @@ You can fix this with "repair" button on the right and refresh Publish at the bo
### __Detailed Info__ (optional) ### __Detailed Info__ (optional)
This might happen if you are reuse old workfile and open it in different context. This might happen if you are reuse old workfile and open it in different context.
(Eg. you created subset "renderCompositingDefault" from asset "Robot' in "your_project_Robot_compositing.aep", now you opened this workfile in a context "Sloth" but existing subset for "Robot" asset stayed in the workfile.) (Eg. you created product name "renderCompositingDefault" from folder "Robot' in "your_project_Robot_compositing.aep", now you opened this workfile in a context "Sloth" but existing product for "Robot" asset stayed in the workfile.)
</detail> </detail>
</error> </error>
</root> </root>

View file

@ -1,54 +0,0 @@
import json
import pyblish.api
from ayon_core.hosts.aftereffects.api import AfterEffectsHost
class PreCollectRender(pyblish.api.ContextPlugin):
"""
Checks if render instance is of old type, adds to families to both
existing collectors work same way.
Could be removed in the future when no one uses old publish.
"""
label = "PreCollect Render"
order = pyblish.api.CollectorOrder + 0.400
hosts = ["aftereffects"]
family_remapping = {
"render": ("render.farm", "farm"), # (family, label)
"renderLocal": ("render.local", "local")
}
def process(self, context):
if context.data.get("newPublishing"):
self.log.debug("Not applicable for New Publisher, skip")
return
for inst in AfterEffectsHost().list_instances():
if inst.get("creator_attributes"):
raise ValueError("Instance created in New publisher, "
"cannot be published in Pyblish.\n"
"Please publish in New Publisher "
"or recreate instances with legacy Creators")
if inst["family"] not in self.family_remapping.keys():
continue
if not inst["members"]:
raise ValueError("Couldn't find id, unable to publish. " +
"Please recreate instance.")
instance = context.create_instance(inst["subset"])
inst["families"] = [self.family_remapping[inst["family"]][0]]
instance.data.update(inst)
self._debug_log(instance)
def _debug_log(self, instance):
def _default_json(value):
return str(value)
self.log.info(
json.dumps(instance.data, indent=4, default=_default_json)
)

View file

@ -30,7 +30,7 @@ class ValidateInstanceAssetRepair(pyblish.api.Action):
for instance in instances: for instance in instances:
data = stub.read(instance[0]) data = stub.read(instance[0])
data["asset"] = get_current_asset_name() data["folderPath"] = get_current_asset_name()
stub.imprint(instance[0].instance_id, data) stub.imprint(instance[0].instance_id, data)
@ -53,7 +53,7 @@ class ValidateInstanceAsset(pyblish.api.InstancePlugin):
order = ValidateContentsOrder order = ValidateContentsOrder
def process(self, instance): def process(self, instance):
instance_asset = instance.data["asset"] instance_asset = instance.data["folderPath"]
current_asset = get_current_asset_name() current_asset = get_current_asset_name()
msg = ( msg = (
f"Instance asset {instance_asset} is not the same " f"Instance asset {instance_asset} is not the same "

View file

@ -26,6 +26,7 @@ from ayon_core.pipeline import (
deregister_loader_plugin_path, deregister_loader_plugin_path,
deregister_creator_plugin_path, deregister_creator_plugin_path,
AVALON_CONTAINER_ID, AVALON_CONTAINER_ID,
AYON_CONTAINER_ID,
) )
from ayon_core.lib import ( from ayon_core.lib import (
Logger, Logger,
@ -563,8 +564,9 @@ def ls() -> Iterator:
called containers. called containers.
""" """
for container in lib.lsattr("id", AVALON_CONTAINER_ID): for id_type in {AYON_CONTAINER_ID, AVALON_CONTAINER_ID}:
yield parse_container(container) for container in lib.lsattr("id", id_type):
yield parse_container(container)
def publish(): def publish():

View file

@ -10,6 +10,8 @@ from ayon_core.pipeline import (
Creator, Creator,
CreatedInstance, CreatedInstance,
LoaderPlugin, LoaderPlugin,
AVALON_INSTANCE_ID,
AYON_INSTANCE_ID,
) )
from ayon_core.lib import BoolDef from ayon_core.lib import BoolDef
@ -28,13 +30,13 @@ VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"]
def prepare_scene_name( def prepare_scene_name(
asset: str, subset: str, namespace: Optional[str] = None folder_name: str, product_name: str, namespace: Optional[str] = None
) -> str: ) -> str:
"""Return a consistent name for an asset.""" """Return a consistent name for an asset."""
name = f"{asset}" name = f"{folder_name}"
if namespace: if namespace:
name = f"{name}_{namespace}" name = f"{name}_{namespace}"
name = f"{name}_{subset}" name = f"{name}_{product_name}"
# Blender name for a collection or object cannot be longer than 63 # Blender name for a collection or object cannot be longer than 63
# characters. If the name is longer, it will raise an error. # characters. If the name is longer, it will raise an error.
@ -45,7 +47,7 @@ def prepare_scene_name(
def get_unique_number( def get_unique_number(
asset: str, subset: str folder_name: str, product_name: str
) -> str: ) -> str:
"""Return a unique number based on the asset name.""" """Return a unique number based on the asset name."""
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
@ -62,10 +64,10 @@ def get_unique_number(
if c.get(AVALON_PROPERTY)} if c.get(AVALON_PROPERTY)}
container_names = obj_group_names.union(coll_group_names) container_names = obj_group_names.union(coll_group_names)
count = 1 count = 1
name = f"{asset}_{count:0>2}_{subset}" name = f"{folder_name}_{count:0>2}_{product_name}"
while name in container_names: while name in container_names:
count += 1 count += 1
name = f"{asset}_{count:0>2}_{subset}" name = f"{folder_name}_{count:0>2}_{product_name}"
return f"{count:0>2}" return f"{count:0>2}"
@ -159,24 +161,22 @@ class BaseCreator(Creator):
create_as_asset_group = False create_as_asset_group = False
@staticmethod @staticmethod
def cache_subsets(shared_data): def cache_instance_data(shared_data):
"""Cache instances for Creators shared data. """Cache instances for Creators shared data.
Create `blender_cached_subsets` key when needed in shared data and Create `blender_cached_instances` key when needed in shared data and
fill it with all collected instances from the scene under its fill it with all collected instances from the scene under its
respective creator identifiers. respective creator identifiers.
If legacy instances are detected in the scene, create If legacy instances are detected in the scene, create
`blender_cached_legacy_subsets` key and fill it with `blender_cached_legacy_instances` key and fill it with
all legacy subsets from this family as a value. # key or value? all legacy products from this family as a value. # key or value?
Args: Args:
shared_data(Dict[str, Any]): Shared data. shared_data(Dict[str, Any]): Shared data.
Return:
Dict[str, Any]: Shared data with cached subsets.
""" """
if not shared_data.get('blender_cached_subsets'): if not shared_data.get('blender_cached_instances'):
cache = {} cache = {}
cache_legacy = {} cache_legacy = {}
@ -193,7 +193,9 @@ class BaseCreator(Creator):
if not avalon_prop: if not avalon_prop:
continue continue
if avalon_prop.get('id') != 'pyblish.avalon.instance': if avalon_prop.get('id') not in {
AYON_INSTANCE_ID, AVALON_INSTANCE_ID
}:
continue continue
creator_id = avalon_prop.get('creator_identifier') creator_id = avalon_prop.get('creator_identifier')
@ -206,19 +208,19 @@ class BaseCreator(Creator):
# Legacy creator instance # Legacy creator instance
cache_legacy.setdefault(family, []).append(obj_or_col) cache_legacy.setdefault(family, []).append(obj_or_col)
shared_data["blender_cached_subsets"] = cache shared_data["blender_cached_instances"] = cache
shared_data["blender_cached_legacy_subsets"] = cache_legacy shared_data["blender_cached_legacy_instances"] = cache_legacy
return shared_data return shared_data
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
"""Override abstract method from Creator. """Override abstract method from Creator.
Create new instance and store it. Create new instance and store it.
Args: Args:
subset_name(str): Subset name of created instance. product_name(str): Subset name of created instance.
instance_data(dict): Instance base data. instance_data(dict): Instance base data.
pre_create_data(dict): Data based on pre creation attributes. pre_create_data(dict): Data based on pre creation attributes.
Those may affect how creator works. Those may affect how creator works.
@ -232,7 +234,7 @@ class BaseCreator(Creator):
# Create asset group # Create asset group
asset_name = instance_data["folderPath"].split("/")[-1] asset_name = instance_data["folderPath"].split("/")[-1]
name = prepare_scene_name(asset_name, subset_name) name = prepare_scene_name(asset_name, product_name)
if self.create_as_asset_group: if self.create_as_asset_group:
# Create instance as empty # Create instance as empty
instance_node = bpy.data.objects.new(name=name, object_data=None) instance_node = bpy.data.objects.new(name=name, object_data=None)
@ -243,10 +245,10 @@ class BaseCreator(Creator):
instance_node = bpy.data.collections.new(name=name) instance_node = bpy.data.collections.new(name=name)
instances.children.link(instance_node) instances.children.link(instance_node)
self.set_instance_data(subset_name, instance_data) self.set_instance_data(product_name, instance_data)
instance = CreatedInstance( instance = CreatedInstance(
self.family, subset_name, instance_data, self self.product_type, product_name, instance_data, self
) )
instance.transient_data["instance_node"] = instance_node instance.transient_data["instance_node"] = instance_node
self._add_instance_to_context(instance) self._add_instance_to_context(instance)
@ -259,18 +261,18 @@ class BaseCreator(Creator):
"""Override abstract method from BaseCreator. """Override abstract method from BaseCreator.
Collect existing instances related to this creator plugin.""" Collect existing instances related to this creator plugin."""
# Cache subsets in shared data # Cache instances in shared data
self.cache_subsets(self.collection_shared_data) self.cache_instance_data(self.collection_shared_data)
# Get cached subsets # Get cached instances
cached_subsets = self.collection_shared_data.get( cached_instances = self.collection_shared_data.get(
"blender_cached_subsets" "blender_cached_instances"
) )
if not cached_subsets: if not cached_instances:
return return
# Process only instances that were created by this creator # Process only instances that were created by this creator
for instance_node in cached_subsets.get(self.identifier, []): for instance_node in cached_instances.get(self.identifier, []):
property = instance_node.get(AVALON_PROPERTY) property = instance_node.get(AVALON_PROPERTY)
# Create instance object from existing data # Create instance object from existing data
instance = CreatedInstance.from_existing( instance = CreatedInstance.from_existing(
@ -302,16 +304,17 @@ class BaseCreator(Creator):
) )
return return
# Rename the instance node in the scene if subset or asset changed. # Rename the instance node in the scene if product
# or folder changed.
# Do not rename the instance if the family is workfile, as the # Do not rename the instance if the family is workfile, as the
# workfile instance is included in the AVALON_CONTAINER collection. # workfile instance is included in the AVALON_CONTAINER collection.
if ( if (
"subset" in changes.changed_keys "productName" in changes.changed_keys
or "folderPath" in changes.changed_keys or "folderPath" in changes.changed_keys
) and created_instance.family != "workfile": ) and created_instance.product_type != "workfile":
asset_name = data["folderPath"].split("/")[-1] asset_name = data["folderPath"].split("/")[-1]
name = prepare_scene_name( name = prepare_scene_name(
asset=asset_name, subset=data["subset"] asset_name, data["productName"]
) )
node.name = name node.name = name
@ -337,13 +340,13 @@ class BaseCreator(Creator):
def set_instance_data( def set_instance_data(
self, self,
subset_name: str, product_name: str,
instance_data: dict instance_data: dict
): ):
"""Fill instance data with required items. """Fill instance data with required items.
Args: Args:
subset_name(str): Subset name of created instance. product_name(str): Subset name of created instance.
instance_data(dict): Instance base data. instance_data(dict): Instance base data.
instance_node(bpy.types.ID): Instance node in blender scene. instance_node(bpy.types.ID): Instance node in blender scene.
""" """
@ -352,9 +355,9 @@ class BaseCreator(Creator):
instance_data.update( instance_data.update(
{ {
"id": "pyblish.avalon.instance", "id": AVALON_INSTANCE_ID,
"creator_identifier": self.identifier, "creator_identifier": self.identifier,
"subset": subset_name, "productName": product_name,
} }
) )
@ -462,14 +465,14 @@ class AssetLoader(LoaderPlugin):
filepath = self.filepath_from_context(context) filepath = self.filepath_from_context(context)
assert Path(filepath).exists(), f"{filepath} doesn't exist." assert Path(filepath).exists(), f"{filepath} doesn't exist."
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
unique_number = get_unique_number( unique_number = get_unique_number(
asset, subset folder_name, product_name
) )
namespace = namespace or f"{asset}_{unique_number}" namespace = namespace or f"{folder_name}_{unique_number}"
name = name or prepare_scene_name( name = name or prepare_scene_name(
asset, subset, unique_number folder_name, product_name, unique_number
) )
nodes = self.process_asset( nodes = self.process_asset(
@ -495,21 +498,21 @@ class AssetLoader(LoaderPlugin):
# loader=self.__class__.__name__, # loader=self.__class__.__name__,
# ) # )
# asset = context["asset"]["name"] # folder_name = context["asset"]["name"]
# subset = context["subset"]["name"] # product_name = context["subset"]["name"]
# instance_name = prepare_scene_name( # instance_name = prepare_scene_name(
# asset, subset, unique_number # folder_name, product_name, unique_number
# ) + '_CON' # ) + '_CON'
# return self._get_instance_collection(instance_name, nodes) # return self._get_instance_collection(instance_name, nodes)
def exec_update(self, container: Dict, representation: Dict): def exec_update(self, container: Dict, context: Dict):
"""Must be implemented by a sub-class""" """Must be implemented by a sub-class"""
raise NotImplementedError("Must be implemented by a sub-class") raise NotImplementedError("Must be implemented by a sub-class")
def update(self, container: Dict, representation: Dict): def update(self, container: Dict, context: Dict):
""" Run the update on Blender main thread""" """ Run the update on Blender main thread"""
mti = MainThreadItem(self.exec_update, container, representation) mti = MainThreadItem(self.exec_update, container, context)
execute_in_main_thread(mti) execute_in_main_thread(mti)
def exec_remove(self, container: Dict) -> bool: def exec_remove(self, container: Dict) -> bool:

View file

@ -47,6 +47,22 @@ def get_multilayer(settings):
["multilayer_exr"]) ["multilayer_exr"])
def get_renderer(settings):
"""Get renderer from blender settings."""
return (settings["blender"]
["RenderSettings"]
["renderer"])
def get_compositing(settings):
"""Get compositing from blender settings."""
return (settings["blender"]
["RenderSettings"]
["compositing"])
def get_render_product(output_path, name, aov_sep): def get_render_product(output_path, name, aov_sep):
""" """
Generate the path to the render product. Blender interprets the `#` Generate the path to the render product. Blender interprets the `#`
@ -91,66 +107,120 @@ def set_render_format(ext, multilayer):
image_settings.file_format = "TIFF" image_settings.file_format = "TIFF"
def set_render_passes(settings): def set_render_passes(settings, renderer):
aov_list = (settings["blender"] aov_list = set(settings["blender"]["RenderSettings"]["aov_list"])
["RenderSettings"] custom_passes = settings["blender"]["RenderSettings"]["custom_passes"]
["aov_list"])
custom_passes = (settings["blender"]
["RenderSettings"]
["custom_passes"])
# Common passes for both renderers
vl = bpy.context.view_layer vl = bpy.context.view_layer
# Data Passes
vl.use_pass_combined = "combined" in aov_list vl.use_pass_combined = "combined" in aov_list
vl.use_pass_z = "z" in aov_list vl.use_pass_z = "z" in aov_list
vl.use_pass_mist = "mist" in aov_list vl.use_pass_mist = "mist" in aov_list
vl.use_pass_normal = "normal" in aov_list vl.use_pass_normal = "normal" in aov_list
# Light Passes
vl.use_pass_diffuse_direct = "diffuse_light" in aov_list vl.use_pass_diffuse_direct = "diffuse_light" in aov_list
vl.use_pass_diffuse_color = "diffuse_color" in aov_list vl.use_pass_diffuse_color = "diffuse_color" in aov_list
vl.use_pass_glossy_direct = "specular_light" in aov_list vl.use_pass_glossy_direct = "specular_light" in aov_list
vl.use_pass_glossy_color = "specular_color" in aov_list vl.use_pass_glossy_color = "specular_color" in aov_list
vl.eevee.use_pass_volume_direct = "volume_light" in aov_list
vl.use_pass_emit = "emission" in aov_list vl.use_pass_emit = "emission" in aov_list
vl.use_pass_environment = "environment" in aov_list vl.use_pass_environment = "environment" in aov_list
vl.use_pass_shadow = "shadow" in aov_list
vl.use_pass_ambient_occlusion = "ao" in aov_list vl.use_pass_ambient_occlusion = "ao" in aov_list
cycles = vl.cycles # Cryptomatte Passes
vl.use_pass_cryptomatte_object = "cryptomatte_object" in aov_list
vl.use_pass_cryptomatte_material = "cryptomatte_material" in aov_list
vl.use_pass_cryptomatte_asset = "cryptomatte_asset" in aov_list
cycles.denoising_store_passes = "denoising" in aov_list if renderer == "BLENDER_EEVEE":
cycles.use_pass_volume_direct = "volume_direct" in aov_list # Eevee exclusive passes
cycles.use_pass_volume_indirect = "volume_indirect" in aov_list eevee = vl.eevee
# Light Passes
vl.use_pass_shadow = "shadow" in aov_list
eevee.use_pass_volume_direct = "volume_light" in aov_list
# Effects Passes
eevee.use_pass_bloom = "bloom" in aov_list
eevee.use_pass_transparent = "transparent" in aov_list
# Cryptomatte Passes
vl.use_pass_cryptomatte_accurate = "cryptomatte_accurate" in aov_list
elif renderer == "CYCLES":
# Cycles exclusive passes
cycles = vl.cycles
# Data Passes
vl.use_pass_position = "position" in aov_list
vl.use_pass_vector = "vector" in aov_list
vl.use_pass_uv = "uv" in aov_list
cycles.denoising_store_passes = "denoising" in aov_list
vl.use_pass_object_index = "object_index" in aov_list
vl.use_pass_material_index = "material_index" in aov_list
cycles.pass_debug_sample_count = "sample_count" in aov_list
# Light Passes
vl.use_pass_diffuse_indirect = "diffuse_indirect" in aov_list
vl.use_pass_glossy_indirect = "specular_indirect" in aov_list
vl.use_pass_transmission_direct = "transmission_direct" in aov_list
vl.use_pass_transmission_indirect = "transmission_indirect" in aov_list
vl.use_pass_transmission_color = "transmission_color" in aov_list
cycles.use_pass_volume_direct = "volume_light" in aov_list
cycles.use_pass_volume_indirect = "volume_indirect" in aov_list
cycles.use_pass_shadow_catcher = "shadow" in aov_list
aovs_names = [aov.name for aov in vl.aovs] aovs_names = [aov.name for aov in vl.aovs]
for cp in custom_passes: for cp in custom_passes:
cp_name = cp[0] cp_name = cp["attribute"]
if cp_name not in aovs_names: if cp_name not in aovs_names:
aov = vl.aovs.add() aov = vl.aovs.add()
aov.name = cp_name aov.name = cp_name
else: else:
aov = vl.aovs[cp_name] aov = vl.aovs[cp_name]
aov.type = cp[1].get("type", "VALUE") aov.type = cp["value"]
return aov_list, custom_passes return list(aov_list), custom_passes
def set_node_tree(output_path, name, aov_sep, ext, multilayer): def _create_aov_slot(name, aov_sep, slots, rpass_name, multi_exr, output_path):
filename = f"{name}{aov_sep}{rpass_name}.####"
slot = slots.new(rpass_name if multi_exr else filename)
filepath = str(output_path / filename.lstrip("/"))
return slot, filepath
def set_node_tree(
output_path, render_product, name, aov_sep, ext, multilayer, compositing
):
# Set the scene to use the compositor node tree to render # Set the scene to use the compositor node tree to render
bpy.context.scene.use_nodes = True bpy.context.scene.use_nodes = True
tree = bpy.context.scene.node_tree tree = bpy.context.scene.node_tree
# Get the Render Layers node comp_layer_type = "CompositorNodeRLayers"
rl_node = None output_type = "CompositorNodeOutputFile"
compositor_type = "CompositorNodeComposite"
# Get the Render Layer, Composite and the previous output nodes
render_layer_node = None
composite_node = None
old_output_node = None
for node in tree.nodes: for node in tree.nodes:
if node.bl_idname == "CompositorNodeRLayers": if node.bl_idname == comp_layer_type:
rl_node = node render_layer_node = node
elif node.bl_idname == compositor_type:
composite_node = node
elif node.bl_idname == output_type and "AYON" in node.name:
old_output_node = node
if render_layer_node and composite_node and old_output_node:
break break
# If there's not a Render Layers node, we create it # If there's not a Render Layers node, we create it
if not rl_node: if not render_layer_node:
rl_node = tree.nodes.new("CompositorNodeRLayers") render_layer_node = tree.nodes.new(comp_layer_type)
# Get the enabled output sockets, that are the active passes for the # Get the enabled output sockets, that are the active passes for the
# render. # render.
@ -158,48 +228,81 @@ def set_node_tree(output_path, name, aov_sep, ext, multilayer):
exclude_sockets = ["Image", "Alpha", "Noisy Image"] exclude_sockets = ["Image", "Alpha", "Noisy Image"]
passes = [ passes = [
socket socket
for socket in rl_node.outputs for socket in render_layer_node.outputs
if socket.enabled and socket.name not in exclude_sockets if socket.enabled and socket.name not in exclude_sockets
] ]
# Remove all output nodes
for node in tree.nodes:
if node.bl_idname == "CompositorNodeOutputFile":
tree.nodes.remove(node)
# Create a new output node # Create a new output node
output = tree.nodes.new("CompositorNodeOutputFile") output = tree.nodes.new(output_type)
image_settings = bpy.context.scene.render.image_settings image_settings = bpy.context.scene.render.image_settings
output.format.file_format = image_settings.file_format output.format.file_format = image_settings.file_format
slots = None
# In case of a multilayer exr, we don't need to use the output node, # In case of a multilayer exr, we don't need to use the output node,
# because the blender render already outputs a multilayer exr. # because the blender render already outputs a multilayer exr.
if ext == "exr" and multilayer: multi_exr = ext == "exr" and multilayer
output.layer_slots.clear() slots = output.layer_slots if multi_exr else output.file_slots
return [] output.base_path = render_product if multi_exr else str(output_path)
output.file_slots.clear() slots.clear()
output.base_path = str(output_path)
aov_file_products = [] aov_file_products = []
old_links = {
link.from_socket.name: link for link in tree.links
if link.to_node == old_output_node}
# Create a new socket for the beauty output
pass_name = "rgba" if multi_exr else "beauty"
slot, _ = _create_aov_slot(
name, aov_sep, slots, pass_name, multi_exr, output_path)
tree.links.new(render_layer_node.outputs["Image"], slot)
if compositing:
# Create a new socket for the composite output
pass_name = "composite"
comp_socket, filepath = _create_aov_slot(
name, aov_sep, slots, pass_name, multi_exr, output_path)
aov_file_products.append(("Composite", filepath))
# For each active render pass, we add a new socket to the output node # For each active render pass, we add a new socket to the output node
# and link it # and link it
for render_pass in passes: for rpass in passes:
filepath = f"{name}{aov_sep}{render_pass.name}.####" slot, filepath = _create_aov_slot(
name, aov_sep, slots, rpass.name, multi_exr, output_path)
aov_file_products.append((rpass.name, filepath))
output.file_slots.new(filepath) # If the rpass was not connected with the old output node, we connect
# it with the new one.
if not old_links.get(rpass.name):
tree.links.new(rpass, slot)
filename = str(output_path / filepath.lstrip("/")) for link in list(old_links.values()):
# Check if the socket is still available in the new output node.
socket = output.inputs.get(link.to_socket.name)
# If it is, we connect it with the new output node.
if socket:
tree.links.new(link.from_socket, socket)
# Then, we remove the old link.
tree.links.remove(link)
aov_file_products.append((render_pass.name, filename)) # If there's a composite node, we connect its input with the new output
if compositing and composite_node:
for link in tree.links:
if link.to_node == composite_node:
tree.links.new(link.from_socket, comp_socket)
break
node_input = output.inputs[-1] if old_output_node:
output.location = old_output_node.location
tree.nodes.remove(old_output_node)
tree.links.new(render_pass, node_input) output.name = "AYON File Output"
output.label = "AYON File Output"
return aov_file_products return [] if multi_exr else aov_file_products
def imprint_render_settings(node, data): def imprint_render_settings(node, data):
@ -228,17 +331,23 @@ def prepare_rendering(asset_group):
aov_sep = get_aov_separator(settings) aov_sep = get_aov_separator(settings)
ext = get_image_format(settings) ext = get_image_format(settings)
multilayer = get_multilayer(settings) multilayer = get_multilayer(settings)
renderer = get_renderer(settings)
compositing = get_compositing(settings)
set_render_format(ext, multilayer) set_render_format(ext, multilayer)
aov_list, custom_passes = set_render_passes(settings) bpy.context.scene.render.engine = renderer
aov_list, custom_passes = set_render_passes(settings, renderer)
output_path = Path.joinpath(dirpath, render_folder, file_name) output_path = Path.joinpath(dirpath, render_folder, file_name)
render_product = get_render_product(output_path, name, aov_sep) render_product = get_render_product(output_path, name, aov_sep)
aov_file_product = set_node_tree( aov_file_product = set_node_tree(
output_path, name, aov_sep, ext, multilayer) output_path, render_product, name, aov_sep,
ext, multilayer, compositing)
bpy.context.scene.render.filepath = render_product # Clear the render filepath, so that the output is handled only by the
# output node in the compositor.
bpy.context.scene.render.filepath = ""
render_settings = { render_settings = {
"render_folder": render_folder, "render_folder": render_folder,

View file

@ -1,24 +1,24 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Converter for legacy Houdini subsets.""" """Converter for legacy Houdini products."""
from ayon_core.pipeline.create.creator_plugins import SubsetConvertorPlugin from ayon_core.pipeline.create.creator_plugins import SubsetConvertorPlugin
from ayon_core.hosts.blender.api.lib import imprint from ayon_core.hosts.blender.api.lib import imprint
class BlenderLegacyConvertor(SubsetConvertorPlugin): class BlenderLegacyConvertor(SubsetConvertorPlugin):
"""Find and convert any legacy subsets in the scene. """Find and convert any legacy products in the scene.
This Converter will find all legacy subsets in the scene and will This Converter will find all legacy products in the scene and will
transform them to the current system. Since the old subsets doesn't transform them to the current system. Since the old products doesn't
retain any information about their original creators, the only mapping retain any information about their original creators, the only mapping
we can do is based on their families. we can do is based on their product types.
Its limitation is that you can have multiple creators creating subset Its limitation is that you can have multiple creators creating product
of the same family and there is no way to handle it. This code should of the same product type and there is no way to handle it. This code
nevertheless cover all creators that came with OpenPype. should nevertheless cover all creators that came with OpenPype.
""" """
identifier = "io.openpype.creators.blender.legacy" identifier = "io.openpype.creators.blender.legacy"
family_to_id = { product_type_to_id = {
"action": "io.openpype.creators.blender.action", "action": "io.openpype.creators.blender.action",
"camera": "io.openpype.creators.blender.camera", "camera": "io.openpype.creators.blender.camera",
"animation": "io.openpype.creators.blender.animation", "animation": "io.openpype.creators.blender.animation",
@ -33,42 +33,42 @@ class BlenderLegacyConvertor(SubsetConvertorPlugin):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(BlenderLegacyConvertor, self).__init__(*args, **kwargs) super(BlenderLegacyConvertor, self).__init__(*args, **kwargs)
self.legacy_subsets = {} self.legacy_instances = {}
def find_instances(self): def find_instances(self):
"""Find legacy subsets in the scene. """Find legacy products in the scene.
Legacy subsets are the ones that doesn't have `creator_identifier` Legacy products are the ones that doesn't have `creator_identifier`
parameter on them. parameter on them.
This is using cached entries done in This is using cached entries done in
:py:meth:`~BaseCreator.cache_subsets()` :py:meth:`~BaseCreator.cache_instance_data()`
""" """
self.legacy_subsets = self.collection_shared_data.get( self.legacy_instances = self.collection_shared_data.get(
"blender_cached_legacy_subsets") "blender_cached_legacy_instances")
if not self.legacy_subsets: if not self.legacy_instances:
return return
self.add_convertor_item( self.add_convertor_item(
"Found {} incompatible subset{}".format( "Found {} incompatible product{}".format(
len(self.legacy_subsets), len(self.legacy_instances),
"s" if len(self.legacy_subsets) > 1 else "" "s" if len(self.legacy_instances) > 1 else ""
) )
) )
def convert(self): def convert(self):
"""Convert all legacy subsets to current. """Convert all legacy products to current.
It is enough to add `creator_identifier` and `instance_node`. It is enough to add `creator_identifier` and `instance_node`.
""" """
if not self.legacy_subsets: if not self.legacy_instances:
return return
for family, instance_nodes in self.legacy_subsets.items(): for product_type, instance_nodes in self.legacy_instances.items():
if family in self.family_to_id: if product_type in self.product_type_to_id:
for instance_node in instance_nodes: for instance_node in instance_nodes:
creator_identifier = self.family_to_id[family] creator_identifier = self.product_type_to_id[product_type]
self.log.info( self.log.info(
"Converting {} to {}".format(instance_node.name, "Converting {} to {}".format(instance_node.name,
creator_identifier) creator_identifier)

View file

@ -10,19 +10,21 @@ class CreateAction(plugin.BaseCreator):
identifier = "io.openpype.creators.blender.action" identifier = "io.openpype.creators.blender.action"
label = "Action" label = "Action"
family = "action" product_type = "action"
icon = "male" icon = "male"
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
# Run parent create method # Run parent create method
collection = super().create( collection = super().create(
subset_name, instance_data, pre_create_data product_name, instance_data, pre_create_data
) )
# Get instance name # Get instance name
name = plugin.prepare_scene_name(instance_data["asset"], subset_name) name = plugin.prepare_scene_name(
instance_data["folderPath"], product_name
)
if pre_create_data.get("use_selection"): if pre_create_data.get("use_selection"):
for obj in lib.get_selection(): for obj in lib.get_selection():

View file

@ -8,15 +8,15 @@ class CreateAnimation(plugin.BaseCreator):
identifier = "io.openpype.creators.blender.animation" identifier = "io.openpype.creators.blender.animation"
label = "Animation" label = "Animation"
family = "animation" product_type = "animation"
icon = "male" icon = "male"
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
# Run parent create method # Run parent create method
collection = super().create( collection = super().create(
subset_name, instance_data, pre_create_data product_name, instance_data, pre_create_data
) )
if pre_create_data.get("use_selection"): if pre_create_data.get("use_selection"):

View file

@ -10,16 +10,16 @@ class CreateBlendScene(plugin.BaseCreator):
identifier = "io.openpype.creators.blender.blendscene" identifier = "io.openpype.creators.blender.blendscene"
label = "Blender Scene" label = "Blender Scene"
family = "blendScene" product_type = "blendScene"
icon = "cubes" icon = "cubes"
maintain_selection = False maintain_selection = False
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
instance_node = super().create(subset_name, instance_node = super().create(product_name,
instance_data, instance_data,
pre_create_data) pre_create_data)

View file

@ -11,16 +11,16 @@ class CreateCamera(plugin.BaseCreator):
identifier = "io.openpype.creators.blender.camera" identifier = "io.openpype.creators.blender.camera"
label = "Camera" label = "Camera"
family = "camera" product_type = "camera"
icon = "video-camera" icon = "video-camera"
create_as_asset_group = True create_as_asset_group = True
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
asset_group = super().create(subset_name, asset_group = super().create(product_name,
instance_data, instance_data,
pre_create_data) pre_create_data)
@ -30,8 +30,8 @@ class CreateCamera(plugin.BaseCreator):
obj.parent = asset_group obj.parent = asset_group
else: else:
plugin.deselect_all() plugin.deselect_all()
camera = bpy.data.cameras.new(subset_name) camera = bpy.data.cameras.new(product_name)
camera_obj = bpy.data.objects.new(subset_name, camera) camera_obj = bpy.data.objects.new(product_name, camera)
instances = bpy.data.collections.get(AVALON_INSTANCES) instances = bpy.data.collections.get(AVALON_INSTANCES)
instances.objects.link(camera_obj) instances.objects.link(camera_obj)

View file

@ -10,16 +10,16 @@ class CreateLayout(plugin.BaseCreator):
identifier = "io.openpype.creators.blender.layout" identifier = "io.openpype.creators.blender.layout"
label = "Layout" label = "Layout"
family = "layout" product_type = "layout"
icon = "cubes" icon = "cubes"
create_as_asset_group = True create_as_asset_group = True
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
asset_group = super().create(subset_name, asset_group = super().create(product_name,
instance_data, instance_data,
pre_create_data) pre_create_data)

View file

@ -10,15 +10,15 @@ class CreateModel(plugin.BaseCreator):
identifier = "io.openpype.creators.blender.model" identifier = "io.openpype.creators.blender.model"
label = "Model" label = "Model"
family = "model" product_type = "model"
icon = "cube" icon = "cube"
create_as_asset_group = True create_as_asset_group = True
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
asset_group = super().create(subset_name, asset_group = super().create(product_name,
instance_data, instance_data,
pre_create_data) pre_create_data)

View file

@ -8,15 +8,15 @@ class CreatePointcache(plugin.BaseCreator):
identifier = "io.openpype.creators.blender.pointcache" identifier = "io.openpype.creators.blender.pointcache"
label = "Point Cache" label = "Point Cache"
family = "pointcache" product_type = "pointcache"
icon = "gears" icon = "gears"
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
# Run parent create method # Run parent create method
collection = super().create( collection = super().create(
subset_name, instance_data, pre_create_data product_name, instance_data, pre_create_data
) )
if pre_create_data.get("use_selection"): if pre_create_data.get("use_selection"):

View file

@ -1,8 +1,10 @@
"""Create render.""" """Create render."""
import bpy import bpy
from ayon_core.lib import version_up
from ayon_core.hosts.blender.api import plugin from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.render_lib import prepare_rendering from ayon_core.hosts.blender.api.render_lib import prepare_rendering
from ayon_core.hosts.blender.api.workio import save_file
class CreateRenderlayer(plugin.BaseCreator): class CreateRenderlayer(plugin.BaseCreator):
@ -10,16 +12,16 @@ class CreateRenderlayer(plugin.BaseCreator):
identifier = "io.openpype.creators.blender.render" identifier = "io.openpype.creators.blender.render"
label = "Render" label = "Render"
family = "render" product_type = "render"
icon = "eye" icon = "eye"
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
try: try:
# Run parent create method # Run parent create method
collection = super().create( collection = super().create(
subset_name, instance_data, pre_create_data product_name, instance_data, pre_create_data
) )
prepare_rendering(collection) prepare_rendering(collection)
@ -37,6 +39,7 @@ class CreateRenderlayer(plugin.BaseCreator):
# settings. Even the validator to check that the file is saved will # settings. Even the validator to check that the file is saved will
# detect the file as saved, even if it isn't. The only solution for # detect the file as saved, even if it isn't. The only solution for
# now it is to force the file to be saved. # now it is to force the file to be saved.
bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath) filepath = version_up(bpy.data.filepath)
save_file(filepath, copy=False)
return collection return collection

View file

@ -8,15 +8,15 @@ class CreateReview(plugin.BaseCreator):
identifier = "io.openpype.creators.blender.review" identifier = "io.openpype.creators.blender.review"
label = "Review" label = "Review"
family = "review" product_type = "review"
icon = "video-camera" icon = "video-camera"
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
# Run parent create method # Run parent create method
collection = super().create( collection = super().create(
subset_name, instance_data, pre_create_data product_name, instance_data, pre_create_data
) )
if pre_create_data.get("use_selection"): if pre_create_data.get("use_selection"):

View file

@ -10,15 +10,15 @@ class CreateRig(plugin.BaseCreator):
identifier = "io.openpype.creators.blender.rig" identifier = "io.openpype.creators.blender.rig"
label = "Rig" label = "Rig"
family = "rig" product_type = "rig"
icon = "wheelchair" icon = "wheelchair"
create_as_asset_group = True create_as_asset_group = True
def create( def create(
self, subset_name: str, instance_data: dict, pre_create_data: dict self, product_name: str, instance_data: dict, pre_create_data: dict
): ):
asset_group = super().create(subset_name, asset_group = super().create(product_name,
instance_data, instance_data,
pre_create_data) pre_create_data)

View file

@ -19,7 +19,7 @@ class CreateWorkfile(BaseCreator, AutoCreator):
""" """
identifier = "io.openpype.creators.blender.workfile" identifier = "io.openpype.creators.blender.workfile"
label = "Workfile" label = "Workfile"
family = "workfile" product_type = "workfile"
icon = "fa5.file" icon = "fa5.file"
def create(self): def create(self):
@ -43,8 +43,12 @@ class CreateWorkfile(BaseCreator, AutoCreator):
if not workfile_instance: if not workfile_instance:
asset_doc = get_asset_by_name(project_name, asset_name) asset_doc = get_asset_by_name(project_name, asset_name)
subset_name = self.get_subset_name( product_name = self.get_product_name(
task_name, task_name, asset_doc, project_name, host_name project_name,
asset_doc,
task_name,
task_name,
host_name,
) )
data = { data = {
"folderPath": asset_name, "folderPath": asset_name,
@ -53,17 +57,17 @@ class CreateWorkfile(BaseCreator, AutoCreator):
} }
data.update( data.update(
self.get_dynamic_data( self.get_dynamic_data(
task_name,
task_name,
asset_doc,
project_name, project_name,
asset_doc,
task_name,
task_name,
host_name, host_name,
workfile_instance, workfile_instance,
) )
) )
self.log.info("Auto-creating workfile instance...") self.log.info("Auto-creating workfile instance...")
workfile_instance = CreatedInstance( workfile_instance = CreatedInstance(
self.family, subset_name, data, self self.product_type, product_name, data, self
) )
self._add_instance_to_context(workfile_instance) self._add_instance_to_context(workfile_instance)
@ -73,13 +77,17 @@ class CreateWorkfile(BaseCreator, AutoCreator):
): ):
# Update instance context if it's different # Update instance context if it's different
asset_doc = get_asset_by_name(project_name, asset_name) asset_doc = get_asset_by_name(project_name, asset_name)
subset_name = self.get_subset_name( product_name = self.get_product_name(
task_name, task_name, asset_doc, project_name, host_name project_name,
asset_doc,
task_name,
self.default_variant,
host_name,
) )
workfile_instance["folderPath"] = asset_name workfile_instance["folderPath"] = asset_name
workfile_instance["task"] = task_name workfile_instance["task"] = task_name
workfile_instance["subset"] = subset_name workfile_instance["productName"] = product_name
instance_node = bpy.data.collections.get(AVALON_CONTAINERS) instance_node = bpy.data.collections.get(AVALON_CONTAINERS)
if not instance_node: if not instance_node:

View file

@ -4,10 +4,10 @@ from ayon_core.hosts.blender.api import plugin
def append_workfile(context, fname, do_import): def append_workfile(context, fname, do_import):
asset = context['asset']['name'] folder_name = context['asset']['name']
subset = context['subset']['name'] product_name = context['subset']['name']
group_name = plugin.prepare_scene_name(asset, subset) group_name = plugin.prepare_scene_name(folder_name, product_name)
# We need to preserve the original names of the scenes, otherwise, # We need to preserve the original names of the scenes, otherwise,
# if there are duplicate names in the current workfile, the imported # if there are duplicate names in the current workfile, the imported

View file

@ -134,13 +134,15 @@ class CacheModelLoader(plugin.AssetLoader):
""" """
libpath = self.filepath_from_context(context) libpath = self.filepath_from_context(context)
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
asset_name = plugin.prepare_scene_name(asset, subset) asset_name = plugin.prepare_scene_name(folder_name, product_name)
unique_number = plugin.get_unique_number(asset, subset) unique_number = plugin.get_unique_number(folder_name, product_name)
group_name = plugin.prepare_scene_name(asset, subset, unique_number) group_name = plugin.prepare_scene_name(
namespace = namespace or f"{asset}_{unique_number}" folder_name, product_name, unique_number
)
namespace = namespace or f"{folder_name}_{unique_number}"
containers = bpy.data.collections.get(AVALON_CONTAINERS) containers = bpy.data.collections.get(AVALON_CONTAINERS)
if not containers: if not containers:
@ -159,6 +161,7 @@ class CacheModelLoader(plugin.AssetLoader):
self._link_objects(objects, asset_group, containers, asset_group) self._link_objects(objects, asset_group, containers, asset_group)
product_type = context["subset"]["data"]["family"]
asset_group[AVALON_PROPERTY] = { asset_group[AVALON_PROPERTY] = {
"schema": "openpype:container-2.0", "schema": "openpype:container-2.0",
"id": AVALON_CONTAINER_ID, "id": AVALON_CONTAINER_ID,
@ -169,14 +172,14 @@ class CacheModelLoader(plugin.AssetLoader):
"libpath": libpath, "libpath": libpath,
"asset_name": asset_name, "asset_name": asset_name,
"parent": str(context["representation"]["parent"]), "parent": str(context["representation"]["parent"]),
"family": context["representation"]["context"]["family"], "productType": product_type,
"objectName": group_name "objectName": group_name
} }
self[:] = objects self[:] = objects
return objects return objects
def exec_update(self, container: Dict, representation: Dict): def exec_update(self, container: Dict, context: Dict):
"""Update the loaded asset. """Update the loaded asset.
This will remove all objects of the current collection, load the new This will remove all objects of the current collection, load the new
@ -188,15 +191,16 @@ class CacheModelLoader(plugin.AssetLoader):
Warning: Warning:
No nested collections are supported at the moment! No nested collections are supported at the moment!
""" """
repre_doc = context["representation"]
object_name = container["objectName"] object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name) asset_group = bpy.data.objects.get(object_name)
libpath = Path(get_representation_path(representation)) libpath = Path(get_representation_path(repre_doc))
extension = libpath.suffix.lower() extension = libpath.suffix.lower()
self.log.info( self.log.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(repre_doc, indent=2),
) )
assert asset_group, ( assert asset_group, (
@ -241,7 +245,7 @@ class CacheModelLoader(plugin.AssetLoader):
asset_group.matrix_basis = mat asset_group.matrix_basis = mat
metadata["libpath"] = str(libpath) metadata["libpath"] = str(libpath)
metadata["representation"] = str(representation["_id"]) metadata["representation"] = str(repre_doc["_id"])
def exec_remove(self, container: Dict) -> bool: def exec_remove(self, container: Dict) -> bool:
"""Remove an existing container from a Blender scene. """Remove an existing container from a Blender scene.

View file

@ -44,11 +44,11 @@ class BlendActionLoader(plugin.AssetLoader):
""" """
libpath = self.filepath_from_context(context) libpath = self.filepath_from_context(context)
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
lib_container = plugin.prepare_scene_name(asset, subset) lib_container = plugin.prepare_scene_name(folder_name, product_name)
container_name = plugin.prepare_scene_name( container_name = plugin.prepare_scene_name(
asset, subset, namespace folder_name, product_name, namespace
) )
container = bpy.data.collections.new(lib_container) container = bpy.data.collections.new(lib_container)
@ -114,7 +114,7 @@ class BlendActionLoader(plugin.AssetLoader):
self[:] = nodes self[:] = nodes
return nodes return nodes
def update(self, container: Dict, representation: Dict): def update(self, container: Dict, context: Dict):
"""Update the loaded asset. """Update the loaded asset.
This will remove all objects of the current collection, load the new This will remove all objects of the current collection, load the new
@ -126,18 +126,18 @@ class BlendActionLoader(plugin.AssetLoader):
Warning: Warning:
No nested collections are supported at the moment! No nested collections are supported at the moment!
""" """
repre_doc = context["representation"]
collection = bpy.data.collections.get( collection = bpy.data.collections.get(
container["objectName"] container["objectName"]
) )
libpath = Path(get_representation_path(representation)) libpath = Path(get_representation_path(repre_doc))
extension = libpath.suffix.lower() extension = libpath.suffix.lower()
logger.info( logger.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(repre_doc, indent=2),
) )
assert collection, ( assert collection, (
@ -241,7 +241,7 @@ class BlendActionLoader(plugin.AssetLoader):
# Save the list of objects in the metadata container # Save the list of objects in the metadata container
collection_metadata["objects"] = objects_list collection_metadata["objects"] = objects_list
collection_metadata["libpath"] = str(libpath) collection_metadata["libpath"] = str(libpath)
collection_metadata["representation"] = str(representation["_id"]) collection_metadata["representation"] = str(repre_doc["_id"])
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')

View file

@ -39,13 +39,15 @@ class AudioLoader(plugin.AssetLoader):
options: Additional settings dictionary options: Additional settings dictionary
""" """
libpath = self.filepath_from_context(context) libpath = self.filepath_from_context(context)
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
asset_name = plugin.prepare_scene_name(asset, subset) asset_name = plugin.prepare_scene_name(folder_name, product_name)
unique_number = plugin.get_unique_number(asset, subset) unique_number = plugin.get_unique_number(folder_name, product_name)
group_name = plugin.prepare_scene_name(asset, subset, unique_number) group_name = plugin.prepare_scene_name(
namespace = namespace or f"{asset}_{unique_number}" folder_name, product_name, unique_number
)
namespace = namespace or f"{folder_name}_{unique_number}"
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
if not avalon_container: if not avalon_container:
@ -85,7 +87,7 @@ class AudioLoader(plugin.AssetLoader):
"libpath": libpath, "libpath": libpath,
"asset_name": asset_name, "asset_name": asset_name,
"parent": str(context["representation"]["parent"]), "parent": str(context["representation"]["parent"]),
"family": context["representation"]["context"]["family"], "productType": context["subset"]["data"]["family"],
"objectName": group_name, "objectName": group_name,
"audio": audio "audio": audio
} }
@ -94,7 +96,7 @@ class AudioLoader(plugin.AssetLoader):
self[:] = objects self[:] = objects
return [objects] return [objects]
def exec_update(self, container: Dict, representation: Dict): def exec_update(self, container: Dict, context: Dict):
"""Update an audio strip in the sequence editor. """Update an audio strip in the sequence editor.
Arguments: Arguments:
@ -103,14 +105,15 @@ class AudioLoader(plugin.AssetLoader):
representation (openpype:representation-1.0): Representation to representation (openpype:representation-1.0): Representation to
update, from `host.ls()`. update, from `host.ls()`.
""" """
repre_doc = context["representation"]
object_name = container["objectName"] object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name) asset_group = bpy.data.objects.get(object_name)
libpath = Path(get_representation_path(representation)) libpath = Path(get_representation_path(repre_doc))
self.log.info( self.log.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(repre_doc, indent=2),
) )
assert asset_group, ( assert asset_group, (
@ -173,8 +176,8 @@ class AudioLoader(plugin.AssetLoader):
window_manager.windows[-1].screen.areas[0].type = old_type window_manager.windows[-1].screen.areas[0].type = old_type
metadata["libpath"] = str(libpath) metadata["libpath"] = str(libpath)
metadata["representation"] = str(representation["_id"]) metadata["representation"] = str(repre_doc["_id"])
metadata["parent"] = str(representation["parent"]) metadata["parent"] = str(repre_doc["parent"])
metadata["audio"] = new_audio metadata["audio"] = new_audio
def exec_remove(self, container: Dict) -> bool: def exec_remove(self, container: Dict) -> bool:

View file

@ -127,20 +127,22 @@ class BlendLoader(plugin.AssetLoader):
options: Additional settings dictionary options: Additional settings dictionary
""" """
libpath = self.filepath_from_context(context) libpath = self.filepath_from_context(context)
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
try: try:
family = context["representation"]["context"]["family"] product_type = context["subset"]["data"]["family"]
except ValueError: except ValueError:
family = "model" product_type = "model"
representation = str(context["representation"]["_id"]) representation = str(context["representation"]["_id"])
asset_name = plugin.prepare_scene_name(asset, subset) asset_name = plugin.prepare_scene_name(folder_name, product_name)
unique_number = plugin.get_unique_number(asset, subset) unique_number = plugin.get_unique_number(folder_name, product_name)
group_name = plugin.prepare_scene_name(asset, subset, unique_number) group_name = plugin.prepare_scene_name(
namespace = namespace or f"{asset}_{unique_number}" folder_name, product_name, unique_number
)
namespace = namespace or f"{folder_name}_{unique_number}"
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
if not avalon_container: if not avalon_container:
@ -149,8 +151,8 @@ class BlendLoader(plugin.AssetLoader):
container, members = self._process_data(libpath, group_name) container, members = self._process_data(libpath, group_name)
if family == "layout": if product_type == "layout":
self._post_process_layout(container, asset, representation) self._post_process_layout(container, folder_name, representation)
avalon_container.objects.link(container) avalon_container.objects.link(container)
@ -164,7 +166,7 @@ class BlendLoader(plugin.AssetLoader):
"libpath": libpath, "libpath": libpath,
"asset_name": asset_name, "asset_name": asset_name,
"parent": str(context["representation"]["parent"]), "parent": str(context["representation"]["parent"]),
"family": context["representation"]["context"]["family"], "productType": context["subset"]["data"]["family"],
"objectName": group_name, "objectName": group_name,
"members": members, "members": members,
} }
@ -179,13 +181,14 @@ class BlendLoader(plugin.AssetLoader):
self[:] = objects self[:] = objects
return objects return objects
def exec_update(self, container: Dict, representation: Dict): def exec_update(self, container: Dict, context: Dict):
""" """
Update the loaded asset. Update the loaded asset.
""" """
repre_doc = context["representation"]
group_name = container["objectName"] group_name = container["objectName"]
asset_group = bpy.data.objects.get(group_name) asset_group = bpy.data.objects.get(group_name)
libpath = Path(get_representation_path(representation)).as_posix() libpath = Path(get_representation_path(repre_doc)).as_posix()
assert asset_group, ( assert asset_group, (
f"The asset is not loaded: {container['objectName']}" f"The asset is not loaded: {container['objectName']}"
@ -232,8 +235,8 @@ class BlendLoader(plugin.AssetLoader):
new_data = { new_data = {
"libpath": libpath, "libpath": libpath,
"representation": str(representation["_id"]), "representation": str(repre_doc["_id"]),
"parent": str(representation["parent"]), "parent": str(repre_doc["parent"]),
"members": members, "members": members,
} }

View file

@ -34,7 +34,7 @@ class BlendSceneLoader(plugin.AssetLoader):
return None return None
def _process_data(self, libpath, group_name, family): def _process_data(self, libpath, group_name, product_type):
# Append all the data from the .blend file # Append all the data from the .blend file
with bpy.data.libraries.load( with bpy.data.libraries.load(
libpath, link=False, relative=False libpath, link=False, relative=False
@ -82,25 +82,29 @@ class BlendSceneLoader(plugin.AssetLoader):
options: Additional settings dictionary options: Additional settings dictionary
""" """
libpath = self.filepath_from_context(context) libpath = self.filepath_from_context(context)
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
try: try:
family = context["representation"]["context"]["family"] product_type = context["subset"]["data"]["family"]
except ValueError: except ValueError:
family = "model" product_type = "model"
asset_name = plugin.prepare_scene_name(asset, subset) asset_name = plugin.prepare_scene_name(folder_name, product_name)
unique_number = plugin.get_unique_number(asset, subset) unique_number = plugin.get_unique_number(folder_name, product_name)
group_name = plugin.prepare_scene_name(asset, subset, unique_number) group_name = plugin.prepare_scene_name(
namespace = namespace or f"{asset}_{unique_number}" folder_name, product_name, unique_number
)
namespace = namespace or f"{folder_name}_{unique_number}"
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
if not avalon_container: if not avalon_container:
avalon_container = bpy.data.collections.new(name=AVALON_CONTAINERS) avalon_container = bpy.data.collections.new(name=AVALON_CONTAINERS)
bpy.context.scene.collection.children.link(avalon_container) bpy.context.scene.collection.children.link(avalon_container)
container, members = self._process_data(libpath, group_name, family) container, members = self._process_data(
libpath, group_name, product_type
)
avalon_container.children.link(container) avalon_container.children.link(container)
@ -114,7 +118,7 @@ class BlendSceneLoader(plugin.AssetLoader):
"libpath": libpath, "libpath": libpath,
"asset_name": asset_name, "asset_name": asset_name,
"parent": str(context["representation"]["parent"]), "parent": str(context["representation"]["parent"]),
"family": context["representation"]["context"]["family"], "productType": context["subset"]["data"]["family"],
"objectName": group_name, "objectName": group_name,
"members": members, "members": members,
} }
@ -129,13 +133,14 @@ class BlendSceneLoader(plugin.AssetLoader):
self[:] = objects self[:] = objects
return objects return objects
def exec_update(self, container: Dict, representation: Dict): def exec_update(self, container: Dict, context: Dict):
""" """
Update the loaded asset. Update the loaded asset.
""" """
repre_doc = context["representation"]
group_name = container["objectName"] group_name = container["objectName"]
asset_group = bpy.data.collections.get(group_name) asset_group = bpy.data.collections.get(group_name)
libpath = Path(get_representation_path(representation)).as_posix() libpath = Path(get_representation_path(repre_doc)).as_posix()
assert asset_group, ( assert asset_group, (
f"The asset is not loaded: {container['objectName']}" f"The asset is not loaded: {container['objectName']}"
@ -167,8 +172,12 @@ class BlendSceneLoader(plugin.AssetLoader):
self.exec_remove(container) self.exec_remove(container)
family = container["family"] product_type = container.get("productType")
asset_group, members = self._process_data(libpath, group_name, family) if product_type is None:
product_type = container["family"]
asset_group, members = self._process_data(
libpath, group_name, product_type
)
for member in members: for member in members:
if member.name in collection_parents: if member.name in collection_parents:
@ -193,8 +202,8 @@ class BlendSceneLoader(plugin.AssetLoader):
new_data = { new_data = {
"libpath": libpath, "libpath": libpath,
"representation": str(representation["_id"]), "representation": str(repre_doc["_id"]),
"parent": str(representation["parent"]), "parent": str(repre_doc["parent"]),
"members": members, "members": members,
} }

View file

@ -84,13 +84,15 @@ class AbcCameraLoader(plugin.AssetLoader):
libpath = self.filepath_from_context(context) libpath = self.filepath_from_context(context)
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
asset_name = plugin.prepare_scene_name(asset, subset) asset_name = plugin.prepare_scene_name(folder_name, product_name)
unique_number = plugin.get_unique_number(asset, subset) unique_number = plugin.get_unique_number(folder_name, product_name)
group_name = plugin.prepare_scene_name(asset, subset, unique_number) group_name = plugin.prepare_scene_name(
namespace = namespace or f"{asset}_{unique_number}" folder_name, product_name, unique_number
)
namespace = namespace or f"{folder_name}_{unique_number}"
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
if not avalon_container: if not avalon_container:
@ -121,14 +123,14 @@ class AbcCameraLoader(plugin.AssetLoader):
"libpath": libpath, "libpath": libpath,
"asset_name": asset_name, "asset_name": asset_name,
"parent": str(context["representation"]["parent"]), "parent": str(context["representation"]["parent"]),
"family": context["representation"]["context"]["family"], "productType": context["subset"]["data"]["family"],
"objectName": group_name, "objectName": group_name,
} }
self[:] = objects self[:] = objects
return objects return objects
def exec_update(self, container: Dict, representation: Dict): def exec_update(self, container: Dict, context: Dict):
"""Update the loaded asset. """Update the loaded asset.
This will remove all objects of the current collection, load the new This will remove all objects of the current collection, load the new
@ -140,15 +142,16 @@ class AbcCameraLoader(plugin.AssetLoader):
Warning: Warning:
No nested collections are supported at the moment! No nested collections are supported at the moment!
""" """
repre_doc = context["representation"]
object_name = container["objectName"] object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name) asset_group = bpy.data.objects.get(object_name)
libpath = Path(get_representation_path(representation)) libpath = Path(get_representation_path(repre_doc))
extension = libpath.suffix.lower() extension = libpath.suffix.lower()
self.log.info( self.log.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(repre_doc, indent=2),
) )
assert asset_group, ( assert asset_group, (
@ -183,7 +186,7 @@ class AbcCameraLoader(plugin.AssetLoader):
asset_group.matrix_basis = mat asset_group.matrix_basis = mat
metadata["libpath"] = str(libpath) metadata["libpath"] = str(libpath)
metadata["representation"] = str(representation["_id"]) metadata["representation"] = str(repre_doc["_id"])
def exec_remove(self, container: Dict) -> bool: def exec_remove(self, container: Dict) -> bool:
"""Remove an existing container from a Blender scene. """Remove an existing container from a Blender scene.

View file

@ -87,13 +87,15 @@ class FbxCameraLoader(plugin.AssetLoader):
options: Additional settings dictionary options: Additional settings dictionary
""" """
libpath = self.filepath_from_context(context) libpath = self.filepath_from_context(context)
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
asset_name = plugin.prepare_scene_name(asset, subset) asset_name = plugin.prepare_scene_name(folder_name, product_name)
unique_number = plugin.get_unique_number(asset, subset) unique_number = plugin.get_unique_number(folder_name, product_name)
group_name = plugin.prepare_scene_name(asset, subset, unique_number) group_name = plugin.prepare_scene_name(
namespace = namespace or f"{asset}_{unique_number}" folder_name, product_name, unique_number
)
namespace = namespace or f"{folder_name}_{unique_number}"
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
if not avalon_container: if not avalon_container:
@ -124,14 +126,14 @@ class FbxCameraLoader(plugin.AssetLoader):
"libpath": libpath, "libpath": libpath,
"asset_name": asset_name, "asset_name": asset_name,
"parent": str(context["representation"]["parent"]), "parent": str(context["representation"]["parent"]),
"family": context["representation"]["context"]["family"], "productType": context["subset"]["data"]["family"],
"objectName": group_name "objectName": group_name
} }
self[:] = objects self[:] = objects
return objects return objects
def exec_update(self, container: Dict, representation: Dict): def exec_update(self, container: Dict, context: Dict):
"""Update the loaded asset. """Update the loaded asset.
This will remove all objects of the current collection, load the new This will remove all objects of the current collection, load the new
@ -143,15 +145,16 @@ class FbxCameraLoader(plugin.AssetLoader):
Warning: Warning:
No nested collections are supported at the moment! No nested collections are supported at the moment!
""" """
repre_doc = context["representation"]
object_name = container["objectName"] object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name) asset_group = bpy.data.objects.get(object_name)
libpath = Path(get_representation_path(representation)) libpath = Path(get_representation_path(repre_doc))
extension = libpath.suffix.lower() extension = libpath.suffix.lower()
self.log.info( self.log.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(repre_doc, indent=2),
) )
assert asset_group, ( assert asset_group, (
@ -193,7 +196,7 @@ class FbxCameraLoader(plugin.AssetLoader):
asset_group.matrix_basis = mat asset_group.matrix_basis = mat
metadata["libpath"] = str(libpath) metadata["libpath"] = str(libpath)
metadata["representation"] = str(representation["_id"]) metadata["representation"] = str(repre_doc["_id"])
def exec_remove(self, container: Dict) -> bool: def exec_remove(self, container: Dict) -> bool:
"""Remove an existing container from a Blender scene. """Remove an existing container from a Blender scene.

View file

@ -131,13 +131,15 @@ class FbxModelLoader(plugin.AssetLoader):
options: Additional settings dictionary options: Additional settings dictionary
""" """
libpath = self.filepath_from_context(context) libpath = self.filepath_from_context(context)
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
asset_name = plugin.prepare_scene_name(asset, subset) asset_name = plugin.prepare_scene_name(folder_name, product_name)
unique_number = plugin.get_unique_number(asset, subset) unique_number = plugin.get_unique_number(folder_name, product_name)
group_name = plugin.prepare_scene_name(asset, subset, unique_number) group_name = plugin.prepare_scene_name(
namespace = namespace or f"{asset}_{unique_number}" folder_name, product_name, unique_number
)
namespace = namespace or f"{folder_name}_{unique_number}"
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
if not avalon_container: if not avalon_container:
@ -168,14 +170,14 @@ class FbxModelLoader(plugin.AssetLoader):
"libpath": libpath, "libpath": libpath,
"asset_name": asset_name, "asset_name": asset_name,
"parent": str(context["representation"]["parent"]), "parent": str(context["representation"]["parent"]),
"family": context["representation"]["context"]["family"], "productType": context["subset"]["data"]["family"],
"objectName": group_name "objectName": group_name
} }
self[:] = objects self[:] = objects
return objects return objects
def exec_update(self, container: Dict, representation: Dict): def exec_update(self, container: Dict, context: Dict):
"""Update the loaded asset. """Update the loaded asset.
This will remove all objects of the current collection, load the new This will remove all objects of the current collection, load the new
@ -187,15 +189,16 @@ class FbxModelLoader(plugin.AssetLoader):
Warning: Warning:
No nested collections are supported at the moment! No nested collections are supported at the moment!
""" """
repre_doc = context["representation"]
object_name = container["objectName"] object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name) asset_group = bpy.data.objects.get(object_name)
libpath = Path(get_representation_path(representation)) libpath = Path(get_representation_path(repre_doc))
extension = libpath.suffix.lower() extension = libpath.suffix.lower()
self.log.info( self.log.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(repre_doc, indent=2),
) )
assert asset_group, ( assert asset_group, (
@ -248,7 +251,7 @@ class FbxModelLoader(plugin.AssetLoader):
asset_group.matrix_basis = mat asset_group.matrix_basis = mat
metadata["libpath"] = str(libpath) metadata["libpath"] = str(libpath)
metadata["representation"] = str(representation["_id"]) metadata["representation"] = str(repre_doc["_id"])
def exec_remove(self, container: Dict) -> bool: def exec_remove(self, container: Dict) -> bool:
"""Remove an existing container from a Blender scene. """Remove an existing container from a Blender scene.

View file

@ -50,11 +50,11 @@ class JsonLayoutLoader(plugin.AssetLoader):
if anim_collection: if anim_collection:
bpy.data.collections.remove(anim_collection) bpy.data.collections.remove(anim_collection)
def _get_loader(self, loaders, family): def _get_loader(self, loaders, product_type):
name = "" name = ""
if family == 'rig': if product_type == 'rig':
name = "BlendRigLoader" name = "BlendRigLoader"
elif family == 'model': elif product_type == 'model':
name = "BlendModelLoader" name = "BlendModelLoader"
if name == "": if name == "":
@ -76,10 +76,12 @@ class JsonLayoutLoader(plugin.AssetLoader):
for element in data: for element in data:
reference = element.get('reference') reference = element.get('reference')
family = element.get('family') product_type = element.get("product_type")
if product_type is None:
product_type = element.get("family")
loaders = loaders_from_representation(all_loaders, reference) loaders = loaders_from_representation(all_loaders, reference)
loader = self._get_loader(loaders, family) loader = self._get_loader(loaders, product_type)
if not loader: if not loader:
continue continue
@ -95,7 +97,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
'parent': asset_group, 'parent': asset_group,
'transform': element.get('transform'), 'transform': element.get('transform'),
'action': action, 'action': action,
'create_animation': True if family == 'rig' else False, 'create_animation': True if product_type == 'rig' else False,
'animation_asset': asset 'animation_asset': asset
} }
@ -127,7 +129,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
# legacy_create( # legacy_create(
# creator_plugin, # creator_plugin,
# name="camera", # name="camera",
# # name=f"{unique_number}_{subset}_animation", # # name=f"{unique_number}_{product[name]}_animation",
# asset=asset, # asset=asset,
# options={"useSelection": False} # options={"useSelection": False}
# # data={"dependencies": str(context["representation"]["_id"])} # # data={"dependencies": str(context["representation"]["_id"])}
@ -146,13 +148,15 @@ class JsonLayoutLoader(plugin.AssetLoader):
options: Additional settings dictionary options: Additional settings dictionary
""" """
libpath = self.filepath_from_context(context) libpath = self.filepath_from_context(context)
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
asset_name = plugin.prepare_scene_name(asset, subset) asset_name = plugin.prepare_scene_name(folder_name, product_name)
unique_number = plugin.get_unique_number(asset, subset) unique_number = plugin.get_unique_number(folder_name, product_name)
group_name = plugin.prepare_scene_name(asset, subset, unique_number) group_name = plugin.prepare_scene_name(
namespace = namespace or f"{asset}_{unique_number}" folder_name, product_name, unique_number
)
namespace = namespace or f"{folder_name}_{unique_number}"
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
if not avalon_container: if not avalon_container:
@ -177,14 +181,14 @@ class JsonLayoutLoader(plugin.AssetLoader):
"libpath": libpath, "libpath": libpath,
"asset_name": asset_name, "asset_name": asset_name,
"parent": str(context["representation"]["parent"]), "parent": str(context["representation"]["parent"]),
"family": context["representation"]["context"]["family"], "productType": context["subset"]["data"]["family"],
"objectName": group_name "objectName": group_name
} }
self[:] = asset_group.children self[:] = asset_group.children
return asset_group.children return asset_group.children
def exec_update(self, container: Dict, representation: Dict): def exec_update(self, container: Dict, context: Dict):
"""Update the loaded asset. """Update the loaded asset.
This will remove all objects of the current collection, load the new This will remove all objects of the current collection, load the new
@ -193,15 +197,16 @@ class JsonLayoutLoader(plugin.AssetLoader):
will not be removed, only unlinked. Normally this should not be the will not be removed, only unlinked. Normally this should not be the
case though. case though.
""" """
repre_doc = context["representation"]
object_name = container["objectName"] object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name) asset_group = bpy.data.objects.get(object_name)
libpath = Path(get_representation_path(representation)) libpath = Path(get_representation_path(repre_doc))
extension = libpath.suffix.lower() extension = libpath.suffix.lower()
self.log.info( self.log.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(repre_doc, indent=2),
) )
assert asset_group, ( assert asset_group, (
@ -239,7 +244,10 @@ class JsonLayoutLoader(plugin.AssetLoader):
for obj in asset_group.children: for obj in asset_group.children:
obj_meta = obj.get(AVALON_PROPERTY) obj_meta = obj.get(AVALON_PROPERTY)
if obj_meta.get('family') == 'rig': product_type = obj_meta.get("productType")
if product_type is None:
product_type = obj_meta.get("family")
if product_type == "rig":
rig = None rig = None
for child in obj.children: for child in obj.children:
if child.type == 'ARMATURE': if child.type == 'ARMATURE':
@ -262,7 +270,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
asset_group.matrix_basis = mat asset_group.matrix_basis = mat
metadata["libpath"] = str(libpath) metadata["libpath"] = str(libpath)
metadata["representation"] = str(representation["_id"]) metadata["representation"] = str(repre_doc["_id"])
def exec_remove(self, container: Dict) -> bool: def exec_remove(self, container: Dict) -> bool:
"""Remove an existing container from a Blender scene. """Remove an existing container from a Blender scene.

View file

@ -93,18 +93,18 @@ class BlendLookLoader(plugin.AssetLoader):
""" """
libpath = self.filepath_from_context(context) libpath = self.filepath_from_context(context)
asset = context["asset"]["name"] folder_name = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
lib_container = plugin.prepare_scene_name( lib_container = plugin.prepare_scene_name(
asset, subset folder_name, product_name
) )
unique_number = plugin.get_unique_number( unique_number = plugin.get_unique_number(
asset, subset folder_name, product_name
) )
namespace = namespace or f"{asset}_{unique_number}" namespace = namespace or f"{folder_name}_{unique_number}"
container_name = plugin.prepare_scene_name( container_name = plugin.prepare_scene_name(
asset, subset, unique_number folder_name, product_name, unique_number
) )
container = bpy.data.collections.new(lib_container) container = bpy.data.collections.new(lib_container)
@ -131,22 +131,23 @@ class BlendLookLoader(plugin.AssetLoader):
metadata["materials"] = materials metadata["materials"] = materials
metadata["parent"] = str(context["representation"]["parent"]) metadata["parent"] = str(context["representation"]["parent"])
metadata["family"] = context["representation"]["context"]["family"] metadata["product_type"] = context["subset"]["data"]["family"]
nodes = list(container.objects) nodes = list(container.objects)
nodes.append(container) nodes.append(container)
self[:] = nodes self[:] = nodes
return nodes return nodes
def update(self, container: Dict, representation: Dict): def update(self, container: Dict, context: Dict):
collection = bpy.data.collections.get(container["objectName"]) collection = bpy.data.collections.get(container["objectName"])
libpath = Path(get_representation_path(representation)) repre_doc = context["representation"]
libpath = Path(get_representation_path(repre_doc))
extension = libpath.suffix.lower() extension = libpath.suffix.lower()
self.log.info( self.log.info(
"Container: %s\nRepresentation: %s", "Container: %s\nRepresentation: %s",
pformat(container, indent=2), pformat(container, indent=2),
pformat(representation, indent=2), pformat(repre_doc, indent=2),
) )
assert collection, ( assert collection, (
@ -201,7 +202,7 @@ class BlendLookLoader(plugin.AssetLoader):
collection_metadata["objects"] = objects collection_metadata["objects"] = objects
collection_metadata["materials"] = materials collection_metadata["materials"] = materials
collection_metadata["libpath"] = str(libpath) collection_metadata["libpath"] = str(libpath)
collection_metadata["representation"] = str(representation["_id"]) collection_metadata["representation"] = str(repre_doc["_id"])
def remove(self, container: Dict) -> bool: def remove(self, container: Dict) -> bool:
collection = bpy.data.collections.get(container["objectName"]) collection = bpy.data.collections.get(container["objectName"])

View file

@ -25,7 +25,7 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin):
members.extend(instance_node.children) members.extend(instance_node.children)
# Special case for animation instances, include armatures # Special case for animation instances, include armatures
if instance.data["family"] == "animation": if instance.data["productType"] == "animation":
for obj in instance_node.objects: for obj in instance_node.objects:
if obj.type == 'EMPTY' and obj.get(AVALON_PROPERTY): if obj.type == 'EMPTY' and obj.get(AVALON_PROPERTY):
members.extend( members.extend(

View file

@ -19,9 +19,9 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin):
# Define extract output file path # Define extract output file path
stagingdir = self.staging_dir(instance) stagingdir = self.staging_dir(instance)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
instance_name = f"{asset_name}_{subset}" instance_name = f"{folder_name}_{product_name}"
filename = f"{instance_name}.abc" filename = f"{instance_name}.abc"
filepath = os.path.join(stagingdir, filename) filepath = os.path.join(stagingdir, filename)

View file

@ -23,9 +23,9 @@ class ExtractAnimationABC(
# Define extract output file path # Define extract output file path
stagingdir = self.staging_dir(instance) stagingdir = self.staging_dir(instance)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
instance_name = f"{asset_name}_{subset}" instance_name = f"{folder_name}_{product_name}"
filename = f"{instance_name}.abc" filename = f"{instance_name}.abc"
filepath = os.path.join(stagingdir, filename) filepath = os.path.join(stagingdir, filename)

View file

@ -20,9 +20,9 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin):
# Define extract output file path # Define extract output file path
stagingdir = self.staging_dir(instance) stagingdir = self.staging_dir(instance)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
instance_name = f"{asset_name}_{subset}" instance_name = f"{folder_name}_{product_name}"
filename = f"{instance_name}.blend" filename = f"{instance_name}.blend"
filepath = os.path.join(stagingdir, filename) filepath = os.path.join(stagingdir, filename)

View file

@ -23,9 +23,9 @@ class ExtractBlendAnimation(
# Define extract output file path # Define extract output file path
stagingdir = self.staging_dir(instance) stagingdir = self.staging_dir(instance)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
instance_name = f"{asset_name}_{subset}" instance_name = f"{folder_name}_{product_name}"
filename = f"{instance_name}.blend" filename = f"{instance_name}.blend"
filepath = os.path.join(stagingdir, filename) filepath = os.path.join(stagingdir, filename)

View file

@ -21,9 +21,9 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin):
# Define extract output file path # Define extract output file path
stagingdir = self.staging_dir(instance) stagingdir = self.staging_dir(instance)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
instance_name = f"{asset_name}_{subset}" instance_name = f"{folder_name}_{product_name}"
filename = f"{instance_name}.abc" filename = f"{instance_name}.abc"
filepath = os.path.join(stagingdir, filename) filepath = os.path.join(stagingdir, filename)

View file

@ -20,9 +20,9 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin):
# Define extract output file path # Define extract output file path
stagingdir = self.staging_dir(instance) stagingdir = self.staging_dir(instance)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
instance_name = f"{asset_name}_{subset}" instance_name = f"{folder_name}_{product_name}"
filename = f"{instance_name}.fbx" filename = f"{instance_name}.fbx"
filepath = os.path.join(stagingdir, filename) filepath = os.path.join(stagingdir, filename)

View file

@ -21,9 +21,9 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin):
# Define extract output file path # Define extract output file path
stagingdir = self.staging_dir(instance) stagingdir = self.staging_dir(instance)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
instance_name = f"{asset_name}_{subset}" instance_name = f"{folder_name}_{product_name}"
filename = f"{instance_name}.fbx" filename = f"{instance_name}.fbx"
filepath = os.path.join(stagingdir, filename) filepath = os.path.join(stagingdir, filename)

View file

@ -145,9 +145,9 @@ class ExtractAnimationFBX(
root.select_set(True) root.select_set(True)
armature.select_set(True) armature.select_set(True)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
instance_name = f"{asset_name}_{subset}" instance_name = f"{folder_name}_{product_name}"
fbx_filename = f"{instance_name}_{armature.name}.fbx" fbx_filename = f"{instance_name}_{armature.name}.fbx"
filepath = os.path.join(stagingdir, fbx_filename) filepath = os.path.join(stagingdir, fbx_filename)

View file

@ -133,7 +133,7 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
fbx_count = 0 fbx_count = 0
project_name = instance.context.data["projectEntity"]["name"] project_name = instance.context.data["projectName"]
for asset in asset_group.children: for asset in asset_group.children:
metadata = asset.get(AVALON_PROPERTY) metadata = asset.get(AVALON_PROPERTY)
if not metadata: if not metadata:
@ -147,7 +147,9 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
continue continue
version_id = metadata["parent"] version_id = metadata["parent"]
family = metadata["family"] product_type = metadata.get("product_type")
if product_type is None:
product_type = metadata["family"]
self.log.debug("Parent: {}".format(version_id)) self.log.debug("Parent: {}".format(version_id))
# Get blend reference # Get blend reference
@ -179,7 +181,8 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
json_element["reference_fbx"] = str(fbx_id) json_element["reference_fbx"] = str(fbx_id)
if abc_id: if abc_id:
json_element["reference_abc"] = str(abc_id) json_element["reference_abc"] = str(abc_id)
json_element["family"] = family
json_element["product_type"] = product_type
json_element["instance_name"] = asset.name json_element["instance_name"] = asset.name
json_element["asset_name"] = metadata["asset_name"] json_element["asset_name"] = metadata["asset_name"]
json_element["file_path"] = metadata["libpath"] json_element["file_path"] = metadata["libpath"]
@ -215,7 +218,7 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
] ]
# Extract the animation as well # Extract the animation as well
if family == "rig": if product_type == "rig":
f, n = self._export_animation( f, n = self._export_animation(
asset, instance, stagingdir, fbx_count) asset, instance, stagingdir, fbx_count)
if f: if f:
@ -225,9 +228,9 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
json_data.append(json_element) json_data.append(json_element)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
instance_name = f"{asset_name}_{subset}" instance_name = f"{folder_name}_{product_name}"
json_filename = f"{instance_name}.json" json_filename = f"{instance_name}.json"
json_path = os.path.join(stagingdir, json_filename) json_path = os.path.join(stagingdir, json_filename)

View file

@ -55,9 +55,9 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin):
# get output path # get output path
stagingdir = self.staging_dir(instance) stagingdir = self.staging_dir(instance)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
filename = f"{asset_name}_{subset}" filename = f"{folder_name}_{product_name}"
path = os.path.join(stagingdir, filename) path = os.path.join(stagingdir, filename)

View file

@ -32,9 +32,9 @@ class ExtractThumbnail(publish.Extractor):
return return
stagingdir = self.staging_dir(instance) stagingdir = self.staging_dir(instance)
asset_name = instance.data["assetEntity"]["name"] folder_name = instance.data["assetEntity"]["name"]
subset = instance.data["subset"] product_name = instance.data["productName"]
filename = f"{asset_name}_{subset}" filename = f"{folder_name}_{product_name}"
path = os.path.join(stagingdir, filename) path = os.path.join(stagingdir, filename)
@ -42,11 +42,11 @@ class ExtractThumbnail(publish.Extractor):
camera = instance.data.get("review_camera", "AUTO") camera = instance.data.get("review_camera", "AUTO")
start = instance.data.get("frameStart", bpy.context.scene.frame_start) start = instance.data.get("frameStart", bpy.context.scene.frame_start)
family = instance.data.get("family") product_type = instance.data["productType"]
isolate = instance.data("isolate", None) isolate = instance.data("isolate", None)
presets = json.loads(self.presets) presets = json.loads(self.presets)
preset = presets.get(family, {}) preset = presets.get(product_type, {})
preset.update({ preset.update({
"camera": camera, "camera": camera,

View file

@ -28,25 +28,26 @@ class IntegrateAnimation(
# Update the json file for the setdress to add the published # Update the json file for the setdress to add the published
# representations of the animations # representations of the animations
for json_dict in data: for json_dict in data:
json_product_name = json_dict["productName"]
i = None i = None
for elem in instance.context: for elem in instance.context:
if elem.data.get('subset') == json_dict['subset']: if elem.data["productName"] == json_product_name:
i = elem i = elem
break break
if not i: if not i:
continue continue
rep = None rep = None
pub_repr = i.data.get('published_representations') pub_repr = i.data["published_representations"]
for elem in pub_repr: for elem in pub_repr:
if pub_repr.get(elem).get('representation').get('name') == "fbx": if pub_repr[elem]["representation"]["name"] == "fbx":
rep = pub_repr.get(elem) rep = pub_repr[elem]
break break
if not rep: if not rep:
continue continue
obj_id = rep.get('representation').get('_id') obj_id = rep["representation"]["_id"]
if obj_id: if obj_id:
json_dict['_id'] = str(obj_id) json_dict["representation_id"] = str(obj_id)
with open(json_path, "w") as file: with open(json_path, "w") as file:
json.dump(data, fp=file, indent=2) json.dump(data, fp=file, indent=2)

View file

@ -28,15 +28,27 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin,
def process(self, instance): def process(self, instance):
if not self.is_active(instance.data): if not self.is_active(instance.data):
return return
tree = bpy.context.scene.node_tree
output_type = "CompositorNodeOutputFile"
output_node = None
# Remove all output nodes that inlcude "AYON" in the name.
# There should be only one.
for node in tree.nodes:
if node.bl_idname == output_type and "AYON" in node.name:
output_node = node
break
if not output_node:
raise PublishValidationError(
"No output node found in the compositor tree."
)
filepath = bpy.data.filepath filepath = bpy.data.filepath
file = os.path.basename(filepath) file = os.path.basename(filepath)
filename, ext = os.path.splitext(file) filename, ext = os.path.splitext(file)
if filename not in bpy.context.scene.render.filepath: if filename not in output_node.base_path:
raise PublishValidationError( raise PublishValidationError(
"Render output folder " "Render output folder doesn't match the blender scene name! "
"doesn't match the blender scene name! " "Use Repair action to fix the folder file path."
"Use Repair action to "
"fix the folder file path."
) )
@classmethod @classmethod

View file

@ -26,6 +26,7 @@ class ValidateFileSaved(pyblish.api.ContextPlugin,
hosts = ["blender"] hosts = ["blender"]
label = "Validate File Saved" label = "Validate File Saved"
optional = False optional = False
# TODO rename to 'exclude_product_types'
exclude_families = [] exclude_families = []
actions = [SaveWorkfileAction] actions = [SaveWorkfileAction]
@ -41,8 +42,8 @@ class ValidateFileSaved(pyblish.api.ContextPlugin,
# Do not validate workfile has unsaved changes if only instances # Do not validate workfile has unsaved changes if only instances
# present of families that should be excluded # present of families that should be excluded
families = { product_types = {
instance.data["family"] for instance in context instance.data["productType"] for instance in context
# Consider only enabled instances # Consider only enabled instances
if instance.data.get("publish", True) if instance.data.get("publish", True)
and instance.data.get("active", True) and instance.data.get("active", True)
@ -52,7 +53,7 @@ class ValidateFileSaved(pyblish.api.ContextPlugin,
return any(family in exclude_family return any(family in exclude_family
for exclude_family in self.exclude_families) for exclude_family in self.exclude_families)
if all(is_excluded(family) for family in families): if all(is_excluded(product_type) for product_type in product_types):
self.log.debug("Only excluded families found, skipping workfile " self.log.debug("Only excluded families found, skipping workfile "
"unsaved changes validation..") "unsaved changes validation..")
return return

View file

@ -22,7 +22,7 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
asset_name = get_asset_name_identifier(asset_entity) asset_name = get_asset_name_identifier(asset_entity)
shared_instance_data = { shared_instance_data = {
"asset": asset_name, "folderPath": asset_name,
"frameStart": asset_entity["data"]["frameStart"], "frameStart": asset_entity["data"]["frameStart"],
"frameEnd": asset_entity["data"]["frameEnd"], "frameEnd": asset_entity["data"]["frameEnd"],
"handleStart": asset_entity["data"]["handleStart"], "handleStart": asset_entity["data"]["handleStart"],
@ -46,17 +46,18 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
shared_instance_data.update(celaction_kwargs) shared_instance_data.update(celaction_kwargs)
# workfile instance # workfile instance
family = "workfile" product_type = "workfile"
subset = family + task.capitalize() product_name = product_type + task.capitalize()
# Create instance # Create instance
instance = context.create_instance(subset) instance = context.create_instance(product_name)
# creating instance data # creating instance data
instance.data.update({ instance.data.update({
"subset": subset,
"label": scene_file, "label": scene_file,
"family": family, "productName": product_name,
"families": [], "productType": product_type,
"family": product_type,
"families": [product_type],
"representations": [] "representations": []
}) })
@ -76,17 +77,19 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
self.log.info('Publishing Celaction workfile') self.log.info('Publishing Celaction workfile')
# render instance # render instance
subset = f"render{task}Main" product_name = f"render{task}Main"
instance = context.create_instance(name=subset) product_type = "render.farm"
instance = context.create_instance(name=product_name)
# getting instance state # getting instance state
instance.data["publish"] = True instance.data["publish"] = True
# add assetEntity data into instance # add assetEntity data into instance
instance.data.update({ instance.data.update({
"label": "{} - farm".format(subset), "label": "{} - farm".format(product_name),
"family": "render.farm", "productType": product_type,
"families": [], "family": product_type,
"subset": subset "families": [product_type],
"productName": product_name
}) })
# adding basic script data # adding basic script data

View file

@ -19,12 +19,14 @@ class CollectRenderPath(pyblish.api.InstancePlugin):
anatomy = instance.context.data["anatomy"] anatomy = instance.context.data["anatomy"]
anatomy_data = copy.deepcopy(instance.data["anatomyData"]) anatomy_data = copy.deepcopy(instance.data["anatomyData"])
padding = anatomy.templates.get("frame_padding", 4) padding = anatomy.templates.get("frame_padding", 4)
product_type = "render"
anatomy_data.update({ anatomy_data.update({
"frame": f"%0{padding}d", "frame": f"%0{padding}d",
"family": "render", "family": product_type,
"representation": self.output_extension, "representation": self.output_extension,
"ext": self.output_extension "ext": self.output_extension
}) })
anatomy_data["product"]["type"] = product_type
anatomy_filled = anatomy.format(anatomy_data) anatomy_filled = anatomy.format(anatomy_data)

View file

@ -147,8 +147,8 @@ def imprint(segment, data=None):
Examples: Examples:
data = { data = {
'asset': 'sq020sh0280', 'asset': 'sq020sh0280',
'family': 'render', 'productType': 'render',
'subset': 'subsetMain' 'productName': 'productMain'
} }
""" """
data = data or {} data = data or {}

View file

@ -353,9 +353,9 @@ class PublishableClip:
rename_default = False rename_default = False
hierarchy_default = "{_folder_}/{_sequence_}/{_track_}" hierarchy_default = "{_folder_}/{_sequence_}/{_track_}"
clip_name_default = "shot_{_trackIndex_:0>3}_{_clipIndex_:0>4}" clip_name_default = "shot_{_trackIndex_:0>3}_{_clipIndex_:0>4}"
subset_name_default = "[ track name ]"
review_track_default = "[ none ]" review_track_default = "[ none ]"
subset_family_default = "plate" base_product_name_default = "[ track name ]"
base_product_type_default = "plate"
count_from_default = 10 count_from_default = 10
count_steps_default = 10 count_steps_default = 10
vertical_sync_default = False vertical_sync_default = False
@ -368,7 +368,7 @@ class PublishableClip:
def __init__(self, segment, **kwargs): def __init__(self, segment, **kwargs):
self.rename_index = kwargs["rename_index"] self.rename_index = kwargs["rename_index"]
self.family = kwargs["family"] self.product_type = kwargs["family"]
self.log = kwargs["log"] self.log = kwargs["log"]
# get main parent objects # get main parent objects
@ -486,10 +486,10 @@ class PublishableClip:
"countFrom", {}).get("value") or self.count_from_default "countFrom", {}).get("value") or self.count_from_default
self.count_steps = self.ui_inputs.get( self.count_steps = self.ui_inputs.get(
"countSteps", {}).get("value") or self.count_steps_default "countSteps", {}).get("value") or self.count_steps_default
self.subset_name = self.ui_inputs.get( self.base_product_name = self.ui_inputs.get(
"subsetName", {}).get("value") or self.subset_name_default "productName", {}).get("value") or self.base_product_name_default
self.subset_family = self.ui_inputs.get( self.base_product_type = self.ui_inputs.get(
"subsetFamily", {}).get("value") or self.subset_family_default "productType", {}).get("value") or self.base_product_type_default
self.vertical_sync = self.ui_inputs.get( self.vertical_sync = self.ui_inputs.get(
"vSyncOn", {}).get("value") or self.vertical_sync_default "vSyncOn", {}).get("value") or self.vertical_sync_default
self.driving_layer = self.ui_inputs.get( self.driving_layer = self.ui_inputs.get(
@ -509,12 +509,14 @@ class PublishableClip:
or self.retimed_framerange_default or self.retimed_framerange_default
) )
# build subset name from layer name # build product name from layer name
if self.subset_name == "[ track name ]": if self.base_product_name == "[ track name ]":
self.subset_name = self.track_name self.base_product_name = self.track_name
# create subset for publishing # create product for publishing
self.subset = self.subset_family + self.subset_name.capitalize() self.product_name = (
self.base_product_type + self.base_product_name.capitalize()
)
def _replace_hash_to_expression(self, name, text): def _replace_hash_to_expression(self, name, text):
""" Replace hash with number in correct padding. """ """ Replace hash with number in correct padding. """
@ -608,14 +610,14 @@ class PublishableClip:
_hero_data = deepcopy(hero_data) _hero_data = deepcopy(hero_data)
_hero_data.update({"heroTrack": False}) _hero_data.update({"heroTrack": False})
if _in <= self.clip_in and _out >= self.clip_out: if _in <= self.clip_in and _out >= self.clip_out:
data_subset = hero_data["subset"] data_product_name = hero_data["productName"]
# add track index in case duplicity of names in hero data # add track index in case duplicity of names in hero data
if self.subset in data_subset: if self.product_name in data_product_name:
_hero_data["subset"] = self.subset + str( _hero_data["productName"] = self.product_name + str(
self.track_index) self.track_index)
# in case track name and subset name is the same then add # in case track name and product name is the same then add
if self.subset_name == self.track_name: if self.base_product_name == self.track_name:
_hero_data["subset"] = self.subset _hero_data["productName"] = self.product_name
# assign data to return hierarchy data to tag # assign data to return hierarchy data to tag
tag_hierarchy_data = _hero_data tag_hierarchy_data = _hero_data
break break
@ -637,9 +639,9 @@ class PublishableClip:
"hierarchy": hierarchy_filled, "hierarchy": hierarchy_filled,
"parents": self.parents, "parents": self.parents,
"hierarchyData": hierarchy_formatting_data, "hierarchyData": hierarchy_formatting_data,
"subset": self.subset, "productName": self.product_name,
"family": self.subset_family, "productType": self.base_product_type,
"families": [self.family] "families": [self.base_product_type, self.product_type]
} }
def _convert_to_entity(self, type, template): def _convert_to_entity(self, type, template):
@ -704,7 +706,7 @@ class ClipLoader(LoaderPlugin):
_mapping = None _mapping = None
_host_settings = None _host_settings = None
def apply_settings(cls, project_settings, system_settings): def apply_settings(cls, project_settings):
plugin_type_settings = ( plugin_type_settings = (
project_settings project_settings

View file

@ -6,7 +6,7 @@ class CreateShotClip(opfapi.Creator):
"""Publishable clip""" """Publishable clip"""
label = "Create Publishable Clip" label = "Create Publishable Clip"
family = "clip" product_type = "clip"
icon = "film" icon = "film"
defaults = ["Main"] defaults = ["Main"]
@ -32,7 +32,7 @@ class CreateShotClip(opfapi.Creator):
# open widget for plugins inputs # open widget for plugins inputs
results_back = self.create_widget( results_back = self.create_widget(
"Pype publish attributes creator", "AYON publish attributes creator",
"Define sequential rename and fill hierarchy data.", "Define sequential rename and fill hierarchy data.",
gui_inputs gui_inputs
) )
@ -62,7 +62,7 @@ class CreateShotClip(opfapi.Creator):
"log": self.log, "log": self.log,
"ui_inputs": results_back, "ui_inputs": results_back,
"avalon": self.data, "avalon": self.data,
"family": self.data["family"] "product_type": self.data["productType"]
} }
for i, segment in enumerate(sorted_selected_segments): for i, segment in enumerate(sorted_selected_segments):
@ -203,19 +203,19 @@ class CreateShotClip(opfapi.Creator):
"target": "ui", "target": "ui",
"order": 3, "order": 3,
"value": { "value": {
"subsetName": { "productName": {
"value": ["[ track name ]", "main", "bg", "fg", "bg", "value": ["[ track name ]", "main", "bg", "fg", "bg",
"animatic"], "animatic"],
"type": "QComboBox", "type": "QComboBox",
"label": "Subset Name", "label": "Subset Name",
"target": "ui", "target": "ui",
"toolTip": "chose subset name pattern, if [ track name ] is selected, name of track layer will be used", # noqa "toolTip": "chose product name pattern, if [ track name ] is selected, name of track layer will be used", # noqa
"order": 0}, "order": 0},
"subsetFamily": { "productType": {
"value": ["plate", "take"], "value": ["plate", "take"],
"type": "QComboBox", "type": "QComboBox",
"label": "Subset Family", "label": "Subset Family",
"target": "ui", "toolTip": "What use of this subset is for", # noqa "target": "ui", "toolTip": "What use of this product is for", # noqa
"order": 1}, "order": 1},
"reviewTrack": { "reviewTrack": {
"value": ["< none >"] + gui_tracks, "value": ["< none >"] + gui_tracks,
@ -229,7 +229,7 @@ class CreateShotClip(opfapi.Creator):
"type": "QCheckBox", "type": "QCheckBox",
"label": "Include audio", "label": "Include audio",
"target": "tag", "target": "tag",
"toolTip": "Process subsets with corresponding audio", # noqa "toolTip": "Process products with corresponding audio", # noqa
"order": 3}, "order": 3},
"sourceResolution": { "sourceResolution": {
"value": False, "value": False,

View file

@ -11,7 +11,7 @@ from ayon_core.lib.transcoding import (
class LoadClip(opfapi.ClipLoader): class LoadClip(opfapi.ClipLoader):
"""Load a subset to timeline as clip """Load a product to timeline as clip
Place clip to timeline on its asset origin timings collected Place clip to timeline on its asset origin timings collected
during conforming to project during conforming to project
@ -31,14 +31,14 @@ class LoadClip(opfapi.ClipLoader):
# settings # settings
reel_group_name = "OpenPype_Reels" reel_group_name = "OpenPype_Reels"
reel_name = "Loaded" reel_name = "Loaded"
clip_name_template = "{asset}_{subset}<_{output}>" clip_name_template = "{folder[name]}_{product[name]}<_{output}>"
""" Anatomy keys from version context data and dynamically added: """ Anatomy keys from version context data and dynamically added:
- {layerName} - original layer name token - {layerName} - original layer name token
- {layerUID} - original layer UID token - {layerUID} - original layer UID token
- {originalBasename} - original clip name taken from file - {originalBasename} - original clip name taken from file
""" """
layer_rename_template = "{asset}_{subset}<_{output}>" layer_rename_template = "{folder[name]}_{product[name]}<_{output}>"
layer_rename_patterns = [] layer_rename_patterns = []
def load(self, context, name, namespace, options): def load(self, context, name, namespace, options):
@ -180,27 +180,27 @@ class LoadClip(opfapi.ClipLoader):
# unwrapping segment from input clip # unwrapping segment from input clip
pass pass
# def switch(self, container, representation): # def switch(self, container, context):
# self.update(container, representation) # self.update(container, context)
# def update(self, container, representation): # def update(self, container, context):
# """ Updating previously loaded clips # """ Updating previously loaded clips
# """ # """
# # load clip to timeline and get main variables # # load clip to timeline and get main variables
# repre_doc = context['representation']
# name = container['name'] # name = container['name']
# namespace = container['namespace'] # namespace = container['namespace']
# track_item = phiero.get_track_items( # track_item = phiero.get_track_items(
# track_item_name=namespace) # track_item_name=namespace)
# version = io.find_one({ # version = io.find_one({
# "type": "version", # "type": "version",
# "_id": representation["parent"] # "_id": repre_doc["parent"]
# }) # })
# version_data = version.get("data", {}) # version_data = version.get("data", {})
# version_name = version.get("name", None) # version_name = version.get("name", None)
# colorspace = version_data.get("colorspace", None) # colorspace = version_data.get("colorspace", None)
# object_name = "{}_{}".format(name, namespace) # object_name = "{}_{}".format(name, namespace)
# file = get_representation_path(representation).replace("\\", "/") # file = get_representation_path(repre_doc).replace("\\", "/")
# clip = track_item.source() # clip = track_item.source()
# # reconnect media to new path # # reconnect media to new path
@ -225,7 +225,7 @@ class LoadClip(opfapi.ClipLoader):
# # add variables related to version context # # add variables related to version context
# data_imprint.update({ # data_imprint.update({
# "representation": str(representation["_id"]), # "representation": str(repre_doc["_id"]),
# "version": version_name, # "version": version_name,
# "colorspace": colorspace, # "colorspace": colorspace,
# "objectName": object_name # "objectName": object_name

View file

@ -10,7 +10,7 @@ from ayon_core.lib.transcoding import (
) )
class LoadClipBatch(opfapi.ClipLoader): class LoadClipBatch(opfapi.ClipLoader):
"""Load a subset to timeline as clip """Load a product to timeline as clip
Place clip to timeline on its asset origin timings collected Place clip to timeline on its asset origin timings collected
during conforming to project during conforming to project
@ -29,14 +29,14 @@ class LoadClipBatch(opfapi.ClipLoader):
# settings # settings
reel_name = "OP_LoadedReel" reel_name = "OP_LoadedReel"
clip_name_template = "{batch}_{asset}_{subset}<_{output}>" clip_name_template = "{batch}_{folder[name]}_{product[name]}<_{output}>"
""" Anatomy keys from version context data and dynamically added: """ Anatomy keys from version context data and dynamically added:
- {layerName} - original layer name token - {layerName} - original layer name token
- {layerUID} - original layer UID token - {layerUID} - original layer UID token
- {originalBasename} - original clip name taken from file - {originalBasename} - original clip name taken from file
""" """
layer_rename_template = "{asset}_{subset}<_{output}>" layer_rename_template = "{folder[name]}_{product[name]}<_{output}>"
layer_rename_patterns = [] layer_rename_patterns = []
def load(self, context, name, namespace, options): def load(self, context, name, namespace, options):
@ -50,17 +50,8 @@ class LoadClipBatch(opfapi.ClipLoader):
version_name = version.get("name", None) version_name = version.get("name", None)
colorspace = self.get_colorspace(context) colorspace = self.get_colorspace(context)
# TODO remove '{folder[name]}' and '{product[name]}' replacement clip_name_template = self.clip_name_template
clip_name_template = ( layer_rename_template = self.layer_rename_template
self.clip_name_template
.replace("{folder[name]}", "{asset}")
.replace("{product[name]}", "{subset}")
)
layer_rename_template = (
self.layer_rename_template
.replace("{folder[name]}", "{asset}")
.replace("{product[name]}", "{subset}")
)
# in case output is not in context replace key to representation # in case output is not in context replace key to representation
if not context["representation"]["context"].get("output"): if not context["representation"]["context"].get("output"):
clip_name_template = clip_name_template.replace( clip_name_template = clip_name_template.replace(
@ -68,8 +59,22 @@ class LoadClipBatch(opfapi.ClipLoader):
layer_rename_template = layer_rename_template.replace( layer_rename_template = layer_rename_template.replace(
"output", "representation") "output", "representation")
asset_doc = context["asset"]
subset_doc = context["subset"]
formatting_data = deepcopy(context["representation"]["context"]) formatting_data = deepcopy(context["representation"]["context"])
formatting_data["batch"] = self.batch.name.get_value() formatting_data["batch"] = self.batch.name.get_value()
formatting_data.update({
"asset": asset_doc["name"],
"folder": {
"name": asset_doc["name"],
},
"subset": subset_doc["name"],
"family": subset_doc["data"]["family"],
"product": {
"name": subset_doc["name"],
"type": subset_doc["data"]["family"],
}
})
clip_name = StringTemplate(clip_name_template).format( clip_name = StringTemplate(clip_name_template).format(
formatting_data) formatting_data)

View file

@ -59,6 +59,6 @@ class CollectTestSelection(pyblish.api.ContextPlugin):
opfapi.imprint(segment, { opfapi.imprint(segment, {
'asset': segment.name.get_value(), 'asset': segment.name.get_value(),
'family': 'render', 'productType': 'render',
'subset': 'subsetMain' 'productName': 'productMain'
}) })

View file

@ -3,6 +3,7 @@ from types import NoneType
import pyblish import pyblish
import ayon_core.hosts.flame.api as opfapi import ayon_core.hosts.flame.api as opfapi
from ayon_core.hosts.flame.otio import flame_export from ayon_core.hosts.flame.otio import flame_export
from ayon_core.pipeline import AYON_INSTANCE_ID, AVALON_INSTANCE_ID
from ayon_core.pipeline.editorial import ( from ayon_core.pipeline.editorial import (
is_overlapping_otio_ranges, is_overlapping_otio_ranges,
get_media_range_with_retimes get_media_range_with_retimes
@ -47,7 +48,9 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
if not marker_data: if not marker_data:
continue continue
if marker_data.get("id") != "pyblish.avalon.instance": if marker_data.get("id") not in {
AYON_INSTANCE_ID, AVALON_INSTANCE_ID
}:
continue continue
self.log.debug("__ segment.name: {}".format( self.log.debug("__ segment.name: {}".format(
@ -107,24 +110,25 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
# add ocio_data to instance data # add ocio_data to instance data
inst_data.update(otio_data) inst_data.update(otio_data)
asset = marker_data["asset"] folder_path = marker_data["folderPath"]
subset = marker_data["subset"] folder_name = folder_path.rsplit("/")[-1]
product_name = marker_data["productName"]
# insert family into families # insert product type into families
family = marker_data["family"] product_type = marker_data["productType"]
families = [str(f) for f in marker_data["families"]] families = [str(f) for f in marker_data["families"]]
families.insert(0, str(family)) families.insert(0, str(product_type))
# form label # form label
label = asset label = folder_name
if asset != clip_name: if folder_name != clip_name:
label += " ({})".format(clip_name) label += " ({})".format(clip_name)
label += " {} [{}]".format(subset, ", ".join(families)) label += " {} [{}]".format(product_name, ", ".join(families))
inst_data.update({ inst_data.update({
"name": "{}_{}".format(asset, subset), "name": "{}_{}".format(folder_name, product_name),
"label": label, "label": label,
"asset": asset, "folderPath": folder_path,
"item": segment, "item": segment,
"families": families, "families": families,
"publish": marker_data["publish"], "publish": marker_data["publish"],
@ -332,26 +336,28 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
if not hierarchy_data: if not hierarchy_data:
return return
asset = data["asset"] folder_path = data["folderPath"]
subset = "shotMain" folder_name = folder_path.rsplit("/")[-1]
product_name = "shotMain"
# insert family into families # insert product type into families
family = "shot" product_type = "shot"
# form label # form label
label = asset label = folder_name
if asset != clip_name: if folder_name != clip_name:
label += " ({}) ".format(clip_name) label += " ({}) ".format(clip_name)
label += " {}".format(subset) label += " {}".format(product_name)
label += " [{}]".format(family) label += " [{}]".format(product_type)
data.update({ data.update({
"name": "{}_{}".format(asset, subset), "name": "{}_{}".format(folder_name, product_name),
"label": label, "label": label,
"subset": subset, "productName": product_name,
"asset": asset, "folderPath": folder_path,
"family": family, "productType": product_type,
"families": [] "family": product_type,
"families": [product_type]
}) })
instance = context.create_instance(**data) instance = context.create_instance(**data)

View file

@ -3,7 +3,7 @@ import pyblish.api
from ayon_core.client import get_asset_name_identifier from ayon_core.client import get_asset_name_identifier
import ayon_core.hosts.flame.api as opfapi import ayon_core.hosts.flame.api as opfapi
from ayon_core.hosts.flame.otio import flame_export from ayon_core.hosts.flame.otio import flame_export
from ayon_core.pipeline.create import get_subset_name from ayon_core.pipeline.create import get_product_name
class CollecTimelineOTIO(pyblish.api.ContextPlugin): class CollecTimelineOTIO(pyblish.api.ContextPlugin):
@ -14,7 +14,7 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin):
def process(self, context): def process(self, context):
# plugin defined # plugin defined
family = "workfile" product_type = "workfile"
variant = "otioTimeline" variant = "otioTimeline"
# main # main
@ -23,29 +23,30 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin):
project = opfapi.get_current_project() project = opfapi.get_current_project()
sequence = opfapi.get_current_sequence(opfapi.CTX.selection) sequence = opfapi.get_current_sequence(opfapi.CTX.selection)
# create subset name # create product name
subset_name = get_subset_name( product_name = get_product_name(
family,
variant,
task_name,
asset_doc,
context.data["projectName"], context.data["projectName"],
asset_doc,
task_name,
context.data["hostName"], context.data["hostName"],
product_type,
variant,
project_settings=context.data["project_settings"] project_settings=context.data["project_settings"]
) )
asset_name = get_asset_name_identifier(asset_doc) folder_path = get_asset_name_identifier(asset_doc)
# adding otio timeline to context # adding otio timeline to context
with opfapi.maintained_segment_selection(sequence) as selected_seg: with opfapi.maintained_segment_selection(sequence) as selected_seg:
otio_timeline = flame_export.create_otio_timeline(sequence) otio_timeline = flame_export.create_otio_timeline(sequence)
instance_data = { instance_data = {
"name": subset_name, "name": product_name,
"asset": asset_name, "folderPath": folder_path,
"subset": subset_name, "productName": product_name,
"family": "workfile", "productType": product_type,
"families": [] "family": product_type,
"families": [product_type]
} }
# create instance with workfile # create instance with workfile

View file

@ -55,7 +55,7 @@ class ExtractProductResources(publish.Extractor):
# flame objects # flame objects
segment = instance.data["item"] segment = instance.data["item"]
asset_name = instance.data["asset"] folder_path = instance.data["folderPath"]
segment_name = segment.name.get_value() segment_name = segment.name.get_value()
clip_path = instance.data["path"] clip_path = instance.data["path"]
sequence_clip = instance.context.data["flameSequence"] sequence_clip = instance.context.data["flameSequence"]
@ -249,7 +249,7 @@ class ExtractProductResources(publish.Extractor):
out_mark = in_mark + source_duration_handles out_mark = in_mark + source_duration_handles
exporting_clip = self.import_clip(clip_path) exporting_clip = self.import_clip(clip_path)
exporting_clip.name.set_value("{}_{}".format( exporting_clip.name.set_value("{}_{}".format(
asset_name, segment_name)) folder_path, segment_name))
# add xml tags modifications # add xml tags modifications
modify_xml_data.update({ modify_xml_data.update({

View file

@ -44,8 +44,8 @@ class IntegrateBatchGroup(pyblish.api.InstancePlugin):
)) ))
# load plate to batch group # load plate to batch group
self.log.info("Loading subset `{}` into batch `{}`".format( self.log.info("Loading product `{}` into batch `{}`".format(
instance.data["subset"], bgroup.name.get_value() instance.data["productName"], bgroup.name.get_value()
)) ))
self._load_clip_to_context(instance, bgroup) self._load_clip_to_context(instance, bgroup)
@ -168,10 +168,10 @@ class IntegrateBatchGroup(pyblish.api.InstancePlugin):
handle_start = instance.data["handleStart"] handle_start = instance.data["handleStart"]
handle_end = instance.data["handleEnd"] handle_end = instance.data["handleEnd"]
frame_duration = (frame_end - frame_start) + 1 frame_duration = (frame_end - frame_start) + 1
asset_name = instance.data["asset"] folder_path = instance.data["folderPath"]
task_name = task_data["name"] task_name = task_data["name"]
batchgroup_name = "{}_{}".format(asset_name, task_name) batchgroup_name = "{}_{}".format(folder_path, task_name)
batch_data = { batch_data = {
"shematic_reels": [ "shematic_reels": [

View file

@ -12,7 +12,9 @@ from ayon_core.lib import (
) )
from ayon_core.pipeline import ( from ayon_core.pipeline import (
Creator, Creator,
CreatedInstance CreatedInstance,
AVALON_INSTANCE_ID,
AYON_INSTANCE_ID,
) )
@ -31,14 +33,16 @@ class GenericCreateSaver(Creator):
# TODO: This should be renamed together with Nuke so it is aligned # TODO: This should be renamed together with Nuke so it is aligned
temp_rendering_path_template = ( temp_rendering_path_template = (
"{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}") "{workdir}/renders/fusion/{product[name]}/"
"{product[name]}.{frame}.{ext}"
)
def create(self, subset_name, instance_data, pre_create_data): def create(self, product_name, instance_data, pre_create_data):
self.pass_pre_attributes_to_instance(instance_data, pre_create_data) self.pass_pre_attributes_to_instance(instance_data, pre_create_data)
instance = CreatedInstance( instance = CreatedInstance(
family=self.family, product_type=self.product_type,
subset_name=subset_name, product_name=product_name,
data=instance_data, data=instance_data,
creator=self, creator=self,
) )
@ -109,23 +113,23 @@ class GenericCreateSaver(Creator):
tool.SetData(f"openpype.{key}", value) tool.SetData(f"openpype.{key}", value)
def _update_tool_with_data(self, tool, data): def _update_tool_with_data(self, tool, data):
"""Update tool node name and output path based on subset data""" """Update tool node name and output path based on product data"""
if "subset" not in data: if "productName" not in data:
return return
original_subset = tool.GetData("openpype.subset") original_product_name = tool.GetData("openpype.productName")
original_format = tool.GetData( original_format = tool.GetData(
"openpype.creator_attributes.image_format" "openpype.creator_attributes.image_format"
) )
subset = data["subset"] product_name = data["productName"]
if ( if (
original_subset != subset original_product_name != product_name
or original_format != data["creator_attributes"]["image_format"] or original_format != data["creator_attributes"]["image_format"]
): ):
self._configure_saver_tool(data, tool, subset) self._configure_saver_tool(data, tool, product_name)
def _configure_saver_tool(self, data, tool, subset): def _configure_saver_tool(self, data, tool, product_name):
formatting_data = deepcopy(data) formatting_data = deepcopy(data)
# get frame padding from anatomy templates # get frame padding from anatomy templates
@ -135,25 +139,39 @@ class GenericCreateSaver(Creator):
ext = data["creator_attributes"]["image_format"] ext = data["creator_attributes"]["image_format"]
# Subset change detected # Subset change detected
product_type = formatting_data["productType"]
f_product_name = formatting_data["productName"]
folder_path = formatting_data["folderPath"]
folder_name = folder_path.rsplit("/", 1)[-1]
workdir = os.path.normpath(os.getenv("AYON_WORKDIR")) workdir = os.path.normpath(os.getenv("AYON_WORKDIR"))
formatting_data.update({ formatting_data.update({
"workdir": workdir, "workdir": workdir,
"frame": "0" * frame_padding, "frame": "0" * frame_padding,
"ext": ext, "ext": ext,
"product": { "product": {
"name": formatting_data["subset"], "name": f_product_name,
"type": formatting_data["family"], "type": product_type,
}, },
# TODO add more variants for 'folder' and 'task'
"folder": {
"name": folder_name,
},
"task": {
"name": data["task"],
},
# Backwards compatibility
"asset": folder_name,
"subset": f_product_name,
"family": product_type,
}) })
# build file path to render # build file path to render
# TODO make sure the keys are available in 'formatting_data' # TODO make sure the keys are available in 'formatting_data'
temp_rendering_path_template = ( temp_rendering_path_template = (
self.temp_rendering_path_template self.temp_rendering_path_template
.replace("{product[name]}", "{subset}") .replace("{task}", "{task[name]}")
.replace("{product[type]}", "{family}")
.replace("{folder[name]}", "{asset}")
.replace("{task[name]}", "{task}")
) )
filepath = temp_rendering_path_template.format(**formatting_data) filepath = temp_rendering_path_template.format(**formatting_data)
@ -162,9 +180,9 @@ class GenericCreateSaver(Creator):
tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath)) tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath))
# Rename tool # Rename tool
if tool.Name != subset: if tool.Name != product_name:
print(f"Renaming {tool.Name} -> {subset}") print(f"Renaming {tool.Name} -> {product_name}")
tool.SetAttrs({"TOOLS_Name": subset}) tool.SetAttrs({"TOOLS_Name": product_name})
def get_managed_tool_data(self, tool): def get_managed_tool_data(self, tool):
"""Return data of the tool if it matches creator identifier""" """Return data of the tool if it matches creator identifier"""
@ -172,13 +190,13 @@ class GenericCreateSaver(Creator):
if not isinstance(data, dict): if not isinstance(data, dict):
return return
required = { if (
"id": "pyblish.avalon.instance", data.get("creator_identifier") != self.identifier
"creator_identifier": self.identifier, or data.get("id") not in {
} AYON_INSTANCE_ID, AVALON_INSTANCE_ID
for key, value in required.items(): }
if key not in data or data[key] != value: ):
return return
# Get active state from the actual tool state # Get active state from the actual tool state
attrs = tool.GetAttrs() attrs = tool.GetAttrs()

View file

@ -17,7 +17,7 @@ class CreateImageSaver(GenericCreateSaver):
identifier = "io.openpype.creators.fusion.imagesaver" identifier = "io.openpype.creators.fusion.imagesaver"
label = "Image (saver)" label = "Image (saver)"
name = "image" name = "image"
family = "image" product_type = "image"
description = "Fusion Saver to generate image" description = "Fusion Saver to generate image"
default_frame = 0 default_frame = 0

View file

@ -12,7 +12,7 @@ class CreateSaver(GenericCreateSaver):
identifier = "io.openpype.creators.fusion.saver" identifier = "io.openpype.creators.fusion.saver"
label = "Render (saver)" label = "Render (saver)"
name = "render" name = "render"
family = "render" product_type = "render"
description = "Fusion Saver to generate image sequence" description = "Fusion Saver to generate image sequence"
default_frame_range_option = "asset_db" default_frame_range_option = "asset_db"

View file

@ -10,7 +10,7 @@ from ayon_core.pipeline import (
class FusionWorkfileCreator(AutoCreator): class FusionWorkfileCreator(AutoCreator):
identifier = "workfile" identifier = "workfile"
family = "workfile" product_type = "workfile"
label = "Workfile" label = "Workfile"
icon = "fa5.file" icon = "fa5.file"
@ -27,9 +27,12 @@ class FusionWorkfileCreator(AutoCreator):
if not data: if not data:
return return
product_name = data.get("productName")
if product_name is None:
product_name = data["subset"]
instance = CreatedInstance( instance = CreatedInstance(
family=self.family, product_type=self.product_type,
subset_name=data["subset"], product_name=product_name,
data=data, data=data,
creator=self creator=self
) )
@ -59,7 +62,7 @@ class FusionWorkfileCreator(AutoCreator):
existing_instance = None existing_instance = None
for instance in self.create_context.instances: for instance in self.create_context.instances:
if instance.family == self.family: if instance.product_type == self.product_type:
existing_instance = instance existing_instance = instance
break break
@ -75,9 +78,12 @@ class FusionWorkfileCreator(AutoCreator):
if existing_instance is None: if existing_instance is None:
asset_doc = get_asset_by_name(project_name, asset_name) asset_doc = get_asset_by_name(project_name, asset_name)
subset_name = self.get_subset_name( product_name = self.get_product_name(
self.default_variant, task_name, asset_doc, project_name,
project_name, host_name asset_doc,
task_name,
self.default_variant,
host_name,
) )
data = { data = {
"folderPath": asset_name, "folderPath": asset_name,
@ -85,12 +91,17 @@ class FusionWorkfileCreator(AutoCreator):
"variant": self.default_variant, "variant": self.default_variant,
} }
data.update(self.get_dynamic_data( data.update(self.get_dynamic_data(
self.default_variant, task_name, asset_doc, project_name,
project_name, host_name, None asset_doc,
task_name,
self.default_variant,
host_name,
None
)) ))
new_instance = CreatedInstance( new_instance = CreatedInstance(
self.family, subset_name, data, self self.product_type, product_name, data, self
) )
new_instance.transient_data["comp"] = comp new_instance.transient_data["comp"] = comp
self._add_instance_to_context(new_instance) self._add_instance_to_context(new_instance)
@ -100,10 +111,13 @@ class FusionWorkfileCreator(AutoCreator):
or existing_instance["task"] != task_name or existing_instance["task"] != task_name
): ):
asset_doc = get_asset_by_name(project_name, asset_name) asset_doc = get_asset_by_name(project_name, asset_name)
subset_name = self.get_subset_name( product_name = self.get_product_name(
self.default_variant, task_name, asset_doc, project_name,
project_name, host_name asset_doc,
task_name,
self.default_variant,
host_name,
) )
existing_instance["folderPath"] = asset_name existing_instance["folderPath"] = asset_name
existing_instance["task"] = task_name existing_instance["task"] = task_name
existing_instance["subset"] = subset_name existing_instance["productName"] = product_name

View file

@ -44,23 +44,24 @@ class FusionLoadAlembicMesh(load.LoaderPlugin):
context=context, context=context,
loader=self.__class__.__name__) loader=self.__class__.__name__)
def switch(self, container, representation): def switch(self, container, context):
self.update(container, representation) self.update(container, context)
def update(self, container, representation): def update(self, container, context):
"""Update Alembic path""" """Update Alembic path"""
tool = container["_tool"] tool = container["_tool"]
assert tool.ID == self.tool_type, f"Must be {self.tool_type}" assert tool.ID == self.tool_type, f"Must be {self.tool_type}"
comp = tool.Comp() comp = tool.Comp()
path = get_representation_path(representation) repre_doc = context["representation"]
path = get_representation_path(repre_doc)
with comp_lock_and_undo_chunk(comp, "Update tool"): with comp_lock_and_undo_chunk(comp, "Update tool"):
tool["Filename"] = path tool["Filename"] = path
# Update the imprinted representation # Update the imprinted representation
tool.SetData("avalon.representation", str(representation["_id"])) tool.SetData("avalon.representation", str(repre_doc["_id"]))
def remove(self, container): def remove(self, container):
tool = container["_tool"] tool = container["_tool"]

View file

@ -59,23 +59,24 @@ class FusionLoadFBXMesh(load.LoaderPlugin):
loader=self.__class__.__name__, loader=self.__class__.__name__,
) )
def switch(self, container, representation): def switch(self, container, context):
self.update(container, representation) self.update(container, context)
def update(self, container, representation): def update(self, container, context):
"""Update path""" """Update path"""
tool = container["_tool"] tool = container["_tool"]
assert tool.ID == self.tool_type, f"Must be {self.tool_type}" assert tool.ID == self.tool_type, f"Must be {self.tool_type}"
comp = tool.Comp() comp = tool.Comp()
path = get_representation_path(representation) repre_doc = context["representation"]
path = get_representation_path(repre_doc)
with comp_lock_and_undo_chunk(comp, "Update tool"): with comp_lock_and_undo_chunk(comp, "Update tool"):
tool["ImportFile"] = path tool["ImportFile"] = path
# Update the imprinted representation # Update the imprinted representation
tool.SetData("avalon.representation", str(representation["_id"])) tool.SetData("avalon.representation", str(repre_doc["_id"]))
def remove(self, container): def remove(self, container):
tool = container["_tool"] tool = container["_tool"]

View file

@ -136,7 +136,7 @@ class FusionLoadSequence(load.LoaderPlugin):
"render", "render",
"plate", "plate",
"image", "image",
"onilne", "online",
] ]
representations = ["*"] representations = ["*"]
extensions = set( extensions = set(
@ -175,10 +175,10 @@ class FusionLoadSequence(load.LoaderPlugin):
loader=self.__class__.__name__, loader=self.__class__.__name__,
) )
def switch(self, container, representation): def switch(self, container, context):
self.update(container, representation) self.update(container, context)
def update(self, container, representation): def update(self, container, context):
"""Update the Loader's path """Update the Loader's path
Fusion automatically tries to reset some variables when changing Fusion automatically tries to reset some variables when changing
@ -224,7 +224,8 @@ class FusionLoadSequence(load.LoaderPlugin):
assert tool.ID == "Loader", "Must be Loader" assert tool.ID == "Loader", "Must be Loader"
comp = tool.Comp() comp = tool.Comp()
context = get_representation_context(representation) repre_doc = context["representation"]
context = get_representation_context(repre_doc)
path = self.filepath_from_context(context) path = self.filepath_from_context(context)
# Get start frame from version data # Get start frame from version data
@ -255,7 +256,7 @@ class FusionLoadSequence(load.LoaderPlugin):
) )
# Update the imprinted representation # Update the imprinted representation
tool.SetData("avalon.representation", str(representation["_id"])) tool.SetData("avalon.representation", str(repre_doc["_id"]))
def remove(self, container): def remove(self, container):
tool = container["_tool"] tool = container["_tool"]

View file

@ -28,9 +28,8 @@ class FusionLoadUSD(load.LoaderPlugin):
tool_type = "uLoader" tool_type = "uLoader"
@classmethod @classmethod
def apply_settings(cls, project_settings, system_settings): def apply_settings(cls, project_settings):
super(FusionLoadUSD, cls).apply_settings(project_settings, super(FusionLoadUSD, cls).apply_settings(project_settings)
system_settings)
if cls.enabled: if cls.enabled:
# Enable only in Fusion 18.5+ # Enable only in Fusion 18.5+
fusion = get_fusion_module() fusion = get_fusion_module()
@ -61,22 +60,23 @@ class FusionLoadUSD(load.LoaderPlugin):
context=context, context=context,
loader=self.__class__.__name__) loader=self.__class__.__name__)
def switch(self, container, representation): def switch(self, container, context):
self.update(container, representation) self.update(container, context)
def update(self, container, representation): def update(self, container, context):
tool = container["_tool"] tool = container["_tool"]
assert tool.ID == self.tool_type, f"Must be {self.tool_type}" assert tool.ID == self.tool_type, f"Must be {self.tool_type}"
comp = tool.Comp() comp = tool.Comp()
path = get_representation_path(representation) repre_doc = context["representation"]
path = get_representation_path(repre_doc)
with comp_lock_and_undo_chunk(comp, "Update tool"): with comp_lock_and_undo_chunk(comp, "Update tool"):
tool["Filename"] = path tool["Filename"] = path
# Update the imprinted representation # Update the imprinted representation
tool.SetData("avalon.representation", str(representation["_id"])) tool.SetData("avalon.representation", str(repre_doc["_id"]))
def remove(self, container): def remove(self, container):
tool = container["_tool"] tool = container["_tool"]

View file

@ -26,7 +26,7 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
instance.data["frame_range_source"] = frame_range_source instance.data["frame_range_source"] = frame_range_source
# get asset frame ranges to all instances # get asset frame ranges to all instances
# render family instances `asset_db` render target # render product type instances `asset_db` render target
start = context.data["frameStart"] start = context.data["frameStart"]
end = context.data["frameEnd"] end = context.data["frameEnd"]
handle_start = context.data["handleStart"] handle_start = context.data["handleStart"]
@ -34,7 +34,7 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
start_with_handle = start - handle_start start_with_handle = start - handle_start
end_with_handle = end + handle_end end_with_handle = end + handle_end
# conditions for render family instances # conditions for render product type instances
if frame_range_source == "render_range": if frame_range_source == "render_range":
# set comp render frame ranges # set comp render frame ranges
start = context.data["renderFrameStart"] start = context.data["renderFrameStart"]
@ -70,11 +70,11 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
end_with_handle = frame end_with_handle = frame
# Include start and end render frame in label # Include start and end render frame in label
subset = instance.data["subset"] product_name = instance.data["productName"]
label = ( label = (
"{subset} ({start}-{end}) [{handle_start}-{handle_end}]" "{product_name} ({start}-{end}) [{handle_start}-{handle_end}]"
).format( ).format(
subset=subset, product_name=product_name,
start=int(start), start=int(start),
end=int(end), end=int(end),
handle_start=int(handle_start), handle_start=int(handle_start),

View file

@ -49,31 +49,32 @@ class CollectFusionRender(
if not inst.data.get("active", True): if not inst.data.get("active", True):
continue continue
family = inst.data["family"] product_type = inst.data["productType"]
if family not in ["render", "image"]: if product_type not in ["render", "image"]:
continue continue
task_name = context.data["task"] task_name = context.data["task"]
tool = inst.data["transientData"]["tool"] tool = inst.data["transientData"]["tool"]
instance_families = inst.data.get("families", []) instance_families = inst.data.get("families", [])
subset_name = inst.data["subset"] product_name = inst.data["productName"]
instance = FusionRenderInstance( instance = FusionRenderInstance(
family=family,
tool=tool, tool=tool,
workfileComp=comp, workfileComp=comp,
productType=product_type,
family=product_type,
families=instance_families, families=instance_families,
version=version, version=version,
time="", time="",
source=current_file, source=current_file,
label=inst.data["label"], label=inst.data["label"],
subset=subset_name, productName=product_name,
asset=inst.data["asset"], folderPath=inst.data["folderPath"],
task=task_name, task=task_name,
attachTo=False, attachTo=False,
setMembers='', setMembers='',
publish=True, publish=True,
name=subset_name, name=product_name,
resolutionWidth=comp_frame_format_prefs.get("Width"), resolutionWidth=comp_frame_format_prefs.get("Width"),
resolutionHeight=comp_frame_format_prefs.get("Height"), resolutionHeight=comp_frame_format_prefs.get("Height"),
pixelAspect=aspect_x / aspect_y, pixelAspect=aspect_x / aspect_y,

View file

@ -72,7 +72,7 @@ class FusionRenderLocal(
self.log.info( self.log.info(
"Rendered '{nm}' for asset '{ast}' under the task '{tsk}'".format( "Rendered '{nm}' for asset '{ast}' under the task '{tsk}'".format(
nm=instance.data["name"], nm=instance.data["name"],
ast=instance.data["asset"], ast=instance.data["folderPath"],
tsk=instance.data["task"], tsk=instance.data["task"],
) )
) )

View file

@ -7,7 +7,7 @@ from ayon_core.hosts.fusion.api.action import SelectInvalidAction
class ValidateUniqueSubsets(pyblish.api.ContextPlugin): class ValidateUniqueSubsets(pyblish.api.ContextPlugin):
"""Ensure all instances have a unique subset name""" """Ensure all instances have a unique product name"""
order = pyblish.api.ValidatorOrder order = pyblish.api.ValidatorOrder
label = "Validate Unique Subsets" label = "Validate Unique Subsets"
@ -18,25 +18,31 @@ class ValidateUniqueSubsets(pyblish.api.ContextPlugin):
@classmethod @classmethod
def get_invalid(cls, context): def get_invalid(cls, context):
# Collect instances per subset per asset # Collect instances per product per folder
instances_per_subset_asset = defaultdict(lambda: defaultdict(list)) instances_per_product_folder = defaultdict(lambda: defaultdict(list))
for instance in context: for instance in context:
asset = instance.data.get("asset", context.data.get("asset")) folder_path = instance.data["folderPath"]
subset = instance.data.get("subset", context.data.get("subset")) product_name = instance.data["productName"]
instances_per_subset_asset[asset][subset].append(instance) instances_per_product_folder[folder_path][product_name].append(
instance
)
# Find which asset + subset combination has more than one instance # Find which asset + subset combination has more than one instance
# Those are considered invalid because they'd integrate to the same # Those are considered invalid because they'd integrate to the same
# destination. # destination.
invalid = [] invalid = []
for asset, instances_per_subset in instances_per_subset_asset.items(): for folder_path, instances_per_product in (
for subset, instances in instances_per_subset.items(): instances_per_product_folder.items()
):
for product_name, instances in instances_per_product.items():
if len(instances) > 1: if len(instances) > 1:
cls.log.warning( cls.log.warning(
"{asset} > {subset} used by more than " (
"one instance: {instances}".format( "{folder_path} > {product_name} used by more than "
asset=asset, "one instance: {instances}"
subset=subset, ).format(
folder_path=folder_path,
product_name=product_name,
instances=instances instances=instances
) )
) )
@ -50,6 +56,7 @@ class ValidateUniqueSubsets(pyblish.api.ContextPlugin):
def process(self, context): def process(self, context):
invalid = self.get_invalid(context) invalid = self.get_invalid(context)
if invalid: if invalid:
raise PublishValidationError("Multiple instances are set to " raise PublishValidationError(
"the same asset > subset.", "Multiple instances are set to the same folder > product.",
title=self.label) title=self.label
)

View file

@ -204,7 +204,7 @@ class CreateComposite(harmony.Creator):
name = "compositeDefault" name = "compositeDefault"
label = "Composite" label = "Composite"
family = "mindbender.template" product_type = "mindbender.template"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(CreateComposite, self).__init__(*args, **kwargs) super(CreateComposite, self).__init__(*args, **kwargs)
@ -212,6 +212,7 @@ class CreateComposite(harmony.Creator):
The creator plugin can be configured to use other node types. For example here is a write node creator: The creator plugin can be configured to use other node types. For example here is a write node creator:
```python ```python
from uuid import uuid4
import ayon_core.hosts.harmony.api as harmony import ayon_core.hosts.harmony.api as harmony
@ -220,7 +221,7 @@ class CreateRender(harmony.Creator):
name = "writeDefault" name = "writeDefault"
label = "Write" label = "Write"
family = "mindbender.imagesequence" product_type = "mindbender.imagesequence"
node_type = "WRITE" node_type = "WRITE"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -242,6 +243,7 @@ class CreateRender(harmony.Creator):
#### Collector Plugin #### Collector Plugin
```python ```python
import pyblish.api import pyblish.api
from ayon_core.pipeline import AYON_INSTANCE_ID, AVALON_INSTANCE_ID
import ayon_core.hosts.harmony.api as harmony import ayon_core.hosts.harmony.api as harmony
@ -252,7 +254,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
a composite node and marked with a unique identifier; a composite node and marked with a unique identifier;
Identifier: Identifier:
id (str): "pyblish.avalon.instance" id (str): "ayon.create.instance"
""" """
label = "Instances" label = "Instances"
@ -272,7 +274,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
continue continue
# Skip containers. # Skip containers.
if "container" in data["id"]: if data["id"] not in {AYON_INSTANCE_ID, AVALON_INSTANCE_ID}:
continue continue
instance = context.create_instance(node.split("/")[-1]) instance = context.create_instance(node.split("/")[-1])
@ -287,6 +289,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
#### Extractor Plugin #### Extractor Plugin
```python ```python
import os import os
from uuid import uuid4
import pyblish.api import pyblish.api
import ayon_core.hosts.harmony.api as harmony import ayon_core.hosts.harmony.api as harmony
@ -418,6 +421,7 @@ class ExtractImage(pyblish.api.InstancePlugin):
#### Loader Plugin #### Loader Plugin
```python ```python
import os import os
from uuid import uuid4
import ayon_core.hosts.harmony.api as harmony import ayon_core.hosts.harmony.api as harmony
@ -607,11 +611,12 @@ class ImageSequenceLoader(load.LoaderPlugin):
self.__class__.__name__ self.__class__.__name__
) )
def update(self, container, representation): def update(self, container, context):
node = container.pop("node") node = container.pop("node")
repre_doc = context["representation"]
project_name = get_current_project_name() project_name = get_current_project_name()
version = get_version_by_id(project_name, representation["parent"]) version = get_version_by_id(project_name, repre_doc["parent"])
files = [] files = []
for f in version["data"]["files"]: for f in version["data"]["files"]:
files.append( files.append(
@ -628,7 +633,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
) )
harmony.imprint( harmony.imprint(
node, {"representation": str(representation["_id"])} node, {"representation": str(repre_doc["_id"])}
) )
def remove(self, container): def remove(self, container):
@ -644,8 +649,8 @@ class ImageSequenceLoader(load.LoaderPlugin):
{"function": func, "args": [node]} {"function": func, "args": [node]}
) )
def switch(self, container, representation): def switch(self, container, context):
self.update(container, representation) self.update(container, context)
``` ```
## Resources ## Resources

View file

@ -88,7 +88,7 @@ ImageSequenceLoader.getUniqueColumnName = function(columnPrefix) {
* var args = [ * var args = [
* files, // Files in file sequences. * files, // Files in file sequences.
* asset, // Asset name. * asset, // Asset name.
* subset, // Subset name. * productName, // Product name.
* startFrame, // Sequence starting frame. * startFrame, // Sequence starting frame.
* groupId // Unique group ID (uuid4). * groupId // Unique group ID (uuid4).
* ]; * ];
@ -106,7 +106,7 @@ ImageSequenceLoader.prototype.importFiles = function(args) {
var doc = $.scn; var doc = $.scn;
var files = args[0]; var files = args[0];
var asset = args[1]; var asset = args[1];
var subset = args[2]; var productName = args[2];
var startFrame = args[3]; var startFrame = args[3];
var groupId = args[4]; var groupId = args[4];
var vectorFormat = null; var vectorFormat = null;
@ -124,7 +124,7 @@ ImageSequenceLoader.prototype.importFiles = function(args) {
var num = 0; var num = 0;
var name = ''; var name = '';
do { do {
name = asset + '_' + (num++) + '_' + subset; name = asset + '_' + (num++) + '_' + productName;
} while (currentGroup.getNodeByName(name) != null); } while (currentGroup.getNodeByName(name) != null);
extension = filename.substr(pos+1).toLowerCase(); extension = filename.substr(pos+1).toLowerCase();

View file

@ -31,7 +31,7 @@ var TemplateLoader = function() {};
* var args = [ * var args = [
* templatePath, // Path to tpl file. * templatePath, // Path to tpl file.
* assetName, // Asset name. * assetName, // Asset name.
* subsetName, // Subset name. * productName, // Product name.
* groupId // unique ID (uuid4) * groupId // unique ID (uuid4)
* ]; * ];
*/ */
@ -39,7 +39,7 @@ TemplateLoader.prototype.loadContainer = function(args) {
var doc = $.scn; var doc = $.scn;
var templatePath = args[0]; var templatePath = args[0];
var assetName = args[1]; var assetName = args[1];
var subset = args[2]; var productName = args[2];
var groupId = args[3]; var groupId = args[3];
// Get the current group // Get the current group
@ -62,7 +62,7 @@ TemplateLoader.prototype.loadContainer = function(args) {
var num = 0; var num = 0;
var containerGroupName = ''; var containerGroupName = '';
do { do {
containerGroupName = assetName + '_' + (num++) + '_' + subset; containerGroupName = assetName + '_' + (num++) + '_' + productName;
} while (currentGroup.getNodeByName(containerGroupName) != null); } while (currentGroup.getNodeByName(containerGroupName) != null);
// import the template // import the template

View file

@ -9,7 +9,7 @@ class CreateFarmRender(plugin.Creator):
name = "renderDefault" name = "renderDefault"
label = "Render on Farm" label = "Render on Farm"
family = "renderFarm" product_type = "renderFarm"
node_type = "WRITE" node_type = "WRITE"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View file

@ -9,7 +9,7 @@ class CreateRender(plugin.Creator):
name = "renderDefault" name = "renderDefault"
label = "Render" label = "Render"
family = "render" product_type = "render"
node_type = "WRITE" node_type = "WRITE"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View file

@ -6,7 +6,7 @@ class CreateTemplate(plugin.Creator):
name = "templateDefault" name = "templateDefault"
label = "Template" label = "Template"
family = "harmony.template" product_type = "harmony.template"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(CreateTemplate, self).__init__(*args, **kwargs) super(CreateTemplate, self).__init__(*args, **kwargs)

View file

@ -45,17 +45,17 @@ class ImportAudioLoader(load.LoaderPlugin):
{"function": func, "args": [context["subset"]["name"], wav_file]} {"function": func, "args": [context["subset"]["name"], wav_file]}
) )
subset_name = context["subset"]["name"] product_name = context["subset"]["name"]
return harmony.containerise( return harmony.containerise(
subset_name, product_name,
namespace, namespace,
subset_name, product_name,
context, context,
self.__class__.__name__ self.__class__.__name__
) )
def update(self, container, representation): def update(self, container, context):
pass pass
def remove(self, container): def remove(self, container):

View file

@ -254,7 +254,7 @@ class BackgroundLoader(load.LoaderPlugin):
bg_folder = os.path.dirname(path) bg_folder = os.path.dirname(path)
subset_name = context["subset"]["name"] product_name = context["subset"]["name"]
# read_node_name += "_{}".format(uuid.uuid4()) # read_node_name += "_{}".format(uuid.uuid4())
container_nodes = [] container_nodes = []
@ -272,16 +272,17 @@ class BackgroundLoader(load.LoaderPlugin):
container_nodes.append(read_node) container_nodes.append(read_node)
return harmony.containerise( return harmony.containerise(
subset_name, product_name,
namespace, namespace,
subset_name, product_name,
context, context,
self.__class__.__name__, self.__class__.__name__,
nodes=container_nodes nodes=container_nodes
) )
def update(self, container, representation): def update(self, container, context):
path = get_representation_path(representation) repre_doc = context["representation"]
path = get_representation_path(repre_doc)
with open(path) as json_file: with open(path) as json_file:
data = json.load(json_file) data = json.load(json_file)
@ -301,7 +302,7 @@ class BackgroundLoader(load.LoaderPlugin):
print(container) print(container)
is_latest = is_representation_from_latest(representation) is_latest = is_representation_from_latest(repre_doc)
for layer in sorted(layers): for layer in sorted(layers):
file_to_import = [ file_to_import = [
os.path.join(bg_folder, layer).replace("\\", "/") os.path.join(bg_folder, layer).replace("\\", "/")
@ -351,8 +352,11 @@ class BackgroundLoader(load.LoaderPlugin):
harmony.send({"function": func, "args": [node, "red"]}) harmony.send({"function": func, "args": [node, "red"]})
harmony.imprint( harmony.imprint(
container['name'], {"representation": str(representation["_id"]), container['name'],
"nodes": container['nodes']} {
"representation": str(repre_doc["_id"]),
"nodes": container["nodes"]
}
) )
def remove(self, container): def remove(self, container):
@ -369,5 +373,5 @@ class BackgroundLoader(load.LoaderPlugin):
) )
harmony.imprint(container['name'], {}, remove=True) harmony.imprint(container['name'], {}, remove=True)
def switch(self, container, representation): def switch(self, container, context):
self.update(container, representation) self.update(container, context)

View file

@ -47,7 +47,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
files.append(fname.parent.joinpath(remainder[0]).as_posix()) files.append(fname.parent.joinpath(remainder[0]).as_posix())
asset = context["asset"]["name"] asset = context["asset"]["name"]
subset = context["subset"]["name"] product_name = context["subset"]["name"]
group_id = str(uuid.uuid4()) group_id = str(uuid.uuid4())
read_node = harmony.send( read_node = harmony.send(
@ -56,7 +56,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
"args": [ "args": [
files, files,
asset, asset,
subset, product_name,
1, 1,
group_id group_id
] ]
@ -64,7 +64,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
)["result"] )["result"]
return harmony.containerise( return harmony.containerise(
f"{asset}_{subset}", f"{asset}_{product_name}",
namespace, namespace,
read_node, read_node,
context, context,
@ -72,18 +72,19 @@ class ImageSequenceLoader(load.LoaderPlugin):
nodes=[read_node] nodes=[read_node]
) )
def update(self, container, representation): def update(self, container, context):
"""Update loaded containers. """Update loaded containers.
Args: Args:
container (dict): Container data. container (dict): Container data.
representation (dict): Representation data. context (dict): Representation context data.
""" """
self_name = self.__class__.__name__ self_name = self.__class__.__name__
node = container.get("nodes").pop() node = container.get("nodes").pop()
path = get_representation_path(representation) repre_doc = context["representation"]
path = get_representation_path(repre_doc)
collections, remainder = clique.assemble( collections, remainder = clique.assemble(
os.listdir(os.path.dirname(path)) os.listdir(os.path.dirname(path))
) )
@ -110,7 +111,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
) )
# Colour node. # Colour node.
if is_representation_from_latest(representation): if is_representation_from_latest(repre_doc):
harmony.send( harmony.send(
{ {
"function": "PypeHarmony.setColor", "function": "PypeHarmony.setColor",
@ -124,7 +125,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
}) })
harmony.imprint( harmony.imprint(
node, {"representation": str(representation["_id"])} node, {"representation": str(repre_doc["_id"])}
) )
def remove(self, container): def remove(self, container):
@ -140,6 +141,6 @@ class ImageSequenceLoader(load.LoaderPlugin):
) )
harmony.imprint(node, {}, remove=True) harmony.imprint(node, {}, remove=True)
def switch(self, container, representation): def switch(self, container, context):
"""Switch loaded representations.""" """Switch loaded representations."""
self.update(container, representation) self.update(container, context)

View file

@ -26,15 +26,17 @@ class ImportPaletteLoader(load.LoaderPlugin):
self.__class__.__name__ self.__class__.__name__
) )
def load_palette(self, representation): def load_palette(self, context):
subset_name = representation["context"]["subset"] subset_doc = context["subset"]
name = subset_name.replace("palette", "") repre_doc = context["representation"]
product_name = subset_doc["name"]
name = product_name.replace("palette", "")
# Overwrite palette on disk. # Overwrite palette on disk.
scene_path = harmony.send( scene_path = harmony.send(
{"function": "scene.currentProjectPath"} {"function": "scene.currentProjectPath"}
)["result"] )["result"]
src = get_representation_path(representation) src = get_representation_path(repre_doc)
dst = os.path.join( dst = os.path.join(
scene_path, scene_path,
"palette-library", "palette-library",
@ -44,7 +46,7 @@ class ImportPaletteLoader(load.LoaderPlugin):
harmony.save_scene() harmony.save_scene()
msg = "Updated {}.".format(subset_name) msg = "Updated {}.".format(product_name)
msg += " You need to reload the scene to see the changes.\n" msg += " You need to reload the scene to see the changes.\n"
msg += "Please save workfile when ready and use Workfiles " msg += "Please save workfile when ready and use Workfiles "
msg += "to reopen it." msg += "to reopen it."
@ -59,13 +61,14 @@ class ImportPaletteLoader(load.LoaderPlugin):
def remove(self, container): def remove(self, container):
harmony.remove(container["name"]) harmony.remove(container["name"])
def switch(self, container, representation): def switch(self, container, context):
self.update(container, representation) self.update(container, context)
def update(self, container, representation): def update(self, container, context):
self.remove(container) self.remove(container)
name = self.load_palette(representation) name = self.load_palette(context)
container["representation"] = str(representation["_id"]) repre_doc = context["representation"]
container["representation"] = str(repre_doc["_id"])
container["name"] = name container["name"] = name
harmony.imprint(name, container) harmony.imprint(name, container)

View file

@ -70,19 +70,20 @@ class TemplateLoader(load.LoaderPlugin):
self_name self_name
) )
def update(self, container, representation): def update(self, container, context):
"""Update loaded containers. """Update loaded containers.
Args: Args:
container (dict): Container data. container (dict): Container data.
representation (dict): Representation data. context (dict): Representation context data.
""" """
node_name = container["name"] node_name = container["name"]
node = harmony.find_node_by_name(node_name, "GROUP") node = harmony.find_node_by_name(node_name, "GROUP")
self_name = self.__class__.__name__ self_name = self.__class__.__name__
if is_representation_from_latest(representation): repre_doc = context["representation"]
if is_representation_from_latest(repre_doc):
self._set_green(node) self._set_green(node)
else: else:
self._set_red(node) self._set_red(node)
@ -110,7 +111,7 @@ class TemplateLoader(load.LoaderPlugin):
None, container["data"]) None, container["data"])
harmony.imprint( harmony.imprint(
node, {"representation": str(representation["_id"])} node, {"representation": str(repre_doc["_id"])}
) )
def remove(self, container): def remove(self, container):
@ -125,9 +126,9 @@ class TemplateLoader(load.LoaderPlugin):
{"function": "PypeHarmony.deleteNode", "args": [node]} {"function": "PypeHarmony.deleteNode", "args": [node]}
) )
def switch(self, container, representation): def switch(self, container, context):
"""Switch representation containers.""" """Switch representation containers."""
self.update(container, representation) self.update(container, context)
def _set_green(self, node): def _set_green(self, node):
"""Set node color to green `rgba(0, 255, 0, 255)`.""" """Set node color to green `rgba(0, 255, 0, 255)`."""

View file

@ -40,17 +40,17 @@ class ImportTemplateLoader(load.LoaderPlugin):
shutil.rmtree(temp_dir) shutil.rmtree(temp_dir)
subset_name = context["subset"]["name"] product_name = context["subset"]["name"]
return harmony.containerise( return harmony.containerise(
subset_name, product_name,
namespace, namespace,
subset_name, product_name,
context, context,
self.__class__.__name__ self.__class__.__name__
) )
def update(self, container, representation): def update(self, container, context):
pass pass
def remove(self, container): def remove(self, container):

View file

@ -80,7 +80,7 @@ class CollectFarmRender(publish.AbstractCollectRender):
for frame in range(start, end + 1): for frame in range(start, end + 1):
expected_files.append( expected_files.append(
path / "{}-{}.{}".format( path / "{}-{}.{}".format(
render_instance.subset, render_instance.productName,
str(frame).rjust(int(info[2]) + 1, "0"), str(frame).rjust(int(info[2]) + 1, "0"),
ext ext
) )
@ -89,7 +89,7 @@ class CollectFarmRender(publish.AbstractCollectRender):
return expected_files return expected_files
def get_instances(self, context): def get_instances(self, context):
"""Get instances per Write node in `renderFarm` family.""" """Get instances per Write node in `renderFarm` product type."""
version = None version = None
if self.sync_workfile_version: if self.sync_workfile_version:
version = context.data["version"] version = context.data["version"]
@ -98,7 +98,7 @@ class CollectFarmRender(publish.AbstractCollectRender):
self_name = self.__class__.__name__ self_name = self.__class__.__name__
asset_name = context.data["asset"] folder_path = context.data["folderPath"]
for node in context.data["allNodes"]: for node in context.data["allNodes"]:
data = harmony.read(node) data = harmony.read(node)
@ -111,7 +111,10 @@ class CollectFarmRender(publish.AbstractCollectRender):
if "container" in data["id"]: if "container" in data["id"]:
continue continue
if data["family"] != "renderFarm": product_type = data.get("productType")
if product_type is None:
product_type = data.get("family")
if product_type != "renderFarm":
continue continue
# 0 - filename / 1 - type / 2 - zeros / 3 - start / 4 - enabled # 0 - filename / 1 - type / 2 - zeros / 3 - start / 4 - enabled
@ -124,15 +127,14 @@ class CollectFarmRender(publish.AbstractCollectRender):
# TODO: handle pixel aspect and frame step # TODO: handle pixel aspect and frame step
# TODO: set Deadline stuff (pools, priority, etc. by presets) # TODO: set Deadline stuff (pools, priority, etc. by presets)
# because of using 'renderFarm' as a family, replace 'Farm' with # because of using 'renderFarm' as a product type, replace 'Farm'
# capitalized task name - issue of avalon-core Creator app # with capitalized task name - issue of Creator tool
subset_name = node.split("/")[1] product_name = node.split("/")[1]
task_name = context.data["anatomyData"]["task"][ task_name = context.data["task"].capitalize()
"name"].capitalize()
replace_str = "" replace_str = ""
if task_name.lower() not in subset_name.lower(): if task_name.lower() not in product_name.lower():
replace_str = task_name replace_str = task_name
subset_name = subset_name.replace( product_name = product_name.replace(
'Farm', 'Farm',
replace_str) replace_str)
@ -141,8 +143,8 @@ class CollectFarmRender(publish.AbstractCollectRender):
time=get_formatted_current_time(), time=get_formatted_current_time(),
source=context.data["currentFile"], source=context.data["currentFile"],
label=node.split("/")[1], label=node.split("/")[1],
subset=subset_name, productName=product_name,
asset=asset_name, folderPath=folder_path,
task=task_name, task=task_name,
attachTo=False, attachTo=False,
setMembers=[node], setMembers=[node],
@ -151,6 +153,7 @@ class CollectFarmRender(publish.AbstractCollectRender):
priority=50, priority=50,
name=node.split("/")[1], name=node.split("/")[1],
productType="render.farm",
family="render.farm", family="render.farm",
families=["render.farm"], families=["render.farm"],
farm=True, farm=True,

View file

@ -13,13 +13,13 @@ class CollectInstances(pyblish.api.ContextPlugin):
a composite node and marked with a unique identifier. a composite node and marked with a unique identifier.
Identifier: Identifier:
id (str): "pyblish.avalon.instance" id (str): "ayon.create.instance"
""" """
label = "Instances" label = "Instances"
order = pyblish.api.CollectorOrder order = pyblish.api.CollectorOrder
hosts = ["harmony"] hosts = ["harmony"]
families_mapping = { product_type_mapping = {
"render": ["review", "ftrack"], "render": ["review", "ftrack"],
"harmony.template": [], "harmony.template": [],
"palette": ["palette", "ftrack"] "palette": ["palette", "ftrack"]
@ -49,8 +49,14 @@ class CollectInstances(pyblish.api.ContextPlugin):
if "container" in data["id"]: if "container" in data["id"]:
continue continue
# skip render farm family as it is collected separately product_type = data.get("productType")
if data["family"] == "renderFarm": if product_type is None:
product_type = data["family"]
data["productType"] = product_type
data["family"] = product_type
# skip render farm product type as it is collected separately
if product_type == "renderFarm":
continue continue
instance = context.create_instance(node.split("/")[-1]) instance = context.create_instance(node.split("/")[-1])
@ -59,11 +65,14 @@ class CollectInstances(pyblish.api.ContextPlugin):
instance.data["publish"] = harmony.send( instance.data["publish"] = harmony.send(
{"function": "node.getEnable", "args": [node]} {"function": "node.getEnable", "args": [node]}
)["result"] )["result"]
instance.data["families"] = self.families_mapping[data["family"]]
families = [product_type]
families.extend(self.product_type_mapping[product_type])
instance.data["families"] = families
# If set in plugin, pair the scene Version in ftrack with # If set in plugin, pair the scene Version in ftrack with
# thumbnails and review media. # thumbnails and review media.
if (self.pair_media and instance.data["family"] == "scene"): if (self.pair_media and product_type == "scene"):
context.data["scene_instance"] = instance context.data["scene_instance"] = instance
# Produce diagnostic message for any graphical # Produce diagnostic message for any graphical

Some files were not shown because too many files have changed in this diff Show more