From 0401322e167b909dfcf0194755ca76350e235fec Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 10 Feb 2020 17:07:14 +0100 Subject: [PATCH 01/10] validator for ass relative texture paths --- .../publish/validate_ass_relative_paths.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 pype/plugins/maya/publish/validate_ass_relative_paths.py diff --git a/pype/plugins/maya/publish/validate_ass_relative_paths.py b/pype/plugins/maya/publish/validate_ass_relative_paths.py new file mode 100644 index 0000000000..b5e16103ad --- /dev/null +++ b/pype/plugins/maya/publish/validate_ass_relative_paths.py @@ -0,0 +1,79 @@ +import os +import types + +import maya.cmds as cmds + +import pyblish.api +import pype.api +import pype.maya.action + + +class ValidateAssRelativePaths(pyblish.api.InstancePlugin): + """Ensure exporting ass file has set relative texture paths""" + + order = pype.api.ValidateContentsOrder + hosts = ['maya'] + families = ['ass'] + label = "Validate ASS has relative texture paths" + actions = [pype.api.RepairAction] + + def process(self, instance): + # we cannot ask this until user open render settings as + # `defaultArnoldRenderOptions` doesn't exists + try: + relative_texture = cmds.getAttr( + "defaultArnoldRenderOptions.absolute_procedural_paths") + texture_search_path = cmds.getAttr( + "defaultArnoldRenderOptions.tspath" + ) + except ValueError: + assert False, ("Can not validate, render setting were not opened " + "yet so Arnold setting cannot be validate") + + scene_dir, scene_basename = os.path.split(cmds.file(q=True, loc=True)) + scene_name, _ = os.path.splitext(scene_basename) + project_root = "{}{}{}{}".format( + os.environ.get("AVALON_PROJECTS"), + os.path.sep, + os.environ.get("AVALON_PROJECT"), + os.pathsep + ) + assert self.maya_is_true(relative_texture) is not True, \ + ("Texture path are set to be absolute") + + texture_search_path.replace("\\", "/") + assert project_root in texture_search_path, \ + ("Project root is not in texture_search_path") + + @classmethod + def repair(cls, instance): + texture_search_path = cmds.getAttr( + "defaultArnoldRenderOptions.tspath" + ) + project_root = "{}{}{}{}".format( + os.environ.get("AVALON_PROJECTS"), + os.path.sep, + os.environ.get("AVALON_PROJECT"), + os.pathsep + ) + + project_root = project_root.replace("\\", "/") + cmds.setAttr("defaultArnoldRenderOptions.tspath", + project_root + texture_search_path, + type="string") + cmds.setAttr("defaultArnoldRenderOptions.absolute_procedural_paths", + False) + + def maya_is_true(self, attr_val): + """ + Whether a Maya attr evaluates to True. + When querying an attribute value from an ambiguous object the + Maya API will return a list of values, which need to be properly + handled to evaluate properly. + """ + if isinstance(attr_val, types.BooleanType): + return attr_val + elif isinstance(attr_val, (types.ListType, types.GeneratorType)): + return any(attr_val) + else: + return bool(attr_val) From 5a851ad936024dfdc224c529287737e0699c3916 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 18 Feb 2020 20:43:35 +0100 Subject: [PATCH 02/10] adding procedural and texture paths --- .../publish/validate_ass_relative_paths.py | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/pype/plugins/maya/publish/validate_ass_relative_paths.py b/pype/plugins/maya/publish/validate_ass_relative_paths.py index b5e16103ad..b0fd12a550 100644 --- a/pype/plugins/maya/publish/validate_ass_relative_paths.py +++ b/pype/plugins/maya/publish/validate_ass_relative_paths.py @@ -14,7 +14,7 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): order = pype.api.ValidateContentsOrder hosts = ['maya'] families = ['ass'] - label = "Validate ASS has relative texture paths" + label = "ASS has relative texture paths" actions = [pype.api.RepairAction] def process(self, instance): @@ -22,47 +22,65 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): # `defaultArnoldRenderOptions` doesn't exists try: relative_texture = cmds.getAttr( + "defaultArnoldRenderOptions.absolute_texture_paths") + relative_procedural = cmds.getAttr( "defaultArnoldRenderOptions.absolute_procedural_paths") texture_search_path = cmds.getAttr( "defaultArnoldRenderOptions.tspath" ) + procedural_search_path = cmds.getAttr( + "defaultArnoldRenderOptions.pspath" + ) except ValueError: assert False, ("Can not validate, render setting were not opened " "yet so Arnold setting cannot be validate") scene_dir, scene_basename = os.path.split(cmds.file(q=True, loc=True)) scene_name, _ = os.path.splitext(scene_basename) - project_root = "{}{}{}{}".format( + project_root = "{}{}{}".format( os.environ.get("AVALON_PROJECTS"), os.path.sep, - os.environ.get("AVALON_PROJECT"), - os.pathsep + os.environ.get("AVALON_PROJECT") ) assert self.maya_is_true(relative_texture) is not True, \ - ("Texture path are set to be absolute") + ("Texture path is set to be absolute") + assert self.maya_is_true(relative_procedural) is not True, \ + ("Procedural path is set to be absolute") + + texture_search_path = texture_search_path.replace("\\", "/") + procedural_search_path = procedural_search_path.replace("\\", "/") + project_root = project_root.replace("\\", "/") - texture_search_path.replace("\\", "/") assert project_root in texture_search_path, \ ("Project root is not in texture_search_path") + assert project_root in procedural_search_path, \ + ("Project root is not in procedural_search_path") @classmethod def repair(cls, instance): texture_search_path = cmds.getAttr( "defaultArnoldRenderOptions.tspath" ) - project_root = "{}{}{}{}".format( + procedural_search_path = cmds.getAttr( + "defaultArnoldRenderOptions.pspath" + ) + + project_root = "{}{}{}".format( os.environ.get("AVALON_PROJECTS"), os.path.sep, os.environ.get("AVALON_PROJECT"), - os.pathsep - ) + ).replace("\\", "/") - project_root = project_root.replace("\\", "/") cmds.setAttr("defaultArnoldRenderOptions.tspath", - project_root + texture_search_path, + project_root + os.pathsep + texture_search_path, + type="string") + cmds.setAttr("defaultArnoldRenderOptions.pspath", + project_root + os.pathsep + procedural_search_path, type="string") cmds.setAttr("defaultArnoldRenderOptions.absolute_procedural_paths", False) + cmds.setAttr("defaultArnoldRenderOptions.absolute_texture_paths", + False) def maya_is_true(self, attr_val): """ From 2a1e057716df52f88fc6cac6e6086a3d5d26ce34 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 20 Feb 2020 13:18:08 +0100 Subject: [PATCH 03/10] [bugfix] - integrato wasn't comparing intance name with context name --- pype/plugins/global/publish/integrate_new.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index a2343ce8a9..2b11185595 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -111,15 +111,16 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): project_entity = instance.data["projectEntity"] + context_asset_name = context.data["assetEntity"]["name"] + asset_name = instance.data["asset"] asset_entity = instance.data.get("assetEntity") - if not asset_entity: + if not asset_entity or asset_entity["name"] != context_asset_name: asset_entity = io.find_one({ "type": "asset", "name": asset_name, "parent": project_entity["_id"] }) - assert asset_entity, ( "No asset found by the name \"{0}\" in project \"{1}\"" ).format(asset_name, project_entity["name"]) From 1f6bdb44aa07959f439a1fac5093654b9057f704 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Feb 2020 11:55:13 +0100 Subject: [PATCH 04/10] added custom exception message when project is not found --- pype/plugins/ftrack/publish/collect_ftrack_api.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pype/plugins/ftrack/publish/collect_ftrack_api.py b/pype/plugins/ftrack/publish/collect_ftrack_api.py index f79d74453b..dd9f5f5184 100644 --- a/pype/plugins/ftrack/publish/collect_ftrack_api.py +++ b/pype/plugins/ftrack/publish/collect_ftrack_api.py @@ -35,7 +35,17 @@ class CollectFtrackApi(pyblish.api.ContextPlugin): # Find project entity project_query = 'Project where full_name is "{0}"'.format(project_name) self.log.debug("Project query: < {0} >".format(project_query)) - project_entity = session.query(project_query).one() + project_entity = list(session.query(project_query).all()) + if len(project_entity) == 0: + raise AssertionError( + "Project \"{0}\" not found in Ftrack.".format(project_name) + ) + # QUESTION Is possible to happen? + elif len(project_entity) > 1: + raise AssertionError(( + "Found more than one project with name \"{0}\" in Ftrack." + ).format(project_name)) + self.log.debug("Project found: {0}".format(project_entity)) # Find asset entity From 9f6a9ed496d5978970f1207481a54d61f0a4b30a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Feb 2020 11:55:56 +0100 Subject: [PATCH 05/10] ftrack task query won't crash but log warning if task not found --- pype/plugins/ftrack/publish/collect_ftrack_api.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pype/plugins/ftrack/publish/collect_ftrack_api.py b/pype/plugins/ftrack/publish/collect_ftrack_api.py index dd9f5f5184..94bc88b983 100644 --- a/pype/plugins/ftrack/publish/collect_ftrack_api.py +++ b/pype/plugins/ftrack/publish/collect_ftrack_api.py @@ -63,8 +63,15 @@ class CollectFtrackApi(pyblish.api.ContextPlugin): 'Task where name is "{0}" and parent_id is "{1}"' ).format(task_name, asset_entity["id"]) self.log.debug("Task entity query: < {0} >".format(task_query)) - task_entity = session.query(task_query).one() - self.log.debug("Task entity found: {0}".format(task_entity)) + task_entity = session.query(task_query).first() + if not task_entity: + self.log.warning( + "Task entity with name \"{0}\" was not found.".format( + task_name + ) + ) + else: + self.log.debug("Task entity found: {0}".format(task_entity)) else: task_entity = None From 2562dcfc7fde41e4767a43c1df260dcbf0e67deb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Feb 2020 11:56:40 +0100 Subject: [PATCH 06/10] typed context entities ignore tasks and added custom exception messages --- .../ftrack/publish/collect_ftrack_api.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pype/plugins/ftrack/publish/collect_ftrack_api.py b/pype/plugins/ftrack/publish/collect_ftrack_api.py index 94bc88b983..47a6cc3826 100644 --- a/pype/plugins/ftrack/publish/collect_ftrack_api.py +++ b/pype/plugins/ftrack/publish/collect_ftrack_api.py @@ -54,7 +54,25 @@ class CollectFtrackApi(pyblish.api.ContextPlugin): ' and name is "{1}"' ).format(project_entity["id"], asset_name) self.log.debug("Asset entity query: < {0} >".format(entity_query)) - asset_entity = session.query(entity_query).one() + asset_entities = [] + for entity in session.query(entity_query).all(): + # Skip tasks + if entity.entity_type.lower() != "task": + asset_entities.append(entity) + + if len(asset_entities) == 0: + raise AssertionError(( + "Entity with name \"{0}\" not found" + " in Ftrack project \"{1}\"." + ).format(asset_name, project_name)) + + elif len(asset_entities) > 1: + raise AssertionError(( + "Found more than one entity with name \"{0}\"" + " in Ftrack project \"{1}\"." + ).format(asset_name, project_name)) + + asset_entity = asset_entities[0] self.log.debug("Asset found: {0}".format(asset_entity)) # Find task entity if task is set From 21956523445684110bf2786a9f9d12b0fb738641 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 21 Feb 2020 13:28:50 +0100 Subject: [PATCH 07/10] fixed start frame if it doesn't match render --- .../global/publish/collect_filesequences.py | 16 ++++++++++++++++ pype/plugins/global/publish/extract_review.py | 3 +++ 2 files changed, 19 insertions(+) diff --git a/pype/plugins/global/publish/collect_filesequences.py b/pype/plugins/global/publish/collect_filesequences.py index 8b42606e4a..f7ce5fab00 100644 --- a/pype/plugins/global/publish/collect_filesequences.py +++ b/pype/plugins/global/publish/collect_filesequences.py @@ -256,10 +256,16 @@ class CollectRenderedFrames(pyblish.api.ContextPlugin): ) ext = collection.tail.lstrip(".") + detected_start = min(collection.indexes) + detected_end = max(collection.indexes) + representation = { "name": ext, "ext": "{}".format(ext), "files": list(collection), + "frameStart": frame_start, + "detectedStart": detected_start, + "detectedEnd": detected_end, "stagingDir": root, "anatomy_template": "render", "fps": fps, @@ -323,12 +329,17 @@ class CollectRenderedFrames(pyblish.api.ContextPlugin): if "slate" in instance.data["families"]: frame_start += 1 + detected_start = min(collection.indexes) + detected_end = max(collection.indexes) + representation = { "name": ext, "ext": "{}".format(ext), "files": list(collection), "frameStart": frame_start, "frameEnd": frame_end, + "detectedStart": detected_start, + "detectedEnd": detected_end, "stagingDir": root, "anatomy_template": "render", "fps": fps, @@ -394,6 +405,9 @@ class CollectRenderedFrames(pyblish.api.ContextPlugin): if "review" not in families: families.append("review") + detected_start = min(collection.indexes) + detected_end = max(collection.indexes) + instance.data.update( { "name": str(collection), @@ -428,6 +442,8 @@ class CollectRenderedFrames(pyblish.api.ContextPlugin): "files": list(collection), "frameStart": start, "frameEnd": end, + "detectedStart": detected_start, + "detectedEnd": detected_end, "stagingDir": root, "anatomy_template": "render", "fps": fps, diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 4d63e2c641..4f96491638 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -149,6 +149,9 @@ class ExtractReview(pyblish.api.InstancePlugin): # necessary input data # adds start arg only if image sequence if isinstance(repre["files"], list): + + 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)) From 4435eb8c1454e1cd4748f66d0d3a1f27eef29e9c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 21 Feb 2020 17:36:17 +0100 Subject: [PATCH 08/10] current frame now works correctly and has correct position --- pype/scripts/otio_burnin.py | 58 ++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/pype/scripts/otio_burnin.py b/pype/scripts/otio_burnin.py index c61ea66d2d..46b2d1421c 100644 --- a/pype/scripts/otio_burnin.py +++ b/pype/scripts/otio_burnin.py @@ -1,17 +1,14 @@ import os import sys import re -import datetime import subprocess import json import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins from pypeapp.lib import config -from pype import api as pype -from subprocess import Popen, PIPE -# FFmpeg in PATH is required +from pypeapp import Logger -log = pype.Logger().get_logger("BurninWrapper", "burninwrap") +log = Logger().get_logger("BurninWrapper", "burninwrap") ffmpeg_path = os.environ.get("FFMPEG_PATH") @@ -41,6 +38,7 @@ TIMECODE = ( MISSING_KEY_VALUE = "N/A" CURRENT_FRAME_KEY = "{current_frame}" +CURRENT_FRAME_SPLITTER = "_-_CURRENT_FRAME_-_" TIME_CODE_KEY = "{timecode}" @@ -136,7 +134,9 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): if options_init: self.options_init.update(options_init) - def add_text(self, text, align, frame_start=None, options=None): + def add_text( + self, text, align, frame_start=None, frame_end=None, options=None + ): """ Adding static text to a filter. @@ -152,11 +152,15 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): if frame_start: options["frame_offset"] = frame_start + # `frame_end` is only for meassurements of text position + if frame_end: + options["frame_end"] = frame_end + self._add_burnin(text, align, options, DRAWTEXT) def add_timecode( - self, align, frame_start=None, frame_start_tc=None, text=None, - options=None + self, align, frame_start=None, frame_end=None, frame_start_tc=None, + text=None, options=None ): """ Convenience method to create the frame number expression. @@ -174,6 +178,10 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): if frame_start: options["frame_offset"] = frame_start + # `frame_end` is only for meassurements of text position + if frame_end: + options["frame_end"] = frame_end + if not frame_start_tc: frame_start_tc = options["frame_offset"] @@ -197,10 +205,31 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): :param enum align: alignment, must use provided enum flags :param dict options: """ + + final_text = text + text_for_size = text + if CURRENT_FRAME_SPLITTER in text: + frame_start = options["frame_offset"] + frame_end = options.get("frame_end", frame_start) + if not frame_start: + replacement_final = replacement_size = str(MISSING_KEY_VALUE) + else: + replacement_final = "\\'{}\\'".format( + r'%%{eif\:n+%d\:d}' % frame_start + ) + replacement_size = str(frame_end) + + final_text = final_text.replace( + CURRENT_FRAME_SPLITTER, replacement_final + ) + text_for_size = text_for_size.replace( + CURRENT_FRAME_SPLITTER, replacement_size + ) + resolution = self.resolution data = { 'text': ( - text + final_text .replace(",", r"\,") .replace(':', r'\:') ), @@ -208,7 +237,7 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): 'size': options['font_size'] } timecode_text = options.get("timecode") or "" - text_for_size = text + timecode_text + text_for_size += timecode_text data.update(options) data.update( ffmpeg_burnins._drawtext(align, resolution, text_for_size, options) @@ -272,7 +301,7 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): ) print(command) - proc = Popen(command, shell=True) + proc = subprocess.Popen(command, shell=True) proc.communicate() if proc.returncode != 0: raise RuntimeError("Failed to render '%s': %s'" @@ -368,6 +397,7 @@ def burnins_from_data( burnin = ModifiedBurnins(input_path, options_init=options_init) frame_start = data.get("frame_start") + frame_end = data.get("frame_end") frame_start_tc = data.get('frame_start_tc', frame_start) stream = burnin._streams[0] @@ -382,7 +412,7 @@ def burnins_from_data( # Check frame start and add expression if is available if frame_start is not None: - data[CURRENT_FRAME_KEY[1:-1]] = r'%%{eif\:n+%d\:d}' % frame_start + data[CURRENT_FRAME_KEY[1:-1]] = CURRENT_FRAME_SPLITTER if frame_start_tc is not None: data[TIME_CODE_KEY[1:-1]] = TIME_CODE_KEY @@ -432,7 +462,7 @@ def burnins_from_data( # Handle timecode differently if has_timecode: - args = [align, frame_start, frame_start_tc] + args = [align, frame_start, frame_end, frame_start_tc] if not value.startswith(TIME_CODE_KEY): value_items = value.split(TIME_CODE_KEY) text = value_items[0].format(**data) @@ -442,7 +472,7 @@ def burnins_from_data( continue text = value.format(**data) - burnin.add_text(text, align, frame_start) + burnin.add_text(text, align, frame_start, frame_end) codec_args = "" if codec_data: From 7b70a6e52dab4dbe84718865cc12a6c5bd10fc80 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 21 Feb 2020 17:44:52 +0100 Subject: [PATCH 09/10] bugfix: missing io.install in colelctavalonentities --- pype/plugins/global/publish/collect_avalon_entities.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/global/publish/collect_avalon_entities.py b/pype/plugins/global/publish/collect_avalon_entities.py index c256dffd52..35852587d5 100644 --- a/pype/plugins/global/publish/collect_avalon_entities.py +++ b/pype/plugins/global/publish/collect_avalon_entities.py @@ -19,6 +19,7 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): label = "Collect Avalon Entities" def process(self, context): + is.install() project_name = api.Session["AVALON_PROJECT"] asset_name = api.Session["AVALON_ASSET"] From 03fbbf7d10a55f0dd943ae3a3a731ee8e419c681 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 21 Feb 2020 17:47:33 +0100 Subject: [PATCH 10/10] typo --- pype/plugins/global/publish/collect_avalon_entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/collect_avalon_entities.py b/pype/plugins/global/publish/collect_avalon_entities.py index 35852587d5..a429b3fc84 100644 --- a/pype/plugins/global/publish/collect_avalon_entities.py +++ b/pype/plugins/global/publish/collect_avalon_entities.py @@ -19,7 +19,7 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): label = "Collect Avalon Entities" def process(self, context): - is.install() + io.install() project_name = api.Session["AVALON_PROJECT"] asset_name = api.Session["AVALON_ASSET"]