From 106116ed47dba6273aca3e45218b7e3ba653f371 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 26 Jul 2018 18:07:00 +0200 Subject: [PATCH 001/149] renamed for clearity --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 0 colorbleed/plugins/maya/publish/collect_vray_scene.py | 0 .../maya/publish/{submit_deadline.py => submit_maya_deadline.py} | 0 colorbleed/plugins/maya/publish/submit_vray_deadline.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 colorbleed/plugins/maya/create/colorbleed_vrayscene.py create mode 100644 colorbleed/plugins/maya/publish/collect_vray_scene.py rename colorbleed/plugins/maya/publish/{submit_deadline.py => submit_maya_deadline.py} (100%) create mode 100644 colorbleed/plugins/maya/publish/submit_vray_deadline.py diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_maya_deadline.py similarity index 100% rename from colorbleed/plugins/maya/publish/submit_deadline.py rename to colorbleed/plugins/maya/publish/submit_maya_deadline.py diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py new file mode 100644 index 0000000000..e69de29bb2 From 257e61d7690554b45dec61a2ec545a718646f80e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 26 Jul 2018 18:07:23 +0200 Subject: [PATCH 002/149] added submit vray to deadline logic --- .../maya/publish/submit_vray_deadline.py | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index e69de29bb2..ac2a005694 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -0,0 +1,212 @@ +import getpass +import json +import os +from copy import deepcopy + +import pyblish.api + +from avalon import api +from avalon.vendor import requests + +from maya import cmds + + +class VraySubmitDeadline(pyblish.api.InstancePlugin): + """""" + label = "Submit to Deadline ( vrscene )" + order = pyblish.api.IntegratorOrder + hosts = ["maya"] + families = ["colorbleed.vrayscene"] + + def process(self, instance): + + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", + "http://localhost:8082") + assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" + + context = instance.context + + deadline_url = context.data["deadlineUrl"] + deadline_user = context.data.get("deadlineUser", getpass.getuser()) + + filepath = context.data["currentFile"] + filename = os.path.basename(filepath) + task_name = "{} - {}".format(filename, instance.name) + + batch_name = "VRay Scene Export - {}".format(filename) + + output_filepath = context.data["outputFilePath"].replace("\\", "/") + + # This is also the input file for the render job + first_file = self.format_output_filename_zero(instance, filename) + + # Primary job + self.log.info("Submitting export job ..") + + payload = { + "JobInfo": { + # Top-level group name + "BatchName": batch_name, + + # Job name, as seen in Monitor + "Name": task_name, + + # Arbitrary username, for visualisation in Monitor + "UserName": deadline_user, + + "Plugin": "MayaCmd", + "Frames": "1", + + "Comment": context.data.get("comment", ""), + "Whitelist": "cb7" + }, + "PluginInfo": { + + # Mandatory for Deadline + "Version": cmds.about(version=True), + + # Input + "SceneFile": filepath, + + # Output directory and filename + "OutputFilePath": output_filepath, + + "CommandLineOptions": self.build_command(instance), + + "UseOnlyCommandLineOptions": True, + + "SkipExistingFrames": True, + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + environment = dict(AVALON_TOOLS="global;python36;maya2018") + + jobinfo_environment = self.build_jobinfo_environment(environment) + + payload["JobInfo"].update(jobinfo_environment) + + self.log.info("Job Data:\n{}".format(json.dumps(payload))) + + response = requests.post(url=deadline_url, json=payload) + if not response.ok: + raise RuntimeError(response.text) + + # Secondary job + # Store job to create dependency chain + dependency = response.json() + + self.log.info("Submitting render job ..") + + start_frame = int(instance.data["startFrame"]) + end_frame = int(instance.data["endFrame"]) + + payload_b = { + "JobInfo": { + + "JobDependency0": dependency["_id"], + "BatchName": batch_name, + "Name": "Render {}".format(task_name), + "UserName": deadline_user, + + "Frames": "{}-{}".format(start_frame, end_frame), + + "Plugin": "Vray", + "OverrideTaskExtraInfoNames": False, + "Whitelist": "cb7" + }, + "PluginInfo": { + + "InputFilename": first_file, + "Threads": 0, + "OutputFilename": "", + "SeparateFilesPerFrame": True, + "VRayEngine": "V-Ray", + + "Width": instance.data["resolution"][0], + "Height": instance.data["resolution"][1], + + }, + "AuxFiles": [], + } + + tools = environment["AVALON_TOOLS"] + ";vrayrenderslave" + environment_b = deepcopy(environment) + environment_b["AVALON_TOOLS"] = tools + + jobinfo_environment_b = self.build_jobinfo_environment(environment_b) + payload_b["JobInfo"].update(jobinfo_environment_b) + + self.log.info(json.dumps(payload_b)) + + response_b = requests.post(url=deadline_url, json=payload_b) + if not response_b.ok: + raise RuntimeError(response_b.text) + + print(response_b.text) + + def build_command(self, instance): + """Create command for Render.exe to export vray scene + + Returns: + str + + """ + + cmd = ('-r vray -proj {project} -cam {cam} -noRender -s {startFrame} ' + '-e {endFrame} -rl {layer} -exportFramesSeparate') + + return cmd.format(project=instance.context.data["workspaceDir"], + cam=instance.data.get("cam", "persp"), + startFrame=instance.data["startFrame"], + endFrame=instance.data["endFrame"], + layer=instance.name) + + def build_jobinfo_environment(self, env): + """Format environment keys and values to match Deadline rquirements + + Returns: + dict + + """ + return {"EnvironmentKeyValue%d" % index: "%s=%s" % (k, env[k]) + for index, k in enumerate(env)} + + def format_output_filename_zero(self, instance, filename): + """Format the expected output file of the Export job + + Example: + //_ + shot010 + + Args: + instance: + filename(str): + + Returns: + str + """ + + def smart_replace(string, key_values): + new_string = string + for key, value in key_values.items(): + new_string = new_string.replace(key, value) + return new_string + + # Ensure filename has no extension + file_name, _ = os.path.splitext(filename) + output_filename = instance.context.data["outputFilePath"] + + # Reformat without tokens + output_path = smart_replace(output_filename, + {"": file_name, + "": instance.name}) + + start_frame = int(instance.data["startFrame"]) + filename_zero = "{}_{:04d}.vrscene".format(output_path, start_frame) + + result = filename_zero.replace("\\", "/") + + return result From 85f28aefb904a4ab75d13831255de3e7c89e73c9 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 26 Jul 2018 18:07:44 +0200 Subject: [PATCH 003/149] added vrayscene plugin --- .../maya/create/colorbleed_vrayscene.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index e69de29bb2..0f84db8971 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -0,0 +1,30 @@ +from collections import OrderedDict + +import avalon.maya + + +class CreateVRayScene(avalon.maya.Creator): + + label = "VRay Scene" + family = "colorbleed.vrayscene" + # icon = "blocks" + + def __init__(self, *args, **kwargs): + super(CreateVRayScene, self).__init__(*args, **kwargs) + + # We won't be publishing this one + self.data["id"] = "avalon.vrayscene" + + # We don't need subset or asset attributes + self.data.pop("subset", None) + self.data.pop("asset", None) + self.data.pop("active", None) + + data = OrderedDict(**self.data) + + data["camera"] = "persp" + data["pools"] = "" + + self.data = data + + self.options = {"useSelection": False} # Force no content From e20f0ac009573826aed92e4bb34b2fcd68919112 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 26 Jul 2018 18:08:04 +0200 Subject: [PATCH 004/149] added vray scene collector --- .../maya/publish/collect_vray_scene.py | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index e69de29bb2..f99a1a8e86 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -0,0 +1,95 @@ +import os + +import pyblish.api + +from avalon import api, maya + +from maya import cmds + + +class CollectVRayScene(pyblish.api.ContextPlugin): + + order = pyblish.api.CollectorOrder + label = "Collect VRay Scene" + hosts = ["maya"] + + def process(self, context): + + asset = api.Session["AVALON_ASSET"] + + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", None) + assert AVALON_DEADLINE, "Can't submit without Deadline connection!" + + context.data["deadlineUrl"] = "{}/api/jobs".format(AVALON_DEADLINE) + + # Create output file path with template + file_name = context.data["currentFile"].replace("\\", "/") + output_filepath = os.path.join(context.data["workspaceDir"], + "vrayscene", + "", + "", + "_") + + context.data["outputFilePath"] = output_filepath + + # Get VRay Scene instance + vray_scenes = maya.lsattr("family", "colorbleed.vrayscene") + if not vray_scenes: + self.log.info("No instance found of family: `colorbleed.vrayscene`") + return + + assert len(vray_scenes) == 1, "Multiple vrayscene instances found!" + vray_scene = vray_scenes[0] + + camera = cmds.getAttr("{}.camera".format(vray_scene)) or "persp" + + # Animation data + start_frame = cmds.getAttr("defaultRenderGlobals.startFrame") + end_frame = cmds.getAttr("defaultRenderGlobals.endFrame") + context.data["startFrame"] = int(start_frame) + context.data["endFrame"] = int(end_frame) + + # Get render layers + renderlayers = [i for i in cmds.ls(type="renderLayer") if + cmds.getAttr("{}.renderable".format(i)) and not + cmds.referenceQuery(i, isNodeReferenced=True)] + + # Sort by displayOrder + def sort_by_display_order(layer): + return cmds.getAttr("%s.displayOrder" % layer) + + renderlayers = sorted(renderlayers, key=sort_by_display_order) + + resolution = (cmds.getAttr("defaultResolution.width"), + cmds.getAttr("defaultResolution.height")) + + for layer in renderlayers: + + if layer.endswith("defaultRenderLayer"): + layer = "masterLayer" + + data = { + "subset": layer, + "setMembers": layer, + + "camera": camera, + "startFrame": start_frame, + "endFrame": end_frame, + "renderer": "vray", + "resolution": resolution, + + # instance subset + "family": "VRay Scene", + "families": ["colorbleed.vrayscene"], + "asset": asset, + "time": api.time(), + "author": context.data["user"], + + # Add source to allow tracing back to the scene from + # which was submitted originally + "source": file_name + } + + instance = context.create_instance(layer) + self.log.info("Created: %s" % instance.name) + instance.data.update(data) From b1fdec440bc784d806b429ad18e692aa3a7e656b Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 26 Jul 2018 18:09:11 +0200 Subject: [PATCH 005/149] removed debug whitelist --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index ac2a005694..d1fbe3efab 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -58,7 +58,6 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "Frames": "1", "Comment": context.data.get("comment", ""), - "Whitelist": "cb7" }, "PluginInfo": { From 84236373e926c9a5f64d172c98d0e0c9c11f233d Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 27 Jul 2018 13:14:59 +0200 Subject: [PATCH 006/149] added validator for translator settings --- .../publish/validate_translator_settings.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_translator_settings.py diff --git a/colorbleed/plugins/maya/publish/validate_translator_settings.py b/colorbleed/plugins/maya/publish/validate_translator_settings.py new file mode 100644 index 0000000000..7c7479792c --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_translator_settings.py @@ -0,0 +1,45 @@ +import pyblish.api +import colorbleed.api + +from maya import cmds + + +class ValidateTranslatorEnabled(pyblish.api.ContextPlugin): + + order = colorbleed.api.ValidateContentsOrder + label = "VRay Translator Settings" + families = ["colorbleed.vrayscene"] + actions = [colorbleed.api.RepairContextAction] + + def process(self, context): + + # Get vraySettings node + vray_settings = cmds.ls(type="VRaySettingsNode") + assert vray_settings, "Please ensure a VRay Settings Node is present" + + node = vray_settings[0] + + if not cmds.getAttr("{}.vrscene_on".format(node)): + self.info.error("Export vrscene not enabled") + + if not cmds.getAttr("{}.misc_eachFrameInFile".format(node)): + self.info.error("Each Frame in File not enabled") + + vrscene_filename = cmds.getAttr("{}.vrscene_filename".format(node)) + if vrscene_filename != "vrayscene//_/": + self.info.error("Template for file name is wrong") + + @classmethod + def repair(cls, context): + + vray_settings = cmds.ls(type="VRaySettingsNode") + if not vray_settings: + node = cmds.createNode("VRaySettingsNode") + else: + node = vray_settings[0] + + cmds.setAttr("{}.vrscene_on".format(node), True) + cmds.setAttr("{}.misc_eachFrameInFile".format(node), True) + cmds.setAttr("{}.vrscene_filename".format(node), + "vrayscene//_/", + type="string") From 5d6c355b45c7072ab2f767cab78f317c1a4eed61 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 27 Jul 2018 13:16:15 +0200 Subject: [PATCH 007/149] added colorbleed.vrayscene to plugin --- .../plugins/global/publish/submit_publish_job.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 34a09c9b81..5cbba4f800 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -123,7 +123,9 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): label = "Submit image sequence jobs to Deadline" order = pyblish.api.IntegratorOrder + 0.1 hosts = ["fusion", "maya"] - families = ["colorbleed.saver.deadline", "colorbleed.renderlayer"] + families = ["colorbleed.saver.deadline", + "colorbleed.renderlayer", + "colorbleed.vrayscene"] def process(self, instance): @@ -162,8 +164,10 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), ext=ext) + # Remove deadline submission job, not needed in metadata + data.pop("deadlineSubmissionJob") + # Write metadata for publish job - render_job = data.pop("deadlineSubmissionJob") metadata = { "regex": regex, "startFrame": start, @@ -189,7 +193,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): override = data["overrideExistingFrame"] # override = data.get("overrideExistingFrame", False) - out_file = render_job.get("OutFile") + out_file = job.get("OutFile") if not out_file: raise RuntimeError("OutFile not found in render job!") From ad55a128585964f1ba9fe3a046c0617b8b952d39 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 27 Jul 2018 13:16:44 +0200 Subject: [PATCH 008/149] Extended options for vrayscene instance --- .../maya/create/colorbleed_vrayscene.py | 4 + .../maya/publish/collect_vray_scene.py | 74 +++++++++++-------- .../maya/publish/submit_vray_deadline.py | 58 ++++++++++++--- 3 files changed, 93 insertions(+), 43 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 0f84db8971..e30f4ac3ce 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -23,6 +23,10 @@ class CreateVRayScene(avalon.maya.Creator): data = OrderedDict(**self.data) data["camera"] = "persp" + data["suspendRenderJob"] = False + data["suspendPublishJob"] = False + data["includeDefaultRenderLayer"] = False + data["extendFrames"] = False data["pools"] = "" self.data = data diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index f99a1a8e86..6ecb07f123 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -8,6 +8,8 @@ from maya import cmds class CollectVRayScene(pyblish.api.ContextPlugin): + """Collect all information prior for exporting vrscenes + """ order = pyblish.api.CollectorOrder label = "Collect VRay Scene" @@ -15,22 +17,12 @@ class CollectVRayScene(pyblish.api.ContextPlugin): def process(self, context): + # Sort by displayOrder + def sort_by_display_order(layer): + return cmds.getAttr("%s.displayOrder" % layer) + asset = api.Session["AVALON_ASSET"] - - AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", None) - assert AVALON_DEADLINE, "Can't submit without Deadline connection!" - - context.data["deadlineUrl"] = "{}/api/jobs".format(AVALON_DEADLINE) - - # Create output file path with template - file_name = context.data["currentFile"].replace("\\", "/") - output_filepath = os.path.join(context.data["workspaceDir"], - "vrayscene", - "", - "", - "_") - - context.data["outputFilePath"] = output_filepath + work_dir = context.data["workspaceDir"] # Get VRay Scene instance vray_scenes = maya.lsattr("family", "colorbleed.vrayscene") @@ -41,29 +33,48 @@ class CollectVRayScene(pyblish.api.ContextPlugin): assert len(vray_scenes) == 1, "Multiple vrayscene instances found!" vray_scene = vray_scenes[0] - camera = cmds.getAttr("{}.camera".format(vray_scene)) or "persp" + vrscene_data = {k: cmds.getAttr("%s.%s" % (vray_scene, k)) for + k in cmds.listAttr(vray_scene, userDefined=True)} - # Animation data - start_frame = cmds.getAttr("defaultRenderGlobals.startFrame") - end_frame = cmds.getAttr("defaultRenderGlobals.endFrame") - context.data["startFrame"] = int(start_frame) - context.data["endFrame"] = int(end_frame) + # Output data + start_frame = int(cmds.getAttr("defaultRenderGlobals.startFrame")) + end_frame = int(cmds.getAttr("defaultRenderGlobals.endFrame")) - # Get render layers - renderlayers = [i for i in cmds.ls(type="renderLayer") if - cmds.getAttr("{}.renderable".format(i)) and not - cmds.referenceQuery(i, isNodeReferenced=True)] + # Create output file path with template + file_name = context.data["currentFile"].replace("\\", "/") + vrscene = ("vrayscene", "", "_", "") + vrscene_output = os.path.join(work_dir, *vrscene) - # Sort by displayOrder - def sort_by_display_order(layer): - return cmds.getAttr("%s.displayOrder" % layer) + vrscene_data["startFrame"] = start_frame + vrscene_data["endFrame"] = end_frame + vrscene_data["vrsceneOutput"] = vrscene_output - renderlayers = sorted(renderlayers, key=sort_by_display_order) + context.data["startFrame"] = start_frame + context.data["endFrame"] = end_frame + # Check and create render output template for render job + # outputDir is required for submit_publish_job + if not vrscene_data.get("suspendRenderJob", False): + renders = ("renders", "", "_", "") + output_renderpath = os.path.join(work_dir, *renders) + vrscene_data["outputDir"] = output_renderpath + + # Get resolution resolution = (cmds.getAttr("defaultResolution.width"), cmds.getAttr("defaultResolution.height")) - for layer in renderlayers: + # Get render layers + render_layers = [i for i in cmds.ls(type="renderLayer") if + cmds.getAttr("{}.renderable".format(i)) and not + cmds.referenceQuery(i, isNodeReferenced=True)] + + # Check if we need to filter out the default render layer + if vrscene_data.get("includeDefaultRenderLayer", True): + render_layers = [r for r in render_layers + if r != "defaultRenderLayer"] + + render_layers = sorted(render_layers, key=sort_by_display_order) + for layer in render_layers: if layer.endswith("defaultRenderLayer"): layer = "masterLayer" @@ -72,7 +83,6 @@ class CollectVRayScene(pyblish.api.ContextPlugin): "subset": layer, "setMembers": layer, - "camera": camera, "startFrame": start_frame, "endFrame": end_frame, "renderer": "vray", @@ -90,6 +100,8 @@ class CollectVRayScene(pyblish.api.ContextPlugin): "source": file_name } + data.update(vrscene_data) + instance = context.create_instance(layer) self.log.info("Created: %s" % instance.name) instance.data.update(data) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index d1fbe3efab..77cb8f2ec6 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -12,7 +12,15 @@ from maya import cmds class VraySubmitDeadline(pyblish.api.InstancePlugin): - """""" + """Export the scene to `.vrscene` files per frame per render layer + + vrscene files will be written out based on the following template: + /vrayscene//_/ + + A dependency job will be added for each layer to render the framer + through VRay Standalone + + """ label = "Submit to Deadline ( vrscene )" order = pyblish.api.IntegratorOrder hosts = ["maya"] @@ -26,7 +34,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): context = instance.context - deadline_url = context.data["deadlineUrl"] + deadline_url = "{}/api/jobs".format(AVALON_DEADLINE) deadline_user = context.data.get("deadlineUser", getpass.getuser()) filepath = context.data["currentFile"] @@ -35,10 +43,13 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): batch_name = "VRay Scene Export - {}".format(filename) - output_filepath = context.data["outputFilePath"].replace("\\", "/") + # Get the output template for vrscenes + vrscene_output = instance.data["vrsceneOutput"] # This is also the input file for the render job - first_file = self.format_output_filename_zero(instance, filename) + first_file = self.format_output_filename(instance, + filename, + vrscene_output) # Primary job self.log.info("Submitting export job ..") @@ -68,7 +79,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "SceneFile": filepath, # Output directory and filename - "OutputFilePath": output_filepath, + "OutputFilePath": vrscene_output.replace("\\", "/"), "CommandLineOptions": self.build_command(instance), @@ -97,11 +108,26 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): # Store job to create dependency chain dependency = response.json() + if instance.data["suspendRenderJob"]: + self.log.info("Skipping render job and publish job") + return + self.log.info("Submitting render job ..") start_frame = int(instance.data["startFrame"]) end_frame = int(instance.data["endFrame"]) + # Create output directory for renders + render_ouput = self.format_output_filename(instance, + filename, + instance.data["outputDir"], + dir=True) + + self.log.info("Render output: %s" % render_ouput) + + # Update output dir + instance.data["outputDir"] = render_ouput + payload_b = { "JobInfo": { @@ -120,7 +146,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "InputFilename": first_file, "Threads": 0, - "OutputFilename": "", + "OutputFilename": render_ouput, "SeparateFilesPerFrame": True, "VRayEngine": "V-Ray", @@ -131,6 +157,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "AuxFiles": [], } + # Add vray renderslave to environment tools = environment["AVALON_TOOLS"] + ";vrayrenderslave" environment_b = deepcopy(environment) environment_b["AVALON_TOOLS"] = tools @@ -140,11 +167,14 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): self.log.info(json.dumps(payload_b)) + # Post job to deadline response_b = requests.post(url=deadline_url, json=payload_b) if not response_b.ok: raise RuntimeError(response_b.text) - print(response_b.text) + # Add job for publish job + if not instance.data.get("suspendPublishJob", False): + instance.data["deadlineSubmissionJob"] = response_b.json() def build_command(self, instance): """Create command for Render.exe to export vray scene @@ -173,19 +203,21 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): return {"EnvironmentKeyValue%d" % index: "%s=%s" % (k, env[k]) for index, k in enumerate(env)} - def format_output_filename_zero(self, instance, filename): + def format_output_filename(self, instance, filename, template, dir=False): """Format the expected output file of the Export job Example: - //_ - shot010 + /_/ + "shot010_v006/shot010_v006_CHARS/CHARS" Args: instance: filename(str): + dir(bool): Returns: str + """ def smart_replace(string, key_values): @@ -196,13 +228,15 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): # Ensure filename has no extension file_name, _ = os.path.splitext(filename) - output_filename = instance.context.data["outputFilePath"] # Reformat without tokens - output_path = smart_replace(output_filename, + output_path = smart_replace(template, {"": file_name, "": instance.name}) + if dir: + return output_path.replace("\\", "/") + start_frame = int(instance.data["startFrame"]) filename_zero = "{}_{:04d}.vrscene".format(output_path, start_frame) From 72ceefc5484458ac3ca6faeef7da13c4592d94fb Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 27 Jul 2018 18:10:59 +0200 Subject: [PATCH 009/149] Don't throw errors, not needed --- colorbleed/plugins/global/publish/submit_publish_job.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 5cbba4f800..ca5e186825 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -136,8 +136,10 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # Get a submission job job = instance.data.get("deadlineSubmissionJob") if not job: - raise RuntimeError("Can't continue without valid deadline " - "submission prior to this plug-in.") + self.log.warning("Can't continue without valid deadline " + "submission prior to this plug-in.") + self.log.info("Skipping Publish Job") + return data = instance.data.copy() subset = data["subset"] @@ -162,7 +164,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): ext = "\.\D+" regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), - ext=ext) + ext=re.escape(subset)) # Remove deadline submission job, not needed in metadata data.pop("deadlineSubmissionJob") From ccf36bffb3289ae3b935937aa9fbe310316a841c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 27 Jul 2018 18:11:25 +0200 Subject: [PATCH 010/149] Added extension and fixed outputfilename --- colorbleed/plugins/maya/publish/collect_vray_scene.py | 4 ++++ colorbleed/plugins/maya/publish/submit_vray_deadline.py | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index 6ecb07f123..cedd527106 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -63,6 +63,9 @@ class CollectVRayScene(pyblish.api.ContextPlugin): resolution = (cmds.getAttr("defaultResolution.width"), cmds.getAttr("defaultResolution.height")) + # Get format extension + extension = cmds.getAttr("vraySettings.imageFormatStr") + # Get render layers render_layers = [i for i in cmds.ls(type="renderLayer") if cmds.getAttr("{}.renderable".format(i)) and not @@ -87,6 +90,7 @@ class CollectVRayScene(pyblish.api.ContextPlugin): "endFrame": end_frame, "renderer": "vray", "resolution": resolution, + "ext": extension, # instance subset "family": "VRay Scene", diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 77cb8f2ec6..925013c693 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -77,7 +77,6 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): # Input "SceneFile": filepath, - # Output directory and filename "OutputFilePath": vrscene_output.replace("\\", "/"), @@ -93,6 +92,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): } environment = dict(AVALON_TOOLS="global;python36;maya2018") + environment.update(api.Session.copy()) jobinfo_environment = self.build_jobinfo_environment(environment) @@ -116,6 +116,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): start_frame = int(instance.data["startFrame"]) end_frame = int(instance.data["endFrame"]) + ext = instance.data.get("ext", "exr") # Create output directory for renders render_ouput = self.format_output_filename(instance, @@ -128,6 +129,10 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): # Update output dir instance.data["outputDir"] = render_ouput + # Format output file name + sequence_fiename = ".".join([instance.name, "%04d", ext]) + output_filename = os.path.join(render_ouput, sequence_fiename) + payload_b = { "JobInfo": { @@ -146,7 +151,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "InputFilename": first_file, "Threads": 0, - "OutputFilename": render_ouput, + "OutputFilename": output_filename, "SeparateFilesPerFrame": True, "VRayEngine": "V-Ray", From be668ac27ab5250f8af18af84270af72162cbb0b Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 30 Jul 2018 09:48:44 +0200 Subject: [PATCH 011/149] reverting unwanted change --- colorbleed/plugins/global/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index ca5e186825..1c77d5c845 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -164,7 +164,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): ext = "\.\D+" regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), - ext=re.escape(subset)) + ext=re.escape(ext)) # Remove deadline submission job, not needed in metadata data.pop("deadlineSubmissionJob") From c16013beec3d2bafc47b6dbf4bc8cede551649a4 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 30 Jul 2018 12:00:02 +0200 Subject: [PATCH 012/149] include the '.' in the regex --- colorbleed/plugins/global/publish/submit_publish_job.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 1c77d5c845..184ed0b43d 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -159,12 +159,12 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # This assumes the output files start with subset name and ends with # a file extension. if "ext" in instance.data: - ext = re.escape(instance.data["ext"]) + ext = instance.data["ext"].strip(".") else: ext = "\.\D+" - regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), - ext=re.escape(ext)) + regex = "^{subset}.*\d+\.{ext}$".format(subset=re.escape(subset), + ext=re.escape(ext)) # Remove deadline submission job, not needed in metadata data.pop("deadlineSubmissionJob") From 5f6c0b9e574c18c9b69ce318ac1a3aa0d3b0b433 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 30 Jul 2018 12:27:00 +0200 Subject: [PATCH 013/149] Fix typo --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 925013c693..97c26497f8 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -130,8 +130,8 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): instance.data["outputDir"] = render_ouput # Format output file name - sequence_fiename = ".".join([instance.name, "%04d", ext]) - output_filename = os.path.join(render_ouput, sequence_fiename) + sequence_filename = ".".join([instance.name, "%04d", ext]) + output_filename = os.path.join(render_ouput, sequence_filename) payload_b = { "JobInfo": { @@ -150,7 +150,6 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "PluginInfo": { "InputFilename": first_file, - "Threads": 0, "OutputFilename": output_filename, "SeparateFilesPerFrame": True, "VRayEngine": "V-Ray", From 283018b34040b972551298f6007d2bd09f90f492 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 14:41:40 +0200 Subject: [PATCH 014/149] get camera from renderable cameras --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index e30f4ac3ce..7310eda302 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -22,7 +22,7 @@ class CreateVRayScene(avalon.maya.Creator): data = OrderedDict(**self.data) - data["camera"] = "persp" + data["camera"] = self._get_camera() data["suspendRenderJob"] = False data["suspendPublishJob"] = False data["includeDefaultRenderLayer"] = False @@ -32,3 +32,9 @@ class CreateVRayScene(avalon.maya.Creator): self.data = data self.options = {"useSelection": False} # Force no content + + def _get_camera(self): + from maya import cmds + + return [c for c in cmds.ls(type="camera") + if cmds.getAttr("%s.renderable" % i)] From 981f165b62ecedb121c9942e726b02f63ecfb048 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 14:42:10 +0200 Subject: [PATCH 015/149] allow user to control active state of instance --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 7310eda302..5689e0883b 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -18,7 +18,6 @@ class CreateVRayScene(avalon.maya.Creator): # We don't need subset or asset attributes self.data.pop("subset", None) self.data.pop("asset", None) - self.data.pop("active", None) data = OrderedDict(**self.data) From 09c6a9430a21c2ab88eeb73635c1a1c5f6945851 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 14:45:07 +0200 Subject: [PATCH 016/149] fix bug --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 5689e0883b..965c7b37f0 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -36,4 +36,4 @@ class CreateVRayScene(avalon.maya.Creator): from maya import cmds return [c for c in cmds.ls(type="camera") - if cmds.getAttr("%s.renderable" % i)] + if cmds.getAttr("%s.renderable" % c)] From 383a5447734f189ac5a03d9497f0f86b076ad0f8 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 15:12:13 +0200 Subject: [PATCH 017/149] renamed method, changed icon to cubes --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 965c7b37f0..632e3d54a7 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -7,7 +7,7 @@ class CreateVRayScene(avalon.maya.Creator): label = "VRay Scene" family = "colorbleed.vrayscene" - # icon = "blocks" + icon = "cubes" def __init__(self, *args, **kwargs): super(CreateVRayScene, self).__init__(*args, **kwargs) @@ -21,10 +21,9 @@ class CreateVRayScene(avalon.maya.Creator): data = OrderedDict(**self.data) - data["camera"] = self._get_camera() + data["camera"] = self._get_cameras() data["suspendRenderJob"] = False data["suspendPublishJob"] = False - data["includeDefaultRenderLayer"] = False data["extendFrames"] = False data["pools"] = "" @@ -32,7 +31,7 @@ class CreateVRayScene(avalon.maya.Creator): self.options = {"useSelection": False} # Force no content - def _get_camera(self): + def _get_cameras(self): from maya import cmds return [c for c in cmds.ls(type="camera") From 3d2537ee94bab5d344a97d9aea0b4e08011eb1d3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 15:13:37 +0200 Subject: [PATCH 018/149] use registerd_host to get host functions, log settings in debug --- .../plugins/maya/publish/collect_vray_scene.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index cedd527106..8cfa5f2dcc 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -2,10 +2,10 @@ import os import pyblish.api -from avalon import api, maya - from maya import cmds +from avalon import api + class CollectVRayScene(pyblish.api.ContextPlugin): """Collect all information prior for exporting vrscenes @@ -21,11 +21,13 @@ class CollectVRayScene(pyblish.api.ContextPlugin): def sort_by_display_order(layer): return cmds.getAttr("%s.displayOrder" % layer) + host = api.registered_host() + asset = api.Session["AVALON_ASSET"] work_dir = context.data["workspaceDir"] # Get VRay Scene instance - vray_scenes = maya.lsattr("family", "colorbleed.vrayscene") + vray_scenes = host.lsattr("family", "colorbleed.vrayscene") if not vray_scenes: self.log.info("No instance found of family: `colorbleed.vrayscene`") return @@ -33,8 +35,7 @@ class CollectVRayScene(pyblish.api.ContextPlugin): assert len(vray_scenes) == 1, "Multiple vrayscene instances found!" vray_scene = vray_scenes[0] - vrscene_data = {k: cmds.getAttr("%s.%s" % (vray_scene, k)) for - k in cmds.listAttr(vray_scene, userDefined=True)} + vrscene_data = host.read(vray_scene) # Output data start_frame = int(cmds.getAttr("defaultRenderGlobals.startFrame")) @@ -71,11 +72,6 @@ class CollectVRayScene(pyblish.api.ContextPlugin): cmds.getAttr("{}.renderable".format(i)) and not cmds.referenceQuery(i, isNodeReferenced=True)] - # Check if we need to filter out the default render layer - if vrscene_data.get("includeDefaultRenderLayer", True): - render_layers = [r for r in render_layers - if r != "defaultRenderLayer"] - render_layers = sorted(render_layers, key=sort_by_display_order) for layer in render_layers: @@ -108,4 +104,5 @@ class CollectVRayScene(pyblish.api.ContextPlugin): instance = context.create_instance(layer) self.log.info("Created: %s" % instance.name) + self.log.debug("VRay Data: %s" % vrscene_data) instance.data.update(data) From c31cbefa26e7d24494483d62bceb93ebf8f93770 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 15:15:06 +0200 Subject: [PATCH 019/149] updated name of key in data --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 97c26497f8..6e161d8a4e 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -192,7 +192,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): '-e {endFrame} -rl {layer} -exportFramesSeparate') return cmd.format(project=instance.context.data["workspaceDir"], - cam=instance.data.get("cam", "persp"), + cam=instance.data.get("camera", "persp"), startFrame=instance.data["startFrame"], endFrame=instance.data["endFrame"], layer=instance.name) From d1ecf25388f6ca1911673a24bc7080eeae26e769 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 31 Aug 2018 15:24:58 +0200 Subject: [PATCH 020/149] reversed change, throwing runtimer error --- colorbleed/plugins/global/publish/submit_publish_job.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 184ed0b43d..b70382dd54 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -136,10 +136,8 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # Get a submission job job = instance.data.get("deadlineSubmissionJob") if not job: - self.log.warning("Can't continue without valid deadline " - "submission prior to this plug-in.") - self.log.info("Skipping Publish Job") - return + raise RuntimeError("Can't continue without valid deadline " + "submission prior to this plug-in.") data = instance.data.copy() subset = data["subset"] From 1a13d448d5bf3ebeb24183073db555c275258d56 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 16 Oct 2018 11:06:08 +0200 Subject: [PATCH 021/149] Added attr and attrPrefix for alembic export, removed OrderedDict --- .../maya/create/colorbleed_pointcache.py | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index c6a4edc5c1..c24711a838 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import avalon.maya from colorbleed.maya import lib @@ -15,22 +13,14 @@ class CreatePointCache(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreatePointCache, self).__init__(*args, **kwargs) - # create an ordered dict with the existing data first - data = OrderedDict(**self.data) + data = {"writeColorSets": False, # Vertex colors with the geometry. + "renderableOnly": False, # Only renderable visible shapes + "visibleOnly": False, # only nodes that are visible + "attr": "cbId", # Add options for custom attributes + "attrPrefix": ""} # get basic animation data : start / end / handles / steps for key, value in lib.collect_animation_data().items(): data[key] = value - # Write vertex colors with the geometry. - data["writeColorSets"] = False - - # Include only renderable visible shapes. - # Skips locators and empty transforms - data["renderableOnly"] = False - - # Include only nodes that are visible at least once during the - # frame range. - data["visibleOnly"] = False - - self.data = data \ No newline at end of file + self.data.update(data) From 11677142be824e246bdf7088075a2f5d52f0a836 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 16 Oct 2018 11:08:49 +0200 Subject: [PATCH 022/149] Fetch attribute from instance for alembic export --- colorbleed/plugins/maya/publish/extract_pointcache.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 4cfb8c9c25..eb0bf75f66 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -32,6 +32,12 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): start -= handles end += handles + attrs = instance.data.get("attr", "").split(";") + if not attrs: + attrs = ["cbId"] + + attr_prefixes = instance.data.get("attrPrefix", "").split(";") + # Get extra export arguments writeColorSets = instance.data.get("writeColorSets", False) @@ -44,7 +50,8 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): options = { "step": instance.data.get("step", 1.0), - "attr": ["cbId"], + "attr": attrs, + "attrPrefix": attr_prefixes, "writeVisibility": True, "writeCreases": True, "writeColorSets": writeColorSets, From 721cfca954d2d0016855b2fadabac3f908ecedf4 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 16 Oct 2018 12:07:49 +0200 Subject: [PATCH 023/149] Force add cbId, mandatory attribute --- colorbleed/plugins/maya/publish/extract_pointcache.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index eb0bf75f66..ccf222408f 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -33,8 +33,7 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): end += handles attrs = instance.data.get("attr", "").split(";") - if not attrs: - attrs = ["cbId"] + attrs += ["cbId"] attr_prefixes = instance.data.get("attrPrefix", "").split(";") From 1473e54796cc428ada58e1895ff522c2dd666f8e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 16 Oct 2018 12:09:17 +0200 Subject: [PATCH 024/149] Removed cbId, made mandatory during export --- colorbleed/plugins/maya/create/colorbleed_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index c24711a838..02deb0a469 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -16,7 +16,7 @@ class CreatePointCache(avalon.maya.Creator): data = {"writeColorSets": False, # Vertex colors with the geometry. "renderableOnly": False, # Only renderable visible shapes "visibleOnly": False, # only nodes that are visible - "attr": "cbId", # Add options for custom attributes + "attr": "", # Add options for custom attributes "attrPrefix": ""} # get basic animation data : start / end / handles / steps From 576b905aa94e15ad26e8bf71e2742b539e8c1081 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 16 Oct 2018 12:17:56 +0200 Subject: [PATCH 025/149] Add attribute and attribute Prefix support for pointcache --- colorbleed/plugins/maya/create/colorbleed_model.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_model.py b/colorbleed/plugins/maya/create/colorbleed_model.py index c69e4bbf7b..47411e6c52 100644 --- a/colorbleed/plugins/maya/create/colorbleed_model.py +++ b/colorbleed/plugins/maya/create/colorbleed_model.py @@ -14,10 +14,8 @@ class CreateModel(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateModel, self).__init__(*args, **kwargs) - # create an ordered dict with the existing data first - data = OrderedDict(**self.data) + data = {"writeColorSets": False, # Vertex colors with the geometry. + "attr": "", # Add options for custom attributes + "attrPrefix": ""} - # Write vertex colors with the geometry. - data["writeColorSets"] = True - - self.data = data + self.data.update(data) From 0b8f0d33de2fd129fba26407abb8b1ba1c2e1130 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 17 Oct 2018 12:36:15 +0200 Subject: [PATCH 026/149] Added vrayscene family to validator --- .../plugins/maya/publish/increment_current_file_deadline.py | 3 ++- .../plugins/maya/publish/validate_render_single_camera.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py index cb8374a7e1..40a7634fb7 100644 --- a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py +++ b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py @@ -11,7 +11,8 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["maya"] - families = ["colorbleed.renderlayer"] + families = ["colorbleed.renderlayer", + "colorbleed.vrayscene"] optional = True def process(self, context): diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/colorbleed/plugins/maya/publish/validate_render_single_camera.py index 94448d1585..9762b562fd 100644 --- a/colorbleed/plugins/maya/publish/validate_render_single_camera.py +++ b/colorbleed/plugins/maya/publish/validate_render_single_camera.py @@ -19,7 +19,8 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.renderlayer'] + families = ['colorbleed.renderlayer', + "colorbleed.vrayscene"] label = "Render Single Camera" actions = [colorbleed.maya.action.SelectInvalidAction] From e88c32e021eed5fb03298dc8f212d6c0751a9ef5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 17 Oct 2018 12:36:39 +0200 Subject: [PATCH 027/149] Renamed module and class for clearity --- ...nslator_settings.py => validate_vray_translator_settings.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename colorbleed/plugins/maya/publish/{validate_translator_settings.py => validate_vray_translator_settings.py} (95%) diff --git a/colorbleed/plugins/maya/publish/validate_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py similarity index 95% rename from colorbleed/plugins/maya/publish/validate_translator_settings.py rename to colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 7c7479792c..7f0f737886 100644 --- a/colorbleed/plugins/maya/publish/validate_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -4,7 +4,7 @@ import colorbleed.api from maya import cmds -class ValidateTranslatorEnabled(pyblish.api.ContextPlugin): +class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): order = colorbleed.api.ValidateContentsOrder label = "VRay Translator Settings" From 9f73077013b3c77d418ff690480e281ebbca6bd3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:06:10 +0200 Subject: [PATCH 028/149] Added collector for renderable cameras for render layers --- .../maya/publish/collect_renderable_camera.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/collect_renderable_camera.py diff --git a/colorbleed/plugins/maya/publish/collect_renderable_camera.py b/colorbleed/plugins/maya/publish/collect_renderable_camera.py new file mode 100644 index 0000000000..9eed039873 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_renderable_camera.py @@ -0,0 +1,24 @@ +import pyblish.api + +from maya import cmds + +from colorbleed.maya import lib + + +class CollectRenderableCamera(pyblish.api.InstancePlugin): + """Collect the renderable camera(s) for the render layer""" + + order = pyblish.api.CollectorOrder + label = "Collect Renderable Camera(s)" + hosts = ["maya"] + families = ["colorbleed.vrayscene", + "colorbleed.renderlayer"] + + def process(self, instance): + layer = instance.data["setMembers"] + + cameras = cmds.ls(type="camera", long=True) + with lib.renderlayer(layer): + renderable = [c for c in cameras if + cmds.getAttr("%s.renderable" % c)] + instance.data.update({"camera": renderable}) From a473890e36f73589cd6fcf023fdf031f7941aff5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:06:33 +0200 Subject: [PATCH 029/149] Simplified validator --- .../publish/validate_render_single_camera.py | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/colorbleed/plugins/maya/publish/validate_render_single_camera.py index 9762b562fd..581c1e920d 100644 --- a/colorbleed/plugins/maya/publish/validate_render_single_camera.py +++ b/colorbleed/plugins/maya/publish/validate_render_single_camera.py @@ -1,9 +1,6 @@ -from maya import cmds - import pyblish.api import colorbleed.api import colorbleed.maya.action -import colorbleed.maya.lib as lib class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): @@ -18,33 +15,15 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder + label = "Render Single Camera" hosts = ['maya'] families = ['colorbleed.renderlayer', "colorbleed.vrayscene"] - label = "Render Single Camera" + actions = [colorbleed.maya.action.SelectInvalidAction] - @staticmethod - def get_invalid(instance): - - layer = instance.data["setMembers"] - - cameras = cmds.ls(type='camera', long=True) - - with lib.renderlayer(layer): - renderable = [cam for cam in cameras if - cmds.getAttr(cam + ".renderable")] - - if len(renderable) == 0: - raise RuntimeError("No renderable cameras found.") - elif len(renderable) > 1: - return renderable - else: - return [] - def process(self, instance): """Process all the cameras in the instance""" - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Multiple renderable cameras" - "found: {0}".format(invalid)) + cameras = instance.data.get("camera", []) + assert len(cameras) == 1, ("Multiple renderable cameras" "found: %s " % + instance.data["setMembers"]) From 8201c0f62feeeba72741c70baf07a3ba99601509 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:08:20 +0200 Subject: [PATCH 030/149] Removed debug log --- colorbleed/plugins/maya/publish/collect_vray_scene.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index 8cfa5f2dcc..fe1015ee65 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -104,5 +104,4 @@ class CollectVRayScene(pyblish.api.ContextPlugin): instance = context.create_instance(layer) self.log.info("Created: %s" % instance.name) - self.log.debug("VRay Data: %s" % vrscene_data) instance.data.update(data) From 95174b07a64f672e79425e608d6a76dafa9f1836 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:08:55 +0200 Subject: [PATCH 031/149] Added check for vrayscene instances --- .../publish/validate_vray_translator_settings.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 7f0f737886..b882b195b4 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -13,6 +13,20 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): def process(self, context): + # Check if there are any vray scene instances + vrayscene_instances = [] + for inst in context[:]: + if inst.data["family"] in self.families: + # Skip if instances is inactive + if not inst.data["active"]: + continue + + vrayscene_instances.append(inst) + + if not vrayscene_instances: + self.log.info("No VRay Scene instances found, skipping..") + return + # Get vraySettings node vray_settings = cmds.ls(type="VRaySettingsNode") assert vray_settings, "Please ensure a VRay Settings Node is present" From 34009e6750e5fa1f35de299821a7ddf68a1fe092 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:10:53 +0200 Subject: [PATCH 032/149] Added empty string fallback to counter NoneType error --- .../maya/publish/validate_yeti_renderscript_callbacks.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py b/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py index ad4d1db911..b31e31ba52 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py @@ -56,8 +56,11 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): % renderer) return False - pre_render_callback = cmds.getAttr("defaultRenderGlobals.preMel") - post_render_callback = cmds.getAttr("defaultRenderGlobals.postMel") + pre_mel_attr = "defaultRenderGlobals.preMel" + post_mel_attr = "defaultRenderGlobals.postMel" + + pre_render_callback = cmds.getAttr(pre_mel_attr) or "" + post_render_callback = cmds.getAttr(post_mel_attr) or "" pre_callbacks = pre_render_callback.split(";") post_callbacks = post_render_callback.split(";") From 1b515188fc44f507cf63025fc92a08d2257c2f2b Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:42:52 +0200 Subject: [PATCH 033/149] Added log message --- .../plugins/maya/publish/collect_renderable_camera.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderable_camera.py b/colorbleed/plugins/maya/publish/collect_renderable_camera.py index 9eed039873..dd9ec433bb 100644 --- a/colorbleed/plugins/maya/publish/collect_renderable_camera.py +++ b/colorbleed/plugins/maya/publish/collect_renderable_camera.py @@ -8,7 +8,7 @@ from colorbleed.maya import lib class CollectRenderableCamera(pyblish.api.InstancePlugin): """Collect the renderable camera(s) for the render layer""" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder + 0.01 label = "Collect Renderable Camera(s)" hosts = ["maya"] families = ["colorbleed.vrayscene", @@ -21,4 +21,7 @@ class CollectRenderableCamera(pyblish.api.InstancePlugin): with lib.renderlayer(layer): renderable = [c for c in cameras if cmds.getAttr("%s.renderable" % c)] - instance.data.update({"camera": renderable}) + + self.log.info("Found cameras %s" % len(renderable)) + + instance.data.update({"cameras": renderable}) From 4ae1f2f481894bebe3134ca48f7981e6ac1325b2 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:43:16 +0200 Subject: [PATCH 034/149] Extended comments --- .../plugins/maya/publish/validate_vray_translator_settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index b882b195b4..305fa23314 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -14,6 +14,8 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): def process(self, context): # Check if there are any vray scene instances + # The reason to not use host.lsattr() as used in collect_vray_scene + # is because that information is already available in the context vrayscene_instances = [] for inst in context[:]: if inst.data["family"] in self.families: From 91d43e07740de13654c2bfde895f1927b9af57c5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:44:01 +0200 Subject: [PATCH 035/149] Added back get_invalid method --- .../maya/publish/validate_render_single_camera.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/colorbleed/plugins/maya/publish/validate_render_single_camera.py index 581c1e920d..0a2eb997c5 100644 --- a/colorbleed/plugins/maya/publish/validate_render_single_camera.py +++ b/colorbleed/plugins/maya/publish/validate_render_single_camera.py @@ -20,10 +20,14 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): families = ['colorbleed.renderlayer', "colorbleed.vrayscene"] - actions = [colorbleed.maya.action.SelectInvalidAction] - def process(self, instance): """Process all the cameras in the instance""" - cameras = instance.data.get("camera", []) - assert len(cameras) == 1, ("Multiple renderable cameras" "found: %s " % - instance.data["setMembers"]) + + @classmethod + def get_invalid(cls, instance): + cameras = instance.data.get("cameras", []) + if len(cameras) != 1: + cls.log.error("Multiple renderable cameras" "found: %s " % + instance.data["setMembers"]) + + return [instance.data["setMembers"]] From 922c7159a5ca805c65a36925ae402afdba64bde8 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:53:09 +0200 Subject: [PATCH 036/149] Removed %04d, vray handles this on its own --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 6e161d8a4e..1c7c4b1035 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -130,7 +130,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): instance.data["outputDir"] = render_ouput # Format output file name - sequence_filename = ".".join([instance.name, "%04d", ext]) + sequence_filename = ".".join([instance.name, ext]) output_filename = os.path.join(render_ouput, sequence_filename) payload_b = { From 36cb9358e779b8261b5781e79e7eaeaa6d5156db Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:53:41 +0200 Subject: [PATCH 037/149] Get single camera from cameras in instance --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 1c7c4b1035..c213f24ced 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -191,8 +191,11 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): cmd = ('-r vray -proj {project} -cam {cam} -noRender -s {startFrame} ' '-e {endFrame} -rl {layer} -exportFramesSeparate') + # Get the camera + cammera = instance.data["cameras"][0] + return cmd.format(project=instance.context.data["workspaceDir"], - cam=instance.data.get("camera", "persp"), + cam=cammera, startFrame=instance.data["startFrame"], endFrame=instance.data["endFrame"], layer=instance.name) From dc2477cbebb04d4d7dc0fb29a3a7ab4baa1d5b85 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:53:52 +0200 Subject: [PATCH 038/149] Updated doctstrings --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index c213f24ced..a137a9b5e6 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -183,6 +183,9 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): def build_command(self, instance): """Create command for Render.exe to export vray scene + Args: + instance + Returns: str @@ -203,6 +206,9 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): def build_jobinfo_environment(self, env): """Format environment keys and values to match Deadline rquirements + Args: + env(dict): environment dictionary + Returns: dict From dd3b17fc9d3657b9672ede82af60a603689cf36e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:55:18 +0200 Subject: [PATCH 039/149] Removed get camera logic --- .../maya/create/colorbleed_vrayscene.py | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 632e3d54a7..5f145685f8 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import avalon.maya @@ -12,27 +10,16 @@ class CreateVRayScene(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateVRayScene, self).__init__(*args, **kwargs) - # We won't be publishing this one - self.data["id"] = "avalon.vrayscene" - # We don't need subset or asset attributes self.data.pop("subset", None) self.data.pop("asset", None) - data = OrderedDict(**self.data) - - data["camera"] = self._get_cameras() - data["suspendRenderJob"] = False - data["suspendPublishJob"] = False - data["extendFrames"] = False - data["pools"] = "" - - self.data = data + self.data.update({ + "id": "avalon.vrayscene", # We won't be publishing this one + "suspendRenderJob": False, + "suspendPublishJob": False, + "extendFrames": False, + "pools": "" + }) self.options = {"useSelection": False} # Force no content - - def _get_cameras(self): - from maya import cmds - - return [c for c in cmds.ls(type="camera") - if cmds.getAttr("%s.renderable" % c)] From 836990b04a4a71727d5b0744c9f08ca6411137bc Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 11:10:06 +0200 Subject: [PATCH 040/149] Remove active, mirror behavious from renderglobals --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 5f145685f8..b9f404e1d3 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -13,6 +13,7 @@ class CreateVRayScene(avalon.maya.Creator): # We don't need subset or asset attributes self.data.pop("subset", None) self.data.pop("asset", None) + self.data.pop("active", None) self.data.update({ "id": "avalon.vrayscene", # We won't be publishing this one From e7e2afa8979181e09431f86c49419aefc9d854de Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 18:04:32 +0200 Subject: [PATCH 041/149] Added GenerateUUIDsOnInvalidAction to plugin --- .../plugins/maya/publish/validate_node_ids_in_database.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py index 7b0fddee30..d808152245 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py @@ -23,7 +23,8 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): hosts = ['maya'] families = ["*"] - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction, + colorbleed.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) From 90e17109bca0ae0973f29090405b6a0e20d9c48f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 18:24:09 +0200 Subject: [PATCH 042/149] Implement first working version for FBX extraction (PLN-183) --- colorbleed/maya/plugin.py | 3 +- .../plugins/global/publish/integrate.py | 1 + .../plugins/maya/create/colorbleed_fbx.py | 18 ++ colorbleed/plugins/maya/load/load_fbx.py | 36 +++ .../plugins/maya/publish/extract_fbx.py | 212 ++++++++++++++++++ 5 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 colorbleed/plugins/maya/create/colorbleed_fbx.py create mode 100644 colorbleed/plugins/maya/load/load_fbx.py create mode 100644 colorbleed/plugins/maya/publish/extract_fbx.py diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 327cf47cbd..86c6ebbd0f 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -131,7 +131,8 @@ class ReferenceLoader(api.Loader): file_type = { "ma": "mayaAscii", "mb": "mayaBinary", - "abc": "Alembic" + "abc": "Alembic", + "fbx": "FBX" }.get(representation["name"]) assert file_type, "Unsupported representation: %s" % representation diff --git a/colorbleed/plugins/global/publish/integrate.py b/colorbleed/plugins/global/publish/integrate.py index f5d07b339e..2dae53335d 100644 --- a/colorbleed/plugins/global/publish/integrate.py +++ b/colorbleed/plugins/global/publish/integrate.py @@ -25,6 +25,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder families = ["colorbleed.animation", "colorbleed.camera", + "colorbleed.fbx", "colorbleed.imagesequence", "colorbleed.look", "colorbleed.mayaAscii", diff --git a/colorbleed/plugins/maya/create/colorbleed_fbx.py b/colorbleed/plugins/maya/create/colorbleed_fbx.py new file mode 100644 index 0000000000..e6b57fa86f --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_fbx.py @@ -0,0 +1,18 @@ +import avalon.maya +from colorbleed.maya import lib + + +class CreateFBX(avalon.maya.Creator): + """FBX Export""" + + name = "fbxDefault" + label = "FBX" + family = "colorbleed.fbx" + icon = "plug" + + def __init__(self, *args, **kwargs): + super(CreateFBX, self).__init__(*args, **kwargs) + + # get basic animation data : start / end / handles / steps + for key, value in lib.collect_animation_data().items(): + self.data[key] = value diff --git a/colorbleed/plugins/maya/load/load_fbx.py b/colorbleed/plugins/maya/load/load_fbx.py new file mode 100644 index 0000000000..0dade5eca2 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_fbx.py @@ -0,0 +1,36 @@ +import colorbleed.maya.plugin + + +class FBXLoader(colorbleed.maya.plugin.ReferenceLoader): + """Load the FBX""" + + families = ["colorbleed.fbx"] + representations = ["fbx"] + + label = "Reference FBX" + order = -10 + icon = "code-fork" + color = "orange" + + def process_reference(self, context, name, namespace, data): + + import maya.cmds as cmds + from avalon import maya + + # Ensure FBX plug-in is loaded + cmds.loadPlugin("fbxmaya", quiet=True) + + with maya.maintained_selection(): + nodes = cmds.file(self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName="{}:{}".format(namespace, name)) + + self[:] = nodes + + return nodes + + def switch(self, container, representation): + self.update(container, representation) diff --git a/colorbleed/plugins/maya/publish/extract_fbx.py b/colorbleed/plugins/maya/publish/extract_fbx.py new file mode 100644 index 0000000000..4ba28acb58 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_fbx.py @@ -0,0 +1,212 @@ +import os + +from maya import cmds +import maya.mel as mel + +import pyblish.api +import avalon.maya + +import colorbleed.api + + +class ExtractFBX(colorbleed.api.Extractor): + """Extract FBX from Maya. + + This extracts reproducible FBX exports ignoring any of the settings set + on the local machine in the FBX export options window. + + All export settings are applied with the `FBXExport*` commands prior + to the `FBXExport` call itself. The options can be overridden with their + nice names as seen in the "options" property on this class. + + For more information on FBX exports see: + - https://knowledge.autodesk.com/support/maya/learn-explore/caas + /CloudHelp/cloudhelp/2016/ENU/Maya/files/GUID-6CCE943A-2ED4-4CEE-96D4 + -9CB19C28F4E0-htm.html + - http://forums.cgsociety.org/archive/index.php?t-1032853.html + - https://groups.google.com/forum/#!msg/python_inside_maya/cLkaSo361oE + /LKs9hakE28kJ + + """ + + order = pyblish.api.ExtractorOrder + label = "Extract FBX" + families = ["colorbleed.fbx"] + + @property + def options(self): + """Overridable options for FBX Export + + Given in the following format + - {NAME: EXPECTED TYPE} + + If the overridden option's type does not match, + the option is not included and a warning is logged. + + """ + + return { + "smoothingGroups": bool, + "hardEdges": bool, + "tangents": bool, + "smoothMesh": bool, + "instances": bool, + # "referencedContainersContent": bool, # deprecated in Maya 2016+ + "bakeComplexAnimation": int, + "bakeComplexStart": int, + "bakeComplexEnd": int, + "bakeComplexStep": int, + "bakeResampleAnimation": bool, + "animationOnly": bool, + "useSceneName": bool, + "quaternion": str, # "euler" + "shapes": bool, + "skins": bool, + "constraints": bool, + "lights": bool, + "embeddedTextures": bool, + "inputConnections": bool, + "upAxis": str, # x, y or z, + "triangulate": bool + } + + @property + def default_options(self): + """The default options for FBX extraction. + + This includes shapes, skins, constraints, lights and incoming + connections and exports with the Y-axis as up-axis. + + By default this uses the time sliders start and end time. + + """ + + start_frame = int(cmds.playbackOptions(query=True, + animationStartTime=True)) + end_frame = int(cmds.playbackOptions(query=True, + animationEndTime=True)) + + return { + "smoothingGroups": False, + "hardEdges": False, + "tangents": False, + "smoothMesh": False, + "instances": False, + "bakeComplexAnimation": True, + "bakeComplexStart": start_frame, + "bakeComplexEnd": end_frame, + "bakeComplexStep": 1, + "bakeResampleAnimation": True, + "animationOnly": False, + "useSceneName": False, + "quaternion": "euler", + "shapes": True, + "skins": True, + "constraints": False, + "lights": True, + "embeddedTextures": True, + "inputConnections": True, + "upAxis": "y", + "triangulate": False + } + + def parse_overrides(self, instance, options): + """Inspect data of instance to determine overridden options + + An instance may supply any of the overridable options + as data, the option is then added to the extraction. + + """ + + for key in instance.data(): + if key not in self.options: + continue + + # Ensure the data is of correct type + value = instance.data(key) + if not isinstance(value, self.options[key]): + self.log.warning( + "Overridden attribute {key} was of " + "the wrong type: {invalid_type} " + "- should have been {valid_type}".format( + key=key, + invalid_type=type(value).__name__, + valid_type=self.options[key].__name__)) + continue + + options[key] = value + + return options + + def process(self, instance): + + # Ensure FBX plug-in is loaded + cmds.loadPlugin("fbxmaya", quiet=True) + + # Define output path + directory = self.staging_dir(instance) + filename = "{0}.fbx".format(instance.name) + path = os.path.join(directory, filename) + + # The export requires forward slashes because we need + # to format it into a string in a mel expression + path = path.replace('\\', '/') + + self.log.info("Extracting FBX to: {0}".format(path)) + + members = instance.data("setMembers") + self.log.info("Members: {0}".format(members)) + self.log.info("Instance: {0}".format(instance[:])) + + # Parse export options + options = self.default_options + options = self.parse_overrides(instance, options) + self.log.info("Export options: {0}".format(options)) + + # TODO: Move this out of this plug-in? (Colorbleed) + # Fallback to regular instance start and end frame data names + start = instance.data.get("startFrame", None) + if start is not None: + options['bakeComplexStart'] = start + end = instance.data.get("endFrame", None) + if end is not None: + options['bakeComplexEnd'] = end + + # First apply the default export settings to be fully consistent + # each time for successive publishes + mel.eval("FBXResetExport") + + # Apply the FBX overrides through MEL since the commands + # only work correctly in MEL according to online + # available discussions on the topic + for option, value in options.iteritems(): + key = option[0].upper() + option[1:] # uppercase first letter + + # Boolean must be passed as lower-case strings + # as to MEL standards + if isinstance(value, bool): + value = str(value).lower() + + template = "FBXExport{0} -v {1}" + if key == "UpAxis": + template = "FBXExport{0} {1}" + + cmd = template.format(key, value) + self.log.info(cmd) + mel.eval(cmd) + + # Never show the UI or generate a log + mel.eval("FBXExportShowUI -v false") + mel.eval("FBXExportGenerateLog -v false") + + # Export + with avalon.maya.maintained_selection(): + cmds.select(members, r=1, noExpand=True) + mel.eval('FBXExport -f "{}" -s'.format(path)) + + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + + self.log.info("Extract FBX successful to: {0}".format(path)) From 3ce3d9e8aa0f9dce8da6355bd9315297ee7a6b07 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:14:29 +0200 Subject: [PATCH 043/149] Add FBXExportCameras option with "cameras" boolean key, default False --- colorbleed/plugins/maya/publish/extract_fbx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/maya/publish/extract_fbx.py b/colorbleed/plugins/maya/publish/extract_fbx.py index 4ba28acb58..0ec59a06ee 100644 --- a/colorbleed/plugins/maya/publish/extract_fbx.py +++ b/colorbleed/plugins/maya/publish/extract_fbx.py @@ -46,6 +46,7 @@ class ExtractFBX(colorbleed.api.Extractor): """ return { + "cameras": bool, "smoothingGroups": bool, "hardEdges": bool, "tangents": bool, @@ -87,6 +88,7 @@ class ExtractFBX(colorbleed.api.Extractor): animationEndTime=True)) return { + "cameras": False, "smoothingGroups": False, "hardEdges": False, "tangents": False, From 3224208a6ba931e451db8d3c9e574d8dae41daec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:37:52 +0200 Subject: [PATCH 044/149] Remove redundant OrderedDict() since it's implemented in avalon core --- .../plugins/maya/create/colorbleed_animation.py | 13 ++++--------- colorbleed/plugins/maya/create/colorbleed_camera.py | 7 ++----- colorbleed/plugins/maya/create/colorbleed_look.py | 6 +----- .../plugins/maya/create/colorbleed_renderglobals.py | 6 +----- .../plugins/maya/create/colorbleed_vrayproxy.py | 13 ++++--------- 5 files changed, 12 insertions(+), 33 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index b559e15ec9..6d87cb5078 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import avalon.maya from colorbleed.maya import lib @@ -16,21 +14,18 @@ class CreateAnimation(avalon.maya.Creator): super(CreateAnimation, self).__init__(*args, **kwargs) # create an ordered dict with the existing data first - data = OrderedDict(**self.data) # get basic animation data : start / end / handles / steps for key, value in lib.collect_animation_data().items(): - data[key] = value + self.data[key] = value # Write vertex colors with the geometry. - data["writeColorSets"] = False + self.data["writeColorSets"] = False # Include only renderable visible shapes. # Skips locators and empty transforms - data["renderableOnly"] = False + self.data["renderableOnly"] = False # Include only nodes that are visible at least once during the # frame range. - data["visibleOnly"] = False - - self.data = data \ No newline at end of file + self.data["visibleOnly"] = False diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py index 94c1a82225..f38d8e0d43 100644 --- a/colorbleed/plugins/maya/create/colorbleed_camera.py +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -1,4 +1,3 @@ -from collections import OrderedDict import avalon.maya from colorbleed.maya import lib @@ -15,13 +14,11 @@ class CreateCamera(avalon.maya.Creator): super(CreateCamera, self).__init__(*args, **kwargs) # get basic animation data : start / end / handles / steps - data = OrderedDict(**self.data) animation_data = lib.collect_animation_data() for key, value in animation_data.items(): - data[key] = value + self.data[key] = value # Bake to world space by default, when this is False it will also # include the parent hierarchy in the baked results - data['bakeToWorldSpace'] = True + self.data['bakeToWorldSpace'] = True - self.data = data diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/colorbleed/plugins/maya/create/colorbleed_look.py index d5c0255360..011fdd4f92 100644 --- a/colorbleed/plugins/maya/create/colorbleed_look.py +++ b/colorbleed/plugins/maya/create/colorbleed_look.py @@ -1,4 +1,3 @@ -from collections import OrderedDict import avalon.maya from colorbleed.maya import lib @@ -14,7 +13,4 @@ class CreateLook(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateLook, self).__init__(*args, **kwargs) - data = OrderedDict(**self.data) - data["renderlayer"] = lib.get_current_renderlayer() - - self.data = data + self.data["renderlayer"] = lib.get_current_renderlayer() diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 1d12d9fe9d..7d41c72d3a 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from maya import cmds from avalon.vendor import requests @@ -34,8 +32,7 @@ class CreateRenderGlobals(avalon.maya.Creator): self.data.pop("asset", None) self.data.pop("active", None) - data = OrderedDict(**self.data) - + data = self.data data["suspendPublishJob"] = False data["extendFrames"] = False data["overrideExistingFrame"] = True @@ -49,7 +46,6 @@ class CreateRenderGlobals(avalon.maya.Creator): # We add a string "-" to allow the user to not set any secondary pools data["secondaryPool"] = ["-"] + pools - self.data = data self.options = {"useSelection": False} # Force no content def process(self): diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py b/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py index 85e9e71b6d..e866a1d092 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import avalon.maya @@ -14,13 +12,10 @@ class CreateVrayProxy(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateVrayProxy, self).__init__(*args, **kwargs) - data = OrderedDict(**self.data) - - data["animation"] = False - data["startFrame"] = 1 - data["endFrame"] = 1 + self.data["animation"] = False + self.data["startFrame"] = 1 + self.data["endFrame"] = 1 # Write vertex colors - data["vertexColors"] = False + self.data["vertexColors"] = False - self.data.update(data) From 9b7fc5302b0283508bf01f0cd1d0bd8a74a9d066 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:40:24 +0200 Subject: [PATCH 045/149] Remove OrderedDict and force correct order for animation data - Using a regular `dict` as intermediate type will not force any specific order and thus can have any sorting --- .../plugins/maya/create/colorbleed_yeti_cache.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py index 59d37429bd..2af35a41e6 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py @@ -15,12 +15,13 @@ class CreateYetiCache(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateYetiCache, self).__init__(*args, **kwargs) - data = OrderedDict(**self.data) - data["peroll"] = 0 + self.data["peroll"] = 0 + # Add animation data without step and handles anim_data = lib.collect_animation_data() - data.update({"startFrame": anim_data["startFrame"], - "endFrame": anim_data["endFrame"], - "samples": 3}) + anim_data.pop("step") + anim_data.pop("handles") + self.data.update(anim_data) - self.data = data + # Add samples + self.data["samples"] = 3 From ea865601245ac8ff67558a8a36d66461aefea5aa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:40:51 +0200 Subject: [PATCH 046/149] Fix the YetiCache `preroll` key name so it's actually being picked up :) --- colorbleed/plugins/maya/create/colorbleed_yeti_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py index 2af35a41e6..2430821f5f 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py @@ -15,7 +15,7 @@ class CreateYetiCache(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateYetiCache, self).__init__(*args, **kwargs) - self.data["peroll"] = 0 + self.data["preroll"] = 0 # Add animation data without step and handles anim_data = lib.collect_animation_data() From 601004a9a83ba724084c6b0c09d14614cdb51e09 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:43:38 +0200 Subject: [PATCH 047/149] Cosmetics --- .../maya/create/colorbleed_renderglobals.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 7d41c72d3a..e942a827d7 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -17,7 +17,7 @@ class CreateRenderGlobals(avalon.maya.Creator): # We won't be publishing this one self.data["id"] = "avalon.renderglobals" - # get pools + # Get available Deadline pools AVALON_DEADLINE = api.Session["AVALON_DEADLINE"] argument = "{}/api/pools?NamesOnly=true".format(AVALON_DEADLINE) response = requests.get(argument) @@ -32,19 +32,18 @@ class CreateRenderGlobals(avalon.maya.Creator): self.data.pop("asset", None) self.data.pop("active", None) - data = self.data - data["suspendPublishJob"] = False - data["extendFrames"] = False - data["overrideExistingFrame"] = True - data["useLegacyRenderLayers"] = True - data["priority"] = 50 - data["framesPerTask"] = 1 - data["whitelist"] = False - data["machineList"] = "" - data["useMayaBatch"] = True - data["primaryPool"] = pools + self.data["suspendPublishJob"] = False + self.data["extendFrames"] = False + self.data["overrideExistingFrame"] = True + self.data["useLegacyRenderLayers"] = True + self.data["priority"] = 50 + self.data["framesPerTask"] = 1 + self.data["whitelist"] = False + self.data["machineList"] = "" + self.data["useMayaBatch"] = True + self.data["primaryPool"] = pools # We add a string "-" to allow the user to not set any secondary pools - data["secondaryPool"] = ["-"] + pools + self.data["secondaryPool"] = ["-"] + pools self.options = {"useSelection": False} # Force no content @@ -52,7 +51,8 @@ class CreateRenderGlobals(avalon.maya.Creator): exists = cmds.ls(self.name) assert len(exists) <= 1, ( - "More than one renderglobal exists, this is a bug") + "More than one renderglobal exists, this is a bug" + ) if exists: return cmds.warning("%s already exists." % exists[0]) From 9c815635a85bb0f8b232296558afd18f92fe2f63 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:45:06 +0200 Subject: [PATCH 048/149] Remove Unused Loader (old deprecated code, superseded by load_alembic) --- .../plugins/maya/load/_load_animation.py | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 colorbleed/plugins/maya/load/_load_animation.py diff --git a/colorbleed/plugins/maya/load/_load_animation.py b/colorbleed/plugins/maya/load/_load_animation.py deleted file mode 100644 index f4bcd6881d..0000000000 --- a/colorbleed/plugins/maya/load/_load_animation.py +++ /dev/null @@ -1,48 +0,0 @@ -import colorbleed.maya.plugin - - -class AbcLoader(colorbleed.maya.plugin.ReferenceLoader): - """Specific loader of Alembic for the avalon.animation family""" - - families = ["colorbleed.animation", - "colorbleed.camera", - "colorbleed.pointcache"] - representations = ["abc"] - - label = "Reference animation" - order = -10 - icon = "code-fork" - color = "orange" - - def process_reference(self, context, name, namespace, data): - - import maya.cmds as cmds - from avalon import maya - - cmds.loadPlugin("AbcImport.mll", quiet=True) - # Prevent identical alembic nodes from being shared - # Create unique namespace for the cameras - - # Get name from asset being loaded - # Assuming name is subset name from the animation, we split the number - # suffix from the name to ensure the namespace is unique - name = name.split("_")[0] - namespace = maya.unique_namespace("{}_".format(name), - format="%03d", - suffix="_abc") - - # hero_001 (abc) - # asset_counter{optional} - - nodes = cmds.file(self.fname, - namespace=namespace, - sharedReferenceFile=False, - groupReference=True, - groupName="{}:{}".format(namespace, name), - reference=True, - returnNewNodes=True) - - # load colorbleed ID attribute - self[:] = nodes - - return nodes From cee27ac3e4c482baaac683635fc3316ff788cb83 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Oct 2018 13:10:19 +0200 Subject: [PATCH 049/149] Use new-style Pyblish instance.data (dict) access and use handles in frame range --- .../plugins/maya/publish/extract_fbx.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_fbx.py b/colorbleed/plugins/maya/publish/extract_fbx.py index 0ec59a06ee..139c203672 100644 --- a/colorbleed/plugins/maya/publish/extract_fbx.py +++ b/colorbleed/plugins/maya/publish/extract_fbx.py @@ -120,12 +120,12 @@ class ExtractFBX(colorbleed.api.Extractor): """ - for key in instance.data(): + for key in instance.data: if key not in self.options: continue # Ensure the data is of correct type - value = instance.data(key) + value = instance.data[key] if not isinstance(value, self.options[key]): self.log.warning( "Overridden attribute {key} was of " @@ -156,7 +156,7 @@ class ExtractFBX(colorbleed.api.Extractor): self.log.info("Extracting FBX to: {0}".format(path)) - members = instance.data("setMembers") + members = instance.data["setMembers"] self.log.info("Members: {0}".format(members)) self.log.info("Instance: {0}".format(instance[:])) @@ -165,14 +165,16 @@ class ExtractFBX(colorbleed.api.Extractor): options = self.parse_overrides(instance, options) self.log.info("Export options: {0}".format(options)) - # TODO: Move this out of this plug-in? (Colorbleed) - # Fallback to regular instance start and end frame data names - start = instance.data.get("startFrame", None) - if start is not None: - options['bakeComplexStart'] = start - end = instance.data.get("endFrame", None) - if end is not None: - options['bakeComplexEnd'] = end + # Collect the start and end including handles + start = instance.data["startFrame"] + end = instance.data["endFrame"] + handles = instance.data.get("handles", 0) + if handles: + start -= handles + end += handles + + options['bakeComplexStart'] = start + options['bakeComplexEnd'] = end # First apply the default export settings to be fully consistent # each time for successive publishes From 8ae48b7b66d853efff316a85f27f04ad25e850dc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 22 Oct 2018 11:10:46 +0200 Subject: [PATCH 050/149] Fix regex (PLN-188) --- colorbleed/plugins/global/publish/submit_publish_job.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index e91720a884..ed00cb9343 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -155,14 +155,14 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # Add in regex for sequence filename # This assumes the output files start with subset name and ends with - # a file extension. + # a file extension. The "ext" key includes the dot with the extension. if "ext" in instance.data: - ext = instance.data["ext"].strip(".") + ext = re.escape(instance.data["ext"]) else: ext = "\.\D+" - regex = "^{subset}.*\d+\.{ext}$".format(subset=re.escape(subset), - ext=re.escape(ext)) + regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), + ext=ext) # Remove deadline submission job, not needed in metadata data.pop("deadlineSubmissionJob") From 333a2f5303c741bd29288e57e9ef3e79be211f8c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 22 Oct 2018 11:25:29 +0200 Subject: [PATCH 051/149] Removed check for active as active has been removed from instance --- .../maya/publish/validate_vray_translator_settings.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 305fa23314..a41e8e00e4 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -16,14 +16,8 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): # Check if there are any vray scene instances # The reason to not use host.lsattr() as used in collect_vray_scene # is because that information is already available in the context - vrayscene_instances = [] - for inst in context[:]: - if inst.data["family"] in self.families: - # Skip if instances is inactive - if not inst.data["active"]: - continue - - vrayscene_instances.append(inst) + vrayscene_instances = [i for i in context[:] if i.data["family"] + in self.families] if not vrayscene_instances: self.log.info("No VRay Scene instances found, skipping..") From 612445011d95e032387f6a01af894df0b19848b1 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 22 Oct 2018 11:59:49 +0200 Subject: [PATCH 052/149] Added get_invalid method, improved lookup speed, --- .../publish/validate_vray_translator_settings.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index a41e8e00e4..7f2c58aca5 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -13,14 +13,23 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): def process(self, context): + invalid = self.get_invalid(context) + if invalid: + raise RuntimeError("Found invalid VRay Translator settings!") + + @classmethod + def get_invalid(cls, context): + + invalid = False + # Check if there are any vray scene instances # The reason to not use host.lsattr() as used in collect_vray_scene # is because that information is already available in the context vrayscene_instances = [i for i in context[:] if i.data["family"] - in self.families] + in cls.families] if not vrayscene_instances: - self.log.info("No VRay Scene instances found, skipping..") + cls.log.info("No VRay Scene instances found, skipping..") return # Get vraySettings node From 2e3dbaf047c44c80758ab3dc77fddd1fd2473f5c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 22 Oct 2018 12:00:51 +0200 Subject: [PATCH 053/149] Fixed logging issue, return validation result, changed decorator to staticmethod --- .../publish/validate_vray_translator_settings.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 7f2c58aca5..2a7c1584b1 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -39,17 +39,22 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): node = vray_settings[0] if not cmds.getAttr("{}.vrscene_on".format(node)): - self.info.error("Export vrscene not enabled") + cls.log.error("Export vrscene not enabled") + invalid = True if not cmds.getAttr("{}.misc_eachFrameInFile".format(node)): - self.info.error("Each Frame in File not enabled") + cls.log.error("Each Frame in File not enabled") + invalid = True vrscene_filename = cmds.getAttr("{}.vrscene_filename".format(node)) if vrscene_filename != "vrayscene//_/": - self.info.error("Template for file name is wrong") + cls.log.error("Template for file name is wrong") + invalid = True - @classmethod - def repair(cls, context): + return invalid + + @staticmethod + def repair(): vray_settings = cmds.ls(type="VRaySettingsNode") if not vray_settings: From ef46fa1bcb494588327b28974c8a077c268b68c9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 22 Oct 2018 14:22:42 +0200 Subject: [PATCH 054/149] Pass context along with RepairContextAction and refactor its usage --- colorbleed/action.py | 2 +- colorbleed/plugins/maya/publish/validate_maya_units.py | 2 +- .../plugins/maya/publish/validate_vray_translator_settings.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index ea1f5e13f4..bda3b0d61a 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -87,6 +87,6 @@ class RepairContextAction(pyblish.api.Action): # Apply pyblish.logic to get the instances for the plug-in if plugin in errored_plugins: self.log.info("Attempting fix ...") - plugin.repair() + plugin.repair(context) diff --git a/colorbleed/plugins/maya/publish/validate_maya_units.py b/colorbleed/plugins/maya/publish/validate_maya_units.py index 04db95fdde..ba38fbe512 100644 --- a/colorbleed/plugins/maya/publish/validate_maya_units.py +++ b/colorbleed/plugins/maya/publish/validate_maya_units.py @@ -36,7 +36,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): assert fps and fps == asset_fps, "Scene must be %s FPS" % asset_fps @classmethod - def repair(cls): + def repair(cls, context): """Fix the current FPS setting of the scene, set to PAL(25.0 fps)""" cls.log.info("Setting angular unit to 'degrees'") diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 2a7c1584b1..ecd97e6f95 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -53,8 +53,8 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): return invalid - @staticmethod - def repair(): + @classmethod + def repair(cls, context): vray_settings = cmds.ls(type="VRaySettingsNode") if not vray_settings: From b1facf5d37aa9b3a45012b9ef69b2715f0a04dd9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Oct 2018 12:42:31 +0200 Subject: [PATCH 055/149] Fix published textures sometimes not being remapped correctly (LKD-17) --- .../plugins/maya/publish/extract_look.py | 63 +++++++++++++++---- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index c359fc10b5..04db69a3bd 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -1,5 +1,7 @@ import os import json +import tempfile +import contextlib from collections import OrderedDict from maya import cmds @@ -11,6 +13,38 @@ import colorbleed.api import colorbleed.maya.lib as lib +@contextlib.contextmanager +def no_workspace_dir(): + """Force maya to a fake temporary workspace directory. + + Note: This is not maya.cmds.workspace 'rootDirectory' but the 'directory' + + This helps to avoid Maya automatically remapping image paths to files + relative to the currently set directory. + + """ + + # Store current workspace + original = cmds.workspace(query=True, directory=True) + + # Set a fake workspace + fake_workspace_dir = tempfile.mkdtemp() + cmds.workspace(directory=fake_workspace_dir) + + try: + yield + finally: + try: + cmds.workspace(directory=original) + except RuntimeError: + # If the original workspace directory didn't exist either + # ignore the fact that it fails to reset it to the old path + pass + + # Remove the temporary directory + os.rmdir(fake_workspace_dir) + + class ExtractLook(colorbleed.api.Extractor): """Extract Look (Maya Ascii + JSON) @@ -65,18 +99,23 @@ class ExtractLook(colorbleed.api.Extractor): with lib.renderlayer(layer): # TODO: Ensure membership edits don't become renderlayer overrides with lib.empty_sets(sets, force=True): - with lib.attribute_values(remap): - with avalon.maya.maintained_selection(): - cmds.select(sets, noExpand=True) - cmds.file(maya_path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - channels=True, - constraints=True, - expressions=True, - constructionHistory=True) + # To avoid Maya trying to automatically remap the file + # textures relative to the `workspace -directory` we force + # it to a fake temporary workspace. This fixes textures + # getting incorrectly remapped. (LKD-17, PLN-101) + with no_workspace_dir(): + with lib.attribute_values(remap): + with avalon.maya.maintained_selection(): + cmds.select(sets, noExpand=True) + cmds.file(maya_path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + channels=True, + constraints=True, + expressions=True, + constructionHistory=True) # Write the JSON data self.log.info("Extract json..") From 64dd82d08f55832eb33f1f44620d2cae2d5c0ddd Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 24 Oct 2018 12:35:49 +0200 Subject: [PATCH 056/149] Updated submit to match MayaBatch job, added framesPerTask, improved job name --- .../maya/publish/submit_vray_deadline.py | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index a137a9b5e6..16fa6d02a2 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -51,6 +51,9 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): filename, vrscene_output) + start_frame = int(instance.data["startFrame"]) + end_frame = int(instance.data["endFrame"]) + # Primary job self.log.info("Submitting export job ..") @@ -60,31 +63,35 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "BatchName": batch_name, # Job name, as seen in Monitor - "Name": task_name, + "Name": "{} [{}-{}]".format(task_name, start_frame, end_frame), # Arbitrary username, for visualisation in Monitor "UserName": deadline_user, - "Plugin": "MayaCmd", - "Frames": "1", + "Plugin": "MayaBatch", + "Frames": "{}-{}".format(start_frame, end_frame), + "FramesPerTask": instance.data.get("framesPerTask", 1), "Comment": context.data.get("comment", ""), + + "OutputFilename0": os.path.dirname(first_file), }, "PluginInfo": { + # Renderer + "Renderer": "vray", + # Mandatory for Deadline "Version": cmds.about(version=True), # Input "SceneFile": filepath, - # Output directory and filename - "OutputFilePath": vrscene_output.replace("\\", "/"), - - "CommandLineOptions": self.build_command(instance), - - "UseOnlyCommandLineOptions": True, "SkipExistingFrames": True, + + "UsingRenderLayers": True, + + "UseLegacyRenderLayers": True }, # Mandatory for Deadline, may be empty @@ -133,19 +140,26 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): sequence_filename = ".".join([instance.name, ext]) output_filename = os.path.join(render_ouput, sequence_filename) + # Ensure folder exists: + if not os.path.exists(render_ouput): + os.makedirs(render_ouput) + payload_b = { "JobInfo": { "JobDependency0": dependency["_id"], "BatchName": batch_name, - "Name": "Render {}".format(task_name), + "Name": "Render {} [{}-{}]".format(task_name, + start_frame, + end_frame), "UserName": deadline_user, "Frames": "{}-{}".format(start_frame, end_frame), "Plugin": "Vray", "OverrideTaskExtraInfoNames": False, - "Whitelist": "cb7" + + "OutputFilename0": render_ouput, }, "PluginInfo": { From c3c11f63be7e7c31d7e9d270c46980a008197e90 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 24 Oct 2018 12:37:25 +0200 Subject: [PATCH 057/149] Added vrscene_render_on to validator --- .../maya/publish/validate_vray_translator_settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index ecd97e6f95..f28801b708 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -38,6 +38,10 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): node = vray_settings[0] + if cmds.setAttr("{}.vrscene_render_on".format(node)): + cls.log.error("Render is enabled, this should be disabled") + invalid = True + if not cmds.getAttr("{}.vrscene_on".format(node)): cls.log.error("Export vrscene not enabled") invalid = True @@ -62,6 +66,7 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): else: node = vray_settings[0] + cmds.setAttr("{}.vrscene_render_on".format(node), False) cmds.setAttr("{}.vrscene_on".format(node), True) cmds.setAttr("{}.misc_eachFrameInFile".format(node), True) cmds.setAttr("{}.vrscene_filename".format(node), From 0ebabdb6f2635139818bca3fff7fdc7bffdf831a Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 24 Oct 2018 12:37:53 +0200 Subject: [PATCH 058/149] Added frames per task --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index b9f404e1d3..82f1b11682 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -20,7 +20,8 @@ class CreateVRayScene(avalon.maya.Creator): "suspendRenderJob": False, "suspendPublishJob": False, "extendFrames": False, - "pools": "" + "pools": "", + "framesPerTask": 1 }) self.options = {"useSelection": False} # Force no content From 3aeac688087a117d216005bf4d232bcf3d9cc045 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 24 Oct 2018 14:18:33 +0200 Subject: [PATCH 059/149] Added . to extension to ensure regex works when publishing --- colorbleed/plugins/maya/publish/collect_vray_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index fe1015ee65..c97d7fc44b 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -86,7 +86,7 @@ class CollectVRayScene(pyblish.api.ContextPlugin): "endFrame": end_frame, "renderer": "vray", "resolution": resolution, - "ext": extension, + "ext": ".{}".format(extension), # instance subset "family": "VRay Scene", From a2f764a5acc9ff6097c6ee6320eb7638199e8726 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 13:57:12 +0200 Subject: [PATCH 060/149] Collect machine name, add to context data --- .../plugins/global/publish/collect_machine_name.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 colorbleed/plugins/global/publish/collect_machine_name.py diff --git a/colorbleed/plugins/global/publish/collect_machine_name.py b/colorbleed/plugins/global/publish/collect_machine_name.py new file mode 100644 index 0000000000..e1c4a88669 --- /dev/null +++ b/colorbleed/plugins/global/publish/collect_machine_name.py @@ -0,0 +1,14 @@ +import pyblish.api + + +class CollectMachineName(pyblish.api.ContextPlugin): + label = "Local Machine Name" + order = pyblish.api.CollectorOrder + hosts = ["*"] + + def process(self, context): + import socket + + machine_name = socket.gethostname() + self.log.info("Machine name: %s" % machine_name) + context.data.update({"machine": machine_name}) From 8060691cad1bc064ae0c2f836cba86b7b98e75b2 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 13:57:27 +0200 Subject: [PATCH 061/149] Add machine to version data --- colorbleed/plugins/global/publish/integrate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/integrate.py b/colorbleed/plugins/global/publish/integrate.py index 2dae53335d..5d757b3b1c 100644 --- a/colorbleed/plugins/global/publish/integrate.py +++ b/colorbleed/plugins/global/publish/integrate.py @@ -340,7 +340,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "time": context.data["time"], "author": context.data["user"], "source": source, - "comment": context.data.get("comment")} + "comment": context.data.get("comment"), + "machine": context.data.get("machine")} # Include optional data if present in optionals = ["startFrame", "endFrame", "step", "handles"] From 74e4b3b666a67a525491a66c891c433b40bf7929 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 14:57:40 +0200 Subject: [PATCH 062/149] Added FPS collector for Houdini --- .../houdini/publish/collect_workscene_fps.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/collect_workscene_fps.py diff --git a/colorbleed/plugins/houdini/publish/collect_workscene_fps.py b/colorbleed/plugins/houdini/publish/collect_workscene_fps.py new file mode 100644 index 0000000000..c145eea519 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/collect_workscene_fps.py @@ -0,0 +1,15 @@ +import pyblish.api +import hou + + +class CollectWorksceneFPS(pyblish.api.ContextPlugin): + """Get the FPS of the work scene""" + + label = "Workscene FPS" + order = pyblish.api.CollectorOrder + hosts = ["houdini"] + + def process(self, context): + fps = hou.fps() + self.log.info("Workscene FPS: %s" % fps) + context.data.update({"fps": fps}) From fecf960bc84875c0966a3b0099fe22274dcfa612 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 14:57:55 +0200 Subject: [PATCH 063/149] Added workscene fps collector for Maya --- .../plugins/maya/publish/collect_workscene_fps.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/collect_workscene_fps.py diff --git a/colorbleed/plugins/maya/publish/collect_workscene_fps.py b/colorbleed/plugins/maya/publish/collect_workscene_fps.py new file mode 100644 index 0000000000..41d6ffea33 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_workscene_fps.py @@ -0,0 +1,15 @@ +import pyblish.api +from maya import mel + + +class CollectWorksceneFPS(pyblish.api.ContextPlugin): + """Get the FPS of the work scene""" + + label = "Workscene FPS" + order = pyblish.api.CollectorOrder + hosts = ["maya"] + + def process(self, context): + fps = mel.eval('currentTimeUnitToFPS()') + self.log.info("Workscene FPS: %s" % fps) + context.data.update({"fps": fps}) From 8daafb20b682f6d3de93737453f24d40efcaa30a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 26 Oct 2018 15:04:39 +0200 Subject: [PATCH 064/149] Fix PLN-97 `get_id_required_nodes` correctly preserving filtered input nodes --- colorbleed/maya/lib.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 2ca874dd7d..b23ad97331 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -993,9 +993,14 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): nodes (set): list of filtered nodes """ + lookup = None if nodes is None: # Consider all nodes nodes = cmds.ls() + else: + # Build a lookup for the only allowed nodes in output based + # on `nodes` input of the function (+ ensure long names) + lookup = set(cmds.ls(nodes, long=True)) def _node_type_exists(node_type): try: @@ -1004,8 +1009,8 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): except RuntimeError: return False - # `readOnly` flag is obsolete as of Maya 2016 therefor we explicitly remove - # default nodes and reference nodes + # `readOnly` flag is obsolete as of Maya 2016 therefore we explicitly + # remove default nodes and reference nodes camera_shapes = ["frontShape", "sideShape", "topShape", "perspShape"] ignore = set() @@ -1029,8 +1034,7 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): if cmds.pluginInfo("pgYetiMaya", query=True, loaded=True): types.append("pgYetiMaya") - # We *always* ignore intermediate shapes, so we filter them out - # directly + # We *always* ignore intermediate shapes, so we filter them out directly nodes = cmds.ls(nodes, type=types, long=True, noIntermediate=True) # The items which need to pass the id to their parent @@ -1047,6 +1051,12 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): if not nodes: return nodes + # Ensure only nodes from the input `nodes` are returned when a + # filter was applied on function call because we also iterated + # to parents and alike + if lookup is not None: + nodes &= lookup + # Avoid locked nodes nodes_list = list(nodes) locked = cmds.lockNode(nodes_list, query=True, lock=True) From a186b67d6f8b64bdc59ef911a7bd525c55f4e4e6 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 15:16:57 +0200 Subject: [PATCH 065/149] Added fps to version data --- colorbleed/plugins/global/publish/integrate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/integrate.py b/colorbleed/plugins/global/publish/integrate.py index 5d757b3b1c..869291c91b 100644 --- a/colorbleed/plugins/global/publish/integrate.py +++ b/colorbleed/plugins/global/publish/integrate.py @@ -341,7 +341,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "author": context.data["user"], "source": source, "comment": context.data.get("comment"), - "machine": context.data.get("machine")} + "machine": context.data.get("machine"), + "fps": context.data.get("fps")} # Include optional data if present in optionals = ["startFrame", "endFrame", "step", "handles"] From 40d28e3004c8b187252130e74b748706309ec1cc Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 16:00:27 +0200 Subject: [PATCH 066/149] Added validate_fps and set fps functions --- colorbleed/houdini/lib.py | 48 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/colorbleed/houdini/lib.py b/colorbleed/houdini/lib.py index 78adbc5790..179cb0670d 100644 --- a/colorbleed/houdini/lib.py +++ b/colorbleed/houdini/lib.py @@ -4,15 +4,17 @@ from contextlib import contextmanager import hou +from colorbleed import lib + from avalon import api, io -from avalon.houdini import lib +from avalon.houdini import lib as houdini def set_id(node, unique_id, overwrite=False): exists = node.parm("id") if not exists: - lib.imprint(node, {"id": unique_id}) + houdini.imprint(node, {"id": unique_id}) if not exists and overwrite: node.setParm("id", unique_id) @@ -188,3 +190,45 @@ def attribute_values(node, data): pass finally: node.setParms(previous_attrs) + + +def set_scene_fps(fps): + hou.setFps(fps) + + +# Valid FPS +def validate_fps(): + """Validate current scene FPS and show pop-up when it is incorrect + + Returns: + bool + + """ + + fps = lib.get_asset_fps() + current_fps = hou.fps() # returns float + + if current_fps != fps: + + from ..widgets import popup + + # Find main window + parent = hou.ui.mainQtWindow() + if parent is None: + pass + else: + dialog = popup.Popup2(parent=parent) + dialog.setModal(True) + dialog.setWindowTitle("Maya scene not in line with project") + dialog.setMessage("The FPS is out of sync, please fix") + + # Set new text for button (add optional argument for the popup?) + toggle = dialog.widgets["toggle"] + toggle.setEnabled(False) + dialog.on_show.connect(lambda: set_scene_fps(fps)) + + dialog.show() + + return False + + return True From e5f86242a0a051f4fc8364e5d222dbe77968bd01 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 16:00:57 +0200 Subject: [PATCH 067/149] Added before_save to function for callback --- colorbleed/houdini/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index 4d407af609..e4640e2857 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -35,11 +35,10 @@ def install(): log.info("Installing callbacks ... ") avalon.on("init", on_init) + avalon.before("save", before_save) avalon.on("save", on_save) avalon.on("open", on_open) - log.info("Overriding existing event 'taskChanged'") - log.info("Setting default family states for loader..") avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] @@ -48,6 +47,10 @@ def on_init(*args): houdini.on_houdini_initialize() +def before_save(*args): + return lib.validate_fps() + + def on_save(*args): avalon.logger.info("Running callback on save..") @@ -72,7 +75,6 @@ def on_open(*args): # Get main window parent = hou.ui.mainQtWindow() - if parent is None: log.info("Skipping outdated content pop-up " "because Maya window can't be found.") From f639a0c83a5d52f6cb97c867a370e0913c58ed12 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 1 Nov 2018 09:47:49 +0100 Subject: [PATCH 068/149] Only validate mesh shader connections, ignore curves --- .../maya/publish/validate_mesh_shader_connections.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py b/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py index 2d9d82eaa5..723ff2d3e6 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py @@ -24,7 +24,7 @@ def get_invalid_sets(shape): """ invalid = [] - sets = cmds.listSets(object=shape, t=1, extendToShape=False) + sets = cmds.listSets(object=shape, t=1, extendToShape=False) or [] for s in sets: members = cmds.sets(s, query=True, nodesOnly=True) if not members: @@ -93,7 +93,9 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): def get_invalid(instance): shapes = cmds.ls(instance[:], dag=1, leaf=1, shapes=1, long=True) - shapes = cmds.ls(shapes, shapes=True, noIntermediate=True, long=True) + + # todo: allow to check anything that can have a shader + shapes = cmds.ls(shapes, noIntermediate=True, long=True, type="mesh") invalid = [] for shape in shapes: From c0c5e7090711ea6ed42f736b9e1393c5168df38f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 1 Nov 2018 09:48:16 +0100 Subject: [PATCH 069/149] Yeti rig input collection, only search connections to dagNodes --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index c0d7cba2d2..c673581c8a 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -45,6 +45,9 @@ class CollectYetiRig(pyblish.api.InstancePlugin): source=True, destination=False, connections=True, + # Only allow inputs from dagNodes + # (avoid display layers, etc.) + type="dagNode", plugs=True) or [] # Group per source, destination pair. We need to reverse the connection From eb79702951147453ddb26a87a1d27028f600f6d6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 1 Nov 2018 10:00:49 +0100 Subject: [PATCH 070/149] Fix bug on shader context manager (introduced when embedding dependencies) --- colorbleed/maya/lib.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 2ca874dd7d..a0f9823f88 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -541,7 +541,6 @@ def get_shader_assignments_from_shapes(shapes): shapes = cmds.ls(shapes, long=True, - selection=True, shapes=True, objectsOnly=True) if not shapes: @@ -560,7 +559,7 @@ def get_shader_assignments_from_shapes(shapes): type="shadingEngine") or [] shading_groups = list(set(shading_groups)) for shading_group in shading_groups: - assignments[shading_group].add(shape) + assignments[shading_group].append(shape) return dict(assignments) @@ -569,7 +568,7 @@ def get_shader_assignments_from_shapes(shapes): def shader(nodes, shadingEngine="initialShadingGroup"): """Assign a shader to nodes during the context""" - shapes = cmds.ls(nodes, dag=1, o=1, shapes=1, long=1) + shapes = cmds.ls(nodes, dag=1, objectsOnly=1, shapes=1, long=1) original = get_shader_assignments_from_shapes(shapes) try: @@ -582,7 +581,7 @@ def shader(nodes, shadingEngine="initialShadingGroup"): # Assign original shaders for sg, members in original.items(): if members: - cmds.sets(shapes, edit=True, forceElement=shadingEngine) + cmds.sets(members, edit=True, forceElement=sg) @contextlib.contextmanager From f6a99d835fa01f328f73e7a8f2c074f8bee2250a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 1 Nov 2018 10:02:01 +0100 Subject: [PATCH 071/149] Force load objExport plug-in at Maya launch --- colorbleed/maya/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 86106ee593..4e7bb677b4 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -98,9 +98,14 @@ def on_init(_): utils.executeDeferred(_fn) except Exception as exc: print(exc) - + + # Force load Alembic so referenced alembics + # work correctly on scene open cmds.loadPlugin("AbcImport", quiet=True) cmds.loadPlugin("AbcExport", quiet=True) + + # Force load objExport plug-in (requested by artists) + cmds.loadPlugin("objExport", quiet=True) from .customize import override_component_mask_commands safe_deferred(override_component_mask_commands) From e8833e207e84694094be24bf7dabaec84ee42cc1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 4 Nov 2018 14:55:20 +0100 Subject: [PATCH 072/149] PLN-25 Ensure Creator plug-ins in Maya undo in one go. --- .../plugins/maya/create/colorbleed_renderglobals.py | 8 +++++--- colorbleed/plugins/maya/create/colorbleed_rig.py | 12 +++++++----- .../plugins/maya/create/colorbleed_yeti_rig.py | 11 ++++++----- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index e942a827d7..9fc3e0ceab 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -1,5 +1,7 @@ from maya import cmds +import colorbleed.maya.lib as lib + from avalon.vendor import requests import avalon.maya from avalon import api @@ -57,6 +59,6 @@ class CreateRenderGlobals(avalon.maya.Creator): if exists: return cmds.warning("%s already exists." % exists[0]) - super(CreateRenderGlobals, self).process() - - cmds.setAttr("{}.machineList".format(self.name), lock=True) + with lib.undo_chunk(): + super(CreateRenderGlobals, self).process() + cmds.setAttr("{}.machineList".format(self.name), lock=True) diff --git a/colorbleed/plugins/maya/create/colorbleed_rig.py b/colorbleed/plugins/maya/create/colorbleed_rig.py index 6947aaac31..1212abb6ce 100644 --- a/colorbleed/plugins/maya/create/colorbleed_rig.py +++ b/colorbleed/plugins/maya/create/colorbleed_rig.py @@ -1,5 +1,6 @@ from maya import cmds +import colorbleed.maya.lib as lib import avalon.maya @@ -12,10 +13,11 @@ class CreateRig(avalon.maya.Creator): icon = "wheelchair" def process(self): - instance = super(CreateRig, self).process() - self.log.info("Creating Rig instance set up ...") + with lib.undo_chunk(): + instance = super(CreateRig, self).process() - controls = cmds.sets(name="controls_SET", empty=True) - pointcache = cmds.sets(name="out_SET", empty=True) - cmds.sets([controls, pointcache], forceElement=instance) + self.log.info("Creating Rig instance set up ...") + controls = cmds.sets(name="controls_SET", empty=True) + pointcache = cmds.sets(name="out_SET", empty=True) + cmds.sets([controls, pointcache], forceElement=instance) diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py b/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py index 3b21c586a7..55051100ad 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py @@ -1,5 +1,6 @@ from maya import cmds +import colorbleed.maya.lib as lib import avalon.maya @@ -12,9 +13,9 @@ class CreateYetiRig(avalon.maya.Creator): def process(self): - instance = super(CreateYetiRig, self).process() + with lib.undo_chunk(): + instance = super(CreateYetiRig, self).process() - self.log.info("Creating Rig instance set up ...") - - input_meshes = cmds.sets(name="input_SET", empty=True) - cmds.sets(input_meshes, forceElement=instance) + self.log.info("Creating Rig instance set up ...") + input_meshes = cmds.sets(name="input_SET", empty=True) + cmds.sets(input_meshes, forceElement=instance) From 03f267b716c10140114191e2dc9869da6fa9a70a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 5 Nov 2018 21:37:58 +0100 Subject: [PATCH 073/149] Fix PLN-196: Pointcache creator attributes in correct order --- .../maya/create/colorbleed_pointcache.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index 02deb0a469..495c433e87 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -13,14 +13,13 @@ class CreatePointCache(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreatePointCache, self).__init__(*args, **kwargs) - data = {"writeColorSets": False, # Vertex colors with the geometry. - "renderableOnly": False, # Only renderable visible shapes - "visibleOnly": False, # only nodes that are visible - "attr": "", # Add options for custom attributes - "attrPrefix": ""} + # Add animation data + self.data.update(lib.collect_animation_data()) - # get basic animation data : start / end / handles / steps - for key, value in lib.collect_animation_data().items(): - data[key] = value + self.data["writeColorSets"] = False # Vertex colors with the geometry. + self.data["renderableOnly"] = False # Only renderable visible shapes + self.data["visibleOnly"] = False # only nodes that are visible - self.data.update(data) + # Add options for custom attributes + self.data["attr"] = "" + self.data["attrPrefix"] = "" From 002e612f81abb1244e5e987a11a79c0df453307e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 5 Nov 2018 21:49:53 +0100 Subject: [PATCH 074/149] Fix PLN-195: Ignore empty values in list type options in extract_alembic --- colorbleed/maya/lib.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 2ca874dd7d..4bb68c3ef6 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -927,6 +927,18 @@ def extract_alembic(file, raise TypeError("Alembic option unsupported type: " "{0} (expected {1})".format(value, valid_types)) + # Ignore empty values, like an empty string, since they mess up how + # job arguments are built + if isinstance(value, (list, tuple)): + value = [x for x in value if x.strip()] + + # Ignore option completely if no values remaining + if not value: + options.pop(key) + continue + + options[key] = value + # The `writeCreases` argument was changed to `autoSubd` in Maya 2018+ maya_version = int(cmds.about(version=True)) if maya_version >= 2018: From d21d1bea88a6d87764ade6d6cc386d0723734888 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 5 Nov 2018 21:54:12 +0100 Subject: [PATCH 075/149] Ignore empty attribute entries in extract_pointcache plug-in --- colorbleed/plugins/maya/publish/extract_pointcache.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index ccf222408f..4cd894d7f4 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -33,9 +33,11 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): end += handles attrs = instance.data.get("attr", "").split(";") + attrs = [value for value in attrs if value.strip()] attrs += ["cbId"] attr_prefixes = instance.data.get("attrPrefix", "").split(";") + attr_prefixes = [value for value in attr_prefixes if value.strip()] # Get extra export arguments writeColorSets = instance.data.get("writeColorSets", False) From b767b7b472ea04d4ceec51174fb814ad838100c1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 6 Nov 2018 09:32:18 +0100 Subject: [PATCH 076/149] Fix PLN-197: Return component assignments with `get_shader_assignments_from_shapes` --- colorbleed/maya/lib.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 1d632cf0e1..7fa1a3a16d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -519,12 +519,15 @@ def no_undo(flush=False): cmds.undoInfo(**{keyword: original}) -def get_shader_assignments_from_shapes(shapes): +def get_shader_assignments_from_shapes(shapes, components=True): """Return the shape assignment per related shading engines. Returns a dictionary where the keys are shadingGroups and the values are lists of assigned shapes or shape-components. + Since `maya.cmds.sets` returns shader members on the shapes as components + on the transform we correct that in this method too. + For the 'shapes' this will return a dictionary like: { "shadingEngineX": ["nodeX", "nodeY"], @@ -533,6 +536,7 @@ def get_shader_assignments_from_shapes(shapes): Args: shapes (list): The shapes to collect the assignments for. + components (bool): Whether to include the component assignments. Returns: dict: The {shadingEngine: shapes} relationships @@ -561,6 +565,36 @@ def get_shader_assignments_from_shapes(shapes): for shading_group in shading_groups: assignments[shading_group].append(shape) + if components: + # Note: Components returned from maya.cmds.sets are "listed" as if + # being assigned to the transform like: pCube1.f[0] as opposed + # to pCubeShape1.f[0] so we correct that here too. + + # Build a mapping from parent to shapes to include in lookup. + transforms = {shape.rsplit("|", 1)[0]: shape for shape in shapes} + lookup = set(shapes + transforms.keys()) + + component_assignments = defaultdict(list) + for shading_group in assignments.keys(): + members = cmds.ls(cmds.sets(shading_group, query=True), long=True) + for member in members: + + node = member.split(".", 1)[0] + if node not in lookup: + continue + + # Component + if "." in member: + component = member.split(".", 1)[1] + + # Fix transform to shape as shaders are assigned to shapes + if node in transforms: + shape = transforms[node] + member = "{0}.{1}".format(shape, component) + + component_assignments[shading_group].append(member) + assignments = component_assignments + return dict(assignments) From 4b2e9de4c43940695ac4d024117fab0a0eed9f7a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 6 Nov 2018 09:41:11 +0100 Subject: [PATCH 077/149] Move `component` variable more local to where it's used. --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 7fa1a3a16d..8395d5fe43 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -585,11 +585,11 @@ def get_shader_assignments_from_shapes(shapes, components=True): # Component if "." in member: - component = member.split(".", 1)[1] # Fix transform to shape as shaders are assigned to shapes if node in transforms: shape = transforms[node] + component = member.split(".", 1)[1] member = "{0}.{1}".format(shape, component) component_assignments[shading_group].append(member) From b9975d38f28751921f4e0df99157b0d92904179a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 6 Nov 2018 10:01:35 +0100 Subject: [PATCH 078/149] Fix PLN-194: Add Validator for case where FBX Export fails to export skinning. This captures the failure case of FBX when mesh is missing in deformer set for a skinCluster, thus the skinning is not exported along with the FBX export. --- .../validate_skinCluster_deformer_set.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_skinCluster_deformer_set.py diff --git a/colorbleed/plugins/maya/publish/validate_skinCluster_deformer_set.py b/colorbleed/plugins/maya/publish/validate_skinCluster_deformer_set.py new file mode 100644 index 0000000000..61ccb8f29a --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_skinCluster_deformer_set.py @@ -0,0 +1,72 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api +import colorbleed.maya.action + + +class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin): + """Validate skinClusters on meshes have valid member relationships. + + In rare cases it can happen that a mesh has a skinCluster in its history + but it is *not* included in the deformer relationship history. If this is + the case then FBX will not export the skinning. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.fbx'] + label = "Skincluster Deformer Relationships" + actions = [colorbleed.maya.action.SelectInvalidAction] + + def process(self, instance): + """Process all the transform nodes in the instance""" + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Invalid skinCluster relationships " + "found on meshes: {0}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + + meshes = cmds.ls(instance, type="mesh", noIntermediate=True, long=True) + invalid = list() + + for mesh in meshes: + history = cmds.listHistory(mesh) or [] + skins = cmds.ls(history, type="skinCluster") + + # Ensure at most one skinCluster + assert len(skins) <= 1, "Cannot have more than one skinCluster" + + if skins: + skin = skins[0] + + # Ensure the mesh is also in the skinCluster set + # otherwise the skin will not be exported correctly + # by the FBX Exporter. + deformer_sets = cmds.listSets(object=mesh, type=2) + for deformer_set in deformer_sets: + used_by = cmds.listConnections(deformer_set + ".usedBy", + source=True, + destination=False) + + # Ignore those that don't seem to have a usedBy connection + if not used_by: + continue + + # We have a matching deformer set relationship + if skin in set(used_by): + break + + else: + invalid.append(mesh) + cls.log.warning( + "Mesh has skinCluster in history but is not included " + "in its deformer relationship set: " + "{0} (skinCluster: {1})".format(mesh, skin) + ) + + return invalid From 46936419b6aaa0efb7f634b2683939ce1c7d388a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 10 Nov 2018 17:42:05 +0100 Subject: [PATCH 079/149] Fix REN-58: VRay Scene collection fails on masterLayer --- colorbleed/plugins/maya/publish/collect_vray_scene.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index c97d7fc44b..2f7d4c2839 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -75,11 +75,12 @@ class CollectVRayScene(pyblish.api.ContextPlugin): render_layers = sorted(render_layers, key=sort_by_display_order) for layer in render_layers: - if layer.endswith("defaultRenderLayer"): - layer = "masterLayer" + subset = layer + if subset == "defaultRenderLayer": + subset = "masterLayer" data = { - "subset": layer, + "subset": subset, "setMembers": layer, "startFrame": start_frame, @@ -102,6 +103,6 @@ class CollectVRayScene(pyblish.api.ContextPlugin): data.update(vrscene_data) - instance = context.create_instance(layer) + instance = context.create_instance(subset) self.log.info("Created: %s" % instance.name) instance.data.update(data) From 7fcb0743517c08d09d2ed86aa3c58e2c294936b1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 10 Nov 2018 17:42:35 +0100 Subject: [PATCH 080/149] Assert vraySettings node exists --- colorbleed/plugins/maya/publish/collect_vray_scene.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index 2f7d4c2839..612670478e 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -37,6 +37,11 @@ class CollectVRayScene(pyblish.api.ContextPlugin): vrscene_data = host.read(vray_scene) + assert cmds.ls("vraySettings", type="VRaySettingsNode"), ( + "VRay Settings node does not exists. " + "Please ensure V-Ray is the current renderer." + ) + # Output data start_frame = int(cmds.getAttr("defaultRenderGlobals.startFrame")) end_frame = int(cmds.getAttr("defaultRenderGlobals.endFrame")) From 16c5015faa81dee63a23837c729c433eef354ecf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 10 Nov 2018 17:46:31 +0100 Subject: [PATCH 081/149] Fix REN-57: Ignore VRayTranslator Settings validator when instances are off --- .../maya/publish/validate_vray_translator_settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index f28801b708..6ed1637e2d 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -32,6 +32,11 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): cls.log.info("No VRay Scene instances found, skipping..") return + # Ignore if no VRayScenes are enabled for publishing + if not any(i.data.get("publish", True) for i in vrayscene_instances): + cls.log.info("VRay Scene instances are disabled, skipping..") + return + # Get vraySettings node vray_settings = cmds.ls(type="VRaySettingsNode") assert vray_settings, "Please ensure a VRay Settings Node is present" From 1b35bf1c8cad8a18e3d9de40497c38ebc5adecab Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 10 Nov 2018 17:51:55 +0100 Subject: [PATCH 082/149] LKD-31: Improve Validate Look Sets docstring for artists for readability --- colorbleed/plugins/maya/publish/validate_look_sets.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index 1e7645bfaf..3f4bc99059 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -6,11 +6,11 @@ import colorbleed.api class ValidateLookSets(pyblish.api.InstancePlugin): - """Validate if any sets are missing from the instance and look data + """Validate if any sets relationships are not being collected. A shader can be assigned to a node that is missing a Colorbleed ID. Because it is missing the ID it has not been collected in the instance. - This validator ensures no relationships and thus considers it invalid + This validator ensures those relationships and thus considers it invalid if a relationship was not collected. When the relationship needs to be maintained the artist might need to @@ -25,8 +25,10 @@ class ValidateLookSets(pyblish.api.InstancePlugin): - Displacement objectSets (like V-Ray): - It is best practice to add the transform group of the shape to the - displacement objectSet. + It is best practice to add the transform of the shape to the + displacement objectSet. Any parent groups will not work as groups + do not receive a Colorbleed Id. As such the assignments need to be + made to the shapes and their transform. Example content: [asset_GRP|geometry_GRP|body_GES, From e795cd90d7ab4500be9acad30a08517d8fcf9457 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Nov 2018 23:37:13 +0100 Subject: [PATCH 083/149] PLN-198: Add quick icons for Loader/Manager in toolbox in Maya (draft) --- colorbleed/maya/__init__.py | 12 +++-- colorbleed/maya/customize.py | 76 +++++++++++++++++++++++++++- res/icons/colorbleed_logo_36x36.png | Bin 0 -> 20198 bytes res/icons/inventory.png | Bin 0 -> 15767 bytes res/icons/loader.png | Bin 0 -> 408 bytes 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 res/icons/colorbleed_logo_36x36.png create mode 100644 res/icons/inventory.png create mode 100644 res/icons/loader.png diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 4e7bb677b4..6e6d0ca869 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -98,18 +98,24 @@ def on_init(_): utils.executeDeferred(_fn) except Exception as exc: print(exc) - + # Force load Alembic so referenced alembics # work correctly on scene open cmds.loadPlugin("AbcImport", quiet=True) cmds.loadPlugin("AbcExport", quiet=True) - + # Force load objExport plug-in (requested by artists) cmds.loadPlugin("objExport", quiet=True) - from .customize import override_component_mask_commands + from .customize import ( + override_component_mask_commands, + override_toolbox_ui + ) safe_deferred(override_component_mask_commands) + if not IS_HEADLESS: + safe_deferred(override_toolbox_ui) + def on_before_save(return_code, _): """Run validation for scene's FPS prior to saving""" diff --git a/colorbleed/maya/customize.py b/colorbleed/maya/customize.py index 64f33d5aae..4de78fba43 100644 --- a/colorbleed/maya/customize.py +++ b/colorbleed/maya/customize.py @@ -3,6 +3,7 @@ import maya.cmds as mc import maya.mel as mel from functools import partial +import os import logging @@ -17,7 +18,7 @@ def override_component_mask_commands(): This implements special behavior for Maya's component mask menu items where a ctrl+click will instantly make it an isolated behavior disabling all others. - + Tested in Maya 2016 and 2018 """ @@ -64,3 +65,76 @@ def override_component_mask_commands(): original = COMPONENT_MASK_ORIGINAL[btn] new_fn = partial(on_changed_callback, original) mc.iconTextCheckBox(btn, edit=True, cc=new_fn) + + +def override_toolbox_ui(): + """Add custom buttons in Toolbox as replacement for Maya web help icon.""" + + import colorbleed + res = os.path.join(os.path.dirname(os.path.dirname(colorbleed.__file__)), + "res") + icons = os.path.join(res, "icons") + + import avalon.tools.cbsceneinventory as inventory + import avalon.tools.cbloader as loader + + # Ensure the maya web icon on toolbox exists + web_button = "ToolBox|MainToolboxLayout|mayaWebButton" + if not mc.iconTextButton(web_button, query=True, exists=True): + return + + mc.iconTextButton(web_button, edit=True, visible=False) + + # real = 32, but 36 with padding - according to toolbox mel script + icon_size = 36 + parent = web_button.rsplit("|", 1)[0] + + # Ensure the parent is a formLayout + if not mc.objectTypeUI(parent) == "formLayout": + return + + # Create our controls + controls = [] + + control = mc.iconTextButton( + "colorbleed_toolbox_loader", + annotation="Loader", + label="Loader", + image=os.path.join(icons, "loader.png"), + command=lambda: loader.show(use_context=True), + width=icon_size, + height=icon_size, + parent=parent) + controls.append(control) + + control = mc.iconTextButton( + "colorbleed_toolbox_manager", + annotation="Inventory", + label="Inventory", + image=os.path.join(icons, "inventory.png"), + command=lambda: inventory.show(), + width=icon_size, + height=icon_size, + parent=parent) + controls.append(control) + + control = mc.iconTextButton( + "colorbleed_toolbox", + annotation="Colorbleed", + label="Colorbleed", + image=os.path.join(icons, "colorbleed_logo_36x36.png"), + width=icon_size, + height=icon_size, + parent=parent) + controls.append(control) + + # Add the buttons on the bottom and stack + # them above each other with side padding + controls.reverse() + for i, control in enumerate(controls): + previous = controls[i - 1] if i > 0 else web_button + + mc.formLayout(parent, edit=True, + attachControl=[control, "bottom", 1, previous], + attachForm=([control, "left", 1], + [control, "right", 1])) diff --git a/res/icons/colorbleed_logo_36x36.png b/res/icons/colorbleed_logo_36x36.png new file mode 100644 index 0000000000000000000000000000000000000000..847a85c2282d4acd024bc8d719877c09660581df GIT binary patch literal 20198 zcmeHPdvH|ebwBs+d-ax(0Aa+k0f*OWclBCH7NQv00XxFf;2L)(ZC1N0*dl2aX)&@h z(>Ter)BMqNCevv%aobGWNvG3s(@duAB!RJ=I@l$lWhP?`#E6H4ct{9@q}9If_IK_+ zv|QkZHtEEF+;2F0@AsYWJLmWP&N<(``s4DoeS1GsX05jpQQ4l|Jul+AfyHdZ?^XKd zH*v8H?|x;JsHBEPpl|)vDx!|t!2<`w2YWx;=?e`udi|lkK;zipFlG~V?HC*O`i=y` zw!Xlj;Lvva-Q(}rZ9)Hb`^&ApuHNBYfy2Swua5-wzrOc?@AV_T4!?azx3z1m69xtY zVXtj$aA0V(b8Nd^w%dt&9(LMo@|5tA?e?AQ&~~tQpKVuYBw%Z8Y;*WrZ62GaqtV^! z@w9lh*_vGLR;R1k+3a#OHFvhSI$N4-xn}RS;;w7N-{1LS&$Bsq=xw(j4u^+3oz7#& zjx`=@ZVZhaa=JS@I-IU1XH$~{GaRF@4TZg9j-k;{WsT(R^aMtIBf;TtFf?RiyWYOg z(eQS=ogEcgZeN4L1xG`pIqtxsbId#JbT_)34_oB-71j(N9T||J`F+m7KwvO16duJo z_e0kW9}a~>qlZI}a9L14(lKJv+gtGcP`rbK4|P2ne&$tBkfZdWD@PB!HXLxi7#Iy5 z9q|R8c@8@(Zau>UosjV2^?YiDbFv5lgcN*kyjzc=iCEGxxxi|qJ)o&BMaL2tM_ zIOshTa1IY0>T)g*7R@YVRp+kIKxhQ80^QAB&W8;bt&%gXbI;Id*gND4?CI&oKw~iI z@AL)Q+PvT$HYTLO-bX0O-L*Wc3A=l1vmp61rPi-(wbz}4>H zD6;jnqAW%(%O~6Zz!&Zx%_|0i@(DHU9T^R9{cX4B3$Spc6;3Q(BiC`K*T-jDw~xys z;O}zYH@Xa~urjwS_+U66kAkfIqUDeFaqMtlNUglGrsP?VHZmIO4}ykN5p}*$Z^HwYKufr5skS*cTiN3>@qU;v633 z^Q5^gw??UW=)zYXwXjH$TbK=1(QBDpgV|BM^x=KeS$N@mpgaqw*st?uc}Kras|s71 z69pmaU5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia# zU5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zev zeiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}b zE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia# zU5zeveiT}bE(Ia#U5zeveiT}bE(Ia#T~)fQ%b#!thVb3yF?^u;R?V(2;Nwo4Z}*G6 zM6YiodgH4^@eHoFh>p65raw>Ai4R6shQ9IpC!Zx+fiJCg9vBnIzv9gqA(O$>Pf!y- zL0DucpEyAJiOEUgu!(|5m6ersdUNTfJ9G2Xl9aHR^nz$H8dGMAC1TL)m#_tvSTdE0 zqh(Ub6s4tff#kHcnv9qKl}g|4rHmfKItXWkP?+*dZ+IE$^*YjtqV@b#Z0G!)@$U0y zPF@@TcxoH6EWM((b^&q+GE-GqF^%POiDWV!i9{$>oQ^4I zGPFM<5OSNGB#{-sZVGxjj{aXmZa^MrdS(Z^7cSh8HqU+VhtF?~fA4p;R@Itr8{@Yp z-c8(^6e=et>+R$3ls$1}USG0MnTf5dGt#E&>YBu=`ua#iL&F{0x^>qZ*49r+M!Fa= zJwcSDJ-TxG6Dp-ffj`3rLzyIzv_wxz5$Yz9UI(xd0Oqf=?`y)c#*i221AInV4ydk`?5SI_aof6d6Q0G zmNtn$J^GdBEZ=!4ik&3ph(u#4>Fd^j=@N>5I7`ycu2SY2;!{nD^Xok)&#diucdE+v zae4e+_02iULiysQ_5O8#JJ7s2x`HZU9UGGy=MMwoKHr-mQIH5*IZy|n|7VQKO_-Rv zZaHykYG>QWzxDO4Ya^dd>FOwXYgz`CN*hQpt{_8o4W(jwn*YT-rOsX>^R)y;DoHxM zVf&9xta|RP%_S4|BMsjkwyud;iF2GCGb3EXEP1A~d4*5t0VV=aQgmVkgEp#&;0nVuyx@@yERe+(i0^lZdf znmB7O{mkF#sA4yd5k2ud88>Vr!{yV2M5XlN5*UIc=g3%HLKW+(DA~Q9OtA=Aq^VlE zhc$d4ltMTp44_!@pz}!C zz*7|977!Z)5HVvIE`ZA$;#`9ol_p0Sy%r%&XGOtSX12`yTNzFLJ;rh^ahGtQQ6`_1 ze2>KiVWX@|0qqi;-<_k({48aRxL*w}8<0>dkswyyJ}BXELWT1wyKmT%0dncX4icNh z1l4mv;NhSb4F-dW-X9?H;P5EhM44OfW*z2sA@6}fl1_u$clPiRb5RaZbUeh_fmFWf*$cOKuU7?mIP=CMmgMZo`*gn z%z&ab&y;3XH~`30G8a#om)Kc`<6XBIxo}RND1*V80w4y(!i{&C7a0%(gXl1US;K=Z zlSz;uft?iMl?Mm*$OuO!%+aM$Cn-D?7HQ_*V#U(c=u@J}Qa-(ush*`d*y;Ed341>x zs(%T2!A9Yfg_)8C4wDMW&*3YBPeGrCV0l3#>^21|5;&-0a1=!}@L=Nwm`NF=+_{?+ zzdmuzES8p-?k$)$Tp-X!wQcwTuywzkO~RZruCz=-KxN@t5cDx71C&8vVV+F}#KNp| znDgM+IOkA;cNfKJy+FdkB2El_Vn%BCuhrH1%VL!|NsE>3CujHsJq{or{sm<|LdBV3 zkby8G3B)9h>wSXxGPpc2%*nIFAY{pcx-aAm8pE7-X&hU;iA$+1KTno#Ia^_&+j>KK zfuy?Tx5uw+Iahn-qs=saMBap0p_ADlW+CS&lMe;#uu(2qk}CzfW)|cc1`>A2KOzP( zhjU#Xl0ym=PKFF3CNLnh^O+s*udQEs&1$vGi?LXgR@GKcU#WQcAL2X#6($&UBrU=O zhXU+o!-achhymuVGJCn4k`GXhgd8qh3k+dCgXP8>Lzi0$X~c*2#rW!pnabzh*|OGz z$Ads3UPq})yi2k5FTD9deDgWNQ-L>5F@z!t;a$OL24Rzd#c*XEK*@auECWYhW)dFx zs9+FzVGx`WtOuibRFEFwX8qgA&wXd(#*ODIN-eh&IIl!0ouMdRKR2u?yY=&iu`gbk z(Mc4`Wj_EB0LaP8%m9ciaxP&!31Kj~kY-tdmpl-@LS3j*R6G|~1_gvSMmn+3eLTKq z{}0@D^R+kl!4?M&*8NU1{+DC>h&=9;llb0KP?&k^PNrgzc3n2i%Iz7XNZ`|)nz0a zrv}Rc)p=g|(>E=D@U>frsE9`a$64g?a>qYfLwzu{i2MBoU(X_Vw{{fD>>$q^Z6_OfA;9Po0>qSg4-; z(6u@pu_9~WST6&UIF~pMKq~O5%vY{7z5=WKq-29oi5O>HVo4-J41a22_`Q3Lr+#Su z@>jdNp8C&i>q;k=qD&tz6XfU?0Lh~m;>3EQTug|o9H&o2H;hl;o?cZSyWUWWKO0g4 zjsmbH{gJCatWMskcIE-^yb^7bE z1RfoHCKNT{$M5~5xQhuANok2Jc+a^L7j-|ocyqIG?uQ4PrjPGyO0_Nv*CS(smlklAd0 zd_ZYDiTORnWHiyGd+E}TuTE|&x%}Sq_S>g=?8z%z>-BSG6~+wle}AQeaME%~b79`j z7?Ts}y2*=~C(m9sw4J0?o_BY&Zo1_DblpremZYVnB^k*73Xp^cVlu_?70HOdYz3W; zt}h9uiRroe^z?XR$-PUB)}xD#xMM&0^8WLu_Y9x}_6acISpwiM(yaJ_VFpmVJOe!GwjW({4PgK(Y{jzYN cNRe@iH~yn<<^DH#@}6h*_I&s0&wu5A0OFGkkpKVy literal 0 HcmV?d00001 diff --git a/res/icons/inventory.png b/res/icons/inventory.png new file mode 100644 index 0000000000000000000000000000000000000000..34f6488233b392963ce954263e0218f0e0079885 GIT binary patch literal 15767 zcmeI3XH*m07RSeRuWvnlV#i)$>?KSlB&3)`fqQPFoMKynDdaou%4&a9PW=bZoE`<<^W}_;jA-RkvRX+5L43l)G*RG} z$9(?#7x>mJ8g&@_l(2-w*%0J^6_`&kWM*a+1d*?xVq)#F+O`TkZSunjT1WahOctO< z5M>L81=lB%cAk!mr_3t;o1J_4Jc>~9J4v)cttFUDpu*CuWOQ0Yj6N+};d_I%N z_2l)-WYKC}sm!)KdVnM9z%3}|Cqzvq)SIWx9!0tv>DAK~lWHN+DAGnJS@k54*YzGK zBpI|WueW~?C?uIQ+r5SYWA^(%-trTAmyadMYIK4o^eAa0O(2gAFu=S5uq4oS+Lk~U zkwkf|xsjdV~&xG^=kZq7NCJJd=5MXOb`vr=IC<3!8*rUKC?hd}%wMb*$fSDkS0^{fNl}1AEWwm$kzw4kDle6n6v2TanNTQ~ zglYoC7#0!`Bn}M>kxC?ifdN8U5Eh&t9&#-dHr#Bp<7PdX*K!K9oRsP$asnp>LVqD4 zFo?yV`Iv+h$Z?%mXuxzNNw``LxfcqpA+1z0NrYNylXJmXaI1}ECa;R0KYLv(ERU*l z`YWiyZ6d^g8-W=FLW$V!p~OkduU1tV9dByW0d3D08cce>zr$+}+V# zq}^REJXyjho1M0%LKRG-n5`t5>|L-lzrslUAvqU6u<8}Kp4p_-HXP7rpg?8rCg*xE ziE;&Ap#RxKDCAyrj+6w_tOmQbOn_kmOcW#XSBRtvOw^RwzJ>Wsb*hREVxtZA6x>P% z#)Ac?&OIg~^LGtia4>T$NO_GqQD%Zpu{F+*TF|ZN@cIPY98VHzG~Y45z|%#EGDByi z3XWWEX~6-_pB;rK0JF9g!B!Fn6C;MEjepV|PZxK)+|fu4ZYMoRW0*oL%_r>&lB@KH zp`{7Rkm@Byfze`OKuXUT;5;XAJN`#eU@a7f&?^kI)r8yClnIX~QHwcViMk6RrR!W! z1k*;^3YrDFuSC5SLte~@!0h2*BH_*@1NOB-WuE$2X7e&8IrUPJ5IiWxeCN5s)t;_# zzjuYJJzd?N9n6%S>6^2m9>tIsb8O|phz74}q&h$%@s}u3&tgxHNXkJPV>J{w&1}pj z;tw)8eLO3?I>r^{2vy`c<}MWIrOLUEa!bK08+cnpU9XD;mzZnk`j-uNkN)Mra5ydr zOoP?taIqqS1spDz2CL2CVnqZCI9xCdR-41ciU<~PxL_KrHiwH95iH=XafWrmTV6{11tcYL%hYO~`YIC?)5y1iu7fgfI=5Vnhf(0Bdm6%j1paKSWKZ4MVJB3QuTf@!eY94=Nwuz4`lKg(ApbNMzfXknGGF-?zm5I5#Ed(f*t(i?SCs{q^UAs2uX`MQC_nt6Q{a(XwUBz`($uprGL3V2wr-5)u*`8rrH=tFW-J@bK`~ty{Ng)240P zwpy(=A|j$)yLOS0k?q^JkBW+lj*gCriRsXxL&uICJ9X+58yg!J7uUIS=Pq5kbnV); zTeoiAyLa!=qesu4J#ie@>2!L%o*)R4Bn<{be0+RDLIOomy?XUZOiVNyjV6=HY&O$0 zZLwH-_wH@AT5UF)-EL1xN=i;n2G>m;4o7NgYFb)apFVy1_U+rRU%&qS`wti}AU!>O z;J|@{1`QfKc<_)RLxv6=I&9dm;lqcI7%^hx$dRK)jT$|A^q4VY#*Q63Zrr%>&pEz;i~6(;NioEj~+dG{P^*cCr_R}efsR#v**vBzj*QD<;$0^ zUcGw#`t_SPZ{EIr`|jPl_wV0-`0(N5$B&;refs?Q^OrAQzJC3hot^#d+qduEzyJ90 zBPS>4=g*&DVxN2$O@npgV~>i627g@-qI$|71}cKWHGwgX>$Q_8pa-@C(w8#0ozO4Km(Rv{`wSVv*z;Iu4Xz1|YEjqc8x7j=%=(2xWsx3;?~ssXP~h`ja$C z=@Cm-M2x?eD7Gu}JQpT=9;XW|XZVb_F7VB0dreHa17T-j2~Otop6E-!x7_P2(AoY1 zMjMt&F`&|e$5fIi!Mh6x`5u-PoH!x!@!%rl_2`=!MbBPf#Q^?0*LcyCN zkD4i Date: Fri, 23 Nov 2018 11:27:48 +0100 Subject: [PATCH 084/149] Avoid requerying renderer, since it's already collected + ls() nodes once since amount of nodes will remain static --- .../plugins/maya/publish/collect_render_layer_aovs.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py index f39330e10f..6f8c71a33f 100644 --- a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py +++ b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py @@ -37,8 +37,7 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): return # Get renderer - renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") - + renderer = instance.data["renderer"] self.log.info("Renderer found: {}".format(renderer)) rp_node_types = {"vray": ["VRayRenderElement", "VRayRenderElementSet"], @@ -53,10 +52,10 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): # Collect all AOVs / Render Elements layer = instance.data["setMembers"] - with lib.renderlayer(layer): + node_type = rp_node_types[renderer] + render_elements = cmds.ls(type=node_type) - node_type = rp_node_types[renderer] - render_elements = cmds.ls(type=node_type) + with lib.renderlayer(layer): # Check if AOVs / Render Elements are enabled for element in render_elements: From b51cad8d88873205c151c19162e58e8aa4e0c38d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 13:32:52 +0100 Subject: [PATCH 085/149] Fix ValidateDeadlineConnection running per instance --- .../plugins/maya/publish/validate_deadline_connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_deadline_connection.py b/colorbleed/plugins/maya/publish/validate_deadline_connection.py index a893f39572..ef365b44bd 100644 --- a/colorbleed/plugins/maya/publish/validate_deadline_connection.py +++ b/colorbleed/plugins/maya/publish/validate_deadline_connection.py @@ -12,7 +12,7 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): hosts = ["maya"] families = ["colorbleed.renderlayer"] - def process(self, instance): + def process(self, context): AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", "http://localhost:8082") @@ -24,4 +24,4 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): assert response.ok, "Response must be ok" assert response.text.startswith("Deadline Web Service "), ( "Web service did not respond with 'Deadline Web Service'" - ) \ No newline at end of file + ) From ea5043ada24d57eadedd77746d571bdde3e3ac77 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 13:58:30 +0100 Subject: [PATCH 086/149] ValidateSceneSetWorkspace is not families specific, remove families filter --- colorbleed/plugins/maya/publish/validate_scene_set_workspace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py b/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py index 3f4f631897..ed37457164 100644 --- a/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py +++ b/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py @@ -30,7 +30,6 @@ class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): order = colorbleed.api.ValidatePipelineOrder hosts = ['maya'] - families = ['colorbleed.model'] category = 'scene' version = (0, 1, 0) label = 'Maya Workspace Set' From 029dfc43c4e6c728d7dabcb8c62b4dcd761599d5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:00:07 +0100 Subject: [PATCH 087/149] Simplify code --- colorbleed/plugins/global/publish/collect_machine_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/collect_machine_name.py b/colorbleed/plugins/global/publish/collect_machine_name.py index e1c4a88669..02360cff04 100644 --- a/colorbleed/plugins/global/publish/collect_machine_name.py +++ b/colorbleed/plugins/global/publish/collect_machine_name.py @@ -11,4 +11,4 @@ class CollectMachineName(pyblish.api.ContextPlugin): machine_name = socket.gethostname() self.log.info("Machine name: %s" % machine_name) - context.data.update({"machine": machine_name}) + context.data["machine"] = machine_name From 50678ed8386b2c99cc32828889214fdd8f9be7a5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:04:21 +0100 Subject: [PATCH 088/149] Use consistent workaround for ContextPlugin bug: pyblish-base#250 --- colorbleed/plugin.py | 35 +++++++++++++++++++ .../global/publish/collect_deadline_user.py | 5 +++ .../publish/validate_deadline_connection.py | 5 +++ .../validate_vray_translator_settings.py | 20 +++-------- 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/colorbleed/plugin.py b/colorbleed/plugin.py index 0ba1fe5ded..e7c4c374d6 100644 --- a/colorbleed/plugin.py +++ b/colorbleed/plugin.py @@ -32,3 +32,38 @@ class Extractor(pyblish.api.InstancePlugin): instance.data['stagingDir'] = staging_dir return staging_dir + + +def contextplugin_should_run(plugin, context): + """Return whether the ContextPlugin should run on the given context. + + This is a helper function to work around a bug pyblish-base#250 + Whenever a ContextPlugin sets specific families it will still trigger even + when no instances are present that have those families. + + This actually checks it correctly and returns whether it should run. + + """ + required = set(plugin.families) + + # When no filter always run + if "*" in required: + return True + + for instance in context: + + # Ignore inactive instances + if (not instance.data.get("publish", True) or + not instance.data.get("active", True)): + continue + + families = instance.data.get("families", []) + if any(f in required for f in families): + return True + + family = instance.data.get("family") + if family and family in required: + return True + + return False + diff --git a/colorbleed/plugins/global/publish/collect_deadline_user.py b/colorbleed/plugins/global/publish/collect_deadline_user.py index 4f7af94419..f78d0b1c9d 100644 --- a/colorbleed/plugins/global/publish/collect_deadline_user.py +++ b/colorbleed/plugins/global/publish/collect_deadline_user.py @@ -2,6 +2,7 @@ import os import subprocess import pyblish.api +from colorbleed.plugin import contextplugin_should_run CREATE_NO_WINDOW = 0x08000000 @@ -40,6 +41,10 @@ class CollectDeadlineUser(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" + # Workaround bug pyblish-base#250 + if not contextplugin_should_run(self, context): + return + user = deadline_command("GetCurrentUserName").strip() if not user: diff --git a/colorbleed/plugins/maya/publish/validate_deadline_connection.py b/colorbleed/plugins/maya/publish/validate_deadline_connection.py index ef365b44bd..0a962e7afc 100644 --- a/colorbleed/plugins/maya/publish/validate_deadline_connection.py +++ b/colorbleed/plugins/maya/publish/validate_deadline_connection.py @@ -2,6 +2,7 @@ import pyblish.api import avalon.api as api from avalon.vendor import requests +from colorbleed.plugin import contextplugin_should_run class ValidateDeadlineConnection(pyblish.api.ContextPlugin): @@ -14,6 +15,10 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): def process(self, context): + # Workaround bug pyblish-base#250 + if not contextplugin_should_run(self, context): + return + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", "http://localhost:8082") diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 6ed1637e2d..0de41c9563 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -1,5 +1,6 @@ import pyblish.api import colorbleed.api +from colorbleed.plugin import contextplugin_should_run from maya import cmds @@ -13,6 +14,10 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): def process(self, context): + # Workaround bug pyblish-base#250 + if not contextplugin_should_run(self, context): + return + invalid = self.get_invalid(context) if invalid: raise RuntimeError("Found invalid VRay Translator settings!") @@ -22,21 +27,6 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): invalid = False - # Check if there are any vray scene instances - # The reason to not use host.lsattr() as used in collect_vray_scene - # is because that information is already available in the context - vrayscene_instances = [i for i in context[:] if i.data["family"] - in cls.families] - - if not vrayscene_instances: - cls.log.info("No VRay Scene instances found, skipping..") - return - - # Ignore if no VRayScenes are enabled for publishing - if not any(i.data.get("publish", True) for i in vrayscene_instances): - cls.log.info("VRay Scene instances are disabled, skipping..") - return - # Get vraySettings node vray_settings = cmds.ls(type="VRaySettingsNode") assert vray_settings, "Please ensure a VRay Settings Node is present" From b333aa38cffd1d1f5acd26e8dd17c79d4b8ecee6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:15:52 +0100 Subject: [PATCH 089/149] Only try to collect context startFrame and endFrame when not on instance --- .../plugins/global/publish/submit_publish_job.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index ed00cb9343..1806668433 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -147,11 +147,14 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): subset=subset ) - # Add in start/end frame + # Get start/end frame from instance, if not available get from context context = instance.context - start = instance.data.get("startFrame", context.data["startFrame"]) - end = instance.data.get("endFrame", context.data["endFrame"]) - resources = [] + start = instance.data.get("startFrame") + if start is None: + start = context.data["startFrame"] + end = instance.data.get("endFrame") + if end is None: + end = context.data["endFrame"] # Add in regex for sequence filename # This assumes the output files start with subset name and ends with @@ -187,6 +190,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): if not os.path.isdir(output_dir): os.makedirs(output_dir) + resources = [] if data.get("extendFrames", False): family = "colorbleed.imagesequence" From 52df84964926e1d479be72e9c6212d6cf354788d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:16:52 +0100 Subject: [PATCH 090/149] Remove startFrame+endFrame on context, they are per renderlayer instance --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 03a879d17b..a816dc12c3 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -26,12 +26,6 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): "renderGlobals node") return - # Get start and end frame - start_frame = self.get_render_attribute("startFrame") - end_frame = self.get_render_attribute("endFrame") - context.data["startFrame"] = start_frame - context.data["endFrame"] = end_frame - # Get all valid renderlayers # This is how Maya populates the renderlayer display rlm_attribute = "renderLayerManager.renderLayerId" From 51c1ed5dcd93c4a6f7ac7ca9c3a881a3ed467fff Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:22:24 +0100 Subject: [PATCH 091/149] Provide consistent log message for collecting renderlayers and vrayscene --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 4 ++-- colorbleed/plugins/maya/publish/collect_vray_scene.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index a816dc12c3..9e6e97e725 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -22,8 +22,8 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): try: render_globals = cmds.ls("renderglobalsDefault")[0] except IndexError: - self.log.error("Cannot collect renderlayers without " - "renderGlobals node") + self.log.info("Skipping renderlayer collection, no " + "renderGlobalsDefault found..") return # Get all valid renderlayers diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index 612670478e..911d49907c 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -29,7 +29,8 @@ class CollectVRayScene(pyblish.api.ContextPlugin): # Get VRay Scene instance vray_scenes = host.lsattr("family", "colorbleed.vrayscene") if not vray_scenes: - self.log.info("No instance found of family: `colorbleed.vrayscene`") + self.log.info("Skipping vrayScene collection, no " + "colorbleed.vrayscene instance found..") return assert len(vray_scenes) == 1, "Multiple vrayscene instances found!" From 1ef9db118bfa06404077e75f53b9d0091128326f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:28:42 +0100 Subject: [PATCH 092/149] Skip storing startFrame + endFrame on context, as it is per instance data --- .../plugins/maya/publish/collect_vray_scene.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index 911d49907c..e4a9c42364 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -52,13 +52,6 @@ class CollectVRayScene(pyblish.api.ContextPlugin): vrscene = ("vrayscene", "", "_", "") vrscene_output = os.path.join(work_dir, *vrscene) - vrscene_data["startFrame"] = start_frame - vrscene_data["endFrame"] = end_frame - vrscene_data["vrsceneOutput"] = vrscene_output - - context.data["startFrame"] = start_frame - context.data["endFrame"] = end_frame - # Check and create render output template for render job # outputDir is required for submit_publish_job if not vrscene_data.get("suspendRenderJob", False): @@ -104,7 +97,10 @@ class CollectVRayScene(pyblish.api.ContextPlugin): # Add source to allow tracing back to the scene from # which was submitted originally - "source": file_name + "source": file_name, + + # Store VRay Scene additional data + "vrsceneOutput": vrscene_output } data.update(vrscene_data) From 241069d24c97eed2d11b1f4efbb4a18667575f98 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:33:41 +0100 Subject: [PATCH 093/149] Submission cosmetics (improve labeling of submitted tasks) --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 16fa6d02a2..7036a9f1eb 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -41,7 +41,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): filename = os.path.basename(filepath) task_name = "{} - {}".format(filename, instance.name) - batch_name = "VRay Scene Export - {}".format(filename) + batch_name = "{} - (vrscene)".format(filename) # Get the output template for vrscenes vrscene_output = instance.data["vrsceneOutput"] @@ -63,7 +63,9 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "BatchName": batch_name, # Job name, as seen in Monitor - "Name": "{} [{}-{}]".format(task_name, start_frame, end_frame), + "Name": "Export {} [{}-{}]".format(task_name, + start_frame, + end_frame), # Arbitrary username, for visualisation in Monitor "UserName": deadline_user, From b4297b6a41e5b8de89e5b96c943fffb39c9d70cc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 15:54:49 +0100 Subject: [PATCH 094/149] Workaround contextplugin running when instance not present bug --- .../publish/validate_current_renderlayer_renderable.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py b/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py index 04ed259d48..8f09941f06 100644 --- a/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py +++ b/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py @@ -1,6 +1,7 @@ import pyblish.api from maya import cmds +from colorbleed.plugin import contextplugin_should_run class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): @@ -20,7 +21,12 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): hosts = ["maya"] families = ["colorbleed.renderlayer"] - def process(self, instance): + def process(self, context): + + # Workaround bug pyblish-base#250 + if not contextplugin_should_run(self, context): + return + layer = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) cameras = cmds.ls(type="camera", long=True) renderable = any(c for c in cameras if cmds.getAttr(c + ".renderable")) From f7c0c1d88553466ac29872c5d73d36824fd8bad4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 17:36:26 +0100 Subject: [PATCH 095/149] Fix render single camera validation (actually process something) - Also improve logged messages --- .../publish/validate_render_single_camera.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/colorbleed/plugins/maya/publish/validate_render_single_camera.py index 0a2eb997c5..447f52beec 100644 --- a/colorbleed/plugins/maya/publish/validate_render_single_camera.py +++ b/colorbleed/plugins/maya/publish/validate_render_single_camera.py @@ -17,17 +17,28 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder label = "Render Single Camera" hosts = ['maya'] - families = ['colorbleed.renderlayer', + families = ["colorbleed.renderlayer", "colorbleed.vrayscene"] + actions = [colorbleed.maya.action.SelectInvalidAction] def process(self, instance): """Process all the cameras in the instance""" + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Invalid cameras for render.") @classmethod def get_invalid(cls, instance): - cameras = instance.data.get("cameras", []) - if len(cameras) != 1: - cls.log.error("Multiple renderable cameras" "found: %s " % - instance.data["setMembers"]) + cameras = instance.data.get("cameras", []) + + if len(cameras) > 1: + cls.log.error("Multiple renderable cameras found for %s: %s " % + (instance.data["setMembers"], cameras)) + return [instance.data["setMembers"]] + cameras + + elif len(cameras) < 1: + cls.log.error("No renderable cameras found for %s " % + instance.data["setMembers"]) return [instance.data["setMembers"]] + From 0fab828311389bc5a58c435b3681b7b38a2500af Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 17:51:20 +0100 Subject: [PATCH 096/149] Fix REN-59: Less renderlayer switching on render submission from maya - This is a good optimization for render scenes with multiple layers that are slow on switching renderlayers. - This will be slower for render setup because it's not optimized for render setup, see colorbleed.maya.lib.get_attr_in_layer() --- colorbleed/maya/lib.py | 54 +++++++++++++++++++ .../maya/publish/collect_render_layer_aovs.py | 24 ++++----- .../maya/publish/collect_renderable_camera.py | 9 ++-- .../maya/publish/collect_renderlayers.py | 47 ++++++++-------- .../validate_render_no_default_cameras.py | 7 ++- .../maya/publish/validate_rendersettings.py | 50 ++++++++--------- 6 files changed, 121 insertions(+), 70 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 8395d5fe43..7b5f8fab2d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -2087,3 +2087,57 @@ def bake_to_world_space(nodes, shape=shape) return world_space_nodes + + +def get_attr_in_layer(attr, layer): + """Return attribute value in specified renderlayer. + + Same as cmds.getAttr but this gets the attribute's value in a + given render layer without having to switch to it. + + Note: This is much faster for Maya's renderLayer system, yet the code + does no optimized query for render setup. + + Args: + attr (str): attribute name, ex. "node.attribute" + layer (str): layer name + + Returns: + The return value from `maya.cmds.getAttr` + + """ + if cmds.mayaHasRenderSetup(): + log.debug("lib.get_attr_in_layer is not optimized for render setup") + with renderlayer(layer): + return cmds.getAttr(attr) + + connections = cmds.listConnections(attr, + plugs=True, + source=False, + destination=True, + type="renderLayer") or [] + + connections = filter(lambda x: x.endswith(".plug"), connections) + if not connections: + return cmds.getAttr(attr) + + for connection in connections: + if connection.startswith(layer): + attr_split = connection.split(".") + if attr_split[0] == layer: + attr = ".".join(attr_split[0:-1]) + return cmds.getAttr("%s.value" % attr) + + else: + # When connections are present, but none + # to the specific renderlayer than the layer + # should have the "defaultRenderLayer"'s value + layer = "defaultRenderLayer" + for connection in connections: + if connection.startswith(layer): + attr_split = connection.split(".") + if attr_split[0] == "defaultRenderLayer": + attr = ".".join(attr_split[0:-1]) + return cmds.getAttr("%s.value" % attr) + + return cmds.getAttr(attr) diff --git a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py index 6f8c71a33f..e2153e0ee0 100644 --- a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py +++ b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py @@ -6,10 +6,9 @@ import colorbleed.maya.lib as lib class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): - """Validate all render layer's AOVs / Render Elements are registered in - the database + """Collect all render layer's AOVs / Render Elements that will render. - This validator is important to be able to Extend Frames + This collector is important to be able to Extend Frames. Technical information: Each renderer uses different logic to work with render passes. @@ -55,18 +54,17 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): node_type = rp_node_types[renderer] render_elements = cmds.ls(type=node_type) - with lib.renderlayer(layer): + # Check if AOVs / Render Elements are enabled + for element in render_elements: + enabled = lib.get_attr_in_layer("{}.enabled".format(element), + layer=layer) + if not enabled: + continue - # Check if AOVs / Render Elements are enabled - for element in render_elements: - enabled = cmds.getAttr("{}.enabled".format(element)) - if not enabled: - continue + pass_name = self.get_pass_name(renderer, element) + render_pass = "%s.%s" % (instance.data["subset"], pass_name) - pass_name = self.get_pass_name(renderer, element) - render_pass = "%s.%s" % (instance.data["subset"], pass_name) - - result.append(render_pass) + result.append(render_pass) self.log.info("Found {} render elements / AOVs for " "'{}'".format(len(result), instance.data["subset"])) diff --git a/colorbleed/plugins/maya/publish/collect_renderable_camera.py b/colorbleed/plugins/maya/publish/collect_renderable_camera.py index dd9ec433bb..8cf33bd73a 100644 --- a/colorbleed/plugins/maya/publish/collect_renderable_camera.py +++ b/colorbleed/plugins/maya/publish/collect_renderable_camera.py @@ -18,10 +18,9 @@ class CollectRenderableCamera(pyblish.api.InstancePlugin): layer = instance.data["setMembers"] cameras = cmds.ls(type="camera", long=True) - with lib.renderlayer(layer): - renderable = [c for c in cameras if - cmds.getAttr("%s.renderable" % c)] + renderable = [c for c in cameras if + lib.get_attr_in_layer("%s.renderable" % c, layer=layer)] - self.log.info("Found cameras %s" % len(renderable)) + self.log.info("Found cameras %s: %s" % (len(renderable), renderable)) - instance.data.update({"cameras": renderable}) + instance.data["cameras"] = renderable diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 9e6e97e725..369b21de02 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -53,30 +53,34 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): if layer.endswith("defaultRenderLayer"): layername = "masterLayer" else: + # Remove Maya render setup prefix `rs_` layername = layer.split("rs_", 1)[-1] # Get layer specific settings, might be overrides - with lib.renderlayer(layer): - data = { - "subset": layername, - "setMembers": layer, - "publish": True, - "startFrame": self.get_render_attribute("startFrame"), - "endFrame": self.get_render_attribute("endFrame"), - "byFrameStep": self.get_render_attribute("byFrameStep"), - "renderer": self.get_render_attribute("currentRenderer"), + data = { + "subset": layername, + "setMembers": layer, + "publish": True, + "startFrame": self.get_render_attribute("startFrame", + layer=layer), + "endFrame": self.get_render_attribute("endFrame", + layer=layer), + "byFrameStep": self.get_render_attribute("byFrameStep", + layer=layer), + "renderer": self.get_render_attribute("currentRenderer", + layer=layer), - # instance subset - "family": "Render Layers", - "families": ["colorbleed.renderlayer"], - "asset": asset, - "time": api.time(), - "author": context.data["user"], + # instance subset + "family": "Render Layers", + "families": ["colorbleed.renderlayer"], + "asset": asset, + "time": api.time(), + "author": context.data["user"], - # Add source to allow tracing back to the scene from - # which was submitted originally - "source": filepath - } + # Add source to allow tracing back to the scene from + # which was submitted originally + "source": filepath + } # Apply each user defined attribute as data for attr in cmds.listAttr(layer, userDefined=True) or list(): @@ -106,8 +110,9 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): instance.data["label"] = label instance.data.update(data) - def get_render_attribute(self, attr): - return cmds.getAttr("defaultRenderGlobals.{}".format(attr)) + def get_render_attribute(self, attr, layer): + return lib.get_attr_in_layer("defaultRenderGlobals.{}".format(attr), + layer=layer) def parse_options(self, render_globals): """Get all overrides with a value, skip those without diff --git a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py index 588c66eb39..b7c1cbbeb8 100644 --- a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py +++ b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py @@ -26,10 +26,9 @@ class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): cmds.camera(cam, query=True, startupCamera=True)] invalid = [] - with lib.renderlayer(layer): - for cam in defaults: - if cmds.getAttr(cam + ".renderable"): - invalid.append(cam) + for cam in defaults: + if lib.get_attr_in_layer(cam + ".renderable", layer=layer): + invalid.append(cam) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 17d872f7d3..8f8c86e51e 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -50,37 +50,33 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): invalid = False renderer = instance.data['renderer'] - layer_node = instance.data['setMembers'] + layer = instance.data['setMembers'] - # Collect the filename prefix in the render layer - with lib.renderlayer(layer_node): + # Get the node attributes for current renderer + attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS['default']) + prefix = lib.get_attr_in_layer("{node}.{prefix}".format(**attrs), + layer=layer) + padding = lib.get_attr_in_layer("{node}.{padding}".format(**attrs), + layer=layer) - render_attrs = lib.RENDER_ATTRS.get(renderer, - lib.RENDER_ATTRS['default']) - node = render_attrs["node"] - padding_attr = render_attrs["padding"] - prefix_attr = render_attrs["prefix"] + anim_override = lib.get_attr_in_layer("defaultRenderGlobals.animation", + layer=layer) + if not anim_override: + invalid = True + cls.log.error("Animation needs to be enabled. Use the same " + "frame for start and end to render single frame") - prefix = cmds.getAttr("{}.{}".format(node, prefix_attr)) - padding = cmds.getAttr("{}.{}".format(node, padding_attr)) + fname_prefix = cls.RENDERER_PREFIX.get(renderer, + cls.DEFAULT_PREFIX) + if prefix != fname_prefix: + invalid = True + cls.log.error("Wrong file name prefix: %s (expected: %s)" + % (prefix, fname_prefix)) - anim_override = cmds.getAttr("defaultRenderGlobals.animation") - if not anim_override: - invalid = True - cls.log.error("Animation needs to be enabled. Use the same " - "frame for start and end to render single frame") - - fname_prefix = cls.RENDERER_PREFIX.get(renderer, - cls.DEFAULT_PREFIX) - if prefix != fname_prefix: - invalid = True - cls.log.error("Wrong file name prefix, expecting %s" - % fname_prefix) - - if padding != cls.DEFAULT_PADDING: - invalid = True - cls.log.error("Expecting padding of {} ( {} )".format( - cls.DEFAULT_PADDING, "0" * cls.DEFAULT_PADDING)) + if padding != cls.DEFAULT_PADDING: + invalid = True + cls.log.error("Expecting padding of {} ( {} )".format( + cls.DEFAULT_PADDING, "0" * cls.DEFAULT_PADDING)) return invalid From 40bf199f6e87dc7103acff078a2480645264ede3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 18:01:05 +0100 Subject: [PATCH 097/149] Use collected cameras in validation, instead of collecting again. --- .../publish/validate_render_no_default_cameras.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py index b7c1cbbeb8..fbfb50bd4e 100644 --- a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py +++ b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py @@ -3,7 +3,6 @@ from maya import cmds import pyblish.api import colorbleed.api import colorbleed.maya.action -import colorbleed.maya.lib as lib class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): @@ -18,19 +17,14 @@ class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): @staticmethod def get_invalid(instance): - layer = instance.data["setMembers"] + renderable = set(instance.data["cameras"]) # Collect default cameras cameras = cmds.ls(type='camera', long=True) - defaults = [cam for cam in cameras if - cmds.camera(cam, query=True, startupCamera=True)] + defaults = set(cam for cam in cameras if + cmds.camera(cam, query=True, startupCamera=True)) - invalid = [] - for cam in defaults: - if lib.get_attr_in_layer(cam + ".renderable", layer=layer): - invalid.append(cam) - - return invalid + return [cam for cam in renderable if cam in defaults] def process(self, instance): """Process all the cameras in the instance""" From 5e653bbb5511aca8300ca52cced971b14bb91f8e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Dec 2018 08:39:05 +0100 Subject: [PATCH 098/149] Fix custom icons in Maya background color and positioning --- colorbleed/maya/customize.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/customize.py b/colorbleed/maya/customize.py index 4de78fba43..58f2f6d8a5 100644 --- a/colorbleed/maya/customize.py +++ b/colorbleed/maya/customize.py @@ -94,6 +94,7 @@ def override_toolbox_ui(): return # Create our controls + background_color = (0.267, 0.267, 0.267) controls = [] control = mc.iconTextButton( @@ -102,6 +103,7 @@ def override_toolbox_ui(): label="Loader", image=os.path.join(icons, "loader.png"), command=lambda: loader.show(use_context=True), + bgc=background_color, width=icon_size, height=icon_size, parent=parent) @@ -113,6 +115,7 @@ def override_toolbox_ui(): label="Inventory", image=os.path.join(icons, "inventory.png"), command=lambda: inventory.show(), + bgc=background_color, width=icon_size, height=icon_size, parent=parent) @@ -123,6 +126,7 @@ def override_toolbox_ui(): annotation="Colorbleed", label="Colorbleed", image=os.path.join(icons, "colorbleed_logo_36x36.png"), + bgc=background_color, width=icon_size, height=icon_size, parent=parent) @@ -135,6 +139,6 @@ def override_toolbox_ui(): previous = controls[i - 1] if i > 0 else web_button mc.formLayout(parent, edit=True, - attachControl=[control, "bottom", 1, previous], + attachControl=[control, "bottom", 0, previous], attachForm=([control, "left", 1], [control, "right", 1])) From 8e6724805492e88ccbeac19e425a8f368b01d08c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:19:55 +0100 Subject: [PATCH 099/149] Fix Create VDB cache correctly assigning currently selected nodes --- colorbleed/plugins/houdini/create/create_vbd_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index aadd222d41..30f467b3b0 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -28,6 +28,6 @@ class CreateVDBCache(houdini.Creator): if self.nodes: node = self.nodes[0] - parms.update({"sop_path": node.path()}) + parms.update({"soppath": node.path()}) instance.setParms(parms) From d09f088f14202b73f50c049930c69cfd4c958413 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:20:18 +0100 Subject: [PATCH 100/149] Fix filename typo --- .../{valiate_vdb_input_node.py => validate_vdb_input_node.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename colorbleed/plugins/houdini/publish/{valiate_vdb_input_node.py => validate_vdb_input_node.py} (100%) diff --git a/colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py b/colorbleed/plugins/houdini/publish/validate_vdb_input_node.py similarity index 100% rename from colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py rename to colorbleed/plugins/houdini/publish/validate_vdb_input_node.py From 488703bfc73035c6f85610302e72e3c966745736 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:26:45 +0100 Subject: [PATCH 101/149] Remove redundant import --- colorbleed/plugins/houdini/create/create_alembic_camera.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index 6d35e3bd02..63596b1cfc 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from avalon import houdini From e22bd1f26b2c6d698cbb7178d92fc0c057bbfd5c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:34:12 +0100 Subject: [PATCH 102/149] Clarify error messages more for the artists. --- .../houdini/publish/validate_output_node.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index d3393e1c33..bb9eec508e 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -2,10 +2,15 @@ import pyblish.api class ValidateOutputNode(pyblish.api.InstancePlugin): - """Validate if output node: - - exists - - is of type 'output' - - has an input""" + """Validate the instance SOP Output Node. + + This will ensure: + - The SOP Path is set. + - The SOP Path refers to an existing object. + - The SOP Path node is of type 'output' or 'camera' + - The SOP Path node has at least one input connection (has an input) + + """ order = pyblish.api.ValidatorOrder families = ["*"] @@ -16,7 +21,8 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Output node(s) `%s` are incorrect" % invalid) + raise RuntimeError("Output node(s) `%s` are incorrect. " + "See plug-in log for details." % invalid) @classmethod def get_invalid(cls, instance): @@ -25,16 +31,17 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): if output_node is None: node = instance[0] - cls.log.error("Output node at '%s' does not exist, see source" % - node.path()) + cls.log.error("SOP Output node in '%s' does not exist. " + "Ensure a valid SOP output path is set." + % node.path()) return node.path() # Check if type is correct type_name = output_node.type().name() if type_name not in ["output", "cam"]: - cls.log.error("Output node `%s` is not an accepted type `output` " - "or `camera`" % + cls.log.error("Output node `%s` is not an accepted type." + "Expected types: `output` or `camera`" % output_node.path()) return [output_node.path()] From 7a39b50469c4bf8982eb85b8d450baee0022127b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:37:25 +0100 Subject: [PATCH 103/149] Remove redundant instances list that was unused --- colorbleed/plugins/houdini/publish/collect_instances.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py index 61d4cdbe0b..2e456a8d97 100644 --- a/colorbleed/plugins/houdini/publish/collect_instances.py +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -30,8 +30,6 @@ class CollectInstances(pyblish.api.ContextPlugin): def process(self, context): - instances = [] - nodes = hou.node("/out").children() for node in nodes: @@ -68,8 +66,6 @@ class CollectInstances(pyblish.api.ContextPlugin): instance[:] = [node] instance.data.update(data) - instances.append(instance) - def sort_by_family(instance): """Sort by family""" return instance.data.get("families", instance.data.get("family")) From 8c09ada8cb725cf031eb81eaf693689a777624bb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:39:27 +0100 Subject: [PATCH 104/149] Explicitly also check for endFrame + PEP08 and cosmetics --- .../plugins/houdini/publish/collect_instances.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py index 2e456a8d97..5f9fc7d6c9 100644 --- a/colorbleed/plugins/houdini/publish/collect_instances.py +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -15,8 +15,8 @@ class CollectInstances(pyblish.api.ContextPlugin): id (str): "pyblish.avalon.instance Specific node: - The specific node is important because it dictates in which way the subset - is being exported. + The specific node is important because it dictates in which way the + subset is being exported. alembic: will export Alembic file which supports cascading attributes like 'cbId' and 'path' @@ -53,11 +53,9 @@ class CollectInstances(pyblish.api.ContextPlugin): data.update(self.get_frame_data(node)) - # Create nice name - # All nodes in the Outputs graph have the 'Valid Frame Range' - # attribute, we check here if any frames are set + # Create nice name if the instance has a frame range. label = data.get("name", node.name()) - if "startFrame" in data: + if "startFrame" in data and "endFrame" in data: frames = "[{startFrame} - {endFrame}]".format(**data) label = "{} {}".format(label, frames) From 427a5a3ba05cbab24f4a4c15d7b83bde574c4bcc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:42:00 +0100 Subject: [PATCH 105/149] Assert node is valid, we somehow had a case where the node was `None` --- colorbleed/plugins/houdini/publish/validate_outnode_exists.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py index b9f24faa32..ff682ee820 100644 --- a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py +++ b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py @@ -29,6 +29,8 @@ class ValidatOutputNodeExists(pyblish.api.InstancePlugin): result = set() node = instance[0] + assert node, "No node in instance. This is a bug." + if node.type().name() == "alembic": soppath_parm = "sop_path" else: From 077df63286bbeae3374aad8c19ee6e5e71033780 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 08:43:18 +0100 Subject: [PATCH 106/149] Correctly perform attribute type value conversion for render layer overrides --- colorbleed/maya/lib.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 7b5f8fab2d..dd97e46be7 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -2118,15 +2118,27 @@ def get_attr_in_layer(attr, layer): type="renderLayer") or [] connections = filter(lambda x: x.endswith(".plug"), connections) - if not connections: + if not connections or layer == cmds.editRenderLayerGlobals(query=True, + currentRenderLayer=True): return cmds.getAttr(attr) - + + # Some value types perform a conversion when assigning + # TODO: See if there's a maya method to allow this conversion + # instead of computing it ourselves. + attr_type = cmds.getAttr(attr, type=True) + conversion = None + if attr_type == "time": + conversion = mel.eval('currentTimeUnitToFPS()') # returns float + for connection in connections: - if connection.startswith(layer): + if connection.startswith(layer + "."): attr_split = connection.split(".") if attr_split[0] == layer: attr = ".".join(attr_split[0:-1]) - return cmds.getAttr("%s.value" % attr) + value = cmds.getAttr("%s.value" % attr) + if conversion: + value *= conversion + return value else: # When connections are present, but none @@ -2138,6 +2150,9 @@ def get_attr_in_layer(attr, layer): attr_split = connection.split(".") if attr_split[0] == "defaultRenderLayer": attr = ".".join(attr_split[0:-1]) - return cmds.getAttr("%s.value" % attr) + value = cmds.getAttr("%s.value" % attr) + if conversion: + value *= conversion * conversion + return value return cmds.getAttr(attr) From bb39de901d3697f4f6c9041fbdf62d545c820787 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 10:36:20 +0100 Subject: [PATCH 107/149] Some more work-in-progress on correct type conversion for get_attr_in_layer() --- colorbleed/maya/lib.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index dd97e46be7..20df970f74 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -2095,6 +2095,12 @@ def get_attr_in_layer(attr, layer): Same as cmds.getAttr but this gets the attribute's value in a given render layer without having to switch to it. + Warning for parent attribute overrides: + Attributes that have render layer overrides to their parent attribute + are not captured correctly since they do not have a direct connection. + For example, an override to sphere.rotate when querying sphere.rotateX + will not return correctly! + Note: This is much faster for Maya's renderLayer system, yet the code does no optimized query for render setup. @@ -2106,22 +2112,27 @@ def get_attr_in_layer(attr, layer): The return value from `maya.cmds.getAttr` """ + if cmds.mayaHasRenderSetup(): log.debug("lib.get_attr_in_layer is not optimized for render setup") with renderlayer(layer): return cmds.getAttr(attr) + # Ignore complex query if we're in the layer anyway + current_layer = cmds.editRenderLayerGlobals(query=True, + currentRenderLayer=True) + if layer == current_layer: + return cmds.getAttr(attr) + connections = cmds.listConnections(attr, plugs=True, source=False, destination=True, type="renderLayer") or [] - connections = filter(lambda x: x.endswith(".plug"), connections) - if not connections or layer == cmds.editRenderLayerGlobals(query=True, - currentRenderLayer=True): + if not connections: return cmds.getAttr(attr) - + # Some value types perform a conversion when assigning # TODO: See if there's a maya method to allow this conversion # instead of computing it ourselves. @@ -2129,7 +2140,14 @@ def get_attr_in_layer(attr, layer): conversion = None if attr_type == "time": conversion = mel.eval('currentTimeUnitToFPS()') # returns float - + elif attr_type == "doubleAngle": + # Radians to Degrees: 180 / pi + # TODO: This will likely only be correct when Maya units are set + # to degrees + conversion = 57.2957795131 + elif attr_type == "doubleLinear": + raise NotImplementedError("doubleLinear conversion not implemented.") + for connection in connections: if connection.startswith(layer + "."): attr_split = connection.split(".") From 7711938d36f16889e2e331a435b7f318fe64c96e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 14:12:22 +0100 Subject: [PATCH 108/149] By default exclude parent hierarchy for pointcaches (PLN-205) - This adds in a toggle to export alembics without the parent hierarchy --- .../maya/create/colorbleed_animation.py | 3 ++ .../plugins/maya/create/colorbleed_model.py | 14 ++++---- .../maya/create/colorbleed_pointcache.py | 1 + .../plugins/maya/publish/collect_instances.py | 6 +++- .../plugins/maya/publish/extract_animation.py | 14 +++++--- .../maya/publish/extract_pointcache.py | 34 +++++++++++++++++++ .../maya/publish/validate_model_content.py | 3 +- 7 files changed, 63 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index 6d87cb5078..6a113bf74c 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -29,3 +29,6 @@ class CreateAnimation(avalon.maya.Creator): # Include only nodes that are visible at least once during the # frame range. self.data["visibleOnly"] = False + + # Include the groups above the out_SET content + self.data["includeParentHierarchy"] = False # Include parent groups diff --git a/colorbleed/plugins/maya/create/colorbleed_model.py b/colorbleed/plugins/maya/create/colorbleed_model.py index 47411e6c52..956f9f0e4f 100644 --- a/colorbleed/plugins/maya/create/colorbleed_model.py +++ b/colorbleed/plugins/maya/create/colorbleed_model.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import avalon.maya @@ -14,8 +12,12 @@ class CreateModel(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateModel, self).__init__(*args, **kwargs) - data = {"writeColorSets": False, # Vertex colors with the geometry. - "attr": "", # Add options for custom attributes - "attrPrefix": ""} + # Vertex colors with the geometry + self.data["writeColorSets"] = False - self.data.update(data) + # Include attributes by attribute name or prefix + self.data["attr"] = "" + self.data["attrPrefix"] = "" + + # Whether to include parent hierarchy of nodes in the instance + self.data["includeParentHierarchy"] = False diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index 495c433e87..2fa0f6cb36 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -19,6 +19,7 @@ class CreatePointCache(avalon.maya.Creator): self.data["writeColorSets"] = False # Vertex colors with the geometry. self.data["renderableOnly"] = False # Only renderable visible shapes self.data["visibleOnly"] = False # only nodes that are visible + self.data["includeParentHierarchy"] = False # Include parent groups # Add options for custom attributes self.data["attr"] = "" diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 807b57c710..f3ce0aece3 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -99,7 +99,11 @@ class CollectInstances(pyblish.api.ContextPlugin): fullPath=True) or [] children = cmds.ls(children, noIntermediate=True, long=True) - parents = self.get_all_parents(members) + parents = [] + if data.get("includeParentHierarchy", True): + # If `includeParentHierarchy` then include the parents + # so they will also be picked up in the instance by validators + parents = self.get_all_parents(members) members_hierarchy = list(set(members + children + parents)) # Create the instance diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index d62c34e915..53e172fa56 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -27,12 +27,12 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): raise RuntimeError("Couldn't find exactly one out_SET: " "{0}".format(out_sets)) out_set = out_sets[0] - nodes = cmds.sets(out_set, query=True) + roots = cmds.sets(out_set, query=True) # Include all descendants - nodes += cmds.listRelatives(nodes, - allDescendents=True, - fullPath=True) or [] + nodes = roots + cmds.listRelatives(roots, + allDescendents=True, + fullPath=True) or [] # Collect the start and end including handles start = instance.data["startFrame"] @@ -58,6 +58,12 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): "selection": True } + if not instance.data.get("includeParentHierarchy", True): + # Set the root nodes if we don't want to include parents + # The roots are to be considered the ones that are the actual + # direct members of the set + options["root"] = roots + if int(cmds.about(version=True)) >= 2017: # Since Maya 2017 alembic supports multiple uv sets - write them. options["writeUVSets"] = True diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 4cd894d7f4..dc8eabe7ac 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -7,6 +7,34 @@ import colorbleed.api from colorbleed.maya.lib import extract_alembic +def iter_parents(node): + n = node.count("|") + for i in range(1, n): + yield node.rsplit("|", i)[0] + + +def get_highest_in_hierarchy(nodes): + """Return the highest in the hierachies from nodes + + This will return each highest node in separate hierarchies. + E.g. + get_highest_in_hierarchy(["|A|B|C", "A|B", "D|E"]) + # ["A|B", "D|E"] + + """ + # Ensure we use long names + nodes = cmds.ls(nodes, long=True) + lookup = set(nodes) + highest = [] + for node in nodes: + # If no parents are within the original list + # then this is a highest node + if not any(n in lookup for n in iter_parents(node)): + highest.append(node) + + return highest + + class ExtractColorbleedAlembic(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. @@ -60,6 +88,12 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): "selection": True } + if not instance.data.get("includeParentHierarchy", True): + # Set the root nodes if we don't want to include parents + # The roots are to be considered the ones that are the actual + # direct members of the set + options["root"] = instance.data.get("setMembers") + if int(cmds.about(version=True)) >= 2017: # Since Maya 2017 alembic supports multiple uv sets - write them. options["writeUVSets"] = True diff --git a/colorbleed/plugins/maya/publish/validate_model_content.py b/colorbleed/plugins/maya/publish/validate_model_content.py index 1ea26047ee..c9bbb2c23e 100644 --- a/colorbleed/plugins/maya/publish/validate_model_content.py +++ b/colorbleed/plugins/maya/publish/validate_model_content.py @@ -63,7 +63,8 @@ class ValidateModelContent(pyblish.api.InstancePlugin): cls.log.error("Must have exactly one top group") if len(assemblies) == 0: cls.log.warning("No top group found. " - "(Are there objects in the instance?)") + "(Are there objects in the instance?" + " Or is it parented in another group?)") return assemblies or True def _is_visible(node): From bff76636df08bcbbc56c24d3db0613fa53367c82 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 14:48:59 +0100 Subject: [PATCH 109/149] Remove unused code --- .../maya/publish/extract_pointcache.py | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index dc8eabe7ac..1e3b4a2ea2 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -7,34 +7,6 @@ import colorbleed.api from colorbleed.maya.lib import extract_alembic -def iter_parents(node): - n = node.count("|") - for i in range(1, n): - yield node.rsplit("|", i)[0] - - -def get_highest_in_hierarchy(nodes): - """Return the highest in the hierachies from nodes - - This will return each highest node in separate hierarchies. - E.g. - get_highest_in_hierarchy(["|A|B|C", "A|B", "D|E"]) - # ["A|B", "D|E"] - - """ - # Ensure we use long names - nodes = cmds.ls(nodes, long=True) - lookup = set(nodes) - highest = [] - for node in nodes: - # If no parents are within the original list - # then this is a highest node - if not any(n in lookup for n in iter_parents(node)): - highest.append(node) - - return highest - - class ExtractColorbleedAlembic(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. From e2413854a82a8c6d49c159a379c9d981ce766fcf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 21:10:53 +0100 Subject: [PATCH 110/149] Remove outdated docstring content --- .../plugins/maya/publish/collect_instances.py | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 807b57c710..93d8a20c8a 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -12,29 +12,14 @@ class CollectInstances(pyblish.api.ContextPlugin): Identifier: id (str): "pyblish.avalon.instance" - Supported Families: - avalon.model: Geometric representation of artwork - avalon.rig: An articulated model for animators. - A rig may contain a series of sets in which to identify - its contents. - - - cache_SEL: Should contain cachable polygonal meshes - - controls_SEL: Should contain animatable controllers for animators - - resources_SEL: Should contain nodes that reference external files - - Limitations: - - Only Maya is supported - - One (1) rig per scene file - - Unmanaged history, it is up to the TD to ensure - history is up to par. - avalon.animation: Pointcache of `avalon.rig` - Limitations: - Does not take into account nodes connected to those within an objectSet. Extractors are assumed to export with history preserved, but this limits what they will be able to achieve and the amount of data available - to validators. + to validators. An additional collector could also + append this input data into the instance, as we do + for `colorbleed.rig` with collect_history. """ From 825a5587c3a67c7b5572a55b2fdf0a74a5da785d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 21:12:38 +0100 Subject: [PATCH 111/149] Fix bug typo in get_attr_in_layer - This fixes a double conversion issue with renderlayer overrides on a layer that has no override set but other layers have for that same attribute. --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index dd97e46be7..9c2b0d4424 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -2152,7 +2152,7 @@ def get_attr_in_layer(attr, layer): attr = ".".join(attr_split[0:-1]) value = cmds.getAttr("%s.value" % attr) if conversion: - value *= conversion * conversion + value *= conversion return value return cmds.getAttr(attr) From da4b30c8a42dd9c544bac2a1e3a0807fe16910d2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 14 Dec 2018 11:50:01 +0100 Subject: [PATCH 112/149] Suspend all viewports during camera extraction (optimization) (PLN-202) --- colorbleed/plugins/maya/publish/extract_camera_alembic.py | 2 +- colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_camera_alembic.py b/colorbleed/plugins/maya/publish/extract_camera_alembic.py index 0b315a8d6b..7ed65e0d44 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_camera_alembic.py @@ -67,7 +67,7 @@ class ExtractCameraAlembic(colorbleed.api.Extractor): job_str += ' -file "{0}"'.format(path) with lib.evaluation("off"): - with lib.no_refresh(): + with avalon.maya.suspended_refresh(): cmds.AbcExport(j=job_str, verbose=False) if "files" not in instance.data: diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py index 09e1d535e4..e8a67d4f19 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py @@ -127,7 +127,7 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): self.log.info("Performing camera bakes for: {0}".format(transform)) with avalon.maya.maintained_selection(): with lib.evaluation("off"): - with lib.no_refresh(): + with avalon.maya.suspended_refresh(): baked = lib.bake_to_world_space( transform, frame_range=range_with_handles, From 7705c7ea5082e75ac28c92914a55ce498d9d0a1f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 14 Dec 2018 12:49:44 +0100 Subject: [PATCH 113/149] Remove unused function, favored by avalon.maya.suspended_refresh() --- colorbleed/maya/lib.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 20df970f74..67464360c5 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -371,26 +371,6 @@ def evaluation(mode="off"): cmds.evaluationManager(mode=original) -@contextlib.contextmanager -def no_refresh(): - """Temporarily disables Maya's UI updates - - Note: - This only disabled the main pane and will sometimes still - trigger updates in torn off panels. - - """ - - pane = _get_mel_global('gMainPane') - state = cmds.paneLayout(pane, query=True, manage=True) - cmds.paneLayout(pane, edit=True, manage=False) - - try: - yield - finally: - cmds.paneLayout(pane, edit=True, manage=state) - - @contextlib.contextmanager def empty_sets(sets, force=False): """Remove all members of the sets during the context""" From 714f98fcda3f1dfa06f5fb08ff8b7860dde5ca8d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 17 Dec 2018 12:58:17 +0100 Subject: [PATCH 114/149] PEP08 --- colorbleed/plugins/houdini/publish/extract_alembic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py index 632b0a7d7b..f817a25c86 100644 --- a/colorbleed/plugins/houdini/publish/extract_alembic.py +++ b/colorbleed/plugins/houdini/publish/extract_alembic.py @@ -23,7 +23,8 @@ class ExtractAlembic(colorbleed.api.Extractor): file_name = os.path.basename(output) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (file_name, staging_dir)) + self.log.info("Writing alembic '%s' to '%s'" % (file_name, + staging_dir)) ropnode.render() if "files" not in instance.data: From 75a032733e5f3117fc7670f206613613ab7968c2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 17 Dec 2018 13:05:25 +0100 Subject: [PATCH 115/149] Remove render in background functionality for VDB as it was unstable - Publishing would just continue even though background render was not finished yet. --- colorbleed/plugins/houdini/create/create_vbd_cache.py | 6 ++---- colorbleed/plugins/houdini/publish/extract_vdb_cache.py | 9 +++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index 30f467b3b0..617975a711 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -15,10 +15,8 @@ class CreateVDBCache(houdini.Creator): # Remove the active, we are checking the bypass flag of the nodes self.data.pop("active", None) - self.data.update({ - "node_type": "geometry", # Set node type to create for output - "executeBackground": True # Render node in background - }) + # Set node type to create for output + self.data["node_type"] = "geometry" def process(self): instance = super(CreateVDBCache, self).process() diff --git a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py index a489eb2dbb..337460dd58 100644 --- a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py +++ b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py @@ -20,13 +20,10 @@ class ExtractVDBCache(colorbleed.api.Extractor): sop_output = ropnode.evalParm("sopoutput") staging_dir = os.path.normpath(os.path.dirname(sop_output)) instance.data["stagingDir"] = staging_dir + file_name = os.path.basename(sop_output) - if instance.data.get("executeBackground", True): - self.log.info("Creating background task..") - ropnode.parm("executebackground").pressButton() - self.log.info("Finished") - else: - ropnode.render() + self.log.info("Writing VDB '%s' to '%s'" % (file_name, staging_dir)) + ropnode.render() if "files" not in instance.data: instance.data["files"] = [] From 91fdc543e471440d60c9038d0d23d6a12b3af4e9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 19 Dec 2018 10:45:05 +0100 Subject: [PATCH 116/149] Update vendor/pather to 0.1.1 - This fixes ANM-15: Automatic switching of task for some paths failed with special characters in path --- colorbleed/vendor/pather/core.py | 4 ++-- colorbleed/vendor/pather/version.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/vendor/pather/core.py b/colorbleed/vendor/pather/core.py index f2a469dc8b..1203cf9e50 100644 --- a/colorbleed/vendor/pather/core.py +++ b/colorbleed/vendor/pather/core.py @@ -9,8 +9,8 @@ import glob from .error import ParseError # Regex pattern that matches valid file -# TODO: Implement complete pattern if required -RE_FILENAME = '[-\w.,; \[\]]' +# A filename may not contain \/:*?"<>| +RE_FILENAME = r"[^\\/:\"*?<>|]" def format(pattern, data, allow_partial=True): diff --git a/colorbleed/vendor/pather/version.py b/colorbleed/vendor/pather/version.py index 85f96b1e3f..63d31c8524 100644 --- a/colorbleed/vendor/pather/version.py +++ b/colorbleed/vendor/pather/version.py @@ -1,7 +1,7 @@ VERSION_MAJOR = 0 VERSION_MINOR = 1 -VERSION_PATCH = 0 +VERSION_PATCH = 1 version_info = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) version = '%i.%i.%i' % version_info From 7cca8b80cb51f4834eece8725ed636e98f03c40d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 19 Dec 2018 10:48:08 +0100 Subject: [PATCH 117/149] Don't force build_from_path on CreateAlembicCamera default instance --- colorbleed/plugins/houdini/create/create_alembic_camera.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index 63596b1cfc..8da8c4d2b5 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -21,8 +21,6 @@ class CreateAlembicCamera(houdini.Creator): instance = super(CreateAlembicCamera, self).process() parms = {"use_sop_path": True, - "build_from_path": True, - "path_attrib": "path", "filename": "$HIP/pyblish/%s.abc" % self.name} if self.nodes: From 9fef84ef1dd8cbca80df5928d9e22a0725cdc6a8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 19 Dec 2018 10:48:35 +0100 Subject: [PATCH 118/149] Rename plug-in to be clearer and more explicit about what it does --- colorbleed/plugins/houdini/publish/collect_output_node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collect_output_node.py b/colorbleed/plugins/houdini/publish/collect_output_node.py index dbfe8a5890..a3f49761b9 100644 --- a/colorbleed/plugins/houdini/publish/collect_output_node.py +++ b/colorbleed/plugins/houdini/publish/collect_output_node.py @@ -1,13 +1,13 @@ import pyblish.api -class CollectOutputNode(pyblish.api.InstancePlugin): - """Collect the out node which of the instance""" +class CollectOutputSOPPath(pyblish.api.InstancePlugin): + """Collect the out node's SOP Path value.""" order = pyblish.api.CollectorOrder families = ["*"] hosts = ["houdini"] - label = "Collect Output Node" + label = "Collect Output SOP Path" def process(self, instance): From 5ac8394797df215fe0fba987d9f34788b1d02265 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 09:22:04 +0100 Subject: [PATCH 119/149] Fix PLN-220: ROP render error hangs Pyblish - It's because Houdini errors are old-style class error, those seem to hang Pyblish QML - See: https://gitter.im/pyblish/pyblish?at=5c1bdb49b8760c21bbfbee7b --- .../plugins/houdini/publish/extract_alembic.py | 12 +++++++++++- .../plugins/houdini/publish/extract_vdb_cache.py | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py index f817a25c86..8872372484 100644 --- a/colorbleed/plugins/houdini/publish/extract_alembic.py +++ b/colorbleed/plugins/houdini/publish/extract_alembic.py @@ -13,6 +13,8 @@ class ExtractAlembic(colorbleed.api.Extractor): def process(self, instance): + import hou + ropnode = instance[0] # Get the filename from the filename parameter @@ -25,7 +27,15 @@ class ExtractAlembic(colorbleed.api.Extractor): # We run the render self.log.info("Writing alembic '%s' to '%s'" % (file_name, staging_dir)) - ropnode.render() + try: + ropnode.render() + except hou.Error as exc: + # The hou.Error is not inherited from a Python Exception class, + # so we explicitly capture the houdini error, otherwise pyblish + # will remain hanging. + import traceback + traceback.print_exc() + raise RuntimeError("Render failed: {0}".format(exc)) if "files" not in instance.data: instance.data["files"] = [] diff --git a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py index 337460dd58..7fe443b5a1 100644 --- a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py +++ b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py @@ -13,6 +13,8 @@ class ExtractVDBCache(colorbleed.api.Extractor): def process(self, instance): + import hou + ropnode = instance[0] # Get the filename from the filename parameter @@ -23,7 +25,15 @@ class ExtractVDBCache(colorbleed.api.Extractor): file_name = os.path.basename(sop_output) self.log.info("Writing VDB '%s' to '%s'" % (file_name, staging_dir)) - ropnode.render() + try: + ropnode.render() + except hou.Error as exc: + # The hou.Error is not inherited from a Python Exception class, + # so we explicitly capture the houdini error, otherwise pyblish + # will remain hanging. + import traceback + traceback.print_exc() + raise RuntimeError("Render failed: {0}".format(exc)) if "files" not in instance.data: instance.data["files"] = [] From 8959c6276010e1b6ba11c1bdeb4b6714080acab5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 15:56:13 +0100 Subject: [PATCH 120/149] Implement validation of prims attribute for Build from Hierarchy in ROP --- .../validate_primitive_hierarchy_paths.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py diff --git a/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py b/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py new file mode 100644 index 0000000000..6563f4bd87 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py @@ -0,0 +1,74 @@ +import pyblish.api +import colorbleed.api + + +class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): + """Validate all primitives build hierarchy from attribute when enabled. + + The name of the attribute must exist on the prims and have the same name + as Build Hierarchy from Attribute's `Path Attribute` value on the Alembic + ROP node whenever Build Hierarchy from Attribute is enabled. + + """ + + order = colorbleed.api.ValidateContentsOrder + 0.1 + families = ["colorbleed.pointcache"] + hosts = ["houdini"] + label = "Validate Prims Hierarchy Path" + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("See log for details. " + "Invalid nodes: {0}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + + import hou + + output = instance.data["output_node"] + prims = output.geometry().prims() + + rop = instance[0] + build_from_path = rop.parm("build_from_path").eval() + if not build_from_path: + cls.log.debug("Alembic ROP has 'Build from Path' disabled. " + "Validation is ignored..") + + path_attr = rop.parm("path_attrib").eval() + if not path_attr: + cls.log.error("The Alembic ROP node has no Path Attribute" + "value set, but 'Build Hierarchy from Attribute'" + "is enabled.") + return [rop.path()] + + cls.log.debug("Checking for attribute: %s" % path_attr) + + missing_attr = [] + invalid_attr = [] + for prim in prims: + + try: + path = prim.stringAttribValue(path_attr) + except hou.OperationFailed: + # Attribute does not exist. + missing_attr.append(prim) + continue + + if not path: + # Empty path value is invalid. + invalid_attr.append(prim) + continue + + if missing_attr: + cls.log.info("Prims are missing attribute `%s`" % path_attr) + + if invalid_attr: + cls.log.info("Prims have no value for attribute `%s` " + "(%s of %s prims)" % (path_attr, + len(invalid_attr), + len(prims))) + + if missing_attr or invalid_attr: + return [output.path()] From 39fde42c407efaa6bf9d361dbbd86fd6285a80a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 16:00:16 +0100 Subject: [PATCH 121/149] Remove duplicate plug-in functionality - This is replaced by validate_output_node.py --- .../publish/validate_outnode_exists.py | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 colorbleed/plugins/houdini/publish/validate_outnode_exists.py diff --git a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py deleted file mode 100644 index ff682ee820..0000000000 --- a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py +++ /dev/null @@ -1,52 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidatOutputNodeExists(pyblish.api.InstancePlugin): - """Validate if node attribute Create intermediate Directories is turned on - - Rules: - * The node must have Create intermediate Directories turned on to - ensure the output file will be created - - """ - - order = colorbleed.api.ValidateContentsOrder - families = ["*"] - hosts = ['houdini'] - label = "Output Node Exists" - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Could not find output node(s)!") - - @classmethod - def get_invalid(cls, instance): - - import hou - - result = set() - - node = instance[0] - assert node, "No node in instance. This is a bug." - - if node.type().name() == "alembic": - soppath_parm = "sop_path" - else: - # Fall back to geometry node - soppath_parm = "soppath" - - sop_path = node.parm(soppath_parm).eval() - output_node = hou.node(sop_path) - - if output_node is None: - cls.log.error("Node at '%s' does not exist" % sop_path) - result.add(node.path()) - - # Added cam as this is a legit output type (cameras can't - if output_node.type().name() not in ["output", "cam"]: - cls.log.error("SOP Path does not end path at output node") - result.add(node.path()) - - return result From ef179da1f2d4e2a2e8f8ccbac7c11d7119d8a86c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 16:03:00 +0100 Subject: [PATCH 122/149] Remove duplicated information in docstring, simplify readability. --- .../plugins/houdini/publish/validate_mkpaths_toggled.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py index 7e298ce952..1a83565d11 100644 --- a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py +++ b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -3,13 +3,7 @@ import colorbleed.api class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): - """Validate if node attribute Create intermediate Directories is turned on - - Rules: - * The node must have Create intermediate Directories turned on to - ensure the output file will be created - - """ + """Validate Create Intermediate Directories is enabled on ROP node.""" order = colorbleed.api.ValidateContentsOrder families = ['colorbleed.pointcache'] From 6144af98df4dfb9f9abc421c0891b099080df5df Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 16:05:18 +0100 Subject: [PATCH 123/149] Actually ignore validation when it should. --- .../houdini/publish/validate_primitive_hierarchy_paths.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py b/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py index 6563f4bd87..ab91970b20 100644 --- a/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py +++ b/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py @@ -35,6 +35,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): if not build_from_path: cls.log.debug("Alembic ROP has 'Build from Path' disabled. " "Validation is ignored..") + return path_attr = rop.parm("path_attrib").eval() if not path_attr: From d8bcf279c2294f55c04cfa26cb82d09e290a8180 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 22:22:38 +0100 Subject: [PATCH 124/149] Add docstring to Houdini Creators --- colorbleed/plugins/houdini/create/create_alembic_camera.py | 1 + colorbleed/plugins/houdini/create/create_pointcache.py | 2 +- colorbleed/plugins/houdini/create/create_vbd_cache.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index 8da8c4d2b5..c548a6309a 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -2,6 +2,7 @@ from avalon import houdini class CreateAlembicCamera(houdini.Creator): + """Single baked camera from Alembic ROP""" name = "camera" label = "Camera (Abc)" diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index c54a6c91a6..4eab69fd98 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -2,7 +2,7 @@ from avalon import houdini class CreatePointCache(houdini.Creator): - """Alembic pointcache for animated data""" + """Alembic ROP to pointcache""" name = "pointcache" label = "Point Cache" diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index 617975a711..ebdb1271ce 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -2,7 +2,7 @@ from avalon import houdini class CreateVDBCache(houdini.Creator): - """Alembic pointcache for animated data""" + """OpenVDB from Geometry ROP""" name = "vbdcache" label = "VDB Cache" From 7098df26e7fc511d2c540784cfea4467adefd1a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 22:24:44 +0100 Subject: [PATCH 125/149] Cosmetics typo --- colorbleed/plugins/houdini/create/create_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 4eab69fd98..85993678c2 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -22,7 +22,7 @@ class CreatePointCache(houdini.Creator): parms = {"use_sop_path": True, # Export single node from SOP Path "build_from_path": True, # Direct path of primitive in output - "path_attrib": "path", # Pass path attribute for output\ + "path_attrib": "path", # Pass path attribute for output "prim_to_detail_pattern": "cbId", "format": 2, # Set format to Ogawa "filename": "$HIP/pyblish/%s.abc" % self.name} From c33e1274f35c4ada86ab21e7ad5e8bae023be3e6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 22:32:48 +0100 Subject: [PATCH 126/149] Fix docstring typo --- .../plugins/houdini/publish/validate_alembic_input_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py b/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py index 91f9e9f97e..663b1198eb 100644 --- a/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py +++ b/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py @@ -7,7 +7,7 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin): The connected node cannot be of the following types for Alembic: - VDB - - Volumne + - Volume """ From 3e2b506cf302d1ac27e134ef14d30f725231fd84 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 23:25:15 +0100 Subject: [PATCH 127/149] Implement Alembic Camera extraction in Houdini - the right way --- .../houdini/create/create_alembic_camera.py | 20 +++++++-- .../houdini/publish/collect_output_node.py | 3 +- .../houdini/publish/validate_camera_rop.py | 41 +++++++++++++++++++ .../publish/validate_mkpaths_toggled.py | 3 +- .../houdini/publish/validate_output_node.py | 10 ++--- 5 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 colorbleed/plugins/houdini/publish/validate_camera_rop.py diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index c548a6309a..96449fb3db 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -21,11 +21,25 @@ class CreateAlembicCamera(houdini.Creator): def process(self): instance = super(CreateAlembicCamera, self).process() - parms = {"use_sop_path": True, - "filename": "$HIP/pyblish/%s.abc" % self.name} + parms = { + "filename": "$HIP/pyblish/%s.abc" % self.name, + "use_sop_path": False + } if self.nodes: node = self.nodes[0] - parms.update({"sop_path": node.path()}) + path = node.path() + + # Split the node path into the first root and the remainder + # So we can set the root and objects parameters correctly + _, root, remainder = path.split("/", 2) + parms.update({ + "root": "/" + root, + "objects": remainder + }) instance.setParms(parms) + + # Lock the Use Sop Path setting so the + # user doesn't accidentally enable it. + instance.parm("use_sop_path").lock(True) diff --git a/colorbleed/plugins/houdini/publish/collect_output_node.py b/colorbleed/plugins/houdini/publish/collect_output_node.py index a3f49761b9..d90898944f 100644 --- a/colorbleed/plugins/houdini/publish/collect_output_node.py +++ b/colorbleed/plugins/houdini/publish/collect_output_node.py @@ -5,7 +5,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): """Collect the out node's SOP Path value.""" order = pyblish.api.CollectorOrder - families = ["*"] + families = ["colorbleed.pointcache", + "colorbleed.vdbcache"] hosts = ["houdini"] label = "Collect Output SOP Path" diff --git a/colorbleed/plugins/houdini/publish/validate_camera_rop.py b/colorbleed/plugins/houdini/publish/validate_camera_rop.py new file mode 100644 index 0000000000..bf09b26df4 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_camera_rop.py @@ -0,0 +1,41 @@ +import pyblish.api +import colorbleed.api + + +class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): + """Validate Camera ROP settings.""" + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.camera'] + hosts = ['houdini'] + label = 'Camera ROP' + + def process(self, instance): + + import hou + + node = instance[0] + if node.parm("use_sop_path").eval(): + raise RuntimeError("Alembic ROP for Camera export should not be " + "set to 'Use Sop Path'. Please disable.") + + # Get the root and objects parameter of the Alembic ROP node + root = node.parm("root").eval() + objects = node.parm("objects").eval() + assert root, "Root parameter must be set on Alembic ROP" + assert root.startswith("/"), "Root parameter must start with slash /" + assert objects, "Objects parameter must be set on Alembic ROP" + assert len(objects.split(" ")) == 1, "Must have only a single object." + + # Check if the object exists and is a camera + path = root + "/" + objects + camera = hou.node(path) + + if not camera: + raise ValueError("Camera path does not exist: %s" % path) + + if not camera.type().name() == "cam": + raise ValueError("Object set in Alembic ROP is not a camera: " + "%s (type: %s)" % (camera, camera.type().name())) + + diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py index 1a83565d11..a66c6754be 100644 --- a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py +++ b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -6,7 +6,8 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): """Validate Create Intermediate Directories is enabled on ROP node.""" order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.pointcache'] + families = ['colorbleed.pointcache', + 'colorbleed.camera'] hosts = ['houdini'] label = 'Create Intermediate Directories Checked' diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index bb9eec508e..8a088afec9 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -13,7 +13,8 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["*"] + families = ["colorbleed.pointcache", + "colorbleed.vdbcache"] hosts = ["houdini"] label = "Validate Output Node" @@ -39,10 +40,9 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): # Check if type is correct type_name = output_node.type().name() - if type_name not in ["output", "cam"]: - cls.log.error("Output node `%s` is not an accepted type." - "Expected types: `output` or `camera`" % - output_node.path()) + if type_name != "output": + cls.log.error("Output node `%s` is not an `output` type node." + % output_node.path()) return [output_node.path()] # Check if output node has incoming connections From fa11bb55cdaf539d8faeefa09191c08e505963a9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 23:29:49 +0100 Subject: [PATCH 128/149] Refactor class name (fix typo) --- colorbleed/plugins/houdini/publish/validate_camera_rop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_camera_rop.py b/colorbleed/plugins/houdini/publish/validate_camera_rop.py index bf09b26df4..83f4dd5a57 100644 --- a/colorbleed/plugins/houdini/publish/validate_camera_rop.py +++ b/colorbleed/plugins/houdini/publish/validate_camera_rop.py @@ -2,7 +2,7 @@ import pyblish.api import colorbleed.api -class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): +class ValidateCameraROP(pyblish.api.InstancePlugin): """Validate Camera ROP settings.""" order = colorbleed.api.ValidateContentsOrder From a6c6b1ee2d8e841c02245d3c01441ccf4bfbc39a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 31 Dec 2018 12:03:08 +0100 Subject: [PATCH 129/149] Add Work Files app as icon in toolbar in Maya --- colorbleed/maya/customize.py | 13 +++++++++++++ res/icons/workfiles.png | Bin 0 -> 205 bytes 2 files changed, 13 insertions(+) create mode 100644 res/icons/workfiles.png diff --git a/colorbleed/maya/customize.py b/colorbleed/maya/customize.py index 58f2f6d8a5..8b2dda7540 100644 --- a/colorbleed/maya/customize.py +++ b/colorbleed/maya/customize.py @@ -77,6 +77,7 @@ def override_toolbox_ui(): import avalon.tools.cbsceneinventory as inventory import avalon.tools.cbloader as loader + from avalon.maya.pipeline import launch_workfiles_app # Ensure the maya web icon on toolbox exists web_button = "ToolBox|MainToolboxLayout|mayaWebButton" @@ -97,6 +98,18 @@ def override_toolbox_ui(): background_color = (0.267, 0.267, 0.267) controls = [] + control = mc.iconTextButton( + "colorbleed_toolbox_workfiles", + annotation="Work Files", + label="Work Files", + image=os.path.join(icons, "workfiles.png"), + command=lambda: launch_workfiles_app(), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent) + controls.append(control) + control = mc.iconTextButton( "colorbleed_toolbox_loader", annotation="Loader", diff --git a/res/icons/workfiles.png b/res/icons/workfiles.png new file mode 100644 index 0000000000000000000000000000000000000000..f17c869600c4deff23b1a556b54676a9b2943413 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X*aCb)Tz}4y`Tzg_%-XM3KoO>rAiv=M3{STkcma9-o-U3d z6}PTjHsk}De&E~x`DG#^T+J76O!ejvIG|*~EO3O$p+n|C^U{wZh6YYdf7mX?-ev!C w{XRo=gJuHj1(vHK{$`$m_FrdfIX5seOnb>BSjqC?1kh*(Pgg&ebxsLQ00S>Xn*aa+ literal 0 HcmV?d00001 From 105657a0a3e4f7b49b0ee85db985f815c1660272 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 7 Jan 2019 18:28:58 +0100 Subject: [PATCH 130/149] Support Yeti multiple image search paths (split by os.path.pathsep) --- .../plugins/maya/publish/collect_yeti_rig.py | 88 ++++++++++++++----- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index c673581c8a..0400de25d7 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -99,7 +99,13 @@ class CollectYetiRig(pyblish.api.InstancePlugin): list """ resources = [] - image_search_path = cmds.getAttr("{}.imageSearchPath".format(node)) + + image_search_paths = cmds.getAttr("{}.imageSearchPath".format(node)) + + # TODO: Somehow this uses OS environment path separator, `:` vs `;` + # Later on check whether this is pipeline OS cross-compatible. + image_search_paths = [p for p in + image_search_paths.split(os.path.pathsep) if p] # List all related textures texture_filenames = cmds.pgYetiCommand(node, listTextures=True) @@ -111,36 +117,33 @@ class CollectYetiRig(pyblish.api.InstancePlugin): type="reference") self.log.info("Found %i reference node(s)" % len(reference_nodes)) - if texture_filenames and not image_search_path: + if texture_filenames and not image_search_paths: raise ValueError("pgYetiMaya node '%s' is missing the path to the " "files in the 'imageSearchPath " "atttribute'" % node) # Collect all texture files for texture in texture_filenames: - item = {"files": [], "source": texture, "node": node} - texture_filepath = os.path.join(image_search_path, texture) - if len(texture.split(".")) > 2: - - # For UDIM based textures (tiles) - if "" in texture: - sequences = self.get_sequence(texture_filepath, - pattern="") - item["files"].extend(sequences) - - # Based textures (animated masks f.e) - elif "%04d" in texture: - sequences = self.get_sequence(texture_filepath, - pattern="%04d") - item["files"].extend(sequences) - # Assuming it is a fixed name - else: - item["files"].append(texture_filepath) - else: - item["files"].append(texture_filepath) + item = { + "files": self.search_textures(paths=image_search_paths, + texture=texture), + "source": texture, + "node": node + } resources.append(item) + # For now validate that every texture has at least a single file + # resolved. Since a 'resource' does not have the requirement of having + # a `files` explicitly mapped it's not explicitly validated. + # TODO: Validate this as a validator + invalid_resources = [] + for resource in resources: + if not resource['files']: + invalid_resources.append(resource) + if invalid_resources: + raise RuntimeError("Invalid resources") + # Collect all referenced files for reference_node in reference_nodes: ref_file = cmds.pgYetiGraph(node, @@ -149,6 +152,9 @@ class CollectYetiRig(pyblish.api.InstancePlugin): getParamValue=True) if not os.path.isfile(ref_file): + self.log.warning("Reference node '%s' has no valid file " + "path set: %s" % (reference_node, ref_file)) + # TODO: This should allow to pass and fail in Validator instead raise RuntimeError("Reference file must be a full file path!") # Create resource dict @@ -169,6 +175,44 @@ class CollectYetiRig(pyblish.api.InstancePlugin): return resources + def search_textures(self, paths, texture): + """Search the texture source files in the image search paths. + + This also parses to full sequences. + + """ + + # TODO: Check if texture is abspath, if so don't search. + + # Collect the first matching texture from the search paths + for root in paths: + texture_filepath = os.path.join(root, texture) + + # Collect full sequence if it matches a sequence pattern + if len(texture.split(".")) > 2: + + # For UDIM based textures (tiles) + if "" in texture: + sequences = self.get_sequence(texture_filepath, + pattern="") + if sequences: + return sequences + + # Based textures (animated masks f.e) + elif "%04d" in texture: + sequences = self.get_sequence(texture_filepath, + pattern="%04d") + if sequences: + return sequences + + # Assuming it is a fixed name + if os.path.exists(texture_filepath): + return [texture_filepath] + + self.log.warning("No texture found for: %s (searched: %s)" % (texture, + paths)) + return [] + def get_sequence(self, filename, pattern="%04d"): """Get sequence from filename From c6b8375cd2f542d631c35e09bb3238554e80eb49 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Jan 2019 14:20:11 +0100 Subject: [PATCH 131/149] Yeti: Add support for absolute path textures --- .../plugins/maya/publish/collect_yeti_rig.py | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 0400de25d7..0f7b0ff6bd 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -124,9 +124,27 @@ class CollectYetiRig(pyblish.api.InstancePlugin): # Collect all texture files for texture in texture_filenames: + + files = [] + if os.path.isabs(texture): + self.log.debug("Texture is absolute path, ignoring " + "image search paths.. %s" % texture) + files = self.search_textures(texture) + else: + for root in image_search_paths: + filepath = os.path.join(root, image_search_paths) + files = self.search_textures(filepath) + if files: + # Break out on first match in search paths.. + break + + if not files: + self.log.warning( + "No texture found for: %s " + "(searched: %s)" % (texture, image_search_paths)) + item = { - "files": self.search_textures(paths=image_search_paths, - texture=texture), + "files": files, "source": texture, "node": node } @@ -175,46 +193,42 @@ class CollectYetiRig(pyblish.api.InstancePlugin): return resources - def search_textures(self, paths, texture): + def search_textures(self, filepath): """Search the texture source files in the image search paths. This also parses to full sequences. """ + filename = os.path.basename(filepath) - # TODO: Check if texture is abspath, if so don't search. + # Collect full sequence if it matches a sequence pattern + if len(filename.split(".")) > 2: - # Collect the first matching texture from the search paths - for root in paths: - texture_filepath = os.path.join(root, texture) + # For UDIM based textures (tiles) + if "" in filename: + sequences = self.get_sequence(filepath, + pattern="") + if sequences: + return sequences - # Collect full sequence if it matches a sequence pattern - if len(texture.split(".")) > 2: + # Frame/time - Based textures (animated masks f.e) + elif "%04d" in filename: + sequences = self.get_sequence(filepath, + pattern="%04d") + if sequences: + return sequences - # For UDIM based textures (tiles) - if "" in texture: - sequences = self.get_sequence(texture_filepath, - pattern="") - if sequences: - return sequences - - # Based textures (animated masks f.e) - elif "%04d" in texture: - sequences = self.get_sequence(texture_filepath, - pattern="%04d") - if sequences: - return sequences - - # Assuming it is a fixed name - if os.path.exists(texture_filepath): - return [texture_filepath] - - self.log.warning("No texture found for: %s (searched: %s)" % (texture, - paths)) + # Assuming it is a fixed name (single file) + if os.path.exists(filepath): + return [filepath] return [] def get_sequence(self, filename, pattern="%04d"): - """Get sequence from filename + """Get sequence from filename. + + This will only return files if they exist on disk as it tries + to collect the sequence using the filename pattern and searching + for them on disk. Supports negative frame ranges like -001, 0000, 0001 and -0001, 0000, 0001. From dd8e962818b94b7a3e2cba789110905e3da95a78 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Jan 2019 16:28:37 +0100 Subject: [PATCH 132/149] Fix typo and improve debug message readability --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 0f7b0ff6bd..a2ab1c608b 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -128,11 +128,11 @@ class CollectYetiRig(pyblish.api.InstancePlugin): files = [] if os.path.isabs(texture): self.log.debug("Texture is absolute path, ignoring " - "image search paths.. %s" % texture) + "image search paths for: %s" % texture) files = self.search_textures(texture) else: for root in image_search_paths: - filepath = os.path.join(root, image_search_paths) + filepath = os.path.join(root, texture) files = self.search_textures(filepath) if files: # Break out on first match in search paths.. From 12005b62d11589e94858475c700f6baaafd0e156 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Jan 2019 16:32:14 +0100 Subject: [PATCH 133/149] Correctly check reference node content with animated %04d inputs --- .../plugins/maya/publish/collect_yeti_rig.py | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index a2ab1c608b..280721cd9d 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -169,25 +169,27 @@ class CollectYetiRig(pyblish.api.InstancePlugin): param="reference_file", getParamValue=True) - if not os.path.isfile(ref_file): - self.log.warning("Reference node '%s' has no valid file " - "path set: %s" % (reference_node, ref_file)) - # TODO: This should allow to pass and fail in Validator instead - raise RuntimeError("Reference file must be a full file path!") - # Create resource dict - item = {"files": [], - "source": ref_file, - "node": node, - "graphnode": reference_node, - "param": "reference_file"} + item = { + "source": ref_file, + "node": node, + "graphnode": reference_node, + "param": "reference_file", + "files": [] + } ref_file_name = os.path.basename(ref_file) if "%04d" in ref_file_name: - ref_files = self.get_sequence(ref_file) - item["files"].extend(ref_files) + item["files"] = self.get_sequence(ref_file) else: - item["files"].append(ref_file) + if os.path.exists(ref_file) and os.path.isfile(ref_file): + item["files"] = [ref_file] + + if not item["files"]: + self.log.warning("Reference node '%s' has no valid file " + "path set: %s" % (reference_node, ref_file)) + # TODO: This should allow to pass and fail in Validator instead + raise RuntimeError("Reference node must be a full file path!") resources.append(item) From 8d63ed0c059140220ac76712f37d3c6e29ae9a00 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Jan 2019 16:37:23 +0100 Subject: [PATCH 134/149] Improve docstring --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 280721cd9d..c9ac31e5a1 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -196,9 +196,17 @@ class CollectYetiRig(pyblish.api.InstancePlugin): return resources def search_textures(self, filepath): - """Search the texture source files in the image search paths. + """Search all texture files on disk. - This also parses to full sequences. + This also parses to full sequences for those with dynamic patterns + like and %04d in the filename. + + Args: + filepath (str): The full path to the file, including any + dynamic patterns like or %04d + + Returns: + list: The files found on disk """ filename = os.path.basename(filepath) @@ -223,6 +231,7 @@ class CollectYetiRig(pyblish.api.InstancePlugin): # Assuming it is a fixed name (single file) if os.path.exists(filepath): return [filepath] + return [] def get_sequence(self, filename, pattern="%04d"): From 2cc5c1cc44f200a50fe66ef13741bfd01036d361 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Jan 2019 16:38:53 +0100 Subject: [PATCH 135/149] Refactor 'filename' to 'filepath' as it refers to full file path --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index c9ac31e5a1..0308b296ae 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -234,7 +234,7 @@ class CollectYetiRig(pyblish.api.InstancePlugin): return [] - def get_sequence(self, filename, pattern="%04d"): + def get_sequence(self, filepath, pattern="%04d"): """Get sequence from filename. This will only return files if they exist on disk as it tries @@ -245,7 +245,7 @@ class CollectYetiRig(pyblish.api.InstancePlugin): 0000, 0001. Arguments: - filename (str): The full path to filename containing the given + filepath (str): The full path to filename containing the given pattern. pattern (str): The pattern to swap with the variable frame number. @@ -255,10 +255,10 @@ class CollectYetiRig(pyblish.api.InstancePlugin): """ from avalon.vendor import clique - escaped = re.escape(filename) + escaped = re.escape(filepath) re_pattern = escaped.replace(pattern, "-?[0-9]+") - source_dir = os.path.dirname(filename) + source_dir = os.path.dirname(filepath) files = [f for f in os.listdir(source_dir) if re.match(re_pattern, f)] From 49bdf50384212f7c55f8fcec9d3477091e43b591 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 9 Jan 2019 21:07:02 +0100 Subject: [PATCH 136/149] Fix typo (missing comma) and refactor v-ray environment variables - The V-Ray Environment variables changed from V-Ray 3+ to V-Ray Next. The x64 suffix was dropped. --- colorbleed/plugins/maya/publish/submit_maya_deadline.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_maya_deadline.py b/colorbleed/plugins/maya/publish/submit_maya_deadline.py index d2b394c1d1..430f121779 100644 --- a/colorbleed/plugins/maya/publish/submit_maya_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_maya_deadline.py @@ -207,9 +207,9 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # todo: This is a temporary fix for yeti variables "PEREGRINEL_LICENSE", "REDSHIFT_MAYAEXTENSIONSPATH", - "REDSHIFT_DISABLEOUTPUTLOCKFILES" - "VRAY_FOR_MAYA2018_PLUGINS_X64", - "VRAY_PLUGINS_X64", + "REDSHIFT_DISABLEOUTPUTLOCKFILES", + "VRAY_FOR_MAYA2018_PLUGINS", + "VRAY_PLUGINS", "VRAY_USE_THREAD_AFFINITY", "MAYA_MODULE_PATH" ] From 9b832b486e5ee469208e5a8f6b28c24aadcd99cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 14 Jan 2019 18:24:57 +0100 Subject: [PATCH 137/149] Don't force output node, but allow any SOP node. - Also validate there's actual .geometry() data in the node. --- .../houdini/publish/validate_output_node.py | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index 8a088afec9..5f55e1507b 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -7,8 +7,9 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): This will ensure: - The SOP Path is set. - The SOP Path refers to an existing object. - - The SOP Path node is of type 'output' or 'camera' + - The SOP Path node is a SOP node. - The SOP Path node has at least one input connection (has an input) + - The SOP Path has geometry data. """ @@ -28,6 +29,8 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): + import hou + output_node = instance.data["output_node"] if output_node is None: @@ -38,15 +41,33 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): return node.path() - # Check if type is correct - type_name = output_node.type().name() - if type_name != "output": - cls.log.error("Output node `%s` is not an `output` type node." - % output_node.path()) - return [output_node.path()] + # Output node must be a Sop node. + if not isinstance(output_node, hou.SopNode): + cls.log.error("Output node %s is not a SOP node. " + "SOP Path must point to a SOP node, " + "instead found category type: %s" % ( + output_node.path(), + output_node.type().category().name() + ) + ) + return [output_node] + + # For the sake of completeness also assert the category type + # is Sop to avoid potential edge case scenarios even though + # the isinstance check above should be stricter than this category + assert output_node.type().category().name() == "Sop", ( + "Output node %s is not of category Sop. This is a bug.." % + output_node.path() + ) # Check if output node has incoming connections - if type_name == "output" and not output_node.inputConnections(): + if output_node.inputConnections(): cls.log.error("Output node `%s` has no incoming connections" % output_node.path()) return [output_node.path()] + + # Ensure the output node has at least Geometry data + if not output_node.geometry(): + cls.log.error("Output node `%s` has no geometry data." + % output_node.path()) + return [output_node.path()] From 9d8133ef82cd1d23c7fb280dda958d330b4d8290 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 09:27:26 +0100 Subject: [PATCH 138/149] Improve Houdini saved filename collection, ignore default "untitled.hip" --- .../houdini/publish/collect_current_file.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/collect_current_file.py b/colorbleed/plugins/houdini/publish/collect_current_file.py index 7852943b34..ea954c4791 100644 --- a/colorbleed/plugins/houdini/publish/collect_current_file.py +++ b/colorbleed/plugins/houdini/publish/collect_current_file.py @@ -1,3 +1,4 @@ +import os import hou import pyblish.api @@ -12,4 +13,24 @@ class CollectHoudiniCurrentFile(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" - context.data['currentFile'] = hou.hipFile.path() + + filepath = hou.hipFile.path() + if not os.path.exists(filepath): + # By default Houdini will even point a new scene to a path. + # However if the file is not saved at all and does not exist, + # we assume the user never set it. + filepath = "" + + elif os.path.basename(filepath) == "untitled.hip": + # Due to even a new file being called 'untitled.hip' we are unable + # to confirm the current scene was ever saved because the file + # could have existed already. We will allow it if the file exists, + # but show a warning for this edge case to clarify the potential + # false positive. + self.log.warning("Current file is 'untitled.hip' and we are " + "unable to detect whether the current scene is " + "saved correctly.") + + context.data['currentFile'] = filepath + + From f95ef7dd1ca922864b2071df11f78266b95381d0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 09:46:07 +0100 Subject: [PATCH 139/149] Houdini correctly set and validate ROP bypass state on instance toggle --- colorbleed/houdini/__init__.py | 19 +++++++++++ .../houdini/publish/validate_bypass.py | 34 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/validate_bypass.py diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index e4640e2857..05d5195605 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -39,6 +39,8 @@ def install(): avalon.on("save", on_save) avalon.on("open", on_open) + pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + log.info("Setting default family states for loader..") avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] @@ -91,3 +93,20 @@ def on_open(*args): "your Maya scene.") dialog.on_show.connect(_on_show_inventory) dialog.show() + + +def on_pyblish_instance_toggled(instance, new_value, old_value): + """Toggle saver tool passthrough states on instance toggles.""" + + nodes = instance[:] + if not nodes: + return + + # Assume instance node is first node + instance_node = nodes[0] + + if instance_node.isBypassed() != (not old_value): + print("%s old bypass state didn't match old instance state, " + "updating anyway.." % instance_node.path()) + + instance_node.bypass(not new_value) diff --git a/colorbleed/plugins/houdini/publish/validate_bypass.py b/colorbleed/plugins/houdini/publish/validate_bypass.py new file mode 100644 index 0000000000..9af8a2b9ae --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_bypass.py @@ -0,0 +1,34 @@ +import pyblish.api +import colorbleed.api + + +class ValidateBypassed(pyblish.api.InstancePlugin): + """Validate all primitives build hierarchy from attribute when enabled. + + The name of the attribute must exist on the prims and have the same name + as Build Hierarchy from Attribute's `Path Attribute` value on the Alembic + ROP node whenever Build Hierarchy from Attribute is enabled. + + """ + + order = colorbleed.api.ValidateContentsOrder - 0.1 + families = ["*"] + hosts = ["houdini"] + label = "Validate ROP Bypass" + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + rop = invalid[0] + raise RuntimeError( + "ROP node %s is set to bypass, publishing cannot continue.." % + rop.path() + ) + + @classmethod + def get_invalid(cls, instance): + + rop = instance[0] + if rop.isBypassed(): + return [rop] From 5c320088de197080093367d6ada017ae186a43ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 10:12:59 +0100 Subject: [PATCH 140/149] Improve error readability --- colorbleed/plugins/houdini/publish/validate_output_node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index 5f55e1507b..81b4e1e3d5 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -23,6 +23,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: + invalid = [node.path() for node in invalid] raise RuntimeError("Output node(s) `%s` are incorrect. " "See plug-in log for details." % invalid) From b5424e2954147eb2aaf4f6824f26e0641b74e48b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 10:13:36 +0100 Subject: [PATCH 141/149] Include colorbleed.vdbcache family, and report node path in error --- .../plugins/houdini/publish/validate_mkpaths_toggled.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py index a66c6754be..826dedf933 100644 --- a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py +++ b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -7,7 +7,8 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder families = ['colorbleed.pointcache', - 'colorbleed.camera'] + 'colorbleed.camera', + 'colorbleed.vdbcache'] hosts = ['houdini'] label = 'Create Intermediate Directories Checked' @@ -15,8 +16,8 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Found ROP nodes with Create Intermediate " - "Directories turned off") + raise RuntimeError("Found ROP node with Create Intermediate " + "Directories turned off: %s" % invalid) @classmethod def get_invalid(cls, instance): From f7d755d9fe80959f222c720c4ee7a5d7fc425dc2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 10:20:47 +0100 Subject: [PATCH 142/149] Correctly pass on node paths for error message readability --- colorbleed/plugins/houdini/publish/validate_output_node.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index 81b4e1e3d5..eb84ea721b 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -23,7 +23,6 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - invalid = [node.path() for node in invalid] raise RuntimeError("Output node(s) `%s` are incorrect. " "See plug-in log for details." % invalid) @@ -40,7 +39,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): "Ensure a valid SOP output path is set." % node.path()) - return node.path() + return [node.path()] # Output node must be a Sop node. if not isinstance(output_node, hou.SopNode): @@ -51,7 +50,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): output_node.type().category().name() ) ) - return [output_node] + return [output_node.path()] # For the sake of completeness also assert the category type # is Sop to avoid potential edge case scenarios even though From a3daa895e9ffb7b9951e2f0bc2e187a224f424c6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 11:01:27 +0100 Subject: [PATCH 143/149] Fix validation check (typo) --- colorbleed/plugins/houdini/publish/validate_output_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index eb84ea721b..4b140f63df 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -61,7 +61,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): ) # Check if output node has incoming connections - if output_node.inputConnections(): + if not output_node.inputConnections(): cls.log.error("Output node `%s` has no incoming connections" % output_node.path()) return [output_node.path()] From 497463dc32457605dd3a3b9cd543f5e2cb9bcef7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 17:47:50 +0100 Subject: [PATCH 144/149] Fix Yeti callback validation (compare with stripped commands) --- .../validate_yeti_renderscript_callbacks.py | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py b/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py index b31e31ba52..8864637da2 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py @@ -25,6 +25,17 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["colorbleed.renderlayer"] + # Settings per renderer + callbacks = { + "vray": { + "pre": "catch(`pgYetiVRayPreRender`)", + "post": "catch(`pgYetiVRayPostRender`)" + }, + "arnold": { + "pre": "pgYetiPreRender" + } + } + def process(self, instance): invalid = self.get_invalid(instance) @@ -35,14 +46,6 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - # lookup per render - render_scripts = {"vray": - {"pre": "catch(`pgYetiVRayPreRender`)", - "post": "catch(`pgYetiVRayPostRender`)"}, - "arnold": - {"pre": "pgYetiPreRender"} - } - yeti_loaded = cmds.pluginInfo("pgYetiMaya", query=True, loaded=True) renderer = instance.data["renderer"] @@ -50,25 +53,29 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): cls.log.info("Redshift ignores any pre and post render callbacks") return False - callback_lookup = render_scripts.get(renderer, {}) + callback_lookup = cls.callbacks.get(renderer, {}) if not callback_lookup: cls.log.warning("Renderer '%s' is not supported in this plugin" % renderer) return False - pre_mel_attr = "defaultRenderGlobals.preMel" - post_mel_attr = "defaultRenderGlobals.postMel" + pre_mel = cmds.getAttr("defaultRenderGlobals.preMel") or "" + post_mel = cmds.getAttr("defaultRenderGlobals.postMel") or "" - pre_render_callback = cmds.getAttr(pre_mel_attr) or "" - post_render_callback = cmds.getAttr(post_mel_attr) or "" + if pre_mel.strip(): + cls.log.debug("Found pre mel: `%s`" % pre_mel) - pre_callbacks = pre_render_callback.split(";") - post_callbacks = post_render_callback.split(";") + if post_mel.strip(): + cls.log.debug("Found post mel: `%s`" % post_mel) + + # Strip callbacks and turn into a set for quick lookup + pre_callbacks = {cmd.strip() for cmd in pre_mel.split(";")} + post_callbacks = {cmd.strip() for cmd in post_mel.split(";")} pre_script = callback_lookup.get("pre", "") post_script = callback_lookup.get("post", "") - # If not loaded + # If Yeti is not loaded invalid = False if not yeti_loaded: if pre_script and pre_script in pre_callbacks: @@ -80,18 +87,19 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): cls.log.error("Found post render callback '%s which is " "not used!" % post_script) invalid = True - else: - if pre_script: - if pre_script not in pre_callbacks: - cls.log.error( - "Could not find required pre render callback " - "`%s`" % pre_script) - invalid = True - if post_script: - if post_script not in post_callbacks: - cls.log.error("Could not find required post render callback" - " `%s`" % post_script) - invalid = True + # If Yeti is loaded + else: + if pre_script and pre_script not in pre_callbacks: + cls.log.error( + "Could not find required pre render callback " + "`%s`" % pre_script) + invalid = True + + if post_script and post_script not in post_callbacks: + cls.log.error( + "Could not find required post render callback" + " `%s`" % post_script) + invalid = True return invalid From abf22276e759a4b454c4e551a3180a272494a5d0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 18 Jan 2019 10:42:18 +0100 Subject: [PATCH 145/149] Validate V-Ray Distributed Rendering is ignored in batch mode --- .../validate_vray_distributed_rendering.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_vray_distributed_rendering.py diff --git a/colorbleed/plugins/maya/publish/validate_vray_distributed_rendering.py b/colorbleed/plugins/maya/publish/validate_vray_distributed_rendering.py new file mode 100644 index 0000000000..632fd2964e --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_vray_distributed_rendering.py @@ -0,0 +1,55 @@ +import pyblish.api +import colorbleed.api +import colorbleed.maya.lib as lib + +from maya import cmds + + +class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin): + """Validate V-Ray Distributed Rendering is ignored in batch mode. + + Whenever Distributed Rendering is enabled for V-Ray in the render settings + ensure that the "Ignore in batch mode" is enabled so the submitted job + won't try to render each frame with all machines resulting in faulty + errors. + + """ + + order = colorbleed.api.ValidateContentsOrder + label = "VRay Distributed Rendering" + families = ["colorbleed.renderlayer"] + actions = [colorbleed.api.RepairAction] + + # V-Ray attribute names + enabled_attr = "vraySettings.sys_distributed_rendering_on" + ignored_attr = "vraySettings.sys_distributed_rendering_ignore_batch" + + def process(self, instance): + + if instance.data.get("renderer") != "vray": + # If not V-Ray ignore.. + return + + vray_settings = cmds.ls("vraySettings", type="VRaySettingsNode") + assert vray_settings, "Please ensure a VRay Settings Node is present" + + renderlayer = instance.data['setMembers'] + + if not lib.get_attr_in_layer(self.enabled_attr, layer=renderlayer): + # If not distributed rendering enabled, ignore.. + return + + # If distributed rendering is enabled but it is *not* set to ignore + # during batch mode we invalidate the instance + if not lib.get_attr_in_layer(self.ignored_attr, layer=renderlayer): + raise RuntimeError("Renderlayer has distributed rendering enabled " + "but is not set to ignore in batch mode.") + + @classmethod + def repair(cls, instance): + + renderlayer = instance.data.get("setMembers") + with lib.renderlayer(renderlayer): + cls.log.info("Enabling Distributed Rendering " + "ignore in batch mode..") + cmds.setAttr(cls.ignored_attr, True) From f15577a9f596dc2a8fa95ea1492c9ee6e4df5ace Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Jan 2019 17:33:44 +0100 Subject: [PATCH 146/149] Default to world-space output for pointcache+animation, plus add toggle --- colorbleed/plugins/maya/create/colorbleed_animation.py | 3 +++ colorbleed/plugins/maya/create/colorbleed_pointcache.py | 1 + colorbleed/plugins/maya/publish/extract_animation.py | 3 ++- colorbleed/plugins/maya/publish/extract_pointcache.py | 3 ++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index 6a113bf74c..16f321ee68 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -32,3 +32,6 @@ class CreateAnimation(avalon.maya.Creator): # Include the groups above the out_SET content self.data["includeParentHierarchy"] = False # Include parent groups + + # Default to exporting world-space + self.data["worldSpace"] = True diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index 2fa0f6cb36..2738ff6ddf 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -20,6 +20,7 @@ class CreatePointCache(avalon.maya.Creator): self.data["renderableOnly"] = False # Only renderable visible shapes self.data["visibleOnly"] = False # only nodes that are visible self.data["includeParentHierarchy"] = False # Include parent groups + self.data["worldSpace"] = True # Default to exporting world-space # Add options for custom attributes self.data["attr"] = "" diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index 53e172fa56..be56c6c937 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -55,7 +55,8 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): "writeVisibility": True, "writeCreases": True, "uvWrite": True, - "selection": True + "selection": True, + "worldSpace": instance.data.get("worldSpace", True) } if not instance.data.get("includeParentHierarchy", True): diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 1e3b4a2ea2..3fcdf3cb58 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -57,7 +57,8 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): "writeCreases": True, "writeColorSets": writeColorSets, "uvWrite": True, - "selection": True + "selection": True, + "worldSpace": instance.data.get("worldSpace", True) } if not instance.data.get("includeParentHierarchy", True): From 3d8862b326488ed0a16b538a0371f3a220c7a8da Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Jan 2019 11:13:55 +0100 Subject: [PATCH 147/149] Add Remove Unknown Plugins script to Maya scripts menu --- colorbleed/maya/menu.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 0c620d3e4c..a6ecea4b11 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1443,6 +1443,15 @@ "title": "Remove Unknown Nodes", "tooltip": "Remove all unknown nodes" }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnknownPlugins.py", + "sourcetype": "file", + "tags": ["cleanup", + "removeUnknownPlugins"], + "title": "Remove Unknown Plugins UI", + "tooltip": "Remove unknown plugins UI" + }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnloadedReferences.py", From 98de50fe0d2ebf347891e101f0f2085cf4608b83 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Jan 2019 17:17:22 +0100 Subject: [PATCH 148/149] Ignore input connections in Yeti input_SET that are rig internal --- .../plugins/maya/publish/collect_yeti_rig.py | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 0308b296ae..e6d4ab803e 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -6,6 +6,7 @@ from maya import cmds import pyblish.api from colorbleed.maya import lib +from colorbleed.lib import pairwise SETTINGS = {"renderDensity", @@ -29,6 +30,27 @@ class CollectYetiRig(pyblish.api.InstancePlugin): assert "input_SET" in instance.data["setMembers"], ( "Yeti Rig must have an input_SET") + input_connections = self.collect_input_connections(instance) + + # Collect any textures if used + yeti_resources = [] + yeti_nodes = cmds.ls(instance[:], type="pgYetiMaya", long=True) + for node in yeti_nodes: + # Get Yeti resources (textures) + resources = self.get_yeti_resources(node) + yeti_resources.extend(resources) + + instance.data["rigsettings"] = {"inputs": input_connections} + + instance.data["resources"] = yeti_resources + + # Force frame range for export + instance.data["startFrame"] = 1 + instance.data["endFrame"] = 1 + + def collect_input_connections(self, instance): + """Collect the inputs for all nodes in the input_SET""" + # Get the input meshes information input_content = cmds.ls(cmds.sets("input_SET", query=True), long=True) @@ -39,6 +61,8 @@ class CollectYetiRig(pyblish.api.InstancePlugin): # Ignore intermediate objects input_content = cmds.ls(input_content, long=True, noIntermediate=True) + if not input_content: + return [] # Store all connections connections = cmds.listConnections(input_content, @@ -49,37 +73,26 @@ class CollectYetiRig(pyblish.api.InstancePlugin): # (avoid display layers, etc.) type="dagNode", plugs=True) or [] - - # Group per source, destination pair. We need to reverse the connection - # list as it comes in with the shape used to query first while that - # shape is the destination of the connection - grouped = [(connections[i+1], item) for i, item in - enumerate(connections) if i % 2 == 0] + connections = cmds.ls(connections, long=True) # Ensure long names inputs = [] - for src, dest in grouped: + for dest, src in pairwise(connections): source_node, source_attr = src.split(".", 1) dest_node, dest_attr = dest.split(".", 1) + # Ensure the source of the connection is not included in the + # current instance's hierarchy. If so, we ignore that connection + # as we will want to preserve it even over a publish. + if source_node in instance: + self.log.debug("Ignoring input connection between nodes " + "inside the instance: %s -> %s" % (src, dest)) + continue + inputs.append({"connections": [source_attr, dest_attr], "sourceID": lib.get_id(source_node), "destinationID": lib.get_id(dest_node)}) - # Collect any textures if used - yeti_resources = [] - yeti_nodes = cmds.ls(instance[:], type="pgYetiMaya", long=True) - for node in yeti_nodes: - # Get Yeti resources (textures) - resources = self.get_yeti_resources(node) - yeti_resources.extend(resources) - - instance.data["rigsettings"] = {"inputs": inputs} - - instance.data["resources"] = yeti_resources - - # Force frame range for export - instance.data["startFrame"] = 1 - instance.data["endFrame"] = 1 + return inputs def get_yeti_resources(self, node): """Get all resource file paths From 9831801db439f3afedd563fcf743de00005132c7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Jan 2019 17:37:37 +0100 Subject: [PATCH 149/149] Allow to pass without rig settings, but give warning to user. --- .../publish/validate_yeti_rig_settings.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py index 8b6219d989..90ddfafa45 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py @@ -2,32 +2,46 @@ import pyblish.api class ValidateYetiRigSettings(pyblish.api.InstancePlugin): + """Validate Yeti Rig Settings have collected input connections. + + The input connections are collected for the nodes in the `input_SET`. + When no input connections are found a warning is logged but it is allowed + to pass validation. + + """ + order = pyblish.api.ValidatorOrder - label = "Validate Yeti Rig Settings" + label = "Yeti Rig Settings" families = ["colorbleed.yetiRig"] def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Detected invalid Yeti Rig data. " + raise RuntimeError("Detected invalid Yeti Rig data. (See log) " "Tip: Save the scene") @classmethod def get_invalid(cls, instance): - rigsettings = instance.data.get("rigsettings", {}) - if not rigsettings: + rigsettings = instance.data.get("rigsettings", None) + if rigsettings is None: cls.log.error("MAJOR ERROR: No rig settings found!") return True # Get inputs inputs = rigsettings.get("inputs", []) + if not inputs: + # Empty rig settings dictionary + cls.log.warning("No rig inputs found. This can happen when " + "the rig has no inputs from outside the rig.") + return False + for input in inputs: source_id = input["sourceID"] if source_id is None: cls.log.error("Discovered source with 'None' as ID, please " - "check if the input shape has an cbId") + "check if the input shape has a cbId") return True destination_id = input["destinationID"]