diff --git a/colorbleed/action.py b/colorbleed/action.py index a94dc6e921..b8e3c21d49 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -1,7 +1,5 @@ # absolute_import is needed to counter the `module has no cmds error` in Maya from __future__ import absolute_import - -from maya import cmds import pyblish.api @@ -104,6 +102,11 @@ class SelectInvalidAction(pyblish.api.Action): def process(self, context, plugin): + try: + from maya import cmds + except ImportError: + raise ImportError("Current host is not Maya") + errored_instances = get_errored_instances_from_context(context) # Apply pyblish.logic to get the instances for the plug-in diff --git a/colorbleed/plugins/fusion/create/create_saver.py b/colorbleed/plugins/fusion/create/create_saver.py index 1e2cf721ad..b313ca994f 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,15 +8,39 @@ class CreateTiffSaver(avalon.api.Creator): name = "tiffDefault" label = "Create Tiff Saver" - family = "colorbleed.imagesequence" + hosts = ["fusion"] + family = "colorbleed.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 912995d3e6..e9a7bda799 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -1,5 +1,4 @@ import os -import re import pyblish.api @@ -35,6 +34,8 @@ class CollectInstances(pyblish.api.ContextPlugin): def process(self, context): """Collect all image sequence tools""" + from avalon.fusion.lib import get_frame_path + comp = context.data["currentComp"] # Get all savers in the comp @@ -56,7 +57,6 @@ class CollectInstances(pyblish.api.ContextPlugin): "has no path set: {}".format(tool.Name)) continue - from avalon.fusion.lib import get_frame_path filename = os.path.basename(path) head, padding, tail = get_frame_path(filename) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index 271c69ec1d..ad465a853e 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,34 @@ 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 + # 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) + assert collection, "No collection found, this is a bug" + + # 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_background_depth.py b/colorbleed/plugins/fusion/publish/validate_background_depth.py new file mode 100644 index 0000000000..d66f3de451 --- /dev/null +++ b/colorbleed/plugins/fusion/publish/validate_background_depth.py @@ -0,0 +1,40 @@ +import pyblish.api + +from colorbleed import action + + +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.RepairAction] + hosts = ["fusion"] + families = ["*"] + optional = True + + @classmethod + def get_invalid(cls, instance): + + 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, 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, instance): + comp = instance.context.data.get("currentComp") + invalid = cls.get_invalid(instance) + for i in invalid: + 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 new file mode 100644 index 0000000000..444c808cdd --- /dev/null +++ b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py @@ -0,0 +1,41 @@ +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 [tool] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Found Saver with Create Folder During " + "Render checked off") + + @classmethod + def repair(cls, instance): + invalid = cls.get_invalid(instance) + for tool in invalid: + tool.SetInput("CreateDir", 1.0)