Merge branch 'develop' into bugfix/OP-8273_Resolve-missing-GetClipProperty

This commit is contained in:
Jakub Ježek 2024-03-12 11:02:41 +01:00 committed by GitHub
commit b3c7d74789
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
551 changed files with 7222 additions and 6765 deletions

View file

@ -1,6 +1,6 @@
name: Bug Report
description: File a bug report
title: 'Bug: '
title: 'Your issue title here'
labels:
- 'type: bug'
body:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,7 +11,7 @@ from ayon_core.pipeline import (
from ayon_core.hosts.aftereffects.api.pipeline import cache_and_get_instances
from ayon_core.hosts.aftereffects.api.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

View file

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

View file

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

View file

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

View file

@ -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,
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,
@ -176,7 +179,7 @@ class CollectAERender(publish.AbstractCollectRender):
if "#" not in file_name: # single frame (mov)W
path = os.path.join(base_dir, "{}_{}_{}.{}".format(
render_instance.folderPath,
render_instance.subset,
render_instance.productName,
version_str,
ext
))
@ -185,7 +188,7 @@ class CollectAERender(publish.AbstractCollectRender):
for frame in range(start, end + 1):
path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format(
render_instance.folderPath,
render_instance.subset,
render_instance.productName,
version_str,
str(frame).zfill(self.padding_width),
ext

View file

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

View file

@ -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"]
folder_path = get_asset_name_identifier(asset_entity)
instance_data = {
"active": True,
"folderPath": folder_path,
"task": task,
"frameStart": context.data['frameStart'],
"frameEnd": context.data['frameEnd'],
"handleStart": context.data['handleStart'],
"handleEnd": context.data['handleEnd'],
"fps": asset_entity["data"]["fps"],
"resolutionWidth": asset_entity["data"].get(
"resolutionWidth",
project_entity["data"]["resolutionWidth"]),
"resolutionHeight": asset_entity["data"].get(
"resolutionHeight",
project_entity["data"]["resolutionHeight"]),
"pixelAspect": 1,
"step": 1,
"version": version
}
# workfile instance
family = "workfile"
subset = get_subset_name(
family,
self.default_variant,
context.data["anatomyData"]["task"]["name"],
context.data["assetEntity"],
context.data["anatomyData"]["project"]["name"],
host_name=context.data["hostName"],
project_settings=context.data["project_settings"]
)
# Create instance
instance = context.create_instance(subset)
# creating instance data
instance.data.update({
"subset": subset,
"label": scene_file,
"family": family,
"families": [family],
"representations": list()
})
instance.data.update(instance_data)
return instance

View file

@ -3,9 +3,9 @@
<error id="main">
<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>

View file

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

View file

@ -30,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.
@ -47,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)
@ -64,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}"
@ -161,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 = {}
@ -210,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.
@ -236,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)
@ -247,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)
@ -263,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(
@ -306,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
@ -341,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.
"""
@ -358,7 +357,7 @@ class BaseCreator(Creator):
{
"id": AVALON_INSTANCE_ID,
"creator_identifier": self.identifier,
"subset": subset_name,
"productName": product_name,
}
)
@ -466,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(
@ -499,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:

View file

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

View file

@ -10,20 +10,20 @@ 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["folderPath"], subset_name
instance_data["folderPath"], product_name
)
if pre_create_data.get("use_selection"):

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,6 +13,9 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin):
families = ["model", "camera", "rig", "action", "layout", "blendScene"]
optional = True
# From settings
compress = False
def process(self, instance):
if not self.is_active(instance.data):
return
@ -20,9 +23,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)
@ -53,7 +56,7 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin):
if node.image and node.image.packed_file is None:
node.image.pack()
bpy.data.libraries.write(filepath, data_blocks)
bpy.data.libraries.write(filepath, data_blocks, compress=self.compress)
if "representations" not in instance.data:
instance.data["representations"] = []

View file

@ -16,6 +16,9 @@ class ExtractBlendAnimation(
families = ["animation"]
optional = True
# From settings
compress = False
def process(self, instance):
if not self.is_active(instance.data):
return
@ -23,9 +26,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)
@ -46,7 +49,7 @@ class ExtractBlendAnimation(
data_blocks.add(child.animation_data.action)
data_blocks.add(obj)
bpy.data.libraries.write(filepath, data_blocks)
bpy.data.libraries.write(filepath, data_blocks, compress=self.compress)
if "representations" not in instance.data:
instance.data["representations"] = []

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,14 +23,14 @@ 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"]
)
@ -41,11 +41,12 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin):
otio_timeline = flame_export.create_otio_timeline(sequence)
instance_data = {
"name": subset_name,
"name": product_name,
"folderPath": folder_path,
"subset": subset_name,
"family": "workfile",
"families": []
"productName": product_name,
"productType": product_type,
"family": product_type,
"families": [product_type]
}
# create instance with workfile

View file

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

View file

@ -33,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,
)
@ -111,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
@ -137,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)
@ -164,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"""

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,27 +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(
"folderPath", context.data.get("folderPath")
folder_path = instance.data["folderPath"]
product_name = instance.data["productName"]
instances_per_product_folder[folder_path][product_name].append(
instance
)
subset = instance.data.get("subset", context.data.get("subset"))
instances_per_subset_asset[asset][subset].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
)
)
@ -52,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
)

