From 927fe351a33be543a9d02d11b2924240ac2c380c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 4 Jul 2022 22:43:14 +0200 Subject: [PATCH 001/131] settings: adding editorial family --- .../project_settings/traypublisher.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 0b54cfd39e..e938384282 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -30,6 +30,24 @@ ".psb", ".aep" ] + }, + { + "family": "editorial", + "identifier": "", + "label": "Editorial", + "icon": "fa.file", + "default_variants": [ + "Main" + ], + "description": "Editorial files to generate shots.", + "detailed_description": "Supporting publishing new shots to project or updating already created. Publishing will create OTIO file.", + "allow_sequences": false, + "extensions": [ + ".edl", + ".xml", + ".aaf", + ".fcpxml" + ] } ] } \ No newline at end of file From 49fd9e6308f711ee261293081ac1c5375c669043 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 Jul 2022 15:51:54 +0200 Subject: [PATCH 002/131] editorial tray publisher kick-off --- openpype/hosts/traypublisher/api/plugin.py | 94 ++++++++++++++++++- .../plugins/create/create_editorial.py | 25 +++++ .../plugins/create/create_from_settings.py | 7 +- .../project_settings/traypublisher.json | 38 +++++++- .../schema_project_traypublisher.json | 83 ++++++++++++++++ 5 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 openpype/hosts/traypublisher/plugins/create/create_editorial.py diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 202664cfc6..901f05c755 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -2,7 +2,14 @@ from openpype.pipeline import ( Creator, CreatedInstance ) -from openpype.lib import FileDef +from openpype.lib import ( + FileDef, + TextDef, + NumberDef, + EnumDef, + BoolDef, + FileDefItem +) from .pipeline import ( list_instances, @@ -95,3 +102,88 @@ class SettingsCreator(TrayPublishCreator): "default_variants": item_data["default_variants"] } ) + + +class EditorialCreator(TrayPublishCreator): + create_allow_context_change = True + + extensions = [] + + def collect_instances(self): + for instance_data in list_instances(): + creator_id = instance_data.get("creator_identifier") + if creator_id == self.identifier: + instance = CreatedInstance.from_existing( + instance_data, self + ) + self._add_instance_to_context(instance) + + def create(self, subset_name, data, pre_create_data): + # Pass precreate data to creator attributes + data["creator_attributes"] = pre_create_data + data["settings_creator"] = True + # Create new instance + new_instance = CreatedInstance(self.family, subset_name, data, self) + # Host implementation of storing metadata about instance + HostContext.add_instance(new_instance.data_to_store()) + # Add instance to current context + self._add_instance_to_context(new_instance) + + def get_instance_attr_defs(self): + if self.identifier == "editorial.simple": + return [ + FileDef( + "sequence_filepath", + folders=False, + extensions=self.sequence_extensions, + allow_sequences=self.allow_sequences, + label="Filepath", + ) + ] + else: + return [ + FileDef( + "sequence_filepath", + folders=False, + extensions=self.sequence_extensions, + allow_sequences=self.allow_sequences, + label="Sequence filepath", + ), + FileDef( + "clip_source_folder", + folders=True, + extensions=self.clip_extensions, + allow_sequences=False, + label="Clips' Source folder", + ), + TextDef("text input"), + NumberDef("number input"), + EnumDef("enum input", { + "value1": "label1", + "value2": "label2" + }), + BoolDef("bool input") + ] + + @classmethod + def from_settings(cls, item_data): + identifier = item_data["identifier"] + family = item_data["family"] + if not identifier: + identifier = "settings_{}".format(family) + return type( + "{}{}".format(cls.__name__, identifier), + (cls, ), + { + "family": family, + "identifier": identifier, + "label": item_data["label"].strip(), + "icon": item_data["icon"], + "description": item_data["description"], + "detailed_description": item_data["detailed_description"], + "sequence_extensions": item_data["sequence_extensions"], + "clip_extensions": item_data["clip_extensions"], + "allow_sequences": item_data["allow_sequences"], + "default_variants": item_data["default_variants"] + } + ) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py new file mode 100644 index 0000000000..d7fe0f952c --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -0,0 +1,25 @@ +import os +from pprint import pformat +from openpype.api import get_project_settings, Logger + +log = Logger.get_logger(__name__) + + +def CreateEditorial(): + from openpype.hosts.traypublisher.api.plugin import EditorialCreator + + project_name = os.environ["AVALON_PROJECT"] + project_settings = get_project_settings(project_name) + + simple_creators = project_settings["traypublisher"]["editorial_creators"] + + global_variables = globals() + for item in simple_creators: + + log.debug(pformat(item)) + + dynamic_plugin = EditorialCreator.from_settings(item) + global_variables[dynamic_plugin.__name__] = dynamic_plugin + + +CreateEditorial() diff --git a/openpype/hosts/traypublisher/plugins/create/create_from_settings.py b/openpype/hosts/traypublisher/plugins/create/create_from_settings.py index baca274ea6..1271e03fdb 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_from_settings.py +++ b/openpype/hosts/traypublisher/plugins/create/create_from_settings.py @@ -1,6 +1,8 @@ import os +from pprint import pformat +from openpype.api import get_project_settings, Logger -from openpype.api import get_project_settings +log = Logger.get_logger(__name__) def initialize(): @@ -13,6 +15,9 @@ def initialize(): global_variables = globals() for item in simple_creators: + + log.debug(pformat(item)) + dynamic_plugin = SettingsCreator.from_settings(item) global_variables[dynamic_plugin.__name__] = dynamic_plugin diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index e938384282..64cbd4a6f3 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -30,11 +30,13 @@ ".psb", ".aep" ] - }, + } + ], + "editorial_creators": [ { "family": "editorial", - "identifier": "", - "label": "Editorial", + "identifier": "editorial.simple", + "label": "Editorial Simple", "icon": "fa.file", "default_variants": [ "Main" @@ -42,11 +44,39 @@ "description": "Editorial files to generate shots.", "detailed_description": "Supporting publishing new shots to project or updating already created. Publishing will create OTIO file.", "allow_sequences": false, - "extensions": [ + "sequence_extensions": [ ".edl", ".xml", ".aaf", ".fcpxml" + ], + "clip_extensions": [ + ".mov", + ".jpg", + ".png" + ] + }, + { + "family": "editorial", + "identifier": "editorial.complex", + "label": "Editorial Complex", + "icon": "fa.file", + "default_variants": [ + "Main" + ], + "description": "Editorial files to generate shots.", + "detailed_description": "Supporting publishing new shots to project or updating already created. Publishing will create OTIO file.", + "allow_sequences": false, + "sequence_extensions": [ + ".edl", + ".xml", + ".aaf", + ".fcpxml" + ], + "clip_extensions": [ + ".mov", + ".jpg", + ".png" ] } ] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 55c1b7b7d7..e112a8c004 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -78,6 +78,89 @@ } ] } + }, + { + "type": "list", + "collapsible": true, + "key": "editorial_creators", + "label": "Editorial creator plugins", + "use_label_wrap": true, + "collapsible_key": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "family", + "label": "Family" + }, + { + "type": "text", + "key": "identifier", + "label": "Identifier", + "placeholder": "< Use 'Family' >", + "tooltip": "All creators must have unique identifier.\nBy default is used 'family' but if you need to have more creators with same families\nyou have to set identifier too." + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "icon", + "label": "Icon" + }, + { + "type": "list", + "key": "default_variants", + "label": "Default variants", + "object_type": { + "type": "text" + } + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "description", + "label": "Description" + }, + { + "type": "text", + "key": "detailed_description", + "label": "Detailed Description", + "multiline": true + }, + { + "type": "separator" + }, + { + "key": "allow_sequences", + "label": "Allow sequences", + "type": "boolean" + }, + { + "type": "list", + "key": "sequence_extensions", + "label": "Sequence extensions", + "use_label_wrap": true, + "collapsible_key": true, + "collapsed": false, + "object_type": "text" + }, + { + "type": "list", + "key": "clip_extensions", + "label": "Clip source file extensions", + "use_label_wrap": true, + "collapsible_key": true, + "collapsed": false, + "object_type": "text" + } + ] + } } ] } From 7444c2653073ec8a3a8ff49030ec547fa51cbfc2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Jul 2022 11:52:11 +0200 Subject: [PATCH 003/131] trayp: editorial wip --- openpype/hosts/traypublisher/api/editorial.py | 41 +++++++++++++++ openpype/hosts/traypublisher/api/plugin.py | 25 ++-------- .../plugins/create/create_editorial.py | 4 +- .../publish/collect_editorial_instances.py | 50 +++++++++++++++++++ 4 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 openpype/hosts/traypublisher/api/editorial.py create mode 100644 openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py new file mode 100644 index 0000000000..316163b2fa --- /dev/null +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -0,0 +1,41 @@ + +import os +import opentimelineio as otio +from openpype import lib as plib + +from openpype.pipeline import ( + Creator, + CreatedInstance +) + +from .pipeline import ( + list_instances, + update_instances, + remove_instances, + HostContext, +) + + + +class CreateEditorialInstance: + """Create Editorial OTIO timeline""" + + def __init__(self, file_path, extension=None, resources_dir=None): + self.file_path = file_path + self.video_extension = extension or ".mov" + self.resources_dir = resources_dir + + def create(self): + + # get editorial sequence file into otio timeline object + extension = os.path.splitext(self.file_path)[1] + kwargs = {} + if extension == ".edl": + # EDL has no frame rate embedded so needs explicit + # frame rate else 24 is asssumed. + kwargs["rate"] = plib.get_asset()["data"]["fps"] + + instance.data["otio_timeline"] = otio.adapters.read_from_file( + file_path, **kwargs) + + self.log.info(f"Added OTIO timeline from: `{file_path}`") diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 901f05c755..ae9e93fd60 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -41,7 +41,7 @@ class TrayPublishCreator(Creator): self._remove_instance_from_context(instance) def get_pre_create_attr_defs(self): - # Use same attributes as for instance attrobites + # Use same attributes as for instance attributes return self.get_instance_attr_defs() @@ -50,15 +50,6 @@ class SettingsCreator(TrayPublishCreator): extensions = [] - def collect_instances(self): - for instance_data in list_instances(): - creator_id = instance_data.get("creator_identifier") - if creator_id == self.identifier: - instance = CreatedInstance.from_existing( - instance_data, self - ) - self._add_instance_to_context(instance) - def create(self, subset_name, data, pre_create_data): # Pass precreate data to creator attributes data["creator_attributes"] = pre_create_data @@ -109,19 +100,13 @@ class EditorialCreator(TrayPublishCreator): extensions = [] - def collect_instances(self): - for instance_data in list_instances(): - creator_id = instance_data.get("creator_identifier") - if creator_id == self.identifier: - instance = CreatedInstance.from_existing( - instance_data, self - ) - self._add_instance_to_context(instance) - def create(self, subset_name, data, pre_create_data): + # TODO: create otio instance + # TODO: create clip instances + # Pass precreate data to creator attributes data["creator_attributes"] = pre_create_data - data["settings_creator"] = True + data["editorial_creator"] = True # Create new instance new_instance = CreatedInstance(self.family, subset_name, data, self) # Host implementation of storing metadata about instance diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index d7fe0f952c..8b2af8973b 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -11,10 +11,10 @@ def CreateEditorial(): project_name = os.environ["AVALON_PROJECT"] project_settings = get_project_settings(project_name) - simple_creators = project_settings["traypublisher"]["editorial_creators"] + editorial_creators = project_settings["traypublisher"]["editorial_creators"] global_variables = globals() - for item in simple_creators: + for item in editorial_creators: log.debug(pformat(item)) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py new file mode 100644 index 0000000000..874b6101c3 --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py @@ -0,0 +1,50 @@ +import os +import pyblish.api + + +class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): + """Collect data for instances created by settings creators.""" + + label = "Collect Settings Simple Instances" + order = pyblish.api.CollectorOrder - 0.49 + + hosts = ["traypublisher"] + + def process(self, instance): + if not instance.data.get("ediorial_creator"): + return + + if "families" not in instance.data: + instance.data["families"] = [] + + if "representations" not in instance.data: + instance.data["representations"] = [] + repres = instance.data["representations"] + + creator_attributes = instance.data["creator_attributes"] + filepath_item = creator_attributes["filepath"] + self.log.info(filepath_item) + filepaths = [ + os.path.join(filepath_item["directory"], filename) + for filename in filepath_item["filenames"] + ] + + instance.data["sourceFilepaths"] = filepaths + instance.data["stagingDir"] = filepath_item["directory"] + + filenames = filepath_item["filenames"] + _, ext = os.path.splitext(filenames[0]) + ext = ext[1:] + if len(filenames) == 1: + filenames = filenames[0] + + repres.append({ + "ext": ext, + "name": ext, + "stagingDir": filepath_item["directory"], + "files": filenames + }) + + self.log.debug("Created Simple Settings instance {}".format( + instance.data + )) From a88b1f1a33c1dada33a67cbe488776ce0c5f0b22 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 Jul 2022 22:02:15 +0200 Subject: [PATCH 004/131] trayp: editorial family wip --- openpype/hosts/traypublisher/api/editorial.py | 41 ------- openpype/hosts/traypublisher/api/plugin.py | 86 +------------ .../plugins/create/create_editorial.py | 116 ++++++++++++++++-- openpype/pipeline/create/creator_plugins.py | 6 + 4 files changed, 112 insertions(+), 137 deletions(-) delete mode 100644 openpype/hosts/traypublisher/api/editorial.py diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py deleted file mode 100644 index 316163b2fa..0000000000 --- a/openpype/hosts/traypublisher/api/editorial.py +++ /dev/null @@ -1,41 +0,0 @@ - -import os -import opentimelineio as otio -from openpype import lib as plib - -from openpype.pipeline import ( - Creator, - CreatedInstance -) - -from .pipeline import ( - list_instances, - update_instances, - remove_instances, - HostContext, -) - - - -class CreateEditorialInstance: - """Create Editorial OTIO timeline""" - - def __init__(self, file_path, extension=None, resources_dir=None): - self.file_path = file_path - self.video_extension = extension or ".mov" - self.resources_dir = resources_dir - - def create(self): - - # get editorial sequence file into otio timeline object - extension = os.path.splitext(self.file_path)[1] - kwargs = {} - if extension == ".edl": - # EDL has no frame rate embedded so needs explicit - # frame rate else 24 is asssumed. - kwargs["rate"] = plib.get_asset()["data"]["fps"] - - instance.data["otio_timeline"] = otio.adapters.read_from_file( - file_path, **kwargs) - - self.log.info(f"Added OTIO timeline from: `{file_path}`") diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index ae9e93fd60..94f6e7487f 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -3,12 +3,7 @@ from openpype.pipeline import ( CreatedInstance ) from openpype.lib import ( - FileDef, - TextDef, - NumberDef, - EnumDef, - BoolDef, - FileDefItem + FileDef ) from .pipeline import ( @@ -93,82 +88,3 @@ class SettingsCreator(TrayPublishCreator): "default_variants": item_data["default_variants"] } ) - - -class EditorialCreator(TrayPublishCreator): - create_allow_context_change = True - - extensions = [] - - def create(self, subset_name, data, pre_create_data): - # TODO: create otio instance - # TODO: create clip instances - - # Pass precreate data to creator attributes - data["creator_attributes"] = pre_create_data - data["editorial_creator"] = True - # Create new instance - new_instance = CreatedInstance(self.family, subset_name, data, self) - # Host implementation of storing metadata about instance - HostContext.add_instance(new_instance.data_to_store()) - # Add instance to current context - self._add_instance_to_context(new_instance) - - def get_instance_attr_defs(self): - if self.identifier == "editorial.simple": - return [ - FileDef( - "sequence_filepath", - folders=False, - extensions=self.sequence_extensions, - allow_sequences=self.allow_sequences, - label="Filepath", - ) - ] - else: - return [ - FileDef( - "sequence_filepath", - folders=False, - extensions=self.sequence_extensions, - allow_sequences=self.allow_sequences, - label="Sequence filepath", - ), - FileDef( - "clip_source_folder", - folders=True, - extensions=self.clip_extensions, - allow_sequences=False, - label="Clips' Source folder", - ), - TextDef("text input"), - NumberDef("number input"), - EnumDef("enum input", { - "value1": "label1", - "value2": "label2" - }), - BoolDef("bool input") - ] - - @classmethod - def from_settings(cls, item_data): - identifier = item_data["identifier"] - family = item_data["family"] - if not identifier: - identifier = "settings_{}".format(family) - return type( - "{}{}".format(cls.__name__, identifier), - (cls, ), - { - "family": family, - "identifier": identifier, - "label": item_data["label"].strip(), - "icon": item_data["icon"], - "description": item_data["description"], - "detailed_description": item_data["detailed_description"], - "sequence_extensions": item_data["sequence_extensions"], - "clip_extensions": item_data["clip_extensions"], - "allow_sequences": item_data["allow_sequences"], - "default_variants": item_data["default_variants"] - } - ) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 8b2af8973b..49fba65711 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -1,25 +1,119 @@ import os -from pprint import pformat -from openpype.api import get_project_settings, Logger +import opentimelineio as otio +from openpype.api import get_project_settings +from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator +from openpype.pipeline.create.creator_plugins import InvisibleCreator -log = Logger.get_logger(__name__) +from openpype.pipeline import CreatedInstance + +from openpype.lib import ( + FileDef, + TextDef, + NumberDef, + EnumDef, + BoolDef +) + +from openpype.hosts.traypublisher.api.pipeline import HostContext def CreateEditorial(): - from openpype.hosts.traypublisher.api.plugin import EditorialCreator - project_name = os.environ["AVALON_PROJECT"] project_settings = get_project_settings(project_name) editorial_creators = project_settings["traypublisher"]["editorial_creators"] - global_variables = globals() - for item in editorial_creators: - log.debug(pformat(item)) +class EditorialClipInstanceCreator(InvisibleCreator): + identifier = "editorial.clip" + family = "clip" - dynamic_plugin = EditorialCreator.from_settings(item) - global_variables[dynamic_plugin.__name__] = dynamic_plugin + def create(self, instance_data, source_data): + # instance_data > asset, task_name, variant, family + # source_data > additional data + self.log.info(f"instance_data: {instance_data}") + self.log.info(f"source_data: {source_data}") -CreateEditorial() +class EditorialSimpleCreator(TrayPublishCreator): + + label = "Editorial Simple" + family = "editorial" + identifier = "editorial.simple" + default_variants = [ + "main", + "review" + ] + description = "Editorial files to generate shots." + detailed_description = """ +Supporting publishing new shots to project +or updating already created. Publishing will create OTIO file. +""" + icon = "fa.file" + + def create(self, subset_name, data, pre_create_data): + # TODO: create otio instance + otio_timeline = self._create_otio_instance( + subset_name, data, pre_create_data) + + # TODO: create clip instances + editorial_clip_creator = self.create_context.creators["editorial.clip"] + editorial_clip_creator.create({}, {}) + + def _create_otio_instance(self, subset_name, data, pre_create_data): + # from openpype import lib as plib + + # get path of sequence + file_path_data = pre_create_data["sequence_filepath_data"] + file_path = os.path.join( + file_path_data["directory"], file_path_data["filenames"][0]) + + self.log.info(f"file_path: {file_path}") + + # get editorial sequence file into otio timeline object + extension = os.path.splitext(file_path)[1] + kwargs = {} + if extension == ".edl": + # EDL has no frame rate embedded so needs explicit + # frame rate else 24 is asssumed. + kwargs["rate"] = float(25) + # plib.get_asset()["data"]["fps"] + + self.log.info(f"kwargs: {kwargs}") + otio_timeline = otio.adapters.read_from_file( + file_path, **kwargs) + + # Pass precreate data to creator attributes + data.update({ + "creator_attributes": pre_create_data, + "editorial_creator": True + + }) + + self._create_instance(self.family, subset_name, data) + + return otio_timeline + + def _create_instance(self, family, subset_name, data): + # Create new instance + new_instance = CreatedInstance(family, subset_name, data, self) + # Host implementation of storing metadata about instance + HostContext.add_instance(new_instance.data_to_store()) + # Add instance to current context + self._add_instance_to_context(new_instance) + + def get_instance_attr_defs(self): + return [ + FileDef( + "sequence_filepath_data", + folders=False, + extensions=[ + ".edl", + ".xml", + ".aaf", + ".fcpxml" + ], + allow_sequences=False, + label="Filepath", + ) + ] diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 8006d4f4f8..778d6846b2 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -342,6 +342,12 @@ class Creator(BaseCreator): return self.pre_create_attr_defs +class InvisibleCreator(BaseCreator): + @abstractmethod + def create(self, instance_data, source_data): + pass + + class AutoCreator(BaseCreator): """Creator which is automatically triggered without user interaction. From 14acec63c2d0a3760f9ecbf41a431461f9bc459b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 10:49:35 +0200 Subject: [PATCH 005/131] create plugins have access to project name --- openpype/pipeline/create/context.py | 4 ++++ openpype/pipeline/create/creator_plugins.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 12cd9bbc68..c91b13e520 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -748,6 +748,10 @@ class CreateContext: def host_name(self): return os.environ["AVALON_APP"] + @property + def project_name(self): + return self.dbcon.active_project() + @property def log(self): """Dynamic access to logger.""" diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 778d6846b2..be3f3d4cbd 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -92,6 +92,12 @@ class BaseCreator: """Family that plugin represents.""" pass + @property + def project_name(self): + """Family that plugin represents.""" + + self.create_context.project_name + @property def log(self): if self._log is None: From 9eed955303f3937b0e0ddb4fbd515408a69c0e95 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:02:22 +0200 Subject: [PATCH 006/131] use settings on init and query asset document --- .../plugins/create/create_editorial.py | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 49fba65711..54a52dfb75 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -1,6 +1,7 @@ import os +from copy import deepcopy import opentimelineio as otio -from openpype.api import get_project_settings +from openpype.client import get_asset_by_name from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator from openpype.pipeline.create.creator_plugins import InvisibleCreator @@ -17,16 +18,18 @@ from openpype.lib import ( from openpype.hosts.traypublisher.api.pipeline import HostContext -def CreateEditorial(): - project_name = os.environ["AVALON_PROJECT"] - project_settings = get_project_settings(project_name) - - editorial_creators = project_settings["traypublisher"]["editorial_creators"] - - class EditorialClipInstanceCreator(InvisibleCreator): identifier = "editorial.clip" family = "clip" + host_name = "traypublisher" + + def __init__( + self, create_context, system_settings, project_settings, + *args, **kwargs + ): + super(EditorialClipInstanceCreator, self).__init__( + create_context, system_settings, project_settings, *args, **kwargs + ) def create(self, instance_data, source_data): # instance_data > asset, task_name, variant, family @@ -51,10 +54,24 @@ or updating already created. Publishing will create OTIO file. """ icon = "fa.file" - def create(self, subset_name, data, pre_create_data): + def __init__( + self, create_context, system_settings, project_settings, + *args, **kwargs + ): + super(EditorialSimpleCreator, self).__init__( + create_context, system_settings, project_settings, *args, **kwargs + ) + editorial_creators = ( + project_settings["traypublisher"]["editorial_creators"] + ) + self._editorial_creators = deepcopy(editorial_creators) + + def create(self, subset_name, instance_data, pre_create_data): # TODO: create otio instance + asset_name = instance_data["asset"] + asset_doc = get_asset_by_name(self.project_name, asset_name) otio_timeline = self._create_otio_instance( - subset_name, data, pre_create_data) + subset_name, instance_data, pre_create_data) # TODO: create clip instances editorial_clip_creator = self.create_context.creators["editorial.clip"] From dcc64f9eb425c94e85a66df53d1c2ddd7762b7b7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 8 Jul 2022 11:13:15 +0200 Subject: [PATCH 007/131] trayp: updating create_editorial --- .../hosts/traypublisher/plugins/create/create_editorial.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 54a52dfb75..a58d968e3d 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -61,10 +61,13 @@ or updating already created. Publishing will create OTIO file. super(EditorialSimpleCreator, self).__init__( create_context, system_settings, project_settings, *args, **kwargs ) - editorial_creators = ( + editorial_creators = deepcopy( project_settings["traypublisher"]["editorial_creators"] ) - self._editorial_creators = deepcopy(editorial_creators) + self._creator_settings = editorial_creators.get(self.__name__) + + if self._creator_settings.get("default_variants"): + self.default_variants = self._creator_settings["default_variants"] def create(self, subset_name, instance_data, pre_create_data): # TODO: create otio instance From c9a70d410f8de60b4171fad7f2314dda7c4d5e20 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:15:09 +0200 Subject: [PATCH 008/131] use project_name attribute --- openpype/pipeline/create/context.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index c91b13e520..8f79110fdf 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -843,9 +843,8 @@ class CreateContext: self.plugins_with_defs = plugins_with_defs # Prepare settings - project_name = self.dbcon.Session["AVALON_PROJECT"] system_settings = get_system_settings() - project_settings = get_project_settings(project_name) + project_settings = get_project_settings(self.project_name) # Discover and prepare creators creators = {} From 76e36015dcd530c6b5c40b9a8a041821d308a1ec Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:20:44 +0200 Subject: [PATCH 009/131] implemented invisible tray publisher creator --- openpype/hosts/traypublisher/api/plugin.py | 25 +++++++++++++++++++++- openpype/pipeline/create/__init__.py | 2 ++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 94f6e7487f..75f73e88b1 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,5 +1,6 @@ -from openpype.pipeline import ( +from openpype.pipeline.create import ( Creator, + InivisbleCreator, CreatedInstance ) from openpype.lib import ( @@ -14,6 +15,28 @@ from .pipeline import ( ) +class InvisibleTrayPublishCreator(InivisbleCreator): + create_allow_context_change = True + host_name = "traypublisher" + + def collect_instances(self): + for instance_data in list_instances(): + creator_id = instance_data.get("creator_identifier") + if creator_id == self.identifier: + instance = CreatedInstance.from_existing( + instance_data, self + ) + self._add_instance_to_context(instance) + + def update_instances(self, update_list): + update_instances(update_list) + + def remove_instances(self, instances): + remove_instances(instances) + for instance in instances: + self._remove_instance_from_context(instance) + + class TrayPublishCreator(Creator): create_allow_context_change = True host_name = "traypublisher" diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py index 1beeb4267b..a0f2c16f75 100644 --- a/openpype/pipeline/create/__init__.py +++ b/openpype/pipeline/create/__init__.py @@ -7,6 +7,7 @@ from .creator_plugins import ( BaseCreator, Creator, AutoCreator, + InivisbleCreator, discover_creator_plugins, discover_legacy_creator_plugins, @@ -35,6 +36,7 @@ __all__ = ( "BaseCreator", "Creator", "AutoCreator", + "InivisbleCreator", "discover_creator_plugins", "discover_legacy_creator_plugins", From 82899b1acda57320c0faf31fcf0666a762b041d0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 8 Jul 2022 11:22:15 +0200 Subject: [PATCH 010/131] implement get_pre_create_attr_defs only for settings creator --- openpype/hosts/traypublisher/api/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 75f73e88b1..c7f2f4ec13 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -58,10 +58,6 @@ class TrayPublishCreator(Creator): for instance in instances: self._remove_instance_from_context(instance) - def get_pre_create_attr_defs(self): - # Use same attributes as for instance attributes - return self.get_instance_attr_defs() - class SettingsCreator(TrayPublishCreator): create_allow_context_change = True @@ -90,6 +86,10 @@ class SettingsCreator(TrayPublishCreator): ) ] + def get_pre_create_attr_defs(self): + # Use same attributes as for instance attrobites + return self.get_instance_attr_defs() + @classmethod def from_settings(cls, item_data): identifier = item_data["identifier"] From 10aae0e98686c6cc2463b4b763778adcb25608aa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 8 Jul 2022 11:28:02 +0200 Subject: [PATCH 011/131] fixing invisible creator name --- .../plugins/create/create_editorial.py | 13 +++++++++---- openpype/pipeline/create/__init__.py | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index a58d968e3d..61f24ec60e 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -2,8 +2,11 @@ import os from copy import deepcopy import opentimelineio as otio from openpype.client import get_asset_by_name -from openpype.hosts.traypublisher.api.plugin import TrayPublishCreator -from openpype.pipeline.create.creator_plugins import InvisibleCreator +from openpype.hosts.traypublisher.api.plugin import ( + TrayPublishCreator, + InvisibleTrayPublishCreator +) + from openpype.pipeline import CreatedInstance @@ -18,7 +21,7 @@ from openpype.lib import ( from openpype.hosts.traypublisher.api.pipeline import HostContext -class EditorialClipInstanceCreator(InvisibleCreator): +class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): identifier = "editorial.clip" family = "clip" host_name = "traypublisher" @@ -64,8 +67,10 @@ or updating already created. Publishing will create OTIO file. editorial_creators = deepcopy( project_settings["traypublisher"]["editorial_creators"] ) - self._creator_settings = editorial_creators.get(self.__name__) + # get this creator settings by identifier + self._creator_settings = editorial_creators.get(self.identifier) + # try to set main attributes from settings if self._creator_settings.get("default_variants"): self.default_variants = self._creator_settings["default_variants"] diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py index a0f2c16f75..cd01c53cf5 100644 --- a/openpype/pipeline/create/__init__.py +++ b/openpype/pipeline/create/__init__.py @@ -7,7 +7,7 @@ from .creator_plugins import ( BaseCreator, Creator, AutoCreator, - InivisbleCreator, + InvisibleCreator, discover_creator_plugins, discover_legacy_creator_plugins, @@ -36,7 +36,7 @@ __all__ = ( "BaseCreator", "Creator", "AutoCreator", - "InivisbleCreator", + "InvisibleCreator", "discover_creator_plugins", "discover_legacy_creator_plugins", From a31ea2a24d4de68fbbb6f47d5eb224cd02e182e7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 8 Jul 2022 11:28:37 +0200 Subject: [PATCH 012/131] fixing invisible creator name 2 --- openpype/hosts/traypublisher/api/plugin.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index c7f2f4ec13..0d7651e464 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,6 +1,6 @@ from openpype.pipeline.create import ( Creator, - InivisbleCreator, + InvisibleCreator, CreatedInstance ) from openpype.lib import ( @@ -15,8 +15,7 @@ from .pipeline import ( ) -class InvisibleTrayPublishCreator(InivisbleCreator): - create_allow_context_change = True +class InvisibleTrayPublishCreator(InvisibleCreator): host_name = "traypublisher" def collect_instances(self): From 2270c972906b6aac890c95016c125f4001a63f0f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 8 Jul 2022 11:28:51 +0200 Subject: [PATCH 013/131] trayp: adding settings --- .../project_settings/traypublisher.json | 47 +-------- .../schema_project_traypublisher.json | 96 ++++--------------- 2 files changed, 24 insertions(+), 119 deletions(-) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 64cbd4a6f3..4a672789ed 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -32,52 +32,11 @@ ] } ], - "editorial_creators": [ - { - "family": "editorial", - "identifier": "editorial.simple", - "label": "Editorial Simple", - "icon": "fa.file", + "editorial_creators": { + "editorial.simple": { "default_variants": [ "Main" - ], - "description": "Editorial files to generate shots.", - "detailed_description": "Supporting publishing new shots to project or updating already created. Publishing will create OTIO file.", - "allow_sequences": false, - "sequence_extensions": [ - ".edl", - ".xml", - ".aaf", - ".fcpxml" - ], - "clip_extensions": [ - ".mov", - ".jpg", - ".png" - ] - }, - { - "family": "editorial", - "identifier": "editorial.complex", - "label": "Editorial Complex", - "icon": "fa.file", - "default_variants": [ - "Main" - ], - "description": "Editorial files to generate shots.", - "detailed_description": "Supporting publishing new shots to project or updating already created. Publishing will create OTIO file.", - "allow_sequences": false, - "sequence_extensions": [ - ".edl", - ".xml", - ".aaf", - ".fcpxml" - ], - "clip_extensions": [ - ".mov", - ".jpg", - ".png" ] } - ] + } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index e112a8c004..1b24fcbe93 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -80,87 +80,33 @@ } }, { - "type": "list", + "type": "dict", "collapsible": true, "key": "editorial_creators", "label": "Editorial creator plugins", "use_label_wrap": true, "collapsible_key": true, - "object_type": { - "type": "dict", - "children": [ - { - "type": "text", - "key": "family", - "label": "Family" - }, - { - "type": "text", - "key": "identifier", - "label": "Identifier", - "placeholder": "< Use 'Family' >", - "tooltip": "All creators must have unique identifier.\nBy default is used 'family' but if you need to have more creators with same families\nyou have to set identifier too." - }, - { - "type": "text", - "key": "label", - "label": "Label" - }, - { - "type": "text", - "key": "icon", - "label": "Icon" - }, - { - "type": "list", - "key": "default_variants", - "label": "Default variants", - "object_type": { - "type": "text" + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "editorial.simple", + "label": "Editorial simple creator", + "use_label_wrap": true, + "collapsible_key": true, + "children": [ + + { + "type": "list", + "key": "default_variants", + "label": "Default variants", + "object_type": { + "type": "text" + } } - }, - { - "type": "separator" - }, - { - "type": "text", - "key": "description", - "label": "Description" - }, - { - "type": "text", - "key": "detailed_description", - "label": "Detailed Description", - "multiline": true - }, - { - "type": "separator" - }, - { - "key": "allow_sequences", - "label": "Allow sequences", - "type": "boolean" - }, - { - "type": "list", - "key": "sequence_extensions", - "label": "Sequence extensions", - "use_label_wrap": true, - "collapsible_key": true, - "collapsed": false, - "object_type": "text" - }, - { - "type": "list", - "key": "clip_extensions", - "label": "Clip source file extensions", - "use_label_wrap": true, - "collapsible_key": true, - "collapsed": false, - "object_type": "text" - } - ] - } + ] + } + ] } ] } From aa79551cedf9fef3c78535fbe8f3a3b819fa3f7f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 8 Jul 2022 11:34:31 +0200 Subject: [PATCH 014/131] trayp: identifier as key in settings didnt work with dot --- openpype/hosts/traypublisher/plugins/create/create_editorial.py | 2 +- openpype/settings/defaults/project_settings/traypublisher.json | 2 +- .../schemas/projects_schema/schema_project_traypublisher.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 61f24ec60e..442ff77130 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -45,7 +45,7 @@ class EditorialSimpleCreator(TrayPublishCreator): label = "Editorial Simple" family = "editorial" - identifier = "editorial.simple" + identifier = "editorialSimple" default_variants = [ "main", "review" diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 4a672789ed..ef6dc5fec7 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -33,7 +33,7 @@ } ], "editorial_creators": { - "editorial.simple": { + "editorialSimple": { "default_variants": [ "Main" ] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 1b24fcbe93..11ae0e65a7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -90,7 +90,7 @@ { "type": "dict", "collapsible": true, - "key": "editorial.simple", + "key": "editorialSimple", "label": "Editorial simple creator", "use_label_wrap": true, "collapsible_key": true, From ec21481c60847f35c98bbf534a1479440d8bd28f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 8 Jul 2022 12:04:29 +0200 Subject: [PATCH 015/131] trayp: adding precreate properties --- .../plugins/create/create_editorial.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 442ff77130..b31072aaf1 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -15,14 +15,16 @@ from openpype.lib import ( TextDef, NumberDef, EnumDef, - BoolDef + BoolDef, + UISeparatorDef, + UILabelDef ) from openpype.hosts.traypublisher.api.pipeline import HostContext class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): - identifier = "editorial.clip" + identifier = "editorialClip" family = "clip" host_name = "traypublisher" @@ -47,8 +49,7 @@ class EditorialSimpleCreator(TrayPublishCreator): family = "editorial" identifier = "editorialSimple" default_variants = [ - "main", - "review" + "main" ] description = "Editorial files to generate shots." detailed_description = """ @@ -82,7 +83,7 @@ or updating already created. Publishing will create OTIO file. subset_name, instance_data, pre_create_data) # TODO: create clip instances - editorial_clip_creator = self.create_context.creators["editorial.clip"] + editorial_clip_creator = self.create_context.creators["editorialClip"] editorial_clip_creator.create({}, {}) def _create_otio_instance(self, subset_name, data, pre_create_data): @@ -127,7 +128,8 @@ or updating already created. Publishing will create OTIO file. # Add instance to current context self._add_instance_to_context(new_instance) - def get_instance_attr_defs(self): + def get_pre_create_attr_defs(self): + # Use same attributes as for instance attrobites return [ FileDef( "sequence_filepath_data", @@ -140,5 +142,7 @@ or updating already created. Publishing will create OTIO file. ], allow_sequences=False, label="Filepath", - ) - ] + ), + UISeparatorDef(), + UILabelDef("Clip instance attributes") + ] \ No newline at end of file From ba4dd7cc2234b882b0e527e75d0a5bc48666f463 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 8 Jul 2022 12:47:36 +0200 Subject: [PATCH 016/131] creator: fixing returning project_name --- openpype/pipeline/create/creator_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index be3f3d4cbd..e0de2baa77 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -96,7 +96,7 @@ class BaseCreator: def project_name(self): """Family that plugin represents.""" - self.create_context.project_name + return self.create_context.project_name @property def log(self): From 01e548d2ff070fe6e9058f334e097919ea18cee9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 8 Jul 2022 13:52:36 +0200 Subject: [PATCH 017/131] trayp: fixing init arg --- .../traypublisher/plugins/create/create_editorial.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index b31072aaf1..560a5ae047 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -29,11 +29,10 @@ class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): host_name = "traypublisher" def __init__( - self, create_context, system_settings, project_settings, - *args, **kwargs + self, project_settings, *args, **kwargs ): super(EditorialClipInstanceCreator, self).__init__( - create_context, system_settings, project_settings, *args, **kwargs + project_settings, *args, **kwargs ) def create(self, instance_data, source_data): @@ -59,11 +58,10 @@ or updating already created. Publishing will create OTIO file. icon = "fa.file" def __init__( - self, create_context, system_settings, project_settings, - *args, **kwargs + self, project_settings, *args, **kwargs ): super(EditorialSimpleCreator, self).__init__( - create_context, system_settings, project_settings, *args, **kwargs + project_settings, *args, **kwargs ) editorial_creators = deepcopy( project_settings["traypublisher"]["editorial_creators"] From c08713c258a230ab20494e692fce9eaa488b8cd3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 8 Jul 2022 17:19:28 +0200 Subject: [PATCH 018/131] trayp: udpating editorial creator --- .../plugins/create/create_editorial.py | 220 ++++++++++++++++-- 1 file changed, 207 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 560a5ae047..ed91f0201f 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -1,5 +1,6 @@ import os from copy import deepcopy +from pprint import pformat import opentimelineio as otio from openpype.client import get_asset_by_name from openpype.hosts.traypublisher.api.plugin import ( @@ -23,6 +24,31 @@ from openpype.lib import ( from openpype.hosts.traypublisher.api.pipeline import HostContext +CLIP_ATTR_DEFS = [ + NumberDef( + "timeline_offset", + default=900000, + label="Timeline offset" + ), + UISeparatorDef(), + NumberDef( + "workfile_start_frame", + default=1001, + label="Workfile start frame" + ), + NumberDef( + "handle_start", + default=0, + label="Handle start" + ), + NumberDef( + "handle_end", + default=0, + label="Handle end" + ) +] + + class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): identifier = "editorialClip" family = "clip" @@ -41,6 +67,32 @@ class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): self.log.info(f"instance_data: {instance_data}") self.log.info(f"source_data: {source_data}") + instance_name = "{}_{}".format( + instance_data["name"], + "plateMain" + ) + return self._create_instance(instance_name, instance_data) + + def _create_instance(self, subset_name, data): + # Create new instance + new_instance = CreatedInstance(self.family, subset_name, data, self) + # Host implementation of storing metadata about instance + HostContext.add_instance(new_instance.data_to_store()) + # Add instance to current context + self._add_instance_to_context(new_instance) + + return new_instance + + def get_instance_attr_defs(self): + attr_defs = [ + TextDef( + "asset_name", + label="Asset name", + ) + ] + attr_defs.extend(CLIP_ATTR_DEFS) + return attr_defs + class EditorialSimpleCreator(TrayPublishCreator): @@ -57,6 +109,8 @@ or updating already created. Publishing will create OTIO file. """ icon = "fa.file" + + def __init__( self, project_settings, *args, **kwargs ): @@ -74,19 +128,29 @@ or updating already created. Publishing will create OTIO file. self.default_variants = self._creator_settings["default_variants"] def create(self, subset_name, instance_data, pre_create_data): + clip_instance_properties = { + k: v for k, v in pre_create_data.items() + if k != "sequence_filepath_data" + } # TODO: create otio instance asset_name = instance_data["asset"] asset_doc = get_asset_by_name(self.project_name, asset_name) + fps = asset_doc["data"]["fps"] + instance_data.update({ + "fps": fps + }) otio_timeline = self._create_otio_instance( subset_name, instance_data, pre_create_data) # TODO: create clip instances - editorial_clip_creator = self.create_context.creators["editorialClip"] - editorial_clip_creator.create({}, {}) + clip_instance_properties.update({ + "fps": fps, + "asset_name": asset_name + }) + self._get_clip_instances( + asset_name, otio_timeline, clip_instance_properties) def _create_otio_instance(self, subset_name, data, pre_create_data): - # from openpype import lib as plib - # get path of sequence file_path_data = pre_create_data["sequence_filepath_data"] file_path = os.path.join( @@ -100,8 +164,7 @@ or updating already created. Publishing will create OTIO file. if extension == ".edl": # EDL has no frame rate embedded so needs explicit # frame rate else 24 is asssumed. - kwargs["rate"] = float(25) - # plib.get_asset()["data"]["fps"] + kwargs["rate"] = data["fps"] self.log.info(f"kwargs: {kwargs}") otio_timeline = otio.adapters.read_from_file( @@ -109,15 +172,144 @@ or updating already created. Publishing will create OTIO file. # Pass precreate data to creator attributes data.update({ - "creator_attributes": pre_create_data, - "editorial_creator": True - + "sequence_file_path": file_path }) self._create_instance(self.family, subset_name, data) return otio_timeline + def _get_clip_instances( + self, + asset_name, + otio_timeline, + clip_instance_properties + ): + parent_asset_name = clip_instance_properties["asset_name"] + handle_start = clip_instance_properties["handle_start"] + handle_end = clip_instance_properties["handle_end"] + timeline_offset = clip_instance_properties["timeline_offset"] + workfile_start_frame = clip_instance_properties["workfile_start_frame"] + fps = clip_instance_properties["fps"] + + assets_shared = {} + self.asset_name_check = [] + + editorial_clip_creator = self.create_context.creators["editorialClip"] + + tracks = otio_timeline.each_child( + descended_from_type=otio.schema.Track + ) + + for track in tracks: + self.log.debug(f"track.name: {track.name}") + try: + track_start_frame = ( + abs(track.source_range.start_time.value) + ) + self.log.debug(f"track_start_frame: {track_start_frame}") + track_start_frame -= self.timeline_frame_start + except AttributeError: + track_start_frame = 0 + + self.log.debug(f"track_start_frame: {track_start_frame}") + + for clip in track.each_child(): + + if not self._validate_clip_for_processing(clip): + continue + + # basic unique asset name + clip_name = os.path.splitext(clip.name)[0].lower() + name = f"{asset_name.split('_')[0]}_{clip_name}" + + # make sure the name is unique + self._validate_name_uniqueness(name) + + # frame ranges data + clip_in = clip.range_in_parent().start_time.value + clip_in += track_start_frame + clip_out = clip.range_in_parent().end_time_inclusive().value + clip_out += track_start_frame + self.log.info(f"clip_in: {clip_in} | clip_out: {clip_out}") + + # add offset in case there is any + if timeline_offset: + clip_in += timeline_offset + clip_out += timeline_offset + + clip_duration = clip.duration().value + self.log.info(f"clip duration: {clip_duration}") + + source_in = clip.trimmed_range().start_time.value + source_out = source_in + clip_duration + + # define starting frame for future shot + frame_start = ( + clip_in if workfile_start_frame is None + else workfile_start_frame + ) + frame_end = frame_start + (clip_duration - 1) + + # create shared new instance data + instance_data = { + "variant": "Main", + "families": ["plate"], + + # shared attributes + "asset": parent_asset_name, + "name": clip_name, + "task": "Compositing", + + # parent time properties + "trackStartFrame": track_start_frame, + + # creator_attributes + "creator_attributes": { + "asset_name": clip_name, + "timeline_offset": timeline_offset, + "workfile_start_frame": workfile_start_frame, + "frameStart": frame_start, + "frameEnd": frame_end, + "fps": fps, + "handle_start": handle_start, + "handle_end": handle_end, + "clipIn": clip_in, + "clipOut": clip_out, + "sourceIn": source_in, + "sourceOut": source_out, + } + } + + c_instance = editorial_clip_creator.create(instance_data, {}) + self.log.debug(f"{pformat(dict(c_instance.data))}") + + def _validate_clip_for_processing(self, clip): + if clip.name is None: + return False + + if isinstance(clip, otio.schema.Gap): + return False + + # skip all generators like black empty + if isinstance( + clip.media_reference, + otio.schema.GeneratorReference): + return False + + # Transitions are ignored, because Clips have the full frame + # range. + if isinstance(clip, otio.schema.Transition): + return False + + return True + + def _validate_name_uniqueness(self, name): + if name not in self.asset_name_check: + self.asset_name_check.append(name) + else: + self.log.warning(f"duplicate shot name: {name}") + def _create_instance(self, family, subset_name, data): # Create new instance new_instance = CreatedInstance(family, subset_name, data, self) @@ -128,7 +320,7 @@ or updating already created. Publishing will create OTIO file. def get_pre_create_attr_defs(self): # Use same attributes as for instance attrobites - return [ + attr_defs = [ FileDef( "sequence_filepath_data", folders=False, @@ -141,6 +333,8 @@ or updating already created. Publishing will create OTIO file. allow_sequences=False, label="Filepath", ), - UISeparatorDef(), - UILabelDef("Clip instance attributes") - ] \ No newline at end of file + UILabelDef("Clip instance attributes"), + UISeparatorDef() + ] + attr_defs.extend(CLIP_ATTR_DEFS) + return attr_defs From c60c0ff2d9abe9ecb312a3f553b9523c4bbae8f2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 11 Jul 2022 09:58:07 +0200 Subject: [PATCH 019/131] trayp: updating create editorial --- .../plugins/create/create_editorial.py | 66 +++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index ed91f0201f..3164e4aa99 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -25,12 +25,6 @@ from openpype.hosts.traypublisher.api.pipeline import HostContext CLIP_ATTR_DEFS = [ - NumberDef( - "timeline_offset", - default=900000, - label="Timeline offset" - ), - UISeparatorDef(), NumberDef( "workfile_start_frame", default=1001, @@ -62,20 +56,20 @@ class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): ) def create(self, instance_data, source_data): - # instance_data > asset, task_name, variant, family - # source_data > additional data self.log.info(f"instance_data: {instance_data}") - self.log.info(f"source_data: {source_data}") + subset_name = instance_data["subset"] + family = instance_data["family"] instance_name = "{}_{}".format( instance_data["name"], - "plateMain" + subset_name ) - return self._create_instance(instance_name, instance_data) + return self._create_instance(instance_name, family, instance_data) + + def _create_instance(self, subset_name, family, data): - def _create_instance(self, subset_name, data): # Create new instance - new_instance = CreatedInstance(self.family, subset_name, data, self) + new_instance = CreatedInstance(family, subset_name, data, self) # Host implementation of storing metadata about instance HostContext.add_instance(new_instance.data_to_store()) # Add instance to current context @@ -109,8 +103,6 @@ or updating already created. Publishing will create OTIO file. """ icon = "fa.file" - - def __init__( self, project_settings, *args, **kwargs ): @@ -132,23 +124,27 @@ or updating already created. Publishing will create OTIO file. k: v for k, v in pre_create_data.items() if k != "sequence_filepath_data" } - # TODO: create otio instance + # Create otio editorial instance asset_name = instance_data["asset"] asset_doc = get_asset_by_name(self.project_name, asset_name) + + # get asset doc data attributes fps = asset_doc["data"]["fps"] instance_data.update({ "fps": fps }) + + # get otio timeline otio_timeline = self._create_otio_instance( subset_name, instance_data, pre_create_data) - # TODO: create clip instances + # Create all clip instances clip_instance_properties.update({ "fps": fps, - "asset_name": asset_name + "parent_asset_name": asset_name }) self._get_clip_instances( - asset_name, otio_timeline, clip_instance_properties) + otio_timeline, clip_instance_properties) def _create_otio_instance(self, subset_name, data, pre_create_data): # get path of sequence @@ -181,18 +177,19 @@ or updating already created. Publishing will create OTIO file. def _get_clip_instances( self, - asset_name, otio_timeline, clip_instance_properties ): - parent_asset_name = clip_instance_properties["asset_name"] + family = "plate" + + # get clip instance properties + parent_asset_name = clip_instance_properties["parent_asset_name"] handle_start = clip_instance_properties["handle_start"] handle_end = clip_instance_properties["handle_end"] timeline_offset = clip_instance_properties["timeline_offset"] workfile_start_frame = clip_instance_properties["workfile_start_frame"] fps = clip_instance_properties["fps"] - assets_shared = {} self.asset_name_check = [] editorial_clip_creator = self.create_context.creators["editorialClip"] @@ -221,7 +218,7 @@ or updating already created. Publishing will create OTIO file. # basic unique asset name clip_name = os.path.splitext(clip.name)[0].lower() - name = f"{asset_name.split('_')[0]}_{clip_name}" + name = f"{parent_asset_name.split('_')[0]}_{clip_name}" # make sure the name is unique self._validate_name_uniqueness(name) @@ -251,14 +248,24 @@ or updating already created. Publishing will create OTIO file. ) frame_end = frame_start + (clip_duration - 1) + # subset name + variant = self.variant + subset_name = "{}{}".format( + family, variant.capitalize() + ) + # create shared new instance data instance_data = { - "variant": "Main", - "families": ["plate"], + "variant": variant, + "family": family, + "families": ["clip"], + "subset": subset_name, - # shared attributes + # HACK: just for temporal bug workaround + # TODO: should loockup shot name for update "asset": parent_asset_name, "name": clip_name, + # HACK: just for temporal bug workaround "task": "Compositing", # parent time properties @@ -334,6 +341,13 @@ or updating already created. Publishing will create OTIO file. label="Filepath", ), UILabelDef("Clip instance attributes"), + UISeparatorDef(), + # TODO: perhpas better would be timecode and fps input + NumberDef( + "timeline_offset", + default=900000, + label="Timeline offset" + ), UISeparatorDef() ] attr_defs.extend(CLIP_ATTR_DEFS) From 370ee0c254a1a3d6eab1e4a27f8cbf4bc5676986 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 11 Jul 2022 15:21:37 +0200 Subject: [PATCH 020/131] trayp: added `fps` enumerator for rate override --- .../plugins/create/create_editorial.py | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 3164e4aa99..406a7bc3b3 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -25,6 +25,18 @@ from openpype.hosts.traypublisher.api.pipeline import HostContext CLIP_ATTR_DEFS = [ + EnumDef( + "fps", + items={ + "from_project": "From project", + 23.997: "23.976", + 24: "24", + 25: "25", + 29.97: "29.97", + 30: "30" + }, + label="FPS" + ), NumberDef( "workfile_start_frame", default=1001, @@ -128,8 +140,14 @@ or updating already created. Publishing will create OTIO file. asset_name = instance_data["asset"] asset_doc = get_asset_by_name(self.project_name, asset_name) - # get asset doc data attributes - fps = asset_doc["data"]["fps"] + self.log.info(pre_create_data["fps"]) + + if pre_create_data["fps"] == "from_project": + # get asset doc data attributes + fps = asset_doc["data"]["fps"] + else: + fps = float(pre_create_data["fps"]) + instance_data.update({ "fps": fps }) @@ -149,6 +167,10 @@ or updating already created. Publishing will create OTIO file. def _create_otio_instance(self, subset_name, data, pre_create_data): # get path of sequence file_path_data = pre_create_data["sequence_filepath_data"] + + if len(file_path_data["filenames"]) == 0: + raise FileExistsError("File path was not added") + file_path = os.path.join( file_path_data["directory"], file_path_data["filenames"][0]) From 3f7dfb6579394237dfbf18c488c0586b06129fa2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 11 Jul 2022 17:43:00 +0200 Subject: [PATCH 021/131] trayp: removing task --- openpype/hosts/traypublisher/plugins/create/create_editorial.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 406a7bc3b3..6c8c1abdae 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -287,8 +287,6 @@ or updating already created. Publishing will create OTIO file. # TODO: should loockup shot name for update "asset": parent_asset_name, "name": clip_name, - # HACK: just for temporal bug workaround - "task": "Compositing", # parent time properties "trackStartFrame": track_start_frame, From ccffaa38bac0b4f7a6b4bbd500864dfa99cc87be Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 11 Jul 2022 17:43:46 +0200 Subject: [PATCH 022/131] trayp: adding label to created instance --- .../plugins/create/create_editorial.py | 19 ++++++++++++------- .../publish/collect_editorial_instances.py | 9 +++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 6c8c1abdae..e47d28447b 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -72,16 +72,14 @@ class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): subset_name = instance_data["subset"] family = instance_data["family"] - instance_name = "{}_{}".format( - instance_data["name"], - subset_name - ) - return self._create_instance(instance_name, family, instance_data) + return self._create_instance(subset_name, family, instance_data) def _create_instance(self, subset_name, family, data): # Create new instance new_instance = CreatedInstance(family, subset_name, data, self) + self.log.info(f"instance_data: {pformat(new_instance.data)}") + # Host implementation of storing metadata about instance HostContext.add_instance(new_instance.data_to_store()) # Add instance to current context @@ -271,13 +269,20 @@ or updating already created. Publishing will create OTIO file. frame_end = frame_start + (clip_duration - 1) # subset name - variant = self.variant + variant = self.get_variant() + self.log.info( + f"__ variant: {variant}") + subset_name = "{}{}".format( family, variant.capitalize() ) - + label = "{}_{}".format( + clip_name, + subset_name + ) # create shared new instance data instance_data = { + "label": label, "variant": variant, "family": family, "families": ["clip"], diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py index 874b6101c3..6521c97774 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py @@ -1,18 +1,17 @@ import os +from pprint import pformat import pyblish.api class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): """Collect data for instances created by settings creators.""" - label = "Collect Settings Simple Instances" + label = "Collect Editorial Instances" order = pyblish.api.CollectorOrder - 0.49 hosts = ["traypublisher"] def process(self, instance): - if not instance.data.get("ediorial_creator"): - return if "families" not in instance.data: instance.data["families"] = [] @@ -20,7 +19,9 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): if "representations" not in instance.data: instance.data["representations"] = [] repres = instance.data["representations"] - + self.log.debug( + pformat(dict(instance.data)) + ) creator_attributes = instance.data["creator_attributes"] filepath_item = creator_attributes["filepath"] self.log.info(filepath_item) From e77d4a11d82c212930337609158767bf0de2a142 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 11 Jul 2022 18:03:20 +0200 Subject: [PATCH 023/131] trayp: variant rework and timecode offset default to 0 --- .../plugins/create/create_editorial.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index e47d28447b..643c8a2a84 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -160,7 +160,10 @@ or updating already created. Publishing will create OTIO file. "parent_asset_name": asset_name }) self._get_clip_instances( - otio_timeline, clip_instance_properties) + otio_timeline, + clip_instance_properties, + variant=instance_data["variant"] + ) def _create_otio_instance(self, subset_name, data, pre_create_data): # get path of sequence @@ -198,7 +201,8 @@ or updating already created. Publishing will create OTIO file. def _get_clip_instances( self, otio_timeline, - clip_instance_properties + clip_instance_properties, + variant ): family = "plate" @@ -251,6 +255,7 @@ or updating already created. Publishing will create OTIO file. self.log.info(f"clip_in: {clip_in} | clip_out: {clip_out}") # add offset in case there is any + self.log.debug(f"__ timeline_offset: {timeline_offset}") if timeline_offset: clip_in += timeline_offset clip_out += timeline_offset @@ -269,7 +274,6 @@ or updating already created. Publishing will create OTIO file. frame_end = frame_start + (clip_duration - 1) # subset name - variant = self.get_variant() self.log.info( f"__ variant: {variant}") @@ -292,6 +296,7 @@ or updating already created. Publishing will create OTIO file. # TODO: should loockup shot name for update "asset": parent_asset_name, "name": clip_name, + "task": "", # parent time properties "trackStartFrame": track_start_frame, @@ -370,7 +375,7 @@ or updating already created. Publishing will create OTIO file. # TODO: perhpas better would be timecode and fps input NumberDef( "timeline_offset", - default=900000, + default=0, label="Timeline offset" ), UISeparatorDef() From 08afb46ab4f8cbfcbed481a2a03665c47b29a49f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Jul 2022 12:19:04 +0200 Subject: [PATCH 024/131] trayp: implementing variants from settings --- .../plugins/create/create_editorial.py | 125 +++++++++++------- .../project_settings/traypublisher.json | 24 +++- .../schema_project_traypublisher.json | 26 ++++ 3 files changed, 127 insertions(+), 48 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 643c8a2a84..fdcdd74c88 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -130,9 +130,12 @@ or updating already created. Publishing will create OTIO file. self.default_variants = self._creator_settings["default_variants"] def create(self, subset_name, instance_data, pre_create_data): + allowed_variants = self._get_allowed_variants(pre_create_data) + clip_instance_properties = { k: v for k, v in pre_create_data.items() if k != "sequence_filepath_data" + if k not in self._creator_settings["variants"] } # Create otio editorial instance asset_name = instance_data["asset"] @@ -162,7 +165,9 @@ or updating already created. Publishing will create OTIO file. self._get_clip_instances( otio_timeline, clip_instance_properties, - variant=instance_data["variant"] + variant_name=instance_data["variant"], + variants=allowed_variants + ) def _create_otio_instance(self, subset_name, data, pre_create_data): @@ -202,10 +207,9 @@ or updating already created. Publishing will create OTIO file. self, otio_timeline, clip_instance_properties, - variant + variant_name, + variants ): - family = "plate" - # get clip instance properties parent_asset_name = clip_instance_properties["parent_asset_name"] handle_start = clip_instance_properties["handle_start"] @@ -273,53 +277,73 @@ or updating already created. Publishing will create OTIO file. ) frame_end = frame_start + (clip_duration - 1) - # subset name - self.log.info( - f"__ variant: {variant}") + for family, _vconf in variants.items(): + self.log.debug(f"__ family: {family}") + self.log.debug(f"__ _vconf: {_vconf}") - subset_name = "{}{}".format( - family, variant.capitalize() - ) - label = "{}_{}".format( - clip_name, - subset_name - ) - # create shared new instance data - instance_data = { - "label": label, - "variant": variant, - "family": family, - "families": ["clip"], - "subset": subset_name, + families = ["clip"] - # HACK: just for temporal bug workaround - # TODO: should loockup shot name for update - "asset": parent_asset_name, - "name": clip_name, - "task": "", + # add review family if defined + if _vconf.get("review"): + families.append("review") - # parent time properties - "trackStartFrame": track_start_frame, + # subset name + subset_name = "{}{}".format( + family, variant_name.capitalize() + ) + label = "{}_{}".format( + clip_name, + subset_name + ) - # creator_attributes - "creator_attributes": { - "asset_name": clip_name, - "timeline_offset": timeline_offset, - "workfile_start_frame": workfile_start_frame, - "frameStart": frame_start, - "frameEnd": frame_end, - "fps": fps, - "handle_start": handle_start, - "handle_end": handle_end, - "clipIn": clip_in, - "clipOut": clip_out, - "sourceIn": source_in, - "sourceOut": source_out, + # create shared new instance data + instance_data = { + "label": label, + "variant": variant_name, + "family": family, + "families": families, + "subset": subset_name, + + # HACK: just for temporal bug workaround + # TODO: should loockup shot name for update + "asset": parent_asset_name, + "name": clip_name, + "task": "", + + # parent time properties + "trackStartFrame": track_start_frame, + + # allowed file ext from settings + "filterExt": _vconf["filter_ext"], + + # creator_attributes + "creator_attributes": { + "asset_name": clip_name, + "timeline_offset": timeline_offset, + "workfile_start_frame": workfile_start_frame, + "frameStart": frame_start, + "frameEnd": frame_end, + "fps": fps, + "handle_start": handle_start, + "handle_end": handle_end, + "clipIn": clip_in, + "clipOut": clip_out, + "sourceIn": source_in, + "sourceOut": source_out, + } } - } - c_instance = editorial_clip_creator.create(instance_data, {}) - self.log.debug(f"{pformat(dict(c_instance.data))}") + c_instance = editorial_clip_creator.create( + instance_data, {}) + self.log.debug(f"{pformat(dict(c_instance.data))}") + + def _get_allowed_variants(self, pre_create_data): + self.log.debug(f"__ pre_create_data: {pre_create_data}") + return { + key: value + for key, value in self._creator_settings["variants"].items() + if pre_create_data[key] + } def _validate_clip_for_processing(self, clip): if clip.name is None: @@ -370,15 +394,22 @@ or updating already created. Publishing will create OTIO file. allow_sequences=False, label="Filepath", ), - UILabelDef("Clip instance attributes"), - UISeparatorDef(), # TODO: perhpas better would be timecode and fps input NumberDef( "timeline_offset", default=0, label="Timeline offset" ), + UISeparatorDef(), + UILabelDef("Clip instance attributes"), UISeparatorDef() ] + # add variants swithers + attr_defs.extend( + BoolDef(_var, label=_var) + for _var in self._creator_settings["variants"] + ) + attr_defs.append(UISeparatorDef()) + attr_defs.extend(CLIP_ATTR_DEFS) return attr_defs diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index ef6dc5fec7..7f572cf1fb 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -36,7 +36,29 @@ "editorialSimple": { "default_variants": [ "Main" - ] + ], + "variants": { + "reference": { + "review": true, + "filter_ext": [ + "mov", + "mp4" + ] + }, + "plate": { + "review": false, + "filter_ext": [ + "mov", + "mp4" + ] + }, + "audio": { + "review": false, + "filter_ext": [ + "wav" + ] + } + } } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 11ae0e65a7..38597eeb97 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -103,6 +103,32 @@ "object_type": { "type": "text" } + }, + { + "type": "splitter" + }, + { + "key": "variants", + "label": "Variants", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "boolean", + "key": "review", + "label": "Review", + "default": true + }, + { + "type": "list", + "key": "filter_ext", + "label": "Allowed input file types", + "object_type": "text" + } + ] + } } ] } From 75285652ff7cacd999b4aa87213e7f0b52955c05 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Jul 2022 15:24:00 +0200 Subject: [PATCH 025/131] trayp: reworking settings presets --- .../plugins/create/create_editorial.py | 25 +++++++++++-------- .../project_settings/traypublisher.json | 15 ++++++----- .../schema_project_traypublisher.json | 19 ++++++++++---- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index fdcdd74c88..c68c094218 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -56,9 +56,10 @@ CLIP_ATTR_DEFS = [ class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): - identifier = "editorialClip" + identifier = "editorial_clip" family = "clip" host_name = "traypublisher" + label = "Editorial Clip" def __init__( self, project_settings, *args, **kwargs @@ -102,7 +103,7 @@ class EditorialSimpleCreator(TrayPublishCreator): label = "Editorial Simple" family = "editorial" - identifier = "editorialSimple" + identifier = "editorial_simple" default_variants = [ "main" ] @@ -130,12 +131,14 @@ or updating already created. Publishing will create OTIO file. self.default_variants = self._creator_settings["default_variants"] def create(self, subset_name, instance_data, pre_create_data): - allowed_variants = self._get_allowed_variants(pre_create_data) + allowed_variants = self._get_allowed_family_presets(pre_create_data) clip_instance_properties = { k: v for k, v in pre_create_data.items() if k != "sequence_filepath_data" - if k not in self._creator_settings["variants"] + if k not in [ + i["family"] for i in self._creator_settings["family_presets"] + ] } # Create otio editorial instance asset_name = instance_data["asset"] @@ -220,7 +223,7 @@ or updating already created. Publishing will create OTIO file. self.asset_name_check = [] - editorial_clip_creator = self.create_context.creators["editorialClip"] + editorial_clip_creator = self.create_context.creators["editorial_clip"] tracks = otio_timeline.each_child( descended_from_type=otio.schema.Track @@ -337,12 +340,12 @@ or updating already created. Publishing will create OTIO file. instance_data, {}) self.log.debug(f"{pformat(dict(c_instance.data))}") - def _get_allowed_variants(self, pre_create_data): + def _get_allowed_family_presets(self, pre_create_data): self.log.debug(f"__ pre_create_data: {pre_create_data}") return { - key: value - for key, value in self._creator_settings["variants"].items() - if pre_create_data[key] + preset["family"]: preset + for preset in self._creator_settings["family_presets"] + if pre_create_data[preset["family"]] } def _validate_clip_for_processing(self, clip): @@ -406,8 +409,8 @@ or updating already created. Publishing will create OTIO file. ] # add variants swithers attr_defs.extend( - BoolDef(_var, label=_var) - for _var in self._creator_settings["variants"] + BoolDef(_var["family"], label=_var["family"]) + for _var in self._creator_settings["family_presets"] ) attr_defs.append(UISeparatorDef()) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 7f572cf1fb..2717ab6869 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -33,32 +33,35 @@ } ], "editorial_creators": { - "editorialSimple": { + "editorial_simple": { "default_variants": [ "Main" ], - "variants": { - "reference": { + "family_presets": [ + { + "family": "reference", "review": true, "filter_ext": [ "mov", "mp4" ] }, - "plate": { + { + "family": "plate", "review": false, "filter_ext": [ "mov", "mp4" ] }, - "audio": { + { + "family": "audio", "review": false, "filter_ext": [ "wav" ] } - } + ] } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 38597eeb97..4c0aaf41e7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -90,7 +90,7 @@ { "type": "dict", "collapsible": true, - "key": "editorialSimple", + "key": "editorial_simple", "label": "Editorial simple creator", "use_label_wrap": true, "collapsible_key": true, @@ -108,13 +108,22 @@ "type": "splitter" }, { - "key": "variants", - "label": "Variants", - "type": "dict-modifiable", - "highlight_content": true, + "type": "list", + "key": "family_presets", + "label": "Family presets", "object_type": { "type": "dict", "children": [ + { + "type": "enum", + "key": "family", + "label": "Family", + "enum_items": [ + {"reference": "reference"}, + {"plate": "plate"}, + {"audio": "audio"} + ] + }, { "type": "boolean", "key": "review", From 4b42e66c211a86d7e2e93fffe03b585e75103571 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Jul 2022 15:55:30 +0200 Subject: [PATCH 026/131] trayp: adding `shot` instance --- .../plugins/create/create_editorial.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index c68c094218..6dbcf694cb 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -284,12 +284,6 @@ or updating already created. Publishing will create OTIO file. self.log.debug(f"__ family: {family}") self.log.debug(f"__ _vconf: {_vconf}") - families = ["clip"] - - # add review family if defined - if _vconf.get("review"): - families.append("review") - # subset name subset_name = "{}{}".format( family, variant_name.capitalize() @@ -304,7 +298,7 @@ or updating already created. Publishing will create OTIO file. "label": label, "variant": variant_name, "family": family, - "families": families, + "families": [], "subset": subset_name, # HACK: just for temporal bug workaround @@ -316,9 +310,6 @@ or updating already created. Publishing will create OTIO file. # parent time properties "trackStartFrame": track_start_frame, - # allowed file ext from settings - "filterExt": _vconf["filter_ext"], - # creator_attributes "creator_attributes": { "asset_name": clip_name, @@ -335,6 +326,16 @@ or updating already created. Publishing will create OTIO file. "sourceOut": source_out, } } + # add file extension filter only if it is not shot family + if family != "shot": + families = ["clip"] + # add review family if defined + if _vconf.get("review"): + families.append("review") + instance_data.update({ + "filterExt": _vconf["filter_ext"], + "families": families + }) c_instance = editorial_clip_creator.create( instance_data, {}) @@ -342,11 +343,13 @@ or updating already created. Publishing will create OTIO file. def _get_allowed_family_presets(self, pre_create_data): self.log.debug(f"__ pre_create_data: {pre_create_data}") - return { + return_dict = { preset["family"]: preset for preset in self._creator_settings["family_presets"] if pre_create_data[preset["family"]] } + return_dict["shot"] = {} + return return_dict def _validate_clip_for_processing(self, clip): if clip.name is None: From a3e48b558e9a82334454ae4678a04b860bc9e6ab Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 12 Jul 2022 18:05:42 +0200 Subject: [PATCH 027/131] trayp: has parent on instance data --- .../plugins/create/create_editorial.py | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 6dbcf694cb..d0ce7fa452 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -61,6 +61,8 @@ class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): host_name = "traypublisher" label = "Editorial Clip" + has_parent = False + def __init__( self, project_settings, *args, **kwargs ): @@ -69,6 +71,8 @@ class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): ) def create(self, instance_data, source_data): + self.has_parent = source_data.get("has_parent") + self.log.info(f"instance_data: {instance_data}") subset_name = instance_data["subset"] family = instance_data["family"] @@ -95,7 +99,8 @@ class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): label="Asset name", ) ] - attr_defs.extend(CLIP_ATTR_DEFS) + if not self.has_parent: + attr_defs.extend(CLIP_ATTR_DEFS) return attr_defs @@ -131,7 +136,8 @@ or updating already created. Publishing will create OTIO file. self.default_variants = self._creator_settings["default_variants"] def create(self, subset_name, instance_data, pre_create_data): - allowed_variants = self._get_allowed_family_presets(pre_create_data) + allowed_family_presets = self._get_allowed_family_presets( + pre_create_data) clip_instance_properties = { k: v for k, v in pre_create_data.items() @@ -169,7 +175,7 @@ or updating already created. Publishing will create OTIO file. otio_timeline, clip_instance_properties, variant_name=instance_data["variant"], - variants=allowed_variants + family_presets=allowed_family_presets ) @@ -211,7 +217,7 @@ or updating already created. Publishing will create OTIO file. otio_timeline, clip_instance_properties, variant_name, - variants + family_presets ): # get clip instance properties parent_asset_name = clip_instance_properties["parent_asset_name"] @@ -280,9 +286,12 @@ or updating already created. Publishing will create OTIO file. ) frame_end = frame_start + (clip_duration - 1) - for family, _vconf in variants.items(): + parent_instance_label = None + for _fpreset in family_presets: + source_data = {} + family = _fpreset["family"] self.log.debug(f"__ family: {family}") - self.log.debug(f"__ _vconf: {_vconf}") + self.log.debug(f"__ _fpreset: {_fpreset}") # subset name subset_name = "{}{}".format( @@ -299,6 +308,7 @@ or updating already created. Publishing will create OTIO file. "variant": variant_name, "family": family, "families": [], + "group": family.capitalize(), "subset": subset_name, # HACK: just for temporal bug workaround @@ -327,29 +337,37 @@ or updating already created. Publishing will create OTIO file. } } # add file extension filter only if it is not shot family - if family != "shot": + if family == "shot": + parent_instance_label = label + source_data + else: families = ["clip"] # add review family if defined - if _vconf.get("review"): + if _fpreset.get("review"): families.append("review") instance_data.update({ - "filterExt": _vconf["filter_ext"], - "families": families + "filterExt": _fpreset["filter_ext"], + "families": families, + "creator_attributes": { + "asset_name": clip_name, + "parent_instance": parent_instance_label + } }) + source_data["has_parent"] = True c_instance = editorial_clip_creator.create( - instance_data, {}) + instance_data, source_data) self.log.debug(f"{pformat(dict(c_instance.data))}") def _get_allowed_family_presets(self, pre_create_data): self.log.debug(f"__ pre_create_data: {pre_create_data}") - return_dict = { - preset["family"]: preset - for preset in self._creator_settings["family_presets"] - if pre_create_data[preset["family"]] - } - return_dict["shot"] = {} - return return_dict + return [ + {"family": "shot"}, + *[ + preset for preset in self._creator_settings["family_presets"] + if pre_create_data[preset["family"]] + ] + ] def _validate_clip_for_processing(self, clip): if clip.name is None: From eae292edc856be2e81ff78787929835c5f7fd91c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Jul 2022 10:10:31 +0200 Subject: [PATCH 028/131] trayp: adding selection rather then project --- .../hosts/traypublisher/plugins/create/create_editorial.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index d0ce7fa452..afb1368bef 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -28,7 +28,7 @@ CLIP_ATTR_DEFS = [ EnumDef( "fps", items={ - "from_project": "From project", + "from_selection": "From selection", 23.997: "23.976", 24: "24", 25: "25", @@ -152,7 +152,7 @@ or updating already created. Publishing will create OTIO file. self.log.info(pre_create_data["fps"]) - if pre_create_data["fps"] == "from_project": + if pre_create_data["fps"] == "from_selection": # get asset doc data attributes fps = asset_doc["data"]["fps"] else: @@ -339,7 +339,6 @@ or updating already created. Publishing will create OTIO file. # add file extension filter only if it is not shot family if family == "shot": parent_instance_label = label - source_data else: families = ["clip"] # add review family if defined From feeee29660b33d343459aa4f835b48c0c306a670 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Jul 2022 12:24:56 +0200 Subject: [PATCH 029/131] trayp: adding variant to presets, also renaming `reference` family to `review` --- .../settings/defaults/project_settings/traypublisher.json | 5 ++++- .../projects_schema/schema_project_traypublisher.json | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 2717ab6869..13939a87bc 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -39,7 +39,8 @@ ], "family_presets": [ { - "family": "reference", + "family": "review", + "variant": "Reference", "review": true, "filter_ext": [ "mov", @@ -48,6 +49,7 @@ }, { "family": "plate", + "variant": "", "review": false, "filter_ext": [ "mov", @@ -56,6 +58,7 @@ }, { "family": "audio", + "variant": "", "review": false, "filter_ext": [ "wav" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 4c0aaf41e7..8f1caceb49 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -119,11 +119,17 @@ "key": "family", "label": "Family", "enum_items": [ - {"reference": "reference"}, + {"review": "review"}, {"plate": "plate"}, {"audio": "audio"} ] }, + { + "type": "text", + "key": "variant", + "label": "Variant", + "placeholder": "< Inherited >" + }, { "type": "boolean", "key": "review", From 43fa5f55cb904def429fa87170814cb86738f908 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Jul 2022 12:25:22 +0200 Subject: [PATCH 030/131] trayp: adding audio to review families --- .../traypublisher/plugins/publish/collect_review_family.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py b/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py index 965e251527..54ba12c66c 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py @@ -16,7 +16,8 @@ class CollectReviewFamily( "image", "render", "plate", - "review" + "review", + "audio" ] def process(self, instance): From f94b8e9e3db90ded2d09cade25c2665fe9a4c255 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Jul 2022 12:25:50 +0200 Subject: [PATCH 031/131] trayp: editorial creators swarming --- .../plugins/create/create_editorial.py | 131 ++++++++++++------ 1 file changed, 90 insertions(+), 41 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index afb1368bef..f373d2ac7a 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -55,34 +55,26 @@ CLIP_ATTR_DEFS = [ ] -class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): - identifier = "editorial_clip" - family = "clip" +class EditorialClipInstanceCreatorBase(InvisibleTrayPublishCreator): host_name = "traypublisher" - label = "Editorial Clip" - - has_parent = False def __init__( self, project_settings, *args, **kwargs ): - super(EditorialClipInstanceCreator, self).__init__( + super(EditorialClipInstanceCreatorBase, self).__init__( project_settings, *args, **kwargs ) - def create(self, instance_data, source_data): - self.has_parent = source_data.get("has_parent") - + def create(self, instance_data, source_data=None): self.log.info(f"instance_data: {instance_data}") subset_name = instance_data["subset"] - family = instance_data["family"] - return self._create_instance(subset_name, family, instance_data) + return self._create_instance(subset_name, instance_data) - def _create_instance(self, subset_name, family, data): + def _create_instance(self, subset_name, data): # Create new instance - new_instance = CreatedInstance(family, subset_name, data, self) + new_instance = CreatedInstance(self.family, subset_name, data, self) self.log.info(f"instance_data: {pformat(new_instance.data)}") # Host implementation of storing metadata about instance @@ -92,6 +84,19 @@ class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): return new_instance + +class EditorialShotInstanceCreator(EditorialClipInstanceCreatorBase): + identifier = "editorial_shot" + family = "shot" + label = "Editorial Shot" + + def __init__( + self, project_settings, *args, **kwargs + ): + super(EditorialShotInstanceCreator, self).__init__( + project_settings, *args, **kwargs + ) + def get_instance_attr_defs(self): attr_defs = [ TextDef( @@ -99,11 +104,49 @@ class EditorialClipInstanceCreator(InvisibleTrayPublishCreator): label="Asset name", ) ] - if not self.has_parent: - attr_defs.extend(CLIP_ATTR_DEFS) + attr_defs.extend(CLIP_ATTR_DEFS) return attr_defs +class EditorialPlateInstanceCreator(EditorialClipInstanceCreatorBase): + identifier = "editorial_plate" + family = "plate" + label = "Editorial Plate" + + def __init__( + self, project_settings, *args, **kwargs + ): + super(EditorialPlateInstanceCreator, self).__init__( + project_settings, *args, **kwargs + ) + + +class EditorialAudioInstanceCreator(EditorialClipInstanceCreatorBase): + identifier = "editorial_audio" + family = "audio" + label = "Editorial Audio" + + def __init__( + self, project_settings, *args, **kwargs + ): + super(EditorialAudioInstanceCreator, self).__init__( + project_settings, *args, **kwargs + ) + + +class EditorialReviewInstanceCreator(EditorialClipInstanceCreatorBase): + identifier = "editorial_review" + family = "review" + label = "Editorial Review" + + def __init__( + self, project_settings, *args, **kwargs + ): + super(EditorialReviewInstanceCreator, self).__init__( + project_settings, *args, **kwargs + ) + + class EditorialSimpleCreator(TrayPublishCreator): label = "Editorial Simple" @@ -229,8 +272,6 @@ or updating already created. Publishing will create OTIO file. self.asset_name_check = [] - editorial_clip_creator = self.create_context.creators["editorial_clip"] - tracks = otio_timeline.each_child( descended_from_type=otio.schema.Track ) @@ -287,15 +328,17 @@ or updating already created. Publishing will create OTIO file. frame_end = frame_start + (clip_duration - 1) parent_instance_label = None + parent_instance_id = None for _fpreset in family_presets: - source_data = {} + # get variant name from preset or from inharitance + _variant_name = _fpreset.get("variant") or variant_name family = _fpreset["family"] self.log.debug(f"__ family: {family}") self.log.debug(f"__ _fpreset: {_fpreset}") # subset name subset_name = "{}{}".format( - family, variant_name.capitalize() + family, _variant_name.capitalize() ) label = "{}_{}".format( clip_name, @@ -305,10 +348,8 @@ or updating already created. Publishing will create OTIO file. # create shared new instance data instance_data = { "label": label, - "variant": variant_name, + "variant": _variant_name, "family": family, - "families": [], - "group": family.capitalize(), "subset": subset_name, # HACK: just for temporal bug workaround @@ -319,43 +360,51 @@ or updating already created. Publishing will create OTIO file. # parent time properties "trackStartFrame": track_start_frame, + "timelineOffset": timeline_offset, # creator_attributes "creator_attributes": { "asset_name": clip_name, - "timeline_offset": timeline_offset, "workfile_start_frame": workfile_start_frame, - "frameStart": frame_start, - "frameEnd": frame_end, + "frameStart": int(frame_start), + "frameEnd": int(frame_end), "fps": fps, - "handle_start": handle_start, - "handle_end": handle_end, - "clipIn": clip_in, - "clipOut": clip_out, - "sourceIn": source_in, - "sourceOut": source_out, + "handle_start": int(handle_start), + "handle_end": int(handle_end), + "clipIn": int(clip_in), + "clipOut": int(clip_out), + "sourceIn": int(source_in), + "sourceOut": int(source_out), } } # add file extension filter only if it is not shot family if family == "shot": + c_instance = self.create_context.creators[ + "editorial_shot"].create( + instance_data) parent_instance_label = label + parent_instance_id = c_instance.data["instance_id"] else: - families = ["clip"] # add review family if defined - if _fpreset.get("review"): - families.append("review") instance_data.update({ "filterExt": _fpreset["filter_ext"], - "families": families, + "parent_instance_id": parent_instance_id, "creator_attributes": { - "asset_name": clip_name, "parent_instance": parent_instance_label + }, + "publish_attributes": { + "CollectReviewFamily": { + "add_review_family": _fpreset.get("review") + } } }) - source_data["has_parent"] = True - c_instance = editorial_clip_creator.create( - instance_data, source_data) + creator_identifier = f"editorial_{family}" + editorial_clip_creator = self.create_context.creators[ + creator_identifier] + c_instance = editorial_clip_creator.create( + instance_data) + self.log.debug(f"{pformat(dict(c_instance.data))}") def _get_allowed_family_presets(self, pre_create_data): @@ -435,4 +484,4 @@ or updating already created. Publishing will create OTIO file. attr_defs.append(UISeparatorDef()) attr_defs.extend(CLIP_ATTR_DEFS) - return attr_defs + return attr_defs \ No newline at end of file From 3cb9748613ef8cf8fc9a563e8df93c11b275a7d6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 14 Jul 2022 13:23:40 +0200 Subject: [PATCH 032/131] trayp: editorial settings for shot metadata --- .../project_settings/traypublisher.json | 30 +++ .../schema_project_traypublisher.json | 185 ++++++++++++++---- 2 files changed, 180 insertions(+), 35 deletions(-) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index ee8f90df7f..93f6420c21 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -232,6 +232,36 @@ "default_variants": [ "Main" ], + "clip_name_tokenizer": { + "_sequence_": "(sc\\d{3})", + "_shot_": "(sh\\d{3})" + }, + "shot_rename": { + "enabled": true, + "shot_rename_template": "{project[code]}_{_sequence_}_{_shot_}" + }, + "shot_hierarchy": { + "enabled": true, + "parents_path": "{project}/{folder}/{sequence}", + "parents": [ + { + "type": "project", + "name": "projekt", + "value": "{projekt[name]}" + }, + { + "type": "folder", + "name": "folder", + "value": "shots" + }, + { + "type": "sequence", + "name": "sequence", + "value": "{_sequence_}" + } + ] + }, + "shot_add_tasks": {}, "family_presets": [ { "family": "review", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 8f1caceb49..8d95cb19a9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -108,42 +108,157 @@ "type": "splitter" }, { - "type": "list", - "key": "family_presets", - "label": "Family presets", - "object_type": { - "type": "dict", - "children": [ - { - "type": "enum", - "key": "family", - "label": "Family", - "enum_items": [ - {"review": "review"}, - {"plate": "plate"}, - {"audio": "audio"} - ] - }, - { - "type": "text", - "key": "variant", - "label": "Variant", - "placeholder": "< Inherited >" - }, - { - "type": "boolean", - "key": "review", - "label": "Review", - "default": true - }, - { - "type": "list", - "key": "filter_ext", - "label": "Allowed input file types", - "object_type": "text" + "type": "collapsible-wrap", + "label": "Shot metadata creator", + "collapsible": true, + "collapsed": true, + "children": [ + { + "key": "clip_name_tokenizer", + "label": "Clip name tokenizer", + "type": "dict-modifiable", + "highlight_content": true, + "tooltip": "Using Regex expression to create tokens. \nThose can be used later in \"Shot rename\" creator \nor \"Shot hierarchy\". \n\nTokens should be decorated with \"_\" on each side", + "object_type": { + "type": "text" } - ] - } + }, + { + "type": "dict", + "key": "shot_rename", + "label": "Shot rename", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "shot_rename_template", + "label": "Shot rename template", + "tooltip":"Template only supports Anatomy keys and Tokens \nfrom \"Clip name tokenizer\"" + } + ] + }, + { + "type": "dict", + "key": "shot_hierarchy", + "label": "Shot hierarchy", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "text", + "key": "parents_path", + "label": "Parents path template", + "tooltip": "Using keys from \"Token to parent convertor\" or tokens directly" + }, + { + "key": "parents", + "label": "Token to parent convertor", + "type": "list", + "highlight_content": true, + "tooltip": "The left side is key to be used in template. \nThe right is value build from Tokens comming from \n\"Clip name tokenizer\"", + "object_type": { + "type": "dict", + "children": [ + { + "type": "enum", + "key": "type", + "label": "Parent type", + "enum_items": [ + {"project": "Project"}, + {"folder": "Folder"}, + {"episode": "Episode"}, + {"sequence": "Sequence"} + ] + }, + { + "type": "text", + "key": "name", + "label": "Parent token name", + "tooltip": "Unique name used in \"Parent path template\"" + }, + { + "type": "text", + "key": "value", + "label": "Parent name value", + "tooltip": "Template where any text, Anatomy keys and Tokens could be used" + } + ] + } + } + ] + }, + { + "key": "shot_add_tasks", + "label": "Add tasks to shot", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "task-types-enum", + "key": "type", + "label": "Task type" + } + ] + } + } + ] + }, + { + "type": "collapsible-wrap", + "label": "Shot's subset creator", + "collapsible": true, + "collapsed": true, + "children": [ + { + "type": "list", + "key": "family_presets", + "label": "Family presets", + "object_type": { + "type": "dict", + "children": [ + { + "type": "enum", + "key": "family", + "label": "Family", + "enum_items": [ + {"review": "review"}, + {"plate": "plate"}, + {"audio": "audio"} + ] + }, + { + "type": "text", + "key": "variant", + "label": "Variant", + "placeholder": "< Inherited >" + }, + { + "type": "boolean", + "key": "review", + "label": "Review", + "default": true + }, + { + "type": "list", + "key": "filter_ext", + "label": "Allowed input file types", + "object_type": "text" + } + ] + } + } + ] } ] } From 72e07dd0717f958f9ade887bc3aef8715d8343ea Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 14 Jul 2022 15:22:03 +0200 Subject: [PATCH 033/131] trayp: editorial refactory code --- .../plugins/create/create_editorial.py | 306 +++++++++++------- 1 file changed, 193 insertions(+), 113 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index f373d2ac7a..d591256f8c 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -212,12 +212,12 @@ or updating already created. Publishing will create OTIO file. # Create all clip instances clip_instance_properties.update({ "fps": fps, - "parent_asset_name": asset_name + "parent_asset_name": asset_name, + "variant": instance_data["variant"] }) self._get_clip_instances( otio_timeline, clip_instance_properties, - variant_name=instance_data["variant"], family_presets=allowed_family_presets ) @@ -259,17 +259,8 @@ or updating already created. Publishing will create OTIO file. self, otio_timeline, clip_instance_properties, - variant_name, family_presets ): - # get clip instance properties - parent_asset_name = clip_instance_properties["parent_asset_name"] - handle_start = clip_instance_properties["handle_start"] - handle_end = clip_instance_properties["handle_end"] - timeline_offset = clip_instance_properties["timeline_offset"] - workfile_start_frame = clip_instance_properties["workfile_start_frame"] - fps = clip_instance_properties["fps"] - self.asset_name_check = [] tracks = otio_timeline.each_child( @@ -294,118 +285,207 @@ or updating already created. Publishing will create OTIO file. if not self._validate_clip_for_processing(clip): continue - # basic unique asset name - clip_name = os.path.splitext(clip.name)[0].lower() - name = f"{parent_asset_name.split('_')[0]}_{clip_name}" - - # make sure the name is unique - self._validate_name_uniqueness(name) - - # frame ranges data - clip_in = clip.range_in_parent().start_time.value - clip_in += track_start_frame - clip_out = clip.range_in_parent().end_time_inclusive().value - clip_out += track_start_frame - self.log.info(f"clip_in: {clip_in} | clip_out: {clip_out}") - - # add offset in case there is any - self.log.debug(f"__ timeline_offset: {timeline_offset}") - if timeline_offset: - clip_in += timeline_offset - clip_out += timeline_offset - - clip_duration = clip.duration().value - self.log.info(f"clip duration: {clip_duration}") - - source_in = clip.trimmed_range().start_time.value - source_out = source_in + clip_duration - - # define starting frame for future shot - frame_start = ( - clip_in if workfile_start_frame is None - else workfile_start_frame + base_instance_data = self._get_base_instance_data( + clip, + clip_instance_properties, + track_start_frame ) - frame_end = frame_start + (clip_duration - 1) - parent_instance_label = None - parent_instance_id = None + parenting_data = { + "instance_label": None, + "instance_id": None + } for _fpreset in family_presets: - # get variant name from preset or from inharitance - _variant_name = _fpreset.get("variant") or variant_name - family = _fpreset["family"] - self.log.debug(f"__ family: {family}") - self.log.debug(f"__ _fpreset: {_fpreset}") - - # subset name - subset_name = "{}{}".format( - family, _variant_name.capitalize() - ) - label = "{}_{}".format( - clip_name, - subset_name + instance = self._make_subset_instance( + _fpreset["family"], + _fpreset, + deepcopy(base_instance_data), + parenting_data ) + self.log.debug(f"{pformat(dict(instance.data))}") - # create shared new instance data - instance_data = { - "label": label, - "variant": _variant_name, - "family": family, - "subset": subset_name, + def _make_subset_instance( + self, + _fpreset, + family, + future_instance_data, + parenting_data + ): + label = self._make_subset_naming( + _fpreset["family"], + _fpreset, + future_instance_data + ) - # HACK: just for temporal bug workaround - # TODO: should loockup shot name for update - "asset": parent_asset_name, - "name": clip_name, - "task": "", + # add file extension filter only if it is not shot family + if family == "shot": + c_instance = self.create_context.creators[ + "editorial_shot"].create( + future_instance_data) + parenting_data = { + "instance_label": label, + "instance_id": c_instance.data["instance_id"] + } - # parent time properties - "trackStartFrame": track_start_frame, - "timelineOffset": timeline_offset, - - # creator_attributes - "creator_attributes": { - "asset_name": clip_name, - "workfile_start_frame": workfile_start_frame, - "frameStart": int(frame_start), - "frameEnd": int(frame_end), - "fps": fps, - "handle_start": int(handle_start), - "handle_end": int(handle_end), - "clipIn": int(clip_in), - "clipOut": int(clip_out), - "sourceIn": int(source_in), - "sourceOut": int(source_out), - } + else: + # add review family if defined + future_instance_data.update({ + "filterExt": _fpreset["filter_ext"], + "parent_instance_id": parenting_data["instance_id"], + "creator_attributes": { + "parent_instance": parenting_data["instance_label"] + }, + "publish_attributes": { + "CollectReviewFamily": { + "add_review_family": _fpreset.get("review") } - # add file extension filter only if it is not shot family - if family == "shot": - c_instance = self.create_context.creators[ - "editorial_shot"].create( - instance_data) - parent_instance_label = label - parent_instance_id = c_instance.data["instance_id"] - else: - # add review family if defined - instance_data.update({ - "filterExt": _fpreset["filter_ext"], - "parent_instance_id": parent_instance_id, - "creator_attributes": { - "parent_instance": parent_instance_label - }, - "publish_attributes": { - "CollectReviewFamily": { - "add_review_family": _fpreset.get("review") - } - } - }) + } + }) - creator_identifier = f"editorial_{family}" - editorial_clip_creator = self.create_context.creators[ - creator_identifier] - c_instance = editorial_clip_creator.create( - instance_data) + creator_identifier = f"editorial_{family}" + editorial_clip_creator = self.create_context.creators[ + creator_identifier] + c_instance = editorial_clip_creator.create( + future_instance_data) - self.log.debug(f"{pformat(dict(c_instance.data))}") + return c_instance + + def _make_subset_naming( + self, + family, + _fpreset, + future_instance_data + ): + shot_name = future_instance_data["shotName"] + variant_name = future_instance_data["variant"] + + # get variant name from preset or from inharitance + _variant_name = _fpreset.get("variant") or variant_name + + self.log.debug(f"__ family: {family}") + self.log.debug(f"__ _fpreset: {_fpreset}") + + # subset name + subset_name = "{}{}".format( + family, _variant_name.capitalize() + ) + label = "{}_{}".format( + shot_name, + subset_name + ) + + future_instance_data.update({ + "family": family, + "label": label, + "variant": _variant_name, + "subset": subset_name, + }) + + return label + + def _get_base_instance_data( + self, + clip, + clip_instance_properties, + track_start_frame, + ): + # get clip instance properties + parent_asset_name = clip_instance_properties["parent_asset_name"] + handle_start = clip_instance_properties["handle_start"] + handle_end = clip_instance_properties["handle_end"] + timeline_offset = clip_instance_properties["timeline_offset"] + workfile_start_frame = clip_instance_properties["workfile_start_frame"] + fps = clip_instance_properties["fps"] + variant_name = clip_instance_properties["variant"] + + shot_name = self._get_clip_name(clip, parent_asset_name) + + timing_data = self._get_timing_data( + clip, + timeline_offset, + track_start_frame, + workfile_start_frame + ) + + # create creator attributes + creator_attributes = { + "asset_name": shot_name, + "workfile_start_frame": workfile_start_frame, + "fps": fps, + "handle_start": int(handle_start), + "handle_end": int(handle_end) + } + creator_attributes.update(timing_data) + + # create shared new instance data + base_instance_data = { + "shotName": shot_name, + "variant": variant_name, + + # HACK: just for temporal bug workaround + # TODO: should loockup shot name for update + "asset": parent_asset_name, + "task": "", + # parent time properties + "trackStartFrame": track_start_frame, + "timelineOffset": timeline_offset, + # creator_attributes + "creator_attributes": creator_attributes + } + + return base_instance_data + + def _get_timing_data( + self, + clip, + timeline_offset, + track_start_frame, + workfile_start_frame + ): + # frame ranges data + clip_in = clip.range_in_parent().start_time.value + clip_in += track_start_frame + clip_out = clip.range_in_parent().end_time_inclusive().value + clip_out += track_start_frame + self.log.info(f"clip_in: {clip_in} | clip_out: {clip_out}") + + # add offset in case there is any + self.log.debug(f"__ timeline_offset: {timeline_offset}") + if timeline_offset: + clip_in += timeline_offset + clip_out += timeline_offset + + clip_duration = clip.duration().value + self.log.info(f"clip duration: {clip_duration}") + + source_in = clip.trimmed_range().start_time.value + source_out = source_in + clip_duration + + # define starting frame for future shot + frame_start = ( + clip_in if workfile_start_frame is None + else workfile_start_frame + ) + frame_end = frame_start + (clip_duration - 1) + + return { + "frameStart": int(frame_start), + "frameEnd": int(frame_end), + "clipIn": int(clip_in), + "clipOut": int(clip_out), + "sourceIn": int(source_in), + "sourceOut": int(source_out) + } + + def _get_clip_name(self, clip, selected_asset_name): + # basic unique asset name + clip_name = os.path.splitext(clip.name)[0].lower() + name = f"{selected_asset_name.split('_')[0]}_{clip_name}" + + # make sure the name is unique + self._validate_name_uniqueness(name) + + return clip_name def _get_allowed_family_presets(self, pre_create_data): self.log.debug(f"__ pre_create_data: {pre_create_data}") From a5477a15c80e5cbdd3a29541beed2c0e2eb03f8d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 14 Jul 2022 15:39:02 +0200 Subject: [PATCH 034/131] trayp: debugging after refactory --- .../plugins/create/create_editorial.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index d591256f8c..1ff729cf65 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -295,9 +295,11 @@ or updating already created. Publishing will create OTIO file. "instance_label": None, "instance_id": None } + self.log.info( + f"Creating subsets from presets: \n{pformat(family_presets)}") + for _fpreset in family_presets: instance = self._make_subset_instance( - _fpreset["family"], _fpreset, deepcopy(base_instance_data), parenting_data @@ -307,12 +309,11 @@ or updating already created. Publishing will create OTIO file. def _make_subset_instance( self, _fpreset, - family, future_instance_data, parenting_data ): + family = _fpreset["family"] label = self._make_subset_naming( - _fpreset["family"], _fpreset, future_instance_data ) @@ -322,10 +323,10 @@ or updating already created. Publishing will create OTIO file. c_instance = self.create_context.creators[ "editorial_shot"].create( future_instance_data) - parenting_data = { + parenting_data.update({ "instance_label": label, "instance_id": c_instance.data["instance_id"] - } + }) else: # add review family if defined @@ -352,12 +353,12 @@ or updating already created. Publishing will create OTIO file. def _make_subset_naming( self, - family, _fpreset, future_instance_data ): shot_name = future_instance_data["shotName"] variant_name = future_instance_data["variant"] + family = _fpreset["family"] # get variant name from preset or from inharitance _variant_name = _fpreset.get("variant") or variant_name From 8bfcbad8eb6f741e2d38203e63af3e9c53a1e26f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 14 Jul 2022 17:39:33 +0200 Subject: [PATCH 035/131] trayp: wip hierarchical data and rename --- openpype/hosts/traypublisher/api/editorial.py | 178 ++++++++++++++++++ .../plugins/create/create_editorial.py | 34 ++-- .../schema_project_traypublisher.json | 3 +- 3 files changed, 201 insertions(+), 14 deletions(-) create mode 100644 openpype/hosts/traypublisher/api/editorial.py diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py new file mode 100644 index 0000000000..4637d6d1df --- /dev/null +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -0,0 +1,178 @@ +import re +from copy import deepcopy + +from openpype.client import get_asset_by_id + + +class ShotMetadataSover: + """Collecting hierarchy context from `parents` and `hierarchy` data + present in `clip` family instances coming from the request json data file + + It will add `hierarchical_context` into each instance for integrate + plugins to be able to create needed parents for the context if they + don't exist yet + """ + # presets + clip_name_tokenizer = None + shot_rename = True + shot_hierarchy = None + shot_add_tasks = None + + def __init__(self, creator_settings): + self.clip_name_tokenizer = creator_settings["clip_name_tokenizer"] + self.shot_rename = creator_settings["shot_rename"] + self.shot_hierarchy = creator_settings["shot_hierarchy"] + self.shot_add_tasks = creator_settings["shot_add_tasks"] + + def convert_to_entity(self, key, value): + # ftrack compatible entity types + types = {"shot": "Shot", + "folder": "Folder", + "episode": "Episode", + "sequence": "Sequence", + "track": "Sequence", + } + # convert to entity type + entity_type = types.get(key, None) + + # return if any + if entity_type: + return {"entity_type": entity_type, "entity_name": value} + + def _rename_template(self, clip_name, source_data): + if self.clip_name_tokenizer: + search_text = "" + parent_name = source_data["assetEntity"]["name"] + + search_text += parent_name + clip_name + source_data["anatomy_data"].update({"clip_name": clip_name}) + for type, pattern in self.clip_name_tokenizer.items(): + p = re.compile(pattern) + match = p.findall(search_text) + if not match: + continue + source_data["anatomy_data"][type] = match[-1] + + # format to new shot name + return self.shot_rename[ + "shot_rename_template"].format( + **source_data["anatomy_data"]) + + def _create_hierarchy(self, source_data): + asset_doc = source_data["selected_asset_doc"] + project_doc = source_data["project_doc"] + + project_name = project_doc["name"] + visual_hierarchy = [asset_doc] + current_doc = asset_doc + + # TODO: refactory withou the while + while True: + visual_parent_id = current_doc["data"]["visualParent"] + visual_parent = None + if visual_parent_id: + visual_parent = get_asset_by_id(project_name, visual_parent_id) + + if not visual_parent: + visual_hierarchy.append(project_doc) + break + visual_hierarchy.append(visual_parent) + current_doc = visual_parent + + # add current selection context hierarchy from standalonepublisher + parents = [] + parents.extend( + { + "entity_type": entity["data"]["entityType"], + "entity_name": entity["name"] + } + for entity in reversed(visual_hierarchy) + ) + + _hierarchy = [] + if self.shot_hierarchy.get("enabled"): + parent_template_patern = re.compile(r"\{([a-z]*?)\}") + # fill the parents parts from presets + shot_hierarchy = deepcopy(self.shot_hierarchy) + hierarchy_parents = shot_hierarchy["parents"] + + # fill parent keys data template from anatomy data + for parent_key in hierarchy_parents: + hierarchy_parents[parent_key] = hierarchy_parents[ + parent_key].format(**source_data["anatomy_data"]) + + for _index, _parent in enumerate( + shot_hierarchy["parents_path"].split("/")): + parent_filled = _parent.format(**hierarchy_parents) + parent_key = parent_template_patern.findall(_parent).pop() + + # in case SP context is set to the same folder + if (_index == 0) and ("folder" in parent_key) \ + and (parents[-1]["entity_name"] == parent_filled): + self.log.debug(f" skipping : {parent_filled}") + continue + + # in case first parent is project then start parents from start + if (_index == 0) and ("project" in parent_key): + self.log.debug("rebuilding parents from scratch") + project_parent = parents[0] + parents = [project_parent] + self.log.debug(f"project_parent: {project_parent}") + self.log.debug(f"parents: {parents}") + continue + + prnt = self.convert_to_entity( + parent_key, parent_filled) + parents.append(prnt) + _hierarchy.append(parent_filled) + + # convert hierarchy to string + hierarchy_path = "/".join(_hierarchy) + + output_data = { + "hierarchy": hierarchy_path, + "parents": parents + } + # print + self.log.debug(f"__ hierarchy_path: {hierarchy_path}") + self.log.debug(f"__ parents: {parents}") + + output_data["tasks"] = self._generate_tasks_from_settings(project_doc) + + return output_data + + def _generate_tasks_from_settings(self, project_doc): + tasks_to_add = {} + if self.shot_add_tasks: + project_tasks = project_doc["config"]["tasks"] + for task_name, task_data in self.shot_add_tasks.items(): + _task_data = deepcopy(task_data) + + # check if task type in project task types + if _task_data["type"] in project_tasks.keys(): + tasks_to_add[task_name] = _task_data + else: + raise KeyError( + "Missing task type `{}` for `{}` is not" + " existing in `{}``".format( + _task_data["type"], + task_name, + list(project_tasks.keys()) + ) + ) + + return tasks_to_add + + def generate_data(self, clip_name, source_data): + self.log.info(f"_ source_data: {source_data}") + + # match clip to shot name at start + shot_name = clip_name + + if self.shot_rename["enabled"]: + shot_name = self._rename_template(clip_name, source_data) + self.log.info(f"Renamed shot name: {shot_name}") + + hierarchy_data = self._create_hierarchy(source_data) + + return shot_name, hierarchy_data diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 1ff729cf65..7672bb6222 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -2,12 +2,17 @@ import os from copy import deepcopy from pprint import pformat import opentimelineio as otio -from openpype.client import get_asset_by_name +from openpype.client import ( + get_asset_by_name, + get_project +) from openpype.hosts.traypublisher.api.plugin import ( TrayPublishCreator, InvisibleTrayPublishCreator ) - +from openpype.hosts.traypublisher.api.editorial import ( + ShotMetadataSover +) from openpype.pipeline import CreatedInstance @@ -173,6 +178,7 @@ or updating already created. Publishing will create OTIO file. ) # get this creator settings by identifier self._creator_settings = editorial_creators.get(self.identifier) + self._shot_metadata_solver = ShotMetadataSover(self._creator_settings) # try to set main attributes from settings if self._creator_settings.get("default_variants"): @@ -399,7 +405,19 @@ or updating already created. Publishing will create OTIO file. fps = clip_instance_properties["fps"] variant_name = clip_instance_properties["variant"] - shot_name = self._get_clip_name(clip, parent_asset_name) + # basic unique asset name + clip_name = os.path.splitext(clip.name)[0].lower() + + shot_name, shot_metadata = self._shot_metadata_solver.generate_data( + clip_name, + { + "anatomy_data": anatomy_data, + "selected_asset_doc": get_asset_by_name(parent_asset_name), + "project_doc": get_project(self.project_name) + } + ) + + self._validate_name_uniqueness(shot_name) timing_data = self._get_timing_data( clip, @@ -478,16 +496,6 @@ or updating already created. Publishing will create OTIO file. "sourceOut": int(source_out) } - def _get_clip_name(self, clip, selected_asset_name): - # basic unique asset name - clip_name = os.path.splitext(clip.name)[0].lower() - name = f"{selected_asset_name.split('_')[0]}_{clip_name}" - - # make sure the name is unique - self._validate_name_uniqueness(name) - - return clip_name - def _get_allowed_family_presets(self, pre_create_data): self.log.debug(f"__ pre_create_data: {pre_create_data}") return [ diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 8d95cb19a9..3af3839c6f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -207,7 +207,8 @@ { "type": "task-types-enum", "key": "type", - "label": "Task type" + "label": "Task type", + "multiselection": false } ] } From bf82969c1422cfc87379b75d859cb93e5cf983c6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 15 Jul 2022 09:38:51 +0200 Subject: [PATCH 036/131] trayp: shot metadata solver final --- openpype/hosts/traypublisher/api/editorial.py | 240 ++++++++++-------- 1 file changed, 133 insertions(+), 107 deletions(-) diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index 4637d6d1df..d6cc99f87c 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -12,6 +12,9 @@ class ShotMetadataSover: plugins to be able to create needed parents for the context if they don't exist yet """ + + NO_DECOR_PATERN = re.compile(r"\{([a-z]*?)\}") + # presets clip_name_tokenizer = None shot_rename = True @@ -24,49 +27,106 @@ class ShotMetadataSover: self.shot_hierarchy = creator_settings["shot_hierarchy"] self.shot_add_tasks = creator_settings["shot_add_tasks"] - def convert_to_entity(self, key, value): - # ftrack compatible entity types - types = {"shot": "Shot", - "folder": "Folder", - "episode": "Episode", - "sequence": "Sequence", - "track": "Sequence", - } - # convert to entity type - entity_type = types.get(key, None) + def _rename_template(self, data): + # format to new shot name + return self.shot_rename[ + "shot_rename_template"].format(**data) - # return if any - if entity_type: - return {"entity_type": entity_type, "entity_name": value} + def _generate_tokens(self, clip_name, source_data): + output_data = deepcopy(source_data["anatomy_data"]) + output_data["clip_name"] = clip_name - def _rename_template(self, clip_name, source_data): - if self.clip_name_tokenizer: - search_text = "" - parent_name = source_data["assetEntity"]["name"] + if not self.clip_name_tokenizer: + return output_data - search_text += parent_name + clip_name - source_data["anatomy_data"].update({"clip_name": clip_name}) - for type, pattern in self.clip_name_tokenizer.items(): - p = re.compile(pattern) - match = p.findall(search_text) - if not match: - continue - source_data["anatomy_data"][type] = match[-1] + parent_name = source_data["selected_asset_doc"]["name"] - # format to new shot name - return self.shot_rename[ - "shot_rename_template"].format( - **source_data["anatomy_data"]) + search_text = parent_name + clip_name - def _create_hierarchy(self, source_data): - asset_doc = source_data["selected_asset_doc"] - project_doc = source_data["project_doc"] + for token_key, pattern in self.clip_name_tokenizer.items(): + p = re.compile(pattern) + match = p.findall(search_text) + if not match: + continue + # QUESTION:how to refactory `match[-1]` to some better way? + output_data[token_key] = match[-1] + return output_data + + def _create_parents_from_settings(self, parents, data): + + # fill the parents parts from presets + shot_hierarchy = deepcopy(self.shot_hierarchy) + hierarchy_parents = shot_hierarchy["parents"] + + # fill parent keys data template from anatomy data + _parent_tokens_formating_data = { + parent_token["name"]: parent_token["value"].format(**data) + for parent_token in hierarchy_parents + } + _parent_tokens_type = { + parent_token["name"]: parent_token["type"] + for parent_token in hierarchy_parents + } + for _index, _parent in enumerate( + shot_hierarchy["parents_path"].split("/") + ): + # format parent token with value which is formated + parent_name = _parent.format( + **_parent_tokens_formating_data) + parent_token_name = ( + self.NO_DECOR_PATERN.findall(_parent).pop()) + + if not parent_token_name: + raise KeyError( + f"Parent token is not found in: `{_parent}`") + + # find parent type + parent_token_type = _parent_tokens_type[parent_token_name] + + # in case selected context is set to the same asset + if ( + _index == 0 + and parents[-1]["entity_name"] == parent_name + ): + self.log.debug(f" skipping : {parent_name}") + continue + + # in case first parent is project then start parents from start + if ( + _index == 0 + and parent_token_type == "project" + ): + self.log.debug("rebuilding parents from scratch") + project_parent = parents[0] + parents = [project_parent] + continue + + parents.append({ + "entity_type": parent_token_type, + "entity_name": parent_name + }) + + self.log.debug(f"__ parents: {parents}") + + return parents + + def _create_hierarchy_path(self, parents): + return "/".join( + [p for p in parents if p["entity_type"] != "project"] + ) if parents else "" + + def _get_parents_from_selected_asset( + self, + asset_doc, + project_doc + ): project_name = project_doc["name"] visual_hierarchy = [asset_doc] current_doc = asset_doc - # TODO: refactory withou the while + # looping trought all available visual parents + # if they are not available anymore than it breaks while True: visual_parent_id = current_doc["data"]["visualParent"] visual_parent = None @@ -79,100 +139,66 @@ class ShotMetadataSover: visual_hierarchy.append(visual_parent) current_doc = visual_parent - # add current selection context hierarchy from standalonepublisher - parents = [] - parents.extend( + # add current selection context hierarchy + return [ { "entity_type": entity["data"]["entityType"], "entity_name": entity["name"] } for entity in reversed(visual_hierarchy) - ) - - _hierarchy = [] - if self.shot_hierarchy.get("enabled"): - parent_template_patern = re.compile(r"\{([a-z]*?)\}") - # fill the parents parts from presets - shot_hierarchy = deepcopy(self.shot_hierarchy) - hierarchy_parents = shot_hierarchy["parents"] - - # fill parent keys data template from anatomy data - for parent_key in hierarchy_parents: - hierarchy_parents[parent_key] = hierarchy_parents[ - parent_key].format(**source_data["anatomy_data"]) - - for _index, _parent in enumerate( - shot_hierarchy["parents_path"].split("/")): - parent_filled = _parent.format(**hierarchy_parents) - parent_key = parent_template_patern.findall(_parent).pop() - - # in case SP context is set to the same folder - if (_index == 0) and ("folder" in parent_key) \ - and (parents[-1]["entity_name"] == parent_filled): - self.log.debug(f" skipping : {parent_filled}") - continue - - # in case first parent is project then start parents from start - if (_index == 0) and ("project" in parent_key): - self.log.debug("rebuilding parents from scratch") - project_parent = parents[0] - parents = [project_parent] - self.log.debug(f"project_parent: {project_parent}") - self.log.debug(f"parents: {parents}") - continue - - prnt = self.convert_to_entity( - parent_key, parent_filled) - parents.append(prnt) - _hierarchy.append(parent_filled) - - # convert hierarchy to string - hierarchy_path = "/".join(_hierarchy) - - output_data = { - "hierarchy": hierarchy_path, - "parents": parents - } - # print - self.log.debug(f"__ hierarchy_path: {hierarchy_path}") - self.log.debug(f"__ parents: {parents}") - - output_data["tasks"] = self._generate_tasks_from_settings(project_doc) - - return output_data + ] def _generate_tasks_from_settings(self, project_doc): tasks_to_add = {} - if self.shot_add_tasks: - project_tasks = project_doc["config"]["tasks"] - for task_name, task_data in self.shot_add_tasks.items(): - _task_data = deepcopy(task_data) - # check if task type in project task types - if _task_data["type"] in project_tasks.keys(): - tasks_to_add[task_name] = _task_data - else: - raise KeyError( - "Missing task type `{}` for `{}` is not" - " existing in `{}``".format( - _task_data["type"], - task_name, - list(project_tasks.keys()) - ) + project_tasks = project_doc["config"]["tasks"] + for task_name, task_data in self.shot_add_tasks.items(): + _task_data = deepcopy(task_data) + + # check if task type in project task types + if _task_data["type"] in project_tasks.keys(): + tasks_to_add[task_name] = _task_data + else: + raise KeyError( + "Missing task type `{}` for `{}` is not" + " existing in `{}``".format( + _task_data["type"], + task_name, + list(project_tasks.keys()) ) + ) return tasks_to_add def generate_data(self, clip_name, source_data): self.log.info(f"_ source_data: {source_data}") + tasks = {} + asset_doc = source_data["selected_asset_doc"] + project_doc = source_data["project_doc"] + # match clip to shot name at start shot_name = clip_name + # parse all tokens and generate formating data + formating_data = self._generate_tokens(shot_name, source_data) + + # generate parents from selected asset + parents = self._get_parents_from_selected_asset(asset_doc, project_doc) + if self.shot_rename["enabled"]: - shot_name = self._rename_template(clip_name, source_data) + shot_name = self._rename_template(clip_name, formating_data) self.log.info(f"Renamed shot name: {shot_name}") - hierarchy_data = self._create_hierarchy(source_data) + if self.shot_hierarchy["enabled"]: + parents = self._create_parents_from_settings(formating_data) - return shot_name, hierarchy_data + if self.shot_add_tasks: + tasks = self._generate_tasks_from_settings( + project_doc) + + return shot_name, { + "hierarchy": self._create_hierarchy_path(parents), + "parents": parents, + "tasks": tasks + } From 7f9cdaaa0e90b8c00dcdc91a523d09e1f8d32459 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 15 Jul 2022 11:43:19 +0200 Subject: [PATCH 037/131] trayp: updating settings --- .../defaults/project_settings/traypublisher.json | 10 +++++----- .../projects_schema/schema_project_traypublisher.json | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 93f6420c21..82c82c79e9 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -245,17 +245,17 @@ "parents_path": "{project}/{folder}/{sequence}", "parents": [ { - "type": "project", - "name": "projekt", - "value": "{projekt[name]}" + "type": "Project", + "name": "project", + "value": "{project[name]}" }, { - "type": "folder", + "type": "Folder", "name": "folder", "value": "shots" }, { - "type": "sequence", + "type": "Sequence", "name": "sequence", "value": "{_sequence_}" } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 3af3839c6f..909ee02b04 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -173,10 +173,10 @@ "key": "type", "label": "Parent type", "enum_items": [ - {"project": "Project"}, - {"folder": "Folder"}, - {"episode": "Episode"}, - {"sequence": "Sequence"} + {"Project": "Project"}, + {"Folder": "Folder"}, + {"Episode": "Episode"}, + {"Sequence": "Sequence"} ] }, { From a8e4fdba5fa11f1c0a66e90f9e6ed9429212e9d9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 15 Jul 2022 11:43:39 +0200 Subject: [PATCH 038/131] trayp: editorial with hierarchy and parents --- openpype/hosts/traypublisher/api/editorial.py | 69 ++++++++++++++----- .../plugins/create/create_editorial.py | 21 ++++-- 2 files changed, 70 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index d6cc99f87c..713f1b5c6c 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -2,7 +2,7 @@ import re from copy import deepcopy from openpype.client import get_asset_by_id - +from openpype.pipeline.create import CreatorError class ShotMetadataSover: """Collecting hierarchy context from `parents` and `hierarchy` data @@ -21,16 +21,27 @@ class ShotMetadataSover: shot_hierarchy = None shot_add_tasks = None - def __init__(self, creator_settings): + def __init__(self, creator_settings, logger): self.clip_name_tokenizer = creator_settings["clip_name_tokenizer"] self.shot_rename = creator_settings["shot_rename"] self.shot_hierarchy = creator_settings["shot_hierarchy"] self.shot_add_tasks = creator_settings["shot_add_tasks"] + self.log = logger + def _rename_template(self, data): - # format to new shot name - return self.shot_rename[ - "shot_rename_template"].format(**data) + shot_rename_template = self.shot_rename[ + "shot_rename_template"] + try: + # format to new shot name + return shot_rename_template.format(**data) + except KeyError as _E: + raise CreatorError(( + "Make sure all keys are correct in settings: \n\n" + f"From template string {shot_rename_template} > " + f"`{_E}` has no equivalent in \n" + f"{list(data.keys())} input formating keys!" + )) def _generate_tokens(self, clip_name, source_data): output_data = deepcopy(source_data["anatomy_data"]) @@ -47,7 +58,13 @@ class ShotMetadataSover: p = re.compile(pattern) match = p.findall(search_text) if not match: - continue + raise CreatorError(( + "Make sure regex expression is correct: \n\n" + f"From settings '{token_key}' key " + f"with '{pattern}' expression, \n" + f"is not able to find anything in '{search_text}'!" + )) + # QUESTION:how to refactory `match[-1]` to some better way? output_data[token_key] = match[-1] @@ -60,10 +77,17 @@ class ShotMetadataSover: hierarchy_parents = shot_hierarchy["parents"] # fill parent keys data template from anatomy data - _parent_tokens_formating_data = { - parent_token["name"]: parent_token["value"].format(**data) - for parent_token in hierarchy_parents - } + try: + _parent_tokens_formating_data = { + parent_token["name"]: parent_token["value"].format(**data) + for parent_token in hierarchy_parents + } + except KeyError as _E: + raise CreatorError(( + "Make sure all keys are correct in settings: \n" + f"`{_E}` has no equivalent in \n{list(data.keys())}" + )) + _parent_tokens_type = { parent_token["name"]: parent_token["type"] for parent_token in hierarchy_parents @@ -72,8 +96,17 @@ class ShotMetadataSover: shot_hierarchy["parents_path"].split("/") ): # format parent token with value which is formated - parent_name = _parent.format( - **_parent_tokens_formating_data) + try: + parent_name = _parent.format( + **_parent_tokens_formating_data) + except KeyError as _E: + raise CreatorError(( + "Make sure all keys are correct in settings: \n\n" + f"From template string {shot_hierarchy['parents_path']} > " + f"`{_E}` has no equivalent in \n" + f"{list(_parent_tokens_formating_data.keys())} parents" + )) + parent_token_name = ( self.NO_DECOR_PATERN.findall(_parent).pop()) @@ -95,7 +128,7 @@ class ShotMetadataSover: # in case first parent is project then start parents from start if ( _index == 0 - and parent_token_type == "project" + and parent_token_type == "Project" ): self.log.debug("rebuilding parents from scratch") project_parent = parents[0] @@ -113,7 +146,10 @@ class ShotMetadataSover: def _create_hierarchy_path(self, parents): return "/".join( - [p for p in parents if p["entity_type"] != "project"] + [ + p["entity_name"] for p in parents + if p["entity_type"] != "Project" + ] ) if parents else "" def _get_parents_from_selected_asset( @@ -187,11 +223,12 @@ class ShotMetadataSover: parents = self._get_parents_from_selected_asset(asset_doc, project_doc) if self.shot_rename["enabled"]: - shot_name = self._rename_template(clip_name, formating_data) + shot_name = self._rename_template(formating_data) self.log.info(f"Renamed shot name: {shot_name}") if self.shot_hierarchy["enabled"]: - parents = self._create_parents_from_settings(formating_data) + parents = self._create_parents_from_settings( + parents, formating_data) if self.shot_add_tasks: tasks = self._generate_tasks_from_settings( diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 7672bb6222..6bcc692240 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -178,7 +178,8 @@ or updating already created. Publishing will create OTIO file. ) # get this creator settings by identifier self._creator_settings = editorial_creators.get(self.identifier) - self._shot_metadata_solver = ShotMetadataSover(self._creator_settings) + self._shot_metadata_solver = ShotMetadataSover( + self._creator_settings, self.log) # try to set main attributes from settings if self._creator_settings.get("default_variants"): @@ -407,13 +408,22 @@ or updating already created. Publishing will create OTIO file. # basic unique asset name clip_name = os.path.splitext(clip.name)[0].lower() + project_doc = get_project(self.project_name) shot_name, shot_metadata = self._shot_metadata_solver.generate_data( clip_name, { - "anatomy_data": anatomy_data, - "selected_asset_doc": get_asset_by_name(parent_asset_name), - "project_doc": get_project(self.project_name) + "anatomy_data": { + "project": { + "name": self.project_name, + "code": project_doc["data"]["code"] + }, + "parent": parent_asset_name, + "app": self.host_name + }, + "selected_asset_doc": get_asset_by_name( + self.project_name, parent_asset_name), + "project_doc": project_doc } ) @@ -429,6 +439,7 @@ or updating already created. Publishing will create OTIO file. # create creator attributes creator_attributes = { "asset_name": shot_name, + "Parent hierarchy path": shot_metadata["hierarchy"], "workfile_start_frame": workfile_start_frame, "fps": fps, "handle_start": int(handle_start), @@ -451,6 +462,8 @@ or updating already created. Publishing will create OTIO file. # creator_attributes "creator_attributes": creator_attributes } + # add hierarchy shot metadata + base_instance_data.update(shot_metadata) return base_instance_data From fe68a07a90d057526b5c65854528d67ce637c629 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 15 Jul 2022 14:16:43 +0200 Subject: [PATCH 039/131] trayp: update editorial creator --- .../hosts/traypublisher/plugins/create/create_editorial.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 6bcc692240..ffff5de70a 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -255,7 +255,8 @@ or updating already created. Publishing will create OTIO file. # Pass precreate data to creator attributes data.update({ - "sequence_file_path": file_path + "sequenceFilePath": file_path, + "otioTimeline": otio.adapters.write_to_string(otio_timeline) }) self._create_instance(self.family, subset_name, data) @@ -324,6 +325,7 @@ or updating already created. Publishing will create OTIO file. _fpreset, future_instance_data ) + future_instance_data["label"] = label # add file extension filter only if it is not shot family if family == "shot": From b22b28edbc3a6af4e9e68f801a5a5c4a5c13be27 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 15 Jul 2022 14:17:02 +0200 Subject: [PATCH 040/131] trayp: publishing editorial --- .../publish/collect_editorial_instances.py | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py index 6521c97774..c088709a61 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py @@ -1,15 +1,17 @@ import os from pprint import pformat import pyblish.api +import opentimelineio as otio class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): """Collect data for instances created by settings creators.""" label = "Collect Editorial Instances" - order = pyblish.api.CollectorOrder - 0.49 + order = pyblish.api.CollectorOrder hosts = ["traypublisher"] + families = ["editorial"] def process(self, instance): @@ -18,34 +20,27 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): if "representations" not in instance.data: instance.data["representations"] = [] - repres = instance.data["representations"] - self.log.debug( - pformat(dict(instance.data)) - ) - creator_attributes = instance.data["creator_attributes"] - filepath_item = creator_attributes["filepath"] - self.log.info(filepath_item) - filepaths = [ - os.path.join(filepath_item["directory"], filename) - for filename in filepath_item["filenames"] - ] - instance.data["sourceFilepaths"] = filepaths - instance.data["stagingDir"] = filepath_item["directory"] + fpath = instance.data["sequenceFilePath"] + otio_timeline_string = instance.data.pop("otioTimeline") + otio_timeline = otio.adapters.read_from_string( + otio_timeline_string) - filenames = filepath_item["filenames"] - _, ext = os.path.splitext(filenames[0]) - ext = ext[1:] - if len(filenames) == 1: - filenames = filenames[0] + instance.context.data["otioTimeline"] = otio_timeline - repres.append({ - "ext": ext, - "name": ext, - "stagingDir": filepath_item["directory"], - "files": filenames + self.log.info(fpath) + + instance.data["stagingDir"] = os.path.dirname(fpath) + + _, ext = os.path.splitext(fpath) + + instance.data["representations"].append({ + "ext": ext[1:], + "name": ext[1:], + "stagingDir": instance.data["stagingDir"], + "files": os.path.basename(fpath) }) self.log.debug("Created Simple Settings instance {}".format( - instance.data + pformat(instance.data) )) From fb586feaf3dec0eeabe370f512426c03df8d7289 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 15 Jul 2022 14:17:31 +0200 Subject: [PATCH 041/131] general: label could be set from instance data --- openpype/plugins/publish/collect_from_create_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py index d2be633cbe..e070cc411d 100644 --- a/openpype/plugins/publish/collect_from_create_context.py +++ b/openpype/plugins/publish/collect_from_create_context.py @@ -44,7 +44,7 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): "subset": subset, "asset": in_data["asset"], "task": in_data["task"], - "label": subset, + "label": in_data.get("label") or subset, "name": subset, "family": in_data["family"], "families": instance_families, From 3cc78c2f98d3fd652dbe9d865d54df86bf6cd688 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 15 Jul 2022 17:56:30 +0200 Subject: [PATCH 042/131] trayp: rename `invisible` to `hidden` --- openpype/hosts/traypublisher/api/plugin.py | 4 ++-- .../hosts/traypublisher/plugins/create/create_editorial.py | 4 ++-- openpype/pipeline/create/__init__.py | 4 ++-- openpype/pipeline/create/creator_plugins.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index cb2f86eed7..3a268be55d 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,6 +1,6 @@ from openpype.pipeline.create import ( Creator, - InvisibleCreator, + HiddenCreator, CreatedInstance ) from openpype.lib import ( @@ -15,7 +15,7 @@ from .pipeline import ( ) -class InvisibleTrayPublishCreator(InvisibleCreator): +class HiddenTrayPublishCreator(HiddenCreator): host_name = "traypublisher" def collect_instances(self): diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index ffff5de70a..8f7101385c 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -8,7 +8,7 @@ from openpype.client import ( ) from openpype.hosts.traypublisher.api.plugin import ( TrayPublishCreator, - InvisibleTrayPublishCreator + HiddenTrayPublishCreator ) from openpype.hosts.traypublisher.api.editorial import ( ShotMetadataSover @@ -60,7 +60,7 @@ CLIP_ATTR_DEFS = [ ] -class EditorialClipInstanceCreatorBase(InvisibleTrayPublishCreator): +class EditorialClipInstanceCreatorBase(HiddenTrayPublishCreator): host_name = "traypublisher" def __init__( diff --git a/openpype/pipeline/create/__init__.py b/openpype/pipeline/create/__init__.py index cd01c53cf5..bd196ccfd1 100644 --- a/openpype/pipeline/create/__init__.py +++ b/openpype/pipeline/create/__init__.py @@ -7,7 +7,7 @@ from .creator_plugins import ( BaseCreator, Creator, AutoCreator, - InvisibleCreator, + HiddenCreator, discover_creator_plugins, discover_legacy_creator_plugins, @@ -36,7 +36,7 @@ __all__ = ( "BaseCreator", "Creator", "AutoCreator", - "InvisibleCreator", + "HiddenCreator", "discover_creator_plugins", "discover_legacy_creator_plugins", diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py index 4d953a0605..8cb161de20 100644 --- a/openpype/pipeline/create/creator_plugins.py +++ b/openpype/pipeline/create/creator_plugins.py @@ -416,7 +416,7 @@ class Creator(BaseCreator): return self.pre_create_attr_defs -class InvisibleCreator(BaseCreator): +class HiddenCreator(BaseCreator): @abstractmethod def create(self, instance_data, source_data): pass From 29de28cb5371ced19fdf35368ce8e4a9f4f8b074 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 15 Jul 2022 17:57:05 +0200 Subject: [PATCH 043/131] trayp: editorial publishing wip --- openpype/hosts/traypublisher/api/editorial.py | 1 + .../plugins/create/create_editorial.py | 49 +++- .../plugins/publish/collect_clip_instances.py | 32 +++ .../publish/collect_editorial_instances.py | 8 +- .../publish/collect_editorial_resources.py | 271 ++++++++++++++++++ .../plugins/publish/collect_shot_instances.py | 163 +++++++++++ .../publish/extract_trim_video_audio.py | 2 +- .../plugins/publish/validate_asset_docs.py | 4 + 8 files changed, 516 insertions(+), 14 deletions(-) create mode 100644 openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py create mode 100644 openpype/hosts/traypublisher/plugins/publish/collect_editorial_resources.py create mode 100644 openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py rename openpype/{hosts/standalonepublisher => }/plugins/publish/extract_trim_video_audio.py (98%) diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index 713f1b5c6c..948e05ec61 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -4,6 +4,7 @@ from copy import deepcopy from openpype.client import get_asset_by_id from openpype.pipeline.create import CreatorError + class ShotMetadataSover: """Collecting hierarchy context from `parents` and `hierarchy` data present in `clip` family instances coming from the request json data file diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 8f7101385c..b87253a705 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -232,14 +232,10 @@ or updating already created. Publishing will create OTIO file. def _create_otio_instance(self, subset_name, data, pre_create_data): # get path of sequence file_path_data = pre_create_data["sequence_filepath_data"] + media_path_data = pre_create_data["media_filepaths_data"] - if len(file_path_data["filenames"]) == 0: - raise FileExistsError("File path was not added") - - file_path = os.path.join( - file_path_data["directory"], file_path_data["filenames"][0]) - - self.log.info(f"file_path: {file_path}") + file_path = self._get_path_from_file_data(file_path_data) + media_path = self._get_path_from_file_data(media_path_data) # get editorial sequence file into otio timeline object extension = os.path.splitext(file_path)[1] @@ -256,6 +252,7 @@ or updating already created. Publishing will create OTIO file. # Pass precreate data to creator attributes data.update({ "sequenceFilePath": file_path, + "editorialSourcePath": media_path, "otioTimeline": otio.adapters.write_to_string(otio_timeline) }) @@ -263,6 +260,18 @@ or updating already created. Publishing will create OTIO file. return otio_timeline + def _get_path_from_file_data(self, file_path_data): + # TODO: just temporarly solving only one media file + if isinstance(file_path_data, list): + file_path_data = file_path_data.pop() + + if len(file_path_data["filenames"]) == 0: + raise FileExistsError( + f"File path was not added: {file_path_data}") + + return os.path.join( + file_path_data["directory"], file_path_data["filenames"][0]) + def _get_clip_instances( self, otio_timeline, @@ -303,11 +312,14 @@ or updating already created. Publishing will create OTIO file. "instance_label": None, "instance_id": None } - self.log.info( - f"Creating subsets from presets: \n{pformat(family_presets)}") + self.log.info(( + "Creating subsets from presets: \n" + f"{pformat(family_presets)}" + )) for _fpreset in family_presets: instance = self._make_subset_instance( + clip, _fpreset, deepcopy(base_instance_data), parenting_data @@ -316,6 +328,7 @@ or updating already created. Publishing will create OTIO file. def _make_subset_instance( self, + clip, _fpreset, future_instance_data, parenting_data @@ -329,6 +342,8 @@ or updating already created. Publishing will create OTIO file. # add file extension filter only if it is not shot family if family == "shot": + future_instance_data["otioClip"] = ( + otio.adapters.write_to_string(clip)) c_instance = self.create_context.creators[ "editorial_shot"].create( future_instance_data) @@ -458,6 +473,7 @@ or updating already created. Publishing will create OTIO file. # TODO: should loockup shot name for update "asset": parent_asset_name, "task": "", + # parent time properties "trackStartFrame": track_start_frame, "timelineOffset": timeline_offset, @@ -568,7 +584,20 @@ or updating already created. Publishing will create OTIO file. ".fcpxml" ], allow_sequences=False, - label="Filepath", + single_item=True, + label="Sequence file", + ), + FileDef( + "media_filepaths_data", + folders=False, + extensions=[ + ".mov", + ".mp4", + ".wav" + ], + allow_sequences=False, + single_item=False, + label="Media files", ), # TODO: perhpas better would be timecode and fps input NumberDef( diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py new file mode 100644 index 0000000000..e3dfb1512a --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py @@ -0,0 +1,32 @@ +from pprint import pformat +import pyblish.api + + +class CollectClipInstance(pyblish.api.InstancePlugin): + """Collect clip instances and resolve its parent""" + + label = "Collect Clip Instances" + order = pyblish.api.CollectorOrder + + hosts = ["traypublisher"] + families = ["plate", "review", "audio"] + + def process(self, instance): + creator_identifier = instance.data["creator_identifier"] + if "editorial" not in creator_identifier: + return + + instance.data["families"].append("clip") + + parent_instance_id = instance.data["parent_instance_id"] + edit_shared_data = instance.context.data["editorialSharedData"] + instance.data.update( + edit_shared_data[parent_instance_id] + ) + + if "editorialSourcePath" in instance.context.data.keys(): + instance.data["editorialSourcePath"] = ( + instance.context.data["editorialSourcePath"]) + instance.data["families"].append("trimming") + + self.log.debug(pformat(instance.data)) \ No newline at end of file diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py index c088709a61..e181d0abe5 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_instances.py @@ -4,11 +4,11 @@ import pyblish.api import opentimelineio as otio -class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): +class CollectEditorialInstance(pyblish.api.InstancePlugin): """Collect data for instances created by settings creators.""" label = "Collect Editorial Instances" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder - 0.1 hosts = ["traypublisher"] families = ["editorial"] @@ -27,6 +27,8 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): otio_timeline_string) instance.context.data["otioTimeline"] = otio_timeline + instance.context.data["editorialSourcePath"] = ( + instance.data["editorialSourcePath"]) self.log.info(fpath) @@ -41,6 +43,6 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): "files": os.path.basename(fpath) }) - self.log.debug("Created Simple Settings instance {}".format( + self.log.debug("Created Editorial Instance {}".format( pformat(instance.data) )) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_resources.py b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_resources.py new file mode 100644 index 0000000000..33a852e7a5 --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_resources.py @@ -0,0 +1,271 @@ +import os +import re +import tempfile +import pyblish.api +from copy import deepcopy +import clique + + +class CollectInstanceResources(pyblish.api.InstancePlugin): + """Collect instance's resources""" + + # must be after `CollectInstances` + order = pyblish.api.CollectorOrder + label = "Collect Editorial Resources" + hosts = ["standalonepublisher"] + families = ["clip"] + + def process(self, instance): + self.context = instance.context + self.log.info(f"Processing instance: {instance}") + self.new_instances = [] + subset_files = dict() + subset_dirs = list() + anatomy = self.context.data["anatomy"] + anatomy_data = deepcopy(self.context.data["anatomyData"]) + anatomy_data.update({"root": anatomy.roots}) + + subset = instance.data["subset"] + clip_name = instance.data["clipName"] + + editorial_source_root = instance.data["editorialSourceRoot"] + editorial_source_path = instance.data["editorialSourcePath"] + + # if `editorial_source_path` then loop through + if editorial_source_path: + # add family if mov or mp4 found which is longer for + # cutting `trimming` to enable `ExtractTrimmingVideoAudio` plugin + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) + instance.data["stagingDir"] = staging_dir + instance.data["families"] += ["trimming"] + return + + # if template pattern in path then fill it with `anatomy_data` + if "{" in editorial_source_root: + editorial_source_root = editorial_source_root.format( + **anatomy_data) + + self.log.debug(f"root: {editorial_source_root}") + # loop `editorial_source_root` and find clip name in folders + # and look for any subset name alternatives + for root, dirs, _files in os.walk(editorial_source_root): + # search only for directories related to clip name + correct_clip_dir = None + for _d_search in dirs: + # avoid all non clip dirs + if _d_search not in clip_name: + continue + # found correct dir for clip + correct_clip_dir = _d_search + + # continue if clip dir was not found + if not correct_clip_dir: + continue + + clip_dir_path = os.path.join(root, correct_clip_dir) + subset_files_items = list() + # list content of clip dir and search for subset items + for subset_item in os.listdir(clip_dir_path): + # avoid all items which are not defined as subsets by name + if subset not in subset_item: + continue + + subset_item_path = os.path.join( + clip_dir_path, subset_item) + # if it is dir store it to `subset_dirs` list + if os.path.isdir(subset_item_path): + subset_dirs.append(subset_item_path) + + # if it is file then store it to `subset_files` list + if os.path.isfile(subset_item_path): + subset_files_items.append(subset_item_path) + + if subset_files_items: + subset_files.update({clip_dir_path: subset_files_items}) + + # break the loop if correct_clip_dir was captured + # no need to cary on if correct folder was found + if correct_clip_dir: + break + + if subset_dirs: + # look all dirs and check for subset name alternatives + for _dir in subset_dirs: + instance_data = deepcopy( + {k: v for k, v in instance.data.items()}) + sub_dir = os.path.basename(_dir) + # if subset name is only alternative then create new instance + if sub_dir != subset: + instance_data = self.duplicate_instance( + instance_data, subset, sub_dir) + + # create all representations + self.create_representations( + os.listdir(_dir), instance_data, _dir) + + if sub_dir == subset: + self.new_instances.append(instance_data) + # instance.data.update(instance_data) + + if subset_files: + unique_subset_names = list() + root_dir = list(subset_files.keys()).pop() + files_list = subset_files[root_dir] + search_pattern = f"({subset}[A-Za-z0-9]+)(?=[\\._\\s])" + for _file in files_list: + pattern = re.compile(search_pattern) + match = pattern.findall(_file) + if not match: + continue + match_subset = match.pop() + if match_subset in unique_subset_names: + continue + unique_subset_names.append(match_subset) + + self.log.debug(f"unique_subset_names: {unique_subset_names}") + + for _un_subs in unique_subset_names: + instance_data = self.duplicate_instance( + instance.data, subset, _un_subs) + + # create all representations + self.create_representations( + [os.path.basename(f) for f in files_list + if _un_subs in f], + instance_data, root_dir) + + # remove the original instance as it had been used only + # as template and is duplicated + self.context.remove(instance) + + # create all instances in self.new_instances into context + for new_instance in self.new_instances: + _new_instance = self.context.create_instance( + new_instance["name"]) + _new_instance.data.update(new_instance) + + def duplicate_instance(self, instance_data, subset, new_subset): + + new_instance_data = dict() + for _key, _value in instance_data.items(): + new_instance_data[_key] = _value + if not isinstance(_value, str): + continue + if subset in _value: + new_instance_data[_key] = _value.replace( + subset, new_subset) + + self.log.info(f"Creating new instance: {new_instance_data['name']}") + self.new_instances.append(new_instance_data) + return new_instance_data + + def create_representations( + self, files_list, instance_data, staging_dir): + """ Create representations from Collection object + """ + # collecting frames for later frame start/end reset + frames = list() + # break down Collection object to collections and reminders + collections, remainder = clique.assemble(files_list) + # add staging_dir to instance_data + instance_data["stagingDir"] = staging_dir + # add representations to instance_data + instance_data["representations"] = list() + + collection_head_name = None + # loop through collections and create representations + for _collection in collections: + ext = _collection.tail[1:] + collection_head_name = _collection.head + frame_start = list(_collection.indexes)[0] + frame_end = list(_collection.indexes)[-1] + repre_data = { + "frameStart": frame_start, + "frameEnd": frame_end, + "name": ext, + "ext": ext, + "files": [item for item in _collection], + "stagingDir": staging_dir + } + + if instance_data.get("keepSequence"): + repre_data_keep = deepcopy(repre_data) + instance_data["representations"].append(repre_data_keep) + + if "review" in instance_data["families"]: + repre_data.update({ + "thumbnail": True, + "frameStartFtrack": frame_start, + "frameEndFtrack": frame_end, + "step": 1, + "fps": self.context.data.get("fps"), + "name": "review", + "tags": ["review", "ftrackreview", "delete"], + }) + instance_data["representations"].append(repre_data) + + # add to frames for frame range reset + frames.append(frame_start) + frames.append(frame_end) + + # loop through reminders and create representations + for _reminding_file in remainder: + ext = os.path.splitext(_reminding_file)[-1][1:] + if ext not in instance_data["extensions"]: + continue + if collection_head_name and ( + (collection_head_name + ext) not in _reminding_file + ) and (ext in ["mp4", "mov"]): + self.log.info(f"Skipping file: {_reminding_file}") + continue + frame_start = 1 + frame_end = 1 + + repre_data = { + "name": ext, + "ext": ext, + "files": _reminding_file, + "stagingDir": staging_dir + } + + # exception for thumbnail + if "thumb" in _reminding_file: + repre_data.update({ + 'name': "thumbnail", + 'thumbnail': True + }) + + # exception for mp4 preview + if ext in ["mp4", "mov"]: + frame_start = 0 + frame_end = ( + (instance_data["frameEnd"] - instance_data["frameStart"]) + + 1) + # add review ftrack family into families + for _family in ["review", "ftrack"]: + if _family not in instance_data["families"]: + instance_data["families"].append(_family) + repre_data.update({ + "frameStart": frame_start, + "frameEnd": frame_end, + "frameStartFtrack": frame_start, + "frameEndFtrack": frame_end, + "step": 1, + "fps": self.context.data.get("fps"), + "name": "review", + "thumbnail": True, + "tags": ["review", "ftrackreview", "delete"], + }) + + # add to frames for frame range reset only if no collection + if not collections: + frames.append(frame_start) + frames.append(frame_end) + + instance_data["representations"].append(repre_data) + + # reset frame start / end + instance_data["frameStart"] = min(frames) + instance_data["frameEnd"] = max(frames) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py new file mode 100644 index 0000000000..5abafa498d --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -0,0 +1,163 @@ +from pprint import pformat +import pyblish.api +import opentimelineio as otio + + +class CollectShotInstance(pyblish.api.InstancePlugin): + """Collect shot instances and resolve its parent""" + + label = "Collect Shot Instances" + order = pyblish.api.CollectorOrder - 0.09 + + hosts = ["traypublisher"] + families = ["shot"] + + SHARED_KEYS = [ + "asset", + "fps", + "frameStart", + "frameEnd", + "clipIn", + "clipOut", + "sourceIn", + "sourceOut" + ] + + def process(self, instance): + self.log.debug(pformat(instance.data)) + + creator_identifier = instance.data["creator_identifier"] + if "editorial" not in creator_identifier: + return + + # get otio clip object + otio_clip = self._get_otio_clip(instance) + instance.data["otioClip"] = otio_clip + + # first solve the inputs from creator attr + data = self._solve_inputs_to_data(instance) + instance.data.update(data) + + # distribute all shared keys to clips instances + self._distribute_shared_data(instance) + self._solve_hierarchy_context(instance) + + self.log.debug(pformat(instance.data)) + + def _get_otio_clip(self, instance): + context = instance.context + # convert otio clip from string to object + otio_clip_string = instance.data.pop("otioClip") + otio_clip = otio.adapters.read_from_string( + otio_clip_string) + + otio_timeline = context.data["otioTimeline"] + + clips = [ + clip for clip in otio_timeline.each_child( + descended_from_type=otio.schema.Clip) + if clip.name == otio_clip.name + ] + self.log.debug(otio_timeline.each_child( + descended_from_type=otio.schema.Clip)) + + otio_clip = clips.pop() + self.log.debug(f"__ otioclip.parent: {otio_clip.parent}") + + return otio_clip + + def _distribute_shared_data(self, instance): + context = instance.context + + instance_id = instance.data["instance_id"] + + if not context.data.get("editorialSharedData"): + context.data["editorialSharedData"] = {} + + context.data["editorialSharedData"][instance_id] = { + _k: _v for _k, _v in instance.data.items() + if _k in self.SHARED_KEYS + } + + def _solve_inputs_to_data(self, instance): + _cr_attrs = instance.data["creator_attributes"] + workfile_start_frame = _cr_attrs["workfile_start_frame"] + frame_start = _cr_attrs["frameStart"] + frame_end = _cr_attrs["frameEnd"] + frame_dur = frame_end - frame_start + + return { + "asset": _cr_attrs["asset_name"], + "fps": float(_cr_attrs["fps"]), + "handleStart": _cr_attrs["handle_start"], + "handleEnd": _cr_attrs["handle_end"], + "frameStart": workfile_start_frame, + "frameEnd": workfile_start_frame + frame_dur, + "clipIn": _cr_attrs["clipIn"], + "clipOut": _cr_attrs["clipOut"], + "sourceIn": _cr_attrs["sourceIn"], + "sourceOut": _cr_attrs["sourceOut"], + "workfileFrameStart": workfile_start_frame + } + + def _solve_hierarchy_context(self, instance): + context = instance.context + + final_context = ( + context.data["hierarchyContext"] + if context.data.get("hierarchyContext") + else {} + ) + + name = instance.data["asset"] + + # get handles + handle_start = int(instance.data["handleStart"]) + handle_end = int(instance.data["handleEnd"]) + + in_info = { + "entity_type": "Shot", + "custom_attributes": { + "handleStart": handle_start, + "handleEnd": handle_end, + "frameStart": instance.data["frameStart"], + "frameEnd": instance.data["frameEnd"], + "clipIn": instance.data["clipIn"], + "clipOut": instance.data["clipOut"], + "fps": instance.data["fps"] + }, + "tasks": instance.data["tasks"] + } + + parents = instance.data.get('parents', []) + self.log.debug(f"parents: {pformat(parents)}") + + actual = {name: in_info} + + for parent in reversed(parents): + parent_name = parent["entity_name"] + next_dict = { + parent_name: { + "entity_type": parent["entity_type"], + "childs": actual + } + } + actual = next_dict + + final_context = self._update_dict(final_context, actual) + + # adding hierarchy context to instance + context.data["hierarchyContext"] = final_context + self.log.debug(pformat(final_context)) + + def _update_dict(self, ex_dict, new_dict): + for key in ex_dict: + if key in new_dict and isinstance(ex_dict[key], dict): + new_dict[key] = self._update_dict(ex_dict[key], new_dict[key]) + else: + if ex_dict.get(key) and new_dict.get(key): + continue + else: + new_dict[key] = ex_dict[key] + + return new_dict \ No newline at end of file diff --git a/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py b/openpype/plugins/publish/extract_trim_video_audio.py similarity index 98% rename from openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py rename to openpype/plugins/publish/extract_trim_video_audio.py index 51dc84e9a2..b0c30283d9 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py +++ b/openpype/plugins/publish/extract_trim_video_audio.py @@ -14,7 +14,7 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): # must be before `ExtractThumbnailSP` order = pyblish.api.ExtractorOrder - 0.01 label = "Extract Trim Video/Audio" - hosts = ["standalonepublisher"] + hosts = ["standalonepublisher", "traypublisher"] families = ["clip", "trimming"] # make sure it is enabled only if at least both families are available diff --git a/openpype/plugins/publish/validate_asset_docs.py b/openpype/plugins/publish/validate_asset_docs.py index bc1f9b9e6c..daeb442f28 100644 --- a/openpype/plugins/publish/validate_asset_docs.py +++ b/openpype/plugins/publish/validate_asset_docs.py @@ -24,6 +24,10 @@ class ValidateAssetDocs(pyblish.api.InstancePlugin): if instance.data.get("assetEntity"): self.log.info("Instance has set asset document in its data.") + elif "editorial" in instance.data.get("creator_identifier", ""): + # skip if it is editorial + self.log.info("Editorial instance is no need to check...") + else: raise PublishValidationError(( "Instance \"{}\" doesn't have asset document " From c9ad287c7b4521da8c56ccf4d197c3e3befed61f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Jul 2022 16:40:14 +0200 Subject: [PATCH 044/131] trayp: fix import after develop merge --- openpype/hosts/traypublisher/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 0683b149ec..a0c42a55b1 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,5 +1,5 @@ from openpype.lib.attribute_definitions import FileDef -from openpype.pipeline import ( +from openpype.pipeline.create import ( Creator, HiddenCreator, CreatedInstance From ec7e441cea078bdeccf448b69855fd810a627d0c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Jul 2022 17:06:27 +0200 Subject: [PATCH 045/131] trayp: changing extension propagation --- .../defaults/project_settings/traypublisher.json | 14 +++----------- .../schema_project_traypublisher.json | 12 ++++++++---- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index c360dc2a13..2cb7d358ed 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -276,27 +276,19 @@ "family": "review", "variant": "Reference", "review": true, - "filter_ext": [ - "mov", - "mp4" - ] + "output_file_type": ".mp4" }, { "family": "plate", "variant": "", "review": false, - "filter_ext": [ - "mov", - "mp4" - ] + "output_file_type": ".mov" }, { "family": "audio", "variant": "", "review": false, - "filter_ext": [ - "wav" - ] + "output_file_type": ".wav" } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index f77d5fbe06..7c61aeed50 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -256,10 +256,14 @@ "default": true }, { - "type": "list", - "key": "filter_ext", - "label": "Allowed input file types", - "object_type": "text" + "type": "enum", + "key": "output_file_type", + "label": "Integrating file type", + "enum_items": [ + {".mp4": "MP4"}, + {".mov": "MOV"}, + {".wav": "WAV"} + ] } ] } From 3845c90f95f073657f3a07b0d5df7ebf4e99e8c7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Jul 2022 17:06:56 +0200 Subject: [PATCH 046/131] trayp: solving an issue with ocio media source --- .../plugins/create/create_editorial.py | 139 ++++++++++++++---- 1 file changed, 114 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index b87253a705..28e58804c7 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -17,6 +17,8 @@ from openpype.hosts.traypublisher.api.editorial import ( from openpype.pipeline import CreatedInstance from openpype.lib import ( + get_ffprobe_data, + FileDef, TextDef, NumberDef, @@ -212,9 +214,16 @@ or updating already created. Publishing will create OTIO file. "fps": fps }) + # get path of sequence + sequence_path_data = pre_create_data["sequence_filepath_data"] + media_path_data = pre_create_data["media_filepaths_data"] + + sequence_path = self._get_path_from_file_data(sequence_path_data) + media_path = self._get_path_from_file_data(media_path_data) + # get otio timeline - otio_timeline = self._create_otio_instance( - subset_name, instance_data, pre_create_data) + otio_timeline = self._create_otio_timeline( + sequence_path, fps) # Create all clip instances clip_instance_properties.update({ @@ -222,43 +231,52 @@ or updating already created. Publishing will create OTIO file. "parent_asset_name": asset_name, "variant": instance_data["variant"] }) + + # create clip instances self._get_clip_instances( otio_timeline, + media_path, clip_instance_properties, family_presets=allowed_family_presets ) - def _create_otio_instance(self, subset_name, data, pre_create_data): - # get path of sequence - file_path_data = pre_create_data["sequence_filepath_data"] - media_path_data = pre_create_data["media_filepaths_data"] - - file_path = self._get_path_from_file_data(file_path_data) - media_path = self._get_path_from_file_data(media_path_data) - - # get editorial sequence file into otio timeline object - extension = os.path.splitext(file_path)[1] - kwargs = {} - if extension == ".edl": - # EDL has no frame rate embedded so needs explicit - # frame rate else 24 is asssumed. - kwargs["rate"] = data["fps"] - - self.log.info(f"kwargs: {kwargs}") - otio_timeline = otio.adapters.read_from_file( - file_path, **kwargs) + # create otio editorial instance + self._create_otio_instance( + subset_name, instance_data, + sequence_path, media_path, + otio_timeline + ) + def _create_otio_instance( + self, + subset_name, + data, + sequence_path, + media_path, + otio_timeline + ): # Pass precreate data to creator attributes data.update({ - "sequenceFilePath": file_path, + "sequenceFilePath": sequence_path, "editorialSourcePath": media_path, "otioTimeline": otio.adapters.write_to_string(otio_timeline) }) self._create_instance(self.family, subset_name, data) - return otio_timeline + def _create_otio_timeline(self, sequence_path, fps): + # get editorial sequence file into otio timeline object + extension = os.path.splitext(sequence_path)[1] + + kwargs = {} + if extension == ".edl": + # EDL has no frame rate embedded so needs explicit + # frame rate else 24 is asssumed. + kwargs["rate"] = fps + + self.log.info(f"kwargs: {kwargs}") + return otio.adapters.read_from_file(sequence_path, **kwargs) def _get_path_from_file_data(self, file_path_data): # TODO: just temporarly solving only one media file @@ -275,6 +293,7 @@ or updating already created. Publishing will create OTIO file. def _get_clip_instances( self, otio_timeline, + media_path, clip_instance_properties, family_presets ): @@ -284,6 +303,9 @@ or updating already created. Publishing will create OTIO file. descended_from_type=otio.schema.Track ) + # media data for audio sream and reference solving + media_data = self._get_media_source_metadata(media_path) + for track in tracks: self.log.debug(f"track.name: {track.name}") try: @@ -298,10 +320,15 @@ or updating already created. Publishing will create OTIO file. self.log.debug(f"track_start_frame: {track_start_frame}") for clip in track.each_child(): - if not self._validate_clip_for_processing(clip): continue + # get available frames info to clip data + self._create_otio_reference(clip, media_path, media_data) + + # convert timeline range to source range + self._restore_otio_source_range(clip) + base_instance_data = self._get_base_instance_data( clip, clip_instance_properties, @@ -326,6 +353,68 @@ or updating already created. Publishing will create OTIO file. ) self.log.debug(f"{pformat(dict(instance.data))}") + def _restore_otio_source_range(self, otio_clip): + otio_clip.source_range = otio_clip.range_in_parent() + + def _create_otio_reference( + self, + otio_clip, + media_path, + media_data + ): + start_frame = media_data["start_frame"] + frame_duration = media_data["duration"] + fps = media_data["fps"] + + available_range = otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime( + start_frame, fps), + duration=otio.opentime.RationalTime( + frame_duration, fps) + ) + # in case old OTIO or video file create `ExternalReference` + media_reference = otio.schema.ExternalReference( + target_url=media_path, + available_range=available_range + ) + + otio_clip.media_reference = media_reference + + def _get_media_source_metadata(self, full_input_path_single_file): + return_data = {} + + try: + media_data = get_ffprobe_data( + full_input_path_single_file, self.log + ) + self.log.debug(f"__ media_data: {pformat(media_data)}") + + # get video stream data + video_stream = media_data["streams"][0] + return_data = { + "video": True, + "start_frame": 0, + "duration": int(video_stream["nb_frames"]), + "fps": float(video_stream["r_frame_rate"][:-2]) + } + + # get audio streams data + audio_stream = [ + stream for stream in media_data["streams"] + if stream["codec_type"] == "audio" + ] + + if audio_stream: + return_data["audio"] = True + + except Exception as exc: + raise AssertionError(( + "FFprobe couldn't read information about input file: " + f"\"{full_input_path_single_file}\". Error message: {exc}" + )) + + return return_data + def _make_subset_instance( self, clip, @@ -355,7 +444,7 @@ or updating already created. Publishing will create OTIO file. else: # add review family if defined future_instance_data.update({ - "filterExt": _fpreset["filter_ext"], + "outputFileType": _fpreset["output_file_type"], "parent_instance_id": parenting_data["instance_id"], "creator_attributes": { "parent_instance": parenting_data["instance_label"] From 968cbe8b984304769be9730dd3cff2633db00a7e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Jul 2022 17:07:18 +0200 Subject: [PATCH 047/131] removing plugin which will not be needed --- .../publish/collect_editorial_resources.py | 271 ------------------ 1 file changed, 271 deletions(-) delete mode 100644 openpype/hosts/traypublisher/plugins/publish/collect_editorial_resources.py diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_resources.py b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_resources.py deleted file mode 100644 index 33a852e7a5..0000000000 --- a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_resources.py +++ /dev/null @@ -1,271 +0,0 @@ -import os -import re -import tempfile -import pyblish.api -from copy import deepcopy -import clique - - -class CollectInstanceResources(pyblish.api.InstancePlugin): - """Collect instance's resources""" - - # must be after `CollectInstances` - order = pyblish.api.CollectorOrder - label = "Collect Editorial Resources" - hosts = ["standalonepublisher"] - families = ["clip"] - - def process(self, instance): - self.context = instance.context - self.log.info(f"Processing instance: {instance}") - self.new_instances = [] - subset_files = dict() - subset_dirs = list() - anatomy = self.context.data["anatomy"] - anatomy_data = deepcopy(self.context.data["anatomyData"]) - anatomy_data.update({"root": anatomy.roots}) - - subset = instance.data["subset"] - clip_name = instance.data["clipName"] - - editorial_source_root = instance.data["editorialSourceRoot"] - editorial_source_path = instance.data["editorialSourcePath"] - - # if `editorial_source_path` then loop through - if editorial_source_path: - # add family if mov or mp4 found which is longer for - # cutting `trimming` to enable `ExtractTrimmingVideoAudio` plugin - staging_dir = os.path.normpath( - tempfile.mkdtemp(prefix="pyblish_tmp_") - ) - instance.data["stagingDir"] = staging_dir - instance.data["families"] += ["trimming"] - return - - # if template pattern in path then fill it with `anatomy_data` - if "{" in editorial_source_root: - editorial_source_root = editorial_source_root.format( - **anatomy_data) - - self.log.debug(f"root: {editorial_source_root}") - # loop `editorial_source_root` and find clip name in folders - # and look for any subset name alternatives - for root, dirs, _files in os.walk(editorial_source_root): - # search only for directories related to clip name - correct_clip_dir = None - for _d_search in dirs: - # avoid all non clip dirs - if _d_search not in clip_name: - continue - # found correct dir for clip - correct_clip_dir = _d_search - - # continue if clip dir was not found - if not correct_clip_dir: - continue - - clip_dir_path = os.path.join(root, correct_clip_dir) - subset_files_items = list() - # list content of clip dir and search for subset items - for subset_item in os.listdir(clip_dir_path): - # avoid all items which are not defined as subsets by name - if subset not in subset_item: - continue - - subset_item_path = os.path.join( - clip_dir_path, subset_item) - # if it is dir store it to `subset_dirs` list - if os.path.isdir(subset_item_path): - subset_dirs.append(subset_item_path) - - # if it is file then store it to `subset_files` list - if os.path.isfile(subset_item_path): - subset_files_items.append(subset_item_path) - - if subset_files_items: - subset_files.update({clip_dir_path: subset_files_items}) - - # break the loop if correct_clip_dir was captured - # no need to cary on if correct folder was found - if correct_clip_dir: - break - - if subset_dirs: - # look all dirs and check for subset name alternatives - for _dir in subset_dirs: - instance_data = deepcopy( - {k: v for k, v in instance.data.items()}) - sub_dir = os.path.basename(_dir) - # if subset name is only alternative then create new instance - if sub_dir != subset: - instance_data = self.duplicate_instance( - instance_data, subset, sub_dir) - - # create all representations - self.create_representations( - os.listdir(_dir), instance_data, _dir) - - if sub_dir == subset: - self.new_instances.append(instance_data) - # instance.data.update(instance_data) - - if subset_files: - unique_subset_names = list() - root_dir = list(subset_files.keys()).pop() - files_list = subset_files[root_dir] - search_pattern = f"({subset}[A-Za-z0-9]+)(?=[\\._\\s])" - for _file in files_list: - pattern = re.compile(search_pattern) - match = pattern.findall(_file) - if not match: - continue - match_subset = match.pop() - if match_subset in unique_subset_names: - continue - unique_subset_names.append(match_subset) - - self.log.debug(f"unique_subset_names: {unique_subset_names}") - - for _un_subs in unique_subset_names: - instance_data = self.duplicate_instance( - instance.data, subset, _un_subs) - - # create all representations - self.create_representations( - [os.path.basename(f) for f in files_list - if _un_subs in f], - instance_data, root_dir) - - # remove the original instance as it had been used only - # as template and is duplicated - self.context.remove(instance) - - # create all instances in self.new_instances into context - for new_instance in self.new_instances: - _new_instance = self.context.create_instance( - new_instance["name"]) - _new_instance.data.update(new_instance) - - def duplicate_instance(self, instance_data, subset, new_subset): - - new_instance_data = dict() - for _key, _value in instance_data.items(): - new_instance_data[_key] = _value - if not isinstance(_value, str): - continue - if subset in _value: - new_instance_data[_key] = _value.replace( - subset, new_subset) - - self.log.info(f"Creating new instance: {new_instance_data['name']}") - self.new_instances.append(new_instance_data) - return new_instance_data - - def create_representations( - self, files_list, instance_data, staging_dir): - """ Create representations from Collection object - """ - # collecting frames for later frame start/end reset - frames = list() - # break down Collection object to collections and reminders - collections, remainder = clique.assemble(files_list) - # add staging_dir to instance_data - instance_data["stagingDir"] = staging_dir - # add representations to instance_data - instance_data["representations"] = list() - - collection_head_name = None - # loop through collections and create representations - for _collection in collections: - ext = _collection.tail[1:] - collection_head_name = _collection.head - frame_start = list(_collection.indexes)[0] - frame_end = list(_collection.indexes)[-1] - repre_data = { - "frameStart": frame_start, - "frameEnd": frame_end, - "name": ext, - "ext": ext, - "files": [item for item in _collection], - "stagingDir": staging_dir - } - - if instance_data.get("keepSequence"): - repre_data_keep = deepcopy(repre_data) - instance_data["representations"].append(repre_data_keep) - - if "review" in instance_data["families"]: - repre_data.update({ - "thumbnail": True, - "frameStartFtrack": frame_start, - "frameEndFtrack": frame_end, - "step": 1, - "fps": self.context.data.get("fps"), - "name": "review", - "tags": ["review", "ftrackreview", "delete"], - }) - instance_data["representations"].append(repre_data) - - # add to frames for frame range reset - frames.append(frame_start) - frames.append(frame_end) - - # loop through reminders and create representations - for _reminding_file in remainder: - ext = os.path.splitext(_reminding_file)[-1][1:] - if ext not in instance_data["extensions"]: - continue - if collection_head_name and ( - (collection_head_name + ext) not in _reminding_file - ) and (ext in ["mp4", "mov"]): - self.log.info(f"Skipping file: {_reminding_file}") - continue - frame_start = 1 - frame_end = 1 - - repre_data = { - "name": ext, - "ext": ext, - "files": _reminding_file, - "stagingDir": staging_dir - } - - # exception for thumbnail - if "thumb" in _reminding_file: - repre_data.update({ - 'name': "thumbnail", - 'thumbnail': True - }) - - # exception for mp4 preview - if ext in ["mp4", "mov"]: - frame_start = 0 - frame_end = ( - (instance_data["frameEnd"] - instance_data["frameStart"]) - + 1) - # add review ftrack family into families - for _family in ["review", "ftrack"]: - if _family not in instance_data["families"]: - instance_data["families"].append(_family) - repre_data.update({ - "frameStart": frame_start, - "frameEnd": frame_end, - "frameStartFtrack": frame_start, - "frameEndFtrack": frame_end, - "step": 1, - "fps": self.context.data.get("fps"), - "name": "review", - "thumbnail": True, - "tags": ["review", "ftrackreview", "delete"], - }) - - # add to frames for frame range reset only if no collection - if not collections: - frames.append(frame_start) - frames.append(frame_end) - - instance_data["representations"].append(repre_data) - - # reset frame start / end - instance_data["frameStart"] = min(frames) - instance_data["frameEnd"] = max(frames) From eff02322efb897bb4649130b011dd2ad46a9bb87 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Jul 2022 17:08:16 +0200 Subject: [PATCH 048/131] general: adding traypublisher host --- openpype/plugins/publish/collect_otio_frame_ranges.py | 2 +- openpype/plugins/publish/collect_otio_subset_resources.py | 8 +++----- openpype/plugins/publish/extract_otio_trimming_video.py | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py index c86e777850..40e89e29bc 100644 --- a/openpype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -23,7 +23,7 @@ class CollectOtioFrameRanges(pyblish.api.InstancePlugin): label = "Collect OTIO Frame Ranges" order = pyblish.api.CollectorOrder - 0.08 families = ["shot", "clip"] - hosts = ["resolve", "hiero", "flame"] + hosts = ["resolve", "hiero", "flame", "traypublisher"] def process(self, instance): # get basic variables diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index fc6a9b50f2..ca29b82f4e 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -23,7 +23,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): label = "Collect OTIO Subset Resources" order = pyblish.api.CollectorOrder - 0.077 families = ["clip"] - hosts = ["resolve", "hiero", "flame"] + hosts = ["resolve", "hiero", "flame", "traypublisher"] def process(self, instance): @@ -198,7 +198,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): if kwargs.get("collection"): collection = kwargs.get("collection") - files = [f for f in collection] + files = list(collection) ext = collection.format("{tail}") representation_data.update({ "name": ext[1:], @@ -220,7 +220,5 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): }) if kwargs.get("trim") is True: - representation_data.update({ - "tags": ["trim"] - }) + representation_data["tags"] = ["trim"] return representation_data diff --git a/openpype/plugins/publish/extract_otio_trimming_video.py b/openpype/plugins/publish/extract_otio_trimming_video.py index 19625fa568..46a4056a9d 100644 --- a/openpype/plugins/publish/extract_otio_trimming_video.py +++ b/openpype/plugins/publish/extract_otio_trimming_video.py @@ -20,7 +20,7 @@ class ExtractOTIOTrimmingVideo(openpype.api.Extractor): order = api.ExtractorOrder label = "Extract OTIO trim longer video" families = ["trim"] - hosts = ["resolve", "hiero", "flame"] + hosts = ["resolve", "hiero", "flame", "traypublisher"] def process(self, instance): self.staging_dir = self.staging_dir(instance) From 4bd7d4f43e5fa0ba1a3e7db06867ddd28c52fcd1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Jul 2022 21:50:33 +0200 Subject: [PATCH 049/131] trayp: adding reivew toggle to instance also add audio family condition for available ffmpeg streams --- .../plugins/create/create_editorial.py | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 28e58804c7..55c4ca76b7 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -91,6 +91,15 @@ class EditorialClipInstanceCreatorBase(HiddenTrayPublishCreator): return new_instance + def get_instance_attr_defs(self): + return [ + BoolDef( + "add_review_family", + default=True, + label="Review" + ) + ] + class EditorialShotInstanceCreator(EditorialClipInstanceCreatorBase): identifier = "editorial_shot" @@ -114,7 +123,6 @@ class EditorialShotInstanceCreator(EditorialClipInstanceCreatorBase): attr_defs.extend(CLIP_ATTR_DEFS) return attr_defs - class EditorialPlateInstanceCreator(EditorialClipInstanceCreatorBase): identifier = "editorial_plate" family = "plate" @@ -345,6 +353,13 @@ or updating already created. Publishing will create OTIO file. )) for _fpreset in family_presets: + # exclude audio family if no audio stream + if ( + _fpreset["family"] == "audio" + and not media_data.get("audio") + ): + continue + instance = self._make_subset_instance( clip, _fpreset, @@ -447,12 +462,8 @@ or updating already created. Publishing will create OTIO file. "outputFileType": _fpreset["output_file_type"], "parent_instance_id": parenting_data["instance_id"], "creator_attributes": { - "parent_instance": parenting_data["instance_label"] - }, - "publish_attributes": { - "CollectReviewFamily": { - "add_review_family": _fpreset.get("review") - } + "parent_instance": parenting_data["instance_label"], + "add_review_family": _fpreset.get("review") } }) From 49f67f0aca708d0b60055ccefadd414734159c56 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Jul 2022 21:51:09 +0200 Subject: [PATCH 050/131] trayp: collect reviewable for editorial --- .../publish/collect_editorial_reviewable.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py new file mode 100644 index 0000000000..6cd8c42546 --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py @@ -0,0 +1,30 @@ +import os + +import pyblish.api + + +class CollectEditorialReviewable(pyblish.api.InstancePlugin): + """Collect reviwiewable toggle to instance and representation data + """ + + label = "Collect Editorial Reviewable" + order = pyblish.api.CollectorOrder + + families = ["plate", "review", "audio"] + hosts = ["traypublisher"] + + def process(self, instance): + creator_identifier = instance.data["creator_identifier"] + if "editorial" not in creator_identifier: + return + + creator_attributes = instance.data["creator_attributes"] + repre = instance.data["representations"][0] + + if creator_attributes["add_review_family"]: + repre["tags"].append("review") + instance.data["families"].append("review") + + instance.data["representations"] = [repre] + + self.log.debug("instance.data {}".format(instance.data)) From 0c95e86ccc2735c25d3a5d9bcd31a62083a7ce67 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 19 Jul 2022 21:51:40 +0200 Subject: [PATCH 051/131] trayp: add more keys to sync between editorial instances --- .../traypublisher/plugins/publish/collect_shot_instances.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index 5abafa498d..86505f76c5 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -15,12 +15,16 @@ class CollectShotInstance(pyblish.api.InstancePlugin): SHARED_KEYS = [ "asset", "fps", + "handleStart", + "handleEnd", "frameStart", "frameEnd", "clipIn", "clipOut", "sourceIn", - "sourceOut" + "sourceOut", + "otioClip", + "workfileFrameStart" ] def process(self, instance): From b9ffa7720bba5b7bd05a00926a76e7debb9dfc34 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 08:22:49 +0200 Subject: [PATCH 052/131] general: making exctract trim video audio compatible with traypublisher --- .../publish/extract_trim_video_audio.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/extract_trim_video_audio.py b/openpype/plugins/publish/extract_trim_video_audio.py index b0c30283d9..8136ff1a6a 100644 --- a/openpype/plugins/publish/extract_trim_video_audio.py +++ b/openpype/plugins/publish/extract_trim_video_audio.py @@ -40,6 +40,20 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): fps = instance.data["fps"] video_file_path = instance.data["editorialSourcePath"] extensions = instance.data.get("extensions", ["mov"]) + output_file_type = instance.data.get("outputFileType") + + frame_start = int(instance.data["frameStart"]) + frame_end = int(instance.data["frameEnd"]) + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] + + clip_start_h = float(instance.data["clipInH"]) + _dur = instance.data["clipDuration"] + handle_dur = (handle_start + handle_end) + clip_dur_h = float(_dur + handle_dur) + + if output_file_type: + extensions = [output_file_type] for ext in extensions: self.log.info("Processing ext: `{}`".format(ext)) @@ -49,16 +63,10 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): clip_trimed_path = os.path.join( staging_dir, instance.data["name"] + ext) - # # check video file metadata - # input_data = plib.get_ffprobe_streams(video_file_path)[0] - # self.log.debug(f"__ input_data: `{input_data}`") - - start = float(instance.data["clipInH"]) - dur = float(instance.data["clipDurationH"]) if ext == ".wav": # offset time as ffmpeg is having bug - start += 0.5 + clip_start_h += 0.5 # remove "review" from families instance.data["families"] = [ fml for fml in instance.data["families"] @@ -67,9 +75,9 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): ffmpeg_args = [ ffmpeg_path, - "-ss", str(start / fps), + "-ss", str(clip_start_h / fps), "-i", video_file_path, - "-t", str(dur / fps) + "-t", str(clip_dur_h / fps) ] if ext in [".mov", ".mp4"]: ffmpeg_args.extend([ @@ -98,10 +106,10 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): "ext": ext[1:], "files": os.path.basename(clip_trimed_path), "stagingDir": staging_dir, - "frameStart": int(instance.data["frameStart"]), - "frameEnd": int(instance.data["frameEnd"]), - "frameStartFtrack": int(instance.data["frameStartH"]), - "frameEndFtrack": int(instance.data["frameEndH"]), + "frameStart": frame_start, + "frameEnd": frame_end, + "frameStartFtrack": frame_start - handle_start, + "frameEndFtrack": frame_end + handle_end, "fps": fps, } From 34f43fe86a664877e7695a09f9e3a29388db0ca1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 08:24:01 +0200 Subject: [PATCH 053/131] trayp: passing clipDuration attribute --- .../hosts/traypublisher/plugins/create/create_editorial.py | 2 +- .../traypublisher/plugins/publish/collect_clip_instances.py | 4 ++-- .../traypublisher/plugins/publish/collect_shot_instances.py | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 55c4ca76b7..899a45e269 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -455,7 +455,6 @@ or updating already created. Publishing will create OTIO file. "instance_label": label, "instance_id": c_instance.data["instance_id"] }) - else: # add review family if defined future_instance_data.update({ @@ -623,6 +622,7 @@ or updating already created. Publishing will create OTIO file. "frameEnd": int(frame_end), "clipIn": int(clip_in), "clipOut": int(clip_out), + "clipDuration": int(clip.duration().value), "sourceIn": int(source_in), "sourceOut": int(source_out) } diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py index e3dfb1512a..bc86cb8ef3 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py @@ -6,7 +6,7 @@ class CollectClipInstance(pyblish.api.InstancePlugin): """Collect clip instances and resolve its parent""" label = "Collect Clip Instances" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder - 0.081 hosts = ["traypublisher"] families = ["plate", "review", "audio"] @@ -29,4 +29,4 @@ class CollectClipInstance(pyblish.api.InstancePlugin): instance.context.data["editorialSourcePath"]) instance.data["families"].append("trimming") - self.log.debug(pformat(instance.data)) \ No newline at end of file + self.log.debug(pformat(instance.data)) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index 86505f76c5..9d8ed8ed72 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -21,6 +21,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin): "frameEnd", "clipIn", "clipOut", + "clipDuration", "sourceIn", "sourceOut", "otioClip", @@ -99,6 +100,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin): "frameEnd": workfile_start_frame + frame_dur, "clipIn": _cr_attrs["clipIn"], "clipOut": _cr_attrs["clipOut"], + "clipDuration": _cr_attrs["clipDuration"], "sourceIn": _cr_attrs["sourceIn"], "sourceOut": _cr_attrs["sourceOut"], "workfileFrameStart": workfile_start_frame From 449fabf449fcc47e807807dfc8fcbfc9b11a4bc2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 08:24:29 +0200 Subject: [PATCH 054/131] global: removing trayp host from plugins --- .../publish/collect_otio_subset_resources.py | 20 +++++++++---------- .../publish/extract_otio_trimming_video.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index ca29b82f4e..9c19f8a78e 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -23,7 +23,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): label = "Collect OTIO Subset Resources" order = pyblish.api.CollectorOrder - 0.077 families = ["clip"] - hosts = ["resolve", "hiero", "flame", "traypublisher"] + hosts = ["resolve", "hiero", "flame"] def process(self, instance): @@ -116,8 +116,10 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): # check in two way if it is sequence if hasattr(otio.schema, "ImageSequenceReference"): # for OpenTimelineIO 0.13 and newer - if isinstance(media_ref, - otio.schema.ImageSequenceReference): + if isinstance( + media_ref, + otio.schema.ImageSequenceReference + ): is_sequence = True else: # for OpenTimelineIO 0.12 and older @@ -139,11 +141,9 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): padding=media_ref.frame_zero_padding ) collection.indexes.update( - [i for i in range(a_frame_start_h, (a_frame_end_h + 1))]) + list(range(a_frame_start_h, (a_frame_end_h + 1))) + ) - self.log.debug(collection) - repre = self._create_representation( - frame_start, frame_end, collection=collection) else: # in case it is file sequence but not new OTIO schema # `ImageSequenceReference` @@ -152,9 +152,9 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): path, trimmed_media_range_h, metadata) self.staging_dir, collection = collection_data - self.log.debug(collection) - repre = self._create_representation( - frame_start, frame_end, collection=collection) + self.log.debug(collection) + repre = self._create_representation( + frame_start, frame_end, collection=collection) else: _trim = False dirname, filename = os.path.split(media_ref.target_url) diff --git a/openpype/plugins/publish/extract_otio_trimming_video.py b/openpype/plugins/publish/extract_otio_trimming_video.py index 46a4056a9d..19625fa568 100644 --- a/openpype/plugins/publish/extract_otio_trimming_video.py +++ b/openpype/plugins/publish/extract_otio_trimming_video.py @@ -20,7 +20,7 @@ class ExtractOTIOTrimmingVideo(openpype.api.Extractor): order = api.ExtractorOrder label = "Extract OTIO trim longer video" families = ["trim"] - hosts = ["resolve", "hiero", "flame", "traypublisher"] + hosts = ["resolve", "hiero", "flame"] def process(self, instance): self.staging_dir = self.staging_dir(instance) From 09182b312eca0ec853e2a2536b2426a0d5218e6e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 08:31:34 +0200 Subject: [PATCH 055/131] ftrack: adding options for plugin to settings --- openpype/settings/defaults/project_settings/ftrack.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 70cda68cb4..f6074d5464 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -447,6 +447,9 @@ "enabled": false, "ftrack_custom_attributes": {} }, + "IntegrateFtrackComponentOverwrite": { + "enabled": true + }, "IntegrateFtrackInstance": { "family_mapping": { "camera": "cam", From b60384f534c8df83738ca35985c74ce1e83b7c03 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 09:12:02 +0200 Subject: [PATCH 056/131] ftrack: optional plugin with optional attributes --- .../integrate_ftrack_component_overwrite.py | 5 ++++- .../projects_schema/schema_project_ftrack.json | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_component_overwrite.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_component_overwrite.py index 047fd8462c..8cb2336391 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_component_overwrite.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_component_overwrite.py @@ -13,7 +13,10 @@ class IntegrateFtrackComponentOverwrite(pyblish.api.InstancePlugin): active = False def process(self, instance): - component_list = instance.data['ftrackComponentsList'] + component_list = instance.data.get('ftrackComponentsList') + if not component_list: + self.log.info("No component to overwrite...") + return for cl in component_list: cl['component_overwrite'] = True diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index e008fd85ee..c06bec0f58 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -930,6 +930,21 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "IntegrateFtrackComponentOverwrite", + "label": "IntegrateFtrackComponentOverwrite", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { "type": "dict", "key": "IntegrateFtrackInstance", From bb9c03a94f1f0060424aa52973e3f14746cd475b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 09:12:22 +0200 Subject: [PATCH 057/131] ftrack: adding additional families to settings --- openpype/settings/defaults/project_settings/ftrack.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index f6074d5464..3e86581a03 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -301,7 +301,9 @@ "traypublisher" ], "families": [ - "plate" + "plate", + "review", + "audio" ], "task_types": [], "tasks": [], From cc47c30d5a45cad1805b2c796f5fae2b214d18ee Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 09:12:52 +0200 Subject: [PATCH 058/131] global: adding trayp families to plugins --- openpype/plugins/publish/extract_otio_file.py | 2 +- openpype/plugins/publish/validate_editorial_asset_name.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_otio_file.py b/openpype/plugins/publish/extract_otio_file.py index 3bd217d5d4..4d310ce109 100644 --- a/openpype/plugins/publish/extract_otio_file.py +++ b/openpype/plugins/publish/extract_otio_file.py @@ -12,7 +12,7 @@ class ExtractOTIOFile(openpype.api.Extractor): label = "Extract OTIO file" order = pyblish.api.ExtractorOrder - 0.45 families = ["workfile"] - hosts = ["resolve", "hiero"] + hosts = ["resolve", "hiero", "traypublisher"] def process(self, instance): # create representation data diff --git a/openpype/plugins/publish/validate_editorial_asset_name.py b/openpype/plugins/publish/validate_editorial_asset_name.py index 702e87b58d..694788c414 100644 --- a/openpype/plugins/publish/validate_editorial_asset_name.py +++ b/openpype/plugins/publish/validate_editorial_asset_name.py @@ -19,7 +19,8 @@ class ValidateEditorialAssetName(pyblish.api.ContextPlugin): "hiero", "standalonepublisher", "resolve", - "flame" + "flame", + "traypublisher" ] def process(self, context): From f7a6a606f53ae3f3ea376dd546f8f7958953c17b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 09:13:13 +0200 Subject: [PATCH 059/131] global: dealing with reviewable in trim audio/video plugin --- openpype/plugins/publish/extract_trim_video_audio.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_trim_video_audio.py b/openpype/plugins/publish/extract_trim_video_audio.py index 8136ff1a6a..06817c4b5a 100644 --- a/openpype/plugins/publish/extract_trim_video_audio.py +++ b/openpype/plugins/publish/extract_trim_video_audio.py @@ -41,6 +41,7 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): video_file_path = instance.data["editorialSourcePath"] extensions = instance.data.get("extensions", ["mov"]) output_file_type = instance.data.get("outputFileType") + reviewable = "review" in instance.data["families"] frame_start = int(instance.data["frameStart"]) frame_end = int(instance.data["frameEnd"]) @@ -111,9 +112,10 @@ class ExtractTrimVideoAudio(openpype.api.Extractor): "frameStartFtrack": frame_start - handle_start, "frameEndFtrack": frame_end + handle_end, "fps": fps, + "tags": [] } - if ext in [".mov", ".mp4"]: + if ext in [".mov", ".mp4"] and reviewable: repre.update({ "thumbnail": True, "tags": ["review", "ftrackreview", "delete"]}) From 7e6569fdd261481fde442c85452219441ceb629d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 09:13:36 +0200 Subject: [PATCH 060/131] global: adding trayp family --- .../ftrack/plugins/publish/integrate_hierarchy_ftrack.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 1a5d74bf26..b8855ee2bd 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -65,7 +65,13 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder - 0.04 label = 'Integrate Hierarchy To Ftrack' families = ["shot"] - hosts = ["hiero", "resolve", "standalonepublisher", "flame"] + hosts = [ + "hiero", + "resolve", + "standalonepublisher", + "flame", + "traypublisher" + ] optional = False def process(self, context): From 97879475732a5edef30d1ff625b6e2b01a0dd81a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 09:14:13 +0200 Subject: [PATCH 061/131] trayp: collect review input to instance data --- .../plugins/publish/collect_editorial_reviewable.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py index 6cd8c42546..2e4ad9e181 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py @@ -19,12 +19,8 @@ class CollectEditorialReviewable(pyblish.api.InstancePlugin): return creator_attributes = instance.data["creator_attributes"] - repre = instance.data["representations"][0] if creator_attributes["add_review_family"]: - repre["tags"].append("review") instance.data["families"].append("review") - instance.data["representations"] = [repre] - self.log.debug("instance.data {}".format(instance.data)) From 0ea71b05fb0e38c925a771dd551088344ce2479e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 09:24:28 +0200 Subject: [PATCH 062/131] global: adding review family to filters with non trayp exception --- openpype/plugins/publish/extract_thumbnail.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 7933595b89..b4c4bb2036 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -20,7 +20,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): order = pyblish.api.ExtractorOrder families = [ "imagesequence", "render", "render2d", "prerender", - "source", "plate", "take" + "source", "plate", "take", "review" ] hosts = ["shell", "fusion", "resolve", "traypublisher"] enabled = False @@ -29,6 +29,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ffmpeg_args = None def process(self, instance): + # make sure this apply only to reveiw in both family keys + # HACK: only traypublisher review family is allowed + if ( + instance.data["family"] != "review" + and "review" in instance.data["families"] + ): + return + self.log.info("subset {}".format(instance.data['subset'])) # skip crypto passes. From dc7856e919d3b7536c1bd5643d1b0e7ccbc8d059 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 11:56:20 +0200 Subject: [PATCH 063/131] trayp: processing PR comments --- openpype/hosts/traypublisher/api/editorial.py | 20 ++++--- .../plugins/create/create_editorial.py | 55 ++++++------------- .../plugins/create/create_from_settings.py | 3 - .../plugins/publish/collect_clip_instances.py | 8 ++- .../publish/collect_editorial_reviewable.py | 2 - .../plugins/publish/collect_shot_instances.py | 2 +- 6 files changed, 37 insertions(+), 53 deletions(-) diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index 948e05ec61..d6f876ab76 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -5,7 +5,7 @@ from openpype.client import get_asset_by_id from openpype.pipeline.create import CreatorError -class ShotMetadataSover: +class ShotMetadataSolver: """Collecting hierarchy context from `parents` and `hierarchy` data present in `clip` family instances coming from the request json data file @@ -22,12 +22,18 @@ class ShotMetadataSover: shot_hierarchy = None shot_add_tasks = None - def __init__(self, creator_settings, logger): - self.clip_name_tokenizer = creator_settings["clip_name_tokenizer"] - self.shot_rename = creator_settings["shot_rename"] - self.shot_hierarchy = creator_settings["shot_hierarchy"] - self.shot_add_tasks = creator_settings["shot_add_tasks"] - + def __init__( + self, + clip_name_tokenizer, + shot_rename, + shot_hierarchy, + shot_add_tasks, + logger + ): + self.clip_name_tokenizer = clip_name_tokenizer + self.shot_rename = shot_rename + self.shot_hierarchy = shot_hierarchy + self.shot_add_tasks = shot_add_tasks self.log = logger def _rename_template(self, data): diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 899a45e269..7b2585d630 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -11,7 +11,7 @@ from openpype.hosts.traypublisher.api.plugin import ( HiddenTrayPublishCreator ) from openpype.hosts.traypublisher.api.editorial import ( - ShotMetadataSover + ShotMetadataSolver ) from openpype.pipeline import CreatedInstance @@ -65,13 +65,6 @@ CLIP_ATTR_DEFS = [ class EditorialClipInstanceCreatorBase(HiddenTrayPublishCreator): host_name = "traypublisher" - def __init__( - self, project_settings, *args, **kwargs - ): - super(EditorialClipInstanceCreatorBase, self).__init__( - project_settings, *args, **kwargs - ) - def create(self, instance_data, source_data=None): self.log.info(f"instance_data: {instance_data}") subset_name = instance_data["subset"] @@ -106,13 +99,6 @@ class EditorialShotInstanceCreator(EditorialClipInstanceCreatorBase): family = "shot" label = "Editorial Shot" - def __init__( - self, project_settings, *args, **kwargs - ): - super(EditorialShotInstanceCreator, self).__init__( - project_settings, *args, **kwargs - ) - def get_instance_attr_defs(self): attr_defs = [ TextDef( @@ -123,44 +109,24 @@ class EditorialShotInstanceCreator(EditorialClipInstanceCreatorBase): attr_defs.extend(CLIP_ATTR_DEFS) return attr_defs + class EditorialPlateInstanceCreator(EditorialClipInstanceCreatorBase): identifier = "editorial_plate" family = "plate" label = "Editorial Plate" - def __init__( - self, project_settings, *args, **kwargs - ): - super(EditorialPlateInstanceCreator, self).__init__( - project_settings, *args, **kwargs - ) - class EditorialAudioInstanceCreator(EditorialClipInstanceCreatorBase): identifier = "editorial_audio" family = "audio" label = "Editorial Audio" - def __init__( - self, project_settings, *args, **kwargs - ): - super(EditorialAudioInstanceCreator, self).__init__( - project_settings, *args, **kwargs - ) - class EditorialReviewInstanceCreator(EditorialClipInstanceCreatorBase): identifier = "editorial_review" family = "review" label = "Editorial Review" - def __init__( - self, project_settings, *args, **kwargs - ): - super(EditorialReviewInstanceCreator, self).__init__( - project_settings, *args, **kwargs - ) - class EditorialSimpleCreator(TrayPublishCreator): @@ -188,8 +154,19 @@ or updating already created. Publishing will create OTIO file. ) # get this creator settings by identifier self._creator_settings = editorial_creators.get(self.identifier) - self._shot_metadata_solver = ShotMetadataSover( - self._creator_settings, self.log) + + clip_name_tokenizer = self._creator_settings["clip_name_tokenizer"] + shot_rename = self._creator_settings["shot_rename"] + shot_hierarchy = self._creator_settings["shot_hierarchy"] + shot_add_tasks = self._creator_settings["shot_add_tasks"] + + self._shot_metadata_solver = ShotMetadataSolver( + clip_name_tokenizer, + shot_rename, + shot_hierarchy, + shot_add_tasks, + self.log + ) # try to set main attributes from settings if self._creator_settings.get("default_variants"): @@ -717,4 +694,4 @@ or updating already created. Publishing will create OTIO file. attr_defs.append(UISeparatorDef()) attr_defs.extend(CLIP_ATTR_DEFS) - return attr_defs \ No newline at end of file + return attr_defs diff --git a/openpype/hosts/traypublisher/plugins/create/create_from_settings.py b/openpype/hosts/traypublisher/plugins/create/create_from_settings.py index 1271e03fdb..41c1c29bb0 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_from_settings.py +++ b/openpype/hosts/traypublisher/plugins/create/create_from_settings.py @@ -1,5 +1,4 @@ import os -from pprint import pformat from openpype.api import get_project_settings, Logger log = Logger.get_logger(__name__) @@ -16,8 +15,6 @@ def initialize(): global_variables = globals() for item in simple_creators: - log.debug(pformat(item)) - dynamic_plugin = SettingsCreator.from_settings(item) global_variables[dynamic_plugin.__name__] = dynamic_plugin diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py index bc86cb8ef3..ca269a9c27 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py @@ -13,7 +13,13 @@ class CollectClipInstance(pyblish.api.InstancePlugin): def process(self, instance): creator_identifier = instance.data["creator_identifier"] - if "editorial" not in creator_identifier: + if ( + creator_identifier not in [ + "editorial_plate", + "editorial_audio", + "editorial_review" + ] + ): return instance.data["families"].append("clip") diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py index 2e4ad9e181..34f7a9ead8 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py @@ -1,5 +1,3 @@ -import os - import pyblish.api diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index 9d8ed8ed72..e6f1173bc4 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -166,4 +166,4 @@ class CollectShotInstance(pyblish.api.InstancePlugin): else: new_dict[key] = ex_dict[key] - return new_dict \ No newline at end of file + return new_dict From 5d49d9c3d2876bddc4a1856b50e6ef94bf0c90d2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 12:29:38 +0200 Subject: [PATCH 064/131] trayp: adding universal attribute for new asset creation --- openpype/hosts/traypublisher/plugins/create/create_editorial.py | 2 ++ openpype/plugins/publish/validate_asset_docs.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 7b2585d630..fcaaeb1e75 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -550,6 +550,8 @@ or updating already created. Publishing will create OTIO file. "asset": parent_asset_name, "task": "", + "new_asset_publishing": True, + # parent time properties "trackStartFrame": track_start_frame, "timelineOffset": timeline_offset, diff --git a/openpype/plugins/publish/validate_asset_docs.py b/openpype/plugins/publish/validate_asset_docs.py index daeb442f28..9f997d4817 100644 --- a/openpype/plugins/publish/validate_asset_docs.py +++ b/openpype/plugins/publish/validate_asset_docs.py @@ -24,7 +24,7 @@ class ValidateAssetDocs(pyblish.api.InstancePlugin): if instance.data.get("assetEntity"): self.log.info("Instance has set asset document in its data.") - elif "editorial" in instance.data.get("creator_identifier", ""): + elif instance.context.data.get("new_asset_publishing"): # skip if it is editorial self.log.info("Editorial instance is no need to check...") From 7c30798bec528b8410fda39dd409022696afbf95 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 12:30:11 +0200 Subject: [PATCH 065/131] global: removing redundant check --- openpype/plugins/publish/extract_thumbnail.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index b4c4bb2036..89738a8063 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -20,7 +20,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): order = pyblish.api.ExtractorOrder families = [ "imagesequence", "render", "render2d", "prerender", - "source", "plate", "take", "review" + "source", "clip", "take" ] hosts = ["shell", "fusion", "resolve", "traypublisher"] enabled = False @@ -29,13 +29,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ffmpeg_args = None def process(self, instance): - # make sure this apply only to reveiw in both family keys - # HACK: only traypublisher review family is allowed - if ( - instance.data["family"] != "review" - and "review" in instance.data["families"] - ): - return self.log.info("subset {}".format(instance.data['subset'])) From 60adefa5ccf4cf737c8f78338e8e8a5173045726 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 14:54:21 +0200 Subject: [PATCH 066/131] global: renaming `newAssetPublishing` --- openpype/hosts/traypublisher/plugins/create/create_editorial.py | 2 +- openpype/plugins/publish/validate_asset_docs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index fcaaeb1e75..db0287129a 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -550,7 +550,7 @@ or updating already created. Publishing will create OTIO file. "asset": parent_asset_name, "task": "", - "new_asset_publishing": True, + "newAssetPublishing": True, # parent time properties "trackStartFrame": track_start_frame, diff --git a/openpype/plugins/publish/validate_asset_docs.py b/openpype/plugins/publish/validate_asset_docs.py index 9f997d4817..dbec9edd7b 100644 --- a/openpype/plugins/publish/validate_asset_docs.py +++ b/openpype/plugins/publish/validate_asset_docs.py @@ -24,7 +24,7 @@ class ValidateAssetDocs(pyblish.api.InstancePlugin): if instance.data.get("assetEntity"): self.log.info("Instance has set asset document in its data.") - elif instance.context.data.get("new_asset_publishing"): + elif instance.context.data.get("newAssetPublishing"): # skip if it is editorial self.log.info("Editorial instance is no need to check...") From cf2e5177dd6b7635cdcf0b53720375abf67dd2c2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 21 Jul 2022 15:32:25 +0200 Subject: [PATCH 067/131] trayp: adding docstrings --- openpype/hosts/traypublisher/api/editorial.py | 89 +++++++++++++++++-- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index d6f876ab76..92ad65a851 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -6,12 +6,12 @@ from openpype.pipeline.create import CreatorError class ShotMetadataSolver: - """Collecting hierarchy context from `parents` and `hierarchy` data - present in `clip` family instances coming from the request json data file + """ Solving hierarchical metadata - It will add `hierarchical_context` into each instance for integrate - plugins to be able to create needed parents for the context if they - don't exist yet + Used during editorial publishing. Works with imput + clip name and settings defining python formatable + template. Settings also define searching patterns + and its token keys used for formating in templates. """ NO_DECOR_PATERN = re.compile(r"\{([a-z]*?)\}") @@ -37,6 +37,17 @@ class ShotMetadataSolver: self.log = logger def _rename_template(self, data): + """Shot renaming function + + Args: + data (dict): formating data + + Raises: + CreatorError: If missing keys + + Returns: + str: formated new name + """ shot_rename_template = self.shot_rename[ "shot_rename_template"] try: @@ -51,6 +62,20 @@ class ShotMetadataSolver: )) def _generate_tokens(self, clip_name, source_data): + """Token generator + + Settings defines token pairs key and regex expression. + + Args: + clip_name (str): name of clip in editorial + source_data (dict): data for formating + + Raises: + CreatorError: if missing key + + Returns: + dict: updated source_data + """ output_data = deepcopy(source_data["anatomy_data"]) output_data["clip_name"] = clip_name @@ -78,7 +103,20 @@ class ShotMetadataSolver: return output_data def _create_parents_from_settings(self, parents, data): + """Formating parent components. + Args: + parents (list): list of dict parent components + data (dict): formating data + + Raises: + CreatorError: missing formating key + CreatorError: missing token key + KeyError: missing parent token + + Returns: + list: list of dict of parent components + """ # fill the parents parts from presets shot_hierarchy = deepcopy(self.shot_hierarchy) hierarchy_parents = shot_hierarchy["parents"] @@ -152,6 +190,14 @@ class ShotMetadataSolver: return parents def _create_hierarchy_path(self, parents): + """Converting hierarchy path from parents + + Args: + parents (list): list of dict parent components + + Returns: + str: hierarchy path + """ return "/".join( [ p["entity_name"] for p in parents @@ -164,6 +210,17 @@ class ShotMetadataSolver: asset_doc, project_doc ): + """Returning parents from context on selected asset. + + Context defined in Traypublisher project tree. + + Args: + asset_doc (db obj): selected asset doc + project_doc (db obj): actual project doc + + Returns: + list: list of dict parent components + """ project_name = project_doc["name"] visual_hierarchy = [asset_doc] current_doc = asset_doc @@ -192,6 +249,17 @@ class ShotMetadataSolver: ] def _generate_tasks_from_settings(self, project_doc): + """Convert settings inputs to task data. + + Args: + project_doc (db obj): actual project doc + + Raises: + KeyError: Missing task type in project doc + + Returns: + dict: tasks data + """ tasks_to_add = {} project_tasks = project_doc["config"]["tasks"] @@ -214,6 +282,17 @@ class ShotMetadataSolver: return tasks_to_add def generate_data(self, clip_name, source_data): + """Metadata generator. + + Converts input data to hierarchy mentadata. + + Args: + clip_name (str): clip name + source_data (dict): formating data + + Returns: + (str, dict): shot name and hierarchy data + """ self.log.info(f"_ source_data: {source_data}") tasks = {} From 976411521bf4e7f2db521813b9622e16dd62e800 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 22 Jul 2022 11:10:41 +0200 Subject: [PATCH 068/131] trayp: addresing issue from PR - different edl test https://github.com/pypeclub/OpenPype/pull/3492#pullrequestreview-1047573472 --- .../traypublisher/plugins/create/create_editorial.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index db0287129a..d6d669a56c 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -18,6 +18,7 @@ from openpype.pipeline import CreatedInstance from openpype.lib import ( get_ffprobe_data, + convert_ffprobe_fps_value, FileDef, TextDef, @@ -259,6 +260,7 @@ or updating already created. Publishing will create OTIO file. # EDL has no frame rate embedded so needs explicit # frame rate else 24 is asssumed. kwargs["rate"] = fps + kwargs["ignore_timecode_mismatch"] = True self.log.info(f"kwargs: {kwargs}") return otio.adapters.read_from_file(sequence_path, **kwargs) @@ -387,7 +389,11 @@ or updating already created. Publishing will create OTIO file. "video": True, "start_frame": 0, "duration": int(video_stream["nb_frames"]), - "fps": float(video_stream["r_frame_rate"][:-2]) + "fps": float( + convert_ffprobe_fps_value( + video_stream["r_frame_rate"] + ) + ) } # get audio streams data From c34a1270a29c6d660b1c7f40dcca259171b1a553 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 22 Jul 2022 13:05:58 +0200 Subject: [PATCH 069/131] trayp: adding docstrings --- .../plugins/create/create_editorial.py | 290 ++++++++++++++---- 1 file changed, 238 insertions(+), 52 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index d6d669a56c..3bc8f89556 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -64,6 +64,11 @@ CLIP_ATTR_DEFS = [ class EditorialClipInstanceCreatorBase(HiddenTrayPublishCreator): + """ Wrapper class for clip family creators + + Args: + HiddenTrayPublishCreator (BaseCreator): hidden supporting class + """ host_name = "traypublisher" def create(self, instance_data, source_data=None): @@ -96,6 +101,13 @@ class EditorialClipInstanceCreatorBase(HiddenTrayPublishCreator): class EditorialShotInstanceCreator(EditorialClipInstanceCreatorBase): + """ Shot family class + + The shot metadata instance carrier. + + Args: + EditorialClipInstanceCreatorBase (BaseCreator): hidden supporting class + """ identifier = "editorial_shot" family = "shot" label = "Editorial Shot" @@ -112,24 +124,54 @@ class EditorialShotInstanceCreator(EditorialClipInstanceCreatorBase): class EditorialPlateInstanceCreator(EditorialClipInstanceCreatorBase): + """ Plate family class + + Plate representation instance. + + Args: + EditorialClipInstanceCreatorBase (BaseCreator): hidden supporting class + """ identifier = "editorial_plate" family = "plate" label = "Editorial Plate" class EditorialAudioInstanceCreator(EditorialClipInstanceCreatorBase): + """ Audio family class + + Audio representation instance. + + Args: + EditorialClipInstanceCreatorBase (BaseCreator): hidden supporting class + """ identifier = "editorial_audio" family = "audio" label = "Editorial Audio" class EditorialReviewInstanceCreator(EditorialClipInstanceCreatorBase): + """ Review family class + + Review representation instance. + + Args: + EditorialClipInstanceCreatorBase (BaseCreator): hidden supporting class + """ identifier = "editorial_review" family = "review" label = "Editorial Review" class EditorialSimpleCreator(TrayPublishCreator): + """ Editorial creator class + + Simple workflow creator. This creator only disecting input + video file into clip chunks and then converts each to + defined format defined Settings for each subset preset. + + Args: + TrayPublishCreator (Creator): Tray publisher plugin class + """ label = "Editorial Simple" family = "editorial" @@ -242,6 +284,15 @@ or updating already created. Publishing will create OTIO file. media_path, otio_timeline ): + """Otio instance creating function + + Args: + subset_name (str): name of subset + data (dict): instnance data + sequence_path (str): path to sequence file + media_path (str): path to media file + otio_timeline (otio.Timeline): otio timeline object + """ # Pass precreate data to creator attributes data.update({ "sequenceFilePath": sequence_path, @@ -252,6 +303,15 @@ or updating already created. Publishing will create OTIO file. self._create_instance(self.family, subset_name, data) def _create_otio_timeline(self, sequence_path, fps): + """Creating otio timeline from sequence path + + Args: + sequence_path (str): path to sequence file + fps (float): frame per second + + Returns: + otio.Timeline: otio timeline object + """ # get editorial sequence file into otio timeline object extension = os.path.splitext(sequence_path)[1] @@ -266,6 +326,17 @@ or updating already created. Publishing will create OTIO file. return otio.adapters.read_from_file(sequence_path, **kwargs) def _get_path_from_file_data(self, file_path_data): + """Converting creator path data to single path string + + Args: + file_path_data (FileDefItem): creator path data inputs + + Raises: + FileExistsError: in case nothing had been set + + Returns: + str: path string + """ # TODO: just temporarly solving only one media file if isinstance(file_path_data, list): file_path_data = file_path_data.pop() @@ -281,9 +352,17 @@ or updating already created. Publishing will create OTIO file. self, otio_timeline, media_path, - clip_instance_properties, + instance_data, family_presets ): + """Helping function fro creating clip instance + + Args: + otio_timeline (otio.Timeline): otio timeline object + media_path (str): media file path string + instance_data (dict): clip instance data + family_presets (list): list of dict settings subset presets + """ self.asset_name_check = [] tracks = otio_timeline.each_child( @@ -318,7 +397,7 @@ or updating already created. Publishing will create OTIO file. base_instance_data = self._get_base_instance_data( clip, - clip_instance_properties, + instance_data, track_start_frame ) @@ -348,6 +427,14 @@ or updating already created. Publishing will create OTIO file. self.log.debug(f"{pformat(dict(instance.data))}") def _restore_otio_source_range(self, otio_clip): + """Infusing source range. + + Otio clip is missing proper source clip range so + here we add them from from parent timeline frame range. + + Args: + otio_clip (otio.Clip): otio clip object + """ otio_clip.source_range = otio_clip.range_in_parent() def _create_otio_reference( @@ -356,6 +443,13 @@ or updating already created. Publishing will create OTIO file. media_path, media_data ): + """Creating otio reference at otio clip. + + Args: + otio_clip (otio.Clip): otio clip object + media_path (str): media file path string + media_data (dict): media metadata + """ start_frame = media_data["start_frame"] frame_duration = media_data["duration"] fps = media_data["fps"] @@ -374,12 +468,23 @@ or updating already created. Publishing will create OTIO file. otio_clip.media_reference = media_reference - def _get_media_source_metadata(self, full_input_path_single_file): + def _get_media_source_metadata(self, path): + """Get all available metadata from file + + Args: + path (str): media file path string + + Raises: + AssertionError: ffprobe couldn't read metadata + + Returns: + dict: media file metadata + """ return_data = {} try: media_data = get_ffprobe_data( - full_input_path_single_file, self.log + path, self.log ) self.log.debug(f"__ media_data: {pformat(media_data)}") @@ -408,44 +513,55 @@ or updating already created. Publishing will create OTIO file. except Exception as exc: raise AssertionError(( "FFprobe couldn't read information about input file: " - f"\"{full_input_path_single_file}\". Error message: {exc}" + f"\"{path}\". Error message: {exc}" )) return return_data def _make_subset_instance( self, - clip, - _fpreset, - future_instance_data, + otio_clip, + preset, + instance_data, parenting_data ): - family = _fpreset["family"] + """Making subset instance from input preset + + Args: + otio_clip (otio.Clip): otio clip object + preset (dict): sigle family preset + instance_data (dict): instance data + parenting_data (dict): shot instance parent data + + Returns: + CreatedInstance: creator instance object + """ + family = preset["family"] label = self._make_subset_naming( - _fpreset, - future_instance_data + preset, + instance_data ) - future_instance_data["label"] = label + instance_data["label"] = label # add file extension filter only if it is not shot family if family == "shot": - future_instance_data["otioClip"] = ( - otio.adapters.write_to_string(clip)) + instance_data["otioClip"] = ( + otio.adapters.write_to_string(otio_clip)) c_instance = self.create_context.creators[ "editorial_shot"].create( - future_instance_data) + instance_data) parenting_data.update({ "instance_label": label, "instance_id": c_instance.data["instance_id"] }) else: # add review family if defined - future_instance_data.update({ - "outputFileType": _fpreset["output_file_type"], + instance_data.update({ + "outputFileType": preset["output_file_type"], "parent_instance_id": parenting_data["instance_id"], "creator_attributes": { "parent_instance": parenting_data["instance_label"], - "add_review_family": _fpreset.get("review") + "add_review_family": preset.get("review") } }) @@ -453,24 +569,33 @@ or updating already created. Publishing will create OTIO file. editorial_clip_creator = self.create_context.creators[ creator_identifier] c_instance = editorial_clip_creator.create( - future_instance_data) + instance_data) return c_instance def _make_subset_naming( self, - _fpreset, - future_instance_data + preset, + instance_data ): - shot_name = future_instance_data["shotName"] - variant_name = future_instance_data["variant"] - family = _fpreset["family"] + """ Subset name maker + + Args: + preset (dict): single preset item + instance_data (dict): instance data + + Returns: + str: label string + """ + shot_name = instance_data["shotName"] + variant_name = instance_data["variant"] + family = preset["family"] # get variant name from preset or from inharitance - _variant_name = _fpreset.get("variant") or variant_name + _variant_name = preset.get("variant") or variant_name self.log.debug(f"__ family: {family}") - self.log.debug(f"__ _fpreset: {_fpreset}") + self.log.debug(f"__ preset: {preset}") # subset name subset_name = "{}{}".format( @@ -481,7 +606,7 @@ or updating already created. Publishing will create OTIO file. subset_name ) - future_instance_data.update({ + instance_data.update({ "family": family, "label": label, "variant": _variant_name, @@ -492,21 +617,31 @@ or updating already created. Publishing will create OTIO file. def _get_base_instance_data( self, - clip, - clip_instance_properties, + otio_clip, + instance_data, track_start_frame, ): + """ Factoring basic set of instance data. + + Args: + otio_clip (otio.Clip): otio clip object + instance_data (dict): precreate instance data + track_start_frame (int): track start frame + + Returns: + dict: instance data + """ # get clip instance properties - parent_asset_name = clip_instance_properties["parent_asset_name"] - handle_start = clip_instance_properties["handle_start"] - handle_end = clip_instance_properties["handle_end"] - timeline_offset = clip_instance_properties["timeline_offset"] - workfile_start_frame = clip_instance_properties["workfile_start_frame"] - fps = clip_instance_properties["fps"] - variant_name = clip_instance_properties["variant"] + parent_asset_name = instance_data["parent_asset_name"] + handle_start = instance_data["handle_start"] + handle_end = instance_data["handle_end"] + timeline_offset = instance_data["timeline_offset"] + workfile_start_frame = instance_data["workfile_start_frame"] + fps = instance_data["fps"] + variant_name = instance_data["variant"] # basic unique asset name - clip_name = os.path.splitext(clip.name)[0].lower() + clip_name = os.path.splitext(otio_clip.name)[0].lower() project_doc = get_project(self.project_name) shot_name, shot_metadata = self._shot_metadata_solver.generate_data( @@ -529,7 +664,7 @@ or updating already created. Publishing will create OTIO file. self._validate_name_uniqueness(shot_name) timing_data = self._get_timing_data( - clip, + otio_clip, timeline_offset, track_start_frame, workfile_start_frame @@ -571,15 +706,26 @@ or updating already created. Publishing will create OTIO file. def _get_timing_data( self, - clip, + otio_clip, timeline_offset, track_start_frame, workfile_start_frame ): + """Returning available timing data + + Args: + otio_clip (otio.Clip): otio clip object + timeline_offset (int): offset value + track_start_frame (int): starting frame input + workfile_start_frame (int): start frame for shot's workfiles + + Returns: + dict: timing metadata + """ # frame ranges data - clip_in = clip.range_in_parent().start_time.value + clip_in = otio_clip.range_in_parent().start_time.value clip_in += track_start_frame - clip_out = clip.range_in_parent().end_time_inclusive().value + clip_out = otio_clip.range_in_parent().end_time_inclusive().value clip_out += track_start_frame self.log.info(f"clip_in: {clip_in} | clip_out: {clip_out}") @@ -589,10 +735,10 @@ or updating already created. Publishing will create OTIO file. clip_in += timeline_offset clip_out += timeline_offset - clip_duration = clip.duration().value + clip_duration = otio_clip.duration().value self.log.info(f"clip duration: {clip_duration}") - source_in = clip.trimmed_range().start_time.value + source_in = otio_clip.trimmed_range().start_time.value source_out = source_in + clip_duration # define starting frame for future shot @@ -607,12 +753,20 @@ or updating already created. Publishing will create OTIO file. "frameEnd": int(frame_end), "clipIn": int(clip_in), "clipOut": int(clip_out), - "clipDuration": int(clip.duration().value), + "clipDuration": int(otio_clip.duration().value), "sourceIn": int(source_in), "sourceOut": int(source_out) } def _get_allowed_family_presets(self, pre_create_data): + """ Filter out allowed family presets. + + Args: + pre_create_data (dict): precreate attributes inputs + + Returns: + list: lit of dict with preset items + """ self.log.debug(f"__ pre_create_data: {pre_create_data}") return [ {"family": "shot"}, @@ -622,41 +776,73 @@ or updating already created. Publishing will create OTIO file. ] ] - def _validate_clip_for_processing(self, clip): - if clip.name is None: + def _validate_clip_for_processing(self, otio_clip): + """Validate otio clip attribues + + Args: + otio_clip (otio.Clip): otio clip object + + Returns: + bool: True if all passing conditions + """ + if otio_clip.name is None: return False - if isinstance(clip, otio.schema.Gap): + if isinstance(otio_clip, otio.schema.Gap): return False # skip all generators like black empty if isinstance( - clip.media_reference, + otio_clip.media_reference, otio.schema.GeneratorReference): return False # Transitions are ignored, because Clips have the full frame # range. - if isinstance(clip, otio.schema.Transition): + if isinstance(otio_clip, otio.schema.Transition): return False return True def _validate_name_uniqueness(self, name): + """ Validating name uniqueness. + + In context of other clip names in sequence file. + + Args: + name (str): shot name string + """ if name not in self.asset_name_check: self.asset_name_check.append(name) else: - self.log.warning(f"duplicate shot name: {name}") + self.log.warning( + f"Duplicate shot name: {name}! " + "Please check names in the input sequence files." + ) - def _create_instance(self, family, subset_name, data): + def _create_instance(self, family, subset_name, instance_data): + """ CreatedInstance object creator + + Args: + family (str): family name + subset_name (str): subset name + instance_data (dict): instance data + """ # Create new instance - new_instance = CreatedInstance(family, subset_name, data, self) + new_instance = CreatedInstance( + family, subset_name, instance_data, self + ) # Host implementation of storing metadata about instance HostContext.add_instance(new_instance.data_to_store()) # Add instance to current context self._add_instance_to_context(new_instance) def get_pre_create_attr_defs(self): + """ Creating pre-create attributes at creator plugin. + + Returns: + list: list of attribute object instances + """ # Use same attributes as for instance attrobites attr_defs = [ FileDef( From 2acf9289a14da87faabc79180c5c7a53d4361000 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 22 Jul 2022 14:00:47 +0200 Subject: [PATCH 070/131] global: change reading from instance rather then context --- openpype/plugins/publish/validate_asset_docs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/validate_asset_docs.py b/openpype/plugins/publish/validate_asset_docs.py index dbec9edd7b..9a1ca5b8de 100644 --- a/openpype/plugins/publish/validate_asset_docs.py +++ b/openpype/plugins/publish/validate_asset_docs.py @@ -24,7 +24,7 @@ class ValidateAssetDocs(pyblish.api.InstancePlugin): if instance.data.get("assetEntity"): self.log.info("Instance has set asset document in its data.") - elif instance.context.data.get("newAssetPublishing"): + elif instance.data.get("newAssetPublishing"): # skip if it is editorial self.log.info("Editorial instance is no need to check...") From f5f7e52c42c9a43a4746683ba7cc0904fadab661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 22 Jul 2022 14:01:48 +0200 Subject: [PATCH 071/131] Update openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/collect_clip_instances.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py index ca269a9c27..bdf7c05f3d 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_clip_instances.py @@ -13,13 +13,11 @@ class CollectClipInstance(pyblish.api.InstancePlugin): def process(self, instance): creator_identifier = instance.data["creator_identifier"] - if ( - creator_identifier not in [ - "editorial_plate", - "editorial_audio", - "editorial_review" - ] - ): + if creator_identifier not in [ + "editorial_plate", + "editorial_audio", + "editorial_review" + ]: return instance.data["families"].append("clip") From 409cd5b870b9ebf7acc70c752c5b900a72ee9fd3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 22 Jul 2022 14:07:44 +0200 Subject: [PATCH 072/131] trayp: processing PR suggestion --- .../plugins/publish/collect_editorial_reviewable.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py index 34f7a9ead8..4af4fb94e9 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_editorial_reviewable.py @@ -2,7 +2,9 @@ import pyblish.api class CollectEditorialReviewable(pyblish.api.InstancePlugin): - """Collect reviwiewable toggle to instance and representation data + """ Collect review input from user. + + Adds the input to instance data. """ label = "Collect Editorial Reviewable" @@ -13,7 +15,11 @@ class CollectEditorialReviewable(pyblish.api.InstancePlugin): def process(self, instance): creator_identifier = instance.data["creator_identifier"] - if "editorial" not in creator_identifier: + if creator_identifier not in [ + "editorial_plate", + "editorial_audio", + "editorial_review" + ]: return creator_attributes = instance.data["creator_attributes"] From abfe580eeed15293d929cce4170bb41862a33868 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 22 Jul 2022 14:27:32 +0200 Subject: [PATCH 073/131] trayp: adding docstrings --- .../plugins/publish/collect_shot_instances.py | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py index e6f1173bc4..716f73022e 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_shot_instances.py @@ -4,7 +4,11 @@ import opentimelineio as otio class CollectShotInstance(pyblish.api.InstancePlugin): - """Collect shot instances and resolve its parent""" + """ Collect shot instances + + Resolving its user inputs from creator attributes + to instance data. + """ label = "Collect Shot Instances" order = pyblish.api.CollectorOrder - 0.09 @@ -50,6 +54,19 @@ class CollectShotInstance(pyblish.api.InstancePlugin): self.log.debug(pformat(instance.data)) def _get_otio_clip(self, instance): + """ Converts otio string data. + + Convert them to proper otio object + and finds its equivalent at otio timeline. + This process is a hack to support also + resolving parent range. + + Args: + instance (obj): publishing instance + + Returns: + otio.Clip: otio clip object + """ context = instance.context # convert otio clip from string to object otio_clip_string = instance.data.pop("otioClip") @@ -63,8 +80,6 @@ class CollectShotInstance(pyblish.api.InstancePlugin): descended_from_type=otio.schema.Clip) if clip.name == otio_clip.name ] - self.log.debug(otio_timeline.each_child( - descended_from_type=otio.schema.Clip)) otio_clip = clips.pop() self.log.debug(f"__ otioclip.parent: {otio_clip.parent}") @@ -72,6 +87,14 @@ class CollectShotInstance(pyblish.api.InstancePlugin): return otio_clip def _distribute_shared_data(self, instance): + """ Distribute all defined keys. + + All data are shared between all related + instances in context. + + Args: + instance (obj): publishing instance + """ context = instance.context instance_id = instance.data["instance_id"] @@ -85,6 +108,14 @@ class CollectShotInstance(pyblish.api.InstancePlugin): } def _solve_inputs_to_data(self, instance): + """ Resolve all user inputs into instance data. + + Args: + instance (obj): publishing instance + + Returns: + dict: instance data updating data + """ _cr_attrs = instance.data["creator_attributes"] workfile_start_frame = _cr_attrs["workfile_start_frame"] frame_start = _cr_attrs["frameStart"] @@ -107,6 +138,11 @@ class CollectShotInstance(pyblish.api.InstancePlugin): } def _solve_hierarchy_context(self, instance): + """ Adding hierarchy data to context shared data. + + Args: + instance (obj): publishing instance + """ context = instance.context final_context = ( @@ -157,13 +193,21 @@ class CollectShotInstance(pyblish.api.InstancePlugin): self.log.debug(pformat(final_context)) def _update_dict(self, ex_dict, new_dict): + """ Recursion function + + Updating nested data with another nested data. + + Args: + ex_dict (dict): nested data + new_dict (dict): nested data + + Returns: + dict: updated nested data + """ for key in ex_dict: if key in new_dict and isinstance(ex_dict[key], dict): new_dict[key] = self._update_dict(ex_dict[key], new_dict[key]) - else: - if ex_dict.get(key) and new_dict.get(key): - continue - else: - new_dict[key] = ex_dict[key] + elif not ex_dict.get(key) or not new_dict.get(key): + new_dict[key] = ex_dict[key] return new_dict From f0ca08b4959dde095b5ae4599cdee76fd8ac86f2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 22 Jul 2022 16:30:50 +0200 Subject: [PATCH 074/131] nuke: no need to remove slate frame collection is already without it.. --- openpype/hosts/nuke/api/plugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 925cab0bef..37ce03dc55 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -181,8 +181,6 @@ class ExporterReview(object): # get first and last frame self.first_frame = min(self.collection.indexes) self.last_frame = max(self.collection.indexes) - if "slate" in self.instance.data["families"]: - self.first_frame += 1 else: self.fname = os.path.basename(self.path_in) self.fhead = os.path.splitext(self.fname)[0] + "." From 0aeb10b78d204e6e3778e8f7dc1078fe9bad6068 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 22 Jul 2022 16:31:12 +0200 Subject: [PATCH 075/131] nuke: no need to convert to int if it already is int --- openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py b/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py index af5e8e9d27..5f7b1f3806 100644 --- a/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py +++ b/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py @@ -98,7 +98,7 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin): self.log.error(msg) raise ValidationException(msg) - collected_frames_len = int(len(collection.indexes)) + collected_frames_len = len(collection.indexes) coll_start = min(collection.indexes) coll_end = max(collection.indexes) From d2e1fe84456feda9c3a8432d665715c5408c2d57 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 25 Jul 2022 22:16:06 +0200 Subject: [PATCH 076/131] nuke: fixing local rendering slate workflow --- .../hosts/nuke/plugins/publish/extract_render_local.py | 8 -------- .../hosts/nuke/plugins/publish/extract_slate_frame.py | 1 + 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 1595fe03fb..1b3bf46b71 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -31,10 +31,6 @@ class NukeRenderLocal(openpype.api.Extractor): first_frame = instance.data.get("frameStartHandle", None) - # exception for slate workflow - if "slate" in families: - first_frame -= 1 - last_frame = instance.data.get("frameEndHandle", None) node_subset_name = instance.data.get("name", None) @@ -68,10 +64,6 @@ class NukeRenderLocal(openpype.api.Extractor): int(last_frame) ) - # exception for slate workflow - if "slate" in families: - first_frame += 1 - ext = node["file_type"].value() if "representations" not in instance.data: diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index 99ade4cf9b..ccfaf0ed46 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -13,6 +13,7 @@ from openpype.hosts.nuke.api import ( get_view_process_node ) + class ExtractSlateFrame(openpype.api.Extractor): """Extracts movie and thumbnail with baked in luts From 3755c5bf05d352de26647f05b5c2940d4022c30f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 10:45:35 +0200 Subject: [PATCH 077/131] implemented helper method to get representation path --- .../publish/integrate_ftrack_instances.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index c8d9e4117d..09a8672d77 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -360,6 +360,30 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): )) instance.data["ftrackComponentsList"] = component_list + def _get_repre_path(self, instance, repre, only_published): + published_path = repre.get("published_path") + if published_path: + published_path = os.path.normpath(published_path) + if os.path.exists(published_path): + return published_path + + if only_published: + return None + + comp_files = repre["files"] + if isinstance(comp_files, (tuple, list, set)): + filename = comp_files[0] + else: + filename = comp_files + + staging_dir = repre.get("stagingDir") + if not staging_dir: + staging_dir = instance.data["stagingDir"] + src_path = os.path.normpath(os.path.join(staging_dir, filename)) + if os.path.exists(src_path): + return src_path + return None + def _get_asset_version_status_name(self, instance): if not self.asset_versions_status_profiles: return None From 0474456e77c038af1cd1905e4a586cc8a6e27aae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 10:53:06 +0200 Subject: [PATCH 078/131] use helper method to calculate representation path for integration --- .../publish/integrate_ftrack_instances.py | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 09a8672d77..f1a4f28fd1 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -58,7 +58,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): version_number = int(instance_version) family = instance.data["family"] - family_low = instance.data["family"].lower() + family_low = family.lower() asset_type = instance.data.get("ftrackFamily") if not asset_type and family_low in self.family_mapping: @@ -140,24 +140,16 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): first_thumbnail_component = None first_thumbnail_component_repre = None for repre in thumbnail_representations: - published_path = repre.get("published_path") - if not published_path: - comp_files = repre["files"] - if isinstance(comp_files, (tuple, list, set)): - filename = comp_files[0] - else: - filename = comp_files - - published_path = os.path.join( - repre["stagingDir"], filename + repre_path = self._get_repre_path(instance, repre, False) + if not repre_path: + self.log.warning( + "Published path is not set and source was removed." ) - if not os.path.exists(published_path): - continue - repre["published_path"] = published_path + continue # Create copy of base comp item and append it thumbnail_item = copy.deepcopy(base_component_item) - thumbnail_item["component_path"] = repre["published_path"] + thumbnail_item["component_path"] = repre_path thumbnail_item["component_data"] = { "name": "thumbnail" } @@ -216,6 +208,13 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): extended_asset_name = "" multiple_reviewable = len(review_representations) > 1 for repre in review_representations: + repre_path = self._get_repre_path(instance, repre, False) + if not repre_path: + self.log.warning( + "Published path is not set and source was removed." + ) + continue + # Create copy of base comp item and append it review_item = copy.deepcopy(base_component_item) @@ -270,7 +269,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): fps = instance_fps # Change location - review_item["component_path"] = repre["published_path"] + review_item["component_path"] = repre_path # Change component data review_item["component_data"] = { # Default component name is "main". @@ -327,7 +326,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Add others representations as component for repre in other_representations: - published_path = repre.get("published_path") + published_path = self._get_repre_path(instance, repre, True) if not published_path: continue # Create copy of base comp item and append it @@ -368,7 +367,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): return published_path if only_published: - return None + return published_path comp_files = repre["files"] if isinstance(comp_files, (tuple, list, set)): From 266bce0f48070310f8b44ebfc25ab8b83ba51698 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 10:53:13 +0200 Subject: [PATCH 079/131] reduce duplicated variables --- .../modules/ftrack/plugins/publish/integrate_ftrack_api.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py index c4f7b1f05d..58591bacfd 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py @@ -26,8 +26,6 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): families = ["ftrack"] def process(self, instance): - session = instance.context.data["ftrackSession"] - context = instance.context component_list = instance.data.get("ftrackComponentsList") if not component_list: self.log.info( @@ -36,8 +34,8 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): ) return - session = instance.context.data["ftrackSession"] context = instance.context + session = context.data["ftrackSession"] parent_entity = None default_asset_name = None From 8e5a2a082ee18b46f3223c6212fdd65510dd2bee Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 10:57:23 +0200 Subject: [PATCH 080/131] added docstring to ftrack get repre path method --- .../publish/integrate_ftrack_instances.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index f1a4f28fd1..8eb8479183 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -360,6 +360,26 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): instance.data["ftrackComponentsList"] = component_list def _get_repre_path(self, instance, repre, only_published): + """Get representation path that can be used for integration. + + When 'only_published' is set to true the validation of path is not + relevant. In that case we just need what is set in 'published_path' + as "reference". The reference is not used to get or upload the file but + for reference where the file was published. + + Args: + instance (pyblish.Instance): Processed instance object. Used + for source of staging dir if representation does not have + filled it. + repre (dict): Representation on instance which could be and + could not be integrated with main integrator. + only_published (bool): Care only about published paths and + ignore if filepath is not existing anymore. + + Returns: + str: Path to representation file. + None: Path is not filled or does not exists. + """ published_path = repre.get("published_path") if published_path: published_path = os.path.normpath(published_path) From fcf6e70107cf609c9a561ec2821455100b9faa9e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 11:03:24 +0200 Subject: [PATCH 081/131] add missing empty line --- .../modules/ftrack/plugins/publish/integrate_ftrack_instances.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 8eb8479183..d937e64790 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -380,6 +380,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): str: Path to representation file. None: Path is not filled or does not exists. """ + published_path = repre.get("published_path") if published_path: published_path = os.path.normpath(published_path) From 2823cc2d1545adebca84c32aae9fb1e6f83db9d7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 11:40:02 +0200 Subject: [PATCH 082/131] removed unused git progress --- openpype/lib/git_progress.py | 86 ------------------------------------ 1 file changed, 86 deletions(-) delete mode 100644 openpype/lib/git_progress.py diff --git a/openpype/lib/git_progress.py b/openpype/lib/git_progress.py deleted file mode 100644 index 331b7b6745..0000000000 --- a/openpype/lib/git_progress.py +++ /dev/null @@ -1,86 +0,0 @@ -import git -from tqdm import tqdm - - -class _GitProgress(git.remote.RemoteProgress): - """ Class handling displaying progress during git operations. - - This is using **tqdm** for showing progress bars. As **GitPython** - is parsing progress directly from git command, it is somehow unreliable - as in some operations it is difficult to get total count of iterations - to display meaningful progress bar. - - """ - _t = None - _code = 0 - _current_status = '' - _current_max = '' - - _description = { - 256: "Checking out files", - 4: "Counting objects", - 128: "Finding sources", - 32: "Receiving objects", - 64: "Resolving deltas", - 16: "Writing objects" - } - - def __init__(self): - super().__init__() - - def __del__(self): - if self._t is not None: - self._t.close() - - def _detroy_tqdm(self): - """ Used to close tqdm when operation ended. - - """ - if self._t is not None: - self._t.close() - self._t = None - - def _check_mask(self, opcode: int) -> bool: - """" Add meaningful description to **GitPython** opcodes. - - :param opcode: OP_MASK opcode - :type opcode: int - :return: String description of opcode - :rtype: str - - .. seealso:: For opcodes look at :class:`git.RemoteProgress` - - """ - if opcode & self.COUNTING: - return self._description.get(self.COUNTING) - elif opcode & self.CHECKING_OUT: - return self._description.get(self.CHECKING_OUT) - elif opcode & self.WRITING: - return self._description.get(self.WRITING) - elif opcode & self.RECEIVING: - return self._description.get(self.RECEIVING) - elif opcode & self.RESOLVING: - return self._description.get(self.RESOLVING) - elif opcode & self.FINDING_SOURCES: - return self._description.get(self.FINDING_SOURCES) - else: - return "Processing" - - def update(self, op_code, cur_count, max_count=None, message=''): - """ Called when git operation update progress. - - .. seealso:: For more details see - :func:`git.objects.submodule.base.Submodule.update` - `Documentation `_ - - """ - code = self._check_mask(op_code) - if self._current_status != code or self._current_max != max_count: - self._current_max = max_count - self._current_status = code - self._detroy_tqdm() - self._t = tqdm(total=max_count) - self._t.set_description(" . {}".format(code)) - - self._t.update(cur_count) From 297aaa6ee1a265119783b6f9355054d443e3af27 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 11:41:15 +0200 Subject: [PATCH 083/131] removed unused function 'timeit' from log lib --- openpype/lib/__init__.py | 4 +--- openpype/lib/log.py | 22 ---------------------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index fb52a9aca7..c2fa9f0acb 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -63,7 +63,7 @@ from .execute import ( path_to_subprocess_arg, CREATE_NO_WINDOW ) -from .log import PypeLogger, timeit +from .log import PypeLogger from .path_templates import ( merge_dict, @@ -375,8 +375,6 @@ __all__ = [ "validate_mongo_connection", "OpenPypeMongoConnection", - "timeit", - "is_overlapping_otio_ranges", "otio_range_with_handles", "convert_to_padded_path", diff --git a/openpype/lib/log.py b/openpype/lib/log.py index 2cdb7ec8e4..33d3f5c409 100644 --- a/openpype/lib/log.py +++ b/openpype/lib/log.py @@ -483,25 +483,3 @@ class PypeLogger: cls.initialize() return OpenPypeMongoConnection.get_mongo_client() - - -def timeit(method): - """Print time in function. - - For debugging. - - """ - log = logging.getLogger() - - def timed(*args, **kw): - ts = time.time() - result = method(*args, **kw) - te = time.time() - if 'log_time' in kw: - name = kw.get('log_name', method.__name__.upper()) - kw['log_time'][name] = int((te - ts) * 1000) - else: - log.debug('%r %2.2f ms' % (method.__name__, (te - ts) * 1000)) - print('%r %2.2f ms' % (method.__name__, (te - ts) * 1000)) - return result - return timed From 31a3911d4e0e756d5a3d62e957f771cd3a77aece Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 11:48:24 +0200 Subject: [PATCH 084/131] move functions from openpype.lib.config to openpype.lib.dateutils --- openpype/lib/__init__.py | 3 +- openpype/lib/config.py | 103 ++++++------------ openpype/lib/dateutils.py | 95 ++++++++++++++++ .../event_handlers_user/action_delivery.py | 4 +- openpype/plugins/load/delivery.py | 4 +- .../plugins/publish/collect_datetime_data.py | 4 +- 6 files changed, 134 insertions(+), 79 deletions(-) create mode 100644 openpype/lib/dateutils.py diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index c2fa9f0acb..2d99efbe28 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -83,8 +83,9 @@ from .anatomy import ( Anatomy ) -from .config import ( +from .dateutils import ( get_datetime_data, + get_timestamp, get_formatted_current_time ) diff --git a/openpype/lib/config.py b/openpype/lib/config.py index 57e8efa57d..26822649e4 100644 --- a/openpype/lib/config.py +++ b/openpype/lib/config.py @@ -1,82 +1,41 @@ -# -*- coding: utf-8 -*- -"""Get configuration data.""" -import datetime +import warnings +import functools -def get_datetime_data(datetime_obj=None): - """Returns current datetime data as dictionary. +class ConfigDeprecatedWarning(DeprecationWarning): + pass - Args: - datetime_obj (datetime): Specific datetime object - Returns: - dict: prepared date & time data +def deprecated(func): + """Mark functions as deprecated. - Available keys: - "d" - in shortest possible way. - "dd" - with 2 digits. - "ddd" - shortened week day. e.g.: `Mon`, ... - "dddd" - full name of week day. e.g.: `Monday`, ... - "m" - in shortest possible way. e.g.: `1` if January - "mm" - with 2 digits. - "mmm" - shortened month name. e.g.: `Jan`, ... - "mmmm" - full month name. e.g.: `January`, ... - "yy" - shortened year. e.g.: `19`, `20`, ... - "yyyy" - full year. e.g.: `2019`, `2020`, ... - "H" - shortened hours. - "HH" - with 2 digits. - "h" - shortened hours. - "hh" - with 2 digits. - "ht" - AM or PM. - "M" - shortened minutes. - "MM" - with 2 digits. - "S" - shortened seconds. - "SS" - with 2 digits. + It will result in a warning being emitted when the function is used. """ - if not datetime_obj: - datetime_obj = datetime.datetime.now() - - year = datetime_obj.strftime("%Y") - - month = datetime_obj.strftime("%m") - month_name_full = datetime_obj.strftime("%B") - month_name_short = datetime_obj.strftime("%b") - day = datetime_obj.strftime("%d") - - weekday_full = datetime_obj.strftime("%A") - weekday_short = datetime_obj.strftime("%a") - - hours = datetime_obj.strftime("%H") - hours_midday = datetime_obj.strftime("%I") - hour_midday_type = datetime_obj.strftime("%p") - minutes = datetime_obj.strftime("%M") - seconds = datetime_obj.strftime("%S") - - return { - "d": str(int(day)), - "dd": str(day), - "ddd": weekday_short, - "dddd": weekday_full, - "m": str(int(month)), - "mm": str(month), - "mmm": month_name_short, - "mmmm": month_name_full, - "yy": str(year[2:]), - "yyyy": str(year), - "H": str(int(hours)), - "HH": str(hours), - "h": str(int(hours_midday)), - "hh": str(hours_midday), - "ht": hour_midday_type, - "M": str(int(minutes)), - "MM": str(minutes), - "S": str(int(seconds)), - "SS": str(seconds), - } + @functools.wraps(func) + def new_func(*args, **kwargs): + warnings.simplefilter("always", ConfigDeprecatedWarning) + warnings.warn( + ( + "Deprecated import of function '{}'." + " Class was moved to 'openpype.lib.dateutils.{}'." + " Please change your imports." + ).format(func.__name__), + category=ConfigDeprecatedWarning + ) + return func(*args, **kwargs) + return new_func +@deprecated +def get_datetime_data(datetime_obj=None): + from .dateutils import get_datetime_data + + return get_datetime_data(datetime_obj) + + +@deprecated def get_formatted_current_time(): - return datetime.datetime.now().strftime( - "%Y%m%dT%H%M%SZ" - ) + from .dateutils import get_formatted_current_time + + return get_formatted_current_time() diff --git a/openpype/lib/dateutils.py b/openpype/lib/dateutils.py new file mode 100644 index 0000000000..68cd1d1c5b --- /dev/null +++ b/openpype/lib/dateutils.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +"""Get configuration data.""" +import datetime + + +def get_datetime_data(datetime_obj=None): + """Returns current datetime data as dictionary. + + Args: + datetime_obj (datetime): Specific datetime object + + Returns: + dict: prepared date & time data + + Available keys: + "d" - in shortest possible way. + "dd" - with 2 digits. + "ddd" - shortened week day. e.g.: `Mon`, ... + "dddd" - full name of week day. e.g.: `Monday`, ... + "m" - in shortest possible way. e.g.: `1` if January + "mm" - with 2 digits. + "mmm" - shortened month name. e.g.: `Jan`, ... + "mmmm" - full month name. e.g.: `January`, ... + "yy" - shortened year. e.g.: `19`, `20`, ... + "yyyy" - full year. e.g.: `2019`, `2020`, ... + "H" - shortened hours. + "HH" - with 2 digits. + "h" - shortened hours. + "hh" - with 2 digits. + "ht" - AM or PM. + "M" - shortened minutes. + "MM" - with 2 digits. + "S" - shortened seconds. + "SS" - with 2 digits. + """ + + if not datetime_obj: + datetime_obj = datetime.datetime.now() + + year = datetime_obj.strftime("%Y") + + month = datetime_obj.strftime("%m") + month_name_full = datetime_obj.strftime("%B") + month_name_short = datetime_obj.strftime("%b") + day = datetime_obj.strftime("%d") + + weekday_full = datetime_obj.strftime("%A") + weekday_short = datetime_obj.strftime("%a") + + hours = datetime_obj.strftime("%H") + hours_midday = datetime_obj.strftime("%I") + hour_midday_type = datetime_obj.strftime("%p") + minutes = datetime_obj.strftime("%M") + seconds = datetime_obj.strftime("%S") + + return { + "d": str(int(day)), + "dd": str(day), + "ddd": weekday_short, + "dddd": weekday_full, + "m": str(int(month)), + "mm": str(month), + "mmm": month_name_short, + "mmmm": month_name_full, + "yy": str(year[2:]), + "yyyy": str(year), + "H": str(int(hours)), + "HH": str(hours), + "h": str(int(hours_midday)), + "hh": str(hours_midday), + "ht": hour_midday_type, + "M": str(int(minutes)), + "MM": str(minutes), + "S": str(int(seconds)), + "SS": str(seconds), + } + + +def get_timestamp(datetime_obj=None): + """Get standardized timestamp from datetime object. + + Args: + datetime_obj (datetime.datetime): Object of datetime. Current time + is used if not passed. + """ + + if datetime_obj is None: + datetime_obj = datetime.datetime.now() + return datetime_obj.strftime( + "%Y%m%dT%H%M%SZ" + ) + + +def get_formatted_current_time(): + return get_timestamp() diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index ad82af39a3..eec245070c 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -16,7 +16,7 @@ from openpype_modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY from openpype_modules.ftrack.lib.custom_attributes import ( query_custom_attributes ) -from openpype.lib import config +from openpype.lib.dateutils import get_datetime_data from openpype.lib.delivery import ( path_from_representation, get_format_dict, @@ -555,7 +555,7 @@ class Delivery(BaseAction): format_dict = get_format_dict(anatomy, location_path) - datetime_data = config.get_datetime_data() + datetime_data = get_datetime_data() for repre in repres_to_deliver: source_path = repre.get("data", {}).get("path") debug_msg = "Processing representation {}".format(repre["_id"]) diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py index 7585ea4c59..f6e1d4f06b 100644 --- a/openpype/plugins/load/delivery.py +++ b/openpype/plugins/load/delivery.py @@ -4,10 +4,10 @@ from collections import defaultdict from Qt import QtWidgets, QtCore, QtGui from openpype.client import get_representations -from openpype.lib import config from openpype.pipeline import load, Anatomy from openpype import resources, style +from openpype.lib.dateutils import get_datetime_data from openpype.lib.delivery import ( sizeof_fmt, path_from_representation, @@ -160,7 +160,7 @@ class DeliveryOptionsDialog(QtWidgets.QDialog): selected_repres = self._get_selected_repres() - datetime_data = config.get_datetime_data() + datetime_data = get_datetime_data() template_name = self.dropdown.currentText() format_dict = get_format_dict(self.anatomy, self.root_line_edit.text()) for repre in self._representations: diff --git a/openpype/plugins/publish/collect_datetime_data.py b/openpype/plugins/publish/collect_datetime_data.py index 1675ae1a98..f46d616fb3 100644 --- a/openpype/plugins/publish/collect_datetime_data.py +++ b/openpype/plugins/publish/collect_datetime_data.py @@ -5,7 +5,7 @@ Provides: """ import pyblish.api -from openpype.api import config +from openpype.lib.dateutils import get_datetime_data class CollectDateTimeData(pyblish.api.ContextPlugin): @@ -15,4 +15,4 @@ class CollectDateTimeData(pyblish.api.ContextPlugin): def process(self, context): key = "datetimeData" if key not in context.data: - context.data[key] = config.get_datetime_data() + context.data[key] = get_datetime_data() From 09001afa223baacd6748e0a44a6823199a289300 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 11:57:36 +0200 Subject: [PATCH 085/131] reduced 'Pype' from class names in logger --- openpype/lib/log.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/openpype/lib/log.py b/openpype/lib/log.py index 33d3f5c409..aaacb7b004 100644 --- a/openpype/lib/log.py +++ b/openpype/lib/log.py @@ -41,13 +41,13 @@ except ImportError: USE_UNICODE = hasattr(__builtins__, "unicode") -class PypeStreamHandler(logging.StreamHandler): +class LogStreamHandler(logging.StreamHandler): """ StreamHandler class designed to handle utf errors in python 2.x hosts. """ def __init__(self, stream=None): - super(PypeStreamHandler, self).__init__(stream) + super(LogStreamHandler, self).__init__(stream) self.enabled = True def enable(self): @@ -56,7 +56,6 @@ class PypeStreamHandler(logging.StreamHandler): Used to silence output """ self.enabled = True - pass def disable(self): """ Disable StreamHandler @@ -107,13 +106,13 @@ class PypeStreamHandler(logging.StreamHandler): self.handleError(record) -class PypeFormatter(logging.Formatter): +class LogFormatter(logging.Formatter): DFT = '%(levelname)s >>> { %(name)s }: [ %(message)s ]' default_formatter = logging.Formatter(DFT) def __init__(self, formats): - super(PypeFormatter, self).__init__() + super(LogFormatter, self).__init__() self.formatters = {} for loglevel in formats: self.formatters[loglevel] = logging.Formatter(formats[loglevel]) @@ -141,7 +140,7 @@ class PypeFormatter(logging.Formatter): return out -class PypeMongoFormatter(logging.Formatter): +class MongoFormatter(logging.Formatter): DEFAULT_PROPERTIES = logging.LogRecord( '', '', '', '', '', '', '', '').__dict__.keys() @@ -239,7 +238,7 @@ class PypeLogger: for handler in logger.handlers: if isinstance(handler, MongoHandler): add_mongo_handler = False - elif isinstance(handler, PypeStreamHandler): + elif isinstance(handler, LogStreamHandler): add_console_handler = False if add_console_handler: @@ -292,7 +291,7 @@ class PypeLogger: "username": components["username"], "password": components["password"], "capped": True, - "formatter": PypeMongoFormatter() + "formatter": MongoFormatter() } if components["port"] is not None: kwargs["port"] = int(components["port"]) @@ -303,10 +302,10 @@ class PypeLogger: @classmethod def _get_console_handler(cls): - formatter = PypeFormatter(cls.FORMAT_FILE) - console_handler = PypeStreamHandler() + formatter = LogFormatter(cls.FORMAT_FILE) + console_handler = LogStreamHandler() - console_handler.set_name("PypeStreamHandler") + console_handler.set_name("LogStreamHandler") console_handler.setFormatter(formatter) return console_handler @@ -417,9 +416,9 @@ class PypeLogger: def get_process_name(cls): """Process name that is like "label" of a process. - Pype's logging can be used from pype itseld of from hosts. Even in Pype - it's good to know if logs are from Pype tray or from pype's event - server. This should help to identify that information. + OpenPype's logging can be used from OpenPyppe itself of from hosts. + Even in OpenPype process it's good to know if logs are from tray or + from other cli commands. This should help to identify that information. """ if cls._process_name is not None: return cls._process_name From 14224407261d89b19424b4ac3c6608b10796cb01 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 11:58:35 +0200 Subject: [PATCH 086/131] make main class 'Logger' and keep 'PypeLogger' with commented deprecation log --- openpype/api.py | 3 +-- openpype/lib/__init__.py | 7 ++++++- openpype/lib/log.py | 16 ++++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/openpype/api.py b/openpype/api.py index fac2ae572b..c2227c1a52 100644 --- a/openpype/api.py +++ b/openpype/api.py @@ -9,6 +9,7 @@ from .settings import ( ) from .lib import ( PypeLogger, + Logger, Anatomy, config, execute, @@ -58,8 +59,6 @@ from .action import ( RepairContextAction ) -# for backward compatibility with Pype 2 -Logger = PypeLogger __all__ = [ "get_system_settings", diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 2d99efbe28..31cd5e7510 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -63,7 +63,10 @@ from .execute import ( path_to_subprocess_arg, CREATE_NO_WINDOW ) -from .log import PypeLogger +from .log import ( + Logger, + PypeLogger, +) from .path_templates import ( merge_dict, @@ -371,7 +374,9 @@ __all__ = [ "get_datetime_data", "get_formatted_current_time", + "Logger", "PypeLogger", + "get_default_components", "validate_mongo_connection", "OpenPypeMongoConnection", diff --git a/openpype/lib/log.py b/openpype/lib/log.py index aaacb7b004..dc030a6430 100644 --- a/openpype/lib/log.py +++ b/openpype/lib/log.py @@ -160,7 +160,7 @@ class MongoFormatter(logging.Formatter): 'method': record.funcName, 'lineNumber': record.lineno } - document.update(PypeLogger.get_process_data()) + document.update(Logger.get_process_data()) # Standard document decorated with exception info if record.exc_info is not None: @@ -180,7 +180,7 @@ class MongoFormatter(logging.Formatter): return document -class PypeLogger: +class Logger: DFT = '%(levelname)s >>> { %(name)s }: [ %(message)s ] ' DBG = " - { %(name)s }: [ %(message)s ] " INF = ">>> [ %(message)s ] " @@ -482,3 +482,15 @@ class PypeLogger: cls.initialize() return OpenPypeMongoConnection.get_mongo_client() + + +class PypeLogger(Logger): + @classmethod + def get_logger(cls, *args, **kwargs): + logger = Logger.get_logger(*args, **kwargs) + # TODO uncomment when replaced most of places + # logger.warning(( + # "'openpype.lib.PypeLogger' is deprecated class." + # " Please use 'openpype.lib.Logger' instead." + # )) + return logger From 9377d20be1f10c41f49e303062485d7a8f6af85d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 16:12:12 +0200 Subject: [PATCH 087/131] implemented functions to extract template data --- openpype/pipeline/template_data.py | 226 +++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 openpype/pipeline/template_data.py diff --git a/openpype/pipeline/template_data.py b/openpype/pipeline/template_data.py new file mode 100644 index 0000000000..de46650f9d --- /dev/null +++ b/openpype/pipeline/template_data.py @@ -0,0 +1,226 @@ +from openpype.client import get_project, get_asset_by_name +from openpype.settings import get_system_settings +from openpype.lib.local_settings import get_openpype_username + + +def get_general_template_data(system_settings=None): + """General template data based on system settings or machine. + + Output contains formatting keys: + - 'studio[name]' - Studio name filled from system settings + - 'studio[code]' - Studio code filled from system settings + - 'user' - User's name using 'get_openpype_username' + + Args: + system_settings (Dict[str, Any]): System settings. + """ + + if not system_settings: + system_settings = get_system_settings() + studio_name = system_settings["general"]["studio_name"] + studio_code = system_settings["general"]["studio_code"] + return { + "studio": { + "name": studio_name, + "code": studio_code + }, + "user": get_openpype_username() + } + + +def get_project_template_data(project_doc): + """Extract data from project document that are used in templates. + + Project document must have 'name' and (at this moment) optional + key 'data.code'. + + Output contains formatting keys: + - 'project[name]' - Project name + - 'project[code]' - Project code + + Args: + project_doc (Dict[str, Any]): Queried project document. + + Returns: + Dict[str, Dict[str, str]]: Template data based on project document. + """ + + project_code = project_doc.get("data", {}).get("code") + return { + "project": { + "name": project_doc["name"], + "code": project_code + } + } + + +def get_asset_template_data(asset_doc, project_name): + """Extract data from asset document that are used in templates. + + Output dictionary contains keys: + - 'asset' - asset name + - 'hierarchy' - parent asset names joined with '/' + - 'parent' - direct parent name, project name used if is under project + + Required document fields: + Asset: 'name', 'data.parents' + + Args: + asset_doc (Dict[str, Any]): Queried asset document. + project_name (str): Is used for 'parent' key if asset doc does not have + any. + + Returns: + Dict[str, str]: Data that are based on asset document and can be used + in templates. + """ + + asset_parents = asset_doc["data"]["parents"] + hierarchy = "/".join(asset_parents) + if asset_parents: + parent_name = asset_parents[-1] + else: + parent_name = project_name + + return { + "asset": asset_doc["name"], + "hierarchy": hierarchy, + "parent": parent_name + } + + +def get_task_type(asset_doc, task_name): + """Get task type based on asset document and task name. + + Required document fields: + Asset: 'data.tasks' + + Args: + asset_doc (Dict[str, Any]): Queried asset document. + task_name (str): Task name which is under asset. + + Returns: + str: Task type name. + None: Task was not found on asset document. + """ + + asset_tasks_info = asset_doc["data"]["tasks"] + return asset_tasks_info.get(task_name, {}).get("type") + + +def get_task_template_data(project_doc, asset_doc, task_name): + """"Extract task specific data from project and asset documents. + + Required document fields: + Project: 'config.tasks' + Asset: 'data.tasks'. + + Args: + project_doc (Dict[str, Any]): Queried project document. + asset_doc (Dict[str, Any]): Queried asset document. + tas_name (str): Name of task for which data should be returned. + + Returns: + Dict[str, Dict[str, str]]: Template data + """ + + project_task_types = project_doc["config"]["tasks"] + task_type = get_task_type(asset_doc, task_name) + task_code = project_task_types.get(task_type, {}).get("short_name") + + return { + "task": { + "name": task_name, + "type": task_type, + "short": task_code, + } + } + + +def get_template_data( + project_doc, + asset_doc=None, + task_name=None, + host_name=None, + system_settings=None +): + """Prepare data for templates filling from entered documents and info. + + This function does not "auto fill" any values except system settings and + it's on purpose. + + Universal function to receive template data from passed arguments. Only + required argument is project document all other arguments are optional + and their values won't be added to template data if are not passed. + + Required document fields: + Project: 'name', 'data.code', 'config.tasks' + Asset: 'name', 'data.parents', 'data.tasks' + + Args: + project_doc (Dict[str, Any]): Mongo document of project from MongoDB. + asset_doc (Dict[str, Any]): Mongo document of asset from MongoDB. + task_name (Union[str, None]): Task name under passed asset. + host_name (Union[str, None]): Used to fill '{app}' key. + system_settings (Union[Dict, None]): Prepared system settings. + They're queried if not passed (may be slower). + + Returns: + Dict[str, Any]: Data prepared for filling workdir template. + """ + + template_data = get_general_template_data(system_settings) + template_data.update(get_project_template_data(project_doc)) + if asset_doc: + template_data.update(get_asset_template_data( + asset_doc, project_doc["name"] + )) + if task_name: + template_data.update(get_task_template_data( + project_doc, asset_doc, task_name + )) + + if host_name: + template_data["app"] = host_name + + return template_data + + +def get_template_data_with_names( + project_name, + asset_name=None, + task_name=None, + host_name=None, + system_settings=None +): + """Prepare data for templates filling from entered entity names and info. + + Copy of 'get_template_data' but based on entity names instead of documents. + Only difference is that documents are queried. + + Args: + project_name (str): Project name for which template data are + calculated. + asset_name (Union[str, None]): Asset name for which template data are + calculated. + task_name (Union[str, None]): Task name under passed asset. + host_name (Union[str, None]):Used to fill '{app}' key. + because workdir template may contain `{app}` key. + system_settings (Union[Dict, None]): Prepared system settings. + They're queried if not passed. + + Returns: + Dict[str, Any]: Data prepared for filling workdir template. + """ + + project_doc = get_project(project_name, fields=["name", "data.code"]) + asset_doc = None + if asset_name: + asset_doc = get_asset_by_name( + project_name, + asset_name, + fields=["name", "data.parents", "data.tasks"] + ) + return get_template_data( + project_doc, asset_doc, task_name, host_name, system_settings + ) From 2a3255a9cb6a5eed64c906cd28cfdb2e6679d83b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 16:40:35 +0200 Subject: [PATCH 088/131] added function which calculate template data based on context session --- openpype/pipeline/context_tools.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index a8e55479b6..0535ce5d54 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -19,7 +19,9 @@ from openpype.client import ( from openpype.modules import load_modules, ModulesManager from openpype.settings import get_project_settings from openpype.lib import filter_pyblish_plugins + from .anatomy import Anatomy +from .template_data import get_template_data_with_names from . import ( legacy_io, register_loader_plugin_path, @@ -336,6 +338,7 @@ def get_current_project_asset(asset_name=None, asset_id=None, fields=None): return None return get_asset_by_name(project_name, asset_name, fields=fields) + def is_representation_from_latest(representation): """Return whether the representation is from latest version @@ -348,3 +351,29 @@ def is_representation_from_latest(representation): project_name = legacy_io.active_project() return version_is_latest(project_name, representation["parent"]) + + +def get_template_data_from_session(session=None, system_settings=None): + """Template data for template fill from session keys. + + Args: + session (Union[Dict[str, str], None]): The Session to use. If not + provided use the currently active global Session. + system_settings (Union[Dict[str, Any], Any]): Prepared system settings. + Optional are auto received if not passed. + + Returns: + Dict[str, Any]: All available data from session. + """ + + if session is None: + session = legacy_io.Session + + project_name = session["AVALON_PROJECT"] + asset_name = session["AVALON_ASSET"] + task_name = session["AVALON_TASK"] + host_name = session["AVALON_APP"] + + return get_template_data_with_names( + project_name, asset_name, task_name, host_name, system_settings + ) From 5c6b47e503b78e841a173575f222b89d49b5c1f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 16:47:11 +0200 Subject: [PATCH 089/131] mark functions in lib as deprecated and re-use functions from openpype.pipeline --- openpype/lib/avalon_context.py | 80 +++++++++------------------------- 1 file changed, 20 insertions(+), 60 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 4076a91c36..73014f5a5d 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -21,14 +21,10 @@ from openpype.client import ( get_representations, get_workfile_info, ) -from openpype.settings import ( - get_project_settings, - get_system_settings -) +from openpype.settings import get_project_settings from .profiles_filtering import filter_profiles from .events import emit_event from .path_templates import StringTemplate -from .local_settings import get_openpype_username legacy_io = None @@ -222,17 +218,11 @@ def get_asset(asset_name=None): return get_current_project_asset(asset_name=asset_name) +@deprecated("openpype.pipeline.template_data.get_general_template_data") def get_system_general_anatomy_data(system_settings=None): - if not system_settings: - system_settings = get_system_settings() - studio_name = system_settings["general"]["studio_name"] - studio_code = system_settings["general"]["studio_code"] - return { - "studio": { - "name": studio_name, - "code": studio_code - } - } + from openpype.pipeline.template_data import get_general_template_data + + return get_general_template_data(system_settings) def get_linked_asset_ids(asset_doc): @@ -424,7 +414,7 @@ def get_workfile_template_key( return default -# TODO rename function as is not just "work" specific +@deprecated("openpype.pipeline.template_data.get_template_data") def get_workdir_data(project_doc, asset_doc, task_name, host_name): """Prepare data for workdir template filling from entered information. @@ -437,40 +427,14 @@ def get_workdir_data(project_doc, asset_doc, task_name, host_name): Returns: dict: Data prepared for filling workdir template. + """ - task_type = asset_doc['data']['tasks'].get(task_name, {}).get('type') - project_task_types = project_doc["config"]["tasks"] - task_code = project_task_types.get(task_type, {}).get("short_name") + from openpype.pipeline.template_data import get_template_data - asset_parents = asset_doc["data"]["parents"] - hierarchy = "/".join(asset_parents) - - parent_name = project_doc["name"] - if asset_parents: - parent_name = asset_parents[-1] - - data = { - "project": { - "name": project_doc["name"], - "code": project_doc["data"].get("code") - }, - "task": { - "name": task_name, - "type": task_type, - "short": task_code, - }, - "asset": asset_doc["name"], - "parent": parent_name, - "app": host_name, - "user": get_openpype_username(), - "hierarchy": hierarchy, - } - - system_general_data = get_system_general_anatomy_data() - data.update(system_general_data) - - return data + return get_template_data( + project_doc, asset_doc, task_name, host_name + ) def get_workdir_with_workdir_data( @@ -565,27 +529,21 @@ def get_workdir( ) -@with_pipeline_io +@deprecated("openpype.pipeline.context_tools.get_template_data_from_session") def template_data_from_session(session=None): """ Return dictionary with template from session keys. Args: session (dict, Optional): The Session to use. If not provided use the currently active global Session. + Returns: dict: All available data from session. + """ - if session is None: - session = legacy_io.Session - - project_name = session["AVALON_PROJECT"] - asset_name = session["AVALON_ASSET"] - task_name = session["AVALON_TASK"] - host_name = session["AVALON_APP"] - project_doc = get_project(project_name) - asset_doc = get_asset_by_name(project_name, asset_name) - return get_workdir_data(project_doc, asset_doc, task_name, host_name) + from openpype.pipeline.context_tools import get_template_data_from_session + return get_template_data_from_session(session) @with_pipeline_io @@ -660,13 +618,14 @@ def compute_session_changes( @with_pipeline_io def get_workdir_from_session(session=None, template_key=None): from openpype.pipeline import Anatomy + from openpype.pipeline.context_tools import get_template_data_from_session if session is None: session = legacy_io.Session project_name = session["AVALON_PROJECT"] host_name = session["AVALON_APP"] anatomy = Anatomy(project_name) - template_data = template_data_from_session(session) + template_data = get_template_data_from_session(session) anatomy_filled = anatomy.format(template_data) if not template_key: @@ -695,8 +654,8 @@ def update_current_task(task=None, asset=None, app=None, template_key=None): Returns: dict: The changed key, values in the current Session. - """ + changes = compute_session_changes( legacy_io.Session, task=task, @@ -768,6 +727,7 @@ def create_workfile_doc(asset_doc, task_name, filename, workdir, dbcon=None): dbcon (AvalonMongoDB): Optionally enter avalon AvalonMongoDB object and `legacy_io` is used if not entered. """ + from openpype.pipeline import Anatomy # Use legacy_io if dbcon is not entered From f120f22c71ce2590e191fcf58b4be9967b17f15c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 16:48:13 +0200 Subject: [PATCH 090/131] Added information about removement to docstrings of deprecated functions --- openpype/lib/avalon_context.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 73014f5a5d..521d1e05e1 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -184,6 +184,9 @@ def is_latest(representation): Returns: bool: Whether the representation is of latest version. + + Deprecated: + Function will be removed after release version 3.14.* """ from openpype.pipeline.context_tools import is_representation_from_latest @@ -193,7 +196,11 @@ def is_latest(representation): @deprecated("openpype.pipeline.load.any_outdated_containers") def any_outdated(): - """Return whether the current scene has any outdated content""" + """Return whether the current scene has any outdated content. + + Deprecated: + Function will be removed after release version 3.14.* + """ from openpype.pipeline.load import any_outdated_containers @@ -211,6 +218,9 @@ def get_asset(asset_name=None): Returns: (MongoDB document) + + Deprecated: + Function will be removed after release version 3.14.* """ from openpype.pipeline.context_tools import get_current_project_asset @@ -220,6 +230,10 @@ def get_asset(asset_name=None): @deprecated("openpype.pipeline.template_data.get_general_template_data") def get_system_general_anatomy_data(system_settings=None): + """ + Deprecated: + Function will be removed after release version 3.14.* + """ from openpype.pipeline.template_data import get_general_template_data return get_general_template_data(system_settings) @@ -287,7 +301,10 @@ def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): Returns: None: If asset, subset or version were not found. - dict: Last version document for entered . + dict: Last version document for entered. + + Deprecated: + Function will be removed after release version 3.14.* """ if not project_name: @@ -428,6 +445,8 @@ def get_workdir_data(project_doc, asset_doc, task_name, host_name): Returns: dict: Data prepared for filling workdir template. + Deprecated: + Function will be removed after release version 3.14.* """ from openpype.pipeline.template_data import get_template_data @@ -540,6 +559,8 @@ def template_data_from_session(session=None): Returns: dict: All available data from session. + Deprecated: + Function will be removed after release version 3.14.* """ from openpype.pipeline.context_tools import get_template_data_from_session From 3561454a5f83129629929f3c9b6d937654d3e787 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 16:48:41 +0200 Subject: [PATCH 091/131] removed unused imports --- openpype/lib/avalon_context.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 521d1e05e1..95c547ce34 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -13,10 +13,8 @@ from openpype.client import ( get_project, get_assets, get_asset_by_name, - get_subset_by_name, get_subsets, get_last_versions, - get_last_version_by_subset_id, get_last_version_by_subset_name, get_representations, get_workfile_info, From 9f9ac018bdc076f16fd7940b387445674f192277 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 17:23:12 +0200 Subject: [PATCH 092/131] use new functions instead of 'get_workdir_data' --- openpype/hosts/nuke/api/lib.py | 9 ++++---- .../tvpaint/plugins/load/load_workfile.py | 10 ++++----- .../unreal/hooks/pre_workfile_preparation.py | 13 ++++------- openpype/lib/applications.py | 10 ++++++--- openpype/lib/avalon_context.py | 9 +++++--- .../action_fill_workfile_attr.py | 13 +++++++---- openpype/tools/workfiles/save_as_dialog.py | 22 +++++-------------- 7 files changed, 39 insertions(+), 47 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 74db164ae5..87647e214e 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -23,7 +23,6 @@ from openpype.api import ( Logger, BuildWorkfile, get_version_from_path, - get_workdir_data, get_current_project_settings, ) from openpype.tools.utils import host_tools @@ -34,6 +33,7 @@ from openpype.settings import ( get_anatomy_settings, ) from openpype.modules import ModulesManager +from openpype.pipeline.template_data import get_template_data_with_names from openpype.pipeline import ( discover_legacy_creator_plugins, legacy_io, @@ -965,12 +965,11 @@ def format_anatomy(data): data["version"] = get_version_from_path(file) project_name = anatomy.project_name - project_doc = get_project(project_name) - asset_doc = get_asset_by_name(project_name, data["avalon"]["asset"]) + asset_name = data["avalon"]["asset"] task_name = os.environ["AVALON_TASK"] host_name = os.environ["AVALON_APP"] - context_data = get_workdir_data( - project_doc, asset_doc, task_name, host_name + context_data = get_template_data_with_names( + project_name, asset_name, task_name, host_name ) data.update(context_data) data.update({ diff --git a/openpype/hosts/tvpaint/plugins/load/load_workfile.py b/openpype/hosts/tvpaint/plugins/load/load_workfile.py index c6dc765a27..8b09d20755 100644 --- a/openpype/hosts/tvpaint/plugins/load/load_workfile.py +++ b/openpype/hosts/tvpaint/plugins/load/load_workfile.py @@ -1,10 +1,8 @@ import os -from openpype.client import get_project, get_asset_by_name from openpype.lib import ( StringTemplate, get_workfile_template_key_from_context, - get_workdir_data, get_last_workfile_with_version, ) from openpype.pipeline import ( @@ -12,6 +10,7 @@ from openpype.pipeline import ( legacy_io, Anatomy, ) +from openpype.pipeline.template_data import get_template_data_with_names from openpype.hosts.tvpaint.api import lib, pipeline, plugin @@ -54,9 +53,6 @@ class LoadWorkfile(plugin.Loader): asset_name = legacy_io.Session["AVALON_ASSET"] task_name = legacy_io.Session["AVALON_TASK"] - project_doc = get_project(project_name) - asset_doc = get_asset_by_name(project_name, asset_name) - template_key = get_workfile_template_key_from_context( asset_name, task_name, @@ -66,7 +62,9 @@ class LoadWorkfile(plugin.Loader): ) anatomy = Anatomy(project_name) - data = get_workdir_data(project_doc, asset_doc, task_name, host_name) + data = get_template_data_with_names( + project_name, asset_name, task_name, host_name + ) data["root"] = anatomy.roots file_template = anatomy.templates[template_key]["file"] diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 5be04fc841..50b34bd573 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- """Hook to launch Unreal and prepare projects.""" import os +import copy from pathlib import Path from openpype.lib import ( PreLaunchHook, ApplicationLaunchFailed, ApplicationNotFound, - get_workdir_data, get_workfile_template_key ) import openpype.hosts.unreal.lib as unreal_lib @@ -35,18 +35,13 @@ class UnrealPrelaunchHook(PreLaunchHook): return last_workfile.name # Prepare data for fill data and for getting workfile template key - task_name = self.data["task_name"] anatomy = self.data["anatomy"] - asset_doc = self.data["asset_doc"] project_doc = self.data["project_doc"] - asset_tasks = asset_doc.get("data", {}).get("tasks") or {} - task_info = asset_tasks.get(task_name) or {} - task_type = task_info.get("type") + # Use already prepared workdir data + workdir_data = copy.deepcopy(self.data["workdir_data"]) + task_type = workdir_data.get("task", {}).get("type") - workdir_data = get_workdir_data( - project_doc, asset_doc, task_name, self.host_name - ) # QUESTION raise exception if version is part of filename template? workdir_data["version"] = 1 workdir_data["ext"] = "uproject" diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index f46197e15f..da8623ea13 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -28,7 +28,6 @@ from . import PypeLogger from .profiles_filtering import filter_profiles from .local_settings import get_openpype_username from .avalon_context import ( - get_workdir_data, get_workdir_with_workdir_data, get_workfile_template_key, get_last_workfile @@ -1576,6 +1575,9 @@ def prepare_context_environments(data, env_group=None): data (EnvironmentPrepData): Dictionary where result and intermediate result will be stored. """ + + from openpype.pipeline.template_data import get_template_data + # Context environments log = data["log"] @@ -1596,7 +1598,9 @@ def prepare_context_environments(data, env_group=None): # Load project specific environments project_name = project_doc["name"] project_settings = get_project_settings(project_name) + system_settings = get_system_settings() data["project_settings"] = project_settings + data["system_settings"] = system_settings # Apply project specific environments on current env value apply_project_environments_value( project_name, data["env"], project_settings, env_group @@ -1619,8 +1623,8 @@ def prepare_context_environments(data, env_group=None): if not app.is_host: return - workdir_data = get_workdir_data( - project_doc, asset_doc, task_name, app.host_name + workdir_data = get_template_data( + project_doc, asset_doc, task_name, app.host_name, system_settings ) data["workdir_data"] = workdir_data diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 95c547ce34..42854f39d6 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -533,11 +533,13 @@ def get_workdir( TemplateResult: Workdir path. """ + from openpype.pipeline import Anatomy + from openpype.pipeline.template_data import get_template_data + if not anatomy: - from openpype.pipeline import Anatomy anatomy = Anatomy(project_doc["name"]) - workdir_data = get_workdir_data( + workdir_data = get_template_data( project_doc, asset_doc, task_name, host_name ) # Output is TemplateResult object which contain useful data @@ -748,6 +750,7 @@ def create_workfile_doc(asset_doc, task_name, filename, workdir, dbcon=None): """ from openpype.pipeline import Anatomy + from openpype.pipeline.template_data import get_template_data # Use legacy_io if dbcon is not entered if not dbcon: @@ -766,7 +769,7 @@ def create_workfile_doc(asset_doc, task_name, filename, workdir, dbcon=None): # Prepare project for workdir data project_name = dbcon.active_project() project_doc = get_project(project_name) - workdir_data = get_workdir_data( + workdir_data = get_template_data( project_doc, asset_doc, task_name, dbcon.Session["AVALON_APP"] ) # Prepare anatomy diff --git a/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py b/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py index d91649d7ba..c7fa2dce5e 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py +++ b/openpype/modules/ftrack/event_handlers_user/action_fill_workfile_attr.py @@ -11,13 +11,13 @@ from openpype.client import ( get_project, get_assets, ) -from openpype.settings import get_project_settings +from openpype.settings import get_project_settings, get_system_settings from openpype.lib import ( get_workfile_template_key, - get_workdir_data, StringTemplate, ) from openpype.pipeline import Anatomy +from openpype.pipeline.template_data import get_template_data from openpype_modules.ftrack.lib import BaseAction, statics_icon from openpype_modules.ftrack.lib.avalon_sync import create_chunks @@ -279,14 +279,19 @@ class FillWorkfileAttributeAction(BaseAction): extension = "{ext}" project_doc = get_project(project_name) project_settings = get_project_settings(project_name) + system_settings = get_system_settings() anatomy = Anatomy(project_name) templates_by_key = {} operations = [] for asset_doc, task_entities in asset_docs_with_task_entities: for task_entity in task_entities: - workfile_data = get_workdir_data( - project_doc, asset_doc, task_entity["name"], host_name + workfile_data = get_template_data( + project_doc, + asset_doc, + task_entity["name"], + host_name, + system_settings ) # Use version 1 for each workfile workfile_data["version"] = 1 diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py index b62fd2c889..ea602846e7 100644 --- a/openpype/tools/workfiles/save_as_dialog.py +++ b/openpype/tools/workfiles/save_as_dialog.py @@ -5,18 +5,12 @@ import logging from Qt import QtWidgets, QtCore -from openpype.client import ( - get_project, - get_asset_by_name, -) -from openpype.lib import ( - get_last_workfile_with_version, - get_workdir_data, -) +from openpype.lib import get_last_workfile_with_version from openpype.pipeline import ( registered_host, legacy_io, ) +from openpype.pipeline.template_data import get_template_data_with_names from openpype.tools.utils import PlaceholderLineEdit log = logging.getLogger(__name__) @@ -30,16 +24,10 @@ def build_workfile_data(session): asset_name = session["AVALON_ASSET"] task_name = session["AVALON_TASK"] host_name = session["AVALON_APP"] - project_doc = get_project( - project_name, fields=["name", "data.code", "config.tasks"] - ) - asset_doc = get_asset_by_name( - project_name, - asset_name, - fields=["name", "data.tasks", "data.parents"] - ) - data = get_workdir_data(project_doc, asset_doc, task_name, host_name) + data = get_template_data_with_names( + project_name, asset_name, task_name, host_name + ) data.update({ "version": 1, "comment": "", From 8259be5a1ad3815e4a5eb3a39edf7c858dddff0a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 17:36:45 +0200 Subject: [PATCH 093/131] simplified collect anatomy context data --- .../publish/collect_anatomy_context_data.py | 66 ++++++------------- 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_context_data.py b/openpype/plugins/publish/collect_anatomy_context_data.py index 0794adfb67..8433816908 100644 --- a/openpype/plugins/publish/collect_anatomy_context_data.py +++ b/openpype/plugins/publish/collect_anatomy_context_data.py @@ -15,10 +15,8 @@ Provides: import json import pyblish.api -from openpype.lib import ( - get_system_general_anatomy_data -) from openpype.pipeline import legacy_io +from openpype.pipeline.template_data import get_template_data class CollectAnatomyContextData(pyblish.api.ContextPlugin): @@ -33,11 +31,15 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin): "asset": "AssetName", "hierarchy": "path/to/asset", "task": "Working", + "user": "MeDespicable", + # Duplicated entry "username": "MeDespicable", + # Current host name + "app": "maya" + *** OPTIONAL *** - "app": "maya" # Current application base name - + mutliple keys from `datetimeData` # see it's collector + + mutliple keys from `datetimeData` (See it's collector) } """ @@ -45,52 +47,26 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin): label = "Collect Anatomy Context Data" def process(self, context): + host_name = context.data["hostName"] + system_settings = context.data["system_settings"] project_entity = context.data["projectEntity"] - context_data = { - "project": { - "name": project_entity["name"], - "code": project_entity["data"].get("code") - }, - "username": context.data["user"], - "app": context.data["hostName"] - } - - context.data["anatomyData"] = context_data - - # add system general settings anatomy data - system_general_data = get_system_general_anatomy_data() - context_data.update(system_general_data) - - datetime_data = context.data.get("datetimeData") or {} - context_data.update(datetime_data) - asset_entity = context.data.get("assetEntity") + task_name = None if asset_entity: task_name = legacy_io.Session["AVALON_TASK"] - asset_tasks = asset_entity["data"]["tasks"] - task_type = asset_tasks.get(task_name, {}).get("type") + anatomy_data = get_template_data( + project_entity, asset_entity, task_name, host_name, system_settings + ) + anatomy_data.update(context.data.get("datetimeData") or {}) - project_task_types = project_entity["config"]["tasks"] - task_code = project_task_types.get(task_type, {}).get("short_name") + username = context.data["user"] + anatomy_data["user"] = username + # Backwards compatibility for 'username' key + anatomy_data["username"] = username - asset_parents = asset_entity["data"]["parents"] - hierarchy = "/".join(asset_parents) - - parent_name = project_entity["name"] - if asset_parents: - parent_name = asset_parents[-1] - - context_data.update({ - "asset": asset_entity["name"], - "parent": parent_name, - "hierarchy": hierarchy, - "task": { - "name": task_name, - "type": task_type, - "short": task_code, - } - }) + # Store + context.data["anatomyData"] = anatomy_data self.log.info("Global anatomy Data collected") - self.log.debug(json.dumps(context_data, indent=4)) + self.log.debug(json.dumps(anatomy_data, indent=4)) From 7aefc53d98fbc6509c5c90b4b86fd75d7a4344e6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 26 Jul 2022 18:23:58 +0200 Subject: [PATCH 094/131] removed unnecessary "app" key filling --- openpype/hosts/nuke/api/lib.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 87647e214e..501ab4ba93 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -910,19 +910,17 @@ def get_render_path(node): ''' Generate Render path from presets regarding avalon knob data ''' avalon_knob_data = read_avalon_data(node) - data = {'avalon': avalon_knob_data} nuke_imageio_writes = get_imageio_node_setting( node_class=avalon_knob_data["family"], plugin_name=avalon_knob_data["creator"], subset=avalon_knob_data["subset"] ) - host_name = os.environ.get("AVALON_APP") - data.update({ - "app": host_name, + data = { + "avalon": avalon_knob_data, "nuke_imageio_writes": nuke_imageio_writes - }) + } anatomy_filled = format_anatomy(data) return anatomy_filled["render"]["path"].replace("\\", "/") @@ -1127,10 +1125,8 @@ def create_write_node( if knob["name"] == "file_type": representation = knob["value"] - host_name = os.environ.get("AVALON_APP") try: data.update({ - "app": host_name, "imageio_writes": imageio_writes, "representation": representation, }) From a2c61b5233c4d20917c1c4594c6923738dc6b362 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Jul 2022 19:48:06 +0200 Subject: [PATCH 095/131] nuke: slate workflow switch to instance data --- openpype/hosts/nuke/plugins/publish/collect_slate_node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/nuke/plugins/publish/collect_slate_node.py b/openpype/hosts/nuke/plugins/publish/collect_slate_node.py index 4257ed3131..bfe32d8fd1 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_slate_node.py +++ b/openpype/hosts/nuke/plugins/publish/collect_slate_node.py @@ -33,6 +33,7 @@ class CollectSlate(pyblish.api.InstancePlugin): if slate_node: instance.data["slateNode"] = slate_node + instance.data["slate"] = True instance.data["families"].append("slate") instance.data["versionData"]["families"].append("slate") self.log.info( From 427c61f22c7b9bc68b1d6a64a238a4db762e7238 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Jul 2022 19:49:00 +0200 Subject: [PATCH 096/131] nuke: fixing farm and local rendering slate workflow --- .../nuke/plugins/publish/extract_render_local.py | 7 +++++-- .../nuke/plugins/publish/extract_slate_frame.py | 8 ++++++++ .../plugins/publish/submit_nuke_deadline.py | 15 +++++---------- .../plugins/publish/submit_publish_job.py | 8 ++++++-- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 1b3bf46b71..7cc9b2f928 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -80,8 +80,11 @@ class NukeRenderLocal(openpype.api.Extractor): repre = { 'name': ext, 'ext': ext, - 'frameStart': "%0{}d".format( - len(str(last_frame))) % first_frame, + 'frameStart': ( + "{{:0>{}}}" + .format(len(str(last_frame))) + .format(first_frame) + ), 'files': filenames, "stagingDir": out_dir } diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index ccfaf0ed46..b5cad143db 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -237,6 +237,7 @@ class ExtractSlateFrame(openpype.api.Extractor): def _render_slate_to_sequence(self, instance): # set slate frame first_frame = instance.data["frameStartHandle"] + last_frame = instance.data["frameEndHandle"] slate_first_frame = first_frame - 1 # render slate as sequence frame @@ -285,6 +286,13 @@ class ExtractSlateFrame(openpype.api.Extractor): matching_repre["files"] = [first_filename, slate_filename] elif slate_filename not in matching_repre["files"]: matching_repre["files"].insert(0, slate_filename) + matching_repre["frameStart"] = ( + "{{:0>{}}}" + .format(len(str(last_frame))) + .format(slate_first_frame) + ) + self.log.debug( + "__ matching_repre: {}".format(pformat(matching_repre))) self.log.warning("Added slate frame to representation files") diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 93fb511a34..a5f8270ec7 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -80,10 +80,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "Using published scene for render {}".format(script_path) ) - # exception for slate workflow - if "slate" in instance.data["families"]: - submit_frame_start -= 1 - response = self.payload_submit( instance, script_path, @@ -99,10 +95,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): instance.data["publishJobState"] = "Suspended" if instance.data.get("bakingNukeScripts"): - # exception for slate workflow - if "slate" in instance.data["families"]: - submit_frame_start += 1 - for baking_script in instance.data["bakingNukeScripts"]: render_path = baking_script["bakeRenderPath"] script_path = baking_script["bakeScriptPath"] @@ -365,7 +357,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): if not instance.data.get("expectedFiles"): instance.data["expectedFiles"] = [] - dir = os.path.dirname(path) + dirname = os.path.dirname(path) file = os.path.basename(path) if "#" in file: @@ -377,9 +369,12 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): instance.data["expectedFiles"].append(path) return + if instance.data.get("slate"): + start_frame -= 1 + for i in range(start_frame, (end_frame + 1)): instance.data["expectedFiles"].append( - os.path.join(dir, (file % i)).replace("\\", "/")) + os.path.join(dirname, (file % i)).replace("\\", "/")) def get_limit_groups(self): """Search for limit group nodes and return group name. diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 43ea64e565..f05ef31938 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -158,7 +158,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # mapping of instance properties to be transfered to new instance for every # specified family instance_transfer = { - "slate": ["slateFrames"], + "slate": ["slateFrames", "slate"], "review": ["lutPath"], "render2d": ["bakingNukeScripts", "version"], "renderlayer": ["convertToScanline"] @@ -585,11 +585,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): " This may cause issues on farm." ).format(staging)) + frame_start = int(instance.get("frameStartHandle")) + if instance.get("slate"): + frame_start -= 1 + rep = { "name": ext, "ext": ext, "files": [os.path.basename(f) for f in list(collection)], - "frameStart": int(instance.get("frameStartHandle")), + "frameStart": frame_start, "frameEnd": int(instance.get("frameEndHandle")), # If expectedFile are absolute, we need only filenames "stagingDir": staging, From 137ba908b51acf1a79963e71dc9278ec935f002a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Jul 2022 22:08:23 +0200 Subject: [PATCH 097/131] nuke: code style improvements --- .../plugins/publish/extract_render_local.py | 2 +- .../plugins/publish/precollect_instances.py | 17 ++++++++++------- .../nuke/plugins/publish/precollect_writes.py | 6 ++++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 1595fe03fb..7e66cdccda 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -123,4 +123,4 @@ class NukeRenderLocal(openpype.api.Extractor): self.log.info('Finished render') - self.log.debug("instance extracted: {}".format(instance.data)) + self.log.debug("_ instance.data: {}".format(instance.data)) diff --git a/openpype/hosts/nuke/plugins/publish/precollect_instances.py b/openpype/hosts/nuke/plugins/publish/precollect_instances.py index b0da94c4ce..b396056eb9 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_instances.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_instances.py @@ -50,7 +50,7 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): # establish families family = avalon_knob_data["family"] families_ak = avalon_knob_data.get("families", []) - families = list() + families = [] # except disabled nodes but exclude backdrops in test if ("nukenodes" not in family) and (node["disable"].value()): @@ -111,10 +111,10 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): self.log.debug("__ families: `{}`".format(families)) # Get format - format = root['format'].value() - resolution_width = format.width() - resolution_height = format.height() - pixel_aspect = format.pixelAspect() + format_ = root['format'].value() + resolution_width = format_.width() + resolution_height = format_.height() + pixel_aspect = format_.pixelAspect() # get publish knob value if "publish" not in node.knobs(): @@ -125,8 +125,11 @@ class PreCollectNukeInstances(pyblish.api.ContextPlugin): self.log.debug("__ _families_test: `{}`".format(_families_test)) for family_test in _families_test: if family_test in self.sync_workfile_version_on_families: - self.log.debug("Syncing version with workfile for '{}'" - .format(family_test)) + self.log.debug( + "Syncing version with workfile for '{}'".format( + family_test + ) + ) # get version to instance for integration instance.data['version'] = instance.context.data['version'] diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py index a97f34b370..e37cc8a80a 100644 --- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -144,8 +144,10 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): self.log.debug("colorspace: `{}`".format(colorspace)) version_data = { - "families": [f.replace(".local", "").replace(".farm", "") - for f in _families_test if "write" not in f], + "families": [ + _f.replace(".local", "").replace(".farm", "") + for _f in _families_test if "write" != _f + ], "colorspace": colorspace } From 951cc995a52057e163f5cda99b492faf225adb40 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 26 Jul 2022 22:09:06 +0200 Subject: [PATCH 098/131] nuke: fixing family after local render anatomyData family should be also changed --- openpype/hosts/nuke/plugins/publish/extract_render_local.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 7e66cdccda..6f0196690c 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -105,13 +105,16 @@ class NukeRenderLocal(openpype.api.Extractor): instance.data['family'] = 'render' families.remove('render.local') families.insert(0, "render2d") + instance.data["anatomyData"]["family"] = "render" elif "prerender.local" in families: instance.data['family'] = 'prerender' families.remove('prerender.local') families.insert(0, "prerender") + instance.data["anatomyData"]["family"] = "prerender" elif "still.local" in families: instance.data['family'] = 'image' families.remove('still.local') + instance.data["anatomyData"]["family"] = "image" instance.data["families"] = families collections, remainder = clique.assemble(filenames) From a3a839181b0fa94d5696a53c8a4d52cc8aed4119 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Jul 2022 11:21:20 +0200 Subject: [PATCH 099/131] global, flame, hiero, resolve, sp: implementing `newAssetPublishing` --- .../plugins/publish/collect_timeline_instances.py | 3 ++- .../hiero/plugins/publish/precollect_instances.py | 3 ++- .../resolve/plugins/publish/precollect_instances.py | 3 ++- .../plugins/publish/collect_editorial_instances.py | 3 ++- openpype/plugins/publish/integrate.py | 11 ++++++++++- openpype/plugins/publish/validate_asset_docs.py | 4 ++++ 6 files changed, 22 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py index 5db89a0ab9..992db62c75 100644 --- a/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py +++ b/openpype/hosts/flame/plugins/publish/collect_timeline_instances.py @@ -136,7 +136,8 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin): "tasks": { task["name"]: {"type": task["type"]} for task in self.add_tasks}, - "representations": [] + "representations": [], + "newAssetPublishing": True }) self.log.debug("__ inst_data: {}".format(pformat(inst_data))) diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 2d0ec6fc99..0c7dbc1f22 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -109,7 +109,8 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "clipAnnotations": annotations, # add all additional tags - "tags": phiero.get_track_item_tags(track_item) + "tags": phiero.get_track_item_tags(track_item), + "newAssetPublishing": True }) # otio clip data diff --git a/openpype/hosts/resolve/plugins/publish/precollect_instances.py b/openpype/hosts/resolve/plugins/publish/precollect_instances.py index 8f1a13a4e5..ee51998c0d 100644 --- a/openpype/hosts/resolve/plugins/publish/precollect_instances.py +++ b/openpype/hosts/resolve/plugins/publish/precollect_instances.py @@ -70,7 +70,8 @@ class PrecollectInstances(pyblish.api.ContextPlugin): "publish": resolve.get_publish_attribute(timeline_item), "fps": context.data["fps"], "handleStart": handle_start, - "handleEnd": handle_end + "handleEnd": handle_end, + "newAssetPublishing": True }) # otio clip data diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py index 3237fbbe12..75c260bad7 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial_instances.py @@ -170,7 +170,8 @@ class CollectInstances(pyblish.api.InstancePlugin): "frameStart": frame_start, "frameEnd": frame_end, "frameStartH": frame_start - handle_start, - "frameEndH": frame_end + handle_end + "frameEndH": frame_end + handle_end, + "newAssetPublishing": True } for data_key in instance_data_filter: diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 8ab508adc9..a4378bf58d 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -12,6 +12,7 @@ import pyblish.api import openpype.api from openpype.client import ( get_representations, + get_asset_by_name, get_subset_by_name, get_version_by_name, ) @@ -273,6 +274,14 @@ class IntegrateAsset(pyblish.api.InstancePlugin): def register(self, instance, file_transactions, filtered_repres): project_name = legacy_io.active_project() + # making sure editorial instances have its `assetEntity` + if instance.data.get("newAssetPublishing"): + asset_doc = get_asset_by_name( + project_name, + instance.data["asset"] + ) + instance.data["assetEntity"] = asset_doc + instance_stagingdir = instance.data.get("stagingDir") if not instance_stagingdir: self.log.info(( @@ -426,7 +435,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "".format(len(prepared_representations))) def prepare_subset(self, instance, project_name): - asset_doc = instance.data.get("assetEntity") + asset_doc = instance.data["assetEntity"] subset_name = instance.data["subset"] self.log.debug("Subset: {}".format(subset_name)) diff --git a/openpype/plugins/publish/validate_asset_docs.py b/openpype/plugins/publish/validate_asset_docs.py index bc1f9b9e6c..9a1ca5b8de 100644 --- a/openpype/plugins/publish/validate_asset_docs.py +++ b/openpype/plugins/publish/validate_asset_docs.py @@ -24,6 +24,10 @@ class ValidateAssetDocs(pyblish.api.InstancePlugin): if instance.data.get("assetEntity"): self.log.info("Instance has set asset document in its data.") + elif instance.data.get("newAssetPublishing"): + # skip if it is editorial + self.log.info("Editorial instance is no need to check...") + else: raise PublishValidationError(( "Instance \"{}\" doesn't have asset document " From e8a8f86cdf387e777914ae833ea7f469bc63b11c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Jul 2022 12:32:09 +0200 Subject: [PATCH 100/131] global: removing changes from integrate --- openpype/plugins/publish/integrate.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index a4378bf58d..74227fdb40 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -274,14 +274,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): def register(self, instance, file_transactions, filtered_repres): project_name = legacy_io.active_project() - # making sure editorial instances have its `assetEntity` - if instance.data.get("newAssetPublishing"): - asset_doc = get_asset_by_name( - project_name, - instance.data["asset"] - ) - instance.data["assetEntity"] = asset_doc - instance_stagingdir = instance.data.get("stagingDir") if not instance_stagingdir: self.log.info(( From fac4529e4df877bdf5f774907430f9b5662636eb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Jul 2022 12:32:44 +0200 Subject: [PATCH 101/131] global: integrate hierarchy is fixing avalonData and avalonEntity --- .../publish/extract_hierarchy_avalon.py | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index 8d447ba595..967381b02e 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -30,9 +30,15 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): self.log.debug("__ hierarchy_context: {}".format(hierarchy_context)) self.project = None - self.import_to_avalon(project_name, hierarchy_context) + self.import_to_avalon(context, project_name, hierarchy_context) - def import_to_avalon(self, project_name, input_data, parent=None): + def import_to_avalon( + self, + context, + project_name, + input_data, + parent=None, + ): for name in input_data: self.log.info("input_data[name]: {}".format(input_data[name])) entity_data = input_data[name] @@ -133,6 +139,9 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): # Unarchive if entity was archived entity = self.unarchive_entity(unarchive_entity, data) + # make sure all relative instances have correct avalon data + self._set_avalon_data_to_relative_instances(context, entity) + if update_data: # Update entity data with input data legacy_io.update_many( @@ -142,7 +151,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if "childs" in entity_data: self.import_to_avalon( - project_name, entity_data["childs"], entity + context, project_name, entity_data["childs"], entity ) def unarchive_entity(self, entity, data): @@ -159,20 +168,43 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): {"_id": entity["_id"]}, new_entity ) + return new_entity - def create_avalon_asset(self, project_name, name, data): - item = { + def create_avalon_asset(self, name, data): + asset_doc = { "schema": "openpype:asset-3.0", "name": name, "parent": self.project["_id"], "type": "asset", "data": data } - self.log.debug("Creating asset: {}".format(item)) - entity_id = legacy_io.insert_one(item).inserted_id + self.log.debug("Creating asset: {}".format(asset_doc)) + asset_doc["_id"] = legacy_io.insert_one(asset_doc).inserted_id - return get_asset_by_id(project_name, entity_id) + return asset_doc + + def _set_avalon_data_to_relative_instances(self, context, asset_doc): + for instance in context: + asset_name = asset_doc["name"] + inst_asset_name = instance.data["asset"] + + if asset_name == inst_asset_name: + instance.data["assetEntity"] = asset_doc + + # get parenting data + parents = asset_doc["data"].get("parents") or list() + + # equire only relative parent + if parents: + parent_name = parents[-1] + + # update avalon data on instance + instance.data["avalonData"].update({ + "hierarchy": "/".join(parents), + "task": {}, + "parent": parent_name + }) def _get_active_assets(self, context): """ Returns only asset dictionary. From 9b14e486579e209f2ff100842c081fc938406c8c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Jul 2022 12:38:14 +0200 Subject: [PATCH 102/131] fixing avalonData to anatomyData --- openpype/plugins/publish/extract_hierarchy_avalon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index 967381b02e..01dc80d6ee 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -200,7 +200,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): parent_name = parents[-1] # update avalon data on instance - instance.data["avalonData"].update({ + instance.data["anatomyData"].update({ "hierarchy": "/".join(parents), "task": {}, "parent": parent_name From 5af77fe04caf1b38313ce09b182aa4f3eea2946f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 27 Jul 2022 12:59:41 +0200 Subject: [PATCH 103/131] Update openpype/plugins/publish/extract_hierarchy_avalon.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_hierarchy_avalon.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index 01dc80d6ee..37ca42e4cc 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -186,6 +186,9 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): def _set_avalon_data_to_relative_instances(self, context, asset_doc): for instance in context: + # Skip instance if has filled asset entity + if instance.data.get("assetEntity"): + continue asset_name = asset_doc["name"] inst_asset_name = instance.data["asset"] From e9e00831f03d69776a380d826e6a971e44855bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 27 Jul 2022 13:00:14 +0200 Subject: [PATCH 104/131] Update openpype/plugins/publish/extract_hierarchy_avalon.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/extract_hierarchy_avalon.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index 37ca42e4cc..ec01ab4e8f 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -199,6 +199,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): parents = asset_doc["data"].get("parents") or list() # equire only relative parent + parent_name = project_name if parents: parent_name = parents[-1] From 203048bcf814a5ab8e05f769ce19d52fd19937db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 27 Jul 2022 13:00:21 +0200 Subject: [PATCH 105/131] Update openpype/plugins/publish/integrate.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/plugins/publish/integrate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 74227fdb40..cac212b7e2 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -12,7 +12,6 @@ import pyblish.api import openpype.api from openpype.client import ( get_representations, - get_asset_by_name, get_subset_by_name, get_version_by_name, ) From 86d9d0134ad57ebb1a07cdf3dd6d6ef13d466d0d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Jul 2022 13:02:45 +0200 Subject: [PATCH 106/131] fixing missing project_name --- .../plugins/publish/extract_hierarchy_avalon.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index ec01ab4e8f..d765755eee 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -140,7 +140,11 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): entity = self.unarchive_entity(unarchive_entity, data) # make sure all relative instances have correct avalon data - self._set_avalon_data_to_relative_instances(context, entity) + self._set_avalon_data_to_relative_instances( + context, + project_name, + entity + ) if update_data: # Update entity data with input data @@ -184,7 +188,12 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): return asset_doc - def _set_avalon_data_to_relative_instances(self, context, asset_doc): + def _set_avalon_data_to_relative_instances( + self, + context, + project_name, + asset_doc + ): for instance in context: # Skip instance if has filled asset entity if instance.data.get("assetEntity"): From a0149c36ffd80d1dcc5a2b08c5c09d37062de621 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 27 Jul 2022 13:14:35 +0200 Subject: [PATCH 107/131] fixing problem with more function argumets --- openpype/plugins/publish/extract_hierarchy_avalon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index d765755eee..6b4e5f48c5 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -133,7 +133,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if unarchive_entity is None: # Create entity if doesn"t exist entity = self.create_avalon_asset( - project_name, name, data + name, data ) else: # Unarchive if entity was archived From 2e0fe9335151c6b7cdc9d25011216ca3b2705f5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:16:46 +0200 Subject: [PATCH 108/131] use KnownPublishError instead of assertions --- openpype/plugins/publish/integrate.py | 42 ++++++++++++++++----------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 8ab508adc9..e87538a5a4 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -517,14 +517,16 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # pre-flight validations if repre["ext"].startswith("."): - raise ValueError("Extension must not start with a dot '.': " - "{}".format(repre["ext"])) + raise KnownPublishError(( + "Extension must not start with a dot '.': {}" + ).format(repre["ext"])) if repre.get("transfers"): - raise ValueError("Representation is not allowed to have transfers" - "data before integration. They are computed in " - "the integrator" - "Got: {}".format(repre["transfers"])) + raise KnownPublishError(( + "Representation is not allowed to have transfers" + "data before integration. They are computed in " + "the integrator. Got: {}" + ).format(repre["transfers"])) # create template data for Anatomy template_data = copy.deepcopy(instance.data["anatomyData"]) @@ -563,8 +565,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "{}".format(instance_stagingdir)) stagingdir = instance_stagingdir if not stagingdir: - raise ValueError("No staging directory set for representation: " - "{}".format(repre)) + raise KnownPublishError( + "No staging directory set for representation: {}".format(repre) + ) self.log.debug("Anatomy template name: {}".format(template_name)) anatomy = instance.context.data['anatomy'] @@ -574,9 +577,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): is_sequence_representation = isinstance(files, (list, tuple)) if is_sequence_representation: # Collection of files (sequence) - assert not any(os.path.isabs(fname) for fname in files), ( - "Given file names contain full paths" - ) + if any(os.path.isabs(fname) for fname in files): + raise KnownPublishError("Given file names contain full paths") src_collection = assemble(files) @@ -632,9 +634,11 @@ class IntegrateAsset(pyblish.api.InstancePlugin): dst_collection.indexes.clear() dst_collection.indexes.update(set(destination_indexes)) dst_collection.padding = destination_padding - assert ( - len(src_collection.indexes) == len(dst_collection.indexes) - ), "This is a bug" + if len(src_collection.indexes) != len(dst_collection.indexes): + raise KnownPublishError(( + "This is a bug. Source sequence frames length" + " does not match integration frames length" + )) # Multiple file transfers transfers = [] @@ -645,9 +649,13 @@ class IntegrateAsset(pyblish.api.InstancePlugin): else: # Single file fname = files - assert not os.path.isabs(fname), ( - "Given file name is a full path" - ) + if os.path.isabs(fname): + self.log.error( + "Filename in representation is filepath {}".format(fname) + ) + raise KnownPublishError( + "This is a bug. Representation file name is full path" + ) # Manage anatomy template data template_data.pop("frame", None) From 1bb9b27c7ff5a8c7d0a8fb4c1e631e5e6d33be1d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:17:07 +0200 Subject: [PATCH 109/131] simplified staging dir resolving --- openpype/plugins/publish/integrate.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index e87538a5a4..fdf5b21a6b 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -556,14 +556,15 @@ class IntegrateAsset(pyblish.api.InstancePlugin): continue template_data[anatomy_key] = value - if repre.get('stagingDir'): - stagingdir = repre['stagingDir'] - else: + stagingdir = repre.get("stagingDir") + if not stagingdir: # Fall back to instance staging dir if not explicitly # set for representation in the instance - self.log.debug("Representation uses instance staging dir: " - "{}".format(instance_stagingdir)) + self.log.debug(( + "Representation uses instance staging dir: {}" + ).format(instance_stagingdir)) stagingdir = instance_stagingdir + if not stagingdir: raise KnownPublishError( "No staging directory set for representation: {}".format(repre) From 89d49533e4f15b3e055be9d01250780abb1bc199 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:17:56 +0200 Subject: [PATCH 110/131] add the values only if they are not 'None' --- openpype/plugins/publish/integrate.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index fdf5b21a6b..87058dd2da 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -686,9 +686,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Also add these values to the context even if not used by the # destination template value = template_data.get(key) - if not value: - continue - repre_context[key] = template_data[key] + if value is not None: + repre_context[key] = value # Explicitly store the full list even though template data might # have a different value because it uses just a single udim tile From 5272907504aa4b6e825d715dd7b9c1714f6fb85b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:18:34 +0200 Subject: [PATCH 111/131] import source_hash directly --- openpype/plugins/publish/integrate.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 87058dd2da..a5f5a66091 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -9,12 +9,12 @@ from bson.objectid import ObjectId from pymongo import DeleteMany, ReplaceOne, InsertOne, UpdateOne import pyblish.api -import openpype.api from openpype.client import ( get_representations, get_subset_by_name, get_version_by_name, ) +from openype.lib import source_hash from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.file_transaction import FileTransaction from openpype.pipeline import legacy_io @@ -834,6 +834,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): def get_profile_filter_criteria(self, instance): """Return filter criteria for `filter_profiles`""" + # Anatomy data is pre-filled by Collectors anatomy_data = instance.data["anatomyData"] @@ -864,6 +865,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): path: modified path if possible, or unmodified path + warning logged """ + success, rootless_path = anatomy.find_root_template_from_path(path) if success: path = rootless_path @@ -885,6 +887,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): output_resources: array of dictionaries to be added to 'files' key in representation """ + file_infos = [] for file_path in destinations: file_info = self.prepare_file_info(file_path, anatomy, sites=sites) @@ -904,10 +907,11 @@ class IntegrateAsset(pyblish.api.InstancePlugin): Returns: dict: file info dictionary """ + return { "_id": ObjectId(), "path": self.get_rootless_path(anatomy, path), "size": os.path.getsize(path), - "hash": openpype.api.source_hash(path), + "hash": source_hash(path), "sites": sites } From 0c061c50276ac68ead8b7d3918b007e65ab543e8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:26:38 +0200 Subject: [PATCH 112/131] added "output" to representation context keys to auto fill it to context --- openpype/plugins/publish/integrate.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index a5f5a66091..52a5ea2bfc 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -168,7 +168,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # the database even if not used by the destination template db_representation_context_keys = [ "project", "asset", "task", "subset", "version", "representation", - "family", "hierarchy", "username" + "family", "hierarchy", "username", "output" ] skip_host_families = [] @@ -727,11 +727,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "context": repre_context } - # todo: simplify/streamline which additional data makes its way into - # the representation context - if repre.get("outputName"): - representation["context"]["output"] = repre['outputName'] - if is_sequence_representation and repre.get("frameStart") is not None: representation['context']['frame'] = template_data["frame"] From 9875f68cf43fef06e4670c6a5c61f3b3d5c0dbb0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:27:13 +0200 Subject: [PATCH 113/131] don't just check existence of key but also it's value when traversing repre and instance data --- openpype/plugins/publish/integrate.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 52a5ea2bfc..f89e7b33ce 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -548,13 +548,12 @@ class IntegrateAsset(pyblish.api.InstancePlugin): }.items(): # Allow to take value from representation # if not found also consider instance.data - if key in repre: - value = repre[key] - elif key in instance.data: - value = instance.data[key] - else: - continue - template_data[anatomy_key] = value + value = repre.get(key) + if value is None: + value = instance.data.get(key) + + if value is not None: + template_data[anatomy_key] = value stagingdir = repre.get("stagingDir") if not stagingdir: From 0be6d5b55c0266241d7960a9a33056762cf788c2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:29:24 +0200 Subject: [PATCH 114/131] removed backwards compatibility comments which as it's not backwards compatibility --- openpype/plugins/publish/integrate.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index f89e7b33ce..7dfd8e4cac 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -700,14 +700,12 @@ class IntegrateAsset(pyblish.api.InstancePlugin): else: repre_id = ObjectId() - # Backwards compatibility: # Store first transferred destination as published path data - # todo: can we remove this? - # todo: We shouldn't change data that makes its way back into - # instance.data[] until we know the publish actually succeeded - # otherwise `published_path` might not actually be valid? + # - used primarily for reviews that are integrated to custom modules + # TODO we should probably store all integrated files + # related to the representation? published_path = transfers[0][1] - repre["published_path"] = published_path # Backwards compatibility + repre["published_path"] = published_path # todo: `repre` is not the actual `representation` entity # we should simplify/clarify difference between data above From 12af64dbc0ed7eb6b415d55bc472c81c917eff7b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:30:34 +0200 Subject: [PATCH 115/131] use last frame instead of first frame for padding and don't look at source collection padding --- openpype/plugins/publish/integrate.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 7dfd8e4cac..3a86f4b373 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -78,12 +78,6 @@ def get_frame_padded(frame, padding): return "{frame:0{padding}d}".format(padding=padding, frame=frame) -def get_first_frame_padded(collection): - """Return first frame as padded number from `clique.Collection`""" - start_frame = next(iter(collection.indexes)) - return get_frame_padded(start_frame, padding=collection.padding) - - class IntegrateAsset(pyblish.api.InstancePlugin): """Register publish in the database and transfer files to destinations. @@ -588,7 +582,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # differs from the collection we want to shift the destination # frame indices from the source collection. destination_indexes = list(src_collection.indexes) - destination_padding = len(get_first_frame_padded(src_collection)) + # Use last frame for minimum padding + # - that should cover both 'udim' and 'frame' minimum padding + destination_padding = len(str(destination_indexes[-1])) if repre.get("frameStart") is not None and not is_udim: index_frame_start = int(repre.get("frameStart")) From 6cab5917c4903df529429ad5e5bf209409426708 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:36:23 +0200 Subject: [PATCH 116/131] use template padding for frames if padding is bigger then minimum collection's padding --- openpype/plugins/publish/integrate.py | 39 +++++++++++++-------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 3a86f4b373..7a9cee593b 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -565,7 +565,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): self.log.debug("Anatomy template name: {}".format(template_name)) anatomy = instance.context.data['anatomy'] - template = os.path.normpath(anatomy.templates[template_name]["path"]) + publish_template_category = anatomy.templates[template_name] + template = os.path.normpath(publish_template_category["path"]) is_udim = bool(repre.get("udim")) is_sequence_representation = isinstance(files, (list, tuple)) @@ -585,27 +586,25 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Use last frame for minimum padding # - that should cover both 'udim' and 'frame' minimum padding destination_padding = len(str(destination_indexes[-1])) - if repre.get("frameStart") is not None and not is_udim: - index_frame_start = int(repre.get("frameStart")) - - render_template = anatomy.templates[template_name] - # todo: should we ALWAYS manage the frame padding even when not - # having `frameStart` set? - frame_start_padding = int( - render_template.get( - "frame_padding", - render_template.get("padding") - ) + if not is_udim: + # Change padding for frames if template has defined higher + # padding. + template_padding = int( + publish_template_category["frame_padding"] ) + if template_padding > destination_padding: + destination_padding = template_padding - # Shift destination sequence to the start frame - src_start_frame = next(iter(src_collection.indexes)) - shift = index_frame_start - src_start_frame - if shift: - destination_indexes = [ - frame + shift for frame in destination_indexes - ] - destination_padding = frame_start_padding + if repre.get("frameStart") is not None: + index_frame_start = int(repre.get("frameStart")) + + # Shift destination sequence to the start frame + src_start_frame = next(iter(src_collection.indexes)) + shift = index_frame_start - src_start_frame + if shift: + destination_indexes = [ + frame + shift for frame in destination_indexes + ] # To construct the destination template with anatomy we require # a Frame or UDIM tile set for the template data. We use the first From 3835695376ff87983124a9ac802b5ecffa5e0344 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:38:51 +0200 Subject: [PATCH 117/131] simplified recalculation of destination indexes --- openpype/plugins/publish/integrate.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 7a9cee593b..0387196a8a 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -577,11 +577,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): src_collection = assemble(files) - # If the representation has `frameStart` set it renumbers the - # frame indices of the published collection. It will start from - # that `frameStart` index instead. Thus if that frame start - # differs from the collection we want to shift the destination - # frame indices from the source collection. destination_indexes = list(src_collection.indexes) # Use last frame for minimum padding # - that should cover both 'udim' and 'frame' minimum padding @@ -595,16 +590,19 @@ class IntegrateAsset(pyblish.api.InstancePlugin): if template_padding > destination_padding: destination_padding = template_padding - if repre.get("frameStart") is not None: - index_frame_start = int(repre.get("frameStart")) - + # If the representation has `frameStart` set it renumbers the + # frame indices of the published collection. It will start from + # that `frameStart` index instead. Thus if that frame start + # differs from the collection we want to shift the destination + # frame indices from the source collection. + repre_frame_start = repre.get("frameStart") + if repre_frame_start is not None: + index_frame_start = int(repre["frameStart"]) # Shift destination sequence to the start frame - src_start_frame = next(iter(src_collection.indexes)) - shift = index_frame_start - src_start_frame - if shift: - destination_indexes = [ - frame + shift for frame in destination_indexes - ] + destination_indexes = [ + index_frame_start + idx + for idx in range(len(destination_indexes)) + ] # To construct the destination template with anatomy we require # a Frame or UDIM tile set for the template data. We use the first From 879df0a3a79121a2fe9472e89e99537fc24f2040 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:51:16 +0200 Subject: [PATCH 118/131] unify quotations --- openpype/plugins/publish/integrate.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 0387196a8a..81a2190a21 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -526,7 +526,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): template_data = copy.deepcopy(instance.data["anatomyData"]) # required representation keys - files = repre['files'] + files = repre["files"] template_data["representation"] = repre["name"] template_data["ext"] = repre["ext"] @@ -564,11 +564,12 @@ class IntegrateAsset(pyblish.api.InstancePlugin): ) self.log.debug("Anatomy template name: {}".format(template_name)) - anatomy = instance.context.data['anatomy'] + anatomy = instance.context.data["anatomy"] publish_template_category = anatomy.templates[template_name] template = os.path.normpath(publish_template_category["path"]) is_udim = bool(repre.get("udim")) + is_sequence_representation = isinstance(files, (list, tuple)) if is_sequence_representation: # Collection of files (sequence) @@ -704,13 +705,13 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # we should simplify/clarify difference between data above # and the actual representation entity for the database data = repre.get("data", {}) - data.update({'path': published_path, 'template': template}) + data.update({"path": published_path, "template": template}) representation = { "_id": repre_id, "schema": "openpype:representation-2.0", "type": "representation", "parent": version["_id"], - "name": repre['name'], + "name": repre["name"], "data": data, # Imprint shortcut to context for performance reasons. @@ -718,7 +719,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): } if is_sequence_representation and repre.get("frameStart") is not None: - representation['context']['frame'] = template_data["frame"] + representation["context"]["frame"] = template_data["frame"] return { "representation": representation, @@ -779,7 +780,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): version_data[key] = instance.data[key] # Include instance.data[versionData] directly - version_data_instance = instance.data.get('versionData') + version_data_instance = instance.data.get("versionData") if version_data_instance: version_data.update(version_data_instance) From 74ad4a558d9574f85cfe852576b6fdc2d40641ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:51:24 +0200 Subject: [PATCH 119/131] fix typo in import --- openpype/plugins/publish/integrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 81a2190a21..db55a17e59 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -14,7 +14,7 @@ from openpype.client import ( get_subset_by_name, get_version_by_name, ) -from openype.lib import source_hash +from openpype.lib import source_hash from openpype.lib.profiles_filtering import filter_profiles from openpype.lib.file_transaction import FileTransaction from openpype.pipeline import legacy_io From b5cdebe0707c9e4a9acccd16b6db92108ba8cca8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 27 Jul 2022 13:56:39 +0200 Subject: [PATCH 120/131] make sure frame is filled durectly in sequence condition --- openpype/plugins/publish/integrate.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index db55a17e59..c106649f2a 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -621,6 +621,13 @@ class IntegrateAsset(pyblish.api.InstancePlugin): anatomy_filled = anatomy.format(template_data) template_filled = anatomy_filled[template_name]["path"] repre_context = template_filled.used_values + + # Make sure context contains frame + # NOTE: Frame would not be available only if template does not + # contain '{frame}' in template -> Do we want support it? + if not is_udim: + repre_context["frame"] = first_index_padded + self.log.debug("Template filled: {}".format(str(template_filled))) dst_collection = assemble([os.path.normpath(template_filled)]) @@ -718,9 +725,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "context": repre_context } - if is_sequence_representation and repre.get("frameStart") is not None: - representation["context"]["frame"] = template_data["frame"] - return { "representation": representation, "anatomy_data": template_data, From a1122496c1c57e62a6a1118cee0fbcc20d4eec1e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 28 Jul 2022 10:46:25 +0200 Subject: [PATCH 121/131] add missing project tasks into fields --- openpype/pipeline/template_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/template_data.py b/openpype/pipeline/template_data.py index de46650f9d..824a25127c 100644 --- a/openpype/pipeline/template_data.py +++ b/openpype/pipeline/template_data.py @@ -213,7 +213,9 @@ def get_template_data_with_names( Dict[str, Any]: Data prepared for filling workdir template. """ - project_doc = get_project(project_name, fields=["name", "data.code"]) + project_doc = get_project( + project_name, fields=["name", "data.code", "config.tasks"] + ) asset_doc = None if asset_name: asset_doc = get_asset_by_name( From 7adb8453861ce29f095082494ced13b755921fc5 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 28 Jul 2022 12:24:46 +0300 Subject: [PATCH 122/131] Add OCIO submodule. --- .gitmodules | 3 +++ vendor/configs/OpenColorIO-Configs | 1 + 2 files changed, 4 insertions(+) create mode 160000 vendor/configs/OpenColorIO-Configs diff --git a/.gitmodules b/.gitmodules index dfd89cdb3c..bac3132b77 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ [submodule "tools/modules/powershell/PSWriteColor"] path = tools/modules/powershell/PSWriteColor url = https://github.com/EvotecIT/PSWriteColor.git +[submodule "vendor/configs/OpenColorIO-Configs"] + path = vendor/configs/OpenColorIO-Configs + url = https://github.com/imageworks/OpenColorIO-Configs diff --git a/vendor/configs/OpenColorIO-Configs b/vendor/configs/OpenColorIO-Configs new file mode 160000 index 0000000000..0bb079c08b --- /dev/null +++ b/vendor/configs/OpenColorIO-Configs @@ -0,0 +1 @@ +Subproject commit 0bb079c08be410030669cbf5f19ff869b88af953 From 86070835b9883b46baa27e12bb079b9866b18356 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 28 Jul 2022 12:33:29 +0300 Subject: [PATCH 123/131] Add OCIO path function. --- .../maya/plugins/publish/extract_look.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index d35b529c76..ce699d3d9a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -534,3 +534,25 @@ class ExtractModelRenderSets(ExtractLook): self.scene_type = self.scene_type_prefix + self.scene_type return typ + + +def get_ocio_config_path(profile_folder): + """Path to OpenPype vendorized OCIO. + + Vendorized OCIO config file path is grabbed from the specific path + hierarchy specified below. + + "{OPENPYPE_ROOT}/vendor/OpenColorIO-Configs/{profile_folder}/config.ocio" + Args: + profile_folder (str): Name of folder to grab config file from. + + Returns: + str: Path to vendorized config file. + """ + return os.path.join( + os.environ["OPENPYPE_ROOT"], + "vendor", + "OpenColorIO-Configs", + profile_folder, + "config.ocio" + ) From 03767d28912b65a47b66826cc359a6db0baf4533 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 28 Jul 2022 13:03:37 +0300 Subject: [PATCH 124/131] move function --- .../maya/plugins/publish/extract_look.py | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index ce699d3d9a..42d4835fdf 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -27,6 +27,28 @@ def escape_space(path): return '"{}"'.format(path) if " " in path else path +def get_ocio_config_path(profile_folder): + """Path to OpenPype vendorized OCIO. + + Vendorized OCIO config file path is grabbed from the specific path + hierarchy specified below. + + "{OPENPYPE_ROOT}/vendor/OpenColorIO-Configs/{profile_folder}/config.ocio" + Args: + profile_folder (str): Name of folder to grab config file from. + + Returns: + str: Path to vendorized config file. + """ + return os.path.join( + os.environ["OPENPYPE_ROOT"], + "vendor", + "OpenColorIO-Configs", + profile_folder, + "config.ocio" + ) + + def find_paths_by_hash(texture_hash): """Find the texture hash key in the dictionary. @@ -492,7 +514,6 @@ class ExtractLook(openpype.api.Extractor): colorconvert = "--colorconvert sRGB linear" else: colorconvert = "" - # Ensure folder exists if not os.path.exists(os.path.dirname(converted)): os.makedirs(os.path.dirname(converted)) @@ -534,25 +555,3 @@ class ExtractModelRenderSets(ExtractLook): self.scene_type = self.scene_type_prefix + self.scene_type return typ - - -def get_ocio_config_path(profile_folder): - """Path to OpenPype vendorized OCIO. - - Vendorized OCIO config file path is grabbed from the specific path - hierarchy specified below. - - "{OPENPYPE_ROOT}/vendor/OpenColorIO-Configs/{profile_folder}/config.ocio" - Args: - profile_folder (str): Name of folder to grab config file from. - - Returns: - str: Path to vendorized config file. - """ - return os.path.join( - os.environ["OPENPYPE_ROOT"], - "vendor", - "OpenColorIO-Configs", - profile_folder, - "config.ocio" - ) From cd7ef426d891381de1c8d4e028c967793784d130 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 28 Jul 2022 13:31:39 +0300 Subject: [PATCH 125/131] Add configuration variable to `maketx` --- openpype/hosts/maya/plugins/publish/extract_look.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index 42d4835fdf..faea0247da 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -514,6 +514,9 @@ class ExtractLook(openpype.api.Extractor): colorconvert = "--colorconvert sRGB linear" else: colorconvert = "" + + config_path = get_ocio_config_path("nuke-default") + color_config = "--colorconfig {0}".format(config_path) # Ensure folder exists if not os.path.exists(os.path.dirname(converted)): os.makedirs(os.path.dirname(converted)) @@ -523,10 +526,11 @@ class ExtractLook(openpype.api.Extractor): filepath, converted, # Include `source-hash` as string metadata - "-sattrib", + "--sattrib", "sourceHash", escape_space(texture_hash), colorconvert, + color_config ) return converted, COPY, texture_hash From 81f3bd379b34acb9727a9ab6ad621a87e9bcb9b1 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 28 Jul 2022 13:31:58 +0300 Subject: [PATCH 126/131] Fix function path bug --- openpype/hosts/maya/plugins/publish/extract_look.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index faea0247da..f71a01e474 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -43,6 +43,7 @@ def get_ocio_config_path(profile_folder): return os.path.join( os.environ["OPENPYPE_ROOT"], "vendor", + "config", "OpenColorIO-Configs", profile_folder, "config.ocio" From e287e1fd48af95c6bd5822e6d0f93d37b7896080 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Thu, 28 Jul 2022 13:44:19 +0300 Subject: [PATCH 127/131] Fix bugs --- openpype/hosts/maya/plugins/publish/extract_look.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index f71a01e474..0b26e922d5 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -43,7 +43,7 @@ def get_ocio_config_path(profile_folder): return os.path.join( os.environ["OPENPYPE_ROOT"], "vendor", - "config", + "configs", "OpenColorIO-Configs", profile_folder, "config.ocio" @@ -102,10 +102,11 @@ def maketx(source, destination, *args): # use oiio-optimized settings for tile-size, planarconfig, metadata "--oiio", "--filter lanczos3", + escape_space(source) ] cmd.extend(args) - cmd.extend(["-o", escape_space(destination), escape_space(source)]) + cmd.extend(["-o", escape_space(destination)]) cmd = " ".join(cmd) From bc2cec540c8b7962d3b0c0fc8dabe5f6cf54fb36 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 28 Jul 2022 16:29:50 +0200 Subject: [PATCH 128/131] trayp: improving user feedback --- openpype/hosts/traypublisher/api/editorial.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/traypublisher/api/editorial.py b/openpype/hosts/traypublisher/api/editorial.py index 92ad65a851..7c392ef508 100644 --- a/openpype/hosts/traypublisher/api/editorial.py +++ b/openpype/hosts/traypublisher/api/editorial.py @@ -55,7 +55,7 @@ class ShotMetadataSolver: return shot_rename_template.format(**data) except KeyError as _E: raise CreatorError(( - "Make sure all keys are correct in settings: \n\n" + "Make sure all keys in settings are correct:: \n\n" f"From template string {shot_rename_template} > " f"`{_E}` has no equivalent in \n" f"{list(data.keys())} input formating keys!" @@ -91,10 +91,13 @@ class ShotMetadataSolver: match = p.findall(search_text) if not match: raise CreatorError(( - "Make sure regex expression is correct: \n\n" - f"From settings '{token_key}' key " - f"with '{pattern}' expression, \n" - f"is not able to find anything in '{search_text}'!" + "Make sure regex expression works with your data: \n\n" + f"'{token_key}' with regex '{pattern}' in your settings\n" + "can't find any match in your clip name " + f"'{search_text}'!\n\nLook to: " + "'project_settings/traypublisher/editorial_creators" + "/editorial_simple/clip_name_tokenizer'\n" + "at your project settings..." )) # QUESTION:how to refactory `match[-1]` to some better way? @@ -129,7 +132,7 @@ class ShotMetadataSolver: } except KeyError as _E: raise CreatorError(( - "Make sure all keys are correct in settings: \n" + "Make sure all keys in settings are correct : \n" f"`{_E}` has no equivalent in \n{list(data.keys())}" )) @@ -146,9 +149,10 @@ class ShotMetadataSolver: **_parent_tokens_formating_data) except KeyError as _E: raise CreatorError(( - "Make sure all keys are correct in settings: \n\n" - f"From template string {shot_hierarchy['parents_path']} > " - f"`{_E}` has no equivalent in \n" + "Make sure all keys in settings are correct : \n\n" + f"`{_E}` from template string " + f"{shot_hierarchy['parents_path']}, " + f" has no equivalent in \n" f"{list(_parent_tokens_formating_data.keys())} parents" )) From 443c5a369619a907f83c9bdb43783ce64d9edc0e Mon Sep 17 00:00:00 2001 From: Felix David Date: Thu, 28 Jul 2022 17:01:54 +0200 Subject: [PATCH 129/131] Fix: Shot&Sequence name with prefix over appends --- openpype/modules/kitsu/utils/update_op_with_zou.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 02c27382eb..040d6566f7 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -230,9 +230,9 @@ def update_op_assets( if item_type in ["Shot", "Sequence"]: # Name with parents hierarchy "({episode}_){sequence}_{shot}" # to avoid duplicate name issue - item_name = "_".join(item_data["parents"] + [item_doc["name"]]) + item_name = f"{item_data['parents'][-1]}_{item['name']}" else: - item_name = item_doc["name"] + item_name = item["name"] # Set root folders parents item_data["parents"] = entity_parent_folders + item_data["parents"] From f08008d61ec577f46a61469dc4bfa8a495d3dfbc Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 29 Jul 2022 12:22:20 +0300 Subject: [PATCH 130/131] Revert "Add OCIO submodule." This reverts commit 7adb8453861ce29f095082494ced13b755921fc5. --- .gitmodules | 3 --- vendor/configs/OpenColorIO-Configs | 1 - 2 files changed, 4 deletions(-) delete mode 160000 vendor/configs/OpenColorIO-Configs diff --git a/.gitmodules b/.gitmodules index bac3132b77..dfd89cdb3c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,6 +5,3 @@ [submodule "tools/modules/powershell/PSWriteColor"] path = tools/modules/powershell/PSWriteColor url = https://github.com/EvotecIT/PSWriteColor.git -[submodule "vendor/configs/OpenColorIO-Configs"] - path = vendor/configs/OpenColorIO-Configs - url = https://github.com/imageworks/OpenColorIO-Configs diff --git a/vendor/configs/OpenColorIO-Configs b/vendor/configs/OpenColorIO-Configs deleted file mode 160000 index 0bb079c08b..0000000000 --- a/vendor/configs/OpenColorIO-Configs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0bb079c08be410030669cbf5f19ff869b88af953 From 1267e9ea921381ca0b5d8907c0a9271352f0c078 Mon Sep 17 00:00:00 2001 From: Allan Ihsan Date: Fri, 29 Jul 2022 12:48:01 +0300 Subject: [PATCH 131/131] Revert "Revert "Add OCIO submodule."" This reverts commit f08008d61ec577f46a61469dc4bfa8a495d3dfbc. --- .gitmodules | 3 +++ vendor/configs/OpenColorIO-Configs | 1 + 2 files changed, 4 insertions(+) create mode 160000 vendor/configs/OpenColorIO-Configs diff --git a/.gitmodules b/.gitmodules index dfd89cdb3c..bac3132b77 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,6 @@ [submodule "tools/modules/powershell/PSWriteColor"] path = tools/modules/powershell/PSWriteColor url = https://github.com/EvotecIT/PSWriteColor.git +[submodule "vendor/configs/OpenColorIO-Configs"] + path = vendor/configs/OpenColorIO-Configs + url = https://github.com/imageworks/OpenColorIO-Configs diff --git a/vendor/configs/OpenColorIO-Configs b/vendor/configs/OpenColorIO-Configs new file mode 160000 index 0000000000..0bb079c08b --- /dev/null +++ b/vendor/configs/OpenColorIO-Configs @@ -0,0 +1 @@ +Subproject commit 0bb079c08be410030669cbf5f19ff869b88af953