From cf6f213a967724d1fb879583ce6068ad2e5afe5c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 24 Feb 2020 15:18:01 +0100 Subject: [PATCH 01/54] added fixes of crashing sync to avalon event from develop to master --- pype/ftrack/events/event_sync_to_avalon.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index 49ac50c1db..cfeec248fb 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -28,7 +28,7 @@ class SyncToAvalonEvent(BaseEvent): ignore_entTypes = [ "socialfeed", "socialnotification", "note", "assetversion", "job", "user", "reviewsessionobject", "timer", - "timelog", "auth_userrole", "appointment" + "timelog", "auth_userrole", "appointment", "notelabellink" ] ignore_ent_types = ["Milestone"] ignore_keys = ["statusid", "thumbid"] @@ -1545,6 +1545,14 @@ class SyncToAvalonEvent(BaseEvent): entity_type_conf_ids[entity_type] = configuration_id break + if not configuration_id: + self.log.warning( + "BUG REPORT: Missing configuration for `{} < {} >`".format( + entity_type, ent_info["entityType"] + ) + ) + continue + _entity_key = collections.OrderedDict({ "configuration_id": configuration_id, "entity_id": ftrack_id From c55a8c0ec6c62ca1735a2b8e712f0c6136c169d4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Feb 2020 09:43:47 +0100 Subject: [PATCH 02/54] entityTypes are not ignored but are specified which will be processed --- pype/ftrack/events/event_sync_to_avalon.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index cfeec248fb..bb4bba1324 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -25,11 +25,7 @@ class SyncToAvalonEvent(BaseEvent): dbcon = DbConnector() - ignore_entTypes = [ - "socialfeed", "socialnotification", "note", - "assetversion", "job", "user", "reviewsessionobject", "timer", - "timelog", "auth_userrole", "appointment", "notelabellink" - ] + interest_entTypes = ["show", "task"] ignore_ent_types = ["Milestone"] ignore_keys = ["statusid", "thumbid"] @@ -477,7 +473,7 @@ class SyncToAvalonEvent(BaseEvent): found_actions = set() for ent_info in entities_info: entityType = ent_info["entityType"] - if entityType in self.ignore_entTypes: + if entityType not in self.interest_entTypes: continue entity_type = ent_info.get("entity_type") From d51cc4b2ec9284592bb454d921904bd4c7f3880b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Feb 2020 13:53:06 +0100 Subject: [PATCH 03/54] don't care about missing proj mainly when prepare projects is launched --- pype/ftrack/events/event_sync_to_avalon.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index bb4bba1324..6f6e86ee20 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -102,9 +102,10 @@ class SyncToAvalonEvent(BaseEvent): if self._avalon_ents_by_id is None: self._avalon_ents_by_id = {} proj, ents = self.avalon_entities - self._avalon_ents_by_id[proj["_id"]] = proj - for ent in ents: - self._avalon_ents_by_id[ent["_id"]] = ent + if proj: + self._avalon_ents_by_id[proj["_id"]] = proj + for ent in ents: + self._avalon_ents_by_id[ent["_id"]] = ent return self._avalon_ents_by_id @property @@ -124,13 +125,14 @@ class SyncToAvalonEvent(BaseEvent): if self._avalon_ents_by_ftrack_id is None: self._avalon_ents_by_ftrack_id = {} proj, ents = self.avalon_entities - ftrack_id = proj["data"]["ftrackId"] - self._avalon_ents_by_ftrack_id[ftrack_id] = proj - for ent in ents: - ftrack_id = ent["data"].get("ftrackId") - if ftrack_id is None: - continue - self._avalon_ents_by_ftrack_id[ftrack_id] = ent + if proj: + ftrack_id = proj["data"]["ftrackId"] + self._avalon_ents_by_ftrack_id[ftrack_id] = proj + for ent in ents: + ftrack_id = ent["data"].get("ftrackId") + if ftrack_id is None: + continue + self._avalon_ents_by_ftrack_id[ftrack_id] = ent return self._avalon_ents_by_ftrack_id @property From 54cc0781932af210e7346316795da53395ecbb54 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 26 Feb 2020 15:14:35 +0100 Subject: [PATCH 04/54] allow create project structure for project managers --- pype/ftrack/actions/action_create_project_structure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/ftrack/actions/action_create_project_structure.py b/pype/ftrack/actions/action_create_project_structure.py index 4589802f3a..6124ebe843 100644 --- a/pype/ftrack/actions/action_create_project_structure.py +++ b/pype/ftrack/actions/action_create_project_structure.py @@ -19,7 +19,7 @@ class CreateProjectFolders(BaseAction): #: Action description. description = 'Creates folder structure' #: roles that are allowed to register this action - role_list = ['Pypeclub', 'Administrator'] + role_list = ['Pypeclub', 'Administrator', 'Project Manager'] icon = '{}/ftrack/action_icons/CreateProjectFolders.svg'.format( os.environ.get('PYPE_STATICS_SERVER', '') ) From 5fe6854f87ec17e33b860595572ae8796554d78a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Feb 2020 12:26:25 +0100 Subject: [PATCH 05/54] fix(nk): adding new compatibility code submition from nuke to deadline --- .../nuke/publish/submit_nuke_deadline.py | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index 71108189c0..d69d5ba8bc 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -5,7 +5,6 @@ import getpass from avalon import api from avalon.vendor import requests import re - import pyblish.api @@ -55,7 +54,9 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): ) # Store output dir for unified publisher (filesequence) instance.data["deadlineSubmissionJob"] = response.json() - instance.data["publishJobState"] = "Active" + instance.data["outputDir"] = os.path.dirname( + render_path).replace("\\", "/") + instance.data["publishJobState"] = "Suspended" if instance.data.get("bakeScriptPath"): render_path = instance.data.get("bakeRenderPath") @@ -87,6 +88,9 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): script_name = os.path.basename(script_path) jobname = "%s - %s" % (script_name, instance.name) + output_filename_0 = self.preview_fname(render_path) + output_directory_0 = render_dir.replace("\\", "/") + if not responce_data: responce_data = {} @@ -119,6 +123,11 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): ), "Comment": self._comment, + # Optional, enable double-click to preview rendered + # frames from Deadline Monitor + "OutputFilename0": output_filename_0.replace("\\", "/"), + "OutputDirectory0": output_directory_0 + }, "PluginInfo": { # Input @@ -220,6 +229,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): self.log.info("Submitting..") self.log.info(json.dumps(payload, indent=4, sort_keys=True)) + # adding expectied files to instance.data + self.expected_files(instance, render_path) + self.log.debug("__ expectedFiles: `{}`".format( + instance.data["expectedFiles"])) response = requests.post(self.deadline_url, json=payload) if not response.ok: @@ -240,3 +253,51 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "%f=%d was rounded off to nearest integer" % (value, int(value)) ) + + def preview_fname(self, path): + """Return output file path with #### for padding. + + Deadline requires the path to be formatted with # in place of numbers. + For example `/path/to/render.####.png` + + Args: + path (str): path to rendered images + + Returns: + str + + """ + self.log.debug("_ path: `{}`".format(path)) + if "%" in path: + search_results = re.search(r"(%0)(\d)(d.)", path).groups() + self.log.debug("_ search_results: `{}`".format(search_results)) + return int(search_results[1]) + if "#" in path: + self.log.debug("_ path: `{}`".format(path)) + return path + else: + return path + + def expected_files(self, + instance, + path): + """ Create expected files in instance data + """ + if not instance.data.get("expectedFiles"): + instance.data["expectedFiles"] = list() + + dir = os.path.dirname(path) + file = os.path.basename(path) + + if "#" in file: + pparts = file.split("#") + padding = "%0{}d".format(len(pparts) - 1) + file = pparts[0] + padding + pparts[-1] + + if "%" not in file: + instance.data["expectedFiles"].append(path) + return + + for i in range(self._frame_start, (self._frame_end + 1)): + instance.data["expectedFiles"].append( + os.path.join(dir, (file % i)).replace("\\", "/")) From 408db0e8600e7e70d469c6103e96a3e27f9d8242 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Feb 2020 12:27:06 +0100 Subject: [PATCH 06/54] fix(nk): activating back version passing to instance --- pype/plugins/nuke/publish/collect_writes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index c29f676ef7..3882ed0b32 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -52,9 +52,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): output_dir = os.path.dirname(path) self.log.debug('output dir: {}'.format(output_dir)) - # # get version to instance for integration - # instance.data['version'] = instance.context.data.get( - # "version", pype.get_version_from_path(nuke.root().name())) + # get version to instance for integration + instance.data['version'] = instance.context.data["version"] self.log.debug('Write Version: %s' % instance.data('version')) From 682bb996abe3428511b5a362a6fad1f39c9378ae Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Feb 2020 12:27:40 +0100 Subject: [PATCH 07/54] fix(global): adding 2d compatibility --- .../global/publish/submit_publish_job.py | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 29dce58101..e4151d2317 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -131,6 +131,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): - publishJobState (str, Optional): "Active" or "Suspended" This defaults to "Suspended" + - expectedFiles (list or dict): explained bellow + """ label = "Submit image sequence jobs to Deadline or Muster" @@ -166,7 +168,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): instance_transfer = { "slate": ["slateFrame"], "review": ["lutPath"], - "render.farm": ["bakeScriptPath", "bakeRenderPath", "bakeWriteNodeName"] + "render.farm": ["bakeScriptPath", "bakeRenderPath", + "bakeWriteNodeName", "version"] } # list of family names to transfer to new family if present @@ -384,13 +387,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "tags": ["review"] if preview else [] } - # add tags - if preview: - if "ftrack" not in new_instance["families"]: - if os.environ.get("FTRACK_SERVER"): - new_instance["families"].append("ftrack") - if "review" not in new_instance["families"]: - new_instance["families"].append("review") + self._solve_families(new_instance, preview) new_instance["representations"] = [rep] @@ -399,6 +396,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if new_instance.get("extendFrames", False): self._copy_extend_frames(new_instance, rep) instances.append(new_instance) + return instances def _get_representations(self, instance, exp_files): @@ -419,6 +417,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): start = int(instance.get("frameStart")) end = int(instance.get("frameEnd")) cols, rem = clique.assemble(exp_files) + bake_render_path = instance.get("bakeRenderPath") + # create representation for every collected sequence for c in cols: ext = c.tail.lstrip(".") @@ -435,8 +435,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): preview = True break break + + if bake_render_path: + preview = False + rep = { - "name": str(c), + "name": ext, "ext": ext, "files": [os.path.basename(f) for f in list(c)], "frameStart": start, @@ -450,32 +454,41 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): representations.append(rep) - families = instance.get("families") - # if we have one representation with preview tag - # flag whole instance for review and for ftrack - if preview: - if "ftrack" not in families: - if os.environ.get("FTRACK_SERVER"): - families.append("ftrack") - if "review" not in families: - families.append("review") - instance["families"] = families + self._solve_families(instance, preview) # add reminders as representations for r in rem: ext = r.split(".")[-1] rep = { - "name": r, + "name": ext, "ext": ext, "files": os.path.basename(r), "stagingDir": os.path.dirname(r), "anatomy_template": "publish", } - + if r in bake_render_path: + rep.update({ + "anatomy_template": "render", + "tags": ["review", "preview"] + }) + # solve families with `preview` attributes + self._solve_families(instance, True) representations.append(rep) return representations + def _solve_families(self, instance, preview=False): + families = instance.get("families") + # if we have one representation with preview tag + # flag whole instance for review and for ftrack + if preview: + if "ftrack" not in families: + if os.environ.get("FTRACK_SERVER"): + families.append("ftrack") + if "review" not in families: + families.append("review") + instance["families"] = families + def process(self, instance): """ Detect type of renderfarm submission and create and post dependend job From 833ba22ecfc101b8e14250f125d914e4a3ae0147 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Feb 2020 12:28:02 +0100 Subject: [PATCH 08/54] fix(global): failing due missing tag in representation --- pype/plugins/global/publish/extract_jpeg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index 0b0e839529..9ad6a15dfe 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -27,8 +27,9 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): representations_new = representations[:] for repre in representations: + tags = repre.get("tags", []) self.log.debug(repre) - valid = 'review' in repre['tags'] or "thumb-nuke" in repre['tags'] + valid = 'review' in tags or "thumb-nuke" in tags if not valid: continue From 295208279f86fad583b211143329c1e47df489eb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Feb 2020 12:28:26 +0100 Subject: [PATCH 09/54] fix(global): version should be number --- pype/plugins/global/publish/collect_scene_version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/collect_scene_version.py b/pype/plugins/global/publish/collect_scene_version.py index 2844a695e2..02e913199b 100644 --- a/pype/plugins/global/publish/collect_scene_version.py +++ b/pype/plugins/global/publish/collect_scene_version.py @@ -21,7 +21,7 @@ class CollectSceneVersion(pyblish.api.ContextPlugin): if '' in filename: return - rootVersion = pype.get_version_from_path(filename) + rootVersion = int(pype.get_version_from_path(filename)) context.data['version'] = rootVersion - + self.log.info("{}".format(type(rootVersion))) self.log.info('Scene Version: %s' % context.data.get('version')) From 1944cbdd72fbf1f5699ac7e7a0be9ed163a04765 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Feb 2020 16:43:31 +0100 Subject: [PATCH 10/54] action set attribute `is_published` of deleted asset versions to False --- .../actions/action_delete_old_versions.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pype/ftrack/actions/action_delete_old_versions.py b/pype/ftrack/actions/action_delete_old_versions.py index bec21dae96..23ceb124e5 100644 --- a/pype/ftrack/actions/action_delete_old_versions.py +++ b/pype/ftrack/actions/action_delete_old_versions.py @@ -361,6 +361,24 @@ class DeleteOldVersions(BaseAction): self.dbcon.uninstall() + for entity in entities: + entity["is_published"] = False + + try: + session.commit() + + except Exception: + msg = ( + "Could not set `is_published` attribute to `False`" + " for selected AssetVersions." + ) + self.log.warning(msg, exc_info=True) + + return { + "success": False, + "message": msg + } + return True def delete_whole_dir_paths(self, dir_paths): From 3fba3e17ad44a6394419aca019ce67781fc590c0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Feb 2020 16:45:15 +0100 Subject: [PATCH 11/54] just commenting because last commit won't work --- .../actions/action_delete_old_versions.py | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/pype/ftrack/actions/action_delete_old_versions.py b/pype/ftrack/actions/action_delete_old_versions.py index 23ceb124e5..7b8b7c5617 100644 --- a/pype/ftrack/actions/action_delete_old_versions.py +++ b/pype/ftrack/actions/action_delete_old_versions.py @@ -361,23 +361,23 @@ class DeleteOldVersions(BaseAction): self.dbcon.uninstall() - for entity in entities: - entity["is_published"] = False - - try: - session.commit() - - except Exception: - msg = ( - "Could not set `is_published` attribute to `False`" - " for selected AssetVersions." - ) - self.log.warning(msg, exc_info=True) - - return { - "success": False, - "message": msg - } + # for entity in entities: + # entity["is_published"] = False + # + # try: + # session.commit() + # + # except Exception: + # msg = ( + # "Could not set `is_published` attribute to `False`" + # " for selected AssetVersions." + # ) + # self.log.warning(msg, exc_info=True) + # + # return { + # "success": False, + # "message": msg + # } return True From f63ec18f4eb677d1e06a9a91b325dd4a811e0fde Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 28 Feb 2020 17:11:07 +0100 Subject: [PATCH 12/54] first attemp possible solution --- .../actions/action_delete_old_versions.py | 74 +++++++++++++------ 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/pype/ftrack/actions/action_delete_old_versions.py b/pype/ftrack/actions/action_delete_old_versions.py index 7b8b7c5617..f6a66318c9 100644 --- a/pype/ftrack/actions/action_delete_old_versions.py +++ b/pype/ftrack/actions/action_delete_old_versions.py @@ -167,8 +167,11 @@ class DeleteOldVersions(BaseAction): asset_versions_by_parent_id = collections.defaultdict(list) subset_names_by_asset_name = collections.defaultdict(list) + ftrack_assets_by_name = {} for entity in entities: - parent_ent = entity["asset"]["parent"] + ftrack_asset = entity["asset"] + + parent_ent = ftrack_asset["parent"] parent_ftrack_id = parent_ent["id"] parent_name = parent_ent["name"] @@ -183,9 +186,12 @@ class DeleteOldVersions(BaseAction): project = parent_ent["project"] # Collect subset names per asset - subset_name = entity["asset"]["name"] + subset_name = ftrack_asset["name"] subset_names_by_asset_name[parent_name].append(subset_name) + if subset_name not in ftrack_assets_by_name: + ftrack_assets_by_name[subset_name] = ftrack_asset + # Set Mongo collection project_name = project["full_name"] self.dbcon.Session["AVALON_PROJECT"] = project_name @@ -236,7 +242,6 @@ class DeleteOldVersions(BaseAction): def sort_func(ent): return int(ent["name"]) - last_versions_by_parent = collections.defaultdict(list) all_last_versions = [] for parent_id, _versions in versions_by_parent.items(): for idx, version in enumerate( @@ -244,7 +249,6 @@ class DeleteOldVersions(BaseAction): ): if idx >= versions_count: break - last_versions_by_parent[parent_id].append(version) all_last_versions.append(version) self.log.debug("Collected versions ({})".format(len(versions))) @@ -253,6 +257,11 @@ class DeleteOldVersions(BaseAction): for version in all_last_versions: versions.remove(version) + # Update versions_by_parent without filtered versions + versions_by_parent = collections.defaultdict(list) + for ent in versions: + versions_by_parent[ent["parent"]].append(ent) + # Filter already deleted versions versions_to_pop = [] for version in versions: @@ -361,23 +370,46 @@ class DeleteOldVersions(BaseAction): self.dbcon.uninstall() - # for entity in entities: - # entity["is_published"] = False - # - # try: - # session.commit() - # - # except Exception: - # msg = ( - # "Could not set `is_published` attribute to `False`" - # " for selected AssetVersions." - # ) - # self.log.warning(msg, exc_info=True) - # - # return { - # "success": False, - # "message": msg - # } + # Set attribute `is_published` to `False` on ftrack AssetVersions + for subset_id, _versions in versions_by_parent.items(): + subset_name = None + for subset in subsets: + if subset["_id"] == subset_id: + subset_name = subset["name"] + break + + if subset_name is None: + self.log.warning( + "Subset with ID `{}` was not found.".format(str(subset_id)) + ) + continue + + ftrack_asset = ftrack_assets_by_name.get(subset_name) + if not ftrack_asset: + self.log.warning(( + "Could not find Ftrack asset with name `{}`" + ).format(subset_name)) + continue + + version_numbers = [int(ver["name"]) for ver in _versions] + for version in ftrack_asset["versions"]: + if int(version["version"]) in version_numbers: + version["is_published"] = False + + try: + session.commit() + + except Exception: + msg = ( + "Could not set `is_published` attribute to `False`" + " for selected AssetVersions." + ) + self.log.warning(msg, exc_info=True) + + return { + "success": False, + "message": msg + } return True From 3a3774c3baa58da245011d52ede69f5935519767 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Feb 2020 17:31:27 +0100 Subject: [PATCH 13/54] fix(nuke): thumbnail to publish on farm --- pype/plugins/nuke/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nuke/publish/extract_thumbnail.py b/pype/plugins/nuke/publish/extract_thumbnail.py index 55ba34a0d4..88ea78e623 100644 --- a/pype/plugins/nuke/publish/extract_thumbnail.py +++ b/pype/plugins/nuke/publish/extract_thumbnail.py @@ -116,7 +116,7 @@ class ExtractThumbnail(pype.api.Extractor): write_node["raw"].setValue(1) write_node.setInput(0, previous_node) temporary_nodes.append(write_node) - tags = ["thumbnail"] + tags = ["thumbnail", "publish_on_farm"] # retime for first_frame = int(last_frame) / 2 From 6f545cec66e7c60a60b8a5a9d0d415d6bc69b2d2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Feb 2020 17:42:14 +0100 Subject: [PATCH 14/54] fix(nk): adding handles to instance data --- pype/plugins/nuke/publish/collect_writes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index 3882ed0b32..76c2e8fa75 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -111,7 +111,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): "outputDir": output_dir, "ext": ext, "label": label, - "handles": handles, + "handleStart": handle_start, + "handleEnd": handle_end, "frameStart": first_frame, "frameEnd": last_frame, "outputType": output_type, From d9f08df58918295abc7d63e91a3b287c0e7a888b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Feb 2020 17:42:41 +0100 Subject: [PATCH 15/54] fix(nk): removing obsolete plugin --- .../nuke/publish/collect_script_version.py | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 pype/plugins/nuke/publish/collect_script_version.py diff --git a/pype/plugins/nuke/publish/collect_script_version.py b/pype/plugins/nuke/publish/collect_script_version.py deleted file mode 100644 index 9a6b5bf572..0000000000 --- a/pype/plugins/nuke/publish/collect_script_version.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import pype.api as pype -import pyblish.api - - -class CollectScriptVersion(pyblish. api.ContextPlugin): - """Collect Script Version.""" - - order = pyblish.api.CollectorOrder - label = "Collect Script Version" - hosts = [ - "nuke", - "nukeassist" - ] - - def process(self, context): - file_path = context.data["currentFile"] - base_name = os.path.basename(file_path) - # get version string - version = pype.get_version_from_path(base_name) - - context.data['version'] = version From 391861da419d112df89eb8cd99a9a15ee40aa712 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Feb 2020 17:43:22 +0100 Subject: [PATCH 16/54] fix(global): improving representation submition --- .../global/publish/submit_publish_job.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index e4151d2317..80b3137673 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -468,8 +468,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): } if r in bake_render_path: rep.update({ + "fps": instance.get("fps"), "anatomy_template": "render", - "tags": ["review", "preview"] + "tags": ["review", "delete"] }) # solve families with `preview` attributes self._solve_families(instance, True) @@ -498,7 +499,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): :param instance: Instance data :type instance: dict """ - data = instance.data.copy() context = instance.context self.context = context @@ -531,10 +531,19 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): start = instance.data.get("frameStart") if start is None: start = context.data["frameStart"] + end = instance.data.get("frameEnd") if end is None: end = context.data["frameEnd"] + handle_start = instance.data.get("handleStart") + if handle_start is None: + handle_start = context.data["handleStart"] + + handle_end = instance.data.get("handleEnd") + if handle_end is None: + handle_end = context.data["handleEnd"] + if data.get("extendFrames", False): start, end = self._extend_frames( asset, @@ -563,7 +572,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "asset": asset, "frameStart": start, "frameEnd": end, - "fps": data.get("fps", 25), + "handleStart": handle_start, + "handleEnd": handle_end, + "fps": data["fps"], "source": source, "extendFrames": data.get("extendFrames"), "overrideExistingFrame": data.get("overrideExistingFrame"), @@ -584,6 +595,16 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): for v in values: instance_skeleton_data[v] = instance.data.get(v) + # look into instance data if representations are not having any + # which are having tag `publish_on_farm` and include them + for r in instance.data.get("representations", []): + if "publish_on_farm" in r.get("tags"): + # create representations attribute of not there + if "representations" not in instance_skeleton_data.keys(): + instance_skeleton_data["representations"] = [] + + instance_skeleton_data["representations"].append(r) + instances = None assert data.get("expectedFiles"), ("Submission from old Pype version" " - missing expectedFiles") @@ -657,7 +678,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): data.get("expectedFiles") ) - if "representations" not in instance_skeleton_data: + if "representations" not in instance_skeleton_data.keys(): instance_skeleton_data["representations"] = [] # add representation From 81662d4edd50dd9a40965236ff186c25cee6c2ef Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 28 Feb 2020 17:43:41 +0100 Subject: [PATCH 17/54] fix(nuke): improving loaders --- pype/plugins/nuke/load/load_mov.py | 10 +++++++--- pype/plugins/nuke/load/load_sequence.py | 5 ++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pype/plugins/nuke/load/load_mov.py b/pype/plugins/nuke/load/load_mov.py index 77346a82a4..88e65156cb 100644 --- a/pype/plugins/nuke/load/load_mov.py +++ b/pype/plugins/nuke/load/load_mov.py @@ -112,6 +112,7 @@ class LoadMov(api.Loader): ) version = context['version'] version_data = version.get("data", {}) + repr_id = context["representation"]["_id"] orig_first = version_data.get("frameStart") orig_last = version_data.get("frameEnd") @@ -120,12 +121,16 @@ class LoadMov(api.Loader): first = orig_first - diff last = orig_last - diff - handle_start = version_data.get("handleStart") - handle_end = version_data.get("handleEnd") + handle_start = version_data.get("handleStart", 0) + handle_end = version_data.get("handleEnd", 0) colorspace = version_data.get("colorspace") repr_cont = context["representation"]["context"] + self.log.debug( + "Representation id `{}` ".format(repr_id)) + + context["representation"]["_id"] # create handles offset (only to last, because of mov) last += handle_start + handle_end # offset should be with handles so it match orig frame range @@ -138,7 +143,6 @@ class LoadMov(api.Loader): file = self.fname if not file: - repr_id = context["representation"]["_id"] self.log.warning( "Representation id `{}` is failing to load".format(repr_id)) return diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index db77c53aff..690f074c3f 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -86,8 +86,11 @@ class LoadSequence(api.Loader): version = context['version'] version_data = version.get("data", {}) - + repr_id = context["representation"]["_id"] + self.log.info("version_data: {}\n".format(version_data)) + self.log.debug( + "Representation id `{}` ".format(repr_id)) self.first_frame = int(nuke.root()["first_frame"].getValue()) self.handle_start = version_data.get("handleStart", 0) From ce03cacd8b5fcf6976071b46218986179123adef Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 2 Mar 2020 22:37:34 +0100 Subject: [PATCH 18/54] remove useless outputDirectory0 --- pype/plugins/nuke/publish/submit_nuke_deadline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index d69d5ba8bc..ba43ed574b 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -125,8 +125,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor - "OutputFilename0": output_filename_0.replace("\\", "/"), - "OutputDirectory0": output_directory_0 + "OutputFilename0": output_filename_0.replace("\\", "/") }, "PluginInfo": { From 073855cb5f4f203447a71338d0a961f0745ddc08 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 2 Mar 2020 22:37:55 +0100 Subject: [PATCH 19/54] add all outputs to deadline OutputFilename --- .../maya/publish/submit_maya_deadline.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 2f236be424..bd8497152e 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -1,6 +1,7 @@ import os import json import getpass +import clique from maya import cmds @@ -242,7 +243,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor - "OutputFilename0": output_filename_0.replace("\\", "/"), + "OutputDirectory0": os.path.dirname(output_filename_0), + "OutputFilename0": output_filename_0.replace("\\", "/") }, "PluginInfo": { # Input @@ -272,6 +274,26 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "AuxFiles": [] } + exp = instance.data.get("expectedFiles") + + OutputFilenames = {} + expIndex = 0 + + if isinstance(exp[0], dict): + # we have aovs and we need to iterate over them + for aov, files in exp[0].items(): + col = clique.assemble(files)[0][0] + outputFile = col.format('{head}{padding}{tail}') + payload['JobInfo']['OutputFilename' + str(expIndex)] = outputFile + OutputFilenames[expIndex] = outputFile + expIndex += 1 + else: + col = clique.assemble(files)[0][0] + outputFile = col.format('{head}{padding}{tail}') + payload['JobInfo']['OutputFilename' + str(expIndex)] = outputFile + # OutputFilenames[expIndex] = outputFile + + # We need those to pass them to pype for it to set correct context keys = [ "FTRACK_API_KEY", From a7605c6c502ccf857e273e6b772f2b9f1f80bf74 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 2 Mar 2020 22:38:17 +0100 Subject: [PATCH 20/54] add handles and fps to cg renders publishing --- pype/plugins/global/publish/submit_publish_job.py | 6 +++++- pype/plugins/maya/publish/collect_render.py | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 80b3137673..cb835bff11 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -544,6 +544,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if handle_end is None: handle_end = context.data["handleEnd"] + fps = instance.data.get("fps") + if fps is None: + fps = context.data["fps"] + if data.get("extendFrames", False): start, end = self._extend_frames( asset, @@ -574,7 +578,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "frameEnd": end, "handleStart": handle_start, "handleEnd": handle_end, - "fps": data["fps"], + "fps": fps, "source": source, "extendFrames": data.get("extendFrames"), "overrideExistingFrame": data.get("overrideExistingFrame"), diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 07eec4192f..f31198448b 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -220,6 +220,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin): layer=layer_name)), "renderer": self.get_render_attribute("currentRenderer", layer=layer_name), + "handleStart": context.data["assetEntity"]['data']['handleStart'], + "handleEnd": context.data["assetEntity"]['data']['handleEnd'], # instance subset "family": "renderlayer", From b075770388d5e0ec6b7cdd0981ea23ccff052137 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 2 Mar 2020 23:19:12 +0100 Subject: [PATCH 21/54] bump version --- pype/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/__init__.py b/pype/__init__.py index 4858441080..5cd9832558 100644 --- a/pype/__init__.py +++ b/pype/__init__.py @@ -9,7 +9,7 @@ from pypeapp import config import logging log = logging.getLogger(__name__) -__version__ = "2.5.0" +__version__ = "2.6.0" PROJECT_PLUGINS_PATH = os.environ.get("PYPE_PROJECT_PLUGINS") PACKAGE_DIR = os.path.dirname(__file__) From c795e0a3c8b6719f529b53eb00ac7fbc60bc2706 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 3 Mar 2020 11:26:39 +0100 Subject: [PATCH 22/54] tasks model has selectable items --- pype/tools/assetcreator/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/tools/assetcreator/model.py b/pype/tools/assetcreator/model.py index b77ffa7a5d..3af1d77127 100644 --- a/pype/tools/assetcreator/model.py +++ b/pype/tools/assetcreator/model.py @@ -241,7 +241,7 @@ class TasksModel(TreeModel): self.endResetModel() def flags(self, index): - return QtCore.Qt.ItemIsEnabled + return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable def headerData(self, section, orientation, role): From ae256b25f9802c5aad426a63c877df477e91fd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 3 Mar 2020 11:58:09 +0000 Subject: [PATCH 23/54] hotfix: path to metadata json was missing slash --- pype/plugins/global/publish/submit_publish_job.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index cb835bff11..027647a598 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -193,11 +193,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): metadata_path = os.path.normpath(metadata_path) mount_root = os.path.normpath(os.environ["PYPE_STUDIO_PROJECTS_MOUNT"]) - network_root = os.path.normpath( - os.environ["PYPE_STUDIO_PROJECTS_PATH"] - ) - + network_root = os.environ["PYPE_STUDIO_PROJECTS_PATH"] metadata_path = metadata_path.replace(mount_root, network_root) + metadata_path = os.path.normpath(metadata_path) # Generate the payload for Deadline submission payload = { From e53213e8762683ecd61e8034410818cdec787b18 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 Mar 2020 14:39:13 +0100 Subject: [PATCH 24/54] fix(nuke): adding deadline output_directory_0 --- pype/plugins/nuke/publish/submit_nuke_deadline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index ba43ed574b..e7aae0199b 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -125,6 +125,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor + "OutputDirectory0": output_directory_0, "OutputFilename0": output_filename_0.replace("\\", "/") }, From d1e739e699266321c67bdc758aed1ac17592240b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 Mar 2020 14:42:19 +0100 Subject: [PATCH 25/54] feat(global): adding OutputDirectory0 to dependent job revew --- pype/plugins/global/publish/submit_publish_job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index cb835bff11..71ba279af9 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -209,7 +209,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "UserName": job["Props"]["User"], "Comment": instance.context.data.get("comment", ""), "Priority": job["Props"]["Pri"], - "Pool": self.deadline_pool + "Pool": self.deadline_pool, + "OutputDirectory0": output_dir }, "PluginInfo": { "Version": "3.6", From 3c020c478bad1f111353317a2d93d21f74354bb9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 Mar 2020 18:38:12 +0100 Subject: [PATCH 26/54] fix(nk): adding deadline variables from presets --- pype/nuke/lib.py | 2 +- .../nuke/publish/submit_nuke_deadline.py | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 6eb4da951c..dedc42fa1d 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -432,7 +432,7 @@ def add_deadline_tab(node): node.addKnob(nuke.Tab_Knob("Deadline")) knob = nuke.Int_Knob("deadlineChunkSize", "Chunk Size") - knob.setValue(1) + knob.setValue(0) node.addKnob(knob) knob = nuke.Int_Knob("deadlinePriority", "Priority") diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index ba43ed574b..ee7432e241 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -22,6 +22,11 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): families = ["render.farm"] optional = True + deadline_priority = 50 + deadline_pool = "" + deadline_pool_secondary = "" + deadline_chunk_size = 1 + def process(self, instance): node = instance[0] @@ -89,7 +94,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): jobname = "%s - %s" % (script_name, instance.name) output_filename_0 = self.preview_fname(render_path) - output_directory_0 = render_dir.replace("\\", "/") if not responce_data: responce_data = {} @@ -100,6 +104,15 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): except OSError: pass + # define chunk and priority + chunk_size = instance.data.get("deadlineChunkSize") + if chunk_size == 0: + chunk_size = self.deadline_chunk_size + + priority = instance.data.get("deadlinePriority") + if priority != 50: + priority = self.deadline_priority + payload = { "JobInfo": { # Top-level group name @@ -111,10 +124,11 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # Arbitrary username, for visualisation in Monitor "UserName": self._deadline_user, - "Priority": instance.data["deadlinePriority"], + "Priority": priority, + "ChunkSize": chunk_size, - "Pool": "2d", - "SecondaryPool": "2d", + "Pool": self.deadline_pool, + "SecondaryPool": self.deadline_pool_secondary, "Plugin": "Nuke", "Frames": "{start}-{end}".format( From e00ad0f3eb28ab89feec23af918fbde16e50d749 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 Mar 2020 18:39:31 +0100 Subject: [PATCH 27/54] fix(nk): removing already removed `OutputDirectory0` --- pype/plugins/nuke/publish/submit_nuke_deadline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index e7aae0199b..0c0663c147 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -89,7 +89,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): jobname = "%s - %s" % (script_name, instance.name) output_filename_0 = self.preview_fname(render_path) - output_directory_0 = render_dir.replace("\\", "/") if not responce_data: responce_data = {} @@ -125,7 +124,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor - "OutputDirectory0": output_directory_0, "OutputFilename0": output_filename_0.replace("\\", "/") }, From df80aa7088367a36fa487b91af551155a4fc8bad Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 Mar 2020 18:50:04 +0100 Subject: [PATCH 28/54] fix(global): integrate frame to representation was not correct --- pype/plugins/global/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 2a9b813231..0d1606c67c 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -441,7 +441,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if sequence_repre and repre.get("frameStart"): representation['context']['frame'] = ( - src_padding_exp % int(repre.get("frameStart")) + dst_padding_exp % int(repre.get("frameStart")) ) self.log.debug("__ representation: {}".format(representation)) From 525e987427bc633a412d9477d12c1d6742e708ad Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 3 Mar 2020 18:50:39 +0100 Subject: [PATCH 29/54] fix(global): outputName on representation was only single file --- pype/plugins/global/publish/integrate_new.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 0d1606c67c..1d061af173 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -278,6 +278,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): stagingdir = repre['stagingDir'] if repre.get('anatomy_template'): template_name = repre['anatomy_template'] + if repre.get("outputName"): + template_data["output"] = repre['outputName'] template = os.path.normpath( anatomy.templates[template_name]["path"]) @@ -389,9 +391,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): template_data["representation"] = repre['ext'] - if repre.get("outputName"): - template_data["output"] = repre['outputName'] - src = os.path.join(stagingdir, fname) anatomy_filled = anatomy.format(template_data) template_filled = anatomy_filled[template_name]["path"] From a22ca2665502fadd5f776002a7cd0126cf8a2bff Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 4 Mar 2020 15:09:02 +0100 Subject: [PATCH 30/54] feat(glob,nk): old files/dirs remove --- .../_publish_unused/collect_deadline_user.py | 60 ------- .../global/_publish_unused/collect_json.py | 127 --------------- .../_publish_unused/collect_textures.py | 88 ---------- .../global/_publish_unused/extract_json.py | 51 ------ .../_publish_unused/extract_quicktime.py | 86 ---------- .../global/_publish_unused/transcode.py | 153 ------------------ pype/plugins/nuke/_load_unused/load_alembic | 0 .../plugins/nuke/_load_unused/load_camera_abc | 0 pype/plugins/nuke/_load_unused/load_camera_nk | 1 - pype/plugins/nuke/_load_unused/load_still | 1 - .../_publish_unused/collect_render_target.py | 46 ------ .../nuke/_publish_unused/submit_deadline.py | 147 ----------------- .../nuke/_publish_unused/test_instances.py | 24 --- .../_publish_unused/validate_nuke_settings.py | 68 -------- .../_publish_unused/validate_proxy_mode.py | 33 ---- setup/nuke/nuke_path/atom_server.py | 54 ------- setup/nuke/nuke_path/menu.py | 1 - 17 files changed, 940 deletions(-) delete mode 100644 pype/plugins/global/_publish_unused/collect_deadline_user.py delete mode 100644 pype/plugins/global/_publish_unused/collect_json.py delete mode 100644 pype/plugins/global/_publish_unused/collect_textures.py delete mode 100644 pype/plugins/global/_publish_unused/extract_json.py delete mode 100644 pype/plugins/global/_publish_unused/extract_quicktime.py delete mode 100644 pype/plugins/global/_publish_unused/transcode.py delete mode 100644 pype/plugins/nuke/_load_unused/load_alembic delete mode 100644 pype/plugins/nuke/_load_unused/load_camera_abc delete mode 100644 pype/plugins/nuke/_load_unused/load_camera_nk delete mode 100644 pype/plugins/nuke/_load_unused/load_still delete mode 100644 pype/plugins/nuke/_publish_unused/collect_render_target.py delete mode 100644 pype/plugins/nuke/_publish_unused/submit_deadline.py delete mode 100644 pype/plugins/nuke/_publish_unused/test_instances.py delete mode 100644 pype/plugins/nuke/_publish_unused/validate_nuke_settings.py delete mode 100644 pype/plugins/nuke/_publish_unused/validate_proxy_mode.py delete mode 100644 setup/nuke/nuke_path/atom_server.py diff --git a/pype/plugins/global/_publish_unused/collect_deadline_user.py b/pype/plugins/global/_publish_unused/collect_deadline_user.py deleted file mode 100644 index f4d13a0545..0000000000 --- a/pype/plugins/global/_publish_unused/collect_deadline_user.py +++ /dev/null @@ -1,60 +0,0 @@ -import os -import subprocess - -import pyblish.api - -CREATE_NO_WINDOW = 0x08000000 - - -def deadline_command(cmd): - # Find Deadline - path = os.environ.get("DEADLINE_PATH", None) - assert path is not None, "Variable 'DEADLINE_PATH' must be set" - - executable = os.path.join(path, "deadlinecommand") - if os.name == "nt": - executable += ".exe" - assert os.path.exists( - executable), "Deadline executable not found at %s" % executable - assert cmd, "Must have a command" - - query = (executable, cmd) - - process = subprocess.Popen(query, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True, - creationflags=CREATE_NO_WINDOW) - out, err = process.communicate() - - return out - - -class CollectDeadlineUser(pyblish.api.ContextPlugin): - """Retrieve the local active Deadline user""" - - order = pyblish.api.CollectorOrder + 0.499 - label = "Deadline User" - - hosts = ['maya', 'fusion', 'nuke'] - families = [ - "renderlayer", - "saver.deadline", - "imagesequence" - ] - - - def process(self, context): - """Inject the current working file""" - user = None - try: - user = deadline_command("GetCurrentUserName").strip() - except: - self.log.warning("Deadline command seems not to be working") - - if not user: - self.log.warning("No Deadline user found. " - "Do you have Deadline installed?") - return - - self.log.info("Found Deadline user: {}".format(user)) - context.data['deadlineUser'] = user diff --git a/pype/plugins/global/_publish_unused/collect_json.py b/pype/plugins/global/_publish_unused/collect_json.py deleted file mode 100644 index dc5bfb9c81..0000000000 --- a/pype/plugins/global/_publish_unused/collect_json.py +++ /dev/null @@ -1,127 +0,0 @@ -import os -import json -import re - -import pyblish.api -import clique - - -class CollectJSON(pyblish.api.ContextPlugin): - """ Collecting the json files in current directory. """ - - label = "JSON" - order = pyblish.api.CollectorOrder - hosts = ['maya'] - - def version_get(self, string, prefix): - """ Extract version information from filenames. Code from Foundry"s - nukescripts.version_get() - """ - - regex = r"[/_.]{}\d+".format(prefix) - matches = re.findall(regex, string, re.IGNORECASE) - - if not len(matches): - msg = "No '_{}#' found in '{}'".format(prefix, string) - raise ValueError(msg) - return matches[-1:][0][1], re.search(r"\d+", matches[-1:][0]).group() - - def process(self, context): - current_file = context.data.get("currentFile", '') - # Skip if current file is not a directory - if not os.path.isdir(current_file): - return - - # Traverse directory and collect collections from json files. - instances = [] - for root, dirs, files in os.walk(current_file): - for f in files: - if f.endswith(".json"): - with open(os.path.join(root, f)) as json_data: - for data in json.load(json_data): - instances.append(data) - - # Validate instance based on supported families. - valid_families = ["img", "cache", "scene", "mov"] - valid_data = [] - for data in instances: - families = data.get("families", []) + [data["family"]] - family_type = list(set(families) & set(valid_families)) - if family_type: - valid_data.append(data) - - # Create existing output instance. - scanned_dirs = [] - files = [] - collections = [] - for data in valid_data: - if "collection" not in data.keys(): - continue - if data["collection"] is None: - continue - - instance_collection = clique.parse(data["collection"]) - - try: - version = self.version_get( - os.path.basename(instance_collection.format()), "v" - )[1] - except KeyError: - # Ignore any output that is not versioned - continue - - # Getting collections of all previous versions and current version - for count in range(1, int(version) + 1): - - # Generate collection - version_string = "v" + str(count).zfill(len(version)) - head = instance_collection.head.replace( - "v" + version, version_string - ) - collection = clique.Collection( - head=head.replace("\\", "/"), - padding=instance_collection.padding, - tail=instance_collection.tail - ) - collection.version = count - - # Scan collection directory - scan_dir = os.path.dirname(collection.head) - if scan_dir not in scanned_dirs and os.path.exists(scan_dir): - for f in os.listdir(scan_dir): - file_path = os.path.join(scan_dir, f) - files.append(file_path.replace("\\", "/")) - scanned_dirs.append(scan_dir) - - # Match files to collection and add - for f in files: - if collection.match(f): - collection.add(f) - - # Skip if no files were found in the collection - if not list(collection): - continue - - # Skip existing collections - if collection in collections: - continue - - instance = context.create_instance(name=data["name"]) - version = self.version_get( - os.path.basename(collection.format()), "v" - )[1] - - basename = os.path.basename(collection.format()) - instance.data["label"] = "{0} - {1}".format( - data["name"], basename - ) - - families = data["families"] + [data["family"]] - family = list(set(valid_families) & set(families))[0] - instance.data["family"] = family - instance.data["families"] = ["output"] - instance.data["collection"] = collection - instance.data["version"] = int(version) - instance.data["publish"] = False - - collections.append(collection) diff --git a/pype/plugins/global/_publish_unused/collect_textures.py b/pype/plugins/global/_publish_unused/collect_textures.py deleted file mode 100644 index c38e911033..0000000000 --- a/pype/plugins/global/_publish_unused/collect_textures.py +++ /dev/null @@ -1,88 +0,0 @@ -import os -import re -import copy -from avalon import io -from pprint import pprint - -import pyblish.api -from avalon import api - - -texture_extensions = ['.tif', '.tiff', '.jpg', '.jpeg', '.tx', '.png', '.tga', - '.psd', '.dpx', '.hdr', '.hdri', '.exr', '.sxr', '.psb'] - - -class CollectTextures(pyblish.api.ContextPlugin): - """ - Gather all texture files in working directory, traversing whole structure. - """ - - order = pyblish.api.CollectorOrder - targets = ["texture"] - label = "Textures" - hosts = ["shell"] - - def process(self, context): - - if os.environ.get("PYPE_PUBLISH_PATHS"): - paths = os.environ["PYPE_PUBLISH_PATHS"].split(os.pathsep) - else: - cwd = context.get("workspaceDir", os.getcwd()) - paths = [cwd] - - textures = [] - for path in paths: - for dir, subdir, files in os.walk(path): - textures.extend( - os.path.join(dir, x) for x in files - if os.path.splitext(x)[1].lower() in texture_extensions) - - self.log.info("Got {} texture files.".format(len(textures))) - if len(textures) < 1: - raise RuntimeError("no textures found.") - - asset_name = os.environ.get("AVALON_ASSET") - family = 'texture' - subset = 'Main' - - project = io.find_one({'type': 'project'}) - asset = io.find_one({ - 'type': 'asset', - 'name': asset_name - }) - - context.data['project'] = project - context.data['asset'] = asset - - for tex in textures: - self.log.info("Processing: {}".format(tex)) - name, ext = os.path.splitext(tex) - simple_name = os.path.splitext(os.path.basename(tex))[0] - instance = context.create_instance(simple_name) - - instance.data.update({ - "subset": subset, - "asset": asset_name, - "label": simple_name, - "name": simple_name, - "family": family, - "families": [family, 'ftrack'], - }) - instance.data['destination_list'] = list() - instance.data['representations'] = list() - instance.data['source'] = 'pype command' - - texture_data = {} - texture_data['anatomy_template'] = 'texture' - texture_data["ext"] = ext - texture_data["label"] = simple_name - texture_data["name"] = "texture" - texture_data["stagingDir"] = os.path.dirname(tex) - texture_data["files"] = os.path.basename(tex) - texture_data["thumbnail"] = False - texture_data["preview"] = False - - instance.data["representations"].append(texture_data) - self.log.info("collected instance: {}".format(instance.data)) - - self.log.info("All collected.") diff --git a/pype/plugins/global/_publish_unused/extract_json.py b/pype/plugins/global/_publish_unused/extract_json.py deleted file mode 100644 index 8aff324574..0000000000 --- a/pype/plugins/global/_publish_unused/extract_json.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import json -import datetime -import time - -import pyblish.api -import clique - - -class ExtractJSON(pyblish.api.ContextPlugin): - """ Extract all instances to a serialized json file. """ - - order = pyblish.api.IntegratorOrder - label = "JSON" - hosts = ['maya'] - - def process(self, context): - - workspace = os.path.join( - os.path.dirname(context.data["currentFile"]), "workspace", - "instances") - - if not os.path.exists(workspace): - os.makedirs(workspace) - - output_data = [] - for instance in context: - self.log.debug(instance['data']) - - data = {} - for key, value in instance.data.iteritems(): - if isinstance(value, clique.Collection): - value = value.format() - - try: - json.dumps(value) - data[key] = value - except KeyError: - msg = "\"{0}\"".format(value) - msg += " in instance.data[\"{0}\"]".format(key) - msg += " could not be serialized." - self.log.debug(msg) - - output_data.append(data) - - timestamp = datetime.datetime.fromtimestamp( - time.time()).strftime("%Y%m%d-%H%M%S") - filename = timestamp + "_instances.json" - - with open(os.path.join(workspace, filename), "w") as outfile: - outfile.write(json.dumps(output_data, indent=4, sort_keys=True)) diff --git a/pype/plugins/global/_publish_unused/extract_quicktime.py b/pype/plugins/global/_publish_unused/extract_quicktime.py deleted file mode 100644 index 76a920b798..0000000000 --- a/pype/plugins/global/_publish_unused/extract_quicktime.py +++ /dev/null @@ -1,86 +0,0 @@ -import os -import pyblish.api -import subprocess -import clique - - -class ExtractQuicktimeEXR(pyblish.api.InstancePlugin): - """Resolve any dependency issies - - This plug-in resolves any paths which, if not updated might break - the published file. - - The order of families is important, when working with lookdev you want to - first publish the texture, update the texture paths in the nodes and then - publish the shading network. Same goes for file dependent assets. - """ - - label = "Extract Quicktime" - order = pyblish.api.ExtractorOrder - families = ["imagesequence", "render", "write", "source"] - hosts = ["shell"] - - def process(self, instance): - # fps = instance.data.get("fps") - # start = instance.data.get("startFrame") - # stagingdir = os.path.normpath(instance.data.get("stagingDir")) - # - # collected_frames = os.listdir(stagingdir) - # collections, remainder = clique.assemble(collected_frames) - # - # full_input_path = os.path.join( - # stagingdir, collections[0].format('{head}{padding}{tail}') - # ) - # self.log.info("input {}".format(full_input_path)) - # - # filename = collections[0].format('{head}') - # if not filename.endswith('.'): - # filename += "." - # movFile = filename + "mov" - # full_output_path = os.path.join(stagingdir, movFile) - # - # self.log.info("output {}".format(full_output_path)) - # - # config_data = instance.context.data['output_repre_config'] - # - # proj_name = os.environ.get('AVALON_PROJECT', '__default__') - # profile = config_data.get(proj_name, config_data['__default__']) - # - # input_args = [] - # # overrides output file - # input_args.append("-y") - # # preset's input data - # input_args.extend(profile.get('input', [])) - # # necessary input data - # input_args.append("-start_number {}".format(start)) - # input_args.append("-i {}".format(full_input_path)) - # input_args.append("-framerate {}".format(fps)) - # - # output_args = [] - # # preset's output data - # output_args.extend(profile.get('output', [])) - # # output filename - # output_args.append(full_output_path) - # mov_args = [ - # "ffmpeg", - # " ".join(input_args), - # " ".join(output_args) - # ] - # subprocess_mov = " ".join(mov_args) - # sub_proc = subprocess.Popen(subprocess_mov) - # sub_proc.wait() - # - # if not os.path.isfile(full_output_path): - # raise("Quicktime wasn't created succesfully") - # - # if "representations" not in instance.data: - # instance.data["representations"] = [] - # - # representation = { - # 'name': 'mov', - # 'ext': 'mov', - # 'files': movFile, - # "stagingDir": stagingdir, - # "preview": True - # } - # instance.data["representations"].append(representation) diff --git a/pype/plugins/global/_publish_unused/transcode.py b/pype/plugins/global/_publish_unused/transcode.py deleted file mode 100644 index 6da65e3cc7..0000000000 --- a/pype/plugins/global/_publish_unused/transcode.py +++ /dev/null @@ -1,153 +0,0 @@ -import os -import subprocess - -import pyblish.api -import filelink - - -class ExtractTranscode(pyblish.api.InstancePlugin): - """Extracts review movie from image sequence. - - Offset to get images to transcode from. - """ - - order = pyblish.api.ExtractorOrder + 0.1 - label = "Transcode" - optional = True - families = ["review"] - - def find_previous_index(self, index, indexes): - """Finds the closest previous value in a list from a value.""" - - data = [] - for i in indexes: - if i >= index: - continue - data.append(index - i) - - return indexes[data.index(min(data))] - - def process(self, instance): - - if "collection" in instance.data.keys(): - self.process_image(instance) - - if "output_path" in instance.data.keys(): - self.process_movie(instance) - - def process_image(self, instance): - - collection = instance.data.get("collection", []) - - if not list(collection): - msg = "Skipping \"{0}\" because no frames was found." - self.log.warning(msg.format(instance.data["name"])) - return - - # Temporary fill the missing frames. - missing = collection.holes() - if not collection.is_contiguous(): - pattern = collection.format("{head}{padding}{tail}") - for index in missing.indexes: - dst = pattern % index - src_index = self.find_previous_index( - index, list(collection.indexes) - ) - src = pattern % src_index - - filelink.create(src, dst) - - # Generate args. - # Has to be yuv420p for compatibility with older players and smooth - # playback. This does come with a sacrifice of more visible banding - # issues. - # -crf 18 is visually lossless. - args = [ - "ffmpeg", "-y", - "-start_number", str(min(collection.indexes)), - "-framerate", str(instance.context.data["framerate"]), - "-i", collection.format("{head}{padding}{tail}"), - "-pix_fmt", "yuv420p", - "-crf", "18", - "-timecode", "00:00:00:01", - "-vframes", - str(max(collection.indexes) - min(collection.indexes) + 1), - "-vf", - "scale=trunc(iw/2)*2:trunc(ih/2)*2", - ] - - if instance.data.get("baked_colorspace_movie"): - args = [ - "ffmpeg", "-y", - "-i", instance.data["baked_colorspace_movie"], - "-pix_fmt", "yuv420p", - "-crf", "18", - "-timecode", "00:00:00:01", - ] - - args.append(collection.format("{head}.mov")) - - self.log.debug("Executing args: {0}".format(args)) - - # Can't use subprocess.check_output, cause Houdini doesn't like that. - p = subprocess.Popen( - args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - stdin=subprocess.PIPE, - cwd=os.path.dirname(args[-1]) - ) - - output = p.communicate()[0] - - # Remove temporary frame fillers - for f in missing: - os.remove(f) - - if p.returncode != 0: - raise ValueError(output) - - self.log.debug(output) - - def process_movie(self, instance): - # Generate args. - # Has to be yuv420p for compatibility with older players and smooth - # playback. This does come with a sacrifice of more visible banding - # issues. - args = [ - "ffmpeg", "-y", - "-i", instance.data["output_path"], - "-pix_fmt", "yuv420p", - "-crf", "18", - "-timecode", "00:00:00:01", - ] - - if instance.data.get("baked_colorspace_movie"): - args = [ - "ffmpeg", "-y", - "-i", instance.data["baked_colorspace_movie"], - "-pix_fmt", "yuv420p", - "-crf", "18", - "-timecode", "00:00:00:01", - ] - - split = os.path.splitext(instance.data["output_path"]) - args.append(split[0] + "_review.mov") - - self.log.debug("Executing args: {0}".format(args)) - - # Can't use subprocess.check_output, cause Houdini doesn't like that. - 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) diff --git a/pype/plugins/nuke/_load_unused/load_alembic b/pype/plugins/nuke/_load_unused/load_alembic deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pype/plugins/nuke/_load_unused/load_camera_abc b/pype/plugins/nuke/_load_unused/load_camera_abc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pype/plugins/nuke/_load_unused/load_camera_nk b/pype/plugins/nuke/_load_unused/load_camera_nk deleted file mode 100644 index 8b13789179..0000000000 --- a/pype/plugins/nuke/_load_unused/load_camera_nk +++ /dev/null @@ -1 +0,0 @@ - diff --git a/pype/plugins/nuke/_load_unused/load_still b/pype/plugins/nuke/_load_unused/load_still deleted file mode 100644 index c2aa061c5a..0000000000 --- a/pype/plugins/nuke/_load_unused/load_still +++ /dev/null @@ -1 +0,0 @@ -# usually used for mattepainting diff --git a/pype/plugins/nuke/_publish_unused/collect_render_target.py b/pype/plugins/nuke/_publish_unused/collect_render_target.py deleted file mode 100644 index 6c04414f69..0000000000 --- a/pype/plugins/nuke/_publish_unused/collect_render_target.py +++ /dev/null @@ -1,46 +0,0 @@ -import pyblish.api - - -@pyblish.api.log -class CollectRenderTarget(pyblish.api.InstancePlugin): - """Collect families for all instances""" - - order = pyblish.api.CollectorOrder + 0.2 - label = "Collect Render Target" - hosts = ["nuke", "nukeassist"] - families = ['write'] - - def process(self, instance): - - node = instance[0] - - self.log.info('processing {}'.format(node)) - - families = [] - if instance.data.get('families'): - families += instance.data['families'] - - # set for ftrack to accept - # instance.data["families"] = ["ftrack"] - - if node["render"].value(): - # dealing with local/farm rendering - if node["render_farm"].value(): - families.append("render.farm") - else: - families.append("render.local") - else: - families.append("render.frames") - # to ignore staging dir op in integrate - instance.data['transfer'] = False - - families.append('ftrack') - - instance.data["families"] = families - - # Sort/grouped by family (preserving local index) - instance.context[:] = sorted(instance.context, key=self.sort_by_family) - - def sort_by_family(self, instance): - """Sort by family""" - return instance.data.get("families", instance.data.get("family")) diff --git a/pype/plugins/nuke/_publish_unused/submit_deadline.py b/pype/plugins/nuke/_publish_unused/submit_deadline.py deleted file mode 100644 index 8b86189425..0000000000 --- a/pype/plugins/nuke/_publish_unused/submit_deadline.py +++ /dev/null @@ -1,147 +0,0 @@ -import os -import json -import getpass - -from avalon import api -from avalon.vendor import requests - -import pyblish.api - - -class NukeSubmitDeadline(pyblish.api.InstancePlugin): - # TODO: rewrite docstring to nuke - """Submit current Comp to Deadline - - Renders are submitted to a Deadline Web Service as - supplied via the environment variable DEADLINE_REST_URL - - """ - - label = "Submit to Deadline" - order = pyblish.api.IntegratorOrder - hosts = ["nuke"] - families = ["write", "render.deadline"] - - def process(self, instance): - - context = instance.context - - key = "__hasRun{}".format(self.__class__.__name__) - if context.data.get(key, False): - return - else: - context.data[key] = True - - DEADLINE_REST_URL = api.Session.get("DEADLINE_REST_URL", - "http://localhost:8082") - assert DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" - - # Collect all saver instances in context that are to be rendered - write_instances = [] - for instance in context[:]: - if not self.families[0] in instance.data.get("families"): - # Allow only saver family instances - continue - - if not instance.data.get("publish", True): - # Skip inactive instances - continue - self.log.debug(instance.data["name"]) - write_instances.append(instance) - - if not write_instances: - raise RuntimeError("No instances found for Deadline submittion") - - hostVersion = int(context.data["hostVersion"]) - filepath = context.data["currentFile"] - filename = os.path.basename(filepath) - comment = context.data.get("comment", "") - deadline_user = context.data.get("deadlineUser", getpass.getuser()) - - # Documentation for keys available at: - # https://docs.thinkboxsoftware.com - # /products/deadline/8.0/1_User%20Manual/manual - # /manual-submission.html#job-info-file-options - payload = { - "JobInfo": { - # Top-level group name - "BatchName": filename, - - # Job name, as seen in Monitor - "Name": filename, - - # User, as seen in Monitor - "UserName": deadline_user, - - # Use a default submission pool for Nuke - "Pool": "nuke", - - "Plugin": "Nuke", - "Frames": "{start}-{end}".format( - start=int(instance.data["frameStart"]), - end=int(instance.data["frameEnd"]) - ), - - "Comment": comment, - }, - "PluginInfo": { - # Input - "FlowFile": filepath, - - # Mandatory for Deadline - "Version": str(hostVersion), - - # Render in high quality - "HighQuality": True, - - # Whether saver output should be checked after rendering - # is complete - "CheckOutput": True, - - # Proxy: higher numbers smaller images for faster test renders - # 1 = no proxy quality - "Proxy": 1, - }, - - # Mandatory for Deadline, may be empty - "AuxFiles": [] - } - - # Enable going to rendered frames from Deadline Monitor - for index, instance in enumerate(write_instances): - path = instance.data["path"] - folder, filename = os.path.split(path) - payload["JobInfo"]["OutputDirectory%d" % index] = folder - payload["JobInfo"]["OutputFilename%d" % index] = filename - - # Include critical variables with submission - keys = [ - # TODO: This won't work if the slaves don't have accesss to - # these paths, such as if slaves are running Linux and the - # submitter is on Windows. - "PYTHONPATH", - "NUKE_PATH" - # "OFX_PLUGIN_PATH", - ] - environment = dict({key: os.environ[key] for key in keys - if key in os.environ}, **api.Session) - - payload["JobInfo"].update({ - "EnvironmentKeyValue%d" % index: "{key}={value}".format( - key=key, - value=environment[key] - ) for index, key in enumerate(environment) - }) - - self.log.info("Submitting..") - self.log.info(json.dumps(payload, indent=4, sort_keys=True)) - - # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(DEADLINE_REST_URL) - response = requests.post(url, json=payload) - if not response.ok: - raise Exception(response.text) - - # Store the response for dependent job submission plug-ins - for instance in write_instances: - instance.data["deadlineSubmissionJob"] = response.json() diff --git a/pype/plugins/nuke/_publish_unused/test_instances.py b/pype/plugins/nuke/_publish_unused/test_instances.py deleted file mode 100644 index e3fcc4b8f1..0000000000 --- a/pype/plugins/nuke/_publish_unused/test_instances.py +++ /dev/null @@ -1,24 +0,0 @@ -import pyblish.api - - -class IncrementTestPlugin(pyblish.api.ContextPlugin): - """Increment current script version.""" - - order = pyblish.api.CollectorOrder + 0.5 - label = "Test Plugin" - hosts = ['nuke'] - - def process(self, context): - instances = context[:] - - prerender_check = list() - families_check = list() - for instance in instances: - if ("prerender" in str(instance)): - prerender_check.append(instance) - if instance.data.get("families", None): - families_check.append(True) - - if len(prerender_check) != len(families_check): - self.log.info(prerender_check) - self.log.info(families_check) diff --git a/pype/plugins/nuke/_publish_unused/validate_nuke_settings.py b/pype/plugins/nuke/_publish_unused/validate_nuke_settings.py deleted file mode 100644 index 441658297d..0000000000 --- a/pype/plugins/nuke/_publish_unused/validate_nuke_settings.py +++ /dev/null @@ -1,68 +0,0 @@ -import nuke -import os -import pyblish.api -from avalon import io -# TODO: add repair function - - -@pyblish.api.log -class ValidateSettingsNuke(pyblish.api.Validator): - """ Validates settings """ - - families = ['scene'] - hosts = ['nuke'] - optional = True - label = 'Settings' - - def process(self, instance): - - asset = io.find_one({"name": os.environ['AVALON_ASSET']}) - try: - avalon_resolution = asset["data"].get("resolution", '') - avalon_pixel_aspect = asset["data"].get("pixelAspect", '') - avalon_fps = asset["data"].get("fps", '') - avalon_first = asset["data"].get("frameStart", '') - avalon_last = asset["data"].get("frameEnd", '') - avalon_crop = asset["data"].get("crop", '') - except KeyError: - print( - "No resolution information found for \"{0}\".".format( - asset["name"] - ) - ) - return - - # validating first frame - local_first = nuke.root()['first_frame'].value() - msg = 'First frame is incorrect.' - msg += '\n\nLocal first: %s' % local_first - msg += '\n\nOnline first: %s' % avalon_first - assert local_first == avalon_first, msg - - # validating last frame - local_last = nuke.root()['last_frame'].value() - msg = 'Last frame is incorrect.' - msg += '\n\nLocal last: %s' % local_last - msg += '\n\nOnline last: %s' % avalon_last - assert local_last == avalon_last, msg - - # validating fps - local_fps = nuke.root()['fps'].value() - msg = 'FPS is incorrect.' - msg += '\n\nLocal fps: %s' % local_fps - msg += '\n\nOnline fps: %s' % avalon_fps - assert local_fps == avalon_fps, msg - - # validating resolution width - local_width = nuke.root().format().width() - msg = 'Width is incorrect.' - msg += '\n\nLocal width: %s' % local_width - msg += '\n\nOnline width: %s' % avalon_resolution[0] - assert local_width == avalon_resolution[0], msg - - # validating resolution width - local_height = nuke.root().format().height() - msg = 'Height is incorrect.' - msg += '\n\nLocal height: %s' % local_height - msg += '\n\nOnline height: %s' % avalon_resolution[1] - assert local_height == avalon_resolution[1], msg diff --git a/pype/plugins/nuke/_publish_unused/validate_proxy_mode.py b/pype/plugins/nuke/_publish_unused/validate_proxy_mode.py deleted file mode 100644 index a82fb16f31..0000000000 --- a/pype/plugins/nuke/_publish_unused/validate_proxy_mode.py +++ /dev/null @@ -1,33 +0,0 @@ -import nuke - -import pyblish.api - - -class RepairNukeProxyModeAction(pyblish.api.Action): - - label = "Repair" - icon = "wrench" - on = "failed" - - def process(self, context, plugin): - - nuke.root()["proxy"].setValue(0) - - -class ValidateNukeProxyMode(pyblish.api.ContextPlugin): - """Validates against having proxy mode on.""" - - order = pyblish.api.ValidatorOrder - optional = True - label = "Proxy Mode" - actions = [RepairNukeProxyModeAction] - hosts = ["nuke", "nukeassist"] - # targets = ["default", "process"] - - def process(self, context): - - msg = ( - "Proxy mode is not supported. Please disable Proxy Mode in the " - "Project settings." - ) - assert not nuke.root()["proxy"].getValue(), msg diff --git a/setup/nuke/nuke_path/atom_server.py b/setup/nuke/nuke_path/atom_server.py deleted file mode 100644 index 1742c290c1..0000000000 --- a/setup/nuke/nuke_path/atom_server.py +++ /dev/null @@ -1,54 +0,0 @@ -''' - Simple socket server using threads -''' - -import socket -import sys -import threading -import StringIO -import contextlib - -import nuke - -HOST = '' -PORT = 8888 - - -@contextlib.contextmanager -def stdoutIO(stdout=None): - old = sys.stdout - if stdout is None: - stdout = StringIO.StringIO() - sys.stdout = stdout - yield stdout - sys.stdout = old - - -def _exec(data): - with stdoutIO() as s: - exec(data) - return s.getvalue() - - -def server_start(): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind((HOST, PORT)) - s.listen(5) - - while 1: - client, address = s.accept() - try: - data = client.recv(4096) - if data: - result = nuke.executeInMainThreadWithResult(_exec, args=(data)) - client.send(str(result)) - except SystemExit: - result = self.encode('SERVER: Shutting down...') - client.send(str(result)) - raise - finally: - client.close() - -t = threading.Thread(None, server_start) -t.setDaemon(True) -t.start() diff --git a/setup/nuke/nuke_path/menu.py b/setup/nuke/nuke_path/menu.py index 7f5de6013d..15702fa364 100644 --- a/setup/nuke/nuke_path/menu.py +++ b/setup/nuke/nuke_path/menu.py @@ -1,6 +1,5 @@ import os import sys -import atom_server import KnobScripter from pype.nuke.lib import ( From aa40c8030a1ddda1efdf77b63fecda91e5050e02 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 Mar 2020 12:28:44 +0100 Subject: [PATCH 31/54] fix(nks):collection of resolution datas --- .../publish/collect_hierarchy_context.py | 14 ++++++++------ pype/plugins/nukestudio/publish/collect_plates.py | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py index 5085b9719e..ac7a58fe7e 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -161,8 +161,8 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): "asset": asset, "hierarchy": hierarchy, "parents": parents, - "width": width, - "height": height, + "resolutionWidth": width, + "resolutionHeight": height, "pixelAspect": pixel_aspect, "tasks": instance.data["tasks"] }) @@ -223,8 +223,10 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): instance.data["parents"] = s_asset_data["parents"] instance.data["hierarchy"] = s_asset_data["hierarchy"] instance.data["tasks"] = s_asset_data["tasks"] - instance.data["width"] = s_asset_data["width"] - instance.data["height"] = s_asset_data["height"] + instance.data["resolutionWidth"] = s_asset_data[ + "resolutionWidth"] + instance.data["resolutionHeight"] = s_asset_data[ + "resolutionHeight"] instance.data["pixelAspect"] = s_asset_data["pixelAspect"] # adding frame start if any on instance @@ -275,8 +277,8 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): # adding SourceResolution if Tag was present if instance.data.get("main"): in_info['custom_attributes'].update({ - "resolutionWidth": instance.data["width"], - "resolutionHeight": instance.data["height"], + "resolutionWidth": instance.data["resolutionWidth"], + "resolutionHeight": instance.data["resolutionHeight"], "pixelAspect": instance.data["pixelAspect"] }) diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index acdc5193ae..b624cf0edc 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -83,7 +83,7 @@ class CollectPlates(api.InstancePlugin): class CollectPlatesData(api.InstancePlugin): """Collect plates""" - order = api.CollectorOrder + 0.495 + order = api.CollectorOrder + 0.48 label = "Collect Plates Data" hosts = ["nukestudio"] families = ["plate"] @@ -126,7 +126,7 @@ class CollectPlatesData(api.InstancePlugin): transfer_data = [ "handleStart", "handleEnd", "sourceIn", "sourceOut", "frameStart", "frameEnd", "sourceInH", "sourceOutH", "clipIn", "clipOut", - "clipInH", "clipOutH", "asset", "track", "version", "width", "height", "pixelAspect" + "clipInH", "clipOutH", "asset", "track", "version", "resolutionWidth", "resolutionHeight", "pixelAspect" ] # pass data to version From 6d0fd510bf56c2e169cf75ea359f81d7273cfcda Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 Mar 2020 14:16:23 +0100 Subject: [PATCH 32/54] fix(global): adding collector for fps and pixel_aspect --- .../plugins/global/publish/collect_instance_anatomy_data.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/collect_instance_anatomy_data.py b/pype/plugins/global/publish/collect_instance_anatomy_data.py index 825c48dcf4..4afcac118c 100644 --- a/pype/plugins/global/publish/collect_instance_anatomy_data.py +++ b/pype/plugins/global/publish/collect_instance_anatomy_data.py @@ -108,9 +108,13 @@ class CollectInstanceAnatomyData(pyblish.api.InstancePlugin): if resolution_height: anatomy_data["resolution_height"] = resolution_height + pixel_aspect = instance.data.get("pixelAspect") + if pixel_aspect: + anatomy_data["pixel_aspect"] = float("{:0.2f}".format(pixel_aspect)) + fps = instance.data.get("fps") if resolution_height: - anatomy_data["fps"] = fps + anatomy_data["fps"] = float("{:0.2f}".format(fps)) instance.data["projectEntity"] = project_entity instance.data["assetEntity"] = asset_entity From eb261734cdc233bce29284219760cd962a02d0f9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 Mar 2020 14:16:56 +0100 Subject: [PATCH 33/54] fix(nks): passing fps and pixel_aspect to instance data for anatomy --- pype/plugins/nukestudio/publish/collect_hierarchy_context.py | 3 +++ pype/plugins/nukestudio/publish/collect_plates.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py index ac7a58fe7e..5bc9bea7dd 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -42,6 +42,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): width = int(sequence.format().width()) height = int(sequence.format().height()) pixel_aspect = sequence.format().pixelAspect() + fps = context.data["fps"] # build data for inner nukestudio project property data = { @@ -164,6 +165,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): "resolutionWidth": width, "resolutionHeight": height, "pixelAspect": pixel_aspect, + "fps": fps, "tasks": instance.data["tasks"] }) @@ -228,6 +230,7 @@ class CollectHierarchyContext(pyblish.api.ContextPlugin): instance.data["resolutionHeight"] = s_asset_data[ "resolutionHeight"] instance.data["pixelAspect"] = s_asset_data["pixelAspect"] + instance.data["fps"] = s_asset_data["fps"] # adding frame start if any on instance start_frame = s_asset_data.get("startingFrame") diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index b624cf0edc..d08f69d4bb 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -126,7 +126,7 @@ class CollectPlatesData(api.InstancePlugin): transfer_data = [ "handleStart", "handleEnd", "sourceIn", "sourceOut", "frameStart", "frameEnd", "sourceInH", "sourceOutH", "clipIn", "clipOut", - "clipInH", "clipOutH", "asset", "track", "version", "resolutionWidth", "resolutionHeight", "pixelAspect" + "clipInH", "clipOutH", "asset", "track", "version", "resolutionWidth", "resolutionHeight", "pixelAspect", "fps" ] # pass data to version From f4caf8e6f46a2b17883fa75d6c03a5e7e9084bd0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 Mar 2020 15:43:07 +0100 Subject: [PATCH 34/54] fix(nks): didnt format anatomy with hierarchy and pixel_aspect --- pype/plugins/nukestudio/publish/extract_effects.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pype/plugins/nukestudio/publish/extract_effects.py b/pype/plugins/nukestudio/publish/extract_effects.py index a8db5826b8..5c9ee97f2b 100644 --- a/pype/plugins/nukestudio/publish/extract_effects.py +++ b/pype/plugins/nukestudio/publish/extract_effects.py @@ -196,7 +196,8 @@ class ExtractVideoTracksLuts(pyblish.api.InstancePlugin): "asset": asset_name, "family": instance.data["family"], "subset": subset_name, - "version": version_number + "version": version_number, + "hierarchy": instance.data["hierarchy"] }) resolution_width = instance.data.get("resolutionWidth") @@ -207,9 +208,13 @@ class ExtractVideoTracksLuts(pyblish.api.InstancePlugin): if resolution_height: anatomy_data["resolution_height"] = resolution_height + pixel_aspect = instance.data.get("pixelAspect") + if pixel_aspect: + anatomy_data["pixel_aspect"] = float("{:0.2f}".format(pixel_aspect)) + fps = instance.data.get("fps") if resolution_height: - anatomy_data["fps"] = fps + anatomy_data["fps"] = float("{:0.2f}".format(fps)) instance.data["projectEntity"] = project_entity instance.data["assetEntity"] = asset_entity From efd9305438e6de93afe8fb25d28af419dc1cd6e3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 9 Mar 2020 17:02:37 +0100 Subject: [PATCH 35/54] status factory is initialized before checker thread starts --- pype/ftrack/ftrack_server/sub_event_status.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pype/ftrack/ftrack_server/sub_event_status.py b/pype/ftrack/ftrack_server/sub_event_status.py index 1a15a1f28d..d3e6a3d647 100644 --- a/pype/ftrack/ftrack_server/sub_event_status.py +++ b/pype/ftrack/ftrack_server/sub_event_status.py @@ -369,13 +369,6 @@ def main(args): # store socket connection object ObjectFactory.sock = sock - statuse_names = { - "main": "Main process", - "storer": "Event Storer", - "processor": "Event Processor" - } - - ObjectFactory.status_factory = StatusFactory(statuse_names) ObjectFactory.status_factory["main"].update(server_info) _returncode = 0 try: @@ -429,6 +422,13 @@ if __name__ == "__main__": signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) + statuse_names = { + "main": "Main process", + "storer": "Event Storer", + "processor": "Event Processor" + } + ObjectFactory.status_factory = StatusFactory(statuse_names) + checker_thread = OutputChecker() ObjectFactory.checker_thread = checker_thread checker_thread.start() From a206b8fe7ad2c9ce121de8ca635cce6ca54f150d Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 11 Mar 2020 17:37:57 +0100 Subject: [PATCH 36/54] hotfix: add textures family to integrator --- pype/plugins/global/publish/integrate_new.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 1d061af173..aa214f36cb 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -80,7 +80,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "matchmove", "image" "source", - "assembly" + "assembly", + "textures" ] exclude_families = ["clip"] db_representation_context_keys = [ From 02014e20cfec96ba2f188c2d21c2522dca2ebf0a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 12 Mar 2020 11:22:53 +0100 Subject: [PATCH 37/54] fix(global): maya is collecting fps as string --- .../global/publish/collect_instance_anatomy_data.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/collect_instance_anatomy_data.py b/pype/plugins/global/publish/collect_instance_anatomy_data.py index 4afcac118c..06a25b7c8a 100644 --- a/pype/plugins/global/publish/collect_instance_anatomy_data.py +++ b/pype/plugins/global/publish/collect_instance_anatomy_data.py @@ -110,11 +110,13 @@ class CollectInstanceAnatomyData(pyblish.api.InstancePlugin): pixel_aspect = instance.data.get("pixelAspect") if pixel_aspect: - anatomy_data["pixel_aspect"] = float("{:0.2f}".format(pixel_aspect)) + anatomy_data["pixel_aspect"] = float("{:0.2f}".format( + float(pixel_aspect))) fps = instance.data.get("fps") - if resolution_height: - anatomy_data["fps"] = float("{:0.2f}".format(fps)) + if fps: + anatomy_data["fps"] = float("{:0.2f}".format( + float(fps))) instance.data["projectEntity"] = project_entity instance.data["assetEntity"] = asset_entity From 6caa339d8254a65583e032cff9edfa90df82164b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 Mar 2020 12:04:46 +0100 Subject: [PATCH 38/54] feat(global): adding no handles to extract burnin --- pype/plugins/global/publish/extract_burnin.py | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 008bebb271..3d5de28153 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -4,7 +4,6 @@ import copy import pype.api import pyblish -from pypeapp import config class ExtractBurnin(pype.api.Extractor): @@ -30,6 +29,8 @@ class ExtractBurnin(pype.api.Extractor): 'version', instance.context.data.get('version')) frame_start = int(instance.data.get("frameStart") or 0) frame_end = int(instance.data.get("frameEnd") or 1) + handle_start = instance.data.get("handleStart") + handle_end = instance.data.get("handleEnd") duration = frame_end - frame_start + 1 prep_data = copy.deepcopy(instance.data["anatomyData"]) @@ -59,6 +60,9 @@ class ExtractBurnin(pype.api.Extractor): is_sequence = "sequence" in repre.get("tags", []) + # no handles switch from profile tags + no_handles = "no-handles" in repre.get("tags", []) + stagingdir = repre["stagingDir"] filename = "{0}".format(repre["files"]) @@ -90,17 +94,32 @@ class ExtractBurnin(pype.api.Extractor): filled_anatomy = anatomy.format_all(_prep_data) _prep_data["anatomy"] = filled_anatomy.get_solved() + # copy frame range variables + frame_start_cp = frame_start + frame_end_cp = frame_end + duration_cp = duration + + if no_handles: + frame_start_cp = frame_start + handle_start + frame_end_cp = frame_end - handle_end + duration_cp = frame_end_cp - frame_start_cp + 1 + _prep_data.update({ + "frame_start": frame_start_cp, + "frame_end": frame_end_cp, + "duration": duration_cp, + }) + # dealing with slates - slate_frame_start = frame_start - slate_frame_end = frame_end - slate_duration = duration + slate_frame_start = frame_start_cp + slate_frame_end = frame_end_cp + slate_duration = duration_cp # exception for slate workflow if ("slate" in instance.data["families"]): if "slate-frame" in repre.get("tags", []): - slate_frame_start = frame_start - 1 - slate_frame_end = frame_end - slate_duration = duration + 1 + slate_frame_start = frame_start_cp - 1 + slate_frame_end = frame_end_cp + slate_duration = duration_cp + 1 self.log.debug("__1 slate_frame_start: {}".format(slate_frame_start)) From 0123c8c5776fc97887a7202082c4d27f84253004 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 Mar 2020 12:11:35 +0100 Subject: [PATCH 39/54] feat(global): extract review no handles tag --- pype/plugins/global/publish/extract_review.py | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index f5dba108c5..c7f286c3e2 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -31,12 +31,17 @@ class ExtractReview(pyblish.api.InstancePlugin): output_profiles = self.outputs or {} inst_data = instance.data - fps = inst_data.get("fps") - start_frame = inst_data.get("frameStart") - resolution_width = inst_data.get("resolutionWidth", to_width) - resolution_height = inst_data.get("resolutionHeight", to_height) + fps = float(inst_data.get("fps")) + frame_start = inst_data.get("frameStart") + frame_end = inst_data.get("frameEnd") + handle_start = inst_data.get("handleStart") + handle_end = inst_data.get("handleEnd") pixel_aspect = inst_data.get("pixelAspect", 1) self.log.debug("Families In: `{}`".format(inst_data["families"])) + self.log.debug("__ frame_start: {}".format(frame_start)) + self.log.debug("__ frame_end: {}".format(frame_end)) + self.log.debug("__ handle_start: {}".format(handle_start)) + self.log.debug("__ handle_end: {}".format(handle_end)) # get representation and loop them representations = inst_data["representations"] @@ -73,6 +78,9 @@ class ExtractReview(pyblish.api.InstancePlugin): is_sequence = ("sequence" in p_tags) and (ext in ( "png", "jpg", "jpeg")) + # no handles switch from profile tags + no_handles = "no-handles" in p_tags + self.log.debug("Profile name: {}".format(name)) if not ext: @@ -142,6 +150,7 @@ class ExtractReview(pyblish.api.InstancePlugin): self.log.info("new_tags: `{}`".format(new_tags)) input_args = [] + output_args = [] # overrides output file input_args.append("-y") @@ -152,12 +161,20 @@ class ExtractReview(pyblish.api.InstancePlugin): # necessary input data # adds start arg only if image sequence if isinstance(repre["files"], list): + if frame_start != repre.get("detectedStart", frame_start): + frame_start = repre.get("detectedStart") + + # exclude handle if no handles defined + if no_handles: + frame_start += handle_start - if start_frame != repre.get("detectedStart", start_frame): - start_frame = repre.get("detectedStart") input_args.append( "-start_number {0} -framerate {1}".format( - start_frame, fps)) + frame_start, fps)) + else: + if no_handles: + start_sec = float(handle_start) / fps + input_args.append("-ss {:0.2f}".format(start_sec)) input_args.append("-i {}".format(full_input_path)) @@ -191,7 +208,6 @@ class ExtractReview(pyblish.api.InstancePlugin): ] ) - output_args = [] codec_args = profile.get('codec', []) output_args.extend(codec_args) # preset's output data @@ -238,6 +254,13 @@ class ExtractReview(pyblish.api.InstancePlugin): # In case audio is longer than video. output_args.append("-shortest") + if no_handles: + duration_sec = float( + (frame_end - ( + frame_start + handle_start + ) + 1) - handle_end) / fps + output_args.append("-t {:0.2f}".format(duration_sec)) + # output filename output_args.append(full_output_path) @@ -321,6 +344,7 @@ class ExtractReview(pyblish.api.InstancePlugin): self.log.debug( "_ output_args: `{}`".format(output_args)) + if is_sequence: stg_dir = os.path.dirname(full_output_path) @@ -358,7 +382,10 @@ class ExtractReview(pyblish.api.InstancePlugin): "stagingDir": stg_dir, "files": os.listdir(stg_dir) }) - + if no_handles: + repre_new.update({ + "outputName": name + "_noHandles" + }) if repre_new.get('preview'): repre_new.pop("preview") if repre_new.get('thumbnail'): From 5c589dd0232accf72da96ea9b65e31657cb81881 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 Mar 2020 12:34:08 +0100 Subject: [PATCH 40/54] feat(global): delivery resolution to preset - also fixes of reformating --- pype/plugins/global/publish/extract_review.py | 82 ++++++++++++------- .../global/publish/extract_review_slate.py | 33 +++++--- pype/plugins/nuke/publish/collect_writes.py | 1 - 3 files changed, 75 insertions(+), 41 deletions(-) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index f5dba108c5..b7b6efafb8 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -23,18 +23,21 @@ class ExtractReview(pyblish.api.InstancePlugin): outputs = {} ext_filter = [] + to_width = 1920 + to_height = 1080 def process(self, instance): - to_width = 1920 - to_height = 1080 output_profiles = self.outputs or {} inst_data = instance.data - fps = inst_data.get("fps") - start_frame = inst_data.get("frameStart") - resolution_width = inst_data.get("resolutionWidth", to_width) - resolution_height = inst_data.get("resolutionHeight", to_height) + fps = float(inst_data.get("fps")) + frame_start = inst_data.get("frameStart") + frame_end = inst_data.get("frameEnd") + handle_start = inst_data.get("handleStart") + handle_end = inst_data.get("handleEnd") + resolution_width = inst_data.get("resolutionWidth", self.to_width) + resolution_height = inst_data.get("resolutionHeight", self.to_height) pixel_aspect = inst_data.get("pixelAspect", 1) self.log.debug("Families In: `{}`".format(inst_data["families"])) @@ -198,30 +201,42 @@ class ExtractReview(pyblish.api.InstancePlugin): output_args.extend(profile.get('output', [])) # defining image ratios - resolution_ratio = float(resolution_width / ( - resolution_height * pixel_aspect)) - delivery_ratio = float(to_width) / float(to_height) - self.log.debug(resolution_ratio) - self.log.debug(delivery_ratio) + resolution_ratio = (float(resolution_width) * pixel_aspect) / resolution_height + delivery_ratio = float(self.to_width) / float(self.to_height) + self.log.debug( + "__ resolution_ratio: `{}`".format(resolution_ratio)) + self.log.debug( + "__ delivery_ratio: `{}`".format(delivery_ratio)) # get scale factor - scale_factor = to_height / ( + scale_factor = float(self.to_height) / ( resolution_height * pixel_aspect) - self.log.debug(scale_factor) + + # shorten two decimals long float number for testing conditions + resolution_ratio_test = float( + "{:0.2f}".format(resolution_ratio)) + delivery_ratio_test = float( + "{:0.2f}".format(delivery_ratio)) + + if resolution_ratio_test < delivery_ratio_test: + scale_factor = float(self.to_width) / ( + resolution_width * pixel_aspect) + + self.log.debug("__ scale_factor: `{}`".format(scale_factor)) # letter_box lb = profile.get('letter_box', 0) if lb != 0: - ffmpet_width = to_width - ffmpet_height = to_height + ffmpeg_width = self.to_width + ffmpeg_height = self.to_height if "reformat" not in p_tags: lb /= pixel_aspect - if resolution_ratio != delivery_ratio: - ffmpet_width = resolution_width - ffmpet_height = int( + if resolution_ratio_test != delivery_ratio_test: + ffmpeg_width = resolution_width + ffmpeg_height = int( resolution_height * pixel_aspect) else: - if resolution_ratio != delivery_ratio: + if resolution_ratio_test != delivery_ratio_test: lb /= scale_factor else: lb /= pixel_aspect @@ -233,7 +248,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "c=black,drawbox=0:ih-round((ih-(iw*(" "1/{2})))/2):iw:round((ih-(iw*(1/{2})))" "/2):t=fill:c=black").format( - ffmpet_width, ffmpet_height, lb)) + ffmpeg_width, ffmpeg_height, lb)) # In case audio is longer than video. output_args.append("-shortest") @@ -252,24 +267,26 @@ class ExtractReview(pyblish.api.InstancePlugin): # scaling none square pixels and 1920 width if "reformat" in p_tags: - if resolution_ratio < delivery_ratio: + if resolution_ratio_test < delivery_ratio_test: self.log.debug("lower then delivery") - width_scale = int(to_width * scale_factor) + width_scale = int(self.to_width * scale_factor) width_half_pad = int(( - to_width - width_scale)/2) - height_scale = to_height + self.to_width - width_scale)/2) + height_scale = self.to_height height_half_pad = 0 else: self.log.debug("heigher then delivery") - width_scale = to_width + width_scale = self.to_width width_half_pad = 0 - scale_factor = float(to_width) / float( - resolution_width) - self.log.debug(scale_factor) + scale_factor = float(self.to_width) / (float( + resolution_width) * pixel_aspect) + self.log.debug( + "__ scale_factor: `{}`".format( + scale_factor)) height_scale = int( resolution_height * scale_factor) height_half_pad = int( - (to_height - height_scale)/2) + (self.to_height - height_scale)/2) self.log.debug( "__ width_scale: `{}`".format(width_scale)) @@ -287,7 +304,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "scale={0}x{1}:flags=lanczos," "pad={2}:{3}:{4}:{5}:black,setsar=1" ).format(width_scale, height_scale, - to_width, to_height, + self.to_width, self.to_height, width_half_pad, height_half_pad ) @@ -372,6 +389,11 @@ class ExtractReview(pyblish.api.InstancePlugin): if "delete" in repre.get("tags", []): representations_new.remove(repre) + instance.data.update({ + "reviewToWidth": self.to_width, + "reviewToHeight": self.to_height + }) + self.log.debug( "new representations: {}".format(representations_new)) instance.data["representations"] = representations_new diff --git a/pype/plugins/global/publish/extract_review_slate.py b/pype/plugins/global/publish/extract_review_slate.py index 699ed4a5eb..8c33a0d853 100644 --- a/pype/plugins/global/publish/extract_review_slate.py +++ b/pype/plugins/global/publish/extract_review_slate.py @@ -24,24 +24,36 @@ class ExtractReviewSlate(pype.api.Extractor): slate_path = inst_data.get("slateFrame") ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") - to_width = 1920 - to_height = 1080 + # values are set in ExtractReview + to_width = inst_data["reviewToWidth"] + to_height = inst_data["reviewToHeight"] + resolution_width = inst_data.get("resolutionWidth", to_width) resolution_height = inst_data.get("resolutionHeight", to_height) pixel_aspect = inst_data.get("pixelAspect", 1) fps = inst_data.get("fps") # defining image ratios - resolution_ratio = float(resolution_width / ( - resolution_height * pixel_aspect)) + resolution_ratio = (float(resolution_width) * pixel_aspect) / resolution_height delivery_ratio = float(to_width) / float(to_height) - self.log.debug(resolution_ratio) - self.log.debug(delivery_ratio) + self.log.debug("__ resolution_ratio: `{}`".format(resolution_ratio)) + self.log.debug("__ delivery_ratio: `{}`".format(delivery_ratio)) # get scale factor - scale_factor = to_height / ( + scale_factor = float(to_height) / ( resolution_height * pixel_aspect) - self.log.debug(scale_factor) + + # shorten two decimals long float number for testing conditions + resolution_ratio_test = float( + "{:0.2f}".format(resolution_ratio)) + delivery_ratio_test = float( + "{:0.2f}".format(delivery_ratio)) + + if resolution_ratio_test < delivery_ratio_test: + scale_factor = float(to_width) / ( + resolution_width * pixel_aspect) + + self.log.debug("__ scale_factor: `{}`".format(scale_factor)) for i, repre in enumerate(inst_data["representations"]): _remove_at_end = [] @@ -95,7 +107,7 @@ class ExtractReviewSlate(pype.api.Extractor): # scaling none square pixels and 1920 width if "reformat" in p_tags: - if resolution_ratio < delivery_ratio: + if resolution_ratio_test < delivery_ratio_test: self.log.debug("lower then delivery") width_scale = int(to_width * scale_factor) width_half_pad = int(( @@ -106,7 +118,8 @@ class ExtractReviewSlate(pype.api.Extractor): self.log.debug("heigher then delivery") width_scale = to_width width_half_pad = 0 - scale_factor = float(to_width) / float(resolution_width) + scale_factor = float(to_width) / (float( + resolution_width) * pixel_aspect) self.log.debug(scale_factor) height_scale = int( resolution_height * scale_factor) diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index 993b8574f5..a547fd70bd 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -46,7 +46,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): ) if node["use_limit"].getValue(): - handles = 0 first_frame = int(node["first"].getValue()) last_frame = int(node["last"].getValue()) From 787e9d1295dfa1a7a115d22f14fee8e52dfe8c93 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 Mar 2020 14:31:01 +0100 Subject: [PATCH 41/54] feat(nks): optional version sync with project workfile --- .../nukestudio/publish/collect_clips.py | 4 +--- .../publish/collect_instance_version.py | 20 +++++++++++++++++++ .../nukestudio/publish/collect_plates.py | 9 ++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 pype/plugins/nukestudio/publish/collect_instance_version.py diff --git a/pype/plugins/nukestudio/publish/collect_clips.py b/pype/plugins/nukestudio/publish/collect_clips.py index b8654b0784..6a1dad9a6d 100644 --- a/pype/plugins/nukestudio/publish/collect_clips.py +++ b/pype/plugins/nukestudio/publish/collect_clips.py @@ -18,7 +18,6 @@ class CollectClips(api.ContextPlugin): context.data["assetsShared"] = dict() projectdata = context.data["projectEntity"]["data"] - version = context.data.get("version", "001") sequence = context.data.get("activeSequence") selection = context.data.get("selection") @@ -108,8 +107,7 @@ class CollectClips(api.ContextPlugin): "family": "clip", "families": [], "handleStart": projectdata.get("handleStart", 0), - "handleEnd": projectdata.get("handleEnd", 0), - "version": int(version)}) + "handleEnd": projectdata.get("handleEnd", 0)}) instance = context.create_instance(**data) diff --git a/pype/plugins/nukestudio/publish/collect_instance_version.py b/pype/plugins/nukestudio/publish/collect_instance_version.py new file mode 100644 index 0000000000..3e2eb8e8f8 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_instance_version.py @@ -0,0 +1,20 @@ +from pyblish import api + +class CollectInstanceVersion(api.InstancePlugin): + """ Collecting versions of Hiero project into instances + + If activated then any subset version is created in + version of the actual project. + """ + + order = api.CollectorOrder + 0.015 + label = "Collect Instance Version" + + optional = True + active = True + + def process(self, instance): + version = instance.context.data.get("version", "001") + instance.data.update({ + "version": int(version) + }) diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index d08f69d4bb..4ed281f0ee 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -126,7 +126,7 @@ class CollectPlatesData(api.InstancePlugin): transfer_data = [ "handleStart", "handleEnd", "sourceIn", "sourceOut", "frameStart", "frameEnd", "sourceInH", "sourceOutH", "clipIn", "clipOut", - "clipInH", "clipOutH", "asset", "track", "version", "resolutionWidth", "resolutionHeight", "pixelAspect", "fps" + "clipInH", "clipOutH", "asset", "track", "resolutionWidth", "resolutionHeight", "pixelAspect", "fps" ] # pass data to version @@ -141,6 +141,13 @@ class CollectPlatesData(api.InstancePlugin): "fps": instance.context.data["fps"] }) + version = instance.data.get("version") + if version: + version_data.update({ + "version": version + }) + + try: basename, ext = os.path.splitext(source_file) head, padding = os.path.splitext(basename) From fa4c19083d5e0d48887b2a8bf3df506ccb792205 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 Mar 2020 14:35:03 +0100 Subject: [PATCH 42/54] fix(nks): no need to have class activation switcher - it will be controlled by presets --- pype/plugins/nukestudio/publish/collect_instance_version.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pype/plugins/nukestudio/publish/collect_instance_version.py b/pype/plugins/nukestudio/publish/collect_instance_version.py index 3e2eb8e8f8..82cbf201d8 100644 --- a/pype/plugins/nukestudio/publish/collect_instance_version.py +++ b/pype/plugins/nukestudio/publish/collect_instance_version.py @@ -1,5 +1,6 @@ from pyblish import api + class CollectInstanceVersion(api.InstancePlugin): """ Collecting versions of Hiero project into instances @@ -10,9 +11,6 @@ class CollectInstanceVersion(api.InstancePlugin): order = api.CollectorOrder + 0.015 label = "Collect Instance Version" - optional = True - active = True - def process(self, instance): version = instance.context.data.get("version", "001") instance.data.update({ From 569818a25db263b3dbdd59c1b7d952a939744e92 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 Mar 2020 14:42:36 +0100 Subject: [PATCH 43/54] fix(nks): moving order just behind Collect Clips --- pype/plugins/nukestudio/publish/collect_instance_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/collect_instance_version.py b/pype/plugins/nukestudio/publish/collect_instance_version.py index 82cbf201d8..b79ccbdf54 100644 --- a/pype/plugins/nukestudio/publish/collect_instance_version.py +++ b/pype/plugins/nukestudio/publish/collect_instance_version.py @@ -8,7 +8,7 @@ class CollectInstanceVersion(api.InstancePlugin): version of the actual project. """ - order = api.CollectorOrder + 0.015 + order = api.CollectorOrder + 0.011 label = "Collect Instance Version" def process(self, instance): From 76cb3b37e27b70840b999ceff69df02551078ef5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 Mar 2020 16:42:25 +0100 Subject: [PATCH 44/54] fix(global): adding back resolution attribtes wrong commitment had erased them --- pype/plugins/global/publish/extract_review.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index c7f286c3e2..81d7e1668a 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -37,6 +37,8 @@ class ExtractReview(pyblish.api.InstancePlugin): handle_start = inst_data.get("handleStart") handle_end = inst_data.get("handleEnd") pixel_aspect = inst_data.get("pixelAspect", 1) + resolution_width = inst_data.get("resolutionWidth", self.to_width) + resolution_height = inst_data.get("resolutionHeight", self.to_height) self.log.debug("Families In: `{}`".format(inst_data["families"])) self.log.debug("__ frame_start: {}".format(frame_start)) self.log.debug("__ frame_end: {}".format(frame_end)) @@ -344,7 +346,6 @@ class ExtractReview(pyblish.api.InstancePlugin): self.log.debug( "_ output_args: `{}`".format(output_args)) - if is_sequence: stg_dir = os.path.dirname(full_output_path) From d484e5108c034edcc6d84de6038a23e10753f481 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 Mar 2020 18:02:52 +0100 Subject: [PATCH 45/54] fix(global): self.to_width and height is in another PR --- pype/plugins/global/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 81d7e1668a..d03de6ad61 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -37,8 +37,8 @@ class ExtractReview(pyblish.api.InstancePlugin): handle_start = inst_data.get("handleStart") handle_end = inst_data.get("handleEnd") pixel_aspect = inst_data.get("pixelAspect", 1) - resolution_width = inst_data.get("resolutionWidth", self.to_width) - resolution_height = inst_data.get("resolutionHeight", self.to_height) + resolution_width = inst_data.get("resolutionWidth", to_width) + resolution_height = inst_data.get("resolutionHeight", to_height) self.log.debug("Families In: `{}`".format(inst_data["families"])) self.log.debug("__ frame_start: {}".format(frame_start)) self.log.debug("__ frame_end: {}".format(frame_end)) From 7244f78b66533a5e59dd4abc8df34b364fb07171 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 Mar 2020 18:04:59 +0100 Subject: [PATCH 46/54] feat(nuke): adding icon for pype favorite folders --- res/icons/folder-favorite.png | Bin 0 -> 7008 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 res/icons/folder-favorite.png diff --git a/res/icons/folder-favorite.png b/res/icons/folder-favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..198b289e9ed39e6f8e13b6debf5aee857039a032 GIT binary patch literal 7008 zcmaJ_XH-*Lus(p1jw1C+vw}!fQKYLhk*Mrn zP>c@d{}ymU+okrNVpIC##aHeHMl#uRDV)Yn5ATQkFKF~hjia{WF1|Zo9P-reO2-FW zs+i=FrlgQeB?)J(<&BF~8m|YHI{d@>K5)3yG}ey&3iv!Ok=n^)xLkiIjC4VA=w(RNOI3UmoSJ-sf+#ZgA!>3L@%^r?No`;GK z2MSNK?RlTX{`i)VEJonR(e{pmg`d-sm9!pmZDt7i6N^7V#_$K7(r8H!=>iw)UWF1n zWBmkSx!1wNuBA2AwITi+KdB1GROGJ&>wR%c#echOrvQ6?u|w9}>CpgQsztBHH

XIfm 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 Date: Fri, 13 Mar 2020 19:24:07 +0100 Subject: [PATCH 47/54] feat(nuke): adding context_favorite --- pype/nuke/utils.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pype/nuke/utils.py b/pype/nuke/utils.py index 7583221696..c7f98efaea 100644 --- a/pype/nuke/utils.py +++ b/pype/nuke/utils.py @@ -3,6 +3,23 @@ import nuke from avalon.nuke import lib as anlib +def set_context_favorites(favorites={}): + """ Addig favorite folders to nuke's browser + + Argumets: + favorites (dict): couples of {name:path} + """ + dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + icon_path = os.path.join(dir, 'res', 'icons', 'folder-favorite.png') + + for name, path in favorites.items(): + nuke.addFavoriteDir( + name, + path, + nuke.IMAGE | nuke.SCRIPT | nuke.GEO, + icon=icon_path) + + def get_node_outputs(node): ''' Return a dictionary of the nodes and pipes that are connected to node From 13d1e6bf4261e1b9654b01956a37655b07311d00 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 13 Mar 2020 19:24:45 +0100 Subject: [PATCH 48/54] feat(nuke): adding bookmarks --- pype/nuke/__init__.py | 2 +- pype/nuke/lib.py | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/pype/nuke/__init__.py b/pype/nuke/__init__.py index f1f87e40c8..e775468996 100644 --- a/pype/nuke/__init__.py +++ b/pype/nuke/__init__.py @@ -93,11 +93,11 @@ def install(): # Set context settings. nuke.addOnCreate(workfile_settings.set_context_settings, nodeClass="Root") + nuke.addOnCreate(workfile_settings.set_favorites, nodeClass="Root") menu.install() - def launch_workfiles_app(): '''Function letting start workfiles after start of host ''' diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index dedc42fa1d..3130717a75 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -15,13 +15,12 @@ import nuke from .presets import ( get_colorspace_preset, get_node_dataflow_preset, - get_node_colorspace_preset -) - -from .presets import ( + get_node_colorspace_preset, get_anatomy ) +from .utils import set_context_favorites + from pypeapp import Logger log = Logger().get_logger(__name__, "nuke") @@ -944,6 +943,27 @@ class WorkfileSettings(object): # add colorspace menu item self.set_colorspace() + def set_favorites(self): + projects_root = os.getenv("AVALON_PROJECTS") + work_dir = os.getenv("AVALON_WORKDIR") + asset = os.getenv("AVALON_ASSET") + project = os.getenv("AVALON_PROJECT") + hierarchy = os.getenv("AVALON_HIERARCHY") + favorite_items = OrderedDict() + + # project + favorite_items.update({"Projects root": projects_root}) + favorite_items.update({"Project dir": os.path.join( + projects_root, project).replace("\\", "/")}) + # shot + favorite_items.update({"Shot dir": os.path.join( + projects_root, project, + hierarchy, asset).replace("\\", "/")}) + # workdir + favorite_items.update({"Work dir": work_dir}) + + set_context_favorites(favorite_items) + def get_hierarchical_attr(entity, attr, default=None): attr_parts = attr.split('.') From ba55689b4339212e545a4c5da88800f5d362bd2a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 13 Mar 2020 20:02:06 +0100 Subject: [PATCH 49/54] fall back to context handles if not present in instance --- pype/plugins/global/publish/collect_avalon_entities.py | 5 +++++ pype/plugins/global/publish/extract_burnin.py | 8 ++++++-- pype/plugins/global/publish/extract_review.py | 7 +++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/collect_avalon_entities.py b/pype/plugins/global/publish/collect_avalon_entities.py index a429b3fc84..a4f14421f2 100644 --- a/pype/plugins/global/publish/collect_avalon_entities.py +++ b/pype/plugins/global/publish/collect_avalon_entities.py @@ -45,3 +45,8 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): context.data["projectEntity"] = project_entity context.data["assetEntity"] = asset_entity + + data = asset_entity['data'] + context.data['handles'] = int(data.get("handles", 0)) + context.data["handleStart"] = int(data.get( "handleStart", 0)) + context.data["handleEnd"] = int(data.get("handleEnd", 0)) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 3d5de28153..faecbb47a7 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -25,12 +25,16 @@ class ExtractBurnin(pype.api.Extractor): if "representations" not in instance.data: raise RuntimeError("Burnin needs already created mov to work on.") + context_data = instance.context.data + version = instance.data.get( 'version', instance.context.data.get('version')) frame_start = int(instance.data.get("frameStart") or 0) frame_end = int(instance.data.get("frameEnd") or 1) - handle_start = instance.data.get("handleStart") - handle_end = instance.data.get("handleEnd") + handle_start = instance.data.get("handleStart", + context_data.get("handleStart")) + handle_end = instance.data.get("handleEnd", + context_data.get("handleEnd")) duration = frame_end - frame_start + 1 prep_data = copy.deepcopy(instance.data["anatomyData"]) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index d03de6ad61..7f88a89004 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -31,11 +31,14 @@ class ExtractReview(pyblish.api.InstancePlugin): output_profiles = self.outputs or {} inst_data = instance.data + context_data = instance.context.data fps = float(inst_data.get("fps")) frame_start = inst_data.get("frameStart") frame_end = inst_data.get("frameEnd") - handle_start = inst_data.get("handleStart") - handle_end = inst_data.get("handleEnd") + handle_start = inst_data.get("handleStart", + context_data.get("handleStart")) + handle_end = inst_data.get("handleEnd", + context_data.get("handleEnd")) pixel_aspect = inst_data.get("pixelAspect", 1) resolution_width = inst_data.get("resolutionWidth", to_width) resolution_height = inst_data.get("resolutionHeight", to_height) From 300ea97f1ecbce5c6f73761cae469b948b862af8 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 13 Mar 2020 20:32:29 +0100 Subject: [PATCH 50/54] move handles collection to global plugins --- .../global/publish/collect_avalon_entities.py | 6 ++--- .../nuke/publish/collect_asset_info.py | 25 ------------------- 2 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 pype/plugins/nuke/publish/collect_asset_info.py diff --git a/pype/plugins/global/publish/collect_avalon_entities.py b/pype/plugins/global/publish/collect_avalon_entities.py index a4f14421f2..20899361c5 100644 --- a/pype/plugins/global/publish/collect_avalon_entities.py +++ b/pype/plugins/global/publish/collect_avalon_entities.py @@ -30,7 +30,7 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): assert project_entity, ( "Project '{0}' was not found." ).format(project_name) - self.log.debug("Collected Project entity \"{}\"".format(project_entity)) + self.log.debug("Collected Project \"{}\"".format(project_entity)) asset_entity = io.find_one({ "type": "asset", @@ -41,12 +41,12 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): "No asset found by the name '{0}' in project '{1}'" ).format(asset_name, project_name) - self.log.debug("Collected Asset entity \"{}\"".format(asset_entity)) + self.log.debug("Collected Asset \"{}\"".format(asset_entity)) context.data["projectEntity"] = project_entity context.data["assetEntity"] = asset_entity data = asset_entity['data'] context.data['handles'] = int(data.get("handles", 0)) - context.data["handleStart"] = int(data.get( "handleStart", 0)) + context.data["handleStart"] = int(data.get("handleStart", 0)) context.data["handleEnd"] = int(data.get("handleEnd", 0)) diff --git a/pype/plugins/nuke/publish/collect_asset_info.py b/pype/plugins/nuke/publish/collect_asset_info.py deleted file mode 100644 index 8a8791ec36..0000000000 --- a/pype/plugins/nuke/publish/collect_asset_info.py +++ /dev/null @@ -1,25 +0,0 @@ -from avalon import api, io -import pyblish.api - - -class CollectAssetInfo(pyblish.api.ContextPlugin): - """Collect framerate.""" - - order = pyblish.api.CollectorOrder - label = "Collect Asset Info" - hosts = [ - "nuke", - "nukeassist" - ] - - def process(self, context): - asset_data = io.find_one({ - "type": "asset", - "name": api.Session["AVALON_ASSET"] - }) - self.log.info("asset_data: {}".format(asset_data)) - - context.data['handles'] = int(asset_data["data"].get("handles", 0)) - context.data["handleStart"] = int(asset_data["data"].get( - "handleStart", 0)) - context.data["handleEnd"] = int(asset_data["data"].get("handleEnd", 0)) From 7bd4182c30243ecb0f7e3803eb3157f62859f658 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 13 Mar 2020 20:32:51 +0100 Subject: [PATCH 51/54] remove handles from ftrack frame range --- pype/plugins/global/publish/extract_review.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 7f88a89004..fa29fd2fe0 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -12,7 +12,8 @@ class ExtractReview(pyblish.api.InstancePlugin): otherwise the representation is ignored. All new represetnations are created and encoded by ffmpeg following - presets found in `pype-config/presets/plugins/global/publish.json:ExtractReview:outputs`. To change the file extension + presets found in `pype-config/presets/plugins/global/ + publish.json:ExtractReview:outputs`. To change the file extension filter values use preset's attributes `ext_filter` """ @@ -171,7 +172,8 @@ class ExtractReview(pyblish.api.InstancePlugin): # exclude handle if no handles defined if no_handles: - frame_start += handle_start + frame_start_no_handles = frame_start + handle_start + frame_end_no_handles = frame_end - handle_end input_args.append( "-start_number {0} -framerate {1}".format( @@ -180,6 +182,8 @@ class ExtractReview(pyblish.api.InstancePlugin): if no_handles: start_sec = float(handle_start) / fps input_args.append("-ss {:0.2f}".format(start_sec)) + frame_start_no_handles += handle_start + frame_end_no_handles -= handle_end input_args.append("-i {}".format(full_input_path)) @@ -379,7 +383,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "codec": codec_args, "_profile": profile, "resolutionHeight": resolution_height, - "resolutionWidth": resolution_width, + "resolutionWidth": resolution_width }) if is_sequence: repre_new.update({ @@ -388,7 +392,9 @@ class ExtractReview(pyblish.api.InstancePlugin): }) if no_handles: repre_new.update({ - "outputName": name + "_noHandles" + "outputName": name + "_noHandles", + "startFrameReview": frame_start_no_handles, + "endFrameReview": frame_end_no_handles }) if repre_new.get('preview'): repre_new.pop("preview") From 47f04e0fbaf3acf2f8cc1302674573f1aa3c6f6a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 13 Mar 2020 20:41:28 +0100 Subject: [PATCH 52/54] fix frame range on review --- pype/plugins/global/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index fa29fd2fe0..23e582edd2 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -182,8 +182,8 @@ class ExtractReview(pyblish.api.InstancePlugin): if no_handles: start_sec = float(handle_start) / fps input_args.append("-ss {:0.2f}".format(start_sec)) - frame_start_no_handles += handle_start - frame_end_no_handles -= handle_end + frame_start_no_handles = frame_start + handle_start + frame_end_no_handles = frame_end - handle_end input_args.append("-i {}".format(full_input_path)) From e86f1725bfbcdddad71965ca9a30233238c7886a Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Sat, 14 Mar 2020 21:56:12 +0100 Subject: [PATCH 53/54] add new icon and remove projects folder --- pype/nuke/lib.py | 1 - pype/nuke/utils.py | 2 +- res/icons/folder-favorite2.png | Bin 0 -> 22430 bytes res/icons/folder-favorite3.png | Bin 0 -> 7957 bytes 4 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 res/icons/folder-favorite2.png create mode 100644 res/icons/folder-favorite3.png diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 3130717a75..3bbd277ae6 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -952,7 +952,6 @@ class WorkfileSettings(object): favorite_items = OrderedDict() # project - favorite_items.update({"Projects root": projects_root}) favorite_items.update({"Project dir": os.path.join( projects_root, project).replace("\\", "/")}) # shot diff --git a/pype/nuke/utils.py b/pype/nuke/utils.py index c7f98efaea..aa5bc1077e 100644 --- a/pype/nuke/utils.py +++ b/pype/nuke/utils.py @@ -10,7 +10,7 @@ def set_context_favorites(favorites={}): favorites (dict): couples of {name:path} """ dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - icon_path = os.path.join(dir, 'res', 'icons', 'folder-favorite.png') + icon_path = os.path.join(dir, 'res', 'icons', 'folder-favorite3.png') for name, path in favorites.items(): nuke.addFavoriteDir( diff --git a/res/icons/folder-favorite2.png b/res/icons/folder-favorite2.png new file mode 100644 index 0000000000000000000000000000000000000000..91bc3f0fbedc10687402390cf90e285f3394e2c0 GIT binary patch literal 22430 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 Date: Sat, 14 Mar 2020 22:41:29 +0100 Subject: [PATCH 54/54] call to_heigh and to_width vars from self. --- pype/plugins/global/publish/extract_review.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index f67aa0ae94..7b5aba818c 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -41,8 +41,8 @@ class ExtractReview(pyblish.api.InstancePlugin): handle_end = inst_data.get("handleEnd", context_data.get("handleEnd")) pixel_aspect = inst_data.get("pixelAspect", 1) - resolution_width = inst_data.get("resolutionWidth", to_width) - resolution_height = inst_data.get("resolutionHeight", to_height) + resolution_width = inst_data.get("resolutionWidth", self.to_width) + resolution_height = inst_data.get("resolutionHeight", self.to_height) self.log.debug("Families In: `{}`".format(inst_data["families"])) self.log.debug("__ frame_start: {}".format(frame_start)) self.log.debug("__ frame_end: {}".format(frame_end)) @@ -223,7 +223,7 @@ class ExtractReview(pyblish.api.InstancePlugin): output_args.extend(profile.get('output', [])) # defining image ratios - resolution_ratio = (float(resolution_width) * pixel_aspect) / resolution_height + resolution_ratio = (float(resolution_width) * pixel_aspect) / resolution_height delivery_ratio = float(self.to_width) / float(self.to_height) self.log.debug( "__ resolution_ratio: `{}`".format(resolution_ratio))