View file

@ -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)
@ -221,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):
@ -611,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(
@ -632,7 +633,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
)
harmony.imprint(
node, {"representation": str(representation["_id"])}
node, {"representation": str(repre_doc["_id"])}
)
def remove(self, container):
@ -648,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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"]
@ -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,7 +143,7 @@ class CollectFarmRender(publish.AbstractCollectRender):
time=get_formatted_current_time(),
source=context.data["currentFile"],
label=node.split("/")[1],
subset=subset_name,
productName=product_name,
folderPath=folder_path,
task=task_name,
attachTo=False,
@ -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,

View file

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

View file

@ -33,14 +33,16 @@ class CollectPalettes(pyblish.api.ContextPlugin):
return
folder_path = context.data["folderPath"]
product_type = "harmony.palette"
for name, id in palettes.items():
instance = context.create_instance(name)
instance.data.update({
"id": id,
"family": "harmony.palette",
'families': [],
"productType": product_type,
"family": product_type,
"families": [product_type],
"folderPath": folder_path,
"subset": "{}{}".format("palette", name)
"productName": "{}{}".format("palette", name)
})
self.log.info(
"Created instance:\n" + json.dumps(

View file

@ -3,7 +3,7 @@
import os
import pyblish.api
from ayon_core.pipeline.create import get_subset_name
from ayon_core.pipeline.create import get_product_name
class CollectWorkfile(pyblish.api.ContextPlugin):
@ -15,26 +15,27 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
def process(self, context):
"""Plugin entry point."""
family = "workfile"
product_type = "workfile"
basename = os.path.basename(context.data["currentFile"])
subset = get_subset_name(
family,
"",
context.data["anatomyData"]["task"]["name"],
product_name = get_product_name(
context.data["projectName"],
context.data["assetEntity"],
context.data["anatomyData"]["project"]["name"],
host_name=context.data["hostName"],
context.data["task"],
context.data["hostName"],
product_type,
"",
project_settings=context.data["project_settings"]
)
# Create instance
instance = context.create_instance(subset)
instance = context.create_instance(product_name)
instance.data.update({
"subset": subset,
"productName": product_name,
"label": basename,
"name": basename,
"family": family,
"families": [family],
"productType": product_type,
"family": product_type,
"families": [product_type],
"representations": [],
"folderPath": context.data["folderPath"]
})

View file

@ -75,7 +75,9 @@ class ExtractTemplate(publish.Extractor):
instance.data["representations"] = [representation]
instance.data["version_name"] = "{}_{}".format(
instance.data["subset"], instance.context.data["task"])
instance.data["productName"],
instance.context.data["task"]
)
def get_backdrops(self, node: str) -> list:
"""Get backdrops for the node.

View file

@ -3,9 +3,9 @@
<error id="main">
<title>Subset context</title>
<description>
## Invalid subset context
## Invalid product context
Asset name found '{found}' in subsets, expected '{expected}'.
Asset name found '{found}' in products, expected '{expected}'.
### How to repair?
@ -19,7 +19,7 @@ If this is unwanted, close workfile and open again, that way different asset val
### __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 "renderCompositingDefault" from asset "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>

View file

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

View file

@ -363,7 +363,7 @@ class SequenceLoader(LoaderPlugin):
):
pass
def update(self, container, representation):
def update(self, container, context):
"""Update an existing `container`
"""
pass
@ -439,7 +439,7 @@ class ClipLoader:
""" Gets context and convert it to self.data
data structure:
{
"name": "assetName_subsetName_representationName"
"name": "assetName_productName_representationName"
"path": "path/to/file/created/by/get_repr..",
"binPath": "projectBinPath",
}
@ -448,10 +448,10 @@ class ClipLoader:
repr = self.context["representation"]
repr_cntx = repr["context"]
asset = str(repr_cntx["asset"])
subset = str(repr_cntx["subset"])
product_name = str(repr_cntx["subset"])
representation = str(repr_cntx["representation"])
self.data["clip_name"] = self.clip_name_template.format(**repr_cntx)
self.data["track_name"] = "_".join([subset, representation])
self.data["track_name"] = "_".join([product_name, representation])
self.data["versionData"] = self.context["version"]["data"]
# gets file path
file = get_representation_path_from_context(self.context)
@ -659,9 +659,9 @@ class PublishClip:
rename_default = False
hierarchy_default = "{_folder_}/{_sequence_}/{_track_}"
clip_name_default = "shot_{_trackIndex_:0>3}_{_clipIndex_:0>4}"
subset_name_default = "<track_name>"
base_product_name_default = "<track_name>"
review_track_default = "< none >"
subset_family_default = "plate"
product_type_default = "plate"
count_from_default = 10
count_steps_default = 10
vertical_sync_default = False
@ -785,10 +785,10 @@ class PublishClip:
"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.product_type = self.ui_inputs.get(
"productType", {}).get("value") or self.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(
@ -798,12 +798,14 @@ class PublishClip:
self.audio = self.ui_inputs.get(
"audio", {}).get("value") or False
# 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.product_type + self.base_product_name.capitalize()
)
def _replace_hash_to_expression(self, name, text):
""" Replace hash with number in correct padding. """
@ -885,14 +887,14 @@ class PublishClip:
for (_in, _out), hero_data in self.vertical_clip_match.items():
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
@ -913,9 +915,9 @@ class PublishClip:
"hierarchy": hierarchy_filled,
"parents": self.parents,
"hierarchyData": hierarchy_formatting_data,
"subset": self.subset,
"family": self.subset_family,
"families": [self.data["family"]]
"productName": self.product_name,
"productType": self.product_type,
"families": [self.product_type, self.data["family"]]
}
def _convert_to_entity(self, type, template):

View file

@ -28,8 +28,8 @@ def tag_data():
# "note": "Collecting track items to Nuke scripts.",
# "icon": "icons:TagNuke.png",
# "metadata": {
# "family": "nukescript",
# "subset": "main"
# "productType": "nukescript",
# "productName": "main"
# }
# },
"Comment": {
@ -37,17 +37,17 @@ def tag_data():
"note": "Comment on a shot.",
"icon": "icons:TagComment.png",
"metadata": {
"family": "comment",
"subset": "main"
"productType": "comment",
"productName": "main"
}
},
"FrameMain": {
"editable": "1",
"note": "Publishing a frame subset.",
"note": "Publishing a frame product.",
"icon": "z_layer_main.png",
"metadata": {
"family": "frame",
"subset": "main",
"productType": "frame",
"productName": "main",
"format": "png"
}
}
@ -153,7 +153,7 @@ def add_tags_to_workfile():
"note": task_type,
"icon": "icons:TagGood.png",
"metadata": {
"family": "task",
"productType": "task",
"type": task_type
}
}
@ -173,7 +173,7 @@ def add_tags_to_workfile():
"path": "icons:TagActor.png"
},
"metadata": {
"family": "assetbuild"
"productType": "assetbuild"
}
}

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