mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge remote-tracking branch 'origin/develop' into feature/OP-1188_better-representation-model
This commit is contained in:
commit
b5fd14a3cc
636 changed files with 8886 additions and 16080 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -1,6 +1,6 @@
|
|||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: 'Bug: '
|
||||
title: 'Your issue title here'
|
||||
labels:
|
||||
- 'type: bug'
|
||||
body:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name: Enhancement Request
|
||||
description: Create a report to help us enhance a particular feature
|
||||
title: "Enhancement: "
|
||||
title: "Your issue title here"
|
||||
labels:
|
||||
- "type: enhancement"
|
||||
body:
|
||||
|
|
@ -49,4 +49,4 @@ body:
|
|||
label: "Additional context:"
|
||||
description: Add any other context or screenshots about the enhancement request here.
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
|
|
|
|||
28
.github/workflows/issue_to_clickup_trigger.yml
vendored
Normal file
28
.github/workflows/issue_to_clickup_trigger.yml
vendored
Normal 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 }}
|
||||
|
|
@ -15,13 +15,9 @@ from abc import ABCMeta, abstractmethod
|
|||
import six
|
||||
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.settings import get_system_settings
|
||||
from ayon_core.settings.ayon_settings import (
|
||||
is_dev_mode_enabled,
|
||||
get_ayon_settings,
|
||||
)
|
||||
from ayon_core.settings import get_studio_settings
|
||||
|
||||
from .interfaces import (
|
||||
IPluginPaths,
|
||||
|
|
@ -648,7 +644,6 @@ class AddonsManager:
|
|||
|
||||
def __init__(self, settings=None, initialize=True):
|
||||
self._settings = settings
|
||||
self._system_settings = None
|
||||
|
||||
self._addons = []
|
||||
self._addons_by_id = {}
|
||||
|
|
@ -738,14 +733,9 @@ class AddonsManager:
|
|||
# Prepare settings for addons
|
||||
settings = self._settings
|
||||
if settings is None:
|
||||
settings = get_ayon_settings()
|
||||
settings = get_studio_settings()
|
||||
|
||||
# OpenPype settings
|
||||
system_settings = self._system_settings
|
||||
if system_settings is None:
|
||||
system_settings = get_system_settings()
|
||||
|
||||
modules_settings = system_settings["modules"]
|
||||
modules_settings = {}
|
||||
|
||||
report = {}
|
||||
time_start = time.time()
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ class Commands:
|
|||
context = get_global_context()
|
||||
env = get_app_environments_for_context(
|
||||
context["project_name"],
|
||||
context["asset_name"],
|
||||
context["folder_path"],
|
||||
context["task_name"],
|
||||
app_full_name,
|
||||
launch_type=LaunchTypes.farm_publish,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook):
|
|||
"tvpaint",
|
||||
"substancepainter",
|
||||
"aftereffects",
|
||||
"wrap"
|
||||
"wrap",
|
||||
"openrv"
|
||||
}
|
||||
launch_types = {LaunchTypes.local}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class CopyTemplateWorkfile(PreLaunchHook):
|
|||
self.log.info("Last workfile does not exist.")
|
||||
|
||||
project_name = self.data["project_name"]
|
||||
asset_name = self.data["asset_name"]
|
||||
asset_name = self.data["folder_path"]
|
||||
task_name = self.data["task_name"]
|
||||
host_name = self.application.host_name
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class GlobalHostDataHook(PreLaunchHook):
|
|||
app = self.launch_context.application
|
||||
temp_data = EnvironmentPrepData({
|
||||
"project_name": self.data["project_name"],
|
||||
"asset_name": self.data["asset_name"],
|
||||
"folder_path": self.data["folder_path"],
|
||||
"task_name": self.data["task_name"],
|
||||
|
||||
"app": app,
|
||||
|
|
@ -66,7 +66,7 @@ class GlobalHostDataHook(PreLaunchHook):
|
|||
project_doc = get_project(project_name)
|
||||
self.data["project_doc"] = project_doc
|
||||
|
||||
asset_name = self.data.get("asset_name")
|
||||
asset_name = self.data.get("folder_path")
|
||||
if not asset_name:
|
||||
self.log.warning(
|
||||
"Asset name was not set. Skipping asset document query."
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class OCIOEnvHook(PreLaunchHook):
|
|||
"nuke",
|
||||
"hiero",
|
||||
"resolve",
|
||||
"openrv"
|
||||
}
|
||||
launch_types = set()
|
||||
|
||||
|
|
@ -27,10 +28,10 @@ class OCIOEnvHook(PreLaunchHook):
|
|||
|
||||
template_data = get_template_data_with_names(
|
||||
project_name=self.data["project_name"],
|
||||
asset_name=self.data["asset_name"],
|
||||
asset_name=self.data["folder_path"],
|
||||
task_name=self.data["task_name"],
|
||||
host_name=self.host_name,
|
||||
system_settings=self.data["system_settings"]
|
||||
settings=self.data["project_settings"]
|
||||
)
|
||||
|
||||
config_data = get_imageio_config(
|
||||
|
|
|
|||
|
|
@ -134,12 +134,12 @@ class HostBase(object):
|
|||
|
||||
Returns:
|
||||
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 {
|
||||
"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()
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ class HostBase(object):
|
|||
# Use current context to fill the context title
|
||||
current_context = self.get_current_context()
|
||||
project_name = current_context["project_name"]
|
||||
asset_name = current_context["asset_name"]
|
||||
asset_name = current_context["folder_path"]
|
||||
task_name = current_context["task_name"]
|
||||
items = []
|
||||
if project_name:
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ def set_settings(frames, resolution, comp_ids=None, print_msg=True):
|
|||
current_context = get_current_context()
|
||||
|
||||
asset_doc = get_asset_by_name(current_context["project_name"],
|
||||
current_context["asset_name"])
|
||||
current_context["folder_path"])
|
||||
settings = get_asset_settings(asset_doc)
|
||||
|
||||
msg = ''
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ from ayon_core.pipeline import (
|
|||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
AVALON_INSTANCE_ID,
|
||||
AYON_INSTANCE_ID,
|
||||
)
|
||||
from ayon_core.hosts.aftereffects.api.workfile_template_builder import (
|
||||
AEPlaceholderLoadPlugin,
|
||||
|
|
@ -142,7 +144,9 @@ class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
layers_meta = stub.get_metadata()
|
||||
|
||||
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)
|
||||
return instances
|
||||
|
||||
|
|
|
|||
|
|
@ -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.lib import set_settings
|
||||
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):
|
||||
|
|
@ -22,7 +22,7 @@ class RenderCreator(Creator):
|
|||
"""
|
||||
identifier = "render"
|
||||
label = "Render"
|
||||
family = "render"
|
||||
product_type = "render"
|
||||
description = "Render creator"
|
||||
|
||||
create_allow_context_change = True
|
||||
|
|
@ -31,7 +31,7 @@ class RenderCreator(Creator):
|
|||
mark_for_review = 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
|
||||
|
||||
try:
|
||||
|
|
@ -58,33 +58,37 @@ class RenderCreator(Creator):
|
|||
len(comps) > 1)
|
||||
for comp in comps:
|
||||
composition_name = re.sub(
|
||||
"[^{}]+".format(SUBSET_NAME_ALLOWED_SYMBOLS),
|
||||
"[^{}]+".format(PRODUCT_NAME_ALLOWED_SYMBOLS),
|
||||
"",
|
||||
comp.name
|
||||
)
|
||||
if use_composition_name:
|
||||
if "{composition}" not in subset_name_from_ui.lower():
|
||||
subset_name_from_ui += "{Composition}"
|
||||
if "{composition}" not in product_name.lower():
|
||||
product_name += "{Composition}"
|
||||
|
||||
dynamic_fill = prepare_template_data({"composition":
|
||||
composition_name})
|
||||
subset_name = subset_name_from_ui.format(**dynamic_fill)
|
||||
comp_product_name = product_name.format(**dynamic_fill)
|
||||
data["composition_name"] = composition_name
|
||||
else:
|
||||
subset_name = subset_name_from_ui
|
||||
subset_name = re.sub(r"\{composition\}", '', subset_name,
|
||||
flags=re.IGNORECASE)
|
||||
comp_product_name = re.sub(
|
||||
r"\{composition\}",
|
||||
"",
|
||||
product_name,
|
||||
flags=re.IGNORECASE
|
||||
)
|
||||
|
||||
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(
|
||||
inst.subset_name))
|
||||
inst.product_name))
|
||||
|
||||
data["members"] = [comp.id]
|
||||
data["orig_comp_name"] = composition_name
|
||||
|
||||
new_instance = CreatedInstance(self.family, subset_name, data,
|
||||
self)
|
||||
new_instance = CreatedInstance(
|
||||
self.product_type, comp_product_name, data, self
|
||||
)
|
||||
if "farm" in pre_create_data:
|
||||
use_farm = pre_create_data["farm"]
|
||||
new_instance.creator_attributes["farm"] = use_farm
|
||||
|
|
@ -96,7 +100,7 @@ class RenderCreator(Creator):
|
|||
new_instance.data_to_store())
|
||||
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:
|
||||
set_settings(True, True, [comp.id], print_msg=False)
|
||||
|
||||
|
|
@ -107,7 +111,7 @@ class RenderCreator(Creator):
|
|||
"selected by default.",
|
||||
default=True, label="Use selection"),
|
||||
BoolDef("use_composition_name",
|
||||
label="Use composition name in subset"),
|
||||
label="Use composition name in product"),
|
||||
UISeparatorDef(),
|
||||
BoolDef("farm", label="Render on farm"),
|
||||
BoolDef(
|
||||
|
|
@ -133,9 +137,14 @@ class RenderCreator(Creator):
|
|||
|
||||
def collect_instances(self):
|
||||
for instance_data in cache_and_get_instances(self):
|
||||
# legacy instances have family=='render' or 'renderLocal', use them
|
||||
creator_id = (instance_data.get("creator_identifier") or
|
||||
instance_data.get("family", '').replace("Local", ''))
|
||||
# legacy instances have product_type=='render' or 'renderLocal', use them
|
||||
creator_id = instance_data.get("creator_identifier")
|
||||
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:
|
||||
instance_data = self._handle_legacy(instance_data)
|
||||
instance = CreatedInstance.from_existing(
|
||||
|
|
@ -147,10 +156,10 @@ class RenderCreator(Creator):
|
|||
for created_inst, _changes in update_list:
|
||||
api.get_stub().imprint(created_inst.get("instance_id"),
|
||||
created_inst.data_to_store())
|
||||
subset_change = _changes.get("subset")
|
||||
if subset_change:
|
||||
name_change = _changes.get("productName")
|
||||
if name_change:
|
||||
api.get_stub().rename_item(created_inst.data["members"][0],
|
||||
subset_change.new_value)
|
||||
name_change.new_value)
|
||||
|
||||
def remove_instances(self, instances):
|
||||
"""Removes metadata and renames to original comp name if available."""
|
||||
|
|
@ -183,15 +192,15 @@ class RenderCreator(Creator):
|
|||
def get_detail_description(self):
|
||||
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
|
||||
be a final delivery product or loaded and used in another DCCs.
|
||||
|
||||
Select single composition and create instance of 'render' family or
|
||||
turn off 'Use selection' to create instance for all compositions.
|
||||
Select single composition and create instance of 'render' product type
|
||||
or turn off 'Use selection' to create instance for all compositions.
|
||||
|
||||
'Use composition name in subset' allows to explicitly add composition
|
||||
name into created subset name.
|
||||
'Use composition name in product' allows to explicitly add composition
|
||||
name into created product name.
|
||||
|
||||
Position of composition name could be set in
|
||||
`project_settings/global/tools/creator/product_name_profiles` with
|
||||
|
|
@ -201,15 +210,16 @@ class RenderCreator(Creator):
|
|||
be handled at same time.
|
||||
|
||||
If {composition} placeholder is not us 'product_name_profiles'
|
||||
composition name will be capitalized and set at the end of subset name
|
||||
if necessary.
|
||||
composition name will be capitalized and set at the end of
|
||||
product name if necessary.
|
||||
|
||||
If composition name should be used, it will be cleaned up of characters
|
||||
that would cause an issue in published file names.
|
||||
"""
|
||||
|
||||
def get_dynamic_data(self, variant, task_name, asset_doc,
|
||||
project_name, host_name, instance):
|
||||
def get_dynamic_data(
|
||||
self, project_name, asset_doc, task_name, variant, host_name, instance
|
||||
):
|
||||
dynamic_data = {}
|
||||
if instance is not None:
|
||||
composition_name = instance.get("composition_name")
|
||||
|
|
@ -234,9 +244,9 @@ class RenderCreator(Creator):
|
|||
instance_data["task"] = self.create_context.get_current_task_name()
|
||||
|
||||
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["family"] = self.family
|
||||
instance_data["productType"] = self.product_type
|
||||
|
||||
if instance_data["creator_attributes"].get("mark_for_review") is None:
|
||||
instance_data["creator_attributes"]["mark_for_review"] = True
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from ayon_core.hosts.aftereffects.api.pipeline import cache_and_get_instances
|
|||
|
||||
class AEWorkfileCreator(AutoCreator):
|
||||
identifier = "workfile"
|
||||
family = "workfile"
|
||||
product_type = "workfile"
|
||||
|
||||
default_variant = "Main"
|
||||
|
||||
|
|
@ -20,9 +20,9 @@ class AEWorkfileCreator(AutoCreator):
|
|||
for instance_data in cache_and_get_instances(self):
|
||||
creator_id = instance_data.get("creator_identifier")
|
||||
if creator_id == self.identifier:
|
||||
subset_name = instance_data["subset"]
|
||||
product_name = instance_data["productName"]
|
||||
instance = CreatedInstance(
|
||||
self.family, subset_name, instance_data, self
|
||||
self.product_type, product_name, instance_data, self
|
||||
)
|
||||
self._add_instance_to_context(instance)
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ class AEWorkfileCreator(AutoCreator):
|
|||
def create(self, options=None):
|
||||
existing_instance = None
|
||||
for instance in self.create_context.instances:
|
||||
if instance.family == self.family:
|
||||
if instance.product_type == self.product_type:
|
||||
existing_instance = instance
|
||||
break
|
||||
|
||||
|
|
@ -49,9 +49,12 @@ class AEWorkfileCreator(AutoCreator):
|
|||
|
||||
if existing_instance is None:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
product_name = self.get_product_name(
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
self.default_variant,
|
||||
host_name,
|
||||
)
|
||||
data = {
|
||||
"folderPath": asset_name,
|
||||
|
|
@ -59,12 +62,16 @@ class AEWorkfileCreator(AutoCreator):
|
|||
"variant": self.default_variant,
|
||||
}
|
||||
data.update(self.get_dynamic_data(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name, None
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
self.default_variant,
|
||||
host_name,
|
||||
None,
|
||||
))
|
||||
|
||||
new_instance = CreatedInstance(
|
||||
self.family, subset_name, data, self
|
||||
self.product_type, product_name, data, self
|
||||
)
|
||||
self._add_instance_to_context(new_instance)
|
||||
|
||||
|
|
@ -76,10 +83,13 @@ class AEWorkfileCreator(AutoCreator):
|
|||
or existing_instance["task"] != task_name
|
||||
):
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
product_name = self.get_product_name(
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
self.default_variant,
|
||||
host_name,
|
||||
)
|
||||
existing_instance["folderPath"] = asset_name
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
existing_instance["productName"] = product_name
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from ayon_core.hosts.aftereffects.api.lib import (
|
|||
|
||||
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
|
||||
from background json AND automatically created composition with layers,
|
||||
each layer for separate image.
|
||||
|
|
@ -56,16 +56,21 @@ class BackgroundLoader(api.AfterEffectsLoader):
|
|||
self.__class__.__name__
|
||||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
""" Switch asset or change version """
|
||||
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")
|
||||
|
||||
# without iterator number (_001, 002...)
|
||||
namespace_from_container = re.sub(r'_\d{3}$', '',
|
||||
container["namespace"])
|
||||
comp_name = "{}_{}".format(context["asset"], context["subset"])
|
||||
comp_name = "{}_{}".format(folder_name, product_name)
|
||||
|
||||
# switching assets
|
||||
if namespace_from_container != comp_name:
|
||||
|
|
@ -73,11 +78,11 @@ class BackgroundLoader(api.AfterEffectsLoader):
|
|||
existing_items = [layer.name for layer in items]
|
||||
comp_name = get_unique_layer_name(
|
||||
existing_items,
|
||||
"{}_{}".format(context["asset"], context["subset"]))
|
||||
"{}_{}".format(folder_name, product_name))
|
||||
else: # switching version - keep same name
|
||||
comp_name = container["namespace"]
|
||||
|
||||
path = get_representation_path(representation)
|
||||
path = get_representation_path(repre_doc)
|
||||
|
||||
layers = get_background_layers(path)
|
||||
comp = stub.reload_background(container["members"][1],
|
||||
|
|
@ -85,8 +90,8 @@ class BackgroundLoader(api.AfterEffectsLoader):
|
|||
layers)
|
||||
|
||||
# update container
|
||||
container["representation"] = str(representation["_id"])
|
||||
container["name"] = context["subset"]
|
||||
container["representation"] = str(repre_doc["_id"])
|
||||
container["name"] = product_name
|
||||
container["namespace"] = comp_name
|
||||
container["members"] = comp.members
|
||||
|
||||
|
|
@ -104,5 +109,5 @@ class BackgroundLoader(api.AfterEffectsLoader):
|
|||
stub.imprint(layer.id, {})
|
||||
stub.delete_item(layer.id)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
def switch(self, container, context):
|
||||
self.update(container, context)
|
||||
|
|
|
|||
|
|
@ -64,31 +64,36 @@ class FileLoader(api.AfterEffectsLoader):
|
|||
self.__class__.__name__
|
||||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
""" Switch asset or change version """
|
||||
stub = self.get_stub()
|
||||
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}$', '',
|
||||
container["namespace"])
|
||||
layer_name = "{}_{}".format(context["asset"], context["subset"])
|
||||
layer_name = "{}_{}".format(folder_name, product_name)
|
||||
# switching assets
|
||||
if namespace_from_container != layer_name:
|
||||
layers = stub.get_items(comps=True)
|
||||
existing_layers = [layer.name for layer in layers]
|
||||
layer_name = get_unique_layer_name(
|
||||
existing_layers,
|
||||
"{}_{}".format(context["asset"], context["subset"]))
|
||||
"{}_{}".format(folder_name, product_name))
|
||||
else: # switching version - keep same name
|
||||
layer_name = container["namespace"]
|
||||
path = get_representation_path(representation)
|
||||
path = get_representation_path(repre_doc)
|
||||
# with aftereffects.maintained_selection(): # TODO
|
||||
stub.replace_item(layer.id, path, stub.LOADED_ICON + layer_name)
|
||||
stub.imprint(
|
||||
layer.id, {"representation": str(representation["_id"]),
|
||||
"name": context["subset"],
|
||||
layer.id, {"representation": str(repre_doc["_id"]),
|
||||
"name": product_name,
|
||||
"namespace": layer_name}
|
||||
)
|
||||
|
||||
|
|
@ -103,5 +108,5 @@ class FileLoader(api.AfterEffectsLoader):
|
|||
stub.imprint(layer.id, {})
|
||||
stub.delete_item(layer.id)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
def switch(self, container, context):
|
||||
self.update(container, context)
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
if not inst.data.get("active", True):
|
||||
continue
|
||||
|
||||
family = inst.data["family"]
|
||||
if family not in ["render", "renderLocal"]: # legacy
|
||||
product_type = inst.data["productType"]
|
||||
if product_type not in ["render", "renderLocal"]: # legacy
|
||||
continue
|
||||
|
||||
comp_id = int(inst.data["members"][0])
|
||||
|
|
@ -81,29 +81,32 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
fps = comp_info.frameRate
|
||||
# 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)
|
||||
if not render_q:
|
||||
raise ValueError("No file extension set in Render Queue")
|
||||
render_item = render_q[0]
|
||||
|
||||
product_type = "render"
|
||||
instance_families = inst.data.get("families", [])
|
||||
subset_name = inst.data["subset"]
|
||||
instance_families.append(product_type)
|
||||
product_name = inst.data["productName"]
|
||||
instance = AERenderInstance(
|
||||
family="render",
|
||||
productType=product_type,
|
||||
family=product_type,
|
||||
families=instance_families,
|
||||
version=version,
|
||||
time="",
|
||||
source=current_file,
|
||||
label="{} - {}".format(subset_name, family),
|
||||
subset=subset_name,
|
||||
asset=inst.data["asset"],
|
||||
label="{} - {}".format(product_name, product_type),
|
||||
productName=product_name,
|
||||
folderPath=inst.data["folderPath"],
|
||||
task=task_name,
|
||||
attachTo=False,
|
||||
setMembers='',
|
||||
publish=True,
|
||||
name=subset_name,
|
||||
name=product_name,
|
||||
resolutionWidth=render_item.width,
|
||||
resolutionHeight=render_item.height,
|
||||
pixelAspect=1,
|
||||
|
|
@ -175,8 +178,8 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
version_str = "v{:03d}".format(render_instance.version)
|
||||
if "#" not in file_name: # single frame (mov)W
|
||||
path = os.path.join(base_dir, "{}_{}_{}.{}".format(
|
||||
render_instance.asset,
|
||||
render_instance.subset,
|
||||
render_instance.folderPath,
|
||||
render_instance.productName,
|
||||
version_str,
|
||||
ext
|
||||
))
|
||||
|
|
@ -184,8 +187,8 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
else:
|
||||
for frame in range(start, end + 1):
|
||||
path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format(
|
||||
render_instance.asset,
|
||||
render_instance.subset,
|
||||
render_instance.folderPath,
|
||||
render_instance.productName,
|
||||
version_str,
|
||||
str(frame).zfill(self.padding_width),
|
||||
ext
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Requires:
|
|||
None
|
||||
|
||||
Provides:
|
||||
instance -> family ("review")
|
||||
instance -> families ("review")
|
||||
"""
|
||||
import pyblish.api
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@ import os
|
|||
|
||||
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):
|
||||
""" Adds the AE render instances """
|
||||
|
|
@ -15,86 +12,24 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
|
|||
default_variant = "Main"
|
||||
|
||||
def process(self, context):
|
||||
existing_instance = None
|
||||
workfile_instance = None
|
||||
for instance in context:
|
||||
if instance.data["family"] == "workfile":
|
||||
self.log.debug("Workfile instance found, won't create new")
|
||||
existing_instance = instance
|
||||
if instance.data["productType"] == "workfile":
|
||||
self.log.debug("Workfile instance found")
|
||||
workfile_instance = instance
|
||||
break
|
||||
|
||||
current_file = context.data["currentFile"]
|
||||
staging_dir = os.path.dirname(current_file)
|
||||
scene_file = os.path.basename(current_file)
|
||||
if existing_instance is None: # old publish
|
||||
instance = self._get_new_instance(context, scene_file)
|
||||
else:
|
||||
instance = existing_instance
|
||||
if workfile_instance is None:
|
||||
self.log.debug("Workfile instance not found. Skipping")
|
||||
return
|
||||
|
||||
# creating representation
|
||||
representation = {
|
||||
'name': 'aep',
|
||||
'ext': 'aep',
|
||||
'files': scene_file,
|
||||
workfile_instance.data["representations"].append({
|
||||
"name": "aep",
|
||||
"ext": "aep",
|
||||
"files": scene_file,
|
||||
"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
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
<error id="main">
|
||||
<title>Subset context</title>
|
||||
<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?
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ You can fix this with "repair" button on the right and refresh Publish at the bo
|
|||
### __Detailed Info__ (optional)
|
||||
|
||||
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>
|
||||
</error>
|
||||
</root>
|
||||
|
|
@ -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)
|
||||
)
|
||||
|
|
@ -30,7 +30,7 @@ class ValidateInstanceAssetRepair(pyblish.api.Action):
|
|||
for instance in instances:
|
||||
data = stub.read(instance[0])
|
||||
|
||||
data["asset"] = get_current_asset_name()
|
||||
data["folderPath"] = get_current_asset_name()
|
||||
stub.imprint(instance[0].instance_id, data)
|
||||
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ class ValidateInstanceAsset(pyblish.api.InstancePlugin):
|
|||
order = ValidateContentsOrder
|
||||
|
||||
def process(self, instance):
|
||||
instance_asset = instance.data["asset"]
|
||||
instance_asset = instance.data["folderPath"]
|
||||
current_asset = get_current_asset_name()
|
||||
msg = (
|
||||
f"Instance asset {instance_asset} is not the same "
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from ayon_core.pipeline import (
|
|||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
AYON_CONTAINER_ID,
|
||||
)
|
||||
from ayon_core.lib import (
|
||||
Logger,
|
||||
|
|
@ -563,8 +564,9 @@ def ls() -> Iterator:
|
|||
called containers.
|
||||
"""
|
||||
|
||||
for container in lib.lsattr("id", AVALON_CONTAINER_ID):
|
||||
yield parse_container(container)
|
||||
for id_type in {AYON_CONTAINER_ID, AVALON_CONTAINER_ID}:
|
||||
for container in lib.lsattr("id", id_type):
|
||||
yield parse_container(container)
|
||||
|
||||
|
||||
def publish():
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ from ayon_core.pipeline import (
|
|||
Creator,
|
||||
CreatedInstance,
|
||||
LoaderPlugin,
|
||||
AVALON_INSTANCE_ID,
|
||||
AYON_INSTANCE_ID,
|
||||
)
|
||||
from ayon_core.lib import BoolDef
|
||||
|
||||
|
|
@ -28,13 +30,13 @@ VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"]
|
|||
|
||||
|
||||
def prepare_scene_name(
|
||||
asset: str, subset: str, namespace: Optional[str] = None
|
||||
folder_name: str, product_name: str, namespace: Optional[str] = None
|
||||
) -> str:
|
||||
"""Return a consistent name for an asset."""
|
||||
name = f"{asset}"
|
||||
name = f"{folder_name}"
|
||||
if 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
|
||||
# characters. If the name is longer, it will raise an error.
|
||||
|
|
@ -45,7 +47,7 @@ def prepare_scene_name(
|
|||
|
||||
|
||||
def get_unique_number(
|
||||
asset: str, subset: str
|
||||
folder_name: str, product_name: str
|
||||
) -> str:
|
||||
"""Return a unique number based on the asset name."""
|
||||
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
|
|
@ -62,10 +64,10 @@ def get_unique_number(
|
|||
if c.get(AVALON_PROPERTY)}
|
||||
container_names = obj_group_names.union(coll_group_names)
|
||||
count = 1
|
||||
name = f"{asset}_{count:0>2}_{subset}"
|
||||
name = f"{folder_name}_{count:0>2}_{product_name}"
|
||||
while name in container_names:
|
||||
count += 1
|
||||
name = f"{asset}_{count:0>2}_{subset}"
|
||||
name = f"{folder_name}_{count:0>2}_{product_name}"
|
||||
return f"{count:0>2}"
|
||||
|
||||
|
||||
|
|
@ -159,24 +161,22 @@ class BaseCreator(Creator):
|
|||
create_as_asset_group = False
|
||||
|
||||
@staticmethod
|
||||
def cache_subsets(shared_data):
|
||||
def cache_instance_data(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
|
||||
respective creator identifiers.
|
||||
|
||||
If legacy instances are detected in the scene, create
|
||||
`blender_cached_legacy_subsets` key and fill it with
|
||||
all legacy subsets from this family as a value. # key or value?
|
||||
`blender_cached_legacy_instances` key and fill it with
|
||||
all legacy products from this family as a value. # key or value?
|
||||
|
||||
Args:
|
||||
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_legacy = {}
|
||||
|
||||
|
|
@ -193,7 +193,9 @@ class BaseCreator(Creator):
|
|||
if not avalon_prop:
|
||||
continue
|
||||
|
||||
if avalon_prop.get('id') != 'pyblish.avalon.instance':
|
||||
if avalon_prop.get('id') not in {
|
||||
AYON_INSTANCE_ID, AVALON_INSTANCE_ID
|
||||
}:
|
||||
continue
|
||||
|
||||
creator_id = avalon_prop.get('creator_identifier')
|
||||
|
|
@ -206,19 +208,19 @@ class BaseCreator(Creator):
|
|||
# Legacy creator instance
|
||||
cache_legacy.setdefault(family, []).append(obj_or_col)
|
||||
|
||||
shared_data["blender_cached_subsets"] = cache
|
||||
shared_data["blender_cached_legacy_subsets"] = cache_legacy
|
||||
shared_data["blender_cached_instances"] = cache
|
||||
shared_data["blender_cached_legacy_instances"] = cache_legacy
|
||||
|
||||
return shared_data
|
||||
|
||||
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.
|
||||
Create new instance and store it.
|
||||
|
||||
Args:
|
||||
subset_name(str): Subset name of created instance.
|
||||
product_name(str): Subset name of created instance.
|
||||
instance_data(dict): Instance base data.
|
||||
pre_create_data(dict): Data based on pre creation attributes.
|
||||
Those may affect how creator works.
|
||||
|
|
@ -232,7 +234,7 @@ class BaseCreator(Creator):
|
|||
# Create asset group
|
||||
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:
|
||||
# Create instance as empty
|
||||
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)
|
||||
instances.children.link(instance_node)
|
||||
|
||||
self.set_instance_data(subset_name, instance_data)
|
||||
self.set_instance_data(product_name, instance_data)
|
||||
|
||||
instance = CreatedInstance(
|
||||
self.family, subset_name, instance_data, self
|
||||
self.product_type, product_name, instance_data, self
|
||||
)
|
||||
instance.transient_data["instance_node"] = instance_node
|
||||
self._add_instance_to_context(instance)
|
||||
|
|
@ -259,18 +261,18 @@ class BaseCreator(Creator):
|
|||
"""Override abstract method from BaseCreator.
|
||||
Collect existing instances related to this creator plugin."""
|
||||
|
||||
# Cache subsets in shared data
|
||||
self.cache_subsets(self.collection_shared_data)
|
||||
# Cache instances in shared data
|
||||
self.cache_instance_data(self.collection_shared_data)
|
||||
|
||||
# Get cached subsets
|
||||
cached_subsets = self.collection_shared_data.get(
|
||||
"blender_cached_subsets"
|
||||
# Get cached instances
|
||||
cached_instances = self.collection_shared_data.get(
|
||||
"blender_cached_instances"
|
||||
)
|
||||
if not cached_subsets:
|
||||
if not cached_instances:
|
||||
return
|
||||
|
||||
# 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)
|
||||
# Create instance object from existing data
|
||||
instance = CreatedInstance.from_existing(
|
||||
|
|
@ -302,16 +304,17 @@ class BaseCreator(Creator):
|
|||
)
|
||||
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
|
||||
# workfile instance is included in the AVALON_CONTAINER collection.
|
||||
if (
|
||||
"subset" in changes.changed_keys
|
||||
"productName" 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]
|
||||
name = prepare_scene_name(
|
||||
asset=asset_name, subset=data["subset"]
|
||||
asset_name, data["productName"]
|
||||
)
|
||||
node.name = name
|
||||
|
||||
|
|
@ -337,13 +340,13 @@ class BaseCreator(Creator):
|
|||
|
||||
def set_instance_data(
|
||||
self,
|
||||
subset_name: str,
|
||||
product_name: str,
|
||||
instance_data: dict
|
||||
):
|
||||
"""Fill instance data with required items.
|
||||
|
||||
Args:
|
||||
subset_name(str): Subset name of created instance.
|
||||
product_name(str): Subset name of created instance.
|
||||
instance_data(dict): Instance base data.
|
||||
instance_node(bpy.types.ID): Instance node in blender scene.
|
||||
"""
|
||||
|
|
@ -352,9 +355,9 @@ class BaseCreator(Creator):
|
|||
|
||||
instance_data.update(
|
||||
{
|
||||
"id": "pyblish.avalon.instance",
|
||||
"id": AVALON_INSTANCE_ID,
|
||||
"creator_identifier": self.identifier,
|
||||
"subset": subset_name,
|
||||
"productName": product_name,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -462,14 +465,14 @@ class AssetLoader(LoaderPlugin):
|
|||
filepath = self.filepath_from_context(context)
|
||||
assert Path(filepath).exists(), f"{filepath} doesn't exist."
|
||||
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
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(
|
||||
asset, subset, unique_number
|
||||
folder_name, product_name, unique_number
|
||||
)
|
||||
|
||||
nodes = self.process_asset(
|
||||
|
|
@ -495,21 +498,21 @@ class AssetLoader(LoaderPlugin):
|
|||
# loader=self.__class__.__name__,
|
||||
# )
|
||||
|
||||
# asset = context["asset"]["name"]
|
||||
# subset = context["subset"]["name"]
|
||||
# folder_name = context["asset"]["name"]
|
||||
# product_name = context["subset"]["name"]
|
||||
# instance_name = prepare_scene_name(
|
||||
# asset, subset, unique_number
|
||||
# folder_name, product_name, unique_number
|
||||
# ) + '_CON'
|
||||
|
||||
# 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"""
|
||||
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"""
|
||||
mti = MainThreadItem(self.exec_update, container, representation)
|
||||
mti = MainThreadItem(self.exec_update, container, context)
|
||||
execute_in_main_thread(mti)
|
||||
|
||||
def exec_remove(self, container: Dict) -> bool:
|
||||
|
|
|
|||
|
|
@ -47,6 +47,22 @@ def get_multilayer(settings):
|
|||
["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):
|
||||
"""
|
||||
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"
|
||||
|
||||
|
||||
def set_render_passes(settings):
|
||||
aov_list = (settings["blender"]
|
||||
["RenderSettings"]
|
||||
["aov_list"])
|
||||
|
||||
custom_passes = (settings["blender"]
|
||||
["RenderSettings"]
|
||||
["custom_passes"])
|
||||
def set_render_passes(settings, renderer):
|
||||
aov_list = set(settings["blender"]["RenderSettings"]["aov_list"])
|
||||
custom_passes = settings["blender"]["RenderSettings"]["custom_passes"]
|
||||
|
||||
# Common passes for both renderers
|
||||
vl = bpy.context.view_layer
|
||||
|
||||
# Data Passes
|
||||
vl.use_pass_combined = "combined" in aov_list
|
||||
vl.use_pass_z = "z" in aov_list
|
||||
vl.use_pass_mist = "mist" 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_color = "diffuse_color" in aov_list
|
||||
vl.use_pass_glossy_direct = "specular_light" 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_environment = "environment" in aov_list
|
||||
vl.use_pass_shadow = "shadow" 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
|
||||
cycles.use_pass_volume_direct = "volume_direct" in aov_list
|
||||
cycles.use_pass_volume_indirect = "volume_indirect" in aov_list
|
||||
if renderer == "BLENDER_EEVEE":
|
||||
# Eevee exclusive passes
|
||||
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]
|
||||
for cp in custom_passes:
|
||||
cp_name = cp[0]
|
||||
cp_name = cp["attribute"]
|
||||
if cp_name not in aovs_names:
|
||||
aov = vl.aovs.add()
|
||||
aov.name = cp_name
|
||||
else:
|
||||
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
|
||||
bpy.context.scene.use_nodes = True
|
||||
|
||||
tree = bpy.context.scene.node_tree
|
||||
|
||||
# Get the Render Layers node
|
||||
rl_node = None
|
||||
comp_layer_type = "CompositorNodeRLayers"
|
||||
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:
|
||||
if node.bl_idname == "CompositorNodeRLayers":
|
||||
rl_node = node
|
||||
if node.bl_idname == comp_layer_type:
|
||||
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
|
||||
|
||||
# If there's not a Render Layers node, we create it
|
||||
if not rl_node:
|
||||
rl_node = tree.nodes.new("CompositorNodeRLayers")
|
||||
if not render_layer_node:
|
||||
render_layer_node = tree.nodes.new(comp_layer_type)
|
||||
|
||||
# Get the enabled output sockets, that are the active passes for the
|
||||
# render.
|
||||
|
|
@ -158,48 +228,81 @@ def set_node_tree(output_path, name, aov_sep, ext, multilayer):
|
|||
exclude_sockets = ["Image", "Alpha", "Noisy Image"]
|
||||
passes = [
|
||||
socket
|
||||
for socket in rl_node.outputs
|
||||
for socket in render_layer_node.outputs
|
||||
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
|
||||
output = tree.nodes.new("CompositorNodeOutputFile")
|
||||
output = tree.nodes.new(output_type)
|
||||
|
||||
image_settings = bpy.context.scene.render.image_settings
|
||||
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,
|
||||
# because the blender render already outputs a multilayer exr.
|
||||
if ext == "exr" and multilayer:
|
||||
output.layer_slots.clear()
|
||||
return []
|
||||
multi_exr = ext == "exr" and multilayer
|
||||
slots = output.layer_slots if multi_exr else output.file_slots
|
||||
output.base_path = render_product if multi_exr else str(output_path)
|
||||
|
||||
output.file_slots.clear()
|
||||
output.base_path = str(output_path)
|
||||
slots.clear()
|
||||
|
||||
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
|
||||
# and link it
|
||||
for render_pass in passes:
|
||||
filepath = f"{name}{aov_sep}{render_pass.name}.####"
|
||||
for rpass in passes:
|
||||
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):
|
||||
|
|
@ -228,17 +331,23 @@ def prepare_rendering(asset_group):
|
|||
aov_sep = get_aov_separator(settings)
|
||||
ext = get_image_format(settings)
|
||||
multilayer = get_multilayer(settings)
|
||||
renderer = get_renderer(settings)
|
||||
compositing = get_compositing(settings)
|
||||
|
||||
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)
|
||||
|
||||
render_product = get_render_product(output_path, name, aov_sep)
|
||||
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_folder": render_folder,
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
# -*- 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.hosts.blender.api.lib import imprint
|
||||
|
||||
|
||||
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
|
||||
transform them to the current system. Since the old subsets doesn't
|
||||
This Converter will find all legacy products in the scene and will
|
||||
transform them to the current system. Since the old products doesn't
|
||||
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
|
||||
of the same family and there is no way to handle it. This code should
|
||||
nevertheless cover all creators that came with OpenPype.
|
||||
Its limitation is that you can have multiple creators creating product
|
||||
of the same product type and there is no way to handle it. This code
|
||||
should nevertheless cover all creators that came with OpenPype.
|
||||
|
||||
"""
|
||||
identifier = "io.openpype.creators.blender.legacy"
|
||||
family_to_id = {
|
||||
product_type_to_id = {
|
||||
"action": "io.openpype.creators.blender.action",
|
||||
"camera": "io.openpype.creators.blender.camera",
|
||||
"animation": "io.openpype.creators.blender.animation",
|
||||
|
|
@ -33,42 +33,42 @@ class BlenderLegacyConvertor(SubsetConvertorPlugin):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BlenderLegacyConvertor, self).__init__(*args, **kwargs)
|
||||
self.legacy_subsets = {}
|
||||
self.legacy_instances = {}
|
||||
|
||||
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.
|
||||
|
||||
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(
|
||||
"blender_cached_legacy_subsets")
|
||||
if not self.legacy_subsets:
|
||||
self.legacy_instances = self.collection_shared_data.get(
|
||||
"blender_cached_legacy_instances")
|
||||
if not self.legacy_instances:
|
||||
return
|
||||
self.add_convertor_item(
|
||||
"Found {} incompatible subset{}".format(
|
||||
len(self.legacy_subsets),
|
||||
"s" if len(self.legacy_subsets) > 1 else ""
|
||||
"Found {} incompatible product{}".format(
|
||||
len(self.legacy_instances),
|
||||
"s" if len(self.legacy_instances) > 1 else ""
|
||||
)
|
||||
)
|
||||
|
||||
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`.
|
||||
|
||||
"""
|
||||
if not self.legacy_subsets:
|
||||
if not self.legacy_instances:
|
||||
return
|
||||
|
||||
for family, instance_nodes in self.legacy_subsets.items():
|
||||
if family in self.family_to_id:
|
||||
for product_type, instance_nodes in self.legacy_instances.items():
|
||||
if product_type in self.product_type_to_id:
|
||||
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(
|
||||
"Converting {} to {}".format(instance_node.name,
|
||||
creator_identifier)
|
||||
|
|
|
|||
|
|
@ -10,19 +10,21 @@ class CreateAction(plugin.BaseCreator):
|
|||
|
||||
identifier = "io.openpype.creators.blender.action"
|
||||
label = "Action"
|
||||
family = "action"
|
||||
product_type = "action"
|
||||
icon = "male"
|
||||
|
||||
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
|
||||
collection = super().create(
|
||||
subset_name, instance_data, pre_create_data
|
||||
product_name, instance_data, pre_create_data
|
||||
)
|
||||
|
||||
# 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"):
|
||||
for obj in lib.get_selection():
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ class CreateAnimation(plugin.BaseCreator):
|
|||
|
||||
identifier = "io.openpype.creators.blender.animation"
|
||||
label = "Animation"
|
||||
family = "animation"
|
||||
product_type = "animation"
|
||||
icon = "male"
|
||||
|
||||
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
|
||||
collection = super().create(
|
||||
subset_name, instance_data, pre_create_data
|
||||
product_name, instance_data, pre_create_data
|
||||
)
|
||||
|
||||
if pre_create_data.get("use_selection"):
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@ class CreateBlendScene(plugin.BaseCreator):
|
|||
|
||||
identifier = "io.openpype.creators.blender.blendscene"
|
||||
label = "Blender Scene"
|
||||
family = "blendScene"
|
||||
product_type = "blendScene"
|
||||
icon = "cubes"
|
||||
|
||||
maintain_selection = False
|
||||
|
||||
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,
|
||||
pre_create_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,16 +11,16 @@ class CreateCamera(plugin.BaseCreator):
|
|||
|
||||
identifier = "io.openpype.creators.blender.camera"
|
||||
label = "Camera"
|
||||
family = "camera"
|
||||
product_type = "camera"
|
||||
icon = "video-camera"
|
||||
|
||||
create_as_asset_group = True
|
||||
|
||||
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,
|
||||
pre_create_data)
|
||||
|
||||
|
|
@ -30,8 +30,8 @@ class CreateCamera(plugin.BaseCreator):
|
|||
obj.parent = asset_group
|
||||
else:
|
||||
plugin.deselect_all()
|
||||
camera = bpy.data.cameras.new(subset_name)
|
||||
camera_obj = bpy.data.objects.new(subset_name, camera)
|
||||
camera = bpy.data.cameras.new(product_name)
|
||||
camera_obj = bpy.data.objects.new(product_name, camera)
|
||||
|
||||
instances = bpy.data.collections.get(AVALON_INSTANCES)
|
||||
instances.objects.link(camera_obj)
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@ class CreateLayout(plugin.BaseCreator):
|
|||
|
||||
identifier = "io.openpype.creators.blender.layout"
|
||||
label = "Layout"
|
||||
family = "layout"
|
||||
product_type = "layout"
|
||||
icon = "cubes"
|
||||
|
||||
create_as_asset_group = True
|
||||
|
||||
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,
|
||||
pre_create_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@ class CreateModel(plugin.BaseCreator):
|
|||
|
||||
identifier = "io.openpype.creators.blender.model"
|
||||
label = "Model"
|
||||
family = "model"
|
||||
product_type = "model"
|
||||
icon = "cube"
|
||||
|
||||
create_as_asset_group = True
|
||||
|
||||
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,
|
||||
pre_create_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ class CreatePointcache(plugin.BaseCreator):
|
|||
|
||||
identifier = "io.openpype.creators.blender.pointcache"
|
||||
label = "Point Cache"
|
||||
family = "pointcache"
|
||||
product_type = "pointcache"
|
||||
icon = "gears"
|
||||
|
||||
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
|
||||
collection = super().create(
|
||||
subset_name, instance_data, pre_create_data
|
||||
product_name, instance_data, pre_create_data
|
||||
)
|
||||
|
||||
if pre_create_data.get("use_selection"):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
"""Create render."""
|
||||
import bpy
|
||||
|
||||
from ayon_core.lib import version_up
|
||||
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.workio import save_file
|
||||
|
||||
|
||||
class CreateRenderlayer(plugin.BaseCreator):
|
||||
|
|
@ -10,16 +12,16 @@ class CreateRenderlayer(plugin.BaseCreator):
|
|||
|
||||
identifier = "io.openpype.creators.blender.render"
|
||||
label = "Render"
|
||||
family = "render"
|
||||
product_type = "render"
|
||||
icon = "eye"
|
||||
|
||||
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:
|
||||
# Run parent create method
|
||||
collection = super().create(
|
||||
subset_name, instance_data, pre_create_data
|
||||
product_name, instance_data, pre_create_data
|
||||
)
|
||||
|
||||
prepare_rendering(collection)
|
||||
|
|
@ -37,6 +39,7 @@ class CreateRenderlayer(plugin.BaseCreator):
|
|||
# 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
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ class CreateReview(plugin.BaseCreator):
|
|||
|
||||
identifier = "io.openpype.creators.blender.review"
|
||||
label = "Review"
|
||||
family = "review"
|
||||
product_type = "review"
|
||||
icon = "video-camera"
|
||||
|
||||
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
|
||||
collection = super().create(
|
||||
subset_name, instance_data, pre_create_data
|
||||
product_name, instance_data, pre_create_data
|
||||
)
|
||||
|
||||
if pre_create_data.get("use_selection"):
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@ class CreateRig(plugin.BaseCreator):
|
|||
|
||||
identifier = "io.openpype.creators.blender.rig"
|
||||
label = "Rig"
|
||||
family = "rig"
|
||||
product_type = "rig"
|
||||
icon = "wheelchair"
|
||||
|
||||
create_as_asset_group = True
|
||||
|
||||
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,
|
||||
pre_create_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class CreateWorkfile(BaseCreator, AutoCreator):
|
|||
"""
|
||||
identifier = "io.openpype.creators.blender.workfile"
|
||||
label = "Workfile"
|
||||
family = "workfile"
|
||||
product_type = "workfile"
|
||||
icon = "fa5.file"
|
||||
|
||||
def create(self):
|
||||
|
|
@ -43,8 +43,12 @@ class CreateWorkfile(BaseCreator, AutoCreator):
|
|||
|
||||
if not workfile_instance:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
task_name, task_name, asset_doc, project_name, host_name
|
||||
product_name = self.get_product_name(
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
task_name,
|
||||
host_name,
|
||||
)
|
||||
data = {
|
||||
"folderPath": asset_name,
|
||||
|
|
@ -53,17 +57,17 @@ class CreateWorkfile(BaseCreator, AutoCreator):
|
|||
}
|
||||
data.update(
|
||||
self.get_dynamic_data(
|
||||
task_name,
|
||||
task_name,
|
||||
asset_doc,
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
task_name,
|
||||
host_name,
|
||||
workfile_instance,
|
||||
)
|
||||
)
|
||||
self.log.info("Auto-creating workfile instance...")
|
||||
workfile_instance = CreatedInstance(
|
||||
self.family, subset_name, data, self
|
||||
self.product_type, product_name, data, self
|
||||
)
|
||||
self._add_instance_to_context(workfile_instance)
|
||||
|
||||
|
|
@ -73,13 +77,17 @@ class CreateWorkfile(BaseCreator, AutoCreator):
|
|||
):
|
||||
# Update instance context if it's different
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
task_name, task_name, asset_doc, project_name, host_name
|
||||
product_name = self.get_product_name(
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
self.default_variant,
|
||||
host_name,
|
||||
)
|
||||
|
||||
workfile_instance["folderPath"] = asset_name
|
||||
workfile_instance["task"] = task_name
|
||||
workfile_instance["subset"] = subset_name
|
||||
workfile_instance["productName"] = product_name
|
||||
|
||||
instance_node = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
if not instance_node:
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ from ayon_core.hosts.blender.api import plugin
|
|||
|
||||
|
||||
def append_workfile(context, fname, do_import):
|
||||
asset = context['asset']['name']
|
||||
subset = context['subset']['name']
|
||||
folder_name = context['asset']['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,
|
||||
# if there are duplicate names in the current workfile, the imported
|
||||
|
|
|
|||
|
|
@ -134,13 +134,15 @@ class CacheModelLoader(plugin.AssetLoader):
|
|||
"""
|
||||
|
||||
libpath = self.filepath_from_context(context)
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
asset_name = plugin.prepare_scene_name(asset, subset)
|
||||
unique_number = plugin.get_unique_number(asset, subset)
|
||||
group_name = plugin.prepare_scene_name(asset, subset, unique_number)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
asset_name = plugin.prepare_scene_name(folder_name, product_name)
|
||||
unique_number = plugin.get_unique_number(folder_name, product_name)
|
||||
group_name = plugin.prepare_scene_name(
|
||||
folder_name, product_name, unique_number
|
||||
)
|
||||
namespace = namespace or f"{folder_name}_{unique_number}"
|
||||
|
||||
containers = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
if not containers:
|
||||
|
|
@ -159,6 +161,7 @@ class CacheModelLoader(plugin.AssetLoader):
|
|||
|
||||
self._link_objects(objects, asset_group, containers, asset_group)
|
||||
|
||||
product_type = context["subset"]["data"]["family"]
|
||||
asset_group[AVALON_PROPERTY] = {
|
||||
"schema": "openpype:container-2.0",
|
||||
"id": AVALON_CONTAINER_ID,
|
||||
|
|
@ -169,14 +172,14 @@ class CacheModelLoader(plugin.AssetLoader):
|
|||
"libpath": libpath,
|
||||
"asset_name": asset_name,
|
||||
"parent": str(context["representation"]["parent"]),
|
||||
"family": context["representation"]["context"]["family"],
|
||||
"productType": product_type,
|
||||
"objectName": group_name
|
||||
}
|
||||
|
||||
self[:] = objects
|
||||
return objects
|
||||
|
||||
def exec_update(self, container: Dict, representation: Dict):
|
||||
def exec_update(self, container: Dict, context: Dict):
|
||||
"""Update the loaded asset.
|
||||
|
||||
This will remove all objects of the current collection, load the new
|
||||
|
|
@ -188,15 +191,16 @@ class CacheModelLoader(plugin.AssetLoader):
|
|||
Warning:
|
||||
No nested collections are supported at the moment!
|
||||
"""
|
||||
repre_doc = context["representation"]
|
||||
object_name = container["objectName"]
|
||||
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()
|
||||
|
||||
self.log.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
pformat(repre_doc, indent=2),
|
||||
)
|
||||
|
||||
assert asset_group, (
|
||||
|
|
@ -241,7 +245,7 @@ class CacheModelLoader(plugin.AssetLoader):
|
|||
asset_group.matrix_basis = mat
|
||||
|
||||
metadata["libpath"] = str(libpath)
|
||||
metadata["representation"] = str(representation["_id"])
|
||||
metadata["representation"] = str(repre_doc["_id"])
|
||||
|
||||
def exec_remove(self, container: Dict) -> bool:
|
||||
"""Remove an existing container from a Blender scene.
|
||||
|
|
|
|||
|
|
@ -44,11 +44,11 @@ class BlendActionLoader(plugin.AssetLoader):
|
|||
"""
|
||||
|
||||
libpath = self.filepath_from_context(context)
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
lib_container = plugin.prepare_scene_name(asset, subset)
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
lib_container = plugin.prepare_scene_name(folder_name, product_name)
|
||||
container_name = plugin.prepare_scene_name(
|
||||
asset, subset, namespace
|
||||
folder_name, product_name, namespace
|
||||
)
|
||||
|
||||
container = bpy.data.collections.new(lib_container)
|
||||
|
|
@ -114,7 +114,7 @@ class BlendActionLoader(plugin.AssetLoader):
|
|||
self[:] = nodes
|
||||
return nodes
|
||||
|
||||
def update(self, container: Dict, representation: Dict):
|
||||
def update(self, container: Dict, context: Dict):
|
||||
"""Update the loaded asset.
|
||||
|
||||
This will remove all objects of the current collection, load the new
|
||||
|
|
@ -126,18 +126,18 @@ class BlendActionLoader(plugin.AssetLoader):
|
|||
Warning:
|
||||
No nested collections are supported at the moment!
|
||||
"""
|
||||
|
||||
repre_doc = context["representation"]
|
||||
collection = bpy.data.collections.get(
|
||||
container["objectName"]
|
||||
)
|
||||
|
||||
libpath = Path(get_representation_path(representation))
|
||||
libpath = Path(get_representation_path(repre_doc))
|
||||
extension = libpath.suffix.lower()
|
||||
|
||||
logger.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
pformat(repre_doc, indent=2),
|
||||
)
|
||||
|
||||
assert collection, (
|
||||
|
|
@ -241,7 +241,7 @@ class BlendActionLoader(plugin.AssetLoader):
|
|||
# Save the list of objects in the metadata container
|
||||
collection_metadata["objects"] = objects_list
|
||||
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')
|
||||
|
||||
|
|
|
|||
|
|
@ -39,13 +39,15 @@ class AudioLoader(plugin.AssetLoader):
|
|||
options: Additional settings dictionary
|
||||
"""
|
||||
libpath = self.filepath_from_context(context)
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
asset_name = plugin.prepare_scene_name(asset, subset)
|
||||
unique_number = plugin.get_unique_number(asset, subset)
|
||||
group_name = plugin.prepare_scene_name(asset, subset, unique_number)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
asset_name = plugin.prepare_scene_name(folder_name, product_name)
|
||||
unique_number = plugin.get_unique_number(folder_name, product_name)
|
||||
group_name = plugin.prepare_scene_name(
|
||||
folder_name, product_name, unique_number
|
||||
)
|
||||
namespace = namespace or f"{folder_name}_{unique_number}"
|
||||
|
||||
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
if not avalon_container:
|
||||
|
|
@ -85,7 +87,7 @@ class AudioLoader(plugin.AssetLoader):
|
|||
"libpath": libpath,
|
||||
"asset_name": asset_name,
|
||||
"parent": str(context["representation"]["parent"]),
|
||||
"family": context["representation"]["context"]["family"],
|
||||
"productType": context["subset"]["data"]["family"],
|
||||
"objectName": group_name,
|
||||
"audio": audio
|
||||
}
|
||||
|
|
@ -94,7 +96,7 @@ class AudioLoader(plugin.AssetLoader):
|
|||
self[:] = 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.
|
||||
|
||||
Arguments:
|
||||
|
|
@ -103,14 +105,15 @@ class AudioLoader(plugin.AssetLoader):
|
|||
representation (openpype:representation-1.0): Representation to
|
||||
update, from `host.ls()`.
|
||||
"""
|
||||
repre_doc = context["representation"]
|
||||
object_name = container["objectName"]
|
||||
asset_group = bpy.data.objects.get(object_name)
|
||||
libpath = Path(get_representation_path(representation))
|
||||
libpath = Path(get_representation_path(repre_doc))
|
||||
|
||||
self.log.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
pformat(repre_doc, indent=2),
|
||||
)
|
||||
|
||||
assert asset_group, (
|
||||
|
|
@ -173,8 +176,8 @@ class AudioLoader(plugin.AssetLoader):
|
|||
window_manager.windows[-1].screen.areas[0].type = old_type
|
||||
|
||||
metadata["libpath"] = str(libpath)
|
||||
metadata["representation"] = str(representation["_id"])
|
||||
metadata["parent"] = str(representation["parent"])
|
||||
metadata["representation"] = str(repre_doc["_id"])
|
||||
metadata["parent"] = str(repre_doc["parent"])
|
||||
metadata["audio"] = new_audio
|
||||
|
||||
def exec_remove(self, container: Dict) -> bool:
|
||||
|
|
|
|||
|
|
@ -127,20 +127,22 @@ class BlendLoader(plugin.AssetLoader):
|
|||
options: Additional settings dictionary
|
||||
"""
|
||||
libpath = self.filepath_from_context(context)
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
try:
|
||||
family = context["representation"]["context"]["family"]
|
||||
product_type = context["subset"]["data"]["family"]
|
||||
except ValueError:
|
||||
family = "model"
|
||||
product_type = "model"
|
||||
|
||||
representation = str(context["representation"]["_id"])
|
||||
|
||||
asset_name = plugin.prepare_scene_name(asset, subset)
|
||||
unique_number = plugin.get_unique_number(asset, subset)
|
||||
group_name = plugin.prepare_scene_name(asset, subset, unique_number)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
asset_name = plugin.prepare_scene_name(folder_name, product_name)
|
||||
unique_number = plugin.get_unique_number(folder_name, product_name)
|
||||
group_name = plugin.prepare_scene_name(
|
||||
folder_name, product_name, unique_number
|
||||
)
|
||||
namespace = namespace or f"{folder_name}_{unique_number}"
|
||||
|
||||
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
if not avalon_container:
|
||||
|
|
@ -149,8 +151,8 @@ class BlendLoader(plugin.AssetLoader):
|
|||
|
||||
container, members = self._process_data(libpath, group_name)
|
||||
|
||||
if family == "layout":
|
||||
self._post_process_layout(container, asset, representation)
|
||||
if product_type == "layout":
|
||||
self._post_process_layout(container, folder_name, representation)
|
||||
|
||||
avalon_container.objects.link(container)
|
||||
|
||||
|
|
@ -164,7 +166,7 @@ class BlendLoader(plugin.AssetLoader):
|
|||
"libpath": libpath,
|
||||
"asset_name": asset_name,
|
||||
"parent": str(context["representation"]["parent"]),
|
||||
"family": context["representation"]["context"]["family"],
|
||||
"productType": context["subset"]["data"]["family"],
|
||||
"objectName": group_name,
|
||||
"members": members,
|
||||
}
|
||||
|
|
@ -179,13 +181,14 @@ class BlendLoader(plugin.AssetLoader):
|
|||
self[:] = objects
|
||||
return objects
|
||||
|
||||
def exec_update(self, container: Dict, representation: Dict):
|
||||
def exec_update(self, container: Dict, context: Dict):
|
||||
"""
|
||||
Update the loaded asset.
|
||||
"""
|
||||
repre_doc = context["representation"]
|
||||
group_name = container["objectName"]
|
||||
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, (
|
||||
f"The asset is not loaded: {container['objectName']}"
|
||||
|
|
@ -232,8 +235,8 @@ class BlendLoader(plugin.AssetLoader):
|
|||
|
||||
new_data = {
|
||||
"libpath": libpath,
|
||||
"representation": str(representation["_id"]),
|
||||
"parent": str(representation["parent"]),
|
||||
"representation": str(repre_doc["_id"]),
|
||||
"parent": str(repre_doc["parent"]),
|
||||
"members": members,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class BlendSceneLoader(plugin.AssetLoader):
|
|||
|
||||
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
|
||||
with bpy.data.libraries.load(
|
||||
libpath, link=False, relative=False
|
||||
|
|
@ -82,25 +82,29 @@ class BlendSceneLoader(plugin.AssetLoader):
|
|||
options: Additional settings dictionary
|
||||
"""
|
||||
libpath = self.filepath_from_context(context)
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
try:
|
||||
family = context["representation"]["context"]["family"]
|
||||
product_type = context["subset"]["data"]["family"]
|
||||
except ValueError:
|
||||
family = "model"
|
||||
product_type = "model"
|
||||
|
||||
asset_name = plugin.prepare_scene_name(asset, subset)
|
||||
unique_number = plugin.get_unique_number(asset, subset)
|
||||
group_name = plugin.prepare_scene_name(asset, subset, unique_number)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
asset_name = plugin.prepare_scene_name(folder_name, product_name)
|
||||
unique_number = plugin.get_unique_number(folder_name, product_name)
|
||||
group_name = plugin.prepare_scene_name(
|
||||
folder_name, product_name, unique_number
|
||||
)
|
||||
namespace = namespace or f"{folder_name}_{unique_number}"
|
||||
|
||||
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
if not avalon_container:
|
||||
avalon_container = bpy.data.collections.new(name=AVALON_CONTAINERS)
|
||||
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)
|
||||
|
||||
|
|
@ -114,7 +118,7 @@ class BlendSceneLoader(plugin.AssetLoader):
|
|||
"libpath": libpath,
|
||||
"asset_name": asset_name,
|
||||
"parent": str(context["representation"]["parent"]),
|
||||
"family": context["representation"]["context"]["family"],
|
||||
"productType": context["subset"]["data"]["family"],
|
||||
"objectName": group_name,
|
||||
"members": members,
|
||||
}
|
||||
|
|
@ -129,13 +133,14 @@ class BlendSceneLoader(plugin.AssetLoader):
|
|||
self[:] = objects
|
||||
return objects
|
||||
|
||||
def exec_update(self, container: Dict, representation: Dict):
|
||||
def exec_update(self, container: Dict, context: Dict):
|
||||
"""
|
||||
Update the loaded asset.
|
||||
"""
|
||||
repre_doc = context["representation"]
|
||||
group_name = container["objectName"]
|
||||
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, (
|
||||
f"The asset is not loaded: {container['objectName']}"
|
||||
|
|
@ -167,8 +172,12 @@ class BlendSceneLoader(plugin.AssetLoader):
|
|||
|
||||
self.exec_remove(container)
|
||||
|
||||
family = container["family"]
|
||||
asset_group, members = self._process_data(libpath, group_name, family)
|
||||
product_type = container.get("productType")
|
||||
if product_type is None:
|
||||
product_type = container["family"]
|
||||
asset_group, members = self._process_data(
|
||||
libpath, group_name, product_type
|
||||
)
|
||||
|
||||
for member in members:
|
||||
if member.name in collection_parents:
|
||||
|
|
@ -193,8 +202,8 @@ class BlendSceneLoader(plugin.AssetLoader):
|
|||
|
||||
new_data = {
|
||||
"libpath": libpath,
|
||||
"representation": str(representation["_id"]),
|
||||
"parent": str(representation["parent"]),
|
||||
"representation": str(repre_doc["_id"]),
|
||||
"parent": str(repre_doc["parent"]),
|
||||
"members": members,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -84,13 +84,15 @@ class AbcCameraLoader(plugin.AssetLoader):
|
|||
|
||||
libpath = self.filepath_from_context(context)
|
||||
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
asset_name = plugin.prepare_scene_name(asset, subset)
|
||||
unique_number = plugin.get_unique_number(asset, subset)
|
||||
group_name = plugin.prepare_scene_name(asset, subset, unique_number)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
asset_name = plugin.prepare_scene_name(folder_name, product_name)
|
||||
unique_number = plugin.get_unique_number(folder_name, product_name)
|
||||
group_name = plugin.prepare_scene_name(
|
||||
folder_name, product_name, unique_number
|
||||
)
|
||||
namespace = namespace or f"{folder_name}_{unique_number}"
|
||||
|
||||
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
if not avalon_container:
|
||||
|
|
@ -121,14 +123,14 @@ class AbcCameraLoader(plugin.AssetLoader):
|
|||
"libpath": libpath,
|
||||
"asset_name": asset_name,
|
||||
"parent": str(context["representation"]["parent"]),
|
||||
"family": context["representation"]["context"]["family"],
|
||||
"productType": context["subset"]["data"]["family"],
|
||||
"objectName": group_name,
|
||||
}
|
||||
|
||||
self[:] = objects
|
||||
return objects
|
||||
|
||||
def exec_update(self, container: Dict, representation: Dict):
|
||||
def exec_update(self, container: Dict, context: Dict):
|
||||
"""Update the loaded asset.
|
||||
|
||||
This will remove all objects of the current collection, load the new
|
||||
|
|
@ -140,15 +142,16 @@ class AbcCameraLoader(plugin.AssetLoader):
|
|||
Warning:
|
||||
No nested collections are supported at the moment!
|
||||
"""
|
||||
repre_doc = context["representation"]
|
||||
object_name = container["objectName"]
|
||||
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()
|
||||
|
||||
self.log.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
pformat(repre_doc, indent=2),
|
||||
)
|
||||
|
||||
assert asset_group, (
|
||||
|
|
@ -183,7 +186,7 @@ class AbcCameraLoader(plugin.AssetLoader):
|
|||
asset_group.matrix_basis = mat
|
||||
|
||||
metadata["libpath"] = str(libpath)
|
||||
metadata["representation"] = str(representation["_id"])
|
||||
metadata["representation"] = str(repre_doc["_id"])
|
||||
|
||||
def exec_remove(self, container: Dict) -> bool:
|
||||
"""Remove an existing container from a Blender scene.
|
||||
|
|
|
|||
|
|
@ -87,13 +87,15 @@ class FbxCameraLoader(plugin.AssetLoader):
|
|||
options: Additional settings dictionary
|
||||
"""
|
||||
libpath = self.filepath_from_context(context)
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
asset_name = plugin.prepare_scene_name(asset, subset)
|
||||
unique_number = plugin.get_unique_number(asset, subset)
|
||||
group_name = plugin.prepare_scene_name(asset, subset, unique_number)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
asset_name = plugin.prepare_scene_name(folder_name, product_name)
|
||||
unique_number = plugin.get_unique_number(folder_name, product_name)
|
||||
group_name = plugin.prepare_scene_name(
|
||||
folder_name, product_name, unique_number
|
||||
)
|
||||
namespace = namespace or f"{folder_name}_{unique_number}"
|
||||
|
||||
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
if not avalon_container:
|
||||
|
|
@ -124,14 +126,14 @@ class FbxCameraLoader(plugin.AssetLoader):
|
|||
"libpath": libpath,
|
||||
"asset_name": asset_name,
|
||||
"parent": str(context["representation"]["parent"]),
|
||||
"family": context["representation"]["context"]["family"],
|
||||
"productType": context["subset"]["data"]["family"],
|
||||
"objectName": group_name
|
||||
}
|
||||
|
||||
self[:] = objects
|
||||
return objects
|
||||
|
||||
def exec_update(self, container: Dict, representation: Dict):
|
||||
def exec_update(self, container: Dict, context: Dict):
|
||||
"""Update the loaded asset.
|
||||
|
||||
This will remove all objects of the current collection, load the new
|
||||
|
|
@ -143,15 +145,16 @@ class FbxCameraLoader(plugin.AssetLoader):
|
|||
Warning:
|
||||
No nested collections are supported at the moment!
|
||||
"""
|
||||
repre_doc = context["representation"]
|
||||
object_name = container["objectName"]
|
||||
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()
|
||||
|
||||
self.log.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
pformat(repre_doc, indent=2),
|
||||
)
|
||||
|
||||
assert asset_group, (
|
||||
|
|
@ -193,7 +196,7 @@ class FbxCameraLoader(plugin.AssetLoader):
|
|||
asset_group.matrix_basis = mat
|
||||
|
||||
metadata["libpath"] = str(libpath)
|
||||
metadata["representation"] = str(representation["_id"])
|
||||
metadata["representation"] = str(repre_doc["_id"])
|
||||
|
||||
def exec_remove(self, container: Dict) -> bool:
|
||||
"""Remove an existing container from a Blender scene.
|
||||
|
|
|
|||
|
|
@ -131,13 +131,15 @@ class FbxModelLoader(plugin.AssetLoader):
|
|||
options: Additional settings dictionary
|
||||
"""
|
||||
libpath = self.filepath_from_context(context)
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
asset_name = plugin.prepare_scene_name(asset, subset)
|
||||
unique_number = plugin.get_unique_number(asset, subset)
|
||||
group_name = plugin.prepare_scene_name(asset, subset, unique_number)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
asset_name = plugin.prepare_scene_name(folder_name, product_name)
|
||||
unique_number = plugin.get_unique_number(folder_name, product_name)
|
||||
group_name = plugin.prepare_scene_name(
|
||||
folder_name, product_name, unique_number
|
||||
)
|
||||
namespace = namespace or f"{folder_name}_{unique_number}"
|
||||
|
||||
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
if not avalon_container:
|
||||
|
|
@ -168,14 +170,14 @@ class FbxModelLoader(plugin.AssetLoader):
|
|||
"libpath": libpath,
|
||||
"asset_name": asset_name,
|
||||
"parent": str(context["representation"]["parent"]),
|
||||
"family": context["representation"]["context"]["family"],
|
||||
"productType": context["subset"]["data"]["family"],
|
||||
"objectName": group_name
|
||||
}
|
||||
|
||||
self[:] = objects
|
||||
return objects
|
||||
|
||||
def exec_update(self, container: Dict, representation: Dict):
|
||||
def exec_update(self, container: Dict, context: Dict):
|
||||
"""Update the loaded asset.
|
||||
|
||||
This will remove all objects of the current collection, load the new
|
||||
|
|
@ -187,15 +189,16 @@ class FbxModelLoader(plugin.AssetLoader):
|
|||
Warning:
|
||||
No nested collections are supported at the moment!
|
||||
"""
|
||||
repre_doc = context["representation"]
|
||||
object_name = container["objectName"]
|
||||
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()
|
||||
|
||||
self.log.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
pformat(repre_doc, indent=2),
|
||||
)
|
||||
|
||||
assert asset_group, (
|
||||
|
|
@ -248,7 +251,7 @@ class FbxModelLoader(plugin.AssetLoader):
|
|||
asset_group.matrix_basis = mat
|
||||
|
||||
metadata["libpath"] = str(libpath)
|
||||
metadata["representation"] = str(representation["_id"])
|
||||
metadata["representation"] = str(repre_doc["_id"])
|
||||
|
||||
def exec_remove(self, container: Dict) -> bool:
|
||||
"""Remove an existing container from a Blender scene.
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@ class JsonLayoutLoader(plugin.AssetLoader):
|
|||
if anim_collection:
|
||||
bpy.data.collections.remove(anim_collection)
|
||||
|
||||
def _get_loader(self, loaders, family):
|
||||
def _get_loader(self, loaders, product_type):
|
||||
name = ""
|
||||
if family == 'rig':
|
||||
if product_type == 'rig':
|
||||
name = "BlendRigLoader"
|
||||
elif family == 'model':
|
||||
elif product_type == 'model':
|
||||
name = "BlendModelLoader"
|
||||
|
||||
if name == "":
|
||||
|
|
@ -76,10 +76,12 @@ class JsonLayoutLoader(plugin.AssetLoader):
|
|||
|
||||
for element in data:
|
||||
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)
|
||||
loader = self._get_loader(loaders, family)
|
||||
loader = self._get_loader(loaders, product_type)
|
||||
|
||||
if not loader:
|
||||
continue
|
||||
|
|
@ -95,7 +97,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
|
|||
'parent': asset_group,
|
||||
'transform': element.get('transform'),
|
||||
'action': action,
|
||||
'create_animation': True if family == 'rig' else False,
|
||||
'create_animation': True if product_type == 'rig' else False,
|
||||
'animation_asset': asset
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +129,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
|
|||
# legacy_create(
|
||||
# creator_plugin,
|
||||
# name="camera",
|
||||
# # name=f"{unique_number}_{subset}_animation",
|
||||
# # name=f"{unique_number}_{product[name]}_animation",
|
||||
# asset=asset,
|
||||
# options={"useSelection": False}
|
||||
# # data={"dependencies": str(context["representation"]["_id"])}
|
||||
|
|
@ -146,13 +148,15 @@ class JsonLayoutLoader(plugin.AssetLoader):
|
|||
options: Additional settings dictionary
|
||||
"""
|
||||
libpath = self.filepath_from_context(context)
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
asset_name = plugin.prepare_scene_name(asset, subset)
|
||||
unique_number = plugin.get_unique_number(asset, subset)
|
||||
group_name = plugin.prepare_scene_name(asset, subset, unique_number)
|
||||
namespace = namespace or f"{asset}_{unique_number}"
|
||||
asset_name = plugin.prepare_scene_name(folder_name, product_name)
|
||||
unique_number = plugin.get_unique_number(folder_name, product_name)
|
||||
group_name = plugin.prepare_scene_name(
|
||||
folder_name, product_name, unique_number
|
||||
)
|
||||
namespace = namespace or f"{folder_name}_{unique_number}"
|
||||
|
||||
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
if not avalon_container:
|
||||
|
|
@ -177,14 +181,14 @@ class JsonLayoutLoader(plugin.AssetLoader):
|
|||
"libpath": libpath,
|
||||
"asset_name": asset_name,
|
||||
"parent": str(context["representation"]["parent"]),
|
||||
"family": context["representation"]["context"]["family"],
|
||||
"productType": context["subset"]["data"]["family"],
|
||||
"objectName": group_name
|
||||
}
|
||||
|
||||
self[:] = 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.
|
||||
|
||||
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
|
||||
case though.
|
||||
"""
|
||||
repre_doc = context["representation"]
|
||||
object_name = container["objectName"]
|
||||
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()
|
||||
|
||||
self.log.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
pformat(repre_doc, indent=2),
|
||||
)
|
||||
|
||||
assert asset_group, (
|
||||
|
|
@ -239,7 +244,10 @@ class JsonLayoutLoader(plugin.AssetLoader):
|
|||
|
||||
for obj in asset_group.children:
|
||||
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
|
||||
for child in obj.children:
|
||||
if child.type == 'ARMATURE':
|
||||
|
|
@ -262,7 +270,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
|
|||
asset_group.matrix_basis = mat
|
||||
|
||||
metadata["libpath"] = str(libpath)
|
||||
metadata["representation"] = str(representation["_id"])
|
||||
metadata["representation"] = str(repre_doc["_id"])
|
||||
|
||||
def exec_remove(self, container: Dict) -> bool:
|
||||
"""Remove an existing container from a Blender scene.
|
||||
|
|
|
|||
|
|
@ -93,18 +93,18 @@ class BlendLookLoader(plugin.AssetLoader):
|
|||
"""
|
||||
|
||||
libpath = self.filepath_from_context(context)
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
folder_name = context["asset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
lib_container = plugin.prepare_scene_name(
|
||||
asset, subset
|
||||
folder_name, product_name
|
||||
)
|
||||
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(
|
||||
asset, subset, unique_number
|
||||
folder_name, product_name, unique_number
|
||||
)
|
||||
|
||||
container = bpy.data.collections.new(lib_container)
|
||||
|
|
@ -131,22 +131,23 @@ class BlendLookLoader(plugin.AssetLoader):
|
|||
metadata["materials"] = materials
|
||||
|
||||
metadata["parent"] = str(context["representation"]["parent"])
|
||||
metadata["family"] = context["representation"]["context"]["family"]
|
||||
metadata["product_type"] = context["subset"]["data"]["family"]
|
||||
|
||||
nodes = list(container.objects)
|
||||
nodes.append(container)
|
||||
self[:] = nodes
|
||||
return nodes
|
||||
|
||||
def update(self, container: Dict, representation: Dict):
|
||||
def update(self, container: Dict, context: Dict):
|
||||
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()
|
||||
|
||||
self.log.info(
|
||||
"Container: %s\nRepresentation: %s",
|
||||
pformat(container, indent=2),
|
||||
pformat(representation, indent=2),
|
||||
pformat(repre_doc, indent=2),
|
||||
)
|
||||
|
||||
assert collection, (
|
||||
|
|
@ -201,7 +202,7 @@ class BlendLookLoader(plugin.AssetLoader):
|
|||
collection_metadata["objects"] = objects
|
||||
collection_metadata["materials"] = materials
|
||||
collection_metadata["libpath"] = str(libpath)
|
||||
collection_metadata["representation"] = str(representation["_id"])
|
||||
collection_metadata["representation"] = str(repre_doc["_id"])
|
||||
|
||||
def remove(self, container: Dict) -> bool:
|
||||
collection = bpy.data.collections.get(container["objectName"])
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin):
|
|||
members.extend(instance_node.children)
|
||||
|
||||
# Special case for animation instances, include armatures
|
||||
if instance.data["family"] == "animation":
|
||||
if instance.data["productType"] == "animation":
|
||||
for obj in instance_node.objects:
|
||||
if obj.type == 'EMPTY' and obj.get(AVALON_PROPERTY):
|
||||
members.extend(
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
instance_name = f"{folder_name}_{product_name}"
|
||||
filename = f"{instance_name}.abc"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ class ExtractAnimationABC(
|
|||
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
instance_name = f"{folder_name}_{product_name}"
|
||||
filename = f"{instance_name}.abc"
|
||||
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
# Define extract output file path
|
||||
|
||||
stagingdir = self.staging_dir(instance)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
instance_name = f"{folder_name}_{product_name}"
|
||||
filename = f"{instance_name}.blend"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ class ExtractBlendAnimation(
|
|||
# Define extract output file path
|
||||
|
||||
stagingdir = self.staging_dir(instance)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
instance_name = f"{folder_name}_{product_name}"
|
||||
filename = f"{instance_name}.blend"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
instance_name = f"{folder_name}_{product_name}"
|
||||
filename = f"{instance_name}.abc"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
instance_name = f"{folder_name}_{product_name}"
|
||||
filename = f"{instance_name}.fbx"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
instance_name = f"{folder_name}_{product_name}"
|
||||
filename = f"{instance_name}.fbx"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -145,9 +145,9 @@ class ExtractAnimationFBX(
|
|||
|
||||
root.select_set(True)
|
||||
armature.select_set(True)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
instance_name = f"{folder_name}_{product_name}"
|
||||
fbx_filename = f"{instance_name}_{armature.name}.fbx"
|
||||
filepath = os.path.join(stagingdir, fbx_filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
fbx_count = 0
|
||||
|
||||
project_name = instance.context.data["projectEntity"]["name"]
|
||||
project_name = instance.context.data["projectName"]
|
||||
for asset in asset_group.children:
|
||||
metadata = asset.get(AVALON_PROPERTY)
|
||||
if not metadata:
|
||||
|
|
@ -147,7 +147,9 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
continue
|
||||
|
||||
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))
|
||||
# Get blend reference
|
||||
|
|
@ -179,7 +181,8 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
json_element["reference_fbx"] = str(fbx_id)
|
||||
if 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["asset_name"] = metadata["asset_name"]
|
||||
json_element["file_path"] = metadata["libpath"]
|
||||
|
|
@ -215,7 +218,7 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
]
|
||||
|
||||
# Extract the animation as well
|
||||
if family == "rig":
|
||||
if product_type == "rig":
|
||||
f, n = self._export_animation(
|
||||
asset, instance, stagingdir, fbx_count)
|
||||
if f:
|
||||
|
|
@ -225,9 +228,9 @@ class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
json_data.append(json_element)
|
||||
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
instance_name = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
instance_name = f"{folder_name}_{product_name}"
|
||||
json_filename = f"{instance_name}.json"
|
||||
|
||||
json_path = os.path.join(stagingdir, json_filename)
|
||||
|
|
|
|||
|
|
@ -55,9 +55,9 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
|||
|
||||
# get output path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
filename = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
filename = f"{folder_name}_{product_name}"
|
||||
|
||||
path = os.path.join(stagingdir, filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ class ExtractThumbnail(publish.Extractor):
|
|||
return
|
||||
|
||||
stagingdir = self.staging_dir(instance)
|
||||
asset_name = instance.data["assetEntity"]["name"]
|
||||
subset = instance.data["subset"]
|
||||
filename = f"{asset_name}_{subset}"
|
||||
folder_name = instance.data["assetEntity"]["name"]
|
||||
product_name = instance.data["productName"]
|
||||
filename = f"{folder_name}_{product_name}"
|
||||
|
||||
path = os.path.join(stagingdir, filename)
|
||||
|
||||
|
|
@ -42,11 +42,11 @@ class ExtractThumbnail(publish.Extractor):
|
|||
|
||||
camera = instance.data.get("review_camera", "AUTO")
|
||||
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)
|
||||
|
||||
presets = json.loads(self.presets)
|
||||
preset = presets.get(family, {})
|
||||
preset = presets.get(product_type, {})
|
||||
|
||||
preset.update({
|
||||
"camera": camera,
|
||||
|
|
|
|||
|
|
@ -28,25 +28,26 @@ class IntegrateAnimation(
|
|||
# Update the json file for the setdress to add the published
|
||||
# representations of the animations
|
||||
for json_dict in data:
|
||||
json_product_name = json_dict["productName"]
|
||||
i = None
|
||||
for elem in instance.context:
|
||||
if elem.data.get('subset') == json_dict['subset']:
|
||||
if elem.data["productName"] == json_product_name:
|
||||
i = elem
|
||||
break
|
||||
if not i:
|
||||
continue
|
||||
rep = None
|
||||
pub_repr = i.data.get('published_representations')
|
||||
pub_repr = i.data["published_representations"]
|
||||
for elem in pub_repr:
|
||||
if pub_repr.get(elem).get('representation').get('name') == "fbx":
|
||||
rep = pub_repr.get(elem)
|
||||
if pub_repr[elem]["representation"]["name"] == "fbx":
|
||||
rep = pub_repr[elem]
|
||||
break
|
||||
if not rep:
|
||||
continue
|
||||
obj_id = rep.get('representation').get('_id')
|
||||
obj_id = rep["representation"]["_id"]
|
||||
|
||||
if obj_id:
|
||||
json_dict['_id'] = str(obj_id)
|
||||
json_dict["representation_id"] = str(obj_id)
|
||||
|
||||
with open(json_path, "w") as file:
|
||||
json.dump(data, fp=file, indent=2)
|
||||
|
|
|
|||
|
|
@ -28,15 +28,27 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin,
|
|||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
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
|
||||
file = os.path.basename(filepath)
|
||||
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(
|
||||
"Render output folder "
|
||||
"doesn't match the blender scene name! "
|
||||
"Use Repair action to "
|
||||
"fix the folder file path."
|
||||
"Render output folder doesn't match the blender scene name! "
|
||||
"Use Repair action to fix the folder file path."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class ValidateFileSaved(pyblish.api.ContextPlugin,
|
|||
hosts = ["blender"]
|
||||
label = "Validate File Saved"
|
||||
optional = False
|
||||
# TODO rename to 'exclude_product_types'
|
||||
exclude_families = []
|
||||
actions = [SaveWorkfileAction]
|
||||
|
||||
|
|
@ -41,8 +42,8 @@ class ValidateFileSaved(pyblish.api.ContextPlugin,
|
|||
|
||||
# Do not validate workfile has unsaved changes if only instances
|
||||
# present of families that should be excluded
|
||||
families = {
|
||||
instance.data["family"] for instance in context
|
||||
product_types = {
|
||||
instance.data["productType"] for instance in context
|
||||
# Consider only enabled instances
|
||||
if instance.data.get("publish", True)
|
||||
and instance.data.get("active", True)
|
||||
|
|
@ -52,7 +53,7 @@ class ValidateFileSaved(pyblish.api.ContextPlugin,
|
|||
return any(family in exclude_family
|
||||
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 "
|
||||
"unsaved changes validation..")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
|
|||
asset_name = get_asset_name_identifier(asset_entity)
|
||||
|
||||
shared_instance_data = {
|
||||
"asset": asset_name,
|
||||
"folderPath": asset_name,
|
||||
"frameStart": asset_entity["data"]["frameStart"],
|
||||
"frameEnd": asset_entity["data"]["frameEnd"],
|
||||
"handleStart": asset_entity["data"]["handleStart"],
|
||||
|
|
@ -46,17 +46,18 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
|
|||
shared_instance_data.update(celaction_kwargs)
|
||||
|
||||
# workfile instance
|
||||
family = "workfile"
|
||||
subset = family + task.capitalize()
|
||||
product_type = "workfile"
|
||||
product_name = product_type + task.capitalize()
|
||||
# Create instance
|
||||
instance = context.create_instance(subset)
|
||||
instance = context.create_instance(product_name)
|
||||
|
||||
# creating instance data
|
||||
instance.data.update({
|
||||
"subset": subset,
|
||||
"label": scene_file,
|
||||
"family": family,
|
||||
"families": [],
|
||||
"productName": product_name,
|
||||
"productType": product_type,
|
||||
"family": product_type,
|
||||
"families": [product_type],
|
||||
"representations": []
|
||||
})
|
||||
|
||||
|
|
@ -76,17 +77,19 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin):
|
|||
self.log.info('Publishing Celaction workfile')
|
||||
|
||||
# render instance
|
||||
subset = f"render{task}Main"
|
||||
instance = context.create_instance(name=subset)
|
||||
product_name = f"render{task}Main"
|
||||
product_type = "render.farm"
|
||||
instance = context.create_instance(name=product_name)
|
||||
# getting instance state
|
||||
instance.data["publish"] = True
|
||||
|
||||
# add assetEntity data into instance
|
||||
instance.data.update({
|
||||
"label": "{} - farm".format(subset),
|
||||
"family": "render.farm",
|
||||
"families": [],
|
||||
"subset": subset
|
||||
"label": "{} - farm".format(product_name),
|
||||
"productType": product_type,
|
||||
"family": product_type,
|
||||
"families": [product_type],
|
||||
"productName": product_name
|
||||
})
|
||||
|
||||
# adding basic script data
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ class CollectRenderPath(pyblish.api.InstancePlugin):
|
|||
anatomy = instance.context.data["anatomy"]
|
||||
anatomy_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
padding = anatomy.templates.get("frame_padding", 4)
|
||||
product_type = "render"
|
||||
anatomy_data.update({
|
||||
"frame": f"%0{padding}d",
|
||||
"family": "render",
|
||||
"family": product_type,
|
||||
"representation": self.output_extension,
|
||||
"ext": self.output_extension
|
||||
})
|
||||
anatomy_data["product"]["type"] = product_type
|
||||
|
||||
anatomy_filled = anatomy.format(anatomy_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -147,8 +147,8 @@ def imprint(segment, data=None):
|
|||
Examples:
|
||||
data = {
|
||||
'asset': 'sq020sh0280',
|
||||
'family': 'render',
|
||||
'subset': 'subsetMain'
|
||||
'productType': 'render',
|
||||
'productName': 'productMain'
|
||||
}
|
||||
"""
|
||||
data = data or {}
|
||||
|
|
|
|||
|
|
@ -353,9 +353,9 @@ class PublishableClip:
|
|||
rename_default = False
|
||||
hierarchy_default = "{_folder_}/{_sequence_}/{_track_}"
|
||||
clip_name_default = "shot_{_trackIndex_:0>3}_{_clipIndex_:0>4}"
|
||||
subset_name_default = "[ track name ]"
|
||||
review_track_default = "[ none ]"
|
||||
subset_family_default = "plate"
|
||||
base_product_name_default = "[ track name ]"
|
||||
base_product_type_default = "plate"
|
||||
count_from_default = 10
|
||||
count_steps_default = 10
|
||||
vertical_sync_default = False
|
||||
|
|
@ -368,7 +368,7 @@ class PublishableClip:
|
|||
|
||||
def __init__(self, segment, **kwargs):
|
||||
self.rename_index = kwargs["rename_index"]
|
||||
self.family = kwargs["family"]
|
||||
self.product_type = kwargs["family"]
|
||||
self.log = kwargs["log"]
|
||||
|
||||
# get main parent objects
|
||||
|
|
@ -486,10 +486,10 @@ class PublishableClip:
|
|||
"countFrom", {}).get("value") or self.count_from_default
|
||||
self.count_steps = self.ui_inputs.get(
|
||||
"countSteps", {}).get("value") or self.count_steps_default
|
||||
self.subset_name = self.ui_inputs.get(
|
||||
"subsetName", {}).get("value") or self.subset_name_default
|
||||
self.subset_family = self.ui_inputs.get(
|
||||
"subsetFamily", {}).get("value") or self.subset_family_default
|
||||
self.base_product_name = self.ui_inputs.get(
|
||||
"productName", {}).get("value") or self.base_product_name_default
|
||||
self.base_product_type = self.ui_inputs.get(
|
||||
"productType", {}).get("value") or self.base_product_type_default
|
||||
self.vertical_sync = self.ui_inputs.get(
|
||||
"vSyncOn", {}).get("value") or self.vertical_sync_default
|
||||
self.driving_layer = self.ui_inputs.get(
|
||||
|
|
@ -509,12 +509,14 @@ class PublishableClip:
|
|||
or self.retimed_framerange_default
|
||||
)
|
||||
|
||||
# build subset name from layer name
|
||||
if self.subset_name == "[ track name ]":
|
||||
self.subset_name = self.track_name
|
||||
# build product name from layer name
|
||||
if self.base_product_name == "[ track name ]":
|
||||
self.base_product_name = self.track_name
|
||||
|
||||
# create subset for publishing
|
||||
self.subset = self.subset_family + self.subset_name.capitalize()
|
||||
# create product for publishing
|
||||
self.product_name = (
|
||||
self.base_product_type + self.base_product_name.capitalize()
|
||||
)
|
||||
|
||||
def _replace_hash_to_expression(self, name, text):
|
||||
""" Replace hash with number in correct padding. """
|
||||
|
|
@ -608,14 +610,14 @@ class PublishableClip:
|
|||
_hero_data = deepcopy(hero_data)
|
||||
_hero_data.update({"heroTrack": False})
|
||||
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
|
||||
if self.subset in data_subset:
|
||||
_hero_data["subset"] = self.subset + str(
|
||||
if self.product_name in data_product_name:
|
||||
_hero_data["productName"] = self.product_name + str(
|
||||
self.track_index)
|
||||
# in case track name and subset name is the same then add
|
||||
if self.subset_name == self.track_name:
|
||||
_hero_data["subset"] = self.subset
|
||||
# in case track name and product name is the same then add
|
||||
if self.base_product_name == self.track_name:
|
||||
_hero_data["productName"] = self.product_name
|
||||
# assign data to return hierarchy data to tag
|
||||
tag_hierarchy_data = _hero_data
|
||||
break
|
||||
|
|
@ -637,9 +639,9 @@ class PublishableClip:
|
|||
"hierarchy": hierarchy_filled,
|
||||
"parents": self.parents,
|
||||
"hierarchyData": hierarchy_formatting_data,
|
||||
"subset": self.subset,
|
||||
"family": self.subset_family,
|
||||
"families": [self.family]
|
||||
"productName": self.product_name,
|
||||
"productType": self.base_product_type,
|
||||
"families": [self.base_product_type, self.product_type]
|
||||
}
|
||||
|
||||
def _convert_to_entity(self, type, template):
|
||||
|
|
@ -704,7 +706,7 @@ class ClipLoader(LoaderPlugin):
|
|||
_mapping = None
|
||||
_host_settings = None
|
||||
|
||||
def apply_settings(cls, project_settings, system_settings):
|
||||
def apply_settings(cls, project_settings):
|
||||
|
||||
plugin_type_settings = (
|
||||
project_settings
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ class CreateShotClip(opfapi.Creator):
|
|||
"""Publishable clip"""
|
||||
|
||||
label = "Create Publishable Clip"
|
||||
family = "clip"
|
||||
product_type = "clip"
|
||||
icon = "film"
|
||||
defaults = ["Main"]
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ class CreateShotClip(opfapi.Creator):
|
|||
|
||||
# open widget for plugins inputs
|
||||
results_back = self.create_widget(
|
||||
"Pype publish attributes creator",
|
||||
"AYON publish attributes creator",
|
||||
"Define sequential rename and fill hierarchy data.",
|
||||
gui_inputs
|
||||
)
|
||||
|
|
@ -62,7 +62,7 @@ class CreateShotClip(opfapi.Creator):
|
|||
"log": self.log,
|
||||
"ui_inputs": results_back,
|
||||
"avalon": self.data,
|
||||
"family": self.data["family"]
|
||||
"product_type": self.data["productType"]
|
||||
}
|
||||
|
||||
for i, segment in enumerate(sorted_selected_segments):
|
||||
|
|
@ -203,19 +203,19 @@ class CreateShotClip(opfapi.Creator):
|
|||
"target": "ui",
|
||||
"order": 3,
|
||||
"value": {
|
||||
"subsetName": {
|
||||
"productName": {
|
||||
"value": ["[ track name ]", "main", "bg", "fg", "bg",
|
||||
"animatic"],
|
||||
"type": "QComboBox",
|
||||
"label": "Subset Name",
|
||||
"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},
|
||||
"subsetFamily": {
|
||||
"productType": {
|
||||
"value": ["plate", "take"],
|
||||
"type": "QComboBox",
|
||||
"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},
|
||||
"reviewTrack": {
|
||||
"value": ["< none >"] + gui_tracks,
|
||||
|
|
@ -229,7 +229,7 @@ class CreateShotClip(opfapi.Creator):
|
|||
"type": "QCheckBox",
|
||||
"label": "Include audio",
|
||||
"target": "tag",
|
||||
"toolTip": "Process subsets with corresponding audio", # noqa
|
||||
"toolTip": "Process products with corresponding audio", # noqa
|
||||
"order": 3},
|
||||
"sourceResolution": {
|
||||
"value": False,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from ayon_core.lib.transcoding import (
|
|||
|
||||
|
||||
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
|
||||
during conforming to project
|
||||
|
|
@ -31,14 +31,14 @@ class LoadClip(opfapi.ClipLoader):
|
|||
# settings
|
||||
reel_group_name = "OpenPype_Reels"
|
||||
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:
|
||||
- {layerName} - original layer name token
|
||||
- {layerUID} - original layer UID token
|
||||
- {originalBasename} - original clip name taken from file
|
||||
"""
|
||||
layer_rename_template = "{asset}_{subset}<_{output}>"
|
||||
layer_rename_template = "{folder[name]}_{product[name]}<_{output}>"
|
||||
layer_rename_patterns = []
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
|
|
@ -180,27 +180,27 @@ class LoadClip(opfapi.ClipLoader):
|
|||
# unwrapping segment from input clip
|
||||
pass
|
||||
|
||||
# def switch(self, container, representation):
|
||||
# self.update(container, representation)
|
||||
# def switch(self, container, context):
|
||||
# self.update(container, context)
|
||||
|
||||
# def update(self, container, representation):
|
||||
# def update(self, container, context):
|
||||
# """ Updating previously loaded clips
|
||||
# """
|
||||
|
||||
# # load clip to timeline and get main variables
|
||||
# repre_doc = context['representation']
|
||||
# name = container['name']
|
||||
# namespace = container['namespace']
|
||||
# track_item = phiero.get_track_items(
|
||||
# track_item_name=namespace)
|
||||
# version = io.find_one({
|
||||
# "type": "version",
|
||||
# "_id": representation["parent"]
|
||||
# "_id": repre_doc["parent"]
|
||||
# })
|
||||
# version_data = version.get("data", {})
|
||||
# version_name = version.get("name", None)
|
||||
# colorspace = version_data.get("colorspace", None)
|
||||
# object_name = "{}_{}".format(name, namespace)
|
||||
# file = get_representation_path(representation).replace("\\", "/")
|
||||
# file = get_representation_path(repre_doc).replace("\\", "/")
|
||||
# clip = track_item.source()
|
||||
|
||||
# # reconnect media to new path
|
||||
|
|
@ -225,7 +225,7 @@ class LoadClip(opfapi.ClipLoader):
|
|||
|
||||
# # add variables related to version context
|
||||
# data_imprint.update({
|
||||
# "representation": str(representation["_id"]),
|
||||
# "representation": str(repre_doc["_id"]),
|
||||
# "version": version_name,
|
||||
# "colorspace": colorspace,
|
||||
# "objectName": object_name
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from ayon_core.lib.transcoding import (
|
|||
)
|
||||
|
||||
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
|
||||
during conforming to project
|
||||
|
|
@ -29,14 +29,14 @@ class LoadClipBatch(opfapi.ClipLoader):
|
|||
|
||||
# settings
|
||||
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:
|
||||
- {layerName} - original layer name token
|
||||
- {layerUID} - original layer UID token
|
||||
- {originalBasename} - original clip name taken from file
|
||||
"""
|
||||
layer_rename_template = "{asset}_{subset}<_{output}>"
|
||||
layer_rename_template = "{folder[name]}_{product[name]}<_{output}>"
|
||||
layer_rename_patterns = []
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
|
|
@ -50,17 +50,8 @@ class LoadClipBatch(opfapi.ClipLoader):
|
|||
version_name = version.get("name", None)
|
||||
colorspace = self.get_colorspace(context)
|
||||
|
||||
# TODO remove '{folder[name]}' and '{product[name]}' replacement
|
||||
clip_name_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}")
|
||||
)
|
||||
clip_name_template = self.clip_name_template
|
||||
layer_rename_template = self.layer_rename_template
|
||||
# in case output is not in context replace key to representation
|
||||
if not context["representation"]["context"].get("output"):
|
||||
clip_name_template = clip_name_template.replace(
|
||||
|
|
@ -68,8 +59,22 @@ class LoadClipBatch(opfapi.ClipLoader):
|
|||
layer_rename_template = layer_rename_template.replace(
|
||||
"output", "representation")
|
||||
|
||||
asset_doc = context["asset"]
|
||||
subset_doc = context["subset"]
|
||||
formatting_data = deepcopy(context["representation"]["context"])
|
||||
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(
|
||||
formatting_data)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,6 @@ class CollectTestSelection(pyblish.api.ContextPlugin):
|
|||
|
||||
opfapi.imprint(segment, {
|
||||
'asset': segment.name.get_value(),
|
||||
'family': 'render',
|
||||
'subset': 'subsetMain'
|
||||
'productType': 'render',
|
||||
'productName': 'productMain'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from types import NoneType
|
|||
import pyblish
|
||||
import ayon_core.hosts.flame.api as opfapi
|
||||
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 (
|
||||
is_overlapping_otio_ranges,
|
||||
get_media_range_with_retimes
|
||||
|
|
@ -47,7 +48,9 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
|||
if not marker_data:
|
||||
continue
|
||||
|
||||
if marker_data.get("id") != "pyblish.avalon.instance":
|
||||
if marker_data.get("id") not in {
|
||||
AYON_INSTANCE_ID, AVALON_INSTANCE_ID
|
||||
}:
|
||||
continue
|
||||
|
||||
self.log.debug("__ segment.name: {}".format(
|
||||
|
|
@ -107,24 +110,25 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
|||
# add ocio_data to instance data
|
||||
inst_data.update(otio_data)
|
||||
|
||||
asset = marker_data["asset"]
|
||||
subset = marker_data["subset"]
|
||||
folder_path = marker_data["folderPath"]
|
||||
folder_name = folder_path.rsplit("/")[-1]
|
||||
product_name = marker_data["productName"]
|
||||
|
||||
# insert family into families
|
||||
family = marker_data["family"]
|
||||
# insert product type into families
|
||||
product_type = marker_data["productType"]
|
||||
families = [str(f) for f in marker_data["families"]]
|
||||
families.insert(0, str(family))
|
||||
families.insert(0, str(product_type))
|
||||
|
||||
# form label
|
||||
label = asset
|
||||
if asset != clip_name:
|
||||
label = folder_name
|
||||
if folder_name != clip_name:
|
||||
label += " ({})".format(clip_name)
|
||||
label += " {} [{}]".format(subset, ", ".join(families))
|
||||
label += " {} [{}]".format(product_name, ", ".join(families))
|
||||
|
||||
inst_data.update({
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"name": "{}_{}".format(folder_name, product_name),
|
||||
"label": label,
|
||||
"asset": asset,
|
||||
"folderPath": folder_path,
|
||||
"item": segment,
|
||||
"families": families,
|
||||
"publish": marker_data["publish"],
|
||||
|
|
@ -332,26 +336,28 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
|||
if not hierarchy_data:
|
||||
return
|
||||
|
||||
asset = data["asset"]
|
||||
subset = "shotMain"
|
||||
folder_path = data["folderPath"]
|
||||
folder_name = folder_path.rsplit("/")[-1]
|
||||
product_name = "shotMain"
|
||||
|
||||
# insert family into families
|
||||
family = "shot"
|
||||
# insert product type into families
|
||||
product_type = "shot"
|
||||
|
||||
# form label
|
||||
label = asset
|
||||
if asset != clip_name:
|
||||
label = folder_name
|
||||
if folder_name != clip_name:
|
||||
label += " ({}) ".format(clip_name)
|
||||
label += " {}".format(subset)
|
||||
label += " [{}]".format(family)
|
||||
label += " {}".format(product_name)
|
||||
label += " [{}]".format(product_type)
|
||||
|
||||
data.update({
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"name": "{}_{}".format(folder_name, product_name),
|
||||
"label": label,
|
||||
"subset": subset,
|
||||
"asset": asset,
|
||||
"family": family,
|
||||
"families": []
|
||||
"productName": product_name,
|
||||
"folderPath": folder_path,
|
||||
"productType": product_type,
|
||||
"family": product_type,
|
||||
"families": [product_type]
|
||||
})
|
||||
|
||||
instance = context.create_instance(**data)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import pyblish.api
|
|||
from ayon_core.client import get_asset_name_identifier
|
||||
import ayon_core.hosts.flame.api as opfapi
|
||||
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):
|
||||
|
|
@ -14,7 +14,7 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin):
|
|||
|
||||
def process(self, context):
|
||||
# plugin defined
|
||||
family = "workfile"
|
||||
product_type = "workfile"
|
||||
variant = "otioTimeline"
|
||||
|
||||
# main
|
||||
|
|
@ -23,29 +23,30 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin):
|
|||
project = opfapi.get_current_project()
|
||||
sequence = opfapi.get_current_sequence(opfapi.CTX.selection)
|
||||
|
||||
# create subset name
|
||||
subset_name = get_subset_name(
|
||||
family,
|
||||
variant,
|
||||
task_name,
|
||||
asset_doc,
|
||||
# create product name
|
||||
product_name = get_product_name(
|
||||
context.data["projectName"],
|
||||
asset_doc,
|
||||
task_name,
|
||||
context.data["hostName"],
|
||||
product_type,
|
||||
variant,
|
||||
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
|
||||
with opfapi.maintained_segment_selection(sequence) as selected_seg:
|
||||
otio_timeline = flame_export.create_otio_timeline(sequence)
|
||||
|
||||
instance_data = {
|
||||
"name": subset_name,
|
||||
"asset": asset_name,
|
||||
"subset": subset_name,
|
||||
"family": "workfile",
|
||||
"families": []
|
||||
"name": product_name,
|
||||
"folderPath": folder_path,
|
||||
"productName": product_name,
|
||||
"productType": product_type,
|
||||
"family": product_type,
|
||||
"families": [product_type]
|
||||
}
|
||||
|
||||
# create instance with workfile
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class ExtractProductResources(publish.Extractor):
|
|||
|
||||
# flame objects
|
||||
segment = instance.data["item"]
|
||||
asset_name = instance.data["asset"]
|
||||
folder_path = instance.data["folderPath"]
|
||||
segment_name = segment.name.get_value()
|
||||
clip_path = instance.data["path"]
|
||||
sequence_clip = instance.context.data["flameSequence"]
|
||||
|
|
@ -249,7 +249,7 @@ class ExtractProductResources(publish.Extractor):
|
|||
out_mark = in_mark + source_duration_handles
|
||||
exporting_clip = self.import_clip(clip_path)
|
||||
exporting_clip.name.set_value("{}_{}".format(
|
||||
asset_name, segment_name))
|
||||
folder_path, segment_name))
|
||||
|
||||
# add xml tags modifications
|
||||
modify_xml_data.update({
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ class IntegrateBatchGroup(pyblish.api.InstancePlugin):
|
|||
))
|
||||
|
||||
# load plate to batch group
|
||||
self.log.info("Loading subset `{}` into batch `{}`".format(
|
||||
instance.data["subset"], bgroup.name.get_value()
|
||||
self.log.info("Loading product `{}` into batch `{}`".format(
|
||||
instance.data["productName"], bgroup.name.get_value()
|
||||
))
|
||||
self._load_clip_to_context(instance, bgroup)
|
||||
|
||||
|
|
@ -168,10 +168,10 @@ class IntegrateBatchGroup(pyblish.api.InstancePlugin):
|
|||
handle_start = instance.data["handleStart"]
|
||||
handle_end = instance.data["handleEnd"]
|
||||
frame_duration = (frame_end - frame_start) + 1
|
||||
asset_name = instance.data["asset"]
|
||||
folder_path = instance.data["folderPath"]
|
||||
|
||||
task_name = task_data["name"]
|
||||
batchgroup_name = "{}_{}".format(asset_name, task_name)
|
||||
batchgroup_name = "{}_{}".format(folder_path, task_name)
|
||||
|
||||
batch_data = {
|
||||
"shematic_reels": [
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ from ayon_core.lib import (
|
|||
)
|
||||
from ayon_core.pipeline import (
|
||||
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
|
||||
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)
|
||||
|
||||
instance = CreatedInstance(
|
||||
family=self.family,
|
||||
subset_name=subset_name,
|
||||
product_type=self.product_type,
|
||||
product_name=product_name,
|
||||
data=instance_data,
|
||||
creator=self,
|
||||
)
|
||||
|
|
@ -109,23 +113,23 @@ class GenericCreateSaver(Creator):
|
|||
tool.SetData(f"openpype.{key}", value)
|
||||
|
||||
def _update_tool_with_data(self, tool, data):
|
||||
"""Update tool node name and output path based on subset data"""
|
||||
if "subset" not in data:
|
||||
"""Update tool node name and output path based on product data"""
|
||||
if "productName" not in data:
|
||||
return
|
||||
|
||||
original_subset = tool.GetData("openpype.subset")
|
||||
original_product_name = tool.GetData("openpype.productName")
|
||||
original_format = tool.GetData(
|
||||
"openpype.creator_attributes.image_format"
|
||||
)
|
||||
|
||||
subset = data["subset"]
|
||||
product_name = data["productName"]
|
||||
if (
|
||||
original_subset != subset
|
||||
original_product_name != product_name
|
||||
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)
|
||||
|
||||
# get frame padding from anatomy templates
|
||||
|
|
@ -135,25 +139,39 @@ class GenericCreateSaver(Creator):
|
|||
ext = data["creator_attributes"]["image_format"]
|
||||
|
||||
# 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"))
|
||||
formatting_data.update({
|
||||
"workdir": workdir,
|
||||
"frame": "0" * frame_padding,
|
||||
"ext": ext,
|
||||
"product": {
|
||||
"name": formatting_data["subset"],
|
||||
"type": formatting_data["family"],
|
||||
"name": f_product_name,
|
||||
"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
|
||||
# TODO make sure the keys are available in 'formatting_data'
|
||||
temp_rendering_path_template = (
|
||||
self.temp_rendering_path_template
|
||||
.replace("{product[name]}", "{subset}")
|
||||
.replace("{product[type]}", "{family}")
|
||||
.replace("{folder[name]}", "{asset}")
|
||||
.replace("{task[name]}", "{task}")
|
||||
.replace("{task}", "{task[name]}")
|
||||
)
|
||||
|
||||
filepath = temp_rendering_path_template.format(**formatting_data)
|
||||
|
|
@ -162,9 +180,9 @@ class GenericCreateSaver(Creator):
|
|||
tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath))
|
||||
|
||||
# Rename tool
|
||||
if tool.Name != subset:
|
||||
print(f"Renaming {tool.Name} -> {subset}")
|
||||
tool.SetAttrs({"TOOLS_Name": subset})
|
||||
if tool.Name != product_name:
|
||||
print(f"Renaming {tool.Name} -> {product_name}")
|
||||
tool.SetAttrs({"TOOLS_Name": product_name})
|
||||
|
||||
def get_managed_tool_data(self, tool):
|
||||
"""Return data of the tool if it matches creator identifier"""
|
||||
|
|
@ -172,13 +190,13 @@ class GenericCreateSaver(Creator):
|
|||
if not isinstance(data, dict):
|
||||
return
|
||||
|
||||
required = {
|
||||
"id": "pyblish.avalon.instance",
|
||||
"creator_identifier": self.identifier,
|
||||
}
|
||||
for key, value in required.items():
|
||||
if key not in data or data[key] != value:
|
||||
return
|
||||
if (
|
||||
data.get("creator_identifier") != self.identifier
|
||||
or data.get("id") not in {
|
||||
AYON_INSTANCE_ID, AVALON_INSTANCE_ID
|
||||
}
|
||||
):
|
||||
return
|
||||
|
||||
# Get active state from the actual tool state
|
||||
attrs = tool.GetAttrs()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class CreateImageSaver(GenericCreateSaver):
|
|||
identifier = "io.openpype.creators.fusion.imagesaver"
|
||||
label = "Image (saver)"
|
||||
name = "image"
|
||||
family = "image"
|
||||
product_type = "image"
|
||||
description = "Fusion Saver to generate image"
|
||||
|
||||
default_frame = 0
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class CreateSaver(GenericCreateSaver):
|
|||
identifier = "io.openpype.creators.fusion.saver"
|
||||
label = "Render (saver)"
|
||||
name = "render"
|
||||
family = "render"
|
||||
product_type = "render"
|
||||
description = "Fusion Saver to generate image sequence"
|
||||
|
||||
default_frame_range_option = "asset_db"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from ayon_core.pipeline import (
|
|||
|
||||
class FusionWorkfileCreator(AutoCreator):
|
||||
identifier = "workfile"
|
||||
family = "workfile"
|
||||
product_type = "workfile"
|
||||
label = "Workfile"
|
||||
icon = "fa5.file"
|
||||
|
||||
|
|
@ -27,9 +27,12 @@ class FusionWorkfileCreator(AutoCreator):
|
|||
if not data:
|
||||
return
|
||||
|
||||
product_name = data.get("productName")
|
||||
if product_name is None:
|
||||
product_name = data["subset"]
|
||||
instance = CreatedInstance(
|
||||
family=self.family,
|
||||
subset_name=data["subset"],
|
||||
product_type=self.product_type,
|
||||
product_name=product_name,
|
||||
data=data,
|
||||
creator=self
|
||||
)
|
||||
|
|
@ -59,7 +62,7 @@ class FusionWorkfileCreator(AutoCreator):
|
|||
|
||||
existing_instance = None
|
||||
for instance in self.create_context.instances:
|
||||
if instance.family == self.family:
|
||||
if instance.product_type == self.product_type:
|
||||
existing_instance = instance
|
||||
break
|
||||
|
||||
|
|
@ -75,9 +78,12 @@ class FusionWorkfileCreator(AutoCreator):
|
|||
|
||||
if existing_instance is None:
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
product_name = self.get_product_name(
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
self.default_variant,
|
||||
host_name,
|
||||
)
|
||||
data = {
|
||||
"folderPath": asset_name,
|
||||
|
|
@ -85,12 +91,17 @@ class FusionWorkfileCreator(AutoCreator):
|
|||
"variant": self.default_variant,
|
||||
}
|
||||
data.update(self.get_dynamic_data(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name, None
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
self.default_variant,
|
||||
host_name,
|
||||
None
|
||||
|
||||
))
|
||||
|
||||
new_instance = CreatedInstance(
|
||||
self.family, subset_name, data, self
|
||||
self.product_type, product_name, data, self
|
||||
)
|
||||
new_instance.transient_data["comp"] = comp
|
||||
self._add_instance_to_context(new_instance)
|
||||
|
|
@ -100,10 +111,13 @@ class FusionWorkfileCreator(AutoCreator):
|
|||
or existing_instance["task"] != task_name
|
||||
):
|
||||
asset_doc = get_asset_by_name(project_name, asset_name)
|
||||
subset_name = self.get_subset_name(
|
||||
self.default_variant, task_name, asset_doc,
|
||||
project_name, host_name
|
||||
product_name = self.get_product_name(
|
||||
project_name,
|
||||
asset_doc,
|
||||
task_name,
|
||||
self.default_variant,
|
||||
host_name,
|
||||
)
|
||||
existing_instance["folderPath"] = asset_name
|
||||
existing_instance["task"] = task_name
|
||||
existing_instance["subset"] = subset_name
|
||||
existing_instance["productName"] = product_name
|
||||
|
|
|
|||
|
|
@ -44,23 +44,24 @@ class FusionLoadAlembicMesh(load.LoaderPlugin):
|
|||
context=context,
|
||||
loader=self.__class__.__name__)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
def switch(self, container, context):
|
||||
self.update(container, context)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
"""Update Alembic path"""
|
||||
|
||||
tool = container["_tool"]
|
||||
assert tool.ID == self.tool_type, f"Must be {self.tool_type}"
|
||||
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"):
|
||||
tool["Filename"] = path
|
||||
|
||||
# Update the imprinted representation
|
||||
tool.SetData("avalon.representation", str(representation["_id"]))
|
||||
tool.SetData("avalon.representation", str(repre_doc["_id"]))
|
||||
|
||||
def remove(self, container):
|
||||
tool = container["_tool"]
|
||||
|
|
|
|||
|
|
@ -59,23 +59,24 @@ class FusionLoadFBXMesh(load.LoaderPlugin):
|
|||
loader=self.__class__.__name__,
|
||||
)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
def switch(self, container, context):
|
||||
self.update(container, context)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
"""Update path"""
|
||||
|
||||
tool = container["_tool"]
|
||||
assert tool.ID == self.tool_type, f"Must be {self.tool_type}"
|
||||
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"):
|
||||
tool["ImportFile"] = path
|
||||
|
||||
# Update the imprinted representation
|
||||
tool.SetData("avalon.representation", str(representation["_id"]))
|
||||
tool.SetData("avalon.representation", str(repre_doc["_id"]))
|
||||
|
||||
def remove(self, container):
|
||||
tool = container["_tool"]
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ class FusionLoadSequence(load.LoaderPlugin):
|
|||
"render",
|
||||
"plate",
|
||||
"image",
|
||||
"onilne",
|
||||
"online",
|
||||
]
|
||||
representations = ["*"]
|
||||
extensions = set(
|
||||
|
|
@ -175,10 +175,10 @@ class FusionLoadSequence(load.LoaderPlugin):
|
|||
loader=self.__class__.__name__,
|
||||
)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
def switch(self, container, context):
|
||||
self.update(container, context)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
"""Update the Loader's path
|
||||
|
||||
Fusion automatically tries to reset some variables when changing
|
||||
|
|
@ -224,7 +224,8 @@ class FusionLoadSequence(load.LoaderPlugin):
|
|||
assert tool.ID == "Loader", "Must be Loader"
|
||||
comp = tool.Comp()
|
||||
|
||||
context = get_representation_context(representation)
|
||||
repre_doc = context["representation"]
|
||||
context = get_representation_context(repre_doc)
|
||||
path = self.filepath_from_context(context)
|
||||
|
||||
# Get start frame from version data
|
||||
|
|
@ -255,7 +256,7 @@ class FusionLoadSequence(load.LoaderPlugin):
|
|||
)
|
||||
|
||||
# Update the imprinted representation
|
||||
tool.SetData("avalon.representation", str(representation["_id"]))
|
||||
tool.SetData("avalon.representation", str(repre_doc["_id"]))
|
||||
|
||||
def remove(self, container):
|
||||
tool = container["_tool"]
|
||||
|
|
|
|||
|
|
@ -28,9 +28,8 @@ class FusionLoadUSD(load.LoaderPlugin):
|
|||
tool_type = "uLoader"
|
||||
|
||||
@classmethod
|
||||
def apply_settings(cls, project_settings, system_settings):
|
||||
super(FusionLoadUSD, cls).apply_settings(project_settings,
|
||||
system_settings)
|
||||
def apply_settings(cls, project_settings):
|
||||
super(FusionLoadUSD, cls).apply_settings(project_settings)
|
||||
if cls.enabled:
|
||||
# Enable only in Fusion 18.5+
|
||||
fusion = get_fusion_module()
|
||||
|
|
@ -61,22 +60,23 @@ class FusionLoadUSD(load.LoaderPlugin):
|
|||
context=context,
|
||||
loader=self.__class__.__name__)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
def switch(self, container, context):
|
||||
self.update(container, context)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
|
||||
tool = container["_tool"]
|
||||
assert tool.ID == self.tool_type, f"Must be {self.tool_type}"
|
||||
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"):
|
||||
tool["Filename"] = path
|
||||
|
||||
# Update the imprinted representation
|
||||
tool.SetData("avalon.representation", str(representation["_id"]))
|
||||
tool.SetData("avalon.representation", str(repre_doc["_id"]))
|
||||
|
||||
def remove(self, container):
|
||||
tool = container["_tool"]
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
|
|||
instance.data["frame_range_source"] = frame_range_source
|
||||
|
||||
# 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"]
|
||||
end = context.data["frameEnd"]
|
||||
handle_start = context.data["handleStart"]
|
||||
|
|
@ -34,7 +34,7 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
|
|||
start_with_handle = start - handle_start
|
||||
end_with_handle = end + handle_end
|
||||
|
||||
# conditions for render family instances
|
||||
# conditions for render product type instances
|
||||
if frame_range_source == "render_range":
|
||||
# set comp render frame ranges
|
||||
start = context.data["renderFrameStart"]
|
||||
|
|
@ -70,11 +70,11 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
|
|||
end_with_handle = frame
|
||||
|
||||
# Include start and end render frame in label
|
||||
subset = instance.data["subset"]
|
||||
product_name = instance.data["productName"]
|
||||
label = (
|
||||
"{subset} ({start}-{end}) [{handle_start}-{handle_end}]"
|
||||
"{product_name} ({start}-{end}) [{handle_start}-{handle_end}]"
|
||||
).format(
|
||||
subset=subset,
|
||||
product_name=product_name,
|
||||
start=int(start),
|
||||
end=int(end),
|
||||
handle_start=int(handle_start),
|
||||
|
|
|
|||
|
|
@ -49,31 +49,32 @@ class CollectFusionRender(
|
|||
if not inst.data.get("active", True):
|
||||
continue
|
||||
|
||||
family = inst.data["family"]
|
||||
if family not in ["render", "image"]:
|
||||
product_type = inst.data["productType"]
|
||||
if product_type not in ["render", "image"]:
|
||||
continue
|
||||
|
||||
task_name = context.data["task"]
|
||||
tool = inst.data["transientData"]["tool"]
|
||||
|
||||
instance_families = inst.data.get("families", [])
|
||||
subset_name = inst.data["subset"]
|
||||
product_name = inst.data["productName"]
|
||||
instance = FusionRenderInstance(
|
||||
family=family,
|
||||
tool=tool,
|
||||
workfileComp=comp,
|
||||
productType=product_type,
|
||||
family=product_type,
|
||||
families=instance_families,
|
||||
version=version,
|
||||
time="",
|
||||
source=current_file,
|
||||
label=inst.data["label"],
|
||||
subset=subset_name,
|
||||
asset=inst.data["asset"],
|
||||
productName=product_name,
|
||||
folderPath=inst.data["folderPath"],
|
||||
task=task_name,
|
||||
attachTo=False,
|
||||
setMembers='',
|
||||
publish=True,
|
||||
name=subset_name,
|
||||
name=product_name,
|
||||
resolutionWidth=comp_frame_format_prefs.get("Width"),
|
||||
resolutionHeight=comp_frame_format_prefs.get("Height"),
|
||||
pixelAspect=aspect_x / aspect_y,
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class FusionRenderLocal(
|
|||
self.log.info(
|
||||
"Rendered '{nm}' for asset '{ast}' under the task '{tsk}'".format(
|
||||
nm=instance.data["name"],
|
||||
ast=instance.data["asset"],
|
||||
ast=instance.data["folderPath"],
|
||||
tsk=instance.data["task"],
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from ayon_core.hosts.fusion.api.action import SelectInvalidAction
|
|||
|
||||
|
||||
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
|
||||
label = "Validate Unique Subsets"
|
||||
|
|
@ -18,25 +18,31 @@ class ValidateUniqueSubsets(pyblish.api.ContextPlugin):
|
|||
@classmethod
|
||||
def get_invalid(cls, context):
|
||||
|
||||
# Collect instances per subset per asset
|
||||
instances_per_subset_asset = defaultdict(lambda: defaultdict(list))
|
||||
# Collect instances per product per folder
|
||||
instances_per_product_folder = defaultdict(lambda: defaultdict(list))
|
||||
for instance in context:
|
||||
asset = instance.data.get("asset", context.data.get("asset"))
|
||||
subset = instance.data.get("subset", context.data.get("subset"))
|
||||
instances_per_subset_asset[asset][subset].append(instance)
|
||||
folder_path = instance.data["folderPath"]
|
||||
product_name = instance.data["productName"]
|
||||
instances_per_product_folder[folder_path][product_name].append(
|
||||
instance
|
||||
)
|
||||
|
||||
# Find which asset + subset combination has more than one instance
|
||||
# Those are considered invalid because they'd integrate to the same
|
||||
# destination.
|
||||
invalid = []
|
||||
for asset, instances_per_subset in instances_per_subset_asset.items():
|
||||
for subset, instances in instances_per_subset.items():
|
||||
for folder_path, instances_per_product in (
|
||||
instances_per_product_folder.items()
|
||||
):
|
||||
for product_name, instances in instances_per_product.items():
|
||||
if len(instances) > 1:
|
||||
cls.log.warning(
|
||||
"{asset} > {subset} used by more than "
|
||||
"one instance: {instances}".format(
|
||||
asset=asset,
|
||||
subset=subset,
|
||||
(
|
||||
"{folder_path} > {product_name} used by more than "
|
||||
"one instance: {instances}"
|
||||
).format(
|
||||
folder_path=folder_path,
|
||||
product_name=product_name,
|
||||
instances=instances
|
||||
)
|
||||
)
|
||||
|
|
@ -50,6 +56,7 @@ class ValidateUniqueSubsets(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
invalid = self.get_invalid(context)
|
||||
if invalid:
|
||||
raise PublishValidationError("Multiple instances are set to "
|
||||
"the same asset > subset.",
|
||||
title=self.label)
|
||||
raise PublishValidationError(
|
||||
"Multiple instances are set to the same folder > product.",
|
||||
title=self.label
|
||||
)
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ class CreateComposite(harmony.Creator):
|
|||
|
||||
name = "compositeDefault"
|
||||
label = "Composite"
|
||||
family = "mindbender.template"
|
||||
product_type = "mindbender.template"
|
||||
|
||||
def __init__(self, *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:
|
||||
```python
|
||||
from uuid import uuid4
|
||||
import ayon_core.hosts.harmony.api as harmony
|
||||
|
||||
|
||||
|
|
@ -220,7 +221,7 @@ class CreateRender(harmony.Creator):
|
|||
|
||||
name = "writeDefault"
|
||||
label = "Write"
|
||||
family = "mindbender.imagesequence"
|
||||
product_type = "mindbender.imagesequence"
|
||||
node_type = "WRITE"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -242,6 +243,7 @@ class CreateRender(harmony.Creator):
|
|||
#### Collector Plugin
|
||||
```python
|
||||
import pyblish.api
|
||||
from ayon_core.pipeline import AYON_INSTANCE_ID, AVALON_INSTANCE_ID
|
||||
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;
|
||||
|
||||
Identifier:
|
||||
id (str): "pyblish.avalon.instance"
|
||||
id (str): "ayon.create.instance"
|
||||
"""
|
||||
|
||||
label = "Instances"
|
||||
|
|
@ -272,7 +274,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
continue
|
||||
|
||||
# Skip containers.
|
||||
if "container" in data["id"]:
|
||||
if data["id"] not in {AYON_INSTANCE_ID, AVALON_INSTANCE_ID}:
|
||||
continue
|
||||
|
||||
instance = context.create_instance(node.split("/")[-1])
|
||||
|
|
@ -287,6 +289,7 @@ class CollectInstances(pyblish.api.ContextPlugin):
|
|||
#### Extractor Plugin
|
||||
```python
|
||||
import os
|
||||
from uuid import uuid4
|
||||
|
||||
import pyblish.api
|
||||
import ayon_core.hosts.harmony.api as harmony
|
||||
|
|
@ -418,6 +421,7 @@ class ExtractImage(pyblish.api.InstancePlugin):
|
|||
#### Loader Plugin
|
||||
```python
|
||||
import os
|
||||
from uuid import uuid4
|
||||
|
||||
import ayon_core.hosts.harmony.api as harmony
|
||||
|
||||
|
|
@ -607,11 +611,12 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
self.__class__.__name__
|
||||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
node = container.pop("node")
|
||||
|
||||
repre_doc = context["representation"]
|
||||
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 = []
|
||||
for f in version["data"]["files"]:
|
||||
files.append(
|
||||
|
|
@ -628,7 +633,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
)
|
||||
|
||||
harmony.imprint(
|
||||
node, {"representation": str(representation["_id"])}
|
||||
node, {"representation": str(repre_doc["_id"])}
|
||||
)
|
||||
|
||||
def remove(self, container):
|
||||
|
|
@ -644,8 +649,8 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
{"function": func, "args": [node]}
|
||||
)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
def switch(self, container, context):
|
||||
self.update(container, context)
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ ImageSequenceLoader.getUniqueColumnName = function(columnPrefix) {
|
|||
* var args = [
|
||||
* files, // Files in file sequences.
|
||||
* asset, // Asset name.
|
||||
* subset, // Subset name.
|
||||
* productName, // Product name.
|
||||
* startFrame, // Sequence starting frame.
|
||||
* groupId // Unique group ID (uuid4).
|
||||
* ];
|
||||
|
|
@ -106,7 +106,7 @@ ImageSequenceLoader.prototype.importFiles = function(args) {
|
|||
var doc = $.scn;
|
||||
var files = args[0];
|
||||
var asset = args[1];
|
||||
var subset = args[2];
|
||||
var productName = args[2];
|
||||
var startFrame = args[3];
|
||||
var groupId = args[4];
|
||||
var vectorFormat = null;
|
||||
|
|
@ -124,7 +124,7 @@ ImageSequenceLoader.prototype.importFiles = function(args) {
|
|||
var num = 0;
|
||||
var name = '';
|
||||
do {
|
||||
name = asset + '_' + (num++) + '_' + subset;
|
||||
name = asset + '_' + (num++) + '_' + productName;
|
||||
} while (currentGroup.getNodeByName(name) != null);
|
||||
|
||||
extension = filename.substr(pos+1).toLowerCase();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ var TemplateLoader = function() {};
|
|||
* var args = [
|
||||
* templatePath, // Path to tpl file.
|
||||
* assetName, // Asset name.
|
||||
* subsetName, // Subset name.
|
||||
* productName, // Product name.
|
||||
* groupId // unique ID (uuid4)
|
||||
* ];
|
||||
*/
|
||||
|
|
@ -39,7 +39,7 @@ TemplateLoader.prototype.loadContainer = function(args) {
|
|||
var doc = $.scn;
|
||||
var templatePath = args[0];
|
||||
var assetName = args[1];
|
||||
var subset = args[2];
|
||||
var productName = args[2];
|
||||
var groupId = args[3];
|
||||
|
||||
// Get the current group
|
||||
|
|
@ -62,7 +62,7 @@ TemplateLoader.prototype.loadContainer = function(args) {
|
|||
var num = 0;
|
||||
var containerGroupName = '';
|
||||
do {
|
||||
containerGroupName = assetName + '_' + (num++) + '_' + subset;
|
||||
containerGroupName = assetName + '_' + (num++) + '_' + productName;
|
||||
} while (currentGroup.getNodeByName(containerGroupName) != null);
|
||||
|
||||
// import the template
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class CreateFarmRender(plugin.Creator):
|
|||
|
||||
name = "renderDefault"
|
||||
label = "Render on Farm"
|
||||
family = "renderFarm"
|
||||
product_type = "renderFarm"
|
||||
node_type = "WRITE"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class CreateRender(plugin.Creator):
|
|||
|
||||
name = "renderDefault"
|
||||
label = "Render"
|
||||
family = "render"
|
||||
product_type = "render"
|
||||
node_type = "WRITE"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ class CreateTemplate(plugin.Creator):
|
|||
|
||||
name = "templateDefault"
|
||||
label = "Template"
|
||||
family = "harmony.template"
|
||||
product_type = "harmony.template"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateTemplate, self).__init__(*args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -45,17 +45,17 @@ class ImportAudioLoader(load.LoaderPlugin):
|
|||
{"function": func, "args": [context["subset"]["name"], wav_file]}
|
||||
)
|
||||
|
||||
subset_name = context["subset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
return harmony.containerise(
|
||||
subset_name,
|
||||
product_name,
|
||||
namespace,
|
||||
subset_name,
|
||||
product_name,
|
||||
context,
|
||||
self.__class__.__name__
|
||||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
pass
|
||||
|
||||
def remove(self, container):
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ class BackgroundLoader(load.LoaderPlugin):
|
|||
|
||||
bg_folder = os.path.dirname(path)
|
||||
|
||||
subset_name = context["subset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
# read_node_name += "_{}".format(uuid.uuid4())
|
||||
container_nodes = []
|
||||
|
||||
|
|
@ -272,16 +272,17 @@ class BackgroundLoader(load.LoaderPlugin):
|
|||
container_nodes.append(read_node)
|
||||
|
||||
return harmony.containerise(
|
||||
subset_name,
|
||||
product_name,
|
||||
namespace,
|
||||
subset_name,
|
||||
product_name,
|
||||
context,
|
||||
self.__class__.__name__,
|
||||
nodes=container_nodes
|
||||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
path = get_representation_path(representation)
|
||||
def update(self, container, context):
|
||||
repre_doc = context["representation"]
|
||||
path = get_representation_path(repre_doc)
|
||||
with open(path) as json_file:
|
||||
data = json.load(json_file)
|
||||
|
||||
|
|
@ -301,7 +302,7 @@ class BackgroundLoader(load.LoaderPlugin):
|
|||
|
||||
print(container)
|
||||
|
||||
is_latest = is_representation_from_latest(representation)
|
||||
is_latest = is_representation_from_latest(repre_doc)
|
||||
for layer in sorted(layers):
|
||||
file_to_import = [
|
||||
os.path.join(bg_folder, layer).replace("\\", "/")
|
||||
|
|
@ -351,8 +352,11 @@ class BackgroundLoader(load.LoaderPlugin):
|
|||
harmony.send({"function": func, "args": [node, "red"]})
|
||||
|
||||
harmony.imprint(
|
||||
container['name'], {"representation": str(representation["_id"]),
|
||||
"nodes": container['nodes']}
|
||||
container['name'],
|
||||
{
|
||||
"representation": str(repre_doc["_id"]),
|
||||
"nodes": container["nodes"]
|
||||
}
|
||||
)
|
||||
|
||||
def remove(self, container):
|
||||
|
|
@ -369,5 +373,5 @@ class BackgroundLoader(load.LoaderPlugin):
|
|||
)
|
||||
harmony.imprint(container['name'], {}, remove=True)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
def switch(self, container, context):
|
||||
self.update(container, context)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
files.append(fname.parent.joinpath(remainder[0]).as_posix())
|
||||
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
group_id = str(uuid.uuid4())
|
||||
read_node = harmony.send(
|
||||
|
|
@ -56,7 +56,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
"args": [
|
||||
files,
|
||||
asset,
|
||||
subset,
|
||||
product_name,
|
||||
1,
|
||||
group_id
|
||||
]
|
||||
|
|
@ -64,7 +64,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
)["result"]
|
||||
|
||||
return harmony.containerise(
|
||||
f"{asset}_{subset}",
|
||||
f"{asset}_{product_name}",
|
||||
namespace,
|
||||
read_node,
|
||||
context,
|
||||
|
|
@ -72,18 +72,19 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
nodes=[read_node]
|
||||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
"""Update loaded containers.
|
||||
|
||||
Args:
|
||||
container (dict): Container data.
|
||||
representation (dict): Representation data.
|
||||
context (dict): Representation context data.
|
||||
|
||||
"""
|
||||
self_name = self.__class__.__name__
|
||||
node = container.get("nodes").pop()
|
||||
|
||||
path = get_representation_path(representation)
|
||||
repre_doc = context["representation"]
|
||||
path = get_representation_path(repre_doc)
|
||||
collections, remainder = clique.assemble(
|
||||
os.listdir(os.path.dirname(path))
|
||||
)
|
||||
|
|
@ -110,7 +111,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
)
|
||||
|
||||
# Colour node.
|
||||
if is_representation_from_latest(representation):
|
||||
if is_representation_from_latest(repre_doc):
|
||||
harmony.send(
|
||||
{
|
||||
"function": "PypeHarmony.setColor",
|
||||
|
|
@ -124,7 +125,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
})
|
||||
|
||||
harmony.imprint(
|
||||
node, {"representation": str(representation["_id"])}
|
||||
node, {"representation": str(repre_doc["_id"])}
|
||||
)
|
||||
|
||||
def remove(self, container):
|
||||
|
|
@ -140,6 +141,6 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
)
|
||||
harmony.imprint(node, {}, remove=True)
|
||||
|
||||
def switch(self, container, representation):
|
||||
def switch(self, container, context):
|
||||
"""Switch loaded representations."""
|
||||
self.update(container, representation)
|
||||
self.update(container, context)
|
||||
|
|
|
|||
|
|
@ -26,15 +26,17 @@ class ImportPaletteLoader(load.LoaderPlugin):
|
|||
self.__class__.__name__
|
||||
)
|
||||
|
||||
def load_palette(self, representation):
|
||||
subset_name = representation["context"]["subset"]
|
||||
name = subset_name.replace("palette", "")
|
||||
def load_palette(self, context):
|
||||
subset_doc = context["subset"]
|
||||
repre_doc = context["representation"]
|
||||
product_name = subset_doc["name"]
|
||||
name = product_name.replace("palette", "")
|
||||
|
||||
# Overwrite palette on disk.
|
||||
scene_path = harmony.send(
|
||||
{"function": "scene.currentProjectPath"}
|
||||
)["result"]
|
||||
src = get_representation_path(representation)
|
||||
src = get_representation_path(repre_doc)
|
||||
dst = os.path.join(
|
||||
scene_path,
|
||||
"palette-library",
|
||||
|
|
@ -44,7 +46,7 @@ class ImportPaletteLoader(load.LoaderPlugin):
|
|||
|
||||
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 += "Please save workfile when ready and use Workfiles "
|
||||
msg += "to reopen it."
|
||||
|
|
@ -59,13 +61,14 @@ class ImportPaletteLoader(load.LoaderPlugin):
|
|||
def remove(self, container):
|
||||
harmony.remove(container["name"])
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
def switch(self, container, context):
|
||||
self.update(container, context)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
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
|
||||
harmony.imprint(name, container)
|
||||
|
|
|
|||
|
|
@ -70,19 +70,20 @@ class TemplateLoader(load.LoaderPlugin):
|
|||
self_name
|
||||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
"""Update loaded containers.
|
||||
|
||||
Args:
|
||||
container (dict): Container data.
|
||||
representation (dict): Representation data.
|
||||
context (dict): Representation context data.
|
||||
|
||||
"""
|
||||
node_name = container["name"]
|
||||
node = harmony.find_node_by_name(node_name, "GROUP")
|
||||
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)
|
||||
else:
|
||||
self._set_red(node)
|
||||
|
|
@ -110,7 +111,7 @@ class TemplateLoader(load.LoaderPlugin):
|
|||
None, container["data"])
|
||||
|
||||
harmony.imprint(
|
||||
node, {"representation": str(representation["_id"])}
|
||||
node, {"representation": str(repre_doc["_id"])}
|
||||
)
|
||||
|
||||
def remove(self, container):
|
||||
|
|
@ -125,9 +126,9 @@ class TemplateLoader(load.LoaderPlugin):
|
|||
{"function": "PypeHarmony.deleteNode", "args": [node]}
|
||||
)
|
||||
|
||||
def switch(self, container, representation):
|
||||
def switch(self, container, context):
|
||||
"""Switch representation containers."""
|
||||
self.update(container, representation)
|
||||
self.update(container, context)
|
||||
|
||||
def _set_green(self, node):
|
||||
"""Set node color to green `rgba(0, 255, 0, 255)`."""
|
||||
|
|
|
|||
|
|
@ -40,17 +40,17 @@ class ImportTemplateLoader(load.LoaderPlugin):
|
|||
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
subset_name = context["subset"]["name"]
|
||||
product_name = context["subset"]["name"]
|
||||
|
||||
return harmony.containerise(
|
||||
subset_name,
|
||||
product_name,
|
||||
namespace,
|
||||
subset_name,
|
||||
product_name,
|
||||
context,
|
||||
self.__class__.__name__
|
||||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
def update(self, container, context):
|
||||
pass
|
||||
|
||||
def remove(self, container):
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ class CollectFarmRender(publish.AbstractCollectRender):
|
|||
for frame in range(start, end + 1):
|
||||
expected_files.append(
|
||||
path / "{}-{}.{}".format(
|
||||
render_instance.subset,
|
||||
render_instance.productName,
|
||||
str(frame).rjust(int(info[2]) + 1, "0"),
|
||||
ext
|
||||
)
|
||||
|
|
@ -89,7 +89,7 @@ class CollectFarmRender(publish.AbstractCollectRender):
|
|||
return expected_files
|
||||
|
||||
def get_instances(self, context):
|
||||
"""Get instances per Write node in `renderFarm` family."""
|
||||
"""Get instances per Write node in `renderFarm` product type."""
|
||||
version = None
|
||||
if self.sync_workfile_version:
|
||||
version = context.data["version"]
|
||||
|
|
@ -98,7 +98,7 @@ class CollectFarmRender(publish.AbstractCollectRender):
|
|||
|
||||
self_name = self.__class__.__name__
|
||||
|
||||
asset_name = context.data["asset"]
|
||||
folder_path = context.data["folderPath"]
|
||||
|
||||
for node in context.data["allNodes"]:
|
||||
data = harmony.read(node)
|
||||
|
|
@ -111,7 +111,10 @@ class CollectFarmRender(publish.AbstractCollectRender):
|
|||
if "container" in data["id"]:
|
||||
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
|
||||
|
||||
# 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: set Deadline stuff (pools, priority, etc. by presets)
|
||||
# because of using 'renderFarm' as a family, replace 'Farm' with
|
||||
# capitalized task name - issue of avalon-core Creator app
|
||||
subset_name = node.split("/")[1]
|
||||
task_name = context.data["anatomyData"]["task"][
|
||||
"name"].capitalize()
|
||||
# because of using 'renderFarm' as a product type, replace 'Farm'
|
||||
# with capitalized task name - issue of Creator tool
|
||||
product_name = node.split("/")[1]
|
||||
task_name = context.data["task"].capitalize()
|
||||
replace_str = ""
|
||||
if task_name.lower() not in subset_name.lower():
|
||||
if task_name.lower() not in product_name.lower():
|
||||
replace_str = task_name
|
||||
subset_name = subset_name.replace(
|
||||
product_name = product_name.replace(
|
||||
'Farm',
|
||||
replace_str)
|
||||
|
||||
|
|
@ -141,8 +143,8 @@ class CollectFarmRender(publish.AbstractCollectRender):
|
|||
time=get_formatted_current_time(),
|
||||
source=context.data["currentFile"],
|
||||
label=node.split("/")[1],
|
||||
subset=subset_name,
|
||||
asset=asset_name,
|
||||
productName=product_name,
|
||||
folderPath=folder_path,
|
||||
task=task_name,
|
||||
attachTo=False,
|
||||
setMembers=[node],
|
||||
|
|
@ -151,6 +153,7 @@ class CollectFarmRender(publish.AbstractCollectRender):
|
|||
priority=50,
|
||||
name=node.split("/")[1],
|
||||
|
||||
productType="render.farm",
|
||||
family="render.farm",
|
||||
families=["render.farm"],
|
||||
farm=True,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue