From acb4cebbf503dc491a89adfce38a072fb6111398 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 24 Jun 2019 21:55:31 +0100 Subject: [PATCH 01/44] Add tags on project load. --- pype/nukestudio/__init__.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pype/nukestudio/__init__.py b/pype/nukestudio/__init__.py index c8cdc3e0f8..aa4246714b 100644 --- a/pype/nukestudio/__init__.py +++ b/pype/nukestudio/__init__.py @@ -1,17 +1,19 @@ import os -import sys + from avalon import api as avalon from pyblish import api as pyblish from .. import api - from .menu import ( install as menu_install, _update_menu_task_label ) +from .tags import add_tags_from_presets from pypeapp import Logger +import hiero + log = Logger().get_logger(__name__, "nukestudio") AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") @@ -55,6 +57,15 @@ def install(config): # load data from templates api.load_data_from_templates() + # Add tags on project load. + hiero.core.events.registerInterest( + "kAfterProjectLoad", add_tags + ) + + +def add_tags(event): + add_tags_from_presets() + def uninstall(): log.info("Deregistering NukeStudio plug-ins..") From d864fb0cfa1f90aad9c2b38298757a2f046afc3f Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 24 Jun 2019 22:13:14 +0100 Subject: [PATCH 02/44] Get task tags from project config. --- pype/nukestudio/tags.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pype/nukestudio/tags.py b/pype/nukestudio/tags.py index e6c29a4f4e..bb8582da70 100644 --- a/pype/nukestudio/tags.py +++ b/pype/nukestudio/tags.py @@ -1,9 +1,12 @@ -import hiero import re + from pypeapp import ( config, Logger ) +from avalon import io + +import hiero log = Logger().get_logger(__name__, "nukestudio") @@ -59,6 +62,21 @@ def add_tags_from_presets(): nks_pres = presets['nukestudio'] nks_pres_tags = nks_pres.get("tags", None) + # Get project task types. + tasks = io.find_one({"type": "project"})["config"]["tasks"] + nks_pres_tags["[Tasks]"] = {} + for task in tasks: + nks_pres_tags["[Tasks]"][task["name"]] = { + "editable": "1", + "note": "Tag note", + "icon": { + "path": "" + }, + "metadata": { + "family": "task" + } + } + # get project and root bin object project = hiero.core.projects()[-1] root_bin = project.tagsBin() From c331a4c8f653d6f0395e3efce853daf747632424 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 25 Jun 2019 11:12:28 +0100 Subject: [PATCH 03/44] Add assets tag bin --- pype/nukestudio/tags.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pype/nukestudio/tags.py b/pype/nukestudio/tags.py index bb8582da70..04a99d2cb8 100644 --- a/pype/nukestudio/tags.py +++ b/pype/nukestudio/tags.py @@ -68,7 +68,7 @@ def add_tags_from_presets(): for task in tasks: nks_pres_tags["[Tasks]"][task["name"]] = { "editable": "1", - "note": "Tag note", + "note": "", "icon": { "path": "" }, @@ -77,6 +77,22 @@ def add_tags_from_presets(): } } + # Get project assets. Currently Ftrack specific to differentiate between + # asset builds and shots. + nks_pres_tags["[Assets]"] = {} + for asset in io.find({"type": "asset"}): + if asset["data"]["entityType"] == "AssetBuild": + nks_pres_tags["[Assets]"][asset["name"]] = { + "editable": "1", + "note": "", + "icon": { + "path": "" + }, + "metadata": { + "family": "asset" + } + } + # get project and root bin object project = hiero.core.projects()[-1] root_bin = project.tagsBin() From f9c606a5d5721b1c88418601a1daba0cd60c4847 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 26 Jun 2019 10:04:23 +0100 Subject: [PATCH 04/44] Support shot instances that only creates hierarchies. --- .../publish/integrate_hierarchy_ftrack.py | 4 +- .../publish/integrate_hierarchy_avalon.py | 2 +- .../nukestudio/publish/collect_handles.py | 8 +-- .../publish/collect_hierarchy_context.py | 4 +- .../nukestudio/publish/collect_shot.py | 52 +++++++++++++++++++ .../nukestudio/publish/collect_subsets.py | 4 +- .../nukestudio/publish/collect_tag_tasks.py | 13 +---- .../nukestudio/publish/collect_tag_types.py | 2 + .../nukestudio/publish/validate_hierarchy.py | 2 +- 9 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 pype/plugins/nukestudio/publish/collect_shot.py diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index 230cc0e4f1..69788756cf 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -27,7 +27,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder - 0.04 label = 'Integrate Hierarchy To Ftrack' - families = ["clip"] + families = ["clip", "shot"] optional = False def process(self, context): @@ -46,6 +46,8 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): for entity_name in input_data: entity_data = input_data[entity_name] entity_type = entity_data['entity_type'] + self.log.debug(entity_data) + self.log.debug(entity_type) if entity_type.lower() == 'project': query = 'Project where full_name is "{}"'.format(entity_name) diff --git a/pype/plugins/global/publish/integrate_hierarchy_avalon.py b/pype/plugins/global/publish/integrate_hierarchy_avalon.py index d75fd10792..66a3825a1d 100644 --- a/pype/plugins/global/publish/integrate_hierarchy_avalon.py +++ b/pype/plugins/global/publish/integrate_hierarchy_avalon.py @@ -10,7 +10,7 @@ class IntegrateHierarchyToAvalon(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder - 0.1 label = 'Integrate Hierarchy To Avalon' - families = ['clip'] + families = ['clip', "shot"] def process(self, context): if "hierarchyContext" not in context.data: diff --git a/pype/plugins/nukestudio/publish/collect_handles.py b/pype/plugins/nukestudio/publish/collect_handles.py index 9ac0ed76f5..2f2831237f 100644 --- a/pype/plugins/nukestudio/publish/collect_handles.py +++ b/pype/plugins/nukestudio/publish/collect_handles.py @@ -39,6 +39,8 @@ class CollectClipHandles(api.ContextPlugin): instance.data["name"])) name = instance.data["asset"] s_asset_data = assets_shared.get(name) - instance.data["handles"] = s_asset_data["handles"] - instance.data["handleStart"] = s_asset_data["handleStart"] - instance.data["handleEnd"] = s_asset_data["handleEnd"] + instance.data["handles"] = s_asset_data.get("handles", 0) + instance.data["handleStart"] = s_asset_data.get( + "handleStart", 0 + ) + instance.data["handleEnd"] = s_asset_data.get("handleEnd", 0) diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py index 7082ecd210..9a82967311 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -203,8 +203,6 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): self.log.debug("__ instance.data[parents]: {}".format(instance.data["parents"])) self.log.debug("__ instance.data[hierarchy]: {}".format(instance.data["hierarchy"])) self.log.debug("__ instance.data[name]: {}".format(instance.data["name"])) - if "main" not in instance.data["name"].lower(): - continue in_info = {} # suppose that all instances are Shots @@ -224,7 +222,7 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): handle_end = instance.data.get('handleEnd') self.log.debug("__ handle_start: {}".format(handle_start)) self.log.debug("__ handle_end: {}".format(handle_end)) - + if handle_start and handle_end: in_info['custom_attributes'].update({ "handle_start": handle_start, diff --git a/pype/plugins/nukestudio/publish/collect_shot.py b/pype/plugins/nukestudio/publish/collect_shot.py new file mode 100644 index 0000000000..2b371b1aac --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_shot.py @@ -0,0 +1,52 @@ +from pyblish import api + + +class CollectShot(api.InstancePlugin): + """Collect Shot from Clip.""" + + # Run just before CollectSubsets + order = api.CollectorOrder + 0.1025 + label = "Collect Shot" + hosts = ["nukestudio"] + families = ["clip"] + + def process(self, instance): + context = instance.context + + # Collect data. + data = {} + for key, value in instance.data.iteritems(): + data[key] = value + + data["family"] = "shot" + data["families"] = [] + data["frameStart"] = 1 + + # Get handles. + data["handleStart"] = instance.data["handleStart"] + data["handles"] + data["handleEnd"] = instance.data["handleEnd"] + data["handles"] + + # Frame-ranges with handles. + data["sourceInH"] = data["sourceIn"] - data["handleStart"] + data["sourceOutH"] = data["sourceOut"] + data["handleEnd"] + + # Get timeline frames. + data["timelineIn"] = int(data["item"].timelineIn()) + data["timelineOut"] = int(data["item"].timelineOut()) + + # Frame-ranges with handles. + data["timelineInHandles"] = data["timelineIn"] - data["handleStart"] + data["timelineOutHandles"] = data["timelineOut"] + data["handleEnd"] + + # Creating comp frame range. + data["endFrame"] = ( + data["frameStart"] + (data["sourceOut"] - data["sourceIn"]) + ) + + # Get fps. + sequence = context.data["activeSequence"] + data["fps"] = sequence.framerate() + + # Create instance. + self.log.debug("Creating instance with: {}".format(data)) + context.create_instance(**data) diff --git a/pype/plugins/nukestudio/publish/collect_subsets.py b/pype/plugins/nukestudio/publish/collect_subsets.py index 44099e50da..95476b4db7 100644 --- a/pype/plugins/nukestudio/publish/collect_subsets.py +++ b/pype/plugins/nukestudio/publish/collect_subsets.py @@ -116,8 +116,6 @@ class CollectClipSubsets(api.InstancePlugin): # get specific presets pr_host_tasks = deepcopy( nks_presets["rules_tasks"]).get("hostTasks", None) - pr_host_subsets = deepcopy( - nks_presets["rules_tasks"]).get("hostSubsets", None) subsets_collect = dict() # iterate tags and collect subset properities from presets @@ -134,7 +132,7 @@ class CollectClipSubsets(api.InstancePlugin): try: # get subsets for task subsets = None - subsets = pr_host_subsets[host] + #subsets = pr_host_subsets[host] except KeyError: pass diff --git a/pype/plugins/nukestudio/publish/collect_tag_tasks.py b/pype/plugins/nukestudio/publish/collect_tag_tasks.py index 592559fc50..ed2f3009d3 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_tasks.py +++ b/pype/plugins/nukestudio/publish/collect_tag_tasks.py @@ -13,13 +13,6 @@ class CollectClipTagTasks(api.InstancePlugin): # gets tags tags = instance.data["tags"] - # gets presets for nukestudio - presets = instance.context.data['presets'][ - instance.context.data['host']] - - # find preset for default task - default_tasks = presets['rules_tasks']['defaultTasks'] - tasks = list() for t in tags: t_metadata = dict(t["metadata"]) @@ -30,11 +23,7 @@ class CollectClipTagTasks(api.InstancePlugin): t_task = t_metadata.get("tag.label", "") tasks.append(t_task) - if tasks: - instance.data["tasks"] = tasks - else: - # add tasks from presets if no task tag - instance.data["tasks"] = default_tasks + instance.data["tasks"] = tasks self.log.info("Collected Tasks from Tags: `{}`".format( instance.data["tasks"])) diff --git a/pype/plugins/nukestudio/publish/collect_tag_types.py b/pype/plugins/nukestudio/publish/collect_tag_types.py index 6889ddd81a..a33c71254f 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_types.py +++ b/pype/plugins/nukestudio/publish/collect_tag_types.py @@ -31,6 +31,8 @@ class CollectClipTagTypes(api.InstancePlugin): if subset_names: instance.data["subsetType"] = subset_names[0] + else: + instance.data["subsetType"] = "main" self.log.info("Collected Plate Types from Tags: `{}`".format( instance.data["subsetType"])) diff --git a/pype/plugins/nukestudio/publish/validate_hierarchy.py b/pype/plugins/nukestudio/publish/validate_hierarchy.py index 2ddec1bcfc..8013a98efd 100644 --- a/pype/plugins/nukestudio/publish/validate_hierarchy.py +++ b/pype/plugins/nukestudio/publish/validate_hierarchy.py @@ -7,7 +7,7 @@ class ValidateHierarchy(api.InstancePlugin): """ order = api.ValidatorOrder - families = ["clip"] + families = ["clip", "shot"] label = "Validate Hierarchy" hosts = ["nukestudio"] From 7131dbfeffbf7da73e32d6db129da1d24f956783 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 26 Jun 2019 10:25:58 +0100 Subject: [PATCH 05/44] Only Hierarchy tagged clips should generate shots. --- pype/plugins/nukestudio/publish/collect_shot.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pype/plugins/nukestudio/publish/collect_shot.py b/pype/plugins/nukestudio/publish/collect_shot.py index 2b371b1aac..a5116ace4b 100644 --- a/pype/plugins/nukestudio/publish/collect_shot.py +++ b/pype/plugins/nukestudio/publish/collect_shot.py @@ -11,6 +11,18 @@ class CollectShot(api.InstancePlugin): families = ["clip"] def process(self, instance): + hierarchy_tagged = False + for tag in instance.data["tags"]: + if tag["name"].lower() == "hierarchy": + hierarchy_tagged = True + + if not hierarchy_tagged: + self.log.debug( + "Skipping \"{}\" because its not tagged with " + "\"Hierarchy\"".format(instance) + ) + return + context = instance.context # Collect data. From 6f273059362bb94b0f816467ca6a5e32f2df1987 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 26 Jun 2019 11:29:00 +0100 Subject: [PATCH 06/44] Fix same named track item and track would overwrite clip instances and be ignored. --- .../nukestudio/publish/collect_clips.py | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_clips.py b/pype/plugins/nukestudio/publish/collect_clips.py index af542af7a5..ed74bf1863 100644 --- a/pype/plugins/nukestudio/publish/collect_clips.py +++ b/pype/plugins/nukestudio/publish/collect_clips.py @@ -12,8 +12,9 @@ class CollectClips(api.ContextPlugin): def process(self, context): projectdata = context.data["projectData"] version = context.data.get("version", "001") - data = {} + instances_data = [] for item in context.data.get("selection", []): + self.log.debug(item) # Skip audio track items # Try/Except is to handle items types, like EffectTrackItem try: @@ -26,44 +27,49 @@ class CollectClips(api.ContextPlugin): track = item.parent() source = item.source().mediaSource() source_path = source.firstpath() - instance_name = "{0}_{1}".format(track.name(), item.name()) try: - head, padding, ext = os.path.basename(source_path).split('.') + head, padding, ext = os.path.basename(source_path).split(".") source_first_frame = int(padding) except: source_first_frame = 0 - data[instance_name] = { - "item": item, - "source": source, - "sourcePath": source_path, - "track": track.name(), - "sourceFirst": source_first_frame, - "sourceIn": int(item.sourceIn()), - "sourceOut": int(item.sourceOut()), - "startFrame": int(item.timelineIn()), - "endFrame": int(item.timelineOut()) - } + instances_data.append( + { + "name": "{0}_{1}".format(track.name(), item.name()), + "item": item, + "source": source, + "sourcePath": source_path, + "track": track.name(), + "sourceFirst": source_first_frame, + "sourceIn": int(item.sourceIn()), + "sourceOut": int(item.sourceOut()), + "startFrame": int(item.timelineIn()), + "endFrame": int(item.timelineOut()) + } + ) - for key, value in data.items(): + for data in instances_data: family = "clip" - context.create_instance( - name=key, - asset=value["item"].name(), - item=value["item"], - source=value["source"], - sourcePath=value["sourcePath"], + instance = context.create_instance( + name=data["name"], + asset=data["item"].name(), + item=data["item"], + source=data["source"], + sourcePath=data["sourcePath"], family=family, families=[], - sourceFirst=value["sourceFirst"], - sourceIn=value["sourceIn"], - sourceOut=value["sourceOut"], - startFrame=value["startFrame"], - endFrame=value["endFrame"], - handles=projectdata['handles'], + sourceFirst=data["sourceFirst"], + sourceIn=data["sourceIn"], + sourceOut=data["sourceOut"], + startFrame=data["startFrame"], + endFrame=data["endFrame"], + handles=projectdata["handles"], handleStart=0, handleEnd=0, version=version, - track=value["track"] + track=data["track"] + ) + self.log.debug( + "Created instance with data: {}".format(instance.data) ) From 420472d469d49239496a2028812616317be2e178 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 26 Jun 2019 11:34:29 +0100 Subject: [PATCH 07/44] Code cosmetics Remove double data entries. --- .../nukestudio/publish/collect_clips.py | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_clips.py b/pype/plugins/nukestudio/publish/collect_clips.py index ed74bf1863..f705186ffe 100644 --- a/pype/plugins/nukestudio/publish/collect_clips.py +++ b/pype/plugins/nukestudio/publish/collect_clips.py @@ -50,26 +50,18 @@ class CollectClips(api.ContextPlugin): ) for data in instances_data: - family = "clip" - instance = context.create_instance( - name=data["name"], - asset=data["item"].name(), - item=data["item"], - source=data["source"], - sourcePath=data["sourcePath"], - family=family, - families=[], - sourceFirst=data["sourceFirst"], - sourceIn=data["sourceIn"], - sourceOut=data["sourceOut"], - startFrame=data["startFrame"], - endFrame=data["endFrame"], - handles=projectdata["handles"], - handleStart=0, - handleEnd=0, - version=version, - track=data["track"] + data.update( + { + "asset": data["item"].name(), + "family": "clip", + "families": [], + "handles": projectdata["handles"], + "handleStart": 0, + "handleEnd": 0, + "version": version + } ) + instance = context.create_instance(**data) self.log.debug( "Created instance with data: {}".format(instance.data) ) From bae768260c429cd56e8fd825e984d04ecca47b84 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 26 Jun 2019 12:54:19 +0100 Subject: [PATCH 08/44] Collect plates from tags. --- .../nukestudio/publish/collect_plates.py | 108 ++++++++++++++++-- .../nukestudio/publish/collect_shot.py | 6 +- 2 files changed, 101 insertions(+), 13 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index ca211401a5..2343835863 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -1,12 +1,99 @@ +import os + from pyblish import api -import pype -class CollectPlates(api.InstancePlugin): +class CollectPlates(api.ContextPlugin): + """Collect plates from tags. + + Tag is expected to have metadata: + { + "family": "plate" + "subset": "main" + } + """ + + # Run just before CollectSubsets + order = api.CollectorOrder + 0.1025 + label = "Collect Plates" + hosts = ["nukestudio"] + + def process(self, context): + for instance in context[:]: + # Exclude non-tagged instances. + tagged = False + for tag in instance.data["tags"]: + family = dict(tag["metadata"]).get("tag.family", "") + if family.lower() == "plate": + tagged = True + + if not tagged: + self.log.debug( + "Skipping \"{}\" because its not tagged with " + "\"plate\"".format(instance) + ) + continue + + # Collect data. + data = {} + for key, value in instance.data.iteritems(): + data[key] = value + + data["family"] = "plate" + data["families"] = [] + data["label"] += ( + " ({})".format(os.path.splitext(data["sourcePath"])[1]) + ) + data["subset"] = dict(tag["metadata"])["tag.subset"] + + # Timeline data. + handle_start = int(instance.data["handleStart"] + data["handles"]) + handle_end = int(instance.data["handleEnd"] + data["handles"]) + + source_in_h = data["sourceIn"] - handle_start + source_out_h = data["sourceOut"] + handle_end + + timeline_in = int(data["item"].timelineIn()) + timeline_out = int(data["item"].timelineOut()) + + timeline_frame_start = timeline_in - handle_start + timeline_frame_end = timeline_out + handle_end + + frame_start = 1 + frame_end = frame_start + (data["sourceOut"] - data["sourceIn"]) + + sequence = context.data["activeSequence"] + fps = sequence.framerate() + + data.update( + { + "sourceFirst": data["sourceFirst"], + "sourceIn": data["sourceIn"], + "sourceOut": data["sourceOut"], + "sourceInH": source_in_h, + "sourceOutH": source_out_h, + "frameStart": frame_start, + "startFrame": frame_start, + "endFrame": frame_end, + "timelineIn": timeline_in, + "timelineOut": timeline_out, + "timelineInHandles": timeline_frame_start, + "timelineOutHandles": timeline_frame_end, + "fps": fps, + "handleStart": handle_start, + "handleEnd": handle_end + } + ) + + self.log.debug("Creating instance with data: {}".format(data)) + context.create_instance(**data) + + +class CollectPlatesData(api.InstancePlugin): """Collect plates""" - order = api.CollectorOrder + 0.49 - label = "Collect Plates" + order = api.CollectorOrder + 0.495 + label = "Collect Plates Data" hosts = ["nukestudio"] families = ["plate"] @@ -25,12 +112,17 @@ class CollectPlates(api.InstancePlugin): name = instance.data["subset"] asset = instance.data["asset"] track = instance.data["track"] - family = instance.data["family"] - families = instance.data["families"] version = instance.data["version"] source_path = instance.data["sourcePath"] source_file = os.path.basename(source_path) + # Filter out "clip" family. + families = instance.data["families"] + [instance.data["family"]] + families = list(set(families)) + if "clip" in families: + families.remove("clip") + family = families[-1] + # staging dir creation staging_dir = os.path.dirname( source_path) @@ -95,10 +187,6 @@ class CollectPlates(api.InstancePlugin): self.log.debug("__ before family: {}".format(family)) self.log.debug("__ before families: {}".format(families)) - # - # this is just workaround because 'clip' family is filtered - instance.data["family"] = families[-1] - instance.data["families"].append(family) # add to data of representation version_data.update({ diff --git a/pype/plugins/nukestudio/publish/collect_shot.py b/pype/plugins/nukestudio/publish/collect_shot.py index a5116ace4b..6188508ecd 100644 --- a/pype/plugins/nukestudio/publish/collect_shot.py +++ b/pype/plugins/nukestudio/publish/collect_shot.py @@ -11,12 +11,12 @@ class CollectShot(api.InstancePlugin): families = ["clip"] def process(self, instance): - hierarchy_tagged = False + tagged = False for tag in instance.data["tags"]: if tag["name"].lower() == "hierarchy": - hierarchy_tagged = True + tagged = True - if not hierarchy_tagged: + if not tagged: self.log.debug( "Skipping \"{}\" because its not tagged with " "\"Hierarchy\"".format(instance) From ae5481d4e30deb252ea20422b44cb96761d0d16f Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 26 Jun 2019 14:44:18 +0100 Subject: [PATCH 09/44] Integrate plates. Remove preview. --- .../publish/integrate_assumed_destination.py | 2 +- .../nukestudio/publish/collect_plates.py | 27 ++----------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/pype/plugins/global/publish/integrate_assumed_destination.py b/pype/plugins/global/publish/integrate_assumed_destination.py index 758eca5a9f..6999ce6ab8 100644 --- a/pype/plugins/global/publish/integrate_assumed_destination.py +++ b/pype/plugins/global/publish/integrate_assumed_destination.py @@ -9,7 +9,7 @@ class IntegrateAssumedDestination(pyblish.api.InstancePlugin): label = "Integrate Assumed Destination" order = pyblish.api.IntegratorOrder - 0.05 - families = ["clip", "projectfile"] + families = ["clip", "projectfile", "plate"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index 2343835863..e45b147096 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -40,11 +40,12 @@ class CollectPlates(api.ContextPlugin): data[key] = value data["family"] = "plate" - data["families"] = [] + data["families"] = ["ftrack"] data["label"] += ( " ({})".format(os.path.splitext(data["sourcePath"])[1]) ) data["subset"] = dict(tag["metadata"])["tag.subset"] + data["source"] = data["sourcePath"] # Timeline data. handle_start = int(instance.data["handleStart"] + data["handles"]) @@ -232,27 +233,6 @@ class CollectPlatesData(api.InstancePlugin): start_frame = source_in_h end_frame = source_out_h - - mov_file = head + ".mov" - mov_path = os.path.normpath(os.path.join(staging_dir, mov_file)) - if os.path.exists(mov_path): - # adding mov into the representations - self.log.debug("__ mov_path: {}".format(mov_path)) - plates_mov_representation = { - 'files': mov_file, - 'stagingDir': staging_dir, - 'startFrame': 0, - 'endFrame': source_out - source_in + 1, - 'step': 1, - 'frameRate': fps, - 'preview': True, - 'thumbnail': False, - 'name': "preview", - 'ext': "mov", - } - instance.data["representations"].append( - plates_mov_representation) - thumb_file = head + ".png" thumb_path = os.path.join(staging_dir, thumb_file) self.log.debug("__ thumb_path: {}".format(thumb_path)) @@ -293,6 +273,3 @@ class CollectPlatesData(api.InstancePlugin): plates_representation)) self.log.debug("__ after family: {}".format(family)) self.log.debug("__ after families: {}".format(families)) - - # # this will do FnNsFrameServer - # FnNsFrameServer.renderFrames(*_args) From 93ec72767a97a1cf0f87f5486379e667061a0c88 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 26 Jun 2019 16:34:52 +0100 Subject: [PATCH 10/44] Support *.nk sources for plates. --- .../nukestudio/publish/collect_clips.py | 30 ++++++++++++++++++- .../nukestudio/publish/collect_plates.py | 23 ++++++++++---- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_clips.py b/pype/plugins/nukestudio/publish/collect_clips.py index f705186ffe..8025bbcd02 100644 --- a/pype/plugins/nukestudio/publish/collect_clips.py +++ b/pype/plugins/nukestudio/publish/collect_clips.py @@ -1,6 +1,9 @@ import os + from pyblish import api +import nuke + class CollectClips(api.ContextPlugin): """Collect all Track items selection.""" @@ -14,7 +17,6 @@ class CollectClips(api.ContextPlugin): version = context.data.get("version", "001") instances_data = [] for item in context.data.get("selection", []): - self.log.debug(item) # Skip audio track items # Try/Except is to handle items types, like EffectTrackItem try: @@ -28,6 +30,32 @@ class CollectClips(api.ContextPlugin): source = item.source().mediaSource() source_path = source.firstpath() + # If source is *.nk its a comp effect and we need to fetch the + # write node output. + if source_path.endswith(".nk"): + nuke.scriptOpen(source_path) + # There should noly be one. + write_node = nuke.allNodes(filter="Write")[0] + path = nuke.filename(write_node) + + if "%" in path: + # Get start frame from Nuke script and use the item source + # in/out, because you can have multiple shots covered with + # one nuke script. + start_frame = int(nuke.root()["first_frame"].getValue()) + if write_node["use_limit"].getValue(): + start_frame = int(write_node["first"].getValue()) + + path = path % (start_frame + item.sourceIn()) + + source_path = path + self.log.debug( + "Fetched source path \"{}\" from \"{}\" in " + "\"{}\".".format( + source_path, write_node.name(), source.firstpath() + ) + ) + try: head, padding, ext = os.path.basename(source_path).split(".") source_first_frame = int(padding) diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index e45b147096..41d0f346f8 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -41,12 +41,19 @@ class CollectPlates(api.ContextPlugin): data["family"] = "plate" data["families"] = ["ftrack"] - data["label"] += ( - " ({})".format(os.path.splitext(data["sourcePath"])[1]) - ) - data["subset"] = dict(tag["metadata"])["tag.subset"] data["source"] = data["sourcePath"] + subset = "" + for tag in instance.data["tags"]: + tag_data = dict(tag["metadata"]) + if "tag.subset" in tag_data: + subset = tag_data["tag.subset"] + data["subset"] = subset + + data["label"] += " - {} - ({})".format( + subset, os.path.splitext(data["sourcePath"])[1] + ) + # Timeline data. handle_start = int(instance.data["handleStart"] + data["handles"]) handle_end = int(instance.data["handleEnd"] + data["handles"]) @@ -173,10 +180,14 @@ class CollectPlatesData(api.InstancePlugin): self.log.debug("__ s duration: {}".format(source_out - source_in + 1)) self.log.debug("__ source_in_h: {}".format(source_in_h)) self.log.debug("__ source_out_h: {}".format(source_out_h)) - self.log.debug("__ sh duration: {}".format(source_out_h - source_in_h + 1)) + self.log.debug("__ sh duration: {}".format( + source_out_h - source_in_h + 1) + ) self.log.debug("__ timeline_in: {}".format(timeline_in)) self.log.debug("__ timeline_out: {}".format(timeline_out)) - self.log.debug("__ t duration: {}".format(timeline_out - timeline_in + 1)) + self.log.debug("__ t duration: {}".format( + timeline_out - timeline_in + 1) + ) self.log.debug("__ timeline_frame_start: {}".format( timeline_frame_start)) self.log.debug("__ timeline_frame_end: {}".format(timeline_frame_end)) From fe33f76da493a95879965856f560ae04a690b5e4 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 26 Jun 2019 18:20:54 +0100 Subject: [PATCH 11/44] Create load_image_plane.py --- pype/plugins/maya/load/load_image_plane.py | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 pype/plugins/maya/load/load_image_plane.py diff --git a/pype/plugins/maya/load/load_image_plane.py b/pype/plugins/maya/load/load_image_plane.py new file mode 100644 index 0000000000..1d9008e09f --- /dev/null +++ b/pype/plugins/maya/load/load_image_plane.py @@ -0,0 +1,79 @@ +import pymel.core as pc + +from avalon import api +from Qt import QtWidgets + + +class ImagePlaneLoader(api.Loader): + """Specific loader of plate for image planes on selected camera.""" + + families = ["plate"] + label = "Create imagePlane on selected camera." + representations = ["mov"] + icon = "image" + color = "orange" + + def load(self, context, name, namespace, data): + new_nodes = [] + image_plane_depth = 100 + + # Getting camera from selection. + selection = pc.ls(selection=True) + + if len(selection) > 1: + QtWidgets.QMessageBox.critical( + None, + "Error!", + "Multiple nodes selected. Please select only one.", + QtWidgets.QMessageBox.Ok + ) + return + + if len(selection) < 1: + QtWidgets.QMessageBox.critical( + None, + "Error!", + "No camera selected.", + QtWidgets.QMessageBox.Ok + ) + return + + relatives = pc.listRelatives(selection[0], shapes=True) + if not pc.ls(relatives, type="camera"): + QtWidgets.QMessageBox.critical( + None, + "Error!", + "Selected node is not a camera.", + QtWidgets.QMessageBox.Ok + ) + return + + camera = selection[0] + + camera.displayResolution.set(1) + camera.farClipPlane.set(image_plane_depth * 10) + + # Create image plane + image_plane_transform, image_plane_shape = pc.imagePlane( + camera=camera, showInAllViews=False + ) + image_plane_shape.depth.set(image_plane_depth) + # Need to get "type" by string, because its a method as well. + pc.Attribute(image_plane_shape + ".type").set(2) + image_plane_shape.imageName.set( + context["representation"]["data"]["path"] + ) + image_plane_shape.useFrameExtension.set(1) + + start_frame = pc.playbackOptions(q=True, min=True) + end_frame = pc.playbackOptions(q=True, max=True) + + image_plane_shape.frameOffset.set(1 - start_frame) + image_plane_shape.frameIn.set(start_frame) + image_plane_shape.frameOut.set(end_frame) + + new_nodes.extend( + [image_plane_transform.name(), image_plane_shape.name()] + ) + + return new_nodes From a29a441625b908fae60c8ba206baf41cccb63918 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 27 Jun 2019 13:21:47 +0100 Subject: [PATCH 12/44] Plate collection should be instance plugin to better filter. --- .../nukestudio/publish/collect_plates.py | 136 +++++++++--------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index 41d0f346f8..4aabdbc184 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -3,7 +3,7 @@ import os from pyblish import api -class CollectPlates(api.ContextPlugin): +class CollectPlates(api.InstancePlugin): """Collect plates from tags. Tag is expected to have metadata: @@ -17,84 +17,84 @@ class CollectPlates(api.ContextPlugin): order = api.CollectorOrder + 0.1025 label = "Collect Plates" hosts = ["nukestudio"] + families = ["clip"] - def process(self, context): - for instance in context[:]: - # Exclude non-tagged instances. - tagged = False - for tag in instance.data["tags"]: - family = dict(tag["metadata"]).get("tag.family", "") - if family.lower() == "plate": - tagged = True + def process(self, instance): + # Exclude non-tagged instances. + tagged = False + for tag in instance.data["tags"]: + family = dict(tag["metadata"]).get("tag.family", "") + if family.lower() == "plate": + tagged = True - if not tagged: - self.log.debug( - "Skipping \"{}\" because its not tagged with " - "\"plate\"".format(instance) - ) - continue - - # Collect data. - data = {} - for key, value in instance.data.iteritems(): - data[key] = value - - data["family"] = "plate" - data["families"] = ["ftrack"] - data["source"] = data["sourcePath"] - - subset = "" - for tag in instance.data["tags"]: - tag_data = dict(tag["metadata"]) - if "tag.subset" in tag_data: - subset = tag_data["tag.subset"] - data["subset"] = subset - - data["label"] += " - {} - ({})".format( - subset, os.path.splitext(data["sourcePath"])[1] + if not tagged: + self.log.debug( + "Skipping \"{}\" because its not tagged with " + "\"plate\"".format(instance) ) + return - # Timeline data. - handle_start = int(instance.data["handleStart"] + data["handles"]) - handle_end = int(instance.data["handleEnd"] + data["handles"]) + # Collect data. + data = {} + for key, value in instance.data.iteritems(): + data[key] = value - source_in_h = data["sourceIn"] - handle_start - source_out_h = data["sourceOut"] + handle_end + data["family"] = "plate" + data["families"] = ["ftrack"] + data["source"] = data["sourcePath"] - timeline_in = int(data["item"].timelineIn()) - timeline_out = int(data["item"].timelineOut()) + subset = "" + for tag in instance.data["tags"]: + tag_data = dict(tag["metadata"]) + if "tag.subset" in tag_data: + subset = tag_data["tag.subset"] + data["subset"] = subset - timeline_frame_start = timeline_in - handle_start - timeline_frame_end = timeline_out + handle_end + data["label"] += " - {} - ({})".format( + subset, os.path.splitext(data["sourcePath"])[1] + ) - frame_start = 1 - frame_end = frame_start + (data["sourceOut"] - data["sourceIn"]) + # Timeline data. + handle_start = int(instance.data["handleStart"] + data["handles"]) + handle_end = int(instance.data["handleEnd"] + data["handles"]) - sequence = context.data["activeSequence"] - fps = sequence.framerate() + source_in_h = data["sourceIn"] - handle_start + source_out_h = data["sourceOut"] + handle_end - data.update( - { - "sourceFirst": data["sourceFirst"], - "sourceIn": data["sourceIn"], - "sourceOut": data["sourceOut"], - "sourceInH": source_in_h, - "sourceOutH": source_out_h, - "frameStart": frame_start, - "startFrame": frame_start, - "endFrame": frame_end, - "timelineIn": timeline_in, - "timelineOut": timeline_out, - "timelineInHandles": timeline_frame_start, - "timelineOutHandles": timeline_frame_end, - "fps": fps, - "handleStart": handle_start, - "handleEnd": handle_end - } - ) + timeline_in = int(data["item"].timelineIn()) + timeline_out = int(data["item"].timelineOut()) - self.log.debug("Creating instance with data: {}".format(data)) - context.create_instance(**data) + timeline_frame_start = timeline_in - handle_start + timeline_frame_end = timeline_out + handle_end + + frame_start = 1 + frame_end = frame_start + (data["sourceOut"] - data["sourceIn"]) + + sequence = instance.context.data["activeSequence"] + fps = sequence.framerate() + + data.update( + { + "sourceFirst": data["sourceFirst"], + "sourceIn": data["sourceIn"], + "sourceOut": data["sourceOut"], + "sourceInH": source_in_h, + "sourceOutH": source_out_h, + "frameStart": frame_start, + "startFrame": frame_start, + "endFrame": frame_end, + "timelineIn": timeline_in, + "timelineOut": timeline_out, + "timelineInHandles": timeline_frame_start, + "timelineOutHandles": timeline_frame_end, + "fps": fps, + "handleStart": handle_start, + "handleEnd": handle_end + } + ) + + self.log.debug("Creating instance with data: {}".format(data)) + instance.context.create_instance(**data) class CollectPlatesData(api.InstancePlugin): From 2239e689db5f3225344ee657f010dc58329fc99a Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 27 Jun 2019 13:22:19 +0100 Subject: [PATCH 13/44] Better logging for "Atomicity". --- pype/plugins/global/publish/integrate_new.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index f96fb240c9..43b3560dce 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -98,6 +98,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # \ / # o __/ # + for result in context.data["results"]: + if not result["success"]: + self.log.debug(result) assert all(result["success"] for result in context.data["results"]), ( "Atomicity not held, aborting.") From e61c0a1ff62738a5cf62be4157c61bb049f9197c Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 27 Jun 2019 13:22:39 +0100 Subject: [PATCH 14/44] Initial working review tagging. --- .../nukestudio/publish/collect_reviews.py | 53 +++++++++++ .../nukestudio/publish/extract_review.py | 87 +++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 pype/plugins/nukestudio/publish/collect_reviews.py create mode 100644 pype/plugins/nukestudio/publish/extract_review.py diff --git a/pype/plugins/nukestudio/publish/collect_reviews.py b/pype/plugins/nukestudio/publish/collect_reviews.py new file mode 100644 index 0000000000..99826aca0d --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_reviews.py @@ -0,0 +1,53 @@ +from pyblish import api + + +class CollectReviews(api.InstancePlugin): + """Collect review from tags. + + Tag is expected to have metadata: + { + "family": "review" + "subset": "main" + } + """ + + # Run just before CollectSubsets + order = api.CollectorOrder + 0.1025 + label = "Collect Reviews" + hosts = ["nukestudio"] + families = ["clip"] + + def process(self, instance): + # Exclude non-tagged instances. + tagged = False + for tag in instance.data["tags"]: + family = dict(tag["metadata"]).get("tag.family", "") + if family.lower() == "review": + tagged = True + + if not tagged: + self.log.debug( + "Skipping \"{}\" because its not tagged with " + "\"review\"".format(instance) + ) + return + + # Collect data. + data = {} + for key, value in instance.data.iteritems(): + data[key] = value + + data["family"] = "review" + data["families"] = ["ftrack"] + + subset = "" + for tag in instance.data["tags"]: + tag_data = dict(tag["metadata"]) + if "tag.subset" in tag_data: + subset = tag_data["tag.subset"] + data["subset"] = subset + + data["source"] = data["sourcePath"] + + self.log.debug("Creating instance with data: {}".format(data)) + instance.context.create_instance(**data) diff --git a/pype/plugins/nukestudio/publish/extract_review.py b/pype/plugins/nukestudio/publish/extract_review.py new file mode 100644 index 0000000000..ef364d7968 --- /dev/null +++ b/pype/plugins/nukestudio/publish/extract_review.py @@ -0,0 +1,87 @@ +import os + +import pype.api + +from pype.vendor import ffmpeg + + +class ExtractQuicktime(pype.api.Extractor): + """Extract Quicktime with optimized codec for reviewing.""" + + label = "Review" + hosts = ["nukestudio"] + families = ["review"] + optional = True + + def process(self, instance): + staging_dir = self.staging_dir(instance) + filename = "{0}".format(instance.name) + ".mov" + output_path = os.path.join(staging_dir, filename) + input_path = instance.data["sourcePath"] + + self.log.info("Outputting movie to %s" % output_path) + + # Has to be yuv420p for compatibility with older players and smooth + # playback. This does come with a sacrifice of more visible banding + # issues. + output_options = { + "pix_fmt": "yuv420p", + "crf": "18", + "timecode": "00:00:00:01", + "vf": "scale=trunc(iw/2)*2:trunc(ih/2)*2" + } + + try: + ( + ffmpeg + .input(input_path) + .output(output_path, **output_options) + .run(overwrite_output=True, + capture_stdout=True, + capture_stderr=True) + ) + except ffmpeg.Error as e: + ffmpeg_error = "ffmpeg error: {}".format(e.stderr) + self.log.error(ffmpeg_error) + raise RuntimeError(ffmpeg_error) + + # Adding movie representation. + start_frame = int( + instance.data["sourceIn"] - ( + instance.data["handleStart"] + instance.data["handles"] + ) + ) + end_frame = int( + instance.data["sourceOut"] + ( + instance.data["handleEnd"] + instance.data["handles"] + ) + ) + representation = { + "files": os.path.basename(output_path), + "staging_dir": staging_dir, + "startFrame": 0, + "endFrame": end_frame - start_frame, + "step": 1, + "frameRate": ( + instance.context.data["activeSequence"].framerate().toFloat() + ), + "preview": True, + "thumbnail": False, + "name": "preview", + "ext": "mov", + } + instance.data["representations"] = [representation] + self.log.debug("Adding representation: {}".format(representation)) + + # Adding thumbnail representation. + representation = { + "files": os.path.basename( + instance.data["sourcePath"].replace(".mov", ".png") + ), + "stagingDir": os.path.dirname(instance.data["sourcePath"]), + "name": "thumbnail", + "thumbnail": True, + "ext": "png" + } + instance.data["representations"].append(representation) + self.log.debug("Adding representation: {}".format(representation)) From ef2d8dc91aa3e7333d20c7e96ca5265cd5645a7b Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 27 Jun 2019 16:50:14 +0100 Subject: [PATCH 15/44] Tasks included in shot label. --- pype/plugins/nukestudio/publish/collect_shot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/plugins/nukestudio/publish/collect_shot.py b/pype/plugins/nukestudio/publish/collect_shot.py index 6188508ecd..35aa926541 100644 --- a/pype/plugins/nukestudio/publish/collect_shot.py +++ b/pype/plugins/nukestudio/publish/collect_shot.py @@ -34,6 +34,8 @@ class CollectShot(api.InstancePlugin): data["families"] = [] data["frameStart"] = 1 + data["label"] += " - tasks: {}".format(data["tasks"]) + # Get handles. data["handleStart"] = instance.data["handleStart"] + data["handles"] data["handleEnd"] = instance.data["handleEnd"] + data["handles"] From 02758aae1e2df59cd3566b43be4fb9729fe78d92 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 27 Jun 2019 17:08:09 +0100 Subject: [PATCH 16/44] Enable override of ftrack family mapping. --- pype/plugins/ftrack/publish/integrate_ftrack_instances.py | 5 +++-- pype/plugins/nukestudio/publish/collect_reviews.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py index d351289dfe..dfee6bdaa4 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py @@ -1,5 +1,4 @@ import pyblish.api -import os import json @@ -38,7 +37,9 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): family = instance.data['family'].lower() asset_type = '' - asset_type = self.family_mapping[family] + asset_type = instance.data.get( + "ftrackFamily", self.family_mapping[family] + ) componentList = [] ft_session = instance.context.data["ftrackSession"] diff --git a/pype/plugins/nukestudio/publish/collect_reviews.py b/pype/plugins/nukestudio/publish/collect_reviews.py index 99826aca0d..33f7221bff 100644 --- a/pype/plugins/nukestudio/publish/collect_reviews.py +++ b/pype/plugins/nukestudio/publish/collect_reviews.py @@ -38,6 +38,7 @@ class CollectReviews(api.InstancePlugin): data[key] = value data["family"] = "review" + data["ftrackFamily"] = "img" data["families"] = ["ftrack"] subset = "" From 37156005948ead516db0178817c8cc7303fd9da9 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 27 Jun 2019 17:59:03 +0100 Subject: [PATCH 17/44] Audio - extract/integrate from NS to Avalon and Ftrack - load into Maya --- .../publish/integrate_ftrack_instances.py | 3 +- pype/plugins/global/publish/integrate_new.py | 3 +- pype/plugins/maya/load/load_audio.py | 22 ++++++++ .../nukestudio/publish/collect_audio.py | 53 +++++++++++++++++++ .../nukestudio/publish/extract_audio.py | 5 +- 5 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 pype/plugins/maya/load/load_audio.py create mode 100644 pype/plugins/nukestudio/publish/collect_audio.py diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py index dfee6bdaa4..f3a348468a 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py @@ -25,7 +25,8 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): 'nukescript': 'comp', 'write': 'render', 'review': 'mov', - 'plate': 'img' + 'plate': 'img', + 'audio': 'audio' } def process(self, instance): diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 43b3560dce..f4d653e97d 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -62,7 +62,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "rendersetup", "rig", "plate", - "look" + "look", + "audio" ] exclude_families = ["clip"] diff --git a/pype/plugins/maya/load/load_audio.py b/pype/plugins/maya/load/load_audio.py new file mode 100644 index 0000000000..61fe654b30 --- /dev/null +++ b/pype/plugins/maya/load/load_audio.py @@ -0,0 +1,22 @@ +from maya import cmds, mel + +from avalon import api + + +class AudioLoader(api.Loader): + """Specific loader of audio.""" + + families = ["audio"] + label = "Import audio." + representations = ["wav"] + icon = "volume-up" + color = "orange" + + def load(self, context, name, namespace, data): + start_frame = cmds.playbackOptions(query=True, min=True) + sound_node = cmds.sound( + file=context["representation"]["data"]["path"], offset=start_frame + ) + mel.eval("setSoundDisplay {} 1".format(sound_node)) + + return [sound_node] diff --git a/pype/plugins/nukestudio/publish/collect_audio.py b/pype/plugins/nukestudio/publish/collect_audio.py new file mode 100644 index 0000000000..1b677d6410 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_audio.py @@ -0,0 +1,53 @@ +from pyblish import api + + +class CollectAudio(api.InstancePlugin): + """Collect audio from tags. + + Tag is expected to have metadata: + { + "family": "audio", + "subset": "main" + } + """ + + # Run just before CollectSubsets + order = api.CollectorOrder + 0.1025 + label = "Collect Audio" + hosts = ["nukestudio"] + families = ["clip"] + + def process(self, instance): + # Exclude non-tagged instances. + tagged = False + for tag in instance.data["tags"]: + family = dict(tag["metadata"]).get("tag.family", "") + if family.lower() == "audio": + tagged = True + + if not tagged: + self.log.debug( + "Skipping \"{}\" because its not tagged with " + "\"audio\"".format(instance) + ) + return + + # Collect data. + data = {} + for key, value in instance.data.iteritems(): + data[key] = value + + data["family"] = "audio" + data["families"] = ["ftrack"] + + subset = "" + for tag in instance.data["tags"]: + tag_data = dict(tag["metadata"]) + if "tag.subset" in tag_data: + subset = tag_data["tag.subset"] + data["subset"] = subset + + data["source"] = data["sourcePath"] + + self.log.debug("Creating instance with data: {}".format(data)) + instance.context.create_instance(**data) diff --git a/pype/plugins/nukestudio/publish/extract_audio.py b/pype/plugins/nukestudio/publish/extract_audio.py index 17ef882690..c16f123353 100644 --- a/pype/plugins/nukestudio/publish/extract_audio.py +++ b/pype/plugins/nukestudio/publish/extract_audio.py @@ -1,6 +1,7 @@ from pyblish import api import pype + class ExtractAudioFile(pype.api.Extractor): """Extracts audio subset file""" @@ -53,10 +54,10 @@ class ExtractAudioFile(pype.api.Extractor): instance.data["representations"] = list() representation = { - 'files': [audio_file], + 'files': os.path.basename(audio_file), 'stagingDir': staging_dir, 'name': "wav", - 'ext': ".wav" + 'ext': "wav" } instance.data["representations"].append(representation) From 7a9d28804428f5cfc5ac4099aa920ff8b2e4922c Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 27 Jun 2019 18:54:14 +0100 Subject: [PATCH 18/44] Support collecting all items when nothing is selected. --- .../nukestudio/publish/collect_selection.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_selection.py b/pype/plugins/nukestudio/publish/collect_selection.py index e22ea79a05..e87f9d03ec 100644 --- a/pype/plugins/nukestudio/publish/collect_selection.py +++ b/pype/plugins/nukestudio/publish/collect_selection.py @@ -2,6 +2,7 @@ import pyblish.api import hiero + class CollectSelection(pyblish.api.ContextPlugin): """Inject the selection in the context.""" @@ -9,7 +10,16 @@ class CollectSelection(pyblish.api.ContextPlugin): label = "Selection" def process(self, context): - selection = getattr(hiero, "selection") + selection = list(hiero.selection) self.log.debug("selection: {}".format(selection)) - context.data["selection"] = hiero.selection + + if not selection: + self.log.debug( + "Nothing is selected. Collecting all items from sequence " + "\"{}\"".format(hiero.ui.activeSequence()) + ) + for track in hiero.ui.activeSequence().items(): + selection.extend(track.items()) + + context.data["selection"] = selection From 3ba41c8d18cbc2465afd62a9611e9ff9945142c7 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 27 Jun 2019 19:33:44 +0100 Subject: [PATCH 19/44] Add native icons. --- pype/nukestudio/tags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/nukestudio/tags.py b/pype/nukestudio/tags.py index 04a99d2cb8..7fec8ae33a 100644 --- a/pype/nukestudio/tags.py +++ b/pype/nukestudio/tags.py @@ -70,7 +70,7 @@ def add_tags_from_presets(): "editable": "1", "note": "", "icon": { - "path": "" + "path": "icons:TagGood.png" }, "metadata": { "family": "task" @@ -86,7 +86,7 @@ def add_tags_from_presets(): "editable": "1", "note": "", "icon": { - "path": "" + "path": "icons:TagActor.png" }, "metadata": { "family": "asset" From 710dac2b5d1e341dcc7c0582f0eb467667898f83 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 28 Jun 2019 10:30:06 +0100 Subject: [PATCH 20/44] Reset the frame range on new scene in Maya. --- pype/maya/lib.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/maya/lib.py b/pype/maya/lib.py index 74e2359d38..fce1772b8e 100644 --- a/pype/maya/lib.py +++ b/pype/maya/lib.py @@ -16,6 +16,7 @@ import maya.api.OpenMaya as om from avalon import api, maya, io, pipeline from avalon.vendor.six import string_types import avalon.maya.lib +import avalon.maya.interactive from pype import lib @@ -1873,6 +1874,9 @@ def set_context_settings(): set_scene_resolution(width, height) + # Set frame range. + avalon.maya.interactive.reset_frame_range() + # Valid FPS def validate_fps(): From 65a6152abe96cd57be4ab2cf0803a13d55ae4536 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 28 Jun 2019 10:30:57 +0100 Subject: [PATCH 21/44] Change start and end frame to timeline in and out. --- .../nukestudio/publish/collect_hierarchy_context.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py index 9a82967311..2e5f8f473b 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -184,10 +184,12 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): source_in = int(instance.data["sourceIn"]) source_out = int(instance.data["sourceOut"]) - instance.data['startFrame'] = int( - source_first + source_in - handle_start) - instance.data['endFrame'] = int( - (source_first + source_out + handle_end)) + instance.data['startFrame'] = ( + instance.data["item"].timelineIn() - handle_start + ) + instance.data['endFrame'] = ( + instance.data["item"].timelineOut() + handle_end + ) # inject assetsShared to other plates types assets_shared = context.data.get("assetsShared") From 7b67805c2da4af43f82635206ed1637f88ba338e Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 28 Jun 2019 10:31:38 +0100 Subject: [PATCH 22/44] Add custom attributes "edit_in" and "edit_out" for Avalon frame range. --- .../nukestudio/publish/collect_hierarchy_context.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py index 2e5f8f473b..b7fca34397 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -213,11 +213,11 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): # get custom attributes of the shot in_info['custom_attributes'] = { 'handles': int(instance.data.get('handles')), - 'fend': int( - (source_first + source_out)), - 'fstart': int( - source_first + source_in), - 'fps': context.data["framerate"] + 'fstart': int(instance.data["startFrame"]), + 'fend': int(instance.data["endFrame"]), + 'fps': context.data["framerate"], + "edit_in": int(instance.data["startFrame"]), + "edit_out": int(instance.data["endFrame"]) } handle_start = instance.data.get('handleStart') From 48fc480611c792efab286f0482763986b235befc Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 28 Jun 2019 10:31:56 +0100 Subject: [PATCH 23/44] Code cosmetics. Line length. --- .../publish/collect_hierarchy_context.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py index b7fca34397..459a8ed002 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -202,9 +202,19 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): instance.data["parents"] = s_asset_data["parents"] instance.data["hierarchy"] = s_asset_data["hierarchy"] - self.log.debug("__ instance.data[parents]: {}".format(instance.data["parents"])) - self.log.debug("__ instance.data[hierarchy]: {}".format(instance.data["hierarchy"])) - self.log.debug("__ instance.data[name]: {}".format(instance.data["name"])) + self.log.debug( + "__ instance.data[parents]: {}".format( + instance.data["parents"] + ) + ) + self.log.debug( + "__ instance.data[hierarchy]: {}".format( + instance.data["hierarchy"] + ) + ) + self.log.debug( + "__ instance.data[name]: {}".format(instance.data["name"]) + ) in_info = {} # suppose that all instances are Shots From fe60155d96168c1e9685255deb8707a595c48a49 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 28 Jun 2019 13:29:49 +0100 Subject: [PATCH 24/44] Tagging assetbuilds. --- pype/nukestudio/tags.py | 6 +- .../publish/integrate_hierarchy_ftrack.py | 34 ++++++++++- .../publish/integrate_hierarchy_avalon.py | 8 ++- .../nukestudio/publish/collect_assets.py | 59 +++++++++++++++++++ .../nukestudio/publish/collect_handles.py | 11 +++- .../publish/collect_hierarchy_context.py | 25 +++++--- .../nukestudio/publish/collect_shot.py | 4 +- 7 files changed, 128 insertions(+), 19 deletions(-) create mode 100644 pype/plugins/nukestudio/publish/collect_assets.py diff --git a/pype/nukestudio/tags.py b/pype/nukestudio/tags.py index 7fec8ae33a..d10fa60ba4 100644 --- a/pype/nukestudio/tags.py +++ b/pype/nukestudio/tags.py @@ -79,17 +79,17 @@ def add_tags_from_presets(): # Get project assets. Currently Ftrack specific to differentiate between # asset builds and shots. - nks_pres_tags["[Assets]"] = {} + nks_pres_tags["[AssetBuilds]"] = {} for asset in io.find({"type": "asset"}): if asset["data"]["entityType"] == "AssetBuild": - nks_pres_tags["[Assets]"][asset["name"]] = { + nks_pres_tags["[AssetBuilds]"][asset["name"]] = { "editable": "1", "note": "", "icon": { "path": "icons:TagActor.png" }, "metadata": { - "family": "asset" + "family": "assetbuild" } } diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index 69788756cf..5f0516c593 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -1,4 +1,5 @@ import pyblish.api +from avalon import io class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): @@ -35,6 +36,9 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): if "hierarchyContext" not in context.data: return + if not io.Session: + io.install() + self.ft_project = None self.session = context.data["ftrackSession"] @@ -81,10 +85,13 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): # CUSTOM ATTRIBUTES custom_attributes = entity_data.get('custom_attributes', []) instances = [ - i for i in self.context[:] if i.data['asset'] in entity['name']] + i for i in self.context[:] if i.data['asset'] in entity['name'] + ] for key in custom_attributes: assert (key in entity['custom_attributes']), ( - 'Missing custom attribute key: `{0}` in attrs: `{1}`'.format(key, entity['custom_attributes'].keys())) + 'Missing custom attribute key: `{0}` in attrs: ' + '`{1}`'.format(key, entity['custom_attributes'].keys()) + ) entity['custom_attributes'][key] = custom_attributes[key] @@ -116,10 +123,33 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): ) self.session.commit() + # Incoming links. + self.create_links(entity_data, entity) + self.session.commit() + if 'childs' in entity_data: self.import_to_ftrack( entity_data['childs'], entity) + def create_links(self, entity_data, entity): + # Clear existing links. + for link in entity.get("incoming_links", []): + self.session.delete(link) + self.session.commit() + + # Create new links. + for input in entity_data.get("inputs", []): + input_id = io.find_one({"_id": input})["data"]["ftrackId"] + assetbuild = self.session.get("AssetBuild", input_id) + self.log.debug( + "Creating link from {0} to {1}".format( + assetbuild["name"], entity["name"] + ) + ) + self.session.create( + "TypedContextLink", {"from": assetbuild, "to": entity} + ) + def get_all_task_types(self, project): tasks = {} proj_template = project['project_schema'] diff --git a/pype/plugins/global/publish/integrate_hierarchy_avalon.py b/pype/plugins/global/publish/integrate_hierarchy_avalon.py index 66a3825a1d..1962bda132 100644 --- a/pype/plugins/global/publish/integrate_hierarchy_avalon.py +++ b/pype/plugins/global/publish/integrate_hierarchy_avalon.py @@ -25,13 +25,15 @@ class IntegrateHierarchyToAvalon(pyblish.api.ContextPlugin): self.import_to_avalon(input_data) def import_to_avalon(self, input_data, parent=None): - for name in input_data: self.log.info('input_data[name]: {}'.format(input_data[name])) entity_data = input_data[name] entity_type = entity_data['entity_type'] data = {} + + data["inputs"] = entity_data.get("inputs", []) + # Process project if entity_type.lower() == 'project': entity = self.db.find_one({'type': 'project'}) @@ -73,7 +75,9 @@ class IntegrateHierarchyToAvalon(pyblish.api.ContextPlugin): data = input_data[name] if self.av_project['_id'] != parent['_id']: visualParent = parent['_id'] - parents.extend(parent.get('data', {}).get('parents', [])) + parents.extend( + parent.get('data', {}).get('parents', []) + ) parents.append(parent['name']) data['visualParent'] = visualParent data['parents'] = parents diff --git a/pype/plugins/nukestudio/publish/collect_assets.py b/pype/plugins/nukestudio/publish/collect_assets.py new file mode 100644 index 0000000000..b1db1b1bf2 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_assets.py @@ -0,0 +1,59 @@ +from pyblish import api +from avalon import io + + +class CollectAssetBuilds(api.InstancePlugin): + """Collect asset from tags. + + Tag is expected to have name of the asset and metadata: + { + "family": "asset" + } + """ + + # Run just before CollectShot + order = api.CollectorOrder + 0.102 + label = "Collect AssetBuilds" + hosts = ["nukestudio"] + families = ["clip"] + + def process(self, instance): + # Exclude non-tagged instances. + tagged = False + asset_names = [] + for tag in instance.data["tags"]: + family = dict(tag["metadata"]).get("tag.family", "") + if family.lower() == "assetbuild": + asset_names.append(tag["name"]) + tagged = True + + if not tagged: + self.log.debug( + "Skipping \"{}\" because its not tagged with " + "\"assetbuild\"".format(instance) + ) + return + + # Collect asset builds. + data = {"assetBuilds": []} + for name in asset_names: + data["assetBuilds"].append( + instance.context.data["assetBuilds"][name] + ) + self.log.debug("Found asset builds: {}".format(data["assetBuilds"])) + + instance.data.update(data) + + +class CollectExistingAssetBuilds(api.ContextPlugin): + """Collect all asset builds from database.""" + + order = CollectAssetBuilds.order - 0.1 + label = "Collect Existing AssetBuilds" + hosts = ["nukestudio"] + + def process(self, context): + context.data["assetBuilds"] = {} + for asset in io.find({"type": "asset"}): + if asset["data"]["entityType"] == "AssetBuild": + context.data["assetBuilds"][asset["name"]] = asset diff --git a/pype/plugins/nukestudio/publish/collect_handles.py b/pype/plugins/nukestudio/publish/collect_handles.py index 2f2831237f..0aa339d039 100644 --- a/pype/plugins/nukestudio/publish/collect_handles.py +++ b/pype/plugins/nukestudio/publish/collect_handles.py @@ -8,7 +8,6 @@ class CollectClipHandles(api.ContextPlugin): order = api.CollectorOrder + 0.1025 label = "Collect Handles" hosts = ["nukestudio"] - families = ['clip'] def process(self, context): assets_shared = context.data.get("assetsShared") @@ -16,7 +15,15 @@ class CollectClipHandles(api.ContextPlugin): # find all main types instances and add its handles to asset shared instances = context[:] + filtered_instances = [] for instance in instances: + families = instance.data.get("families", []) + families += [instance.data["family"]] + if "clip" in families: + filtered_instances.append(instance) + else: + continue + # get handles handles = int(instance.data["handles"]) handle_start = int(instance.data["handleStart"]) @@ -33,7 +40,7 @@ class CollectClipHandles(api.ContextPlugin): "handleEnd": handle_end }) - for instance in instances: + for instance in filtered_instances: if not instance.data.get("main"): self.log.debug("Synchronize handles on: `{}`".format( instance.data["name"])) diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py index 459a8ed002..ba9fe118da 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -43,7 +43,9 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): # build data for inner nukestudio project property data = { - "sequence": context.data['activeSequence'].name().replace(' ', '_'), + "sequence": ( + context.data['activeSequence'].name().replace(' ', '_') + ), "track": clip.parent().name().replace(' ', '_'), "clip": asset } @@ -110,7 +112,10 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): # create new shot asset name instance.data["asset"] = instance.data["asset"].format( **d_metadata) - self.log.debug("__ instance.data[asset]: {}".format(instance.data["asset"])) + self.log.debug( + "__ instance.data[asset]: " + "{}".format(instance.data["asset"]) + ) # lastly fill those individual properties itno # format the string with collected data @@ -126,8 +131,10 @@ class CollectHierarchyInstance(pyblish.api.InstancePlugin): # check if hierarchy attribute is already created # it should not be so return warning if it is hd = instance.data.get("hierarchy") - assert not hd, "Only one Hierarchy Tag is \ - allowed. Clip: `{}`".format(asset) + assert not hd, ( + "Only one Hierarchy Tag is allowed. " + "Clip: `{}`".format(asset) + ) assetsShared = { asset: { @@ -179,11 +186,6 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): handle_start = int(instance.data["handleStart"] + handles) handle_end = int(instance.data["handleEnd"] + handles) - # get source frames - source_first = int(instance.data["sourceFirst"]) - source_in = int(instance.data["sourceIn"]) - source_out = int(instance.data["sourceOut"]) - instance.data['startFrame'] = ( instance.data["item"].timelineIn() - handle_start ) @@ -217,6 +219,11 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): ) in_info = {} + + in_info["inputs"] = [ + x["_id"] for x in instance.data["assetBuilds"] + ] + # suppose that all instances are Shots in_info['entity_type'] = 'Shot' diff --git a/pype/plugins/nukestudio/publish/collect_shot.py b/pype/plugins/nukestudio/publish/collect_shot.py index 35aa926541..33e6a7ee7e 100644 --- a/pype/plugins/nukestudio/publish/collect_shot.py +++ b/pype/plugins/nukestudio/publish/collect_shot.py @@ -34,7 +34,9 @@ class CollectShot(api.InstancePlugin): data["families"] = [] data["frameStart"] = 1 - data["label"] += " - tasks: {}".format(data["tasks"]) + data["label"] += " - tasks: {} - assetbuilds: {}".format( + data["tasks"], [x["name"] for x in data["assetBuilds"]] + ) # Get handles. data["handleStart"] = instance.data["handleStart"] + data["handles"] From 0470fea6d84af4944ae1715b77815ad9a78dad20 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 1 Jul 2019 18:26:27 +0100 Subject: [PATCH 25/44] When creating new assets syncing from Ftrack would overwrite data in Avalon. Since the inputs aren't a custom attribute, the "inputs" would be overwritten on creation. --- pype/ftrack/lib/avalon_sync.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index 030b0b5b6c..0a0dfae571 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -293,12 +293,18 @@ def import_to_avalon( output['errors'] = errors return output + # Update existing data rather than overwriting. + asset_data = database[project_name].find_one( + {"_id": ObjectId(mongo_id)} + )["data"] + asset_data.update(data) + database[project_name].update_many( {'_id': ObjectId(mongo_id)}, {'$set': { 'name': name, 'silo': silo, - 'data': data, + 'data': asset_data, 'parent': ObjectId(projectId) }}) From e9ee9463f12e1905f793169c6754bd6a32261c4a Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 1 Jul 2019 18:26:55 +0100 Subject: [PATCH 26/44] Clean up and simplify Avalon hierarchy integrator. --- .../publish/integrate_hierarchy_avalon.py | 154 +++++++----------- 1 file changed, 55 insertions(+), 99 deletions(-) diff --git a/pype/plugins/global/publish/integrate_hierarchy_avalon.py b/pype/plugins/global/publish/integrate_hierarchy_avalon.py index 1962bda132..c01cb2d26a 100644 --- a/pype/plugins/global/publish/integrate_hierarchy_avalon.py +++ b/pype/plugins/global/publish/integrate_hierarchy_avalon.py @@ -3,143 +3,99 @@ from avalon import io class IntegrateHierarchyToAvalon(pyblish.api.ContextPlugin): - """ - Create entities in ftrack based on collected data from premiere - - """ + """Create entities in Avalon based on collected data.""" order = pyblish.api.IntegratorOrder - 0.1 - label = 'Integrate Hierarchy To Avalon' - families = ['clip', "shot"] + label = "Integrate Hierarchy To Avalon" + families = ["clip", "shot"] def process(self, context): if "hierarchyContext" not in context.data: - self.log.info('skipping IntegrateHierarchyToAvalon') + self.log.info("skipping IntegrateHierarchyToAvalon") return - self.db = io - if not self.db.Session: - self.db.install() + if not io.Session: + io.install() input_data = context.data["hierarchyContext"] + self.project = None self.import_to_avalon(input_data) def import_to_avalon(self, input_data, parent=None): for name in input_data: - self.log.info('input_data[name]: {}'.format(input_data[name])) + self.log.info("input_data[name]: {}".format(input_data[name])) entity_data = input_data[name] - entity_type = entity_data['entity_type'] + entity_type = entity_data["entity_type"] data = {} data["inputs"] = entity_data.get("inputs", []) + data["entityType"] = entity_type + + # Custom attributes. + for k, val in entity_data.get("custom_attributes", {}).items(): + data[k] = val + + # Tasks. + tasks = entity_data.get("tasks", []) + if tasks is not None or len(tasks) > 0: + data["tasks"] = tasks + parents = [] + visualParent = None + # do not store project"s id as visualParent (silo asset) + if self.project is not None: + if self.project["_id"] != parent["_id"]: + visualParent = parent["_id"] + parents.extend(parent.get("data", {}).get("parents", [])) + parents.append(parent["name"]) + data["visualParent"] = visualParent + data["parents"] = parents # Process project - if entity_type.lower() == 'project': - entity = self.db.find_one({'type': 'project'}) + if entity_type.lower() == "project": + entity = io.find_one({"type": "project"}) # TODO: should be in validator? - assert (entity is not None), "Didn't find project in DB" + assert (entity is not None), "Did not find project in DB" # get data from already existing project - for key, value in entity.get('data', {}).items(): + for key, value in entity.get("data", {}).items(): data[key] = value - self.av_project = entity + self.project = entity # Raise error if project or parent are not set - elif self.av_project is None or parent is None: + elif self.project is None or parent is None: raise AssertionError( "Collected items are not in right order!" ) # Else process assset else: - entity = self.db.find_one({'type': 'asset', 'name': name}) - # Create entity if doesn't exist + entity = io.find_one({"type": "asset", "name": name}) + # Create entity if doesn"t exist if entity is None: - if self.av_project['_id'] == parent['_id']: + if self.project["_id"] == parent["_id"]: silo = None - elif parent['silo'] is None: - silo = parent['name'] + elif parent["silo"] is None: + silo = parent["name"] else: - silo = parent['silo'] - entity = self.create_avalon_asset(name, silo) - self.log.info('entity: {}'.format(entity)) - self.log.info('data: {}'.format(entity.get('data', {}))) - self.log.info('____1____') - data['entityType'] = entity_type - # TASKS - tasks = entity_data.get('tasks', []) - if tasks is not None or len(tasks) > 0: - data['tasks'] = tasks - parents = [] - visualParent = None - data = input_data[name] - if self.av_project['_id'] != parent['_id']: - visualParent = parent['_id'] - parents.extend( - parent.get('data', {}).get('parents', []) - ) - parents.append(parent['name']) - data['visualParent'] = visualParent - data['parents'] = parents - - self.db.update_many( - {'_id': entity['_id']}, - {'$set': { - 'data': data, - }}) - - entity = self.db.find_one({'type': 'asset', 'name': name}) - self.log.info('entity: {}'.format(entity)) - self.log.info('data: {}'.format(entity.get('data', {}))) - self.log.info('____2____') - - # Else get data from already existing - else: - self.log.info('entity: {}'.format(entity)) - self.log.info('data: {}'.format(entity.get('data', {}))) - self.log.info('________') - for key, value in entity.get('data', {}).items(): - data[key] = value - - data['entityType'] = entity_type - # TASKS - tasks = entity_data.get('tasks', []) - if tasks is not None or len(tasks) > 0: - data['tasks'] = tasks - parents = [] - visualParent = None - # do not store project's id as visualParent (silo asset) - - if self.av_project['_id'] != parent['_id']: - visualParent = parent['_id'] - parents.extend(parent.get('data', {}).get('parents', [])) - parents.append(parent['name']) - data['visualParent'] = visualParent - data['parents'] = parents - - # CUSTOM ATTRIBUTES - for k, val in entity_data.get('custom_attributes', {}).items(): - data[k] = val + silo = parent["silo"] + entity = self.create_avalon_asset(name, silo, data) # Update entity data with input data - self.db.update_many( - {'_id': entity['_id']}, - {'$set': { - 'data': data, - }}) + io.update_many({"_id": entity["_id"]}, {"$set": {"data": data}}) - if 'childs' in entity_data: - self.import_to_avalon(entity_data['childs'], entity) + if "childs" in entity_data: + self.import_to_avalon(entity_data["childs"], entity) - def create_avalon_asset(self, name, silo): + def create_avalon_asset(self, name, silo, data): item = { - 'schema': 'avalon-core:asset-2.0', - 'name': name, - 'silo': silo, - 'parent': self.av_project['_id'], - 'type': 'asset', - 'data': {} + "schema": "avalon-core:asset-2.0", + "name": name, + "silo": silo, + "parent": self.project["_id"], + "type": "asset", + "data": data } - entity_id = self.db.insert_one(item).inserted_id + self.log.debug("Creating asset: {}".format(item)) + entity_id = io.insert_one(item).inserted_id - return self.db.find_one({'_id': entity_id}) + return io.find_one({"_id": entity_id}) From bb828cb82861a72d9b54dabcc3c6cfc6d5271fdd Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 1 Jul 2019 18:27:31 +0100 Subject: [PATCH 27/44] setSoundDisplay was not loaded, so better to timeControl --- pype/plugins/maya/load/load_audio.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pype/plugins/maya/load/load_audio.py b/pype/plugins/maya/load/load_audio.py index 61fe654b30..e1860d0ca6 100644 --- a/pype/plugins/maya/load/load_audio.py +++ b/pype/plugins/maya/load/load_audio.py @@ -17,6 +17,11 @@ class AudioLoader(api.Loader): sound_node = cmds.sound( file=context["representation"]["data"]["path"], offset=start_frame ) - mel.eval("setSoundDisplay {} 1".format(sound_node)) + cmds.timeControl( + mel.eval("$tmpVar=$gPlayBackSlider"), + edit=True, + sound=sound_node, + displaySound=True + ) return [sound_node] From 01f46db7b5e9d650cc953f16eb2ce8d5647c2122 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 1 Jul 2019 18:27:59 +0100 Subject: [PATCH 28/44] Putting image depth further away from camera. --- pype/plugins/maya/load/load_image_plane.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/maya/load/load_image_plane.py b/pype/plugins/maya/load/load_image_plane.py index 1d9008e09f..5534cce0ee 100644 --- a/pype/plugins/maya/load/load_image_plane.py +++ b/pype/plugins/maya/load/load_image_plane.py @@ -15,7 +15,7 @@ class ImagePlaneLoader(api.Loader): def load(self, context, name, namespace, data): new_nodes = [] - image_plane_depth = 100 + image_plane_depth = 1000 # Getting camera from selection. selection = pc.ls(selection=True) From 7ebadfe2f4eceb94d33350811b2f3a25f2204c16 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 1 Jul 2019 18:28:49 +0100 Subject: [PATCH 29/44] Rename/restructure assetbuilds collector. --- .../nukestudio/publish/collect_assetbuilds.py | 56 ++++++++++++++++++ .../nukestudio/publish/collect_assets.py | 59 ------------------- 2 files changed, 56 insertions(+), 59 deletions(-) create mode 100644 pype/plugins/nukestudio/publish/collect_assetbuilds.py delete mode 100644 pype/plugins/nukestudio/publish/collect_assets.py diff --git a/pype/plugins/nukestudio/publish/collect_assetbuilds.py b/pype/plugins/nukestudio/publish/collect_assetbuilds.py new file mode 100644 index 0000000000..76326c320b --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_assetbuilds.py @@ -0,0 +1,56 @@ +from pyblish import api +from avalon import io + + +class CollectAssetBuilds(api.ContextPlugin): + """Collect asset from tags. + + Tag is expected to have name of the asset and metadata: + { + "family": "assetbuild" + } + """ + + # Run just after CollectClip + order = api.CollectorOrder + 0.02 + label = "Collect AssetBuilds" + hosts = ["nukestudio"] + + def process(self, context): + asset_builds = {} + for asset in io.find({"type": "asset"}): + if asset["data"]["entityType"] == "AssetBuild": + self.log.debug("Found \"{}\" in database.".format(asset)) + asset_builds[asset["name"]] = asset + + for instance in context: + if instance.data["family"] != "clip": + continue + + # Exclude non-tagged instances. + tagged = False + asset_names = [] + for tag in instance.data["tags"]: + family = dict(tag["metadata"]).get("tag.family", "") + if family.lower() == "assetbuild": + asset_names.append(tag["name"]) + tagged = True + + if not tagged: + self.log.debug( + "Skipping \"{}\" because its not tagged with " + "\"assetbuild\"".format(instance) + ) + continue + + # Collect asset builds. + data = {"assetbuilds": []} + for name in asset_names: + data["assetbuilds"].append( + asset_builds[name] + ) + self.log.debug( + "Found asset builds: {}".format(data["assetbuilds"]) + ) + + instance.data.update(data) diff --git a/pype/plugins/nukestudio/publish/collect_assets.py b/pype/plugins/nukestudio/publish/collect_assets.py deleted file mode 100644 index b1db1b1bf2..0000000000 --- a/pype/plugins/nukestudio/publish/collect_assets.py +++ /dev/null @@ -1,59 +0,0 @@ -from pyblish import api -from avalon import io - - -class CollectAssetBuilds(api.InstancePlugin): - """Collect asset from tags. - - Tag is expected to have name of the asset and metadata: - { - "family": "asset" - } - """ - - # Run just before CollectShot - order = api.CollectorOrder + 0.102 - label = "Collect AssetBuilds" - hosts = ["nukestudio"] - families = ["clip"] - - def process(self, instance): - # Exclude non-tagged instances. - tagged = False - asset_names = [] - for tag in instance.data["tags"]: - family = dict(tag["metadata"]).get("tag.family", "") - if family.lower() == "assetbuild": - asset_names.append(tag["name"]) - tagged = True - - if not tagged: - self.log.debug( - "Skipping \"{}\" because its not tagged with " - "\"assetbuild\"".format(instance) - ) - return - - # Collect asset builds. - data = {"assetBuilds": []} - for name in asset_names: - data["assetBuilds"].append( - instance.context.data["assetBuilds"][name] - ) - self.log.debug("Found asset builds: {}".format(data["assetBuilds"])) - - instance.data.update(data) - - -class CollectExistingAssetBuilds(api.ContextPlugin): - """Collect all asset builds from database.""" - - order = CollectAssetBuilds.order - 0.1 - label = "Collect Existing AssetBuilds" - hosts = ["nukestudio"] - - def process(self, context): - context.data["assetBuilds"] = {} - for asset in io.find({"type": "asset"}): - if asset["data"]["entityType"] == "AssetBuild": - context.data["assetBuilds"][asset["name"]] = asset From b5d1f29184426489942937e47b0a6aaebdda30df Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 1 Jul 2019 18:29:18 +0100 Subject: [PATCH 30/44] Add family to subset to prevent overwriting. --- pype/plugins/nukestudio/publish/collect_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/collect_audio.py b/pype/plugins/nukestudio/publish/collect_audio.py index 1b677d6410..61419b1ad9 100644 --- a/pype/plugins/nukestudio/publish/collect_audio.py +++ b/pype/plugins/nukestudio/publish/collect_audio.py @@ -45,7 +45,7 @@ class CollectAudio(api.InstancePlugin): tag_data = dict(tag["metadata"]) if "tag.subset" in tag_data: subset = tag_data["tag.subset"] - data["subset"] = subset + data["subset"] = "audio" + subset.title() data["source"] = data["sourcePath"] From 9dbbba090143a4dbb170e77185168e5a41a177dc Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 1 Jul 2019 18:30:10 +0100 Subject: [PATCH 31/44] Bug fix for assetbuilds not on all instances. --- pype/plugins/nukestudio/publish/collect_hierarchy_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py index ba9fe118da..a20a515077 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -221,7 +221,7 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): in_info = {} in_info["inputs"] = [ - x["_id"] for x in instance.data["assetBuilds"] + x["_id"] for x in instance.data.get("assetbuilds", []) ] # suppose that all instances are Shots From bb483174ec832c4888842c20560696064c6e0818 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 1 Jul 2019 18:30:35 +0100 Subject: [PATCH 32/44] Rename/restructure shots collector. --- .../{collect_shot.py => collect_shots.py} | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) rename pype/plugins/nukestudio/publish/{collect_shot.py => collect_shots.py} (71%) diff --git a/pype/plugins/nukestudio/publish/collect_shot.py b/pype/plugins/nukestudio/publish/collect_shots.py similarity index 71% rename from pype/plugins/nukestudio/publish/collect_shot.py rename to pype/plugins/nukestudio/publish/collect_shots.py index 33e6a7ee7e..952da8d33a 100644 --- a/pype/plugins/nukestudio/publish/collect_shot.py +++ b/pype/plugins/nukestudio/publish/collect_shots.py @@ -1,16 +1,17 @@ from pyblish import api -class CollectShot(api.InstancePlugin): +class CollectShots(api.InstancePlugin): """Collect Shot from Clip.""" - # Run just before CollectSubsets + # Run just before CollectClipSubsets order = api.CollectorOrder + 0.1025 - label = "Collect Shot" + label = "Collect Shots" hosts = ["nukestudio"] families = ["clip"] def process(self, instance): + # Exclude non-tagged instances. tagged = False for tag in instance.data["tags"]: if tag["name"].lower() == "hierarchy": @@ -23,8 +24,6 @@ class CollectShot(api.InstancePlugin): ) return - context = instance.context - # Collect data. data = {} for key, value in instance.data.iteritems(): @@ -35,11 +34,12 @@ class CollectShot(api.InstancePlugin): data["frameStart"] = 1 data["label"] += " - tasks: {} - assetbuilds: {}".format( - data["tasks"], [x["name"] for x in data["assetBuilds"]] + data["tasks"], [x["name"] for x in data["assetbuilds"]] ) # Get handles. - data["handleStart"] = instance.data["handleStart"] + data["handles"] + data["handleStart"] = instance.data["handleStart"] + data["handleStart"] += data["handles"] data["handleEnd"] = instance.data["handleEnd"] + data["handles"] # Frame-ranges with handles. @@ -51,8 +51,10 @@ class CollectShot(api.InstancePlugin): data["timelineOut"] = int(data["item"].timelineOut()) # Frame-ranges with handles. - data["timelineInHandles"] = data["timelineIn"] - data["handleStart"] - data["timelineOutHandles"] = data["timelineOut"] + data["handleEnd"] + data["timelineInHandles"] = data["timelineIn"] + data["timelineInHandles"] -= data["handleStart"] + data["timelineOutHandles"] = data["timelineOut"] + data["timelineOutHandles"] += data["handleEnd"] # Creating comp frame range. data["endFrame"] = ( @@ -60,9 +62,9 @@ class CollectShot(api.InstancePlugin): ) # Get fps. - sequence = context.data["activeSequence"] + sequence = instance.context.data["activeSequence"] data["fps"] = sequence.framerate() # Create instance. self.log.debug("Creating instance with: {}".format(data)) - context.create_instance(**data) + instance.context.create_instance(**data) From 7085c8891036d07efa7a8c29a5b26088a5188c7e Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 1 Jul 2019 18:31:16 +0100 Subject: [PATCH 33/44] Add family to subset to prevent overwriting. --- pype/plugins/nukestudio/publish/collect_plates.py | 2 +- pype/plugins/nukestudio/publish/collect_reviews.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index 4aabdbc184..8c9e7f4b63 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -48,7 +48,7 @@ class CollectPlates(api.InstancePlugin): tag_data = dict(tag["metadata"]) if "tag.subset" in tag_data: subset = tag_data["tag.subset"] - data["subset"] = subset + data["subset"] = "plate" + subset.title() data["label"] += " - {} - ({})".format( subset, os.path.splitext(data["sourcePath"])[1] diff --git a/pype/plugins/nukestudio/publish/collect_reviews.py b/pype/plugins/nukestudio/publish/collect_reviews.py index 33f7221bff..253d2db8ca 100644 --- a/pype/plugins/nukestudio/publish/collect_reviews.py +++ b/pype/plugins/nukestudio/publish/collect_reviews.py @@ -46,7 +46,7 @@ class CollectReviews(api.InstancePlugin): tag_data = dict(tag["metadata"]) if "tag.subset" in tag_data: subset = tag_data["tag.subset"] - data["subset"] = subset + data["subset"] = "review" + subset.title() data["source"] = data["sourcePath"] From 5f3374e1e69b561dd5f90686e96fc2b5eecbaed0 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Jul 2019 09:01:35 +0100 Subject: [PATCH 34/44] Do not update Hierarchy tag. This needs to be explicitly manually deleted before updating. --- pype/nukestudio/tags.py | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/pype/nukestudio/tags.py b/pype/nukestudio/tags.py index d10fa60ba4..227210d6bf 100644 --- a/pype/nukestudio/tags.py +++ b/pype/nukestudio/tags.py @@ -10,8 +10,6 @@ import hiero log = Logger().get_logger(__name__, "nukestudio") -_hierarchy_orig = 'hierarchy_orig' - def create_tag(key, value): """ @@ -39,10 +37,10 @@ def update_tag(tag, value): value (dict): parameters of tag """ - tag.setNote(value['note']) - tag.setIcon(str(value['icon']['path'])) + tag.setNote(value["note"]) + tag.setIcon(str(value["icon"]["path"])) mtd = tag.metadata() - pres_mtd = value.get('metadata', None) + pres_mtd = value.get("metadata", None) if pres_mtd: [mtd.setValue("tag.{}".format(str(k)), str(v)) for k, v in pres_mtd.items()] @@ -59,7 +57,7 @@ def add_tags_from_presets(): presets = config.get_presets() # get nukestudio tag.json from presets - nks_pres = presets['nukestudio'] + nks_pres = presets["nukestudio"] nks_pres_tags = nks_pres.get("tags", None) # Get project task types. @@ -142,24 +140,10 @@ def add_tags_from_presets(): else: # check if Hierarchy in name # update Tag if already exists - tag_names = [tg.name().lower() for tg in tags] for _t in tags: - if 'hierarchy' not in _t.name().lower(): - # update only non hierarchy tags - # because hierarchy could be edited - update_tag(_t, _val) - elif _hierarchy_orig in _t.name().lower(): - # if hierarchy_orig already exists just - # sync with preset - update_tag(_t, _val) - else: - # if history tag already exist then create - # backup synchronisable original Tag - if (_hierarchy_orig not in tag_names): - # create Tag obj - tag = create_tag( - _hierarchy_orig.capitalize(), _val - ) + if "hierarchy" in _t.name().lower(): + continue - # adding Tag to Bin - root_bin.addItem(tag) + # update only non hierarchy tags + # because hierarchy could be edited + update_tag(_t, _val) From 62afca64a34a8a273b7e1c79781393f1b52c903a Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Jul 2019 09:05:33 +0100 Subject: [PATCH 35/44] Fix "assetbuilds" was expected for collecting shots. --- pype/plugins/nukestudio/publish/collect_shots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/collect_shots.py b/pype/plugins/nukestudio/publish/collect_shots.py index 952da8d33a..8cf02ff764 100644 --- a/pype/plugins/nukestudio/publish/collect_shots.py +++ b/pype/plugins/nukestudio/publish/collect_shots.py @@ -34,7 +34,7 @@ class CollectShots(api.InstancePlugin): data["frameStart"] = 1 data["label"] += " - tasks: {} - assetbuilds: {}".format( - data["tasks"], [x["name"] for x in data["assetbuilds"]] + data["tasks"], [x["name"] for x in data.get("assetbuilds", [])] ) # Get handles. From f2c6c6c161658cf279e2c84df72c04353d8241dd Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Jul 2019 09:56:35 +0100 Subject: [PATCH 36/44] Add reminder about opening nuke scripts for getting .nk sources. --- pype/plugins/nukestudio/publish/collect_clips.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/collect_clips.py b/pype/plugins/nukestudio/publish/collect_clips.py index 8025bbcd02..468d990853 100644 --- a/pype/plugins/nukestudio/publish/collect_clips.py +++ b/pype/plugins/nukestudio/publish/collect_clips.py @@ -31,7 +31,8 @@ class CollectClips(api.ContextPlugin): source_path = source.firstpath() # If source is *.nk its a comp effect and we need to fetch the - # write node output. + # write node output. This should be improved by parsing the script + # rather than opening it. if source_path.endswith(".nk"): nuke.scriptOpen(source_path) # There should noly be one. From ded8f467a6ce732a8388aa8e32db73f5d543abbf Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Jul 2019 09:57:01 +0100 Subject: [PATCH 37/44] Cut review media to timeline. --- pype/plugins/nukestudio/publish/extract_review.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/extract_review.py b/pype/plugins/nukestudio/publish/extract_review.py index ef364d7968..0f5d1a4f33 100644 --- a/pype/plugins/nukestudio/publish/extract_review.py +++ b/pype/plugins/nukestudio/publish/extract_review.py @@ -24,11 +24,16 @@ class ExtractQuicktime(pype.api.Extractor): # Has to be yuv420p for compatibility with older players and smooth # playback. This does come with a sacrifice of more visible banding # issues. + item = instance.data["item"] + start_frame = item.mapTimelineToSource(item.timelineIn()) + end_frame = item.mapTimelineToSource(item.timelineOut()) output_options = { "pix_fmt": "yuv420p", "crf": "18", "timecode": "00:00:00:01", - "vf": "scale=trunc(iw/2)*2:trunc(ih/2)*2" + "vf": "scale=trunc(iw/2)*2:trunc(ih/2)*2", + "ss": start_frame / item.sequence().framerate().toFloat(), + "frames": int(end_frame - start_frame) + 1 } try: From 352ba19d0bc90e312e8cd7c7b3a88f59058db629 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Jul 2019 10:03:59 +0100 Subject: [PATCH 38/44] Create review thumbnail if it does not exist. --- pype/plugins/nukestudio/publish/extract_review.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pype/plugins/nukestudio/publish/extract_review.py b/pype/plugins/nukestudio/publish/extract_review.py index 0f5d1a4f33..bf7317f010 100644 --- a/pype/plugins/nukestudio/publish/extract_review.py +++ b/pype/plugins/nukestudio/publish/extract_review.py @@ -79,11 +79,13 @@ class ExtractQuicktime(pype.api.Extractor): self.log.debug("Adding representation: {}".format(representation)) # Adding thumbnail representation. + path = instance.data["sourcePath"].replace(".mov", ".png") + if not os.path.exists(path): + item.thumbnail(start_frame).save(path, format="png") + representation = { - "files": os.path.basename( - instance.data["sourcePath"].replace(".mov", ".png") - ), - "stagingDir": os.path.dirname(instance.data["sourcePath"]), + "files": os.path.basename(path), + "stagingDir": os.path.dirname(path), "name": "thumbnail", "thumbnail": True, "ext": "png" From 2acb4a63e48b4b3227882ee04c801e1b25f97a92 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Jul 2019 10:50:15 +0100 Subject: [PATCH 39/44] Extract plates to timeline range. --- .../nukestudio/publish/extract_plate.py | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 pype/plugins/nukestudio/publish/extract_plate.py diff --git a/pype/plugins/nukestudio/publish/extract_plate.py b/pype/plugins/nukestudio/publish/extract_plate.py new file mode 100644 index 0000000000..08bed1c2a1 --- /dev/null +++ b/pype/plugins/nukestudio/publish/extract_plate.py @@ -0,0 +1,98 @@ +import os + +import pype.api + +from pype.vendor import ffmpeg + + +class ExtractPlate(pype.api.Extractor): + """Extract plate cut to the timeline. + + Only supporting mov plates for now. Image sequences already get cut down to + timeline range. + + """ + + label = "Plate" + hosts = ["nukestudio"] + families = ["plate"] + optional = True + + def process(self, instance): + if not instance.data["sourcePath"].endswith(".mov"): + self.log.debug( + "Skipping {} because its not a \"*.mov\" " + "format.".format(instance) + ) + return + + staging_dir = self.staging_dir(instance) + filename = "{0}".format(instance.name) + ".mov" + output_path = os.path.join(staging_dir, filename) + input_path = instance.data["sourcePath"] + + self.log.info("Outputting movie to %s" % output_path) + + # Cut plate to timeline. + item = instance.data["item"] + start_frame = item.mapTimelineToSource( + item.timelineIn() - ( + instance.data["handleStart"] + instance.data["handles"] + ) + ) + end_frame = item.mapTimelineToSource( + item.timelineOut() + ( + instance.data["handleEnd"] + instance.data["handles"] + ) + ) + framerate = item.sequence().framerate().toFloat() + output_options = { + "vcodec": "copy", + "ss": start_frame / framerate, + "frames": int(end_frame - start_frame) + 1 + } + + try: + ( + ffmpeg + .input(input_path) + .output(output_path, **output_options) + .run(overwrite_output=True, + capture_stdout=True, + capture_stderr=True) + ) + except ffmpeg.Error as e: + ffmpeg_error = "ffmpeg error: {}".format(e.stderr) + self.log.error(ffmpeg_error) + raise RuntimeError(ffmpeg_error) + + # Adding representation. + ext = os.path.splitext(output_path)[1][1:] + representation = { + "files": os.path.basename(output_path), + "staging_dir": staging_dir, + "startFrame": 0, + "endFrame": end_frame - start_frame, + "step": 1, + "frameRate": framerate, + "thumbnail": False, + "name": ext, + "ext": ext + } + instance.data["representations"] = [representation] + self.log.debug("Adding representation: {}".format(representation)) + + # Adding thumbnail representation. + path = instance.data["sourcePath"].replace(".mov", ".png") + if not os.path.exists(path): + item.thumbnail(start_frame).save(path, format="png") + + representation = { + "files": os.path.basename(path), + "stagingDir": os.path.dirname(path), + "name": "thumbnail", + "thumbnail": True, + "ext": "png" + } + instance.data["representations"].append(representation) + self.log.debug("Adding representation: {}".format(representation)) From baf60751823e0625b77316d7d39ad1f144f00458 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Jul 2019 11:58:20 +0100 Subject: [PATCH 40/44] Do not assume project handles. --- pype/plugins/nukestudio/publish/collect_clips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/collect_clips.py b/pype/plugins/nukestudio/publish/collect_clips.py index 468d990853..f678ad9f50 100644 --- a/pype/plugins/nukestudio/publish/collect_clips.py +++ b/pype/plugins/nukestudio/publish/collect_clips.py @@ -84,7 +84,7 @@ class CollectClips(api.ContextPlugin): "asset": data["item"].name(), "family": "clip", "families": [], - "handles": projectdata["handles"], + "handles": projectdata.get("handles", 0), "handleStart": 0, "handleEnd": 0, "version": version From 5cc745b609c38498737ad7b3f6d4f42b3a980ebf Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Jul 2019 16:18:19 +0100 Subject: [PATCH 41/44] Extract review with audio. --- .../nukestudio/publish/extract_review.py | 100 +++++++++++++----- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/pype/plugins/nukestudio/publish/extract_review.py b/pype/plugins/nukestudio/publish/extract_review.py index bf7317f010..45a47b99aa 100644 --- a/pype/plugins/nukestudio/publish/extract_review.py +++ b/pype/plugins/nukestudio/publish/extract_review.py @@ -1,11 +1,12 @@ import os +import subprocess + +from hiero.exporters.FnExportUtil import writeSequenceAudioWithHandles import pype.api -from pype.vendor import ffmpeg - -class ExtractQuicktime(pype.api.Extractor): +class ExtractReview(pype.api.Extractor): """Extract Quicktime with optimized codec for reviewing.""" label = "Review" @@ -15,40 +16,83 @@ class ExtractQuicktime(pype.api.Extractor): def process(self, instance): staging_dir = self.staging_dir(instance) - filename = "{0}".format(instance.name) + ".mov" + filename = "{0}_without_sound".format(instance.name) + ".mov" output_path = os.path.join(staging_dir, filename) input_path = instance.data["sourcePath"] - - self.log.info("Outputting movie to %s" % output_path) + item = instance.data["item"] # Has to be yuv420p for compatibility with older players and smooth # playback. This does come with a sacrifice of more visible banding # issues. - item = instance.data["item"] start_frame = item.mapTimelineToSource(item.timelineIn()) end_frame = item.mapTimelineToSource(item.timelineOut()) - output_options = { - "pix_fmt": "yuv420p", - "crf": "18", - "timecode": "00:00:00:01", - "vf": "scale=trunc(iw/2)*2:trunc(ih/2)*2", - "ss": start_frame / item.sequence().framerate().toFloat(), - "frames": int(end_frame - start_frame) + 1 - } + args = [ + "ffmpeg", + "-ss", str(start_frame / item.sequence().framerate().toFloat()), + "-i", input_path, + "-pix_fmt", "yuv420p", + "-crf", "18", + "-timecode", "00:00:00:01", + "-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2", + "-frames", str(int(end_frame - start_frame) + 1), + output_path + ] - try: - ( - ffmpeg - .input(input_path) - .output(output_path, **output_options) - .run(overwrite_output=True, - capture_stdout=True, - capture_stderr=True) - ) - except ffmpeg.Error as e: - ffmpeg_error = "ffmpeg error: {}".format(e.stderr) - self.log.error(ffmpeg_error) - raise RuntimeError(ffmpeg_error) + self.log.debug(subprocess.list2cmdline(args)) + p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, + cwd=os.path.dirname(args[-1]) + ) + + output = p.communicate()[0] + + if p.returncode != 0: + raise ValueError(output) + + self.log.debug(output) + + # Extract audio. + filename = "{0}".format(instance.name) + ".wav" + audio_path = os.path.join(staging_dir, filename) + writeSequenceAudioWithHandles( + audio_path, + item.sequence(), + item.timelineIn(), + item.timelineOut(), + 0, + 0 + ) + + input_path = output_path + filename = "{0}_with_sound".format(instance.name) + ".mov" + output_path = os.path.join(staging_dir, filename) + + args = [ + "ffmpeg", + "-i", input_path, + "-i", audio_path, + "-vcodec", "copy", + output_path + ] + + self.log.debug(subprocess.list2cmdline(args)) + p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, + cwd=os.path.dirname(args[-1]) + ) + + output = p.communicate()[0] + + if p.returncode != 0: + raise ValueError(output) + + self.log.debug(output) # Adding movie representation. start_frame = int( From 72cea27d4fd1b75d5e788522ad6679a3ccae8871 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 4 Jul 2019 16:18:36 +0100 Subject: [PATCH 42/44] Extract plate with audio. --- .../nukestudio/publish/extract_plate.py | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/pype/plugins/nukestudio/publish/extract_plate.py b/pype/plugins/nukestudio/publish/extract_plate.py index 08bed1c2a1..fbbdae612d 100644 --- a/pype/plugins/nukestudio/publish/extract_plate.py +++ b/pype/plugins/nukestudio/publish/extract_plate.py @@ -1,7 +1,9 @@ import os +import subprocess + +from hiero.exporters.FnExportUtil import writeSequenceAudioWithHandles import pype.api - from pype.vendor import ffmpeg @@ -27,7 +29,7 @@ class ExtractPlate(pype.api.Extractor): return staging_dir = self.staging_dir(instance) - filename = "{0}".format(instance.name) + ".mov" + filename = "{0}_without_sound".format(instance.name) + ".mov" output_path = os.path.join(staging_dir, filename) input_path = instance.data["sourcePath"] @@ -66,6 +68,46 @@ class ExtractPlate(pype.api.Extractor): self.log.error(ffmpeg_error) raise RuntimeError(ffmpeg_error) + # Extract audio. + filename = "{0}".format(instance.name) + ".wav" + audio_path = os.path.join(staging_dir, filename) + writeSequenceAudioWithHandles( + audio_path, + item.sequence(), + item.timelineIn(), + item.timelineOut(), + 0, + 0 + ) + + input_path = output_path + filename = "{0}_with_sound".format(instance.name) + ".mov" + output_path = os.path.join(staging_dir, filename) + + args = [ + "ffmpeg", + "-i", input_path, + "-i", audio_path, + "-vcodec", "copy", + output_path + ] + + self.log.debug(subprocess.list2cmdline(args)) + p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, + cwd=os.path.dirname(args[-1]) + ) + + output = p.communicate()[0] + + if p.returncode != 0: + raise ValueError(output) + + self.log.debug(output) + # Adding representation. ext = os.path.splitext(output_path)[1][1:] representation = { From 02d66037bd03edb1b59a44b68cf1a6edb40470a5 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 5 Jul 2019 13:02:02 +0100 Subject: [PATCH 43/44] Better logging from Atomicity. --- pype/plugins/global/publish/integrate_new.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index f4d653e97d..75ad687d0f 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -2,6 +2,8 @@ import os import logging import shutil import clique +import traceback +import sys import errno import pyblish.api @@ -102,6 +104,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): for result in context.data["results"]: if not result["success"]: self.log.debug(result) + exc_type, exc_value, exc_traceback = result["error_info"] + extracted_traceback = traceback.extract_tb(exc_traceback)[-1] + self.log.debug( + "Error at line {}: \"{}\"".format( + extracted_traceback[1], result["error"] + ) + ) assert all(result["success"] for result in context.data["results"]), ( "Atomicity not held, aborting.") From 4be54ff8276d2296c28a6ffdf3fd51893f829e52 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 5 Jul 2019 13:05:16 +0100 Subject: [PATCH 44/44] Wrong plate image sequence files collected. --- pype/plugins/nukestudio/publish/collect_plates.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index 8c9e7f4b63..eb6b1ab355 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -233,10 +233,9 @@ class CollectPlatesData(api.InstancePlugin): ext=ext ) start_frame = source_first_frame - end_frame = source_first_frame + source_out - files = [file % i for i in range( - (source_first_frame + source_in_h), - ((source_first_frame + source_out_h) + 1), 1)] + duration = source_out_h - source_in_h + end_frame = source_first_frame + duration + files = [file % i for i in range(start_frame, (end_frame + 1), 1)] except Exception as e: self.log.debug("Exception in file: {}".format(e)) head, ext = source_file.split('.')