From 45d18636380f73e6de54914369108558283bcc12 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 9 Feb 2018 16:04:35 +0100 Subject: [PATCH 1/4] improved extractor and saver --- .../plugins/fusion/create/create_saver.py | 37 +++++++-- .../fusion/publish/_validate_filenames.py | 52 ------------ .../fusion/publish/_validate_frame_range.py | 19 ----- .../fusion/publish/collect_instances.py | 18 ++-- .../fusion/publish/extract_image_sequence.py | 82 ++++++++++--------- .../fusion/publish/validate_unique_name.py | 2 +- 6 files changed, 85 insertions(+), 125 deletions(-) delete mode 100644 colorbleed/plugins/fusion/publish/_validate_filenames.py delete mode 100644 colorbleed/plugins/fusion/publish/_validate_frame_range.py diff --git a/colorbleed/plugins/fusion/create/create_saver.py b/colorbleed/plugins/fusion/create/create_saver.py index 75581da9cc..57420015f8 100644 --- a/colorbleed/plugins/fusion/create/create_saver.py +++ b/colorbleed/plugins/fusion/create/create_saver.py @@ -1,3 +1,5 @@ +import os + import avalon.api from avalon import fusion @@ -6,16 +8,39 @@ class CreateTiffSaver(avalon.api.Creator): name = "tiffDefault" label = "Create Tiff Saver" - hosts = "fusion" - family = "colorbleed.imagesequence" + hosts = ["fusion"] + family = "fusion.saver" def process(self): + file_format = "TiffFormat" + comp = fusion.get_current_comp() + + # todo: improve method of getting current environment + # todo: pref avalon.Session over os.environ + + workdir = os.path.normpath(os.environ["AVALON_WORKDIR"]) + + filename = "{}..tiff".format(self.name) + filepath = os.path.join(workdir, "render", "preview", filename) + with fusion.comp_lock_and_undo_chunk(comp): args = (-32768, -32768) # Magical position numbers saver = comp.AddTool("Saver", *args) - saver.SetAttrs({ - "TOOLS_Name": self.data.get("name", self.name), - 'TOOLST_Clip_FormatID': {1.0: 'TiffFormat'}, - }) + saver.SetAttrs({"TOOLS_Name": self.name}) + + # Setting input attributes is different from basic attributes + # Not confused with "MainInputAttributes" which + saver["Clip"] = filepath + saver["OutputFormat"] = file_format + + # # # Set standard TIFF settings + if saver[file_format] is None: + raise RuntimeError("File format is not set to TiffFormat, " + "this is a bug") + + # Set file format attributes + saver[file_format]["Depth"] = 1 # int8 | int16 | float32 | other + saver[file_format]["SaveAlpha"] = 0 + diff --git a/colorbleed/plugins/fusion/publish/_validate_filenames.py b/colorbleed/plugins/fusion/publish/_validate_filenames.py deleted file mode 100644 index 7f7764fb17..0000000000 --- a/colorbleed/plugins/fusion/publish/_validate_filenames.py +++ /dev/null @@ -1,52 +0,0 @@ -import os - -import pyblish.api - - -class ValidateFileNames(pyblish.api.Validator): - """Ensure all file names follow the same structure - - Filename should have identifiable parts: - - name ( example: Avengers_shot010_preview ) - - frame ( example: #### ) - - extension ( example: tiff ) - - The result when rendering frame 1250 would be as follows: - Avengers_shot010_preview.1250.tiff - - When certain parts need to be rendered out separately for some reason it - is advisable to something all the lines of: - Avengers_shot010_character_beauty.1250.tiff - """ - - order = pyblish.api.ValidatorOrder - label = "Validate File Names (Saver)" - families = ["colorbleed.imagesequence"] - hosts = ["fusion"] - - @classmethod - def get_invalid(cls, instance): - - invalid = [] - - path = instance.data["path"] - basename = os.path.basename(path) - - parts = basename.split(".") - if len(parts) != 3: - invalid.append(instance) - cls.log.error("%s has %i parts, should be 3" - % (instance, len(parts))) - else: - is_numbers = all(i.isdigit() for i in parts[1]) - if len(parts[1]) != 4 or not is_numbers: - cls.log.error("Number padding is not four digits") - invalid.append(instance) - - return invalid - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Found %i instances with a wrong file name " - "structure" % len(invalid)) diff --git a/colorbleed/plugins/fusion/publish/_validate_frame_range.py b/colorbleed/plugins/fusion/publish/_validate_frame_range.py deleted file mode 100644 index 134180d675..0000000000 --- a/colorbleed/plugins/fusion/publish/_validate_frame_range.py +++ /dev/null @@ -1,19 +0,0 @@ -import pyblish.api - - -class ValidateFrameRange(pyblish.api.InstancePlugin): - """Validate the frame range of the current Saver""" - - order = pyblish.api.ValidatorOrder - label = "Validate Frame Range" - families = ["colorbleed.imagesequence"] - hosts = ["fusion"] - - @classmethod - def get_invalid(cls, instance): - return [] - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Animation content is invalid. See log.") \ No newline at end of file diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index e04fb1d432..ae8e72c85a 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -3,6 +3,8 @@ import re import pyblish.api +from avalon.vendor import clique + def get_comp_render_range(comp): """Return comp's start and end render range.""" @@ -39,21 +41,18 @@ class CollectInstances(pyblish.api.ContextPlugin): start, end = get_comp_render_range(comp) for tool in savers: path = tool["Clip"][comp.TIME_UNDEFINED] - if not path: self.log.warning("Skipping saver because it " "has no path set: {}".format(tool.Name)) continue fname = os.path.basename(path) - _subset, ext = os.path.splitext(fname) + # we don't use the padding + basename, ext = os.path.splitext(fname) + chars = [char for char in basename if + not char.isdigit() and char != "."] - # match all digits and character but no points - match = re.match("([\d\w]+)", _subset) - if not match: - self.log.warning("Skipping save because the file name is not" - "compatible") - subset = match.group(0) + subset = "".join(chars) # Include start and end render frame in label label = "{subset} ({start}-{end})".format(subset=subset, @@ -69,9 +68,10 @@ class CollectInstances(pyblish.api.ContextPlugin): "label": label, "families": ["colorbleed.imagesequence"], "family": "colorbleed.imagesequence", - "tool": tool # keep link to the tool }) + instance.append(tool) # For future use, store the tool + self.log.info("Found: \"%s\" " % path) # Sort/grouped by family (preserving local index) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index 26264bfcad..e3f406ba39 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -1,10 +1,27 @@ import os -import glob -import re import pyblish.api -_frame_regex = re.compile("[0-9]") +from avalon.vendor import clique + + +def get_collection_for_instance(subset, collections): + """Get the collection which matches the subset name + + Args: + subset (str): name of the subset + collections (clique.Collection): + + Returns: + list + """ + for collection in collections: + name = collection.head + if name[-1] == ".": + name = name[:-1] + + if name == subset: + return collection class ExtractImageSequence(pyblish.api.Extractor): @@ -14,7 +31,7 @@ class ExtractImageSequence(pyblish.api.Extractor): """ order = pyblish.api.ExtractorOrder - label = "Extract Image Sequence" + label = "Extract Image Sequence (Local)" families = ["colorbleed.imagesequence"] hosts = ["fusion"] @@ -37,44 +54,33 @@ class ExtractImageSequence(pyblish.api.Extractor): # Get all output paths after render was successful # Note the .ID check, this is to ensure we only have savers - instances = [i for i in context[:] if i.data["tool"].ID == "Saver"] + # Use instance[0] to get the tool + instances = [i for i in context[:] if i[0].ID == "Saver"] for instance in instances: - # Ensure each instance has its files for the integrator - output_path = instance.data["path"] - query = self._create_qeury(output_path) - files = glob.glob(query) + # Ensure each instance has its files for the integrator + output_path = os.path.dirname(instance.data["path"]) + files = os.listdir(output_path) + pattern = clique.PATTERNS["frames"] + collections, remainder = clique.assemble(files, + patterns=[pattern], + minimum_items=1) + + assert not remainder, ("There shouldn't have been a remainder " + "for '%s': %s" % + (instance.data["subset"], + remainder)) + + # Filter collections to ensure specific files are part of + # the instance, store instance's collection if "files" not in instance.data: instance.data["files"] = list() - print("{} files : {}".format(instance.data["subset"], - len(files))) - instance.data["files"].append(files) + subset = instance.data["subset"] + collection = get_collection_for_instance(subset, collections) + + # Add found collection to the instance + instance.data["files"].append(list(collection)) # Ensure the integrator has stagingDir - instance.data["stagingDir"] = os.path.dirname(output_path) - - def _create_qeury(self, instance): - """Create a queriable string for glob - - Args: - instance: instance of current context (comp) - - Returns: - str - """ - - clipname = instance.data["path"] - clip_dir = os.path.dirname(clipname) - basename = os.path.basename(clipname) - _, ext = os.path.splitext(basename) - - match = re.match("([0-9]{4})", basename) - if not match: - query_name = "{}.*.{}".format(instance.data["subset"], ext[1:]) - else: - query_name = basename.replace(match.group(0), ".*.") - - query = os.path.join(clip_dir, query_name) - - return query + instance.data["stagingDir"] = output_path diff --git a/colorbleed/plugins/fusion/publish/validate_unique_name.py b/colorbleed/plugins/fusion/publish/validate_unique_name.py index 6933729eb2..3388263422 100644 --- a/colorbleed/plugins/fusion/publish/validate_unique_name.py +++ b/colorbleed/plugins/fusion/publish/validate_unique_name.py @@ -4,7 +4,7 @@ import pyblish.api class ValidateUniqueSubsetName(pyblish.api.InstancePlugin): """Ensure all instances have a unique subset name""" - order = pyblish.api.ValidatorOrder + order = pyblish.api.ValidatorOrder + 0.1 label = "Validate Unique Subset Names" families = ["colorbleed.imagesequence"] hosts = ["fusion"] From bdf85919c07ad19feac87b92b0f93bdd265ce47a Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 12 Feb 2018 12:03:05 +0100 Subject: [PATCH 2/4] extended and improved validators and image sequence extractor --- .../fusion/publish/extract_image_sequence.py | 1 + .../publish/validate_background_depth.py | 41 ++++++++++++++++++ .../publish/validate_create_folder_checked.py | 42 +++++++++++++++++++ .../fusion/publish/validate_unique_subsets.py | 6 --- 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index e3f406ba39..ad465a853e 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -78,6 +78,7 @@ class ExtractImageSequence(pyblish.api.Extractor): subset = instance.data["subset"] collection = get_collection_for_instance(subset, collections) + assert collection, "No collection found, this is a bug" # Add found collection to the instance instance.data["files"].append(list(collection)) diff --git a/colorbleed/plugins/fusion/publish/validate_background_depth.py b/colorbleed/plugins/fusion/publish/validate_background_depth.py index e69de29bb2..0664ab4002 100644 --- a/colorbleed/plugins/fusion/publish/validate_background_depth.py +++ b/colorbleed/plugins/fusion/publish/validate_background_depth.py @@ -0,0 +1,41 @@ +import pyblish.api + +from colorbleed import action + + +class ValidateBackgroundDepth(pyblish.api.ContextPlugin): + """Validate if all Background tool are set to float32 bit""" + + order = pyblish.api.ValidatorOrder + label = "Validate Background Depth 32 bit" + actions = [action.RepairContextAction] + hosts = ["fusion"] + families = ["*"] + optional = True + + comp = None + + @classmethod + def get_invalid(cls, context): + cls.comp = context.data.get("currentComp") + assert cls.comp, "Must have Comp object" + + backgrounds = cls.comp.GetToolList(False, "Background").values() + if not backgrounds: + return [] + + return [i for i in backgrounds if i.GetInput("Depth") != 4.0] + + def process(self, context): + invalid = self.get_invalid(context) + if invalid: + raise RuntimeError("Found %i nodes which are not set to float32" + % len(invalid)) + + @classmethod + def repair(cls): + # todo: improve this method, context should be available(?) + backgrounds = cls.comp.GetToolList(False, "Background").values() + invalid = [i for i in backgrounds if i.GetInput("Depth") != 4.0] + for i in invalid: + i.SetInput("Depth", 4.0, cls.comp.TIME_UNDEFINED) diff --git a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py index e69de29bb2..634296b2da 100644 --- a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py +++ b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py @@ -0,0 +1,42 @@ +import pyblish.api + +from colorbleed import action + + +class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): + """Valid if all savers have the input attribute CreateDir checked on + + This attribute ensures that the folders to which the saver will write + will be created. + """ + + order = pyblish.api.ValidatorOrder + actions = [action.RepairAction] + label = "Validate Create Folder Checked" + family = "colorbleed.saver" + hosts = ["fusion"] + + @classmethod + def get_invalid(cls, instance): + active = instance.data.get("active", instance.data.get("publish")) + if not active: + return [] + + tool = instance[0] + create_dir = tool.GetInput("CreateDir") + if create_dir == 0.0: + cls.log.error("%s has Create Folder turned off" % instance[0].Name) + return [instance] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Found %i Savers with Create Folder During " + "Render checked off") + + @classmethod + def repair(cls, instance): + invalid = cls.get_invalid(instance) + for i in invalid: + tool = i[0] + tool.SetInput("CreateDir", 1.0) diff --git a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py index f0c629849a..527b4acc69 100644 --- a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py +++ b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py @@ -4,15 +4,9 @@ import pyblish.api class ValidateUniqueSubsets(pyblish.api.InstancePlugin): """Ensure all instances have a unique subset name""" -<<<<<<< HEAD:colorbleed/plugins/fusion/publish/validate_unique_name.py - order = pyblish.api.ValidatorOrder + 0.1 - label = "Validate Unique Subset Names" - families = ["colorbleed.imagesequence"] -======= order = pyblish.api.ValidatorOrder label = "Validate Unique Subsets" families = ["colorbleed.saver"] ->>>>>>> 1e4234f45691f328f3d347c326c017e00979ec57:colorbleed/plugins/fusion/publish/validate_unique_subsets.py hosts = ["fusion"] @classmethod From adc429edf30d117c39e6ef0f70ade1cace2f6861 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 12 Feb 2018 15:16:10 +0100 Subject: [PATCH 3/4] refactored based on feedback --- .../publish/validate_background_depth.py | 29 +++++++++---------- .../publish/validate_create_folder_checked.py | 8 ++--- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/validate_background_depth.py b/colorbleed/plugins/fusion/publish/validate_background_depth.py index 0664ab4002..d66f3de451 100644 --- a/colorbleed/plugins/fusion/publish/validate_background_depth.py +++ b/colorbleed/plugins/fusion/publish/validate_background_depth.py @@ -3,39 +3,38 @@ import pyblish.api from colorbleed import action -class ValidateBackgroundDepth(pyblish.api.ContextPlugin): +class ValidateBackgroundDepth(pyblish.api.InstancePlugin): """Validate if all Background tool are set to float32 bit""" order = pyblish.api.ValidatorOrder label = "Validate Background Depth 32 bit" - actions = [action.RepairContextAction] + actions = [action.RepairAction] hosts = ["fusion"] families = ["*"] optional = True - comp = None - @classmethod - def get_invalid(cls, context): - cls.comp = context.data.get("currentComp") - assert cls.comp, "Must have Comp object" + def get_invalid(cls, instance): - backgrounds = cls.comp.GetToolList(False, "Background").values() + context = instance.context + comp = context.data.get("currentComp") + assert comp, "Must have Comp object" + + backgrounds = comp.GetToolList(False, "Background").values() if not backgrounds: return [] return [i for i in backgrounds if i.GetInput("Depth") != 4.0] - def process(self, context): - invalid = self.get_invalid(context) + def process(self, instance): + invalid = self.get_invalid(instance) if invalid: raise RuntimeError("Found %i nodes which are not set to float32" % len(invalid)) @classmethod - def repair(cls): - # todo: improve this method, context should be available(?) - backgrounds = cls.comp.GetToolList(False, "Background").values() - invalid = [i for i in backgrounds if i.GetInput("Depth") != 4.0] + def repair(cls, instance): + comp = instance.context.data.get("currentComp") + invalid = cls.get_invalid(instance) for i in invalid: - i.SetInput("Depth", 4.0, cls.comp.TIME_UNDEFINED) + i.SetInput("Depth", 4.0, comp.TIME_UNDEFINED) diff --git a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py index 634296b2da..c1a52ee671 100644 --- a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py +++ b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py @@ -26,17 +26,17 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): create_dir = tool.GetInput("CreateDir") if create_dir == 0.0: cls.log.error("%s has Create Folder turned off" % instance[0].Name) - return [instance] + return [tool] def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Found %i Savers with Create Folder During " + print(">>>", invalid) + raise RuntimeError("Found Saver with Create Folder During " "Render checked off") @classmethod def repair(cls, instance): invalid = cls.get_invalid(instance) - for i in invalid: - tool = i[0] + for tool in invalid: tool.SetInput("CreateDir", 1.0) From 666d81c236ef74246189276162a1a9abc6e5c8d1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 12 Feb 2018 15:28:45 +0100 Subject: [PATCH 4/4] removed debug print --- .../plugins/fusion/publish/validate_create_folder_checked.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py index c1a52ee671..444c808cdd 100644 --- a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py +++ b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py @@ -31,7 +31,6 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): def process(self, instance): invalid = self.get_invalid(instance) if invalid: - print(">>>", invalid) raise RuntimeError("Found Saver with Create Folder During " "Render checked off")