From e4365b746b40d311c59b3305c39adc61dc1a0425 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 28 Jan 2022 18:18:06 +0100 Subject: [PATCH 001/347] Draft for allowing Arnold ROP render publishing to Deadline from Houdini --- .../plugins/create/create_arnold_rop.py | 50 ++++++ .../plugins/publish/collect_arnold_rop.py | 142 ++++++++++++++++++ .../plugins/publish/collect_instances.py | 2 +- .../plugins/publish/increment_current_file.py | 2 +- .../houdini/plugins/publish/save_scene.py | 3 +- .../publish/submit_houdini_render_deadline.py | 24 +-- 6 files changed, 208 insertions(+), 15 deletions(-) create mode 100644 openpype/hosts/houdini/plugins/create/create_arnold_rop.py create mode 100644 openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py new file mode 100644 index 0000000000..e0fad2a1cc --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -0,0 +1,50 @@ +from avalon import houdini + + +class CreateArnoldRop(houdini.Creator): + """Arnold ROP""" + + label = "Arnold ROP" + family = "arnold_rop" + icon = "magic" + defaults = ["master"] + + def __init__(self, *args, **kwargs): + super(CreateArnoldRop, self).__init__(*args, **kwargs) + + # Clear the family prefix from the subset + subset = self.data["subset"] + subset_no_prefix = subset[len(self.family):] + subset_no_prefix = subset_no_prefix[0].lower() + subset_no_prefix[1:] + self.data["subset"] = subset_no_prefix + + # Add chunk size attribute + self.data["chunkSize"] = 1 + + # Remove the active, we are checking the bypass flag of the nodes + self.data.pop("active", None) + + self.data.update({"node_type": "arnold"}) + + def process(self): + instance = super(CreateArnoldRop, self).process() + + basename = instance.name() + instance.setName(basename + "_ROP", unique_name=True) + + prefix = '${HIP}/render/${HIPNAME}/`chs("subset")`.$F4.exr' + parms = { + # Render frame range + "trange": 1, + + # Arnold ROP settings + "ar_picture": prefix, + "ar_exr_half_precision": 1 # half precision + } + instance.setParms(parms) + + # Lock some Avalon attributes + to_lock = ["family", "id"] + for name in to_lock: + parm = instance.parm(name) + parm.lock(True) diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py new file mode 100644 index 0000000000..e5abfccca0 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -0,0 +1,142 @@ +import re +import os + +import hou +import avalon.io as io +import avalon.api as api +import pyblish.api + + +def get_top_referenced_parm(parm): + + processed = set() # disallow infinite loop + while True: + if parm.path() in processed: + raise RuntimeError("Parameter references result in cycle.") + + processed.add(parm.path()) + + ref = parm.getReferencedParm() + if ref.path() == parm.path(): + # It returns itself when it doesn't reference + # another parameter + return ref + else: + parm = ref + + +def evalParmNoFrame(node, parm, pad_character="#"): + + parameter = node.parm(parm) + assert parameter, "Parameter does not exist: %s.%s" % (node, parm) + + # If the parameter has a parameter reference, then get that + # parameter instead as otherwise `unexpandedString()` fails. + parameter = get_top_referenced_parm(parameter) + + # Substitute out the frame numbering with padded characters + try: + raw = parameter.unexpandedString() + except hou.Error as exc: + print("Failed: %s" % parameter) + raise RuntimeError(exc) + + def replace(match): + padding = 1 + n = match.group(2) + if n and int(n): + padding = int(n) + return pad_character * padding + + expression = re.sub(r"(\$F([0-9]*))", replace, raw) + + with hou.ScriptEvalContext(parameter): + return hou.expandStringAtFrame(expression, 0) + + +class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): + """Collect Arnold ROP Render Products + + Collects the instance.data["files"] for the render products. + + Provides: + instance -> files + + """ + + label = "Arnold ROP Render Products" + order = pyblish.api.CollectorOrder + 0.4 + hosts = ["houdini"] + families = ["arnold_rop"] + + def process(self, instance): + + rop = instance[0] + + # Collect chunkSize + chunk_size_parm = rop.parm("chunkSize") + if chunk_size_parm: + chunk_size = int(chunk_size_parm.eval()) + instance.data["chunkSize"] = chunk_size + self.log.debug("Chunk Size: %s" % chunk_size) + + default_prefix = evalParmNoFrame(rop, "ar_picture") + render_products = [] + + # Default beauty AOV + beauty_product = self.get_render_product_name(prefix=default_prefix, + suffix=None) + render_products.append(beauty_product) + + # TODO: Implement AOVS + # num_aovs = rop.evalParm("ar_aovs") + # for index in range(num_aovs): + # i = index + 1 + # + # # Skip disabled AOVs + # if not rop.evalParm("ar_enable_aov%s" % i): + # continue + # + # label = evalParmNoFrame(rop, "ar_aov_label%s" % i) + # if rop.evalParm("ar_aov_exr_enable_layer_name%s" % i): + # label = rop.evalParm("ar_aov_exr_layer_name%s" % i) + # + # aov_product = self.get_render_product_name(default_prefix, + # suffix=label) + # render_products.append(aov_product) + + for product in render_products: + self.log.debug("Found render product: %s" % product) + + filenames = list(render_products) + instance.data["files"] = filenames + + # For now by default do NOT try to publish the rendered output + instance.data["publishJobState"] = "Suspended" + + def get_render_product_name(self, prefix, suffix): + """Return the output filename using the AOV prefix and suffix""" + + # When AOV is explicitly defined in prefix we just swap it out + # directly with the AOV suffix to embed it. + # Note: ${AOV} seems to be evaluated in the parameter as %AOV% + has_aov_in_prefix = "%AOV%" in prefix + if has_aov_in_prefix: + # It seems that when some special separator characters are present + # before the %AOV% token that Redshift will secretly remove it if + # there is no suffix for the current product, for example: + # foo_%AOV% -> foo.exr + pattern = "%AOV%" if suffix else "[._-]?%AOV%" + product_name = re.sub(pattern, + suffix, + prefix, + flags=re.IGNORECASE) + else: + if suffix: + # Add ".{suffix}" before the extension + prefix_base, ext = os.path.splitext(prefix) + product_name = prefix_base + "." + suffix + ext + else: + product_name = prefix + + return product_name diff --git a/openpype/hosts/houdini/plugins/publish/collect_instances.py b/openpype/hosts/houdini/plugins/publish/collect_instances.py index 12d118f0cc..094f791a0b 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_instances.py +++ b/openpype/hosts/houdini/plugins/publish/collect_instances.py @@ -109,6 +109,6 @@ class CollectInstances(pyblish.api.ContextPlugin): data["frameStart"] = node.evalParm("f1") data["frameEnd"] = node.evalParm("f2") - data["steps"] = node.evalParm("f3") + data["byFrameStep"] = node.evalParm("f3") return data diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file.py b/openpype/hosts/houdini/plugins/publish/increment_current_file.py index 31c2954ee7..7a1ad09249 100644 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/increment_current_file.py @@ -15,7 +15,7 @@ class IncrementCurrentFile(pyblish.api.InstancePlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["houdini"] - families = ["colorbleed.usdrender", "redshift_rop"] + families = ["colorbleed.usdrender", "redshift_rop", "arnold_rop"] targets = ["local"] def process(self, instance): diff --git a/openpype/hosts/houdini/plugins/publish/save_scene.py b/openpype/hosts/houdini/plugins/publish/save_scene.py index 1b12efa603..75ff4dcdfd 100644 --- a/openpype/hosts/houdini/plugins/publish/save_scene.py +++ b/openpype/hosts/houdini/plugins/publish/save_scene.py @@ -9,7 +9,8 @@ class SaveCurrentScene(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder - 0.49 hosts = ["houdini"] families = ["usdrender", - "redshift_rop"] + "redshift_rop", + "arnold_rop"] targets = ["local"] def process(self, instance): diff --git a/openpype/modules/default_modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/default_modules/deadline/plugins/publish/submit_houdini_render_deadline.py index fa146c0d30..9613bae7fe 100644 --- a/openpype/modules/default_modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/default_modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -27,13 +27,12 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder hosts = ["houdini"] families = ["usdrender", - "redshift_rop"] - targets = ["local"] + "redshift_rop", + "arnold_rop"] def process(self, instance): context = instance.context - code = context.data["code"] filepath = context.data["currentFile"] filename = os.path.basename(filepath) comment = context.data.get("comment", "") @@ -42,16 +41,14 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): # Support code prefix label for batch name batch_name = filename - if code: - batch_name = "{0} - {1}".format(code, batch_name) # Output driver to render driver = instance[0] # StartFrame to EndFrame by byFrameStep frames = "{start}-{end}x{step}".format( - start=int(instance.data["startFrame"]), - end=int(instance.data["endFrame"]), + start=int(instance.data["frameStart"]), + end=int(instance.data["frameEnd"]), step=int(instance.data["byFrameStep"]), ) @@ -71,7 +68,7 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): "UserName": deadline_user, "Plugin": "Houdini", - "Pool": "houdini_redshift", # todo: remove hardcoded pool + #"Pool": "houdini", # todo(roy): Add pool options "Frames": frames, "ChunkSize": instance.data.get("chunkSize", 10), @@ -136,9 +133,12 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): def submit(self, instance, payload): - AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", - "http://localhost:8082") - assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" + # get default deadline webservice url from deadline module + deadline_url = instance.context.data.get("defaultDeadline") + # if custom one is set in instance, use that + if instance.data.get("deadlineUrl"): + deadline_url = instance.data.get("deadlineUrl") + assert deadline_url, "Requires Deadline Webservice URL" plugin = payload["JobInfo"]["Plugin"] self.log.info("Using Render Plugin : {}".format(plugin)) @@ -147,7 +147,7 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(AVALON_DEADLINE) + url = "{}/api/jobs".format(deadline_url) response = requests.post(url, json=payload) if not response.ok: raise Exception(response.text) From 553518064efea3af8241a46164600d45b77cb3a7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 28 Jan 2022 18:28:14 +0100 Subject: [PATCH 002/347] Shush the hound --- openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py | 4 +--- .../plugins/publish/submit_houdini_render_deadline.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py index e5abfccca0..93849502be 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -2,14 +2,12 @@ import re import os import hou -import avalon.io as io -import avalon.api as api import pyblish.api def get_top_referenced_parm(parm): - processed = set() # disallow infinite loop + processed = set() # disallow infinite loop while True: if parm.path() in processed: raise RuntimeError("Parameter references result in cycle.") diff --git a/openpype/modules/default_modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/default_modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 9613bae7fe..996c5a5d46 100644 --- a/openpype/modules/default_modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/default_modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -68,7 +68,7 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): "UserName": deadline_user, "Plugin": "Houdini", - #"Pool": "houdini", # todo(roy): Add pool options + # "Pool": "houdini", # todo(roy): Add pool options "Frames": frames, "ChunkSize": instance.data.get("chunkSize", 10), From 976c708d59ecb860c7b2eccd270f99cb6e0fe1ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Aug 2022 12:11:09 +0200 Subject: [PATCH 003/347] Refactor to new houdini Creator --- openpype/hosts/houdini/plugins/create/create_arnold_rop.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index e0fad2a1cc..f9b5f0c53c 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -1,7 +1,7 @@ -from avalon import houdini +from openpype.hosts.houdini.api import plugin -class CreateArnoldRop(houdini.Creator): +class CreateArnoldRop(plugin.Creator): """Arnold ROP""" label = "Arnold ROP" @@ -26,8 +26,7 @@ class CreateArnoldRop(houdini.Creator): self.data.update({"node_type": "arnold"}) - def process(self): - instance = super(CreateArnoldRop, self).process() + def _process(self, instance): basename = instance.name() instance.setName(basename + "_ROP", unique_name=True) From 8bedec713d8d6e81bd6df43122ff6f8a780666e9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Aug 2022 12:11:30 +0200 Subject: [PATCH 004/347] Enable AOVs + fix collected files for publishing --- .../plugins/publish/collect_arnold_rop.py | 66 ++++++++++++++----- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py index 93849502be..dfb8f0d5ee 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -86,22 +86,28 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): suffix=None) render_products.append(beauty_product) - # TODO: Implement AOVS - # num_aovs = rop.evalParm("ar_aovs") - # for index in range(num_aovs): - # i = index + 1 - # - # # Skip disabled AOVs - # if not rop.evalParm("ar_enable_aov%s" % i): - # continue - # - # label = evalParmNoFrame(rop, "ar_aov_label%s" % i) - # if rop.evalParm("ar_aov_exr_enable_layer_name%s" % i): - # label = rop.evalParm("ar_aov_exr_layer_name%s" % i) - # - # aov_product = self.get_render_product_name(default_prefix, - # suffix=label) - # render_products.append(aov_product) + files_by_aov = { + "": self.generate_expected_files(instance, beauty_product) + } + + num_aovs = rop.evalParm("ar_aovs") + for index in range(num_aovs): + i = index + 1 + + # Skip disabled AOVs + if not rop.evalParm("ar_enable_aov%s" % i): + continue + + if rop.evalParm("ar_aov_exr_enable_layer_name%s" % i): + label = rop.evalParm("ar_aov_exr_layer_name%s" % i) + else: + label = evalParmNoFrame(rop, "ar_aov_label%s" % i) + + aov_product = self.get_render_product_name(default_prefix, + suffix=label) + render_products.append(aov_product) + files_by_aov[label] = self.generate_expected_files(instance, + aov_product) for product in render_products: self.log.debug("Found render product: %s" % product) @@ -111,6 +117,11 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): # For now by default do NOT try to publish the rendered output instance.data["publishJobState"] = "Suspended" + instance.data["attachTo"] = [] # stub required data + + if "expectedFiles" not in instance.data: + instance.data["expectedFiles"] = list() + instance.data["expectedFiles"].append(files_by_aov) def get_render_product_name(self, prefix, suffix): """Return the output filename using the AOV prefix and suffix""" @@ -138,3 +149,26 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): product_name = prefix return product_name + + def generate_expected_files(self, instance, path): + """Create expected files in instance data""" + + dir = os.path.dirname(path) + file = os.path.basename(path) + + if "#" in file: + pparts = file.split("#") + padding = "%0{}d".format(len(pparts) - 1) + file = pparts[0] + padding + pparts[-1] + + if "%" not in file: + return path + + expected_files = [] + start = instance.data["frameStart"] + end = instance.data["frameEnd"] + for i in range(int(start), (int(end) + 1)): + expected_files.append( + os.path.join(dir, (file % i)).replace("\\", "/")) + + return expected_files From 3f59f39847158975fed9467275729ba516507ce6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Aug 2022 12:16:23 +0200 Subject: [PATCH 005/347] Refactor submit deadline to use `AbstractSubmitDeadline` class --- .../publish/submit_houdini_render_deadline.py | 175 +++++++----------- 1 file changed, 71 insertions(+), 104 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 3cacc8dd16..8aabec6e30 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -1,16 +1,24 @@ -import os -import json -import getpass - -import requests -import pyblish.api - import hou +import os +import attr +import getpass +import pyblish.api + from openpype.pipeline import legacy_io +from openpype_modules.deadline import abstract_submit_deadline +from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo -class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): +@attr.s +class DeadlinePluginInfo(): + SceneFile = attr.ib(default=None) + OutputDriver = attr.ib(default=None) + Version = attr.ib(default=None) + IgnoreInputs = attr.ib(default=True) + + +class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): """Submit Solaris USD Render ROPs to Deadline. Renders are submitted to a Deadline Web Service as @@ -30,136 +38,95 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): "redshift_rop", "arnold_rop"] targets = ["local"] + use_published = True - def process(self, instance): + def get_job_info(self): + job_info = DeadlineJobInfo(Plugin="Houdini") + instance = self._instance context = instance.context + filepath = context.data["currentFile"] filename = os.path.basename(filepath) - comment = context.data.get("comment", "") - deadline_user = context.data.get("deadlineUser", getpass.getuser()) - jobname = "%s - %s" % (filename, instance.name) - # Support code prefix label for batch name - batch_name = filename + job_info.Name = "%s - %s" % (filename, instance.name) + job_info.BatchName = filename + job_info.Plugin = "Houdini" + job_info.UserName = context.data.get( + "deadlineUser", getpass.getuser()) - # Output driver to render - driver = instance[0] - - # StartFrame to EndFrame by byFrameStep + # Deadline requires integers in frame range frames = "{start}-{end}x{step}".format( start=int(instance.data["frameStart"]), end=int(instance.data["frameEnd"]), step=int(instance.data["byFrameStep"]), ) + job_info.Frames = frames - # Documentation for keys available at: - # https://docs.thinkboxsoftware.com - # /products/deadline/8.0/1_User%20Manual/manual - # /manual-submission.html#job-info-file-options - payload = { - "JobInfo": { - # Top-level group name - "BatchName": batch_name, + job_info.Pool = instance.data.get("primaryPool") + job_info.SecondaryPool = instance.data.get("secondaryPool") + job_info.ChunkSize = instance.data.get("chunkSize", 10) + job_info.Comment = context.data.get("comment") - # Job name, as seen in Monitor - "Name": jobname, - - # Arbitrary username, for visualisation in Monitor - "UserName": deadline_user, - - "Plugin": "Houdini", - "Pool": instance.data.get("primaryPool"), - "secondaryPool": instance.data.get("secondaryPool"), - "Frames": frames, - - "ChunkSize": instance.data.get("chunkSize", 10), - - "Comment": comment - }, - "PluginInfo": { - # Input - "SceneFile": filepath, - "OutputDriver": driver.path(), - - # Mandatory for Deadline - # Houdini version without patch number - "Version": hou.applicationVersionString().rsplit(".", 1)[0], - - "IgnoreInputs": True - }, - - # Mandatory for Deadline, may be empty - "AuxFiles": [] - } - - # Include critical environment variables with submission + api.Session keys = [ - # Submit along the current Avalon tool setup that we launched - # this application with so the Render Slave can build its own - # similar environment using it, e.g. "maya2018;vray4.x;yeti3.1.9" - "AVALON_TOOLS", + "FTRACK_API_KEY", + "FTRACK_API_USER", + "FTRACK_SERVER", + "OPENPYPE_SG_USER", + "AVALON_PROJECT", + "AVALON_ASSET", + "AVALON_TASK", + "AVALON_APP_NAME", + "OPENPYPE_DEV", + "OPENPYPE_LOG_NO_COLORS", "OPENPYPE_VERSION" ] # Add mongo url if it's enabled - if context.data.get("deadlinePassMongoUrl"): + if self._instance.context.data.get("deadlinePassMongoUrl"): keys.append("OPENPYPE_MONGO") environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) + for key in keys: + val = environment.get(key) + if val: + job_info.EnvironmentKeyValue = "{key}={value}".format( + key=key, + value=val) + # to recognize job from PYPE for turning Event On/Off + job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" - payload["JobInfo"].update({ - "EnvironmentKeyValue%d" % index: "{key}={value}".format( - key=key, - value=environment[key] - ) for index, key in enumerate(environment) - }) - - # Include OutputFilename entries - # The first entry also enables double-click to preview rendered - # frames from Deadline Monitor - output_data = {} for i, filepath in enumerate(instance.data["files"]): dirname = os.path.dirname(filepath) fname = os.path.basename(filepath) - output_data["OutputDirectory%d" % i] = dirname.replace("\\", "/") - output_data["OutputFilename%d" % i] = fname + job_info.OutputDirectory = dirname.replace("\\", "/") + job_info.OutputFilename = fname - # For now ensure destination folder exists otherwise HUSK - # will fail to render the output image. This is supposedly fixed - # in new production builds of Houdini - # TODO Remove this workaround with Houdini 18.0.391+ - if not os.path.exists(dirname): - self.log.info("Ensuring output directory exists: %s" % - dirname) - os.makedirs(dirname) + return job_info - payload["JobInfo"].update(output_data) + def get_plugin_info(self): - self.submit(instance, payload) + instance = self._instance + context = instance.context - def submit(self, instance, payload): + # Output driver to render + driver = instance[0] + hou_major_minor = hou.applicationVersionString().rsplit(".", 1)[0] - # get default deadline webservice url from deadline module - deadline_url = instance.context.data.get("defaultDeadline") - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") - assert deadline_url, "Requires Deadline Webservice URL" + plugin_info = DeadlinePluginInfo( + SceneFile=context.data["currentFile"], + OutputDriver=driver.path(), + Version=hou_major_minor, + IgnoreInputs=True + ) - plugin = payload["JobInfo"]["Plugin"] - self.log.info("Using Render Plugin : {}".format(plugin)) + return attr.asdict(plugin_info) - self.log.info("Submitting..") - self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) - - # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(deadline_url) - response = requests.post(url, json=payload) - if not response.ok: - raise Exception(response.text) + def process(self, instance): + super(HoudiniSubmitDeadline, self).process(instance) + # TODO: Avoid the need for this logic here, needed for submit publish # Store output dir for unified publisher (filesequence) output_dir = os.path.dirname(instance.data["files"][0]) instance.data["outputDir"] = output_dir - instance.data["deadlineSubmissionJob"] = response.json() + instance.data["toBeRenderedOn"] = "deadline" From 2b6ef60b654e383f9c17ec843747c30b3c1f0a76 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Aug 2022 12:16:37 +0200 Subject: [PATCH 006/347] Submit publish job for houdini arnold rop --- .../modules/deadline/plugins/publish/submit_publish_job.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 379953c9e4..552aa253f2 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -116,10 +116,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): deadline_plugin = "OpenPype" targets = ["local"] - hosts = ["fusion", "maya", "nuke", "celaction", "aftereffects", "harmony"] + hosts = ["fusion", "maya", "nuke", "celaction", "aftereffects", "harmony", + "houdini"] families = ["render.farm", "prerender.farm", - "renderlayer", "imagesequence", "vrayscene"] + "renderlayer", "imagesequence", "vrayscene", + "arnold_rop"] aov_filter = {"maya": [r".*([Bb]eauty).*"], "aftereffects": [r".*"], # for everything from AE From 58b524407b5035f592773004c312f970ca374bf1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Aug 2022 12:18:43 +0200 Subject: [PATCH 007/347] Fix style issue / shush hound --- openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py | 4 ++-- .../plugins/publish/submit_houdini_render_deadline.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py index dfb8f0d5ee..d90530d3a0 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -107,7 +107,7 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): suffix=label) render_products.append(aov_product) files_by_aov[label] = self.generate_expected_files(instance, - aov_product) + aov_product) for product in render_products: self.log.debug("Found render product: %s" % product) @@ -120,7 +120,7 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): instance.data["attachTo"] = [] # stub required data if "expectedFiles" not in instance.data: - instance.data["expectedFiles"] = list() + instance.data["expectedFiles"] = list() instance.data["expectedFiles"].append(files_by_aov) def get_render_product_name(self, prefix, suffix): diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 8aabec6e30..6b6eebba7e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -91,8 +91,9 @@ class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): val = environment.get(key) if val: job_info.EnvironmentKeyValue = "{key}={value}".format( - key=key, - value=val) + key=key, + value=val + ) # to recognize job from PYPE for turning Event On/Off job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" From 4ffef19e28d1d8f22d36eb6416228545721eff4e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 25 Jan 2023 13:59:19 +0100 Subject: [PATCH 008/347] Refactor to use new style creator --- .../plugins/create/create_arnold_rop.py | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index f9b5f0c53c..c01586ef11 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -1,7 +1,7 @@ from openpype.hosts.houdini.api import plugin -class CreateArnoldRop(plugin.Creator): +class CreateArnoldRop(plugin.HoudiniCreator): """Arnold ROP""" label = "Arnold ROP" @@ -9,41 +9,47 @@ class CreateArnoldRop(plugin.Creator): icon = "magic" defaults = ["master"] - def __init__(self, *args, **kwargs): - super(CreateArnoldRop, self).__init__(*args, **kwargs) + # Default extension + ext = ".exr" - # Clear the family prefix from the subset - subset = self.data["subset"] - subset_no_prefix = subset[len(self.family):] - subset_no_prefix = subset_no_prefix[0].lower() + subset_no_prefix[1:] - self.data["subset"] = subset_no_prefix - - # Add chunk size attribute - self.data["chunkSize"] = 1 + def create(self, subset_name, instance_data, pre_create_data): + import hou # Remove the active, we are checking the bypass flag of the nodes - self.data.pop("active", None) + instance_data.pop("active", None) + instance_data.update({"node_type": "arnold"}) - self.data.update({"node_type": "arnold"}) + # Add chunk size attribute + instance_data["chunkSize"] = 1 - def _process(self, instance): + instance = super(CreateArnoldRop, self).create( + subset_name, + instance_data, + pre_create_data) # type: plugin.CreatedInstance - basename = instance.name() - instance.setName(basename + "_ROP", unique_name=True) + instance_node = hou.node(instance.get("instance_node")) - prefix = '${HIP}/render/${HIPNAME}/`chs("subset")`.$F4.exr' + # Hide Properties Tab on Arnold ROP since that's used + # for rendering instead of .ass Archive Export + parm_template_group = instance_node.parmTemplateGroup() + parm_template_group.hideFolder("Properties", True) + instance_node.setParmTemplateGroup(parm_template_group) + + filepath = "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}.$F4{}".format(subset_name, self.ext) + ) parms = { # Render frame range "trange": 1, # Arnold ROP settings - "ar_picture": prefix, + "ar_picture": filepath, "ar_exr_half_precision": 1 # half precision } - instance.setParms(parms) - # Lock some Avalon attributes + instance_node.setParms(parms) + + # Lock any parameters in this list to_lock = ["family", "id"] - for name in to_lock: - parm = instance.parm(name) - parm.lock(True) + self.lock_parameters(instance_node, to_lock) From 56885f0eb3755fa4469854b94f7d8cf8be9a1d3f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 25 Jan 2023 14:26:13 +0100 Subject: [PATCH 009/347] Add identifier --- openpype/hosts/houdini/plugins/create/create_arnold_rop.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index c01586ef11..41c4fcfaef 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -4,6 +4,7 @@ from openpype.hosts.houdini.api import plugin class CreateArnoldRop(plugin.HoudiniCreator): """Arnold ROP""" + identifier = "io.openpype.creators.houdini.arnold_rop" label = "Arnold ROP" family = "arnold_rop" icon = "magic" From 2b0f19a8a61ed2035781933686580ece30769119 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 25 Jan 2023 14:28:23 +0100 Subject: [PATCH 010/347] Fix arnold rop collector for new style creator --- openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py index d90530d3a0..4d82b74aa2 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -69,7 +69,7 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): def process(self, instance): - rop = instance[0] + rop = hou.node(instance.data.get("instance_node")) # Collect chunkSize chunk_size_parm = rop.parm("chunkSize") From 6fdbf2b0e16f5c368b31427e01ccd25154982594 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 25 Jan 2023 14:30:57 +0100 Subject: [PATCH 011/347] The 'publish' key in instance data is optional and may not exist --- openpype/modules/deadline/abstract_submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 512ff800ee..51f2df19be 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -659,7 +659,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): # test if there is instance of workfile waiting # to be published. - assert i.data["publish"] is True, ( + assert i.data.get("publish", True) is True, ( "Workfile (scene) must be published along") return i From 4c50564d029dde1bfcce9a73a866f0d54ef0cfbe Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 25 Jan 2023 15:09:24 +0100 Subject: [PATCH 012/347] Fix node access --- .../deadline/plugins/publish/submit_houdini_render_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 6b6eebba7e..a25bd0f0bb 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -111,7 +111,7 @@ class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): context = instance.context # Output driver to render - driver = instance[0] + driver = hou.node(instance.data["instance_node"]) hou_major_minor = hou.applicationVersionString().rsplit(".", 1)[0] plugin_info = DeadlinePluginInfo( From bbc20c16df3550bf0dcb340d88108c3bdb07a088 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 25 Jan 2023 15:09:38 +0100 Subject: [PATCH 013/347] Fix job info usage --- .../publish/submit_houdini_render_deadline.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index a25bd0f0bb..d5c28994e0 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -88,20 +88,18 @@ class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) for key in keys: - val = environment.get(key) - if val: - job_info.EnvironmentKeyValue = "{key}={value}".format( - key=key, - value=val - ) + value = environment.get(key) + if value: + job_info.EnvironmentKeyValue[key] = value + # to recognize job from PYPE for turning Event On/Off - job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" + job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1" for i, filepath in enumerate(instance.data["files"]): dirname = os.path.dirname(filepath) fname = os.path.basename(filepath) - job_info.OutputDirectory = dirname.replace("\\", "/") - job_info.OutputFilename = fname + job_info.OutputDirectory += dirname.replace("\\", "/") + job_info.OutputFilename += fname return job_info From e2850d74e5e80bdeaae261ebbf2b2f0bfba8e47a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 25 Jan 2023 15:12:50 +0100 Subject: [PATCH 014/347] Add dedicated collector for instance frame data --- .../publish/collect_instance_frame_data.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/publish/collect_instance_frame_data.py diff --git a/openpype/hosts/houdini/plugins/publish/collect_instance_frame_data.py b/openpype/hosts/houdini/plugins/publish/collect_instance_frame_data.py new file mode 100644 index 0000000000..584343cd64 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_instance_frame_data.py @@ -0,0 +1,56 @@ +import hou + +import pyblish.api + + +class CollectInstanceNodeFrameRange(pyblish.api.InstancePlugin): + """Collect time range frame data for the instance node.""" + + order = pyblish.api.CollectorOrder + 0.001 + label = "Instance Node Frame Range" + hosts = ["houdini"] + + def process(self, instance): + + node_path = instance.data.get("instance_node") + node = hou.node(node_path) if node_path else None + if not node_path or not node: + self.log.debug("No instance node found for instance: " + "{}".format(instance)) + return + + frame_data = self.get_frame_data(node) + if not frame_data: + return + + self.log.info("Collected time data: {}".format(frame_data)) + instance.data.update(frame_data) + + def get_frame_data(self, node): + """Get the frame data: start frame, end frame and steps + Args: + node(hou.Node) + + Returns: + dict + + """ + + data = {} + + if node.parm("trange") is None: + self.log.debug("Node has no 'trange' parameter: " + "{}".format(node.path())) + return data + + if node.evalParm("trange") == 0: + # Ignore 'render current frame' + self.log.debug("Node '{}' has 'Render current frame' set. " + "Time range data ignored.".format(node.path())) + return data + + data["frameStart"] = node.evalParm("f1") + data["frameEnd"] = node.evalParm("f2") + data["byFrameStep"] = node.evalParm("f3") + + return data From b3e4d8efd2a949986335d05c9f7a34abbc2f911c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 25 Jan 2023 15:16:07 +0100 Subject: [PATCH 015/347] Fix increment current file plug-in --- .../plugins/publish/increment_current_file.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file.py b/openpype/hosts/houdini/plugins/publish/increment_current_file.py index 8947530874..3b6496c38b 100644 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/increment_current_file.py @@ -5,6 +5,7 @@ from openpype.pipeline import registered_host from openpype.action import get_errored_plugins_from_data from openpype.hosts.houdini.api import HoudiniHost + class IncrementCurrentFile(pyblish.api.ContextPlugin): """Increment the current file. @@ -18,18 +19,8 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): families = ["workfile", "redshift_rop", "arnold_rop", "usdrender"] optional = True - def process(self, instance): + def process(self, context): - # This should be a ContextPlugin, but this is a workaround - # for a bug in pyblish to run once for a family: issue #250 - context = instance.context - key = "__hasRun{}".format(self.__class__.__name__) - if context.data.get(key, False): - return - else: - context.data[key] = True - - context = instance.context errored_plugins = get_errored_plugins_from_data(context) if any( plugin.__name__ == "HoudiniSubmitPublishDeadline" From bd3183bcbc25b670255416f302bd1b11d261abc8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 25 Jan 2023 15:17:07 +0100 Subject: [PATCH 016/347] Tweak error messages --- .../hosts/houdini/plugins/publish/increment_current_file.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file.py b/openpype/hosts/houdini/plugins/publish/increment_current_file.py index 3b6496c38b..a307ee452c 100644 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/increment_current_file.py @@ -4,6 +4,7 @@ from openpype.lib import version_up from openpype.pipeline import registered_host from openpype.action import get_errored_plugins_from_data from openpype.hosts.houdini.api import HoudiniHost +from openpype.pipeline.publish import KnownPublishError class IncrementCurrentFile(pyblish.api.ContextPlugin): @@ -26,7 +27,7 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): plugin.__name__ == "HoudiniSubmitPublishDeadline" for plugin in errored_plugins ): - raise RuntimeError( + raise KnownPublishError( "Skipping incrementing current file because " "submission to deadline failed." ) @@ -36,7 +37,7 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): current_file = host.current_file() assert ( context.data["currentFile"] == current_file - ), "Collected filename from current scene name." + ), "Collected filename mismatches from current scene name." new_filepath = version_up(current_file) host.save_workfile(new_filepath) From 5218b6550678cb3e3a5127a449a631652d62c249 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 11 Mar 2023 18:59:43 +0100 Subject: [PATCH 017/347] Shush hound --- .../deadline/plugins/publish/submit_houdini_render_deadline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 3c7250e65b..d781496cde 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -12,6 +12,7 @@ from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from openpype.lib import is_running_from_build + @attr.s class DeadlinePluginInfo(): SceneFile = attr.ib(default=None) From b430d9f71db9699add0a07140af31c267a822ad9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Mar 2023 21:05:56 +0100 Subject: [PATCH 018/347] cross hosts: enhancing imageio settings with Enable toggle also adding imageio settings to Max --- .../project_settings/aftereffects.json | 1 + .../defaults/project_settings/blender.json | 1 + .../defaults/project_settings/celaction.json | 1 + .../defaults/project_settings/flame.json | 1 + .../defaults/project_settings/fusion.json | 1 + .../defaults/project_settings/harmony.json | 1 + .../defaults/project_settings/hiero.json | 1 + .../defaults/project_settings/houdini.json | 1 + .../defaults/project_settings/max.json | 11 +++++++++ .../defaults/project_settings/maya.json | 1 + .../defaults/project_settings/nuke.json | 1 + .../defaults/project_settings/photoshop.json | 1 + .../defaults/project_settings/resolve.json | 1 + .../project_settings/traypublisher.json | 1 + .../defaults/project_settings/tvpaint.json | 1 + .../defaults/project_settings/unreal.json | 1 + .../project_settings/webpublisher.json | 1 + .../schema_project_aftereffects.json | 6 +++++ .../schema_project_blender.json | 6 +++++ .../schema_project_celaction.json | 6 +++++ .../projects_schema/schema_project_flame.json | 6 +++++ .../schema_project_fusion.json | 6 +++++ .../schema_project_harmony.json | 6 +++++ .../projects_schema/schema_project_hiero.json | 6 +++++ .../schema_project_houdini.json | 8 ++++++- .../projects_schema/schema_project_max.json | 23 +++++++++++++++++++ .../projects_schema/schema_project_maya.json | 6 +++++ .../schema_project_photoshop.json | 6 +++++ .../schema_project_resolve.json | 6 +++++ .../schema_project_traypublisher.json | 6 +++++ .../schema_project_tvpaint.json | 6 +++++ .../schema_project_unreal.json | 6 +++++ .../schema_project_webpublisher.json | 6 +++++ .../schemas/schema_nuke_imageio.json | 8 ++++++- 34 files changed, 148 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json index 669e1db0b8..d1b2309d26 100644 --- a/openpype/settings/defaults/project_settings/aftereffects.json +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 20eec0c09d..d9aabea9ad 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/celaction.json b/openpype/settings/defaults/project_settings/celaction.json index 822604fd2f..10dfd70ac6 100644 --- a/openpype/settings/defaults/project_settings/celaction.json +++ b/openpype/settings/defaults/project_settings/celaction.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index 5a13d81384..f323af7496 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index f974eebaca..9130c9322c 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/harmony.json b/openpype/settings/defaults/project_settings/harmony.json index 3f51a9c28b..97c9cdf761 100644 --- a/openpype/settings/defaults/project_settings/harmony.json +++ b/openpype/settings/defaults/project_settings/harmony.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index 100c1f5b47..65d6da59f9 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 1b7faf8526..8d7f9865c5 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index d59cdf8c4a..8df4d0ca57 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -1,4 +1,15 @@ { + "imageio": { + "enabled": false, + "ocio_config": { + "enabled": false, + "filepath": [] + }, + "file_rules": { + "enabled": false, + "rules": {} + } + }, "RenderSettings": { "default_render_image_folder": "renders/3dsmax", "aov_separator": "underscore", diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 2aa95fd1be..aa05ae145b 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1,6 +1,7 @@ { "open_workfile_post_initialization": false, "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index c249955dc8..72f599c98b 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -9,6 +9,7 @@ } }, "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index bcf21f55dd..9fd4fe54f1 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/resolve.json b/openpype/settings/defaults/project_settings/resolve.json index 264f3bd902..3720dc54f4 100644 --- a/openpype/settings/defaults/project_settings/resolve.json +++ b/openpype/settings/defaults/project_settings/resolve.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index fdea4aeaba..311c5b0cfc 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index 9173a8c3d5..5da63110be 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index 75cee11bd9..d83e090fae 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/webpublisher.json b/openpype/settings/defaults/project_settings/webpublisher.json index e830ba6a40..a9dc9012eb 100644 --- a/openpype/settings/defaults/project_settings/webpublisher.json +++ b/openpype/settings/defaults/project_settings/webpublisher.json @@ -1,5 +1,6 @@ { "imageio": { + "enabled": false, "ocio_config": { "enabled": false, "filepath": [] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 8dc83f5506..2d48e06ccb 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 725d9bfb08..2e4dcb4e31 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json index c5ca3eb9f5..efecb2a89c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index aab8f21d15..7c839037ad 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 464cf2c06d..87856380ac 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "collapsible": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json index e6bf835c9f..da80648a14 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index f44f92438c..3e9ac41b1c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -11,7 +11,13 @@ "label": "Color Management (ImageIO)", "is_group": true, "collapsible": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 24b06f77db..74ffbbe9f4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" @@ -35,4 +41,4 @@ "name": "schema_houdini_publish" } ] -} \ No newline at end of file +} diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index 4fba9aff0a..de7b4aca0b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -5,6 +5,29 @@ "label": "Max", "is_file": true, "children": [ + { + "key": "imageio", + "type": "dict", + "label": "Color Management (ImageIO)", + "is_group": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "schema", + "name": "schema_imageio_config" + }, + { + "type": "schema", + "name": "schema_imageio_file_rules" + } + + ] + }, { "type": "dict", "collapsible": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index 47dfb37024..8373a57429 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -16,7 +16,13 @@ "label": "Color Management (ImageIO)", "collapsible": true, "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 0071e632af..95f402ca7c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json index b326f22394..da252dd9b1 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 2ef1d2a414..4bce299747 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index 708b688ba5..3af5e7f5ca 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index 8988dd2ff0..b330fd600f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json index 66ccca644d..d0c2145298 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json @@ -10,7 +10,13 @@ "type": "dict", "label": "Color Management (ImageIO)", "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json index 1cd6f0e67f..743b0d66a6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json @@ -4,7 +4,13 @@ "label": "Color Management (ImageIO)", "collapsible": true, "is_group": true, + "checkbox_key": "enabled", "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { "type": "label", "label": "'Custom OCIO config path' has deprecated.
If you need to set custom config, just enable and add path into 'OCIO config'.
Anatomy keys are supported.." @@ -257,4 +263,4 @@ ] } ] -} \ No newline at end of file +} From 52f4b75b62b3776f7b1a3c4d65690b3e5b53cace Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Mar 2023 22:30:23 +0100 Subject: [PATCH 019/347] Flame: skip colorspace management if imageio is not enabled also set enabled to True by default since some productions are already using it --- openpype/hosts/flame/hooks/pre_flame_setup.py | 21 +++++++++++++++---- .../defaults/project_settings/flame.json | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 713daf1031..6367e132de 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -47,6 +47,13 @@ class FlamePrelaunch(PreLaunchHook): imageio_flame = project_settings["flame"]["imageio"] + colormanaged = True + # check if host settings are having enabled key and if it is False + if imageio_flame.get("enabled") and imageio_flame["enabled"] is False: + # if host settings are disabled return False because + # it is expected that no colorspace management is needed + colormanaged = False + # get user name and host name user_name = get_openpype_username() user_name = user_name.replace(".", "_") @@ -68,9 +75,7 @@ class FlamePrelaunch(PreLaunchHook): "FrameWidth": int(width), "FrameHeight": int(height), "AspectRatio": float((width / height) * _db_p_data["pixelAspect"]), - "FrameRate": self._get_flame_fps(fps), - "FrameDepth": str(imageio_flame["project"]["frameDepth"]), - "FieldDominance": str(imageio_flame["project"]["fieldDominance"]) + "FrameRate": self._get_flame_fps(fps) } data_to_script = { @@ -78,7 +83,6 @@ class FlamePrelaunch(PreLaunchHook): "host_name": _env.get("FLAME_WIRETAP_HOSTNAME") or hostname, "volume_name": volume_name, "group_name": _env.get("FLAME_WIRETAP_GROUP"), - "color_policy": str(imageio_flame["project"]["colourPolicy"]), # from project "project_name": project_name, @@ -86,6 +90,15 @@ class FlamePrelaunch(PreLaunchHook): "project_data": project_data } + # add color management data + if colormanaged: + project_data.update({ + "FrameDepth": str(imageio_flame["project"]["frameDepth"]), + "FieldDominance": str(imageio_flame["project"]["fieldDominance"]) + }) + data_to_script["color_policy"] = str( + imageio_flame["project"]["colourPolicy"]) + self.log.info(pformat(dict(_env))) self.log.info(pformat(data_to_script)) diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index f323af7496..5eb6ec2d2a 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -1,6 +1,6 @@ { "imageio": { - "enabled": false, + "enabled": true, "ocio_config": { "enabled": false, "filepath": [] From e09fea633417f5ca7d193e022673466a279bedc7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Mar 2023 22:31:16 +0100 Subject: [PATCH 020/347] global: adding imageio enable switch to colorspace module --- openpype/pipeline/colorspace.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 2085e2d37f..e42838d9dd 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -336,6 +336,7 @@ def get_imageio_config( anatomy_data = get_template_data_from_session() formatting_data = deepcopy(anatomy_data) + # add project roots to anatomy data formatting_data["root"] = anatomy.roots formatting_data["platform"] = platform.system().lower() @@ -344,6 +345,12 @@ def get_imageio_config( imageio_global, imageio_host = _get_imageio_settings( project_settings, host_name) + # check if host settings are having enabled key and if it is False + if imageio_host.get("enabled") and imageio_host["enabled"] is False: + # if host settings are disabled return False because + # it is expected that no colorspace management is needed + return False + config_host = imageio_host.get("ocio_config", {}) if config_host.get("enabled"): From 75aee631c18d9dc2eb1cd9e31d6a4f1785a00cd2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Mar 2023 22:31:50 +0100 Subject: [PATCH 021/347] Nuke: implementing imageio enable switch --- openpype/hosts/nuke/api/lib.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 2a14096f0e..1296bca9b0 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2001,18 +2001,18 @@ class WorkfileSettings(object): "Attention! Viewer nodes {} were erased." "It had wrong color profile".format(erased_viewers)) - def set_root_colorspace(self, nuke_colorspace): + def set_root_colorspace(self, imageio_host): ''' Adds correct colorspace to root Arguments: - nuke_colorspace (dict): adjustmensts from presets + imageio_host (dict): adjustments from presets ''' - workfile_settings = nuke_colorspace["workfile"] + workfile_settings = imageio_host["workfile"] - # resolve config data if they are enabled in host + # get config data if imageio is enabled config_data = None - if nuke_colorspace.get("ocio_config", {}).get("enabled"): + if imageio_host.get("enabled") and imageio_host["enabled"] is True: # switch ocio config to custom config workfile_settings["OCIO_config"] = "custom" workfile_settings["colorManagement"] = "OCIO" From 4f430c56bb9aeef4baeabdbe47bc862b10c6ae37 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Mar 2023 22:32:01 +0100 Subject: [PATCH 022/347] Maya: implementing imageio enable switch --- openpype/hosts/maya/plugins/load/load_image.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/maya/plugins/load/load_image.py b/openpype/hosts/maya/plugins/load/load_image.py index b464c268fc..552bcc33af 100644 --- a/openpype/hosts/maya/plugins/load/load_image.py +++ b/openpype/hosts/maya/plugins/load/load_image.py @@ -273,6 +273,11 @@ class FileNodeLoader(load.LoaderPlugin): project_name, host_name, project_settings=project_settings ) + + # ignore if host imageio is not enabled + if not config_data: + return + file_rules = get_imageio_file_rules( project_name, host_name, project_settings=project_settings From f388fa3e1670342f2501bb2e5d0d5a670e4cc46a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Mar 2023 22:32:13 +0100 Subject: [PATCH 023/347] Fusion: implementing imageio enable switch --- openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py b/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py index 6bf0f55081..4dfb4ef223 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py @@ -26,7 +26,9 @@ class FusionPreLaunchOCIO(PreLaunchHook): anatomy_data=template_data, anatomy=self.data["anatomy"] ) - ocio_path = config_data["path"] - self.log.info(f"Setting OCIO config path: {ocio_path}") - self.launch_context.env["OCIO"] = ocio_path + if config_data: + ocio_path = config_data["path"] + + self.log.info(f"Setting OCIO config path: {ocio_path}") + self.launch_context.env["OCIO"] = ocio_path From 56a775710fd2fc4f1dec8051ed824e54062efab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 24 Mar 2023 14:22:18 +0100 Subject: [PATCH 024/347] Update openpype/hosts/flame/hooks/pre_flame_setup.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/flame/hooks/pre_flame_setup.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 6367e132de..019bf1adda 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -47,12 +47,9 @@ class FlamePrelaunch(PreLaunchHook): imageio_flame = project_settings["flame"]["imageio"] - colormanaged = True - # check if host settings are having enabled key and if it is False - if imageio_flame.get("enabled") and imageio_flame["enabled"] is False: - # if host settings are disabled return False because - # it is expected that no colorspace management is needed - colormanaged = False + colormanaged = imageio_flame.get("enabled") + if colormanaged is None: + colormanaged = True # get user name and host name user_name = get_openpype_username() From 5feee4cdff183b6dc119ffd25bbea9c15245b7ad Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Mar 2023 14:30:49 +0100 Subject: [PATCH 025/347] hound --- openpype/hosts/flame/hooks/pre_flame_setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 6367e132de..a5cab84d0d 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -94,7 +94,8 @@ class FlamePrelaunch(PreLaunchHook): if colormanaged: project_data.update({ "FrameDepth": str(imageio_flame["project"]["frameDepth"]), - "FieldDominance": str(imageio_flame["project"]["fieldDominance"]) + "FieldDominance": str( + imageio_flame["project"]["fieldDominance"]) }) data_to_script["color_policy"] = str( imageio_flame["project"]["colourPolicy"]) From 4ee7604dd7518188f1a9c4662402463b2a558520 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Mar 2023 14:46:30 +0100 Subject: [PATCH 026/347] hound --- openpype/hosts/flame/hooks/pre_flame_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index c6db0d6462..3249fbfe9a 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -92,7 +92,7 @@ class FlamePrelaunch(PreLaunchHook): project_data.update({ "FrameDepth": str(imageio_flame["project"]["frameDepth"]), "FieldDominance": str( - imageio_flame["project"]["fieldDominance"]) + imageio_flame["project"]["fieldDominance"]) }) data_to_script["color_policy"] = str( imageio_flame["project"]["colourPolicy"]) From a1e57e54cdf11be787406fd61b91ee823d99a0e1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Mar 2023 22:19:22 +0200 Subject: [PATCH 027/347] more explicit condition for backward compatibility --- openpype/pipeline/colorspace.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index e42838d9dd..5c7442e1c9 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -345,8 +345,13 @@ def get_imageio_config( imageio_global, imageio_host = _get_imageio_settings( project_settings, host_name) - # check if host settings are having enabled key and if it is False - if imageio_host.get("enabled") and imageio_host["enabled"] is False: + # check if host settings group is having enabled key + imageio_enabled = imageio_host.get("enabled") + if imageio_enabled is None: + # it it does not have enabled key, use global settings + imageio_enabled = True + + if not imageio_enabled : # if host settings are disabled return False because # it is expected that no colorspace management is needed return False From f6ac8138b3649b4a6ad5239c094b8636c4dbcb88 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Mar 2023 22:30:48 +0200 Subject: [PATCH 028/347] simplification of condition --- openpype/hosts/nuke/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 1296bca9b0..411c0afb8a 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2012,7 +2012,7 @@ class WorkfileSettings(object): # get config data if imageio is enabled config_data = None - if imageio_host.get("enabled") and imageio_host["enabled"] is True: + if imageio_host.get("enabled"): # switch ocio config to custom config workfile_settings["OCIO_config"] = "custom" workfile_settings["colorManagement"] = "OCIO" From 6f1f4046d3a9e957cb89087cc5caf84c806f6cdd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Mar 2023 22:48:32 +0200 Subject: [PATCH 029/347] creating global prelaunch hook for OCIO env var --- .../pre_ocio_hook.py} | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) rename openpype/{hosts/fusion/hooks/pre_fusion_ocio_hook.py => hooks/pre_ocio_hook.py} (80%) diff --git a/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py similarity index 80% rename from openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py rename to openpype/hooks/pre_ocio_hook.py index 4dfb4ef223..ff16a8d174 100644 --- a/openpype/hosts/fusion/hooks/pre_fusion_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -4,9 +4,17 @@ from openpype.pipeline.colorspace import get_imageio_config from openpype.pipeline.template_data import get_template_data_with_names -class FusionPreLaunchOCIO(PreLaunchHook): - """Set OCIO environment variable for Fusion""" - app_groups = ["fusion"] +class OCIOEnvHook(PreLaunchHook): + """Set OCIO environment variable for hosts that use OpenColorIO.""" + + order = 0 + app_groups = [ + "fusion", + "blender", + "aftereffects", + "3dsmax", + "houdini" + ] def execute(self): """Hook entry method.""" From f58bcc8abbaf5d2de14ae31d67fe872c2fd74e0a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Mar 2023 23:25:33 +0200 Subject: [PATCH 030/347] flame in line comments --- openpype/hosts/flame/hooks/pre_flame_setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 3249fbfe9a..b5939b168c 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -47,7 +47,10 @@ class FlamePrelaunch(PreLaunchHook): imageio_flame = project_settings["flame"]["imageio"] + # get host imageio settings enabled key if exists colormanaged = imageio_flame.get("enabled") + # if key was not found, set to True + # ensuring backward compatibility if colormanaged is None: colormanaged = True From 62eb6d269b82d19510d30d80a82878fa1042939d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Mar 2023 23:29:45 +0200 Subject: [PATCH 031/347] making docstring more readable --- openpype/hosts/nuke/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 411c0afb8a..d183f2fea9 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2005,7 +2005,7 @@ class WorkfileSettings(object): ''' Adds correct colorspace to root Arguments: - imageio_host (dict): adjustments from presets + imageio_host (dict): host colorspace configurations ''' workfile_settings = imageio_host["workfile"] From 4c78f3044118c1623000e5a7f385d1041d0a7713 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 6 Apr 2023 15:42:02 +0200 Subject: [PATCH 032/347] maya: adding maya to global ocio prelaunch hook --- openpype/hooks/pre_ocio_hook.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py index ff16a8d174..f51e9f48d8 100644 --- a/openpype/hooks/pre_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -13,7 +13,8 @@ class OCIOEnvHook(PreLaunchHook): "blender", "aftereffects", "3dsmax", - "houdini" + "houdini", + "maya" ] def execute(self): From 414cd0cce113328755103f046e3a5aeef0e432e5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 11 Apr 2023 16:09:56 +0800 Subject: [PATCH 033/347] add mantra and karma farm publishing for Houdini --- .../plugins/create/create_karma_rop.py | 114 ++++++++++++ .../plugins/create/create_mantra_rop.py | 78 ++++++++ .../publish/collect_instance_frame_data.py | 56 ++++++ .../plugins/publish/collect_instances.py | 2 +- .../plugins/publish/collect_karma_rop.py | 136 ++++++++++++++ .../plugins/publish/collect_mantra_rop.py | 158 ++++++++++++++++ .../plugins/publish/collect_redshift_rop.py | 41 +++- .../plugins/publish/increment_current_file.py | 7 +- .../deadline/abstract_submit_deadline.py | 2 +- .../publish/submit_houdini_render_deadline.py | 175 +++++++----------- .../plugins/publish/submit_publish_job.py | 7 +- 11 files changed, 666 insertions(+), 110 deletions(-) create mode 100644 openpype/hosts/houdini/plugins/create/create_karma_rop.py create mode 100644 openpype/hosts/houdini/plugins/create/create_mantra_rop.py create mode 100644 openpype/hosts/houdini/plugins/publish/collect_instance_frame_data.py create mode 100644 openpype/hosts/houdini/plugins/publish/collect_karma_rop.py create mode 100644 openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py diff --git a/openpype/hosts/houdini/plugins/create/create_karma_rop.py b/openpype/hosts/houdini/plugins/create/create_karma_rop.py new file mode 100644 index 0000000000..f2a6908d01 --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_karma_rop.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +"""Creator plugin to create Karma ROP.""" +from openpype.hosts.houdini.api import plugin +from openpype.pipeline import CreatedInstance +from openpype.lib import BoolDef, EnumDef, NumberDef + + +class CreateKarmaROP(plugin.HoudiniCreator): + """Karma ROP""" + identifier = "io.openpype.creators.houdini.karma_rop" + label = "Karma ROP" + family = "karma_rop" + icon = "magic" + defaults = ["master"] + + def create(self, subset_name, instance_data, pre_create_data): + import hou # noqa + + instance_data.pop("active", None) + instance_data.update({"node_type": "karma"}) + # Add chunk size attribute + instance_data["chunkSize"] = 10 + + instance = super(CreateKarmaROP, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + + instance_node = hou.node(instance.get("instance_node")) + + ext = pre_create_data.get("image_format") + + filepath = "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}.$F4.{}".format(subset_name, ext) + ) + checkpoint = "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}.$F4.checkpoint".format(subset_name) + ) + + usd_directory = "{}{}".format( + hou.text.expandString("$HIP/pyblish/usd_renders/"), + "{}_$RENDERID".format(subset_name) + ) + + parms = { + # Render Frame Range + "trange": 1, + # Karma ROP Setting + "picture": filepath, + # Karma Checkpoint Setting + "productName": checkpoint, + # USD Output Directory + "savetodirectory": usd_directory, + } + + res_x = pre_create_data.get("res_x") + res_y = pre_create_data.get("res_y") + + if self.selected_nodes: + # If camera found in selection + # we will use as render camera + camera = None + for node in self.selected_nodes: + if node.type().name() == "cam": + camera = node.path() + camera_node = hou.node(camera) + has_camera = pre_create_data.get("cam_res") + if has_camera: + res_x = camera_node.evalParm("resx") + res_y = camera_node.evalParm("resy") + + if not camera: + self.log.warning("No render camera found in selection") + + + parms.update({ + "camera": camera or "", + "resolutionx": res_x, + "resolutiony": res_y, + }) + + instance_node.setParms(parms) + + # Lock some Avalon attributes + to_lock = ["family", "id"] + self.lock_parameters(instance_node, to_lock) + + def get_pre_create_attr_defs(self): + attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs() + + image_format_enum = [ + "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", + "rad", "rat", "rta", "sgi", "tga", "tif", + ] + + return attrs + [ + EnumDef("image_format", + image_format_enum, + default="exr", + label="Image Format Options"), + NumberDef("res_x", + label="width", + default=1920, + decimals=0), + NumberDef("res_y", + label="height", + default=720, + decimals=0), + BoolDef("cam_res", + label="Camera Resolution", + default=False) + ] diff --git a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py new file mode 100644 index 0000000000..b1ae996a30 --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +"""Creator plugin to create Mantra ROP.""" +from openpype.hosts.houdini.api import plugin +from openpype.pipeline import CreatedInstance +from openpype.lib import EnumDef + + +class CreateMantraROP(plugin.HoudiniCreator): + """Mantra ROP""" + identifier = "io.openpype.creators.houdini.mantra_rop" + label = "Mantra ROP" + family = "mantra_rop" + icon = "magic" + defaults = ["master"] + + def create(self, subset_name, instance_data, pre_create_data): + import hou # noqa + + instance_data.pop("active", None) + instance_data.update({"node_type": "ifd"}) + # Add chunk size attribute + instance_data["chunkSize"] = 10 + + instance = super(CreateMantraROP, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + + instance_node = hou.node(instance.get("instance_node")) + + ext = pre_create_data.get("image_format") + + filepath = "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}.$F4.{}".format(subset_name, ext) + ) + + parms = { + # Render Frame Range + "trange": 1, + # Mantra ROP Setting + "vm_picture": filepath, + } + + if self.selected_nodes: + # If camera found in selection + # we will use as render camera + camera = None + for node in self.selected_nodes: + if node.type().name() == "cam": + camera = node.path() + + if not camera: + self.log.warning("No render camera found in selection") + + parms.update({"camera": camera or ""}) + + instance_node.setParms(parms) + + # Lock some Avalon attributes + to_lock = ["family", "id"] + self.lock_parameters(instance_node, to_lock) + + def get_pre_create_attr_defs(self): + attrs = super(CreateMantraROP, self).get_pre_create_attr_defs() + + image_format_enum = [ + "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", + "rad", "rat", "rta", "sgi", "tga", "tif", + ] + + return attrs + [ + EnumDef("image_format", + image_format_enum, + default="exr", + label="Image Format Options") + ] + # Extract Import Plane parameters(Should be in the setting) diff --git a/openpype/hosts/houdini/plugins/publish/collect_instance_frame_data.py b/openpype/hosts/houdini/plugins/publish/collect_instance_frame_data.py new file mode 100644 index 0000000000..584343cd64 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_instance_frame_data.py @@ -0,0 +1,56 @@ +import hou + +import pyblish.api + + +class CollectInstanceNodeFrameRange(pyblish.api.InstancePlugin): + """Collect time range frame data for the instance node.""" + + order = pyblish.api.CollectorOrder + 0.001 + label = "Instance Node Frame Range" + hosts = ["houdini"] + + def process(self, instance): + + node_path = instance.data.get("instance_node") + node = hou.node(node_path) if node_path else None + if not node_path or not node: + self.log.debug("No instance node found for instance: " + "{}".format(instance)) + return + + frame_data = self.get_frame_data(node) + if not frame_data: + return + + self.log.info("Collected time data: {}".format(frame_data)) + instance.data.update(frame_data) + + def get_frame_data(self, node): + """Get the frame data: start frame, end frame and steps + Args: + node(hou.Node) + + Returns: + dict + + """ + + data = {} + + if node.parm("trange") is None: + self.log.debug("Node has no 'trange' parameter: " + "{}".format(node.path())) + return data + + if node.evalParm("trange") == 0: + # Ignore 'render current frame' + self.log.debug("Node '{}' has 'Render current frame' set. " + "Time range data ignored.".format(node.path())) + return data + + data["frameStart"] = node.evalParm("f1") + data["frameEnd"] = node.evalParm("f2") + data["byFrameStep"] = node.evalParm("f3") + + return data diff --git a/openpype/hosts/houdini/plugins/publish/collect_instances.py b/openpype/hosts/houdini/plugins/publish/collect_instances.py index bb85630552..3fec2c4673 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_instances.py +++ b/openpype/hosts/houdini/plugins/publish/collect_instances.py @@ -116,6 +116,6 @@ class CollectInstances(pyblish.api.ContextPlugin): data["frameStart"] = node.evalParm("f1") data["frameEnd"] = node.evalParm("f2") - data["steps"] = node.evalParm("f3") + data["byFrameStep"] = node.evalParm("f3") return data diff --git a/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py b/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py new file mode 100644 index 0000000000..10c97269fc --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py @@ -0,0 +1,136 @@ +import re +import os + +import hou +import pyblish.api + + +def get_top_referenced_parm(parm): + + processed = set() # disallow infinite loop + while True: + if parm.path() in processed: + raise RuntimeError("Parameter references result in cycle.") + + processed.add(parm.path()) + + ref = parm.getReferencedParm() + if ref.path() == parm.path(): + # It returns itself when it doesn't reference + # another parameter + return ref + else: + parm = ref + + +def evalParmNoFrame(node, parm, pad_character="#"): + + parameter = node.parm(parm) + assert parameter, "Parameter does not exist: %s.%s" % (node, parm) + + # If the parameter has a parameter reference, then get that + # parameter instead as otherwise `unexpandedString()` fails. + parameter = get_top_referenced_parm(parameter) + + # Substitute out the frame numbering with padded characters + try: + raw = parameter.unexpandedString() + except hou.Error as exc: + print("Failed: %s" % parameter) + raise RuntimeError(exc) + + def replace(match): + padding = 1 + n = match.group(2) + if n and int(n): + padding = int(n) + return pad_character * padding + + expression = re.sub(r"(\$F([0-9]*))", replace, raw) + + with hou.ScriptEvalContext(parameter): + return hou.expandStringAtFrame(expression, 0) + + +class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin): + """Collect Karma Render Products + + Collects the instance.data["files"] for the multipart render product. + + Provides: + instance -> files + + """ + + label = "Karma ROP Render Products" + order = pyblish.api.CollectorOrder + 0.4 + hosts = ["houdini"] + families = ["karma_rop"] + + def process(self, instance): + + rop = hou.node(instance.data.get("instance_node")) + + # Collect chunkSize + chunk_size_parm = rop.parm("chunkSize") + if chunk_size_parm: + chunk_size = int(chunk_size_parm.eval()) + instance.data["chunkSize"] = chunk_size + self.log.debug("Chunk Size: %s" % chunk_size) + + default_prefix = evalParmNoFrame(rop, "picture") + render_products = [] + + # Default beauty AOV + beauty_product = self.get_render_product_name( + prefix=default_prefix, suffix=None + ) + render_products.append(beauty_product) + + files_by_aov = { + "beauty": self.generate_expected_files(instance, + beauty_product) + } + + filenames = list(render_products) + instance.data["files"] = filenames + + for product in render_products: + self.log.debug("Found render product: %s" % product) + + if "expectedFiles" not in instance.data: + instance.data["expectedFiles"] = list() + instance.data["expectedFiles"].append(files_by_aov) + + def get_render_product_name(self, prefix, suffix): + if suffix: + # Add ".{suffix}" before the extension + prefix_base, ext = os.path.splitext(prefix) + product_name = prefix_base + "." + suffix + ext + else: + product_name = prefix + + return product_name + + def generate_expected_files(self, instance, path): + """Create expected files in instance data""" + + dir = os.path.dirname(path) + file = os.path.basename(path) + + if "#" in file: + pparts = file.split("#") + padding = "%0{}d".format(len(pparts) - 1) + file = pparts[0] + padding + pparts[-1] + + if "%" not in file: + return path + + expected_files = [] + start = instance.data["frameStart"] + end = instance.data["frameEnd"] + for i in range(int(start), (int(end) + 1)): + expected_files.append( + os.path.join(dir, (file % i)).replace("\\", "/")) + + return expected_files diff --git a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py new file mode 100644 index 0000000000..c6cb2a6f49 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -0,0 +1,158 @@ +import re +import os + +import hou +import pyblish.api + + +def get_top_referenced_parm(parm): + + processed = set() # disallow infinite loop + while True: + if parm.path() in processed: + raise RuntimeError("Parameter references result in cycle.") + + processed.add(parm.path()) + + ref = parm.getReferencedParm() + if ref.path() == parm.path(): + # It returns itself when it doesn't reference + # another parameter + return ref + else: + parm = ref + + +def evalParmNoFrame(node, parm, pad_character="#"): + + parameter = node.parm(parm) + assert parameter, "Parameter does not exist: %s.%s" % (node, parm) + + # If the parameter has a parameter reference, then get that + # parameter instead as otherwise `unexpandedString()` fails. + parameter = get_top_referenced_parm(parameter) + + # Substitute out the frame numbering with padded characters + try: + raw = parameter.unexpandedString() + except hou.Error as exc: + print("Failed: %s" % parameter) + raise RuntimeError(exc) + + def replace(match): + padding = 1 + n = match.group(2) + if n and int(n): + padding = int(n) + return pad_character * padding + + expression = re.sub(r"(\$F([0-9]*))", replace, raw) + + with hou.ScriptEvalContext(parameter): + return hou.expandStringAtFrame(expression, 0) + + +class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): + """Collect Mantra Render Products + + Collects the instance.data["files"] for the render products. + + Provides: + instance -> files + + """ + + label = "Mantra ROP Render Products" + order = pyblish.api.CollectorOrder + 0.4 + hosts = ["houdini"] + families = ["mantra_rop"] + + def process(self, instance): + + rop = hou.node(instance.data.get("instance_node")) + + # Collect chunkSize + chunk_size_parm = rop.parm("chunkSize") + if chunk_size_parm: + chunk_size = int(chunk_size_parm.eval()) + instance.data["chunkSize"] = chunk_size + self.log.debug("Chunk Size: %s" % chunk_size) + + default_prefix = evalParmNoFrame(rop, "vm_picture") + render_products = [] + + # Default beauty AOV + beauty_product = self.get_render_product_name( + prefix=default_prefix, suffix=None + ) + render_products.append(beauty_product) + + files_by_aov = { + "beauty": self.generate_expected_files(instance, + beauty_product) + } + + aov_numbers = rop.evalParm("vm_numaux") + if aov_numbers > 0: + # get the filenames of the AOVs + for i in range(1, aov_numbers + 1): + var = rop.evalParm("vm_variable_plane%d" % i) + if var: + aov_name = "vm_filename_plane%d" % i + has_aov_path = rop.evalParm(aov_name) + if has_aov_path: + aov_prefix = evalParmNoFrame(rop, aov_name) + aov_product = self.get_render_product_name( + prefix=aov_prefix, suffix=None + ) + render_products.append(aov_product) + + files_by_aov[var] = self.generate_expected_files(instance, + aov_product) + for product in render_products: + self.log.debug("Found render product: %s" % product) + filenames = list(render_products) + instance.data["files"] = filenames + + # For now by default do NOT try to publish the rendered output + instance.data["publishJobState"] = "Suspended" + instance.data["attachTo"] = [] # stub required data + + if "expectedFiles" not in instance.data: + instance.data["expectedFiles"] = list() + instance.data["expectedFiles"].append(files_by_aov) + self.log.debug("expectedFiles: %s" % files_by_aov) + + + def get_render_product_name(self, prefix, suffix): + if suffix: + # Add ".{suffix}" before the extension + prefix_base, ext = os.path.splitext(prefix) + product_name = prefix_base + "." + suffix + ext + else: + product_name = prefix + + return product_name + + def generate_expected_files(self, instance, path): + """Create expected files in instance data""" + + dir = os.path.dirname(path) + file = os.path.basename(path) + + if "#" in file: + pparts = file.split("#") + padding = "%0{}d".format(len(pparts) - 1) + file = pparts[0] + padding + pparts[-1] + + if "%" not in file: + return path + + expected_files = [] + start = instance.data["frameStart"] + end = instance.data["frameEnd"] + for i in range(int(start), (int(end) + 1)): + expected_files.append( + os.path.join(dir, (file % i)).replace("\\", "/")) + + return expected_files diff --git a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py index f1d73d7523..1a0100dd73 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -87,6 +87,10 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): prefix=default_prefix, suffix=beauty_suffix ) render_products.append(beauty_product) + files_by_aov = { + "beauty": self.generate_expected_files(instance, + beauty_product) + } num_aovs = rop.evalParm("RS_aov") for index in range(num_aovs): @@ -104,11 +108,21 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): aov_product = self.get_render_product_name(aov_prefix, aov_suffix) render_products.append(aov_product) + files_by_aov[aov_suffix] = self.generate_expected_files(instance, + aov_product) + for product in render_products: self.log.debug("Found render product: %s" % product) + filenames = list(render_products) + instance.data["files"] = filenames - filenames = list(render_products) - instance.data["files"] = filenames + # For now by default do NOT try to publish the rendered output + instance.data["publishJobState"] = "Suspended" + instance.data["attachTo"] = [] # stub required data + + if "expectedFiles" not in instance.data: + instance.data["expectedFiles"] = list() + instance.data["expectedFiles"].append(files_by_aov) def get_render_product_name(self, prefix, suffix): """Return the output filename using the AOV prefix and suffix""" @@ -133,3 +147,26 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): product_name = prefix return product_name + + def generate_expected_files(self, instance, path): + """Create expected files in instance data""" + + dir = os.path.dirname(path) + file = os.path.basename(path) + + if "#" in file: + pparts = file.split("#") + padding = "%0{}d".format(len(pparts) - 1) + file = pparts[0] + padding + pparts[-1] + + if "%" not in file: + return path + + expected_files = [] + start = instance.data["frameStart"] + end = instance.data["frameEnd"] + for i in range(int(start), (int(end) + 1)): + expected_files.append( + os.path.join(dir, (file % i)).replace("\\", "/")) + + return expected_files diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file.py b/openpype/hosts/houdini/plugins/publish/increment_current_file.py index 16d9ef9aec..3c71dd6287 100644 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/increment_current_file.py @@ -14,7 +14,12 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["houdini"] - families = ["workfile"] + families = ["workfile", + "redshift_rop", + "arnold_rop", + "mantra_rop", + "karma_rop", + "usdrender"] optional = True def process(self, context): diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 648eb77007..aea18f6dfd 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -663,7 +663,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): # test if there is instance of workfile waiting # to be published. - assert i.data["publish"] is True, ( + assert i.data.get("publish", True) is True, ( "Workfile (scene) must be published along") return i diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 73ab689c9a..d8de47b596 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -1,19 +1,27 @@ +import hou + import os -import json +import attr import getpass from datetime import datetime - -import requests import pyblish.api -# import hou ??? - from openpype.pipeline import legacy_io from openpype.tests.lib import is_in_tests +from openpype_modules.deadline import abstract_submit_deadline +from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from openpype.lib import is_running_from_build -class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): +@attr.s +class DeadlinePluginInfo(): + SceneFile = attr.ib(default=None) + OutputDriver = attr.ib(default=None) + Version = attr.ib(default=None) + IgnoreInputs = attr.ib(default=True) + + +class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): """Submit Solaris USD Render ROPs to Deadline. Renders are submitted to a Deadline Web Service as @@ -30,83 +38,55 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder hosts = ["houdini"] families = ["usdrender", - "redshift_rop"] + "redshift_rop", + "mantra_rop", + "karma_rop"] targets = ["local"] + use_published = True - def process(self, instance): + def get_job_info(self): + job_info = DeadlineJobInfo(Plugin="Houdini") + instance = self._instance context = instance.context - code = context.data["code"] + filepath = context.data["currentFile"] filename = os.path.basename(filepath) - comment = context.data.get("comment", "") - deadline_user = context.data.get("deadlineUser", getpass.getuser()) - jobname = "%s - %s" % (filename, instance.name) - # Support code prefix label for batch name - batch_name = filename - if code: - batch_name = "{0} - {1}".format(code, batch_name) + job_info.Name = "%s - %s" % (filename, instance.name) + job_info.BatchName = filename + job_info.Plugin = "Houdini" + job_info.UserName = context.data.get( + "deadlineUser", getpass.getuser()) if is_in_tests(): - batch_name += datetime.now().strftime("%d%m%Y%H%M%S") + job_info.BatchName += datetime.now().strftime("%d%m%Y%H%M%S") - # Output driver to render - driver = instance[0] - - # StartFrame to EndFrame by byFrameStep + # Deadline requires integers in frame range frames = "{start}-{end}x{step}".format( start=int(instance.data["frameStart"]), end=int(instance.data["frameEnd"]), step=int(instance.data["byFrameStep"]), ) + job_info.Frames = frames - # Documentation for keys available at: - # https://docs.thinkboxsoftware.com - # /products/deadline/8.0/1_User%20Manual/manual - # /manual-submission.html#job-info-file-options - payload = { - "JobInfo": { - # Top-level group name - "BatchName": batch_name, + job_info.Pool = instance.data.get("primaryPool") + job_info.SecondaryPool = instance.data.get("secondaryPool") + job_info.ChunkSize = instance.data.get("chunkSize", 10) + job_info.Comment = context.data.get("comment") - # Job name, as seen in Monitor - "Name": jobname, - - # Arbitrary username, for visualisation in Monitor - "UserName": deadline_user, - - "Plugin": "Houdini", - "Pool": instance.data.get("primaryPool"), - "secondaryPool": instance.data.get("secondaryPool"), - "Frames": frames, - - "ChunkSize": instance.data.get("chunkSize", 10), - - "Comment": comment - }, - "PluginInfo": { - # Input - "SceneFile": filepath, - "OutputDriver": driver.path(), - - # Mandatory for Deadline - # Houdini version without patch number - "Version": hou.applicationVersionString().rsplit(".", 1)[0], - - "IgnoreInputs": True - }, - - # Mandatory for Deadline, may be empty - "AuxFiles": [] - } - - # Include critical environment variables with submission + api.Session keys = [ - # Submit along the current Avalon tool setup that we launched - # this application with so the Render Slave can build its own - # similar environment using it, e.g. "maya2018;vray4.x;yeti3.1.9" - "AVALON_TOOLS" + "FTRACK_API_KEY", + "FTRACK_API_USER", + "FTRACK_SERVER", + "OPENPYPE_SG_USER", + "AVALON_PROJECT", + "AVALON_ASSET", + "AVALON_TASK", + "AVALON_APP_NAME", + "OPENPYPE_DEV", + "OPENPYPE_LOG_NO_COLORS", + "OPENPYPE_VERSION" ] # Add OpenPype version if we are running from build. @@ -114,61 +94,50 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin): keys.append("OPENPYPE_VERSION") # Add mongo url if it's enabled - if context.data.get("deadlinePassMongoUrl"): + if self._instance.context.data.get("deadlinePassMongoUrl"): keys.append("OPENPYPE_MONGO") environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **legacy_io.Session) + for key in keys: + value = environment.get(key) + if value: + job_info.EnvironmentKeyValue[key] = value - payload["JobInfo"].update({ - "EnvironmentKeyValue%d" % index: "{key}={value}".format( - key=key, - value=environment[key] - ) for index, key in enumerate(environment) - }) + # to recognize job from PYPE for turning Event On/Off + job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1" - # Include OutputFilename entries - # The first entry also enables double-click to preview rendered - # frames from Deadline Monitor - output_data = {} for i, filepath in enumerate(instance.data["files"]): dirname = os.path.dirname(filepath) fname = os.path.basename(filepath) - output_data["OutputDirectory%d" % i] = dirname.replace("\\", "/") - output_data["OutputFilename%d" % i] = fname + job_info.OutputDirectory += dirname.replace("\\", "/") + job_info.OutputFilename += fname - # For now ensure destination folder exists otherwise HUSK - # will fail to render the output image. This is supposedly fixed - # in new production builds of Houdini - # TODO Remove this workaround with Houdini 18.0.391+ - if not os.path.exists(dirname): - self.log.info("Ensuring output directory exists: %s" % - dirname) - os.makedirs(dirname) + return job_info - payload["JobInfo"].update(output_data) + def get_plugin_info(self): - self.submit(instance, payload) + instance = self._instance + context = instance.context - def submit(self, instance, payload): + # Output driver to render + driver = hou.node(instance.data["instance_node"]) + hou_major_minor = hou.applicationVersionString().rsplit(".", 1)[0] - AVALON_DEADLINE = legacy_io.Session.get("AVALON_DEADLINE", - "http://localhost:8082") - assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" + plugin_info = DeadlinePluginInfo( + SceneFile=context.data["currentFile"], + OutputDriver=driver.path(), + Version=hou_major_minor, + IgnoreInputs=True + ) - plugin = payload["JobInfo"]["Plugin"] - self.log.info("Using Render Plugin : {}".format(plugin)) + return attr.asdict(plugin_info) - self.log.info("Submitting..") - self.log.debug(json.dumps(payload, indent=4, sort_keys=True)) - - # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(AVALON_DEADLINE) - response = requests.post(url, json=payload) - if not response.ok: - raise Exception(response.text) + def process(self, instance): + super(HoudiniSubmitDeadline, self).process(instance) + # TODO: Avoid the need for this logic here, needed for submit publish # Store output dir for unified publisher (filesequence) output_dir = os.path.dirname(instance.data["files"][0]) instance.data["outputDir"] = output_dir - instance.data["deadlineSubmissionJob"] = response.json() + instance.data["toBeRenderedOn"] = "deadline" diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 4765772bcf..cd70201832 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -122,7 +122,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "celaction", "aftereffects", "harmony"] families = ["render.farm", "prerender.farm", - "renderlayer", "imagesequence", "maxrender", "vrayscene"] + "renderlayer", "imagesequence", + "maxrender", "vrayscene", + "mantra_rop", "karma_rop"] aov_filter = {"maya": [r".*([Bb]eauty).*"], "aftereffects": [r".*"], # for everything from AE @@ -140,7 +142,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_SERVER", "AVALON_APP_NAME", "OPENPYPE_USERNAME", - "OPENPYPE_SG_USER", + "OPENPYPE_VERSION", + "OPENPYPE_SG_USER" ] # Add OpenPype version if we are running from build. From ecc673295cc4bf8ddb4e725a027050bc67e79b31 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 11 Apr 2023 16:24:00 +0800 Subject: [PATCH 034/347] hound fix --- openpype/hosts/houdini/plugins/create/create_karma_rop.py | 7 +++---- .../hosts/houdini/plugins/publish/collect_mantra_rop.py | 5 +---- .../hosts/houdini/plugins/publish/collect_redshift_rop.py | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_karma_rop.py b/openpype/hosts/houdini/plugins/create/create_karma_rop.py index f2a6908d01..8d55298926 100644 --- a/openpype/hosts/houdini/plugins/create/create_karma_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_karma_rop.py @@ -34,12 +34,12 @@ class CreateKarmaROP(plugin.HoudiniCreator): hou.text.expandString("$HIP/pyblish/"), "{}.$F4.{}".format(subset_name, ext) ) - checkpoint = "{}{}".format( + checkpoint = "{}{}".format( hou.text.expandString("$HIP/pyblish/"), "{}.$F4.checkpoint".format(subset_name) ) - usd_directory = "{}{}".format( + usd_directory = "{}{}".format( hou.text.expandString("$HIP/pyblish/usd_renders/"), "{}_$RENDERID".format(subset_name) ) @@ -74,12 +74,11 @@ class CreateKarmaROP(plugin.HoudiniCreator): if not camera: self.log.warning("No render camera found in selection") - parms.update({ "camera": camera or "", "resolutionx": res_x, "resolutiony": res_y, - }) + }) instance_node.setParms(parms) diff --git a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py index c6cb2a6f49..1a881389db 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -22,7 +22,6 @@ def get_top_referenced_parm(parm): else: parm = ref - def evalParmNoFrame(node, parm, pad_character="#"): parameter = node.parm(parm) @@ -108,7 +107,7 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): render_products.append(aov_product) files_by_aov[var] = self.generate_expected_files(instance, - aov_product) + aov_product) # noqa for product in render_products: self.log.debug("Found render product: %s" % product) filenames = list(render_products) @@ -121,8 +120,6 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): if "expectedFiles" not in instance.data: instance.data["expectedFiles"] = list() instance.data["expectedFiles"].append(files_by_aov) - self.log.debug("expectedFiles: %s" % files_by_aov) - def get_render_product_name(self, prefix, suffix): if suffix: diff --git a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py index 1a0100dd73..60d3a977fc 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -109,7 +109,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): render_products.append(aov_product) files_by_aov[aov_suffix] = self.generate_expected_files(instance, - aov_product) + aov_product) # noqa for product in render_products: self.log.debug("Found render product: %s" % product) From c9a88d06abdf419fc08dde0529dd4f2de51775ad Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 11 Apr 2023 16:28:38 +0800 Subject: [PATCH 035/347] hound fix --- openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py index 1a881389db..1eb850e52e 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -22,6 +22,7 @@ def get_top_referenced_parm(parm): else: parm = ref + def evalParmNoFrame(node, parm, pad_character="#"): parameter = node.parm(parm) @@ -106,8 +107,8 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): ) render_products.append(aov_product) - files_by_aov[var] = self.generate_expected_files(instance, - aov_product) # noqa + files_by_aov[var] = self.generate_expected_files(instance, aov_product) # noqa + for product in render_products: self.log.debug("Found render product: %s" % product) filenames = list(render_products) From eee45f42879cfcd117dbd4ab70c0dd0e686cd583 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 11 Apr 2023 16:29:21 +0800 Subject: [PATCH 036/347] hound fix --- openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py index 60d3a977fc..82874e546f 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -90,7 +90,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): files_by_aov = { "beauty": self.generate_expected_files(instance, beauty_product) - } + } num_aovs = rop.evalParm("RS_aov") for index in range(num_aovs): From dfa2ee370580054e9979a070c6ea83dba03524dc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 11 Apr 2023 16:30:11 +0800 Subject: [PATCH 037/347] hound fix --- openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py index 82874e546f..15df12e075 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -89,8 +89,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): render_products.append(beauty_product) files_by_aov = { "beauty": self.generate_expected_files(instance, - beauty_product) - } + beauty_product)} num_aovs = rop.evalParm("RS_aov") for index in range(num_aovs): From 63a168c2f25be5b479da5907c69f28810b06420d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Apr 2023 15:09:40 +0800 Subject: [PATCH 038/347] allows the user to choose image format for arnold rendering option --- .../plugins/create/create_arnold_rop.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index 41c4fcfaef..824d891e04 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -1,5 +1,5 @@ from openpype.hosts.houdini.api import plugin - +from openpype.lib import EnumDef class CreateArnoldRop(plugin.HoudiniCreator): """Arnold ROP""" @@ -36,9 +36,11 @@ class CreateArnoldRop(plugin.HoudiniCreator): parm_template_group.hideFolder("Properties", True) instance_node.setParmTemplateGroup(parm_template_group) + ext = pre_create_data.get("image_format") + filepath = "{}{}".format( hou.text.expandString("$HIP/pyblish/"), - "{}.$F4{}".format(subset_name, self.ext) + "{}.$F4{}".format(subset_name, ext) ) parms = { # Render frame range @@ -54,3 +56,18 @@ class CreateArnoldRop(plugin.HoudiniCreator): # Lock any parameters in this list to_lock = ["family", "id"] self.lock_parameters(instance_node, to_lock) + + def get_pre_create_attr_defs(self): + attrs = super(CreateArnoldRop, self).get_pre_create_attr_defs() + + image_format_enum = [ + "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", + "rad", "rat", "rta", "sgi", "tga", "tif", + ] + + return attrs + [ + EnumDef("image_format", + image_format_enum, + default=self.ext, + label="Image Format Options") + ] From fd2d365f1e6ad254422759da011387d72e797367 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Apr 2023 15:11:23 +0800 Subject: [PATCH 039/347] hounf --- openpype/hosts/houdini/plugins/create/create_arnold_rop.py | 1 + openpype/hosts/houdini/plugins/create/create_mantra_rop.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index 824d891e04..e012dd467b 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -1,6 +1,7 @@ from openpype.hosts.houdini.api import plugin from openpype.lib import EnumDef + class CreateArnoldRop(plugin.HoudiniCreator): """Arnold ROP""" diff --git a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py index b1ae996a30..0d7ccce099 100644 --- a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py @@ -75,4 +75,3 @@ class CreateMantraROP(plugin.HoudiniCreator): default="exr", label="Image Format Options") ] - # Extract Import Plane parameters(Should be in the setting) From 26ba2e6bee906dc1f69a625c24333a0a12264369 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Apr 2023 15:26:03 +0800 Subject: [PATCH 040/347] fix the syntax error --- openpype/hosts/houdini/plugins/create/create_arnold_rop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index e012dd467b..0657d349f9 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -12,7 +12,7 @@ class CreateArnoldRop(plugin.HoudiniCreator): defaults = ["master"] # Default extension - ext = ".exr" + ext = "exr" def create(self, subset_name, instance_data, pre_create_data): import hou @@ -41,7 +41,7 @@ class CreateArnoldRop(plugin.HoudiniCreator): filepath = "{}{}".format( hou.text.expandString("$HIP/pyblish/"), - "{}.$F4{}".format(subset_name, ext) + "{}.$F4.{}".format(subset_name, ext) ) parms = { # Render frame range From ee99b21e97fcc3236693f2a5dfe846cd815d85d2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Apr 2023 22:24:28 +0800 Subject: [PATCH 041/347] add override camera resolution options in creator's setting --- .../houdini/plugins/create/create_mantra_rop.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py index 0d7ccce099..2632f8d6c0 100644 --- a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py @@ -2,7 +2,7 @@ """Creator plugin to create Mantra ROP.""" from openpype.hosts.houdini.api import plugin from openpype.pipeline import CreatedInstance -from openpype.lib import EnumDef +from openpype.lib import EnumDef, BoolDef class CreateMantraROP(plugin.HoudiniCreator): @@ -55,6 +55,9 @@ class CreateMantraROP(plugin.HoudiniCreator): parms.update({"camera": camera or ""}) + custom_res = pre_create_data.get("override_resolution") + if custom_res: + parms.update({"override_camerares": 1}) instance_node.setParms(parms) # Lock some Avalon attributes @@ -73,5 +76,10 @@ class CreateMantraROP(plugin.HoudiniCreator): EnumDef("image_format", image_format_enum, default="exr", - label="Image Format Options") + label="Image Format Options"), + BoolDef("override_resolution", + label="Override Camera Resolution", + tooltip="Override the current camera " + "resolution, recommended for IPR.", + default=False) ] From 80a5982a2460b09cc982fbc1076d26f9aab731e5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Apr 2023 22:27:33 +0800 Subject: [PATCH 042/347] allows to fully delete the render instances if users click the remove button in creator --- openpype/hosts/houdini/api/plugin.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py index 340a7f0770..6fb0f8b967 100644 --- a/openpype/hosts/houdini/api/plugin.py +++ b/openpype/hosts/houdini/api/plugin.py @@ -247,9 +247,22 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase): """ for instance in instances: instance_node = hou.node(instance.data.get("instance_node")) + node = instance.data.get("instance_node") + if instance_node: instance_node.destroy() + # for the extra render node from the plugins + # such as vray and redshift + ipr_node = hou.node("{}{}".format(node, + "_IPR")) + if ipr_node: + ipr_node.destroy() + re_node = hou.node("{}{}".format(node, + "_render_element")) + if re_node: + re_node.destroy() + self._remove_instance_from_context(instance) def get_pre_create_attr_defs(self): From d925322aa4e270595000b32a7aa00531130bf12c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Apr 2023 22:28:15 +0800 Subject: [PATCH 043/347] add vray render creator --- .../houdini/plugins/create/create_vray_rop.py | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 openpype/hosts/houdini/plugins/create/create_vray_rop.py diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py new file mode 100644 index 0000000000..445f55581c --- /dev/null +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +"""Creator plugin to create VRay ROP.""" +from openpype.hosts.houdini.api import plugin +from openpype.pipeline import CreatedInstance +from openpype.lib import EnumDef, BoolDef + + +class CreateVrayROP(plugin.HoudiniCreator): + """VRay ROP""" + + identifier = "io.openpype.creators.houdini.vray_rop" + label = "VRay ROP" + family = "vray_rop" + icon = "magic" + defaults = ["master"] + + ext = "exr" + + def create(self, subset_name, instance_data, pre_create_data): + import hou # + + instance_data.pop("active", None) + instance_data.update({"node_type": "vray_renderer"}) + # Add chunk size attribute + instance_data["chunkSize"] = 10 + + instance = super(CreateVrayROP, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance + + instance_node = hou.node(instance.get("instance_node")) + + # Add IPR for Vray + basename = instance_node.name() + try: + ipr_rop = instance_node.parent().createNode( + "vray", node_name=basename + "_IPR" + ) + except hou.OperationFailed: + raise plugin.OpenPypeCreatorError( + "Cannot create Vray render node. " + "Make sure Vray installed and enabled!" + ) + + ipr_rop.setPosition(instance_node.position() + hou.Vector2(0, -1)) + ipr_rop.parm("rop").set(instance_node.path()) + + ext = pre_create_data.get("image_format") + + filepath = "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}.$F4.{}".format(subset_name, ext) + ) + + parms = { + "trange": 1, + "SettingsOutput_img_file_path": filepath, + "SettingsEXR_bits_per_channel": "16" # half precision + } + + if self.selected_nodes: + # set up the render camera from the selected node + camera = None + for node in self.selected_nodes: + if node.type().name() == "cam": + camera = node.path() + parms.update({ + "render_camera": camera or "" + }) + + #TODO:Add Render Element + has_re = pre_create_data.get("render_element_enabled") + if has_re: + re_rop = instance_node.parent().createNode( + "vray_render_channels", + node_name=basename + "_render_element" + ) + # move the render element node next to the vray renderer node + re_rop.setPosition(instance_node.position() + hou.Vector2(0, 1)) + re_path = re_rop.path() + parms.update({ + "use_render_channels": 1, + "render_network_render_channels": re_path + }) + else: + parms.update({ + "use_render_channels": 0 + }) + + custom_res = pre_create_data.get("override_resolution") + if custom_res: + parms.update({"override_camerares": 1}) + + instance_node.setParms(parms) + + # lock parameters from AVALON + to_lock = ["family", "id"] + self.lock_parameters(instance_node, to_lock) + + def get_pre_create_attr_defs(self): + attrs = super(CreateVrayROP, self).get_pre_create_attr_defs() + image_format_enum = [ + "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", + "rad", "rat", "rta", "sgi", "tga", "tif", + ] + + return attrs + [ + EnumDef("image_format", + image_format_enum, + default=self.ext, + label="Image Format Options"), + BoolDef("override_resolution", + label="Override Camera Resolution", + tooltip="Override the current camera " + "resolution, recommended for IPR.", + default=False), + BoolDef("render_element_enabled", + label="Render Element", + tooltip="Create Render Element Node " + "if enabled", + default=False) + ] +# ${HIP}/render/${HIPNAME}.${AOV}.$F4.exr From 6c242d9285f3d6c924098b04049fd93525614a8b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 12 Apr 2023 22:30:12 +0800 Subject: [PATCH 044/347] hound --- openpype/hosts/houdini/plugins/create/create_vray_rop.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index 445f55581c..f0430a2892 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -56,11 +56,11 @@ class CreateVrayROP(plugin.HoudiniCreator): parms = { "trange": 1, "SettingsOutput_img_file_path": filepath, - "SettingsEXR_bits_per_channel": "16" # half precision + "SettingsEXR_bits_per_channel": "16" # half precision } if self.selected_nodes: - # set up the render camera from the selected node + # set up the render camera from the selected node camera = None for node in self.selected_nodes: if node.type().name() == "cam": @@ -69,7 +69,7 @@ class CreateVrayROP(plugin.HoudiniCreator): "render_camera": camera or "" }) - #TODO:Add Render Element + # Enable render element has_re = pre_create_data.get("render_element_enabled") if has_re: re_rop = instance_node.parent().createNode( From ae0d6dd1b5d80eec95958da110bc420b5cfc668b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Apr 2023 17:25:41 +0200 Subject: [PATCH 045/347] refactoring of settings for more explicitly communicated attributes --- .../project_settings/aftereffects.json | 6 ++-- .../defaults/project_settings/blender.json | 6 ++-- .../defaults/project_settings/celaction.json | 6 ++-- .../defaults/project_settings/flame.json | 6 ++-- .../defaults/project_settings/fusion.json | 6 ++-- .../defaults/project_settings/global.json | 1 - .../defaults/project_settings/harmony.json | 6 ++-- .../defaults/project_settings/hiero.json | 6 ++-- .../defaults/project_settings/houdini.json | 6 ++-- .../defaults/project_settings/max.json | 6 ++-- .../defaults/project_settings/maya.json | 6 ++-- .../defaults/project_settings/nuke.json | 11 ++---- .../defaults/project_settings/photoshop.json | 6 ++-- .../defaults/project_settings/resolve.json | 6 ++-- .../project_settings/traypublisher.json | 6 ++-- .../defaults/project_settings/tvpaint.json | 6 ++-- .../defaults/project_settings/unreal.json | 6 ++-- .../project_settings/webpublisher.json | 6 ++-- .../schema_project_aftereffects.json | 6 ++-- .../schema_project_blender.json | 6 ++-- .../schema_project_celaction.json | 6 ++-- .../projects_schema/schema_project_flame.json | 6 ++-- .../schema_project_fusion.json | 6 ++-- .../schema_project_global.json | 35 +++++++++++++++++-- .../schema_project_harmony.json | 6 ++-- .../projects_schema/schema_project_hiero.json | 7 ++-- .../schema_project_houdini.json | 6 ++-- .../projects_schema/schema_project_max.json | 6 ++-- .../projects_schema/schema_project_maya.json | 5 ++- .../schema_project_photoshop.json | 6 ++-- .../schema_project_resolve.json | 6 ++-- .../schema_project_traypublisher.json | 6 ++-- .../schema_project_tvpaint.json | 6 ++-- .../schema_project_unreal.json | 6 ++-- .../schema_project_webpublisher.json | 6 ++-- .../schemas/schema_imageio_config.json | 7 ++-- .../schemas/schema_imageio_file_rules.json | 7 ++-- .../schemas/schema_nuke_imageio.json | 19 ++-------- 38 files changed, 139 insertions(+), 133 deletions(-) diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json index d1b2309d26..74bd519bbd 100644 --- a/openpype/settings/defaults/project_settings/aftereffects.json +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index d9aabea9ad..8328ceeda3 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/celaction.json b/openpype/settings/defaults/project_settings/celaction.json index 10dfd70ac6..0e8b465118 100644 --- a/openpype/settings/defaults/project_settings/celaction.json +++ b/openpype/settings/defaults/project_settings/celaction.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index 5eb6ec2d2a..64021baeef 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": true, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} }, "project": { diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index 9130c9322c..fa44bbe3d4 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} }, "ocio": { diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 4c4a7487cf..fccd02d130 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -7,7 +7,6 @@ ] }, "file_rules": { - "enabled": false, "rules": { "example": { "pattern": ".*(beauty).*", diff --git a/openpype/settings/defaults/project_settings/harmony.json b/openpype/settings/defaults/project_settings/harmony.json index 97c9cdf761..e6fb00a700 100644 --- a/openpype/settings/defaults/project_settings/harmony.json +++ b/openpype/settings/defaults/project_settings/harmony.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index df460e0d86..b7d5d9af23 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} }, "workfile": { diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 8d7f9865c5..2b7192ff99 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index 8df4d0ca57..f6462c3d9a 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 6016323b4b..fa3a7bc648 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1,13 +1,13 @@ { "open_workfile_post_initialization": false, "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} }, "colorManagementPreference_v2": { diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 0573df028d..1dd0e5128a 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -9,13 +9,13 @@ } }, "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} }, "viewer": { @@ -27,11 +27,6 @@ "workfile": { "colorManagement": "Nuke", "OCIO_config": "nuke-default", - "customOCIOConfigPath": { - "windows": [], - "darwin": [], - "linux": [] - }, "workingSpaceLUT": "linear", "monitorLut": "sRGB", "int8Lut": "sRGB", diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 9fd4fe54f1..47f397663b 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/resolve.json b/openpype/settings/defaults/project_settings/resolve.json index 3720dc54f4..7379e74200 100644 --- a/openpype/settings/defaults/project_settings/resolve.json +++ b/openpype/settings/defaults/project_settings/resolve.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 311c5b0cfc..2668b5d638 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index b649d56337..3c930b84eb 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index d83e090fae..d92d3403ed 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/webpublisher.json b/openpype/settings/defaults/project_settings/webpublisher.json index a9dc9012eb..17d61ef028 100644 --- a/openpype/settings/defaults/project_settings/webpublisher.json +++ b/openpype/settings/defaults/project_settings/webpublisher.json @@ -1,12 +1,12 @@ { "imageio": { - "enabled": false, + "activate_host_color_management": true, "ocio_config": { - "enabled": false, + "override_global_config": false, "filepath": [] }, "file_rules": { - "enabled": false, + "override_global_rules": false, "rules": {} } }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 2d48e06ccb..7bc20fed87 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 2e4dcb4e31..dbba7dfdd2 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json index efecb2a89c..ab3acaf4a2 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index 7c839037ad..5b96a49679 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 87856380ac..fad6361119 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -10,12 +10,12 @@ "type": "dict", "label": "Color Management (ImageIO)", "collapsible": true, - "checkbox_key": "enabled", + "is_group": true, "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_global.json b/openpype/settings/entities/schemas/projects_schema/schema_project_global.json index 6f31f4f685..f200c1722f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_global.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_global.json @@ -27,8 +27,39 @@ ] }, { - "type": "schema", - "name": "schema_imageio_file_rules" + "key": "file_rules", + "type": "dict", + "label": "File Rules", + "collapsible": true, + "children": [ + { + "key": "rules", + "label": "Rules", + "type": "dict-modifiable", + "highlight_content": true, + "collapsible": false, + "object_type": { + "type": "dict", + "children": [ + { + "key": "pattern", + "label": "Regex pattern", + "type": "text" + }, + { + "key": "colorspace", + "label": "Colorspace name", + "type": "text" + }, + { + "key": "ext", + "label": "File extension", + "type": "text" + } + ] + } + } + ] } ] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json index da80648a14..71f8cb4db2 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index af3ee713c4..5e42cb0a00 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -9,14 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", - "is_group": true, "collapsible": true, - "checkbox_key": "enabled", + "is_group": true, "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 74ffbbe9f4..14217c944e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index de7b4aca0b..5c4b825872 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index 8373a57429..ef32f907ed 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -16,12 +16,11 @@ "label": "Color Management (ImageIO)", "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 95f402ca7c..53e59956eb 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json index da252dd9b1..16de175933 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 4bce299747..bc80562940 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index b1a31b5c93..a0d94ad7dc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index b330fd600f..87ba3d2d43 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json index d0c2145298..f596c89686 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json @@ -9,13 +9,13 @@ "key": "imageio", "type": "dict", "label": "Color Management (ImageIO)", + "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_config.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_config.json index e7cff969d3..bc65dd7826 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_config.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_config.json @@ -3,12 +3,11 @@ "type": "dict", "label": "OCIO config", "collapsible": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "override_global_config", + "label": "Override global OCIO config" }, { "type": "path", @@ -18,4 +17,4 @@ "multipath": true } ] -} \ No newline at end of file +} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json index a171ba1c55..e76c8a326f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json @@ -3,12 +3,11 @@ "type": "dict", "label": "File Rules", "collapsible": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" + "key": "override_global_rules", + "label": "Override global file rules" }, { "key": "rules", @@ -38,4 +37,4 @@ } } ] -} \ No newline at end of file +} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json index a46958e616..a986db1ade 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json @@ -4,16 +4,11 @@ "label": "Color Management (ImageIO)", "collapsible": true, "is_group": true, - "checkbox_key": "enabled", "children": [ { "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "type": "label", - "label": "'Custom OCIO config path' has deprecated.
If you need to set custom config, just enable and add path into 'OCIO config'.
Anatomy keys are supported.." + "key": "activate_host_color_management", + "label": "Enable Color Management in host" }, { "type": "schema", @@ -108,19 +103,9 @@ }, { "cg-config-v1.0.0_aces-v1.3_ocio-v2.1": "cg-config-v1.0.0_aces-v1.3_ocio-v2.1 (14)" - }, - { - "custom": "custom" } ] }, - { - "type": "path", - "key": "customOCIOConfigPath", - "label": "Custom OCIO config path", - "multiplatform": true, - "multipath": true - }, { "type": "text", "key": "workingSpaceLUT", From 57e8722d7151e0343f20991dcd48b4a04bca304e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Apr 2023 17:50:46 +0200 Subject: [PATCH 046/347] redefining switches for imageio host activation and config overrides --- openpype/pipeline/colorspace.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index f1a50281d2..ed5c284faf 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -344,28 +344,31 @@ def get_imageio_config( imageio_global, imageio_host = _get_imageio_settings( project_settings, host_name) - # check if host settings group is having enabled key - imageio_enabled = imageio_host.get("enabled") - if imageio_enabled is None: - # it it does not have enabled key, use global settings - imageio_enabled = True + # check if host settings group is having activate_host_color_management + # it it does not have activation key then use global settings + # this is for backward compatibility + # TODO: in future rewrite this to be more explicit + activate_host_color_management = imageio_host.get( + "activate_host_color_management", True) - if not imageio_enabled : + if not activate_host_color_management: # if host settings are disabled return False because # it is expected that no colorspace management is needed return False config_host = imageio_host.get("ocio_config", {}) - if config_host.get("enabled"): + # get config path from either global or host_name + # depending on override flag + # TODO: in future rewrite this to be more explicit + config_data = None + override_global_rules = config_host.get("override_global_rules") + if override_global_rules: config_data = _get_config_data( config_host["filepath"], formatting_data ) else: - config_data = None - - if not config_data: - # get config path from either global or host_name + # get config path from global config_global = imageio_global["ocio_config"] config_data = _get_config_data( config_global["filepath"], formatting_data From 8f057e79d51b2e21586cb8733cf8aaff5c19f4fb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Apr 2023 21:26:25 +0200 Subject: [PATCH 047/347] fixing typo --- openpype/pipeline/colorspace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index ed5c284faf..098e2a02c6 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -362,8 +362,8 @@ def get_imageio_config( # depending on override flag # TODO: in future rewrite this to be more explicit config_data = None - override_global_rules = config_host.get("override_global_rules") - if override_global_rules: + override_global_config = config_host.get("override_global_config") + if override_global_config: config_data = _get_config_data( config_host["filepath"], formatting_data ) From 153810b919efae29db4d7afc84562ac20068e5f7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Apr 2023 21:45:53 +0200 Subject: [PATCH 048/347] adding hosts colorspace management switches into code --- openpype/pipeline/colorspace.py | 21 +++++++++++++------- openpype/pipeline/publish/publish_plugins.py | 15 +++++++++----- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 098e2a02c6..b3774e5e90 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -92,6 +92,11 @@ def get_imageio_colorspace_from_filepath( ) config_data = get_imageio_config( project_name, host_name, project_settings) + + # in case host color management is not enabled + if not config_data: + return None + file_rules = get_imageio_file_rules( project_name, host_name, project_settings) @@ -354,6 +359,10 @@ def get_imageio_config( if not activate_host_color_management: # if host settings are disabled return False because # it is expected that no colorspace management is needed + log.info( + "Colorspace management for host '{}' is disabled.".format( + host_name) + ) return False config_host = imageio_host.get("ocio_config", {}) @@ -456,13 +465,11 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): frules_host = imageio_host.get("file_rules", {}) # compile file rules dictionary - file_rules = {} - if frules_global["enabled"]: - file_rules.update(frules_global["rules"]) - if frules_host and frules_host["enabled"]: - file_rules.update(frules_host["rules"]) - - return file_rules + override_global_rules = frules_host.get("override_global_rules") + if override_global_rules: + return frules_host["rules"] + else: + return frules_global["rules"] def _get_imageio_settings(project_settings, host_name): diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 331235fadc..a3f8413979 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -331,6 +331,11 @@ class ColormanagedPyblishPluginMixin(object): project_settings=project_settings_, anatomy_data=anatomy_data ) + + # in case host color management is not enabled + if not config_data: + return None + file_rules = get_imageio_file_rules( project_name, host_name, project_settings=project_settings_ @@ -385,14 +390,14 @@ class ColormanagedPyblishPluginMixin(object): if colorspace_settings is None: colorspace_settings = self.get_colorspace_settings(context) + # in case host color management is not enabled + if not colorspace_settings: + self.log.warning("Host's colorspace management is disabled.") + return + # unpack colorspace settings config_data, file_rules = colorspace_settings - if not config_data: - # warn in case no colorspace path was defined - self.log.warning("No colorspace management was defined") - return - self.log.info("Config data is : `{}`".format( config_data)) From 6ae7b415330778d9588ef8a8bea2e0ca83f51881 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 12 Apr 2023 22:50:32 +0200 Subject: [PATCH 049/347] fixing tests --- .../openpype/pipeline/publish/test_publish_plugins.py | 10 +++++----- tests/unit/openpype/pipeline/test_colorspace.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/unit/openpype/pipeline/publish/test_publish_plugins.py b/tests/unit/openpype/pipeline/publish/test_publish_plugins.py index 88e0095e34..bbeab2cc90 100644 --- a/tests/unit/openpype/pipeline/publish/test_publish_plugins.py +++ b/tests/unit/openpype/pipeline/publish/test_publish_plugins.py @@ -26,7 +26,7 @@ log = logging.getLogger(__name__) class TestPipelinePublishPlugins(TestPipeline): - """ Testing Pipeline pubish_plugins.py + """ Testing Pipeline publish_plugins.py Example: cd to OpenPype repo root dir @@ -37,7 +37,7 @@ class TestPipelinePublishPlugins(TestPipeline): # files are the same as those used in `test_pipeline_colorspace` TEST_FILES = [ ( - "1d7t9_cVKeZRVF0ppCHiE5MJTTtTlJgBe", + "1YinxOToVyAd3-jAMFgVf7EWQa2x8Ma-O", "test_pipeline_colorspace.zip", "" ) @@ -140,7 +140,7 @@ class TestPipelinePublishPlugins(TestPipeline): config_data, file_rules = plugin.get_colorspace_settings(context) assert config_data["template"] == expected_config_template, ( - "Returned config tempate is not " + "Returned config template is not " f"matching {expected_config_template}" ) assert file_rules == expected_file_rules, ( @@ -193,11 +193,11 @@ class TestPipelinePublishPlugins(TestPipeline): colorspace_data_hiero = representation_hiero.get("colorspaceData") assert colorspace_data_nuke, ( - "Colorspace data were not created in prepresentation" + "Colorspace data were not created in representation" f"matching {representation_nuke}" ) assert colorspace_data_hiero, ( - "Colorspace data were not created in prepresentation" + "Colorspace data were not created in representation" f"matching {representation_hiero}" ) diff --git a/tests/unit/openpype/pipeline/test_colorspace.py b/tests/unit/openpype/pipeline/test_colorspace.py index d064ca2be4..d0981723ad 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -31,7 +31,7 @@ class TestPipelineColorspace(TestPipeline): TEST_FILES = [ ( - "1d7t9_cVKeZRVF0ppCHiE5MJTTtTlJgBe", + "1YinxOToVyAd3-jAMFgVf7EWQa2x8Ma-O", "test_pipeline_colorspace.zip", "" ) @@ -120,7 +120,7 @@ class TestPipelineColorspace(TestPipeline): ) assert config_data["template"] == expected_template, ( f"Config template \'{config_data['template']}\' doesn't match " - f"expected tempalte \'{expected_template}\'" + f"expected template \'{expected_template}\'" ) def test_parse_colorspace_from_filepath( From 7fe588fdea99f085dddcd7e37ff44961c37fff03 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 13 Apr 2023 14:44:47 +0800 Subject: [PATCH 050/347] roy's comment --- openpype/hosts/houdini/api/plugin.py | 13 ------------- .../houdini/plugins/create/create_vray_rop.py | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py index 6fb0f8b967..340a7f0770 100644 --- a/openpype/hosts/houdini/api/plugin.py +++ b/openpype/hosts/houdini/api/plugin.py @@ -247,22 +247,9 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase): """ for instance in instances: instance_node = hou.node(instance.data.get("instance_node")) - node = instance.data.get("instance_node") - if instance_node: instance_node.destroy() - # for the extra render node from the plugins - # such as vray and redshift - ipr_node = hou.node("{}{}".format(node, - "_IPR")) - if ipr_node: - ipr_node.destroy() - re_node = hou.node("{}{}".format(node, - "_render_element")) - if re_node: - re_node.destroy() - self._remove_instance_from_context(instance) def get_pre_create_attr_defs(self): diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index f0430a2892..74e53eed15 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- """Creator plugin to create VRay ROP.""" +import hou + from openpype.hosts.houdini.api import plugin from openpype.pipeline import CreatedInstance from openpype.lib import EnumDef, BoolDef @@ -17,7 +19,6 @@ class CreateVrayROP(plugin.HoudiniCreator): ext = "exr" def create(self, subset_name, instance_data, pre_create_data): - import hou # instance_data.pop("active", None) instance_data.update({"node_type": "vray_renderer"}) @@ -98,6 +99,21 @@ class CreateVrayROP(plugin.HoudiniCreator): to_lock = ["family", "id"] self.lock_parameters(instance_node, to_lock) + def remove_instances(self, instances): + for instance in instances: + node = instance.data.get("instance_node") + # for the extra render node from the plugins + # such as vray and redshift + ipr_node = hou.node("{}{}".format(node, "_IPR")) + if ipr_node: + ipr_node.destroy() + re_node = hou.node("{}{}".format(node, + "_render_element")) + if re_node: + re_node.destroy() + + return super(CreateVrayROP, self).remove_instances(instances) + def get_pre_create_attr_defs(self): attrs = super(CreateVrayROP, self).get_pre_create_attr_defs() image_format_enum = [ From 99b8cbd9ed3112be46da2413545b4a506abf371e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 13 Apr 2023 22:52:58 +0800 Subject: [PATCH 051/347] wip vray collector and update vray creator --- .../houdini/plugins/create/create_vray_rop.py | 25 +-- .../plugins/publish/collect_vray_rop.py | 147 ++++++++++++++++++ 2 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 openpype/hosts/houdini/plugins/publish/collect_vray_rop.py diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index 74e53eed15..cd80ad2d93 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -47,16 +47,8 @@ class CreateVrayROP(plugin.HoudiniCreator): ipr_rop.setPosition(instance_node.position() + hou.Vector2(0, -1)) ipr_rop.parm("rop").set(instance_node.path()) - ext = pre_create_data.get("image_format") - - filepath = "{}{}".format( - hou.text.expandString("$HIP/pyblish/"), - "{}.$F4.{}".format(subset_name, ext) - ) - parms = { "trange": 1, - "SettingsOutput_img_file_path": filepath, "SettingsEXR_bits_per_channel": "16" # half precision } @@ -71,8 +63,16 @@ class CreateVrayROP(plugin.HoudiniCreator): }) # Enable render element + ext = pre_create_data.get("image_format") has_re = pre_create_data.get("render_element_enabled") if has_re: + # Vray has its own tag for AOV file output + filepath = "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}.${}.$F4.{}".format(subset_name, + "AOV", + ext) + ) re_rop = instance_node.parent().createNode( "vray_render_channels", node_name=basename + "_render_element" @@ -84,9 +84,15 @@ class CreateVrayROP(plugin.HoudiniCreator): "use_render_channels": 1, "render_network_render_channels": re_path }) + else: + filepath = "{}{}".format( + hou.text.expandString("$HIP/pyblish/"), + "{}.$F4.{}".format(subset_name, ext) + ) parms.update({ - "use_render_channels": 0 + "use_render_channels": 0, + "SettingsOutput_img_file_path": filepath }) custom_res = pre_create_data.get("override_resolution") @@ -137,4 +143,5 @@ class CreateVrayROP(plugin.HoudiniCreator): "if enabled", default=False) ] + # ${HIP}/render/${HIPNAME}.${AOV}.$F4.exr diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py new file mode 100644 index 0000000000..90a6f10585 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -0,0 +1,147 @@ +import re +import os + +import hou +import pyblish.api + + +def get_top_referenced_parm(parm): + + processed = set() # disallow infinite loop + while True: + if parm.path() in processed: + raise RuntimeError("Parameter references result in cycle.") + + processed.add(parm.path()) + + ref = parm.getReferencedParm() + if ref.path() == parm.path(): + # It returns itself when it doesn't reference + # another parameter + return ref + else: + parm = ref + + +def evalParmNoFrame(node, parm, pad_character="#"): + + parameter = node.parm(parm) + assert parameter, "Parameter does not exist: %s.%s" % (node, parm) + + # If the parameter has a parameter reference, then get that + # parameter instead as otherwise `unexpandedString()` fails. + parameter = get_top_referenced_parm(parameter) + + # Substitute out the frame numbering with padded characters + try: + raw = parameter.unexpandedString() + except hou.Error as exc: + print("Failed: %s" % parameter) + raise RuntimeError(exc) + + def replace(match): + padding = 1 + n = match.group(2) + if n and int(n): + padding = int(n) + return pad_character * padding + + expression = re.sub(r"(\$F([0-9]*))", replace, raw) + + with hou.ScriptEvalContext(parameter): + return hou.expandStringAtFrame(expression, 0) + + +class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): + """Collect Vray Render Products + + Collects the instance.data["files"] for the render products. + + Provides: + instance -> files + + """ + + label = "VRay ROP Render Products" + order = pyblish.api.CollectorOrder + 0.4 + hosts = ["houdini"] + families = ["vray_rop"] + + def process(self, instance): + + rop = hou.node(instance.data.get("instance_node")) + + # Collect chunkSize + chunk_size_parm = rop.parm("chunkSize") + if chunk_size_parm: + chunk_size = int(chunk_size_parm.eval()) + instance.data["chunkSize"] = chunk_size + self.log.debug("Chunk Size: %s" % chunk_size) + + beauty_product = evalParmNoFrame(rop, "SettingsOutput_img_file_path") + render_products = [] + # Default beauty AOV + render_products.append(beauty_product) + files_by_aov = { + "RGB Color": self.generate_expected_files(instance, + beauty_product) + } + # TODO: add render elements if render element + + for product in render_products: + self.log.debug("Found render product: %s" % product) + filenames = list(render_products) + instance.data["files"] = filenames + self.log.debug("files:{}".format(render_products)) + + # For now by default do NOT try to publish the rendered output + instance.data["publishJobState"] = "Suspended" + instance.data["attachTo"] = [] # stub required data + + if "expectedFiles" not in instance.data: + instance.data["expectedFiles"] = list() + instance.data["expectedFiles"].append(files_by_aov) + self.log.debug("expectedFiles:{}".format(files_by_aov)) + + def get_render_element_name(self, prefix, suffix="AOV"): + """Return the output filename using the AOV prefix and suffix + """ + # need a rewrite + basename = os.path.basename(prefix) + filename, ext = os.path.splitext(basename) + aov_parm = "${%s}" % suffix + # prefix = ${HIP}/render/${HIPNAME}.${AOV}.$F4.exr + prefix = prefix.replace(filename.split("."), + filename.split(".") + + aov_parm) + # find the render element names + """ + children = hou.node("renderelement node").children() + for c in children: + print c # all the aov names + add into aov_list except "channelsContainer" + if c only has channelsContainer please dont do anything + """ + + def generate_expected_files(self, instance, path): + """Create expected files in instance data""" + + dir = os.path.dirname(path) + file = os.path.basename(path) + + if "#" in file: + pparts = file.split("#") + padding = "%0{}d".format(len(pparts) - 1) + file = pparts[0] + padding + pparts[-1] + + if "%" not in file: + return path + + expected_files = [] + start = instance.data["frameStart"] + end = instance.data["frameEnd"] + for i in range(int(start), (int(end) + 1)): + expected_files.append( + os.path.join(dir, (file % i)).replace("\\", "/")) + + return expected_files From 3e4c0cb47ea44dc568019a977417086e72168ce7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 14 Apr 2023 20:49:32 +0800 Subject: [PATCH 052/347] add vray collector --- .../houdini/plugins/create/create_vray_rop.py | 5 +- .../plugins/publish/collect_redshift_rop.py | 2 +- .../plugins/publish/collect_vray_rop.py | 56 +++++++++++-------- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index cd80ad2d93..bb2d025cde 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -64,8 +64,8 @@ class CreateVrayROP(plugin.HoudiniCreator): # Enable render element ext = pre_create_data.get("image_format") - has_re = pre_create_data.get("render_element_enabled") - if has_re: + instance_data["RenderElement"] = pre_create_data.get("render_element_enabled") # noqa + if pre_create_data.get("render_element_enabled", True): # Vray has its own tag for AOV file output filepath = "{}{}".format( hou.text.expandString("$HIP/pyblish/"), @@ -82,6 +82,7 @@ class CreateVrayROP(plugin.HoudiniCreator): re_path = re_rop.path() parms.update({ "use_render_channels": 1, + "SettingsOutput_img_file_path": filepath, "render_network_render_channels": re_path }) diff --git a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py index 15df12e075..ac55a01209 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -88,7 +88,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): ) render_products.append(beauty_product) files_by_aov = { - "beauty": self.generate_expected_files(instance, + "_": self.generate_expected_files(instance, beauty_product)} num_aovs = rop.evalParm("RS_aov") diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index 90a6f10585..f246de6e53 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -78,15 +78,21 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): instance.data["chunkSize"] = chunk_size self.log.debug("Chunk Size: %s" % chunk_size) - beauty_product = evalParmNoFrame(rop, "SettingsOutput_img_file_path") + default_prefix = evalParmNoFrame(rop, "SettingsOutput_img_file_path") render_products = [] - # Default beauty AOV + # TODO: add render elements if render element + + beauty_product = self.get_beauty_render_product(default_prefix) render_products.append(beauty_product) files_by_aov = { - "RGB Color": self.generate_expected_files(instance, - beauty_product) - } - # TODO: add render elements if render element + "RGB Color": self.generate_expected_files(instance, + beauty_product) + } + render_element, aov = self.get_render_element_name(rop, default_prefix) + if render_element is not None: + render_products.append(render_element) + files_by_aov[aov] = self.generate_expected_files(instance, + render_element) for product in render_products: self.log.debug("Found render product: %s" % product) @@ -103,25 +109,31 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): instance.data["expectedFiles"].append(files_by_aov) self.log.debug("expectedFiles:{}".format(files_by_aov)) - def get_render_element_name(self, prefix, suffix="AOV"): + def get_beauty_render_product(self, prefix, suffix=""): + """Return the beauty output filename if render element enabled + """ + aov_parm = ".{}".format(suffix) + beauty_product = None + if aov_parm in prefix: + beauty_product = prefix.replace(aov_parm, "") + else: + beauty_product = prefix + + return beauty_product + + def get_render_element_name(self, node, prefix, suffix=""): """Return the output filename using the AOV prefix and suffix """ # need a rewrite - basename = os.path.basename(prefix) - filename, ext = os.path.splitext(basename) - aov_parm = "${%s}" % suffix - # prefix = ${HIP}/render/${HIPNAME}.${AOV}.$F4.exr - prefix = prefix.replace(filename.split("."), - filename.split(".") + - aov_parm) - # find the render element names - """ - children = hou.node("renderelement node").children() - for c in children: - print c # all the aov names - add into aov_list except "channelsContainer" - if c only has channelsContainer please dont do anything - """ + re_path = node.evalParm("render_network_render_channels") + node_children = hou.node(re_path).children() + for element in node_children: + if element != "channelsContainer": + render_product = prefix.replace(suffix, str(element)) + else: + self.log.debug("skipping non render element output..") + continue + return render_product, str(element) def generate_expected_files(self, instance, path): """Create expected files in instance data""" From 1e0670d0860192d529a215477f711be51c437ddb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Apr 2023 17:03:28 +0800 Subject: [PATCH 053/347] fix the vray collector and hound fix --- .../plugins/publish/collect_redshift_rop.py | 2 +- .../plugins/publish/collect_vray_rop.py | 35 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py index ac55a01209..353a3756db 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -89,7 +89,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): render_products.append(beauty_product) files_by_aov = { "_": self.generate_expected_files(instance, - beauty_product)} + beauty_product)} num_aovs = rop.evalParm("RS_aov") for index in range(num_aovs): diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index f246de6e53..263abb375e 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -85,14 +85,16 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): beauty_product = self.get_beauty_render_product(default_prefix) render_products.append(beauty_product) files_by_aov = { - "RGB Color": self.generate_expected_files(instance, - beauty_product) - } - render_element, aov = self.get_render_element_name(rop, default_prefix) - if render_element is not None: - render_products.append(render_element) - files_by_aov[aov] = self.generate_expected_files(instance, - render_element) + "RGB Color": self.generate_expected_files(instance, + beauty_product)} + + if instance.data.get("RenderElement", True): + render_element = self.get_render_element_name(rop, default_prefix) + if render_element: + for aov, renderpass in render_element.items(): + render_products.append(renderpass) + files_by_aov[aov] = self.generate_expected_files(instance, + renderpass) for product in render_products: self.log.debug("Found render product: %s" % product) @@ -124,16 +126,17 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): def get_render_element_name(self, node, prefix, suffix=""): """Return the output filename using the AOV prefix and suffix """ + render_element_dict = {} # need a rewrite re_path = node.evalParm("render_network_render_channels") - node_children = hou.node(re_path).children() - for element in node_children: - if element != "channelsContainer": - render_product = prefix.replace(suffix, str(element)) - else: - self.log.debug("skipping non render element output..") - continue - return render_product, str(element) + if re_path: + node_children = hou.node(re_path).children() + for element in node_children: + if element.shaderName() != "vray:SettingsRenderChannels": + aov = str(element) + render_product = prefix.replace(suffix, aov) + render_element_dict[aov] = render_product + return render_element_dict def generate_expected_files(self, instance, path): """Create expected files in instance data""" From 664637219d9e673b3d1823cce0ee6a04502ef99a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 17 Apr 2023 17:04:48 +0800 Subject: [PATCH 054/347] hound fix --- openpype/hosts/houdini/plugins/publish/collect_vray_rop.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index 263abb375e..6ec9e0b37e 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -93,8 +93,7 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): if render_element: for aov, renderpass in render_element.items(): render_products.append(renderpass) - files_by_aov[aov] = self.generate_expected_files(instance, - renderpass) + files_by_aov[aov] = self.generate_expected_files(instance, renderpass) # noqa for product in render_products: self.log.debug("Found render product: %s" % product) From b8ee128bc09922c97bedd71780111ab707db1043 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 17 Apr 2023 12:00:31 +0200 Subject: [PATCH 055/347] imageio: adding `use_ocio_config` toggle --- openpype/hooks/pre_ocio_hook.py | 21 +++++++++++++++-- openpype/pipeline/colorspace.py | 23 +++++++++++++++++++ .../project_settings/aftereffects.json | 1 + .../defaults/project_settings/blender.json | 1 + .../defaults/project_settings/fusion.json | 1 + .../defaults/project_settings/hiero.json | 1 + .../defaults/project_settings/houdini.json | 1 + .../defaults/project_settings/max.json | 1 + .../defaults/project_settings/maya.json | 1 + .../defaults/project_settings/nuke.json | 1 + .../defaults/project_settings/unreal.json | 1 + .../schema_project_aftereffects.json | 5 ++++ .../schema_project_blender.json | 5 ++++ .../schema_project_fusion.json | 5 ++++ .../projects_schema/schema_project_hiero.json | 5 ++++ .../schema_project_houdini.json | 5 ++++ .../projects_schema/schema_project_max.json | 5 ++++ .../projects_schema/schema_project_maya.json | 5 ++++ .../schema_project_unreal.json | 5 ++++ .../schemas/schema_nuke_imageio.json | 5 ++++ 20 files changed, 96 insertions(+), 2 deletions(-) diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py index f51e9f48d8..9038d57e9e 100644 --- a/openpype/hooks/pre_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -1,6 +1,9 @@ from openpype.lib import PreLaunchHook -from openpype.pipeline.colorspace import get_imageio_config +from openpype.pipeline.colorspace import ( + get_imageio_config, + is_host_use_ocio_config_activated +) from openpype.pipeline.template_data import get_template_data_with_names @@ -14,7 +17,12 @@ class OCIOEnvHook(PreLaunchHook): "aftereffects", "3dsmax", "houdini", - "maya" + "maya", + "nuke", + "nukex", + "nukeassist", + "nukestudio", + "hiero" ] def execute(self): @@ -37,6 +45,15 @@ class OCIOEnvHook(PreLaunchHook): ) if config_data: + use_config_path = is_host_use_ocio_config_activated( + project_name=self.data["project_name"], + host_name=self.host_name, + host_name=self.data["project_settings"] + ) + if not use_config_path: + self.log.info("Using of OCIO config path was not activated...") + return + ocio_path = config_data["path"] self.log.info(f"Setting OCIO config path: {ocio_path}") diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index b3774e5e90..5520dab627 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -392,6 +392,29 @@ def get_imageio_config( return config_data +def is_host_use_ocio_config_activated( + project_name, host_name, project_settings=None +): + """Check if host OCIO config path is activated + + Args: + project_name (str): project name + host_name (str): host name + + Returns: + bool: True if activated + """ + project_settings = project_settings or get_project_settings(project_name) + + # get colorspace settings + _, imageio_host = _get_imageio_settings( + project_settings, host_name) + + # check if host settings is having use_ocio_config + if imageio_host.get("use_ocio_config", False): + return True + + def _get_config_data(path_list, anatomy_data): """Return first existing path in path list. diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json index 74bd519bbd..5b6dffe67e 100644 --- a/openpype/settings/defaults/project_settings/aftereffects.json +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -1,6 +1,7 @@ { "imageio": { "activate_host_color_management": true, + "use_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 8328ceeda3..f1a3286488 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -1,6 +1,7 @@ { "imageio": { "activate_host_color_management": true, + "use_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index fa44bbe3d4..ede907e415 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -1,6 +1,7 @@ { "imageio": { "activate_host_color_management": true, + "use_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index b7d5d9af23..e2b5933b6d 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -1,6 +1,7 @@ { "imageio": { "activate_host_color_management": true, + "use_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 2b7192ff99..fca782b2b8 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -1,6 +1,7 @@ { "imageio": { "activate_host_color_management": true, + "use_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index f6462c3d9a..a9625cc539 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -1,6 +1,7 @@ { "imageio": { "activate_host_color_management": true, + "use_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index fa3a7bc648..60d5b5ad67 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -2,6 +2,7 @@ "open_workfile_post_initialization": false, "imageio": { "activate_host_color_management": true, + "use_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 1dd0e5128a..cea458d289 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -10,6 +10,7 @@ }, "imageio": { "activate_host_color_management": true, + "use_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index d92d3403ed..60471f28c9 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -1,6 +1,7 @@ { "imageio": { "activate_host_color_management": true, + "use_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 7bc20fed87..35371f3505 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -17,6 +17,11 @@ "key": "activate_host_color_management", "label": "Enable Color Management in host" }, + { + "type": "boolean", + "key": "use_ocio_config", + "label": "Use OCIO config file in host" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index dbba7dfdd2..793ac5e908 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -17,6 +17,11 @@ "key": "activate_host_color_management", "label": "Enable Color Management in host" }, + { + "type": "boolean", + "key": "use_ocio_config", + "label": "Use OCIO config file in host" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index fad6361119..b088f3f034 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -17,6 +17,11 @@ "key": "activate_host_color_management", "label": "Enable Color Management in host" }, + { + "type": "boolean", + "key": "use_ocio_config", + "label": "Use OCIO config file in host" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index 5e42cb0a00..d09a9efa25 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -17,6 +17,11 @@ "key": "activate_host_color_management", "label": "Enable Color Management in host" }, + { + "type": "boolean", + "key": "use_ocio_config", + "label": "Use OCIO config file in host" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 14217c944e..24e741ff66 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -17,6 +17,11 @@ "key": "activate_host_color_management", "label": "Enable Color Management in host" }, + { + "type": "boolean", + "key": "use_ocio_config", + "label": "Use OCIO config file in host" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index 5c4b825872..aa336d0791 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -17,6 +17,11 @@ "key": "activate_host_color_management", "label": "Enable Color Management in host" }, + { + "type": "boolean", + "key": "use_ocio_config", + "label": "Use OCIO config file in host" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index ef32f907ed..534afe2e12 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -22,6 +22,11 @@ "key": "activate_host_color_management", "label": "Enable Color Management in host" }, + { + "type": "boolean", + "key": "use_ocio_config", + "label": "Use OCIO config file in host" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index 87ba3d2d43..bfcb4d7fe6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -17,6 +17,11 @@ "key": "activate_host_color_management", "label": "Enable Color Management in host" }, + { + "type": "boolean", + "key": "use_ocio_config", + "label": "Use OCIO config file in host" + }, { "type": "schema", "name": "schema_imageio_config" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json index a986db1ade..1122eb1949 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json @@ -10,6 +10,11 @@ "key": "activate_host_color_management", "label": "Enable Color Management in host" }, + { + "type": "boolean", + "key": "use_ocio_config", + "label": "Use OCIO config file in host" + }, { "type": "schema", "name": "schema_imageio_config" From 4148788761dc0c966bd83af00cbb2cd1e235d79a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 17 Apr 2023 12:16:37 +0200 Subject: [PATCH 056/347] removing obsolete settings all of those settings were now driven form global hook`pre_ocio_hook.py` which is activating OCIO environment variable --- .../defaults/project_settings/hiero.json | 5 ---- .../defaults/project_settings/maya.json | 10 -------- .../schema_project_fusion.json | 25 ------------------- .../projects_schema/schema_project_hiero.json | 14 ----------- .../projects_schema/schema_project_maya.json | 22 ---------------- 5 files changed, 76 deletions(-) diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index e2b5933b6d..a1ca0e8933 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -12,11 +12,6 @@ }, "workfile": { "ocioConfigName": "nuke-default", - "ocioconfigpath": { - "windows": [], - "darwin": [], - "linux": [] - }, "workingSpace": "linear", "sixteenBitLut": "sRGB", "eightBitLut": "sRGB", diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 60d5b5ad67..b33636a446 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -13,21 +13,11 @@ }, "colorManagementPreference_v2": { "enabled": true, - "configFilePath": { - "windows": [], - "darwin": [], - "linux": [] - }, "renderSpace": "ACEScg", "displayName": "sRGB", "viewName": "ACES 1.0 SDR-video" }, "colorManagementPreference": { - "configFilePath": { - "windows": [], - "darwin": [], - "linux": [] - }, "renderSpace": "scene-linear Rec 709/sRGB", "viewTransform": "sRGB gamma" } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index b088f3f034..d488c9f551 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -29,31 +29,6 @@ { "type": "schema", "name": "schema_imageio_file_rules" - }, - { - "key": "ocio", - "type": "dict", - "label": "OpenColorIO (OCIO)", - "collapsible": true, - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Set OCIO variable for Fusion" - }, - { - "type": "label", - "label": "'configFilePath' will be deprecated.
Please move values to : project_settings/{app}/imageio/ocio_config/filepath." - }, - { - "type": "path", - "key": "configFilePath", - "label": "OCIO Config File Path", - "multiplatform": true, - "multipath": true - } - ] } ] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index d09a9efa25..0bd88c6e11 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -36,10 +36,6 @@ "label": "Workfile", "collapsible": false, "children": [ - { - "type": "label", - "label": "'ocioconfigpath' will be deprecated.
Please move values to : project_settings/{app}/imageio/ocio_config/filepath." - }, { "type": "form", "children": [ @@ -65,19 +61,9 @@ }, { "cg-config-v1.0.0_aces-v1.3_ocio-v2.1": "cg-config-v1.0.0_aces-v1.3_ocio-v2.1 (14)" - }, - { - "custom": "custom" } ] }, - { - "type": "path", - "key": "ocioconfigpath", - "label": "Custom OCIO path", - "multiplatform": true, - "multipath": true - }, { "type": "text", "key": "workingSpace", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index 534afe2e12..cb2292319a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -47,17 +47,6 @@ "key": "enabled", "label": "Use Color Management Preference v2" }, - { - "type": "label", - "label": "'configFilePath' will be deprecated.
Please move values to : project_settings/{app}/imageio/ocio_config/filepath." - }, - { - "type": "path", - "key": "configFilePath", - "label": "OCIO Config File Path", - "multiplatform": true, - "multipath": true - }, { "type": "text", "key": "renderSpace", @@ -81,17 +70,6 @@ "label": "Color Management Preference (legacy)", "collapsible": true, "children": [ - { - "type": "label", - "label": "'configFilePath' will be deprecated.
Please move values to : project_settings/{app}/imageio/ocio_config/filepath." - }, - { - "type": "path", - "key": "configFilePath", - "label": "OCIO Config File Path", - "multiplatform": true, - "multipath": true - }, { "type": "text", "key": "renderSpace", From 5a39139aaa0321bea3635cc8d8b9ef144af87282 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 17 Apr 2023 12:27:27 +0200 Subject: [PATCH 057/347] fix duplicated keyword input --- openpype/hooks/pre_ocio_hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py index 9038d57e9e..d65433fba6 100644 --- a/openpype/hooks/pre_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -48,7 +48,7 @@ class OCIOEnvHook(PreLaunchHook): use_config_path = is_host_use_ocio_config_activated( project_name=self.data["project_name"], host_name=self.host_name, - host_name=self.data["project_settings"] + project_settings=self.data["project_settings"] ) if not use_config_path: self.log.info("Using of OCIO config path was not activated...") From 30d0b35c9a80960ee3f620c521c96fc729982d58 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 17 Apr 2023 12:28:02 +0200 Subject: [PATCH 058/347] removing obsolete code in nuke host since we are using OCIO env var there is no need to set this per attribute --- openpype/hosts/nuke/api/lib.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 71643a2fd0..57c3207463 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2013,18 +2013,6 @@ class WorkfileSettings(object): ''' workfile_settings = imageio_host["workfile"] - # get config data if imageio is enabled - config_data = None - if imageio_host.get("enabled"): - # switch ocio config to custom config - workfile_settings["OCIO_config"] = "custom" - workfile_settings["colorManagement"] = "OCIO" - - # get resolved ocio config path - config_data = get_imageio_config( - legacy_io.active_project(), "nuke" - ) - # first set OCIO if self._root_node["colorManagement"].value() \ not in str(workfile_settings["colorManagement"]): @@ -2034,6 +2022,7 @@ class WorkfileSettings(object): # we dont need the key anymore workfile_settings.pop("colorManagement") + # second set ocio version if self._root_node["OCIO_config"].value() \ not in str(workfile_settings["OCIO_config"]): @@ -2043,14 +2032,6 @@ class WorkfileSettings(object): # we dont need the key anymore workfile_settings.pop("OCIO_config") - # third set ocio custom path - if config_data: - self._root_node["customOCIOConfigPath"].setValue( - str(config_data["path"]).replace("\\", "/") - ) - # backward compatibility, remove in case it exists - workfile_settings.pop("customOCIOConfigPath") - # then set the rest for knob, value in workfile_settings.items(): # skip unfilled ocio config path From 3cf5667787141512dfaf27896666f86968b54c51 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 18 Apr 2023 21:34:01 +0800 Subject: [PATCH 059/347] adding vray_rop families into houdini deadline submission and submit publish job --- .../deadline/plugins/publish/submit_houdini_render_deadline.py | 3 ++- .../modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 2bddb28792..6a62ee0ea8 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -41,7 +41,8 @@ class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): "redshift_rop", "arnold_rop", "mantra_rop", - "karma_rop"] + "karma_rop", + "vray_rop"] targets = ["local"] use_published = True diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 58c9ffc9a0..c382a7e7ec 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -125,7 +125,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "renderlayer", "imagesequence", "vrayscene", "maxrender", "arnold_rop", "mantra_rop", - "karma_rop"] + "karma_rop", "vray_rop"] aov_filter = {"maya": [r".*([Bb]eauty).*"], "aftereffects": [r".*"], # for everything from AE From 8e3cc04cdbc14de43cc9c7a5153791df1f7156ba Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 18 Apr 2023 21:56:07 +0800 Subject: [PATCH 060/347] add farm instance to the render creators --- openpype/hosts/houdini/plugins/create/create_arnold_rop.py | 2 ++ openpype/hosts/houdini/plugins/create/create_karma_rop.py | 2 ++ openpype/hosts/houdini/plugins/create/create_mantra_rop.py | 2 ++ openpype/hosts/houdini/plugins/create/create_redshift_rop.py | 2 ++ openpype/hosts/houdini/plugins/create/create_vray_rop.py | 2 ++ 5 files changed, 10 insertions(+) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index 0657d349f9..382279e812 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -23,6 +23,8 @@ class CreateArnoldRop(plugin.HoudiniCreator): # Add chunk size attribute instance_data["chunkSize"] = 1 + # Submit for job publishing + instance_data["farm"] = True instance = super(CreateArnoldRop, self).create( subset_name, diff --git a/openpype/hosts/houdini/plugins/create/create_karma_rop.py b/openpype/hosts/houdini/plugins/create/create_karma_rop.py index 8d55298926..4326a98af4 100644 --- a/openpype/hosts/houdini/plugins/create/create_karma_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_karma_rop.py @@ -20,6 +20,8 @@ class CreateKarmaROP(plugin.HoudiniCreator): instance_data.update({"node_type": "karma"}) # Add chunk size attribute instance_data["chunkSize"] = 10 + # Submit for job publishing + instance_data["farm"] = True instance = super(CreateKarmaROP, self).create( subset_name, diff --git a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py index 2632f8d6c0..7ccb554be0 100644 --- a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py @@ -20,6 +20,8 @@ class CreateMantraROP(plugin.HoudiniCreator): instance_data.update({"node_type": "ifd"}) # Add chunk size attribute instance_data["chunkSize"] = 10 + # Submit for job publishing + instance_data["farm"] = True instance = super(CreateMantraROP, self).create( subset_name, diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_rop.py b/openpype/hosts/houdini/plugins/create/create_redshift_rop.py index 2cbe9bfda1..1fb9ab2f67 100644 --- a/openpype/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_redshift_rop.py @@ -19,6 +19,8 @@ class CreateRedshiftROP(plugin.HoudiniCreator): instance_data.update({"node_type": "Redshift_ROP"}) # Add chunk size attribute instance_data["chunkSize"] = 10 + # Submit for job publishing + instance_data["farm"] = True # Clear the family prefix from the subset subset = subset_name diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index bb2d025cde..40981da430 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -24,6 +24,8 @@ class CreateVrayROP(plugin.HoudiniCreator): instance_data.update({"node_type": "vray_renderer"}) # Add chunk size attribute instance_data["chunkSize"] = 10 + # Submit for job publishing + instance_data["farm"] = True instance = super(CreateVrayROP, self).create( subset_name, From 23105fcf9052804dcbfa034b63301faf8d6c884e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 20 Apr 2023 22:00:41 +0200 Subject: [PATCH 061/347] Update openpype/pipeline/colorspace.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fabià Serra Arrizabalaga --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 5520dab627..ceb4572b38 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -350,7 +350,7 @@ def get_imageio_config( project_settings, host_name) # check if host settings group is having activate_host_color_management - # it it does not have activation key then use global settings + # it it does not have activation key then default it to True so it uses global settings # this is for backward compatibility # TODO: in future rewrite this to be more explicit activate_host_color_management = imageio_host.get( From 3793830b3db1d340f4e594ea34b8209d383067ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 20 Apr 2023 22:05:50 +0200 Subject: [PATCH 062/347] Update openpype/pipeline/colorspace.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fabià Serra Arrizabalaga --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index ceb4572b38..5dd7f01009 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -411,7 +411,7 @@ def is_host_use_ocio_config_activated( project_settings, host_name) # check if host settings is having use_ocio_config - if imageio_host.get("use_ocio_config", False): + return imageio_host.get("use_ocio_config", False) return True From b66aaf2bdff6bd8ee013ecf9d96b63fbdb9f573c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 20 Apr 2023 22:09:48 +0200 Subject: [PATCH 063/347] keep consistency in returning types --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index ceb4572b38..29794aa7aa 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -363,7 +363,7 @@ def get_imageio_config( "Colorspace management for host '{}' is disabled.".format( host_name) ) - return False + return {} config_host = imageio_host.get("ocio_config", {}) From 92c148a42e2f3dfb4c67edbd8d2e387b1cd2fe72 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 20 Apr 2023 22:22:33 +0200 Subject: [PATCH 064/347] hound --- openpype/pipeline/colorspace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index eecaa64705..e8d88bf533 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -350,7 +350,8 @@ def get_imageio_config( project_settings, host_name) # check if host settings group is having activate_host_color_management - # it it does not have activation key then default it to True so it uses global settings + # it it does not have activation key then default it to True so it uses + # global settings # this is for backward compatibility # TODO: in future rewrite this to be more explicit activate_host_color_management = imageio_host.get( @@ -412,7 +413,6 @@ def is_host_use_ocio_config_activated( # check if host settings is having use_ocio_config return imageio_host.get("use_ocio_config", False) - return True def _get_config_data(path_list, anatomy_data): From 35c1e4cd89d6694d729cb11604ed1c21e5c44f3c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Apr 2023 10:58:18 +0200 Subject: [PATCH 065/347] Converting `app_groups` to `hosts` --- openpype/hooks/pre_ocio_hook.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py index d65433fba6..7c67be5cfe 100644 --- a/openpype/hooks/pre_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -11,7 +11,7 @@ class OCIOEnvHook(PreLaunchHook): """Set OCIO environment variable for hosts that use OpenColorIO.""" order = 0 - app_groups = [ + hosts = [ "fusion", "blender", "aftereffects", @@ -19,9 +19,6 @@ class OCIOEnvHook(PreLaunchHook): "houdini", "maya", "nuke", - "nukex", - "nukeassist", - "nukestudio", "hiero" ] From 63af34e9ab76e1e1ccc15067c188bb1648fd279e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Apr 2023 11:52:34 +0200 Subject: [PATCH 066/347] rename `use_ocio_config` to `set_ocio_config` --- openpype/hooks/pre_ocio_hook.py | 4 ++-- openpype/pipeline/colorspace.py | 6 +++--- .../settings/defaults/project_settings/aftereffects.json | 2 +- openpype/settings/defaults/project_settings/blender.json | 2 +- openpype/settings/defaults/project_settings/fusion.json | 2 +- openpype/settings/defaults/project_settings/hiero.json | 2 +- openpype/settings/defaults/project_settings/houdini.json | 2 +- openpype/settings/defaults/project_settings/max.json | 2 +- openpype/settings/defaults/project_settings/maya.json | 2 +- openpype/settings/defaults/project_settings/nuke.json | 2 +- openpype/settings/defaults/project_settings/unreal.json | 2 +- .../projects_schema/schema_project_aftereffects.json | 2 +- .../schemas/projects_schema/schema_project_blender.json | 2 +- .../schemas/projects_schema/schema_project_fusion.json | 2 +- .../schemas/projects_schema/schema_project_hiero.json | 2 +- .../schemas/projects_schema/schema_project_houdini.json | 2 +- .../schemas/projects_schema/schema_project_max.json | 2 +- .../schemas/projects_schema/schema_project_maya.json | 2 +- .../schemas/projects_schema/schema_project_unreal.json | 2 +- .../projects_schema/schemas/schema_nuke_imageio.json | 2 +- 20 files changed, 23 insertions(+), 23 deletions(-) diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py index 7c67be5cfe..e09460db14 100644 --- a/openpype/hooks/pre_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -2,7 +2,7 @@ from openpype.lib import PreLaunchHook from openpype.pipeline.colorspace import ( get_imageio_config, - is_host_use_ocio_config_activated + is_set_ocio_config_activated ) from openpype.pipeline.template_data import get_template_data_with_names @@ -42,7 +42,7 @@ class OCIOEnvHook(PreLaunchHook): ) if config_data: - use_config_path = is_host_use_ocio_config_activated( + use_config_path = is_set_ocio_config_activated( project_name=self.data["project_name"], host_name=self.host_name, project_settings=self.data["project_settings"] diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index e8d88bf533..a1714bc75e 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -393,7 +393,7 @@ def get_imageio_config( return config_data -def is_host_use_ocio_config_activated( +def is_set_ocio_config_activated( project_name, host_name, project_settings=None ): """Check if host OCIO config path is activated @@ -411,8 +411,8 @@ def is_host_use_ocio_config_activated( _, imageio_host = _get_imageio_settings( project_settings, host_name) - # check if host settings is having use_ocio_config - return imageio_host.get("use_ocio_config", False) + # check if host settings is having set_ocio_config + return imageio_host.get("set_ocio_config", False) def _get_config_data(path_list, anatomy_data): diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json index 5b6dffe67e..c30356335b 100644 --- a/openpype/settings/defaults/project_settings/aftereffects.json +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -1,7 +1,7 @@ { "imageio": { "activate_host_color_management": true, - "use_ocio_config": false, + "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index f1a3286488..1969cd8346 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -1,7 +1,7 @@ { "imageio": { "activate_host_color_management": true, - "use_ocio_config": false, + "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index ede907e415..c80936d402 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -1,7 +1,7 @@ { "imageio": { "activate_host_color_management": true, - "use_ocio_config": false, + "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index a1ca0e8933..e876d1727d 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -1,7 +1,7 @@ { "imageio": { "activate_host_color_management": true, - "use_ocio_config": false, + "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index fca782b2b8..dd3fc87b80 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -1,7 +1,7 @@ { "imageio": { "activate_host_color_management": true, - "use_ocio_config": false, + "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index a9625cc539..89ba7a702d 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -1,7 +1,7 @@ { "imageio": { "activate_host_color_management": true, - "use_ocio_config": false, + "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 9b86a04bd9..d50441d961 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -411,7 +411,7 @@ }, "imageio": { "activate_host_color_management": true, - "use_ocio_config": false, + "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 0f7c5fdaef..119a240ad5 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -10,7 +10,7 @@ }, "imageio": { "activate_host_color_management": true, - "use_ocio_config": false, + "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index 60471f28c9..71c3498f1e 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -1,7 +1,7 @@ { "imageio": { "activate_host_color_management": true, - "use_ocio_config": false, + "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 35371f3505..777d185275 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -19,7 +19,7 @@ }, { "type": "boolean", - "key": "use_ocio_config", + "key": "set_ocio_config", "label": "Use OCIO config file in host" }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 793ac5e908..8872cd123e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -19,7 +19,7 @@ }, { "type": "boolean", - "key": "use_ocio_config", + "key": "set_ocio_config", "label": "Use OCIO config file in host" }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index d488c9f551..41f464589c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -19,7 +19,7 @@ }, { "type": "boolean", - "key": "use_ocio_config", + "key": "set_ocio_config", "label": "Use OCIO config file in host" }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index 0bd88c6e11..55d29e8b07 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -19,7 +19,7 @@ }, { "type": "boolean", - "key": "use_ocio_config", + "key": "set_ocio_config", "label": "Use OCIO config file in host" }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 24e741ff66..4782295006 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -19,7 +19,7 @@ }, { "type": "boolean", - "key": "use_ocio_config", + "key": "set_ocio_config", "label": "Use OCIO config file in host" }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index aa336d0791..d57f603641 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -19,7 +19,7 @@ }, { "type": "boolean", - "key": "use_ocio_config", + "key": "set_ocio_config", "label": "Use OCIO config file in host" }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index ef6e3f9171..a99b650401 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -59,7 +59,7 @@ }, { "type": "boolean", - "key": "use_ocio_config", + "key": "set_ocio_config", "label": "Use OCIO config file in host" }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index bfcb4d7fe6..d5c58e6a5d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -19,7 +19,7 @@ }, { "type": "boolean", - "key": "use_ocio_config", + "key": "set_ocio_config", "label": "Use OCIO config file in host" }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json index 1122eb1949..4bc741703a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json @@ -12,7 +12,7 @@ }, { "type": "boolean", - "key": "use_ocio_config", + "key": "set_ocio_config", "label": "Use OCIO config file in host" }, { From bae3ca9a3be102c926fda14eaa497e3b49ec63ea Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Apr 2023 11:57:22 +0200 Subject: [PATCH 067/347] change label to reflect attribute name --- .../schemas/projects_schema/schema_project_aftereffects.json | 2 +- .../schemas/projects_schema/schema_project_blender.json | 2 +- .../entities/schemas/projects_schema/schema_project_fusion.json | 2 +- .../entities/schemas/projects_schema/schema_project_hiero.json | 2 +- .../schemas/projects_schema/schema_project_houdini.json | 2 +- .../entities/schemas/projects_schema/schema_project_max.json | 2 +- .../entities/schemas/projects_schema/schema_project_maya.json | 2 +- .../entities/schemas/projects_schema/schema_project_unreal.json | 2 +- .../schemas/projects_schema/schemas/schema_nuke_imageio.json | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 777d185275..148c1840e5 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -20,7 +20,7 @@ { "type": "boolean", "key": "set_ocio_config", - "label": "Use OCIO config file in host" + "label": "Set OCIO config file in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 8872cd123e..fe6ee94654 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -20,7 +20,7 @@ { "type": "boolean", "key": "set_ocio_config", - "label": "Use OCIO config file in host" + "label": "Set OCIO config file in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 41f464589c..f97a3a3a40 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -20,7 +20,7 @@ { "type": "boolean", "key": "set_ocio_config", - "label": "Use OCIO config file in host" + "label": "Set OCIO config file in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index 55d29e8b07..a46611dc8b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -20,7 +20,7 @@ { "type": "boolean", "key": "set_ocio_config", - "label": "Use OCIO config file in host" + "label": "Set OCIO config file in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 4782295006..d254b92269 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -20,7 +20,7 @@ { "type": "boolean", "key": "set_ocio_config", - "label": "Use OCIO config file in host" + "label": "Set OCIO config file in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index d57f603641..1141cefb40 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -20,7 +20,7 @@ { "type": "boolean", "key": "set_ocio_config", - "label": "Use OCIO config file in host" + "label": "Set OCIO config file in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index a99b650401..37f864a71c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -60,7 +60,7 @@ { "type": "boolean", "key": "set_ocio_config", - "label": "Use OCIO config file in host" + "label": "Set OCIO config file in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index d5c58e6a5d..4ff4bddc47 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -20,7 +20,7 @@ { "type": "boolean", "key": "set_ocio_config", - "label": "Use OCIO config file in host" + "label": "Set OCIO config file in host" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json index 4bc741703a..c69a4c4f4b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json @@ -13,7 +13,7 @@ { "type": "boolean", "key": "set_ocio_config", - "label": "Use OCIO config file in host" + "label": "Set OCIO config file in host" }, { "type": "schema", From a31e90c53255d4cd84983218f4647aa50ae14649 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Apr 2023 15:41:38 +0200 Subject: [PATCH 068/347] renaming variable according to attribute --- openpype/hooks/pre_ocio_hook.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py index e09460db14..bcff31fc93 100644 --- a/openpype/hooks/pre_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -42,16 +42,21 @@ class OCIOEnvHook(PreLaunchHook): ) if config_data: - use_config_path = is_set_ocio_config_activated( + set_config_path = is_set_ocio_config_activated( project_name=self.data["project_name"], host_name=self.host_name, project_settings=self.data["project_settings"] ) - if not use_config_path: - self.log.info("Using of OCIO config path was not activated...") + if not set_config_path: + self.log.info( + "Setting of OCIO environment with " + "config path was not activated..." + ) return ocio_path = config_data["path"] - self.log.info(f"Setting OCIO config path: {ocio_path}") + self.log.info( + f"Setting OCIO environment to config path: {ocio_path}") + self.launch_context.env["OCIO"] = ocio_path From 3fe4710b2115a5618ec898c94587f52d5522dbf6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Apr 2023 15:42:19 +0200 Subject: [PATCH 069/347] Maya: refactor colorspace preferecies with new settings --- openpype/hosts/maya/api/lib.py | 147 ++++++++---------- .../defaults/project_settings/maya.json | 14 +- .../projects_schema/schema_project_maya.json | 61 ++++++-- 3 files changed, 127 insertions(+), 95 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 61ea3d59df..58537db5f0 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1,6 +1,7 @@ """Standalone helper functions""" import os +from pprint import pformat import sys import platform import uuid @@ -3177,75 +3178,6 @@ def iter_shader_edits(relationships, shader_nodes, nodes_by_id, label=None): def set_colorspace(): """Set Colorspace from project configuration """ - project_name = os.getenv("AVALON_PROJECT") - imageio = get_project_settings(project_name)["maya"]["imageio"] - - # Maya 2022+ introduces new OCIO v2 color management settings that - # can override the old color managenement preferences. OpenPype has - # separate settings for both so we fall back when necessary. - use_ocio_v2 = imageio["colorManagementPreference_v2"]["enabled"] - required_maya_version = 2022 - maya_version = int(cmds.about(version=True)) - maya_supports_ocio_v2 = maya_version >= required_maya_version - if use_ocio_v2 and not maya_supports_ocio_v2: - # Fallback to legacy behavior with a warning - log.warning("Color Management Preference v2 is enabled but not " - "supported by current Maya version: {} (< {}). Falling " - "back to legacy settings.".format( - maya_version, required_maya_version) - ) - use_ocio_v2 = False - - if use_ocio_v2: - root_dict = imageio["colorManagementPreference_v2"] - else: - root_dict = imageio["colorManagementPreference"] - - if not isinstance(root_dict, dict): - msg = "set_colorspace(): argument should be dictionary" - log.error(msg) - - log.debug(">> root_dict: {}".format(root_dict)) - - # enable color management - cmds.colorManagementPrefs(e=True, cmEnabled=True) - cmds.colorManagementPrefs(e=True, ocioRulesEnabled=True) - - # set config path - custom_ocio_config = False - if root_dict.get("configFilePath"): - unresolved_path = root_dict["configFilePath"] - ocio_paths = unresolved_path[platform.system().lower()] - - resolved_path = None - for ocio_p in ocio_paths: - resolved_path = str(ocio_p).format(**os.environ) - if not os.path.exists(resolved_path): - continue - - if resolved_path: - filepath = str(resolved_path).replace("\\", "/") - cmds.colorManagementPrefs(e=True, configFilePath=filepath) - cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=True) - log.debug("maya '{}' changed to: {}".format( - "configFilePath", resolved_path)) - custom_ocio_config = True - else: - cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=False) - cmds.colorManagementPrefs(e=True, configFilePath="") - - # If no custom OCIO config file was set we make sure that Maya 2022+ - # either chooses between Maya's newer default v2 or legacy config based - # on OpenPype setting to use ocio v2 or not. - if maya_supports_ocio_v2 and not custom_ocio_config: - if use_ocio_v2: - # Use Maya 2022+ default OCIO v2 config - log.info("Setting default Maya OCIO v2 config") - cmds.colorManagementPrefs(edit=True, configFilePath="") - else: - # Set the Maya default config file path - log.info("Setting default Maya OCIO v1 legacy config") - cmds.colorManagementPrefs(edit=True, configFilePath="legacy") # set color spaces for rendering space and view transforms def _colormanage(**kwargs): @@ -3262,17 +3194,74 @@ def set_colorspace(): except RuntimeError as exc: log.error(exc) - if use_ocio_v2: - _colormanage(renderingSpaceName=root_dict["renderSpace"]) - _colormanage(displayName=root_dict["displayName"]) - _colormanage(viewName=root_dict["viewName"]) - else: - _colormanage(renderingSpaceName=root_dict["renderSpace"]) - if maya_supports_ocio_v2: - _colormanage(viewName=root_dict["viewTransform"]) - _colormanage(displayName="legacy") + project_name = os.getenv("AVALON_PROJECT") + imageio = get_project_settings(project_name)["maya"]["imageio"] + + # ocio compatibility variables + ocio_v2_maya_version = 2022 + maya_version = int(cmds.about(version=True)) + ocio_v2_support = use_ocio_v2 = maya_version >= ocio_v2_maya_version + + root_dict = {} + use_workfile_settings = imageio.get("workfile", {}).get("enabled") + + if use_workfile_settings: + # TODO: deprecated code from 3.15.5 - remove + # Maya 2022+ introduces new OCIO v2 color management settings that + # can override the old color management preferences. OpenPype has + # separate settings for both so we fall back when necessary. + use_ocio_v2 = imageio["colorManagementPreference_v2"]["enabled"] + if use_ocio_v2 and not ocio_v2_support: + # Fallback to legacy behavior with a warning + log.warning( + "Color Management Preference v2 is enabled but not " + "supported by current Maya version: {} (< {}). Falling " + "back to legacy settings.".format( + maya_version, ocio_v2_maya_version) + ) + + if use_ocio_v2: + root_dict = imageio["colorManagementPreference_v2"] else: - _colormanage(viewTransformName=root_dict["viewTransform"]) + root_dict = imageio["colorManagementPreference"] + + if not isinstance(root_dict, dict): + msg = "set_colorspace(): argument should be dictionary" + log.error(msg) + + else: + root_dict = imageio["workfile"] + + log.debug(">> root_dict: {}".format(pformat(root_dict))) + + if root_dict: + # enable color management + cmds.colorManagementPrefs(e=True, cmEnabled=True) + cmds.colorManagementPrefs(e=True, ocioRulesEnabled=True) + + # backward compatibility + # TODO: deprecated code from 3.15.5 - refactor to use new settings + view_name = root_dict.get("viewTransform") + if view_name is None: + view_name = root_dict.get("viewName") + + if use_ocio_v2: + # Use Maya 2022+ default OCIO v2 config + log.info("Setting default Maya OCIO v2 config") + cmds.colorManagementPrefs(edit=True, configFilePath="") + + # set rendering space and view transform + _colormanage(renderingSpaceName=root_dict["renderSpace"]) + _colormanage(viewName=view_name) + _colormanage(displayName=root_dict["displayName"]) + else: + # Set the Maya default config file path + log.info("Setting default Maya OCIO v1 legacy config") + cmds.colorManagementPrefs(edit=True, configFilePath="legacy") + + # set rendering space and view transform + _colormanage(renderingSpaceName=root_dict["renderSpace"]) + _colormanage(viewTransformName=view_name) @contextlib.contextmanager diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index d50441d961..b09ed146bf 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -420,6 +420,12 @@ "override_global_rules": false, "rules": {} }, + "workfile": { + "enabled": false, + "renderSpace": "ACEScg", + "displayName": "sRGB", + "viewName": "ACES 1.0 SDR-video" + }, "colorManagementPreference_v2": { "enabled": true, "renderSpace": "ACEScg", @@ -448,6 +454,10 @@ "destination-path": [] } }, + "include_handles": { + "include_handles_default": false, + "per_task_type": [] + }, "scriptsmenu": { "name": "OpenPype Tools", "definition": [ @@ -1546,10 +1556,6 @@ } ] }, - "include_handles": { - "include_handles_default": false, - "per_task_type": [] - }, "templated_workfile_build": { "profiles": [] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index 37f864a71c..b5366bb0a7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -71,16 +71,16 @@ "name": "schema_imageio_file_rules" }, { - "key": "colorManagementPreference_v2", + "key": "workfile", "type": "dict", - "label": "Color Management Preference v2 (Maya 2022+)", + "label": "Workfile", "collapsible": true, "checkbox_key": "enabled", "children": [ { "type": "boolean", "key": "enabled", - "label": "Use Color Management Preference v2" + "label": "Enabled" }, { "type": "text", @@ -100,20 +100,57 @@ ] }, { - "key": "colorManagementPreference", - "type": "dict", - "label": "Color Management Preference (legacy)", + "type": "collapsible-wrap", + "label": "[Deprecated] please migrate all to 'Workfile' and enable it.", "collapsible": true, + "collapsed": true, "children": [ { - "type": "text", - "key": "renderSpace", - "label": "Rendering Space" + "key": "colorManagementPreference_v2", + "type": "dict", + "label": "[DEPRECATED] Color Management Preference v2 (Maya 2022+)", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Use Color Management Preference v2" + }, + { + "type": "text", + "key": "renderSpace", + "label": "Rendering Space" + }, + { + "type": "text", + "key": "displayName", + "label": "Display" + }, + { + "type": "text", + "key": "viewName", + "label": "View" + } + ] }, { - "type": "text", - "key": "viewTransform", - "label": "Viewer Transform" + "key": "colorManagementPreference", + "type": "dict", + "label": "[DEPRECATED] Color Management Preference (legacy)", + "collapsible": true, + "children": [ + { + "type": "text", + "key": "renderSpace", + "label": "Rendering Space" + }, + { + "type": "text", + "key": "viewTransform", + "label": "Viewer Transform (workfile/viewName)" + } + ] } ] } From 7aaa5f767c196bb9b95e3225e0347e84f11660f7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 21 Apr 2023 15:42:51 +0200 Subject: [PATCH 070/347] removing old settings --- openpype/settings/defaults/project_settings/fusion.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index c80936d402..ba2abd467f 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -9,14 +9,6 @@ "file_rules": { "override_global_rules": false, "rules": {} - }, - "ocio": { - "enabled": false, - "configFilePath": { - "windows": [], - "darwin": [], - "linux": [] - } } }, "copy_fusion_settings": { From e3b5aa0f3fd48ae5b9cf3753d636593b069de809 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 24 Apr 2023 16:22:19 +0800 Subject: [PATCH 071/347] clarify the directory for each renderer's rop --- .../houdini/plugins/create/create_arnold_rop.py | 4 ++-- .../houdini/plugins/create/create_karma_rop.py | 8 ++++---- .../houdini/plugins/create/create_mantra_rop.py | 4 ++-- .../hosts/houdini/plugins/create/create_vray_rop.py | 13 +++++++------ .../houdini/plugins/publish/collect_mantra_rop.py | 4 +++- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index 382279e812..2ae6727ce4 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -42,8 +42,8 @@ class CreateArnoldRop(plugin.HoudiniCreator): ext = pre_create_data.get("image_format") filepath = "{}{}".format( - hou.text.expandString("$HIP/pyblish/"), - "{}.$F4.{}".format(subset_name, ext) + hou.text.expandString("$HIP/pyblish/renders/"), + "{}/{}.$F4.{}".format(subset_name, subset_name, ext) ) parms = { # Render frame range diff --git a/openpype/hosts/houdini/plugins/create/create_karma_rop.py b/openpype/hosts/houdini/plugins/create/create_karma_rop.py index 4326a98af4..e2fe7f40be 100644 --- a/openpype/hosts/houdini/plugins/create/create_karma_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_karma_rop.py @@ -33,8 +33,8 @@ class CreateKarmaROP(plugin.HoudiniCreator): ext = pre_create_data.get("image_format") filepath = "{}{}".format( - hou.text.expandString("$HIP/pyblish/"), - "{}.$F4.{}".format(subset_name, ext) + hou.text.expandString("$HIP/pyblish/render/"), + "{}/{}.$F4.{}".format(subset_name, subset_name, ext) ) checkpoint = "{}{}".format( hou.text.expandString("$HIP/pyblish/"), @@ -42,8 +42,8 @@ class CreateKarmaROP(plugin.HoudiniCreator): ) usd_directory = "{}{}".format( - hou.text.expandString("$HIP/pyblish/usd_renders/"), - "{}_$RENDERID".format(subset_name) + hou.text.expandString("$HIP/pyblish/renders/usd_renders/"), + "{}_$RENDERID".format(subset_name, subset_name, ext) ) parms = { diff --git a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py index 7ccb554be0..83332ec775 100644 --- a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py @@ -33,8 +33,8 @@ class CreateMantraROP(plugin.HoudiniCreator): ext = pre_create_data.get("image_format") filepath = "{}{}".format( - hou.text.expandString("$HIP/pyblish/"), - "{}.$F4.{}".format(subset_name, ext) + hou.text.expandString("$HIP/pyblish/render/"), + "{}/{}.$F4.{}".format(subset_name, subset_name, ext) ) parms = { diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index 40981da430..e4875d5b0d 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -70,10 +70,11 @@ class CreateVrayROP(plugin.HoudiniCreator): if pre_create_data.get("render_element_enabled", True): # Vray has its own tag for AOV file output filepath = "{}{}".format( - hou.text.expandString("$HIP/pyblish/"), - "{}.${}.$F4.{}".format(subset_name, - "AOV", - ext) + hou.text.expandString("$HIP/pyblish/renders/"), + "{}/{}.${}.$F4.{}".format(subset_name, + subset_name, + "AOV", + ext) ) re_rop = instance_node.parent().createNode( "vray_render_channels", @@ -90,8 +91,8 @@ class CreateVrayROP(plugin.HoudiniCreator): else: filepath = "{}{}".format( - hou.text.expandString("$HIP/pyblish/"), - "{}.$F4.{}".format(subset_name, ext) + hou.text.expandString("$HIP/pyblish/renders/"), + "{}/{}.$F4.{}".format(subset_name, subset_name, ext) ) parms.update({ "use_render_channels": 0, diff --git a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py index 1eb850e52e..8f06eb12cf 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -99,8 +99,10 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): var = rop.evalParm("vm_variable_plane%d" % i) if var: aov_name = "vm_filename_plane%d" % i + aov_boolean = "vm_usefile_plane%d" % i + aov_enabled = rop.evalParm(aov_boolean) has_aov_path = rop.evalParm(aov_name) - if has_aov_path: + if has_aov_path and aov_enabled == 1: aov_prefix = evalParmNoFrame(rop, aov_name) aov_product = self.get_render_product_name( prefix=aov_prefix, suffix=None From 4274300874dc2a3bdf6d91c79ccebd480a0ddd7e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 27 Apr 2023 20:55:40 +0800 Subject: [PATCH 072/347] oscar's comments --- .../plugins/create/create_arnold_rop.py | 7 +++--- .../plugins/create/create_karma_rop.py | 25 +++++++++---------- .../plugins/create/create_mantra_rop.py | 7 +++--- .../houdini/plugins/create/create_vray_rop.py | 13 +++++++--- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index 2ae6727ce4..9634bf1bd9 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -41,9 +41,10 @@ class CreateArnoldRop(plugin.HoudiniCreator): ext = pre_create_data.get("image_format") - filepath = "{}{}".format( - hou.text.expandString("$HIP/pyblish/renders/"), - "{}/{}.$F4.{}".format(subset_name, subset_name, ext) + filepath = "{renders_dir}{subset_name}/{subset_name}.$F4.{ext}".format( + renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), + subset_name=subset_name, + ext=ext, ) parms = { # Render frame range diff --git a/openpype/hosts/houdini/plugins/create/create_karma_rop.py b/openpype/hosts/houdini/plugins/create/create_karma_rop.py index e2fe7f40be..891a6c9794 100644 --- a/openpype/hosts/houdini/plugins/create/create_karma_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_karma_rop.py @@ -32,18 +32,19 @@ class CreateKarmaROP(plugin.HoudiniCreator): ext = pre_create_data.get("image_format") - filepath = "{}{}".format( - hou.text.expandString("$HIP/pyblish/render/"), - "{}/{}.$F4.{}".format(subset_name, subset_name, ext) + filepath = "{renders_dir}{subset_name}/{subset_name}.$F4.{ext}".format( + renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), + subset_name=subset_name, + ext=ext, ) - checkpoint = "{}{}".format( - hou.text.expandString("$HIP/pyblish/"), - "{}.$F4.checkpoint".format(subset_name) + checkpoint = "{cp_dir}{subset_name}.$F4.checkpoint".format( + cp_dir= hou.text.expandString("$HIP/pyblish/"), + subset_name= subset_name ) - usd_directory = "{}{}".format( - hou.text.expandString("$HIP/pyblish/renders/usd_renders/"), - "{}_$RENDERID".format(subset_name, subset_name, ext) + usd_directory = "{usd_dir}{subset_name}_$RENDERID".format( + usd_dir = hou.text.expandString("$HIP/pyblish/renders/usd_renders/"), + subset_name=subset_name ) parms = { @@ -66,12 +67,10 @@ class CreateKarmaROP(plugin.HoudiniCreator): camera = None for node in self.selected_nodes: if node.type().name() == "cam": - camera = node.path() - camera_node = hou.node(camera) has_camera = pre_create_data.get("cam_res") if has_camera: - res_x = camera_node.evalParm("resx") - res_y = camera_node.evalParm("resy") + res_x = node.evalParm("resx") + res_y = node.evalParm("resy") if not camera: self.log.warning("No render camera found in selection") diff --git a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py index 83332ec775..5ca53e96de 100644 --- a/openpype/hosts/houdini/plugins/create/create_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_mantra_rop.py @@ -32,9 +32,10 @@ class CreateMantraROP(plugin.HoudiniCreator): ext = pre_create_data.get("image_format") - filepath = "{}{}".format( - hou.text.expandString("$HIP/pyblish/render/"), - "{}/{}.$F4.{}".format(subset_name, subset_name, ext) + filepath = "{renders_dir}{subset_name}/{subset_name}.$F4.{ext}".format( + renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), + subset_name=subset_name, + ext=ext, ) parms = { diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index e4875d5b0d..d9cbea2e53 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -69,6 +69,12 @@ class CreateVrayROP(plugin.HoudiniCreator): instance_data["RenderElement"] = pre_create_data.get("render_element_enabled") # noqa if pre_create_data.get("render_element_enabled", True): # Vray has its own tag for AOV file output + filepath = "{renders_dir}{subset_name}/{subset_name}.${aov}.$F4.{ext}".format( + renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), + subset_name=subset_name, + aov = "AOV", + ext=ext, + ) filepath = "{}{}".format( hou.text.expandString("$HIP/pyblish/renders/"), "{}/{}.${}.$F4.{}".format(subset_name, @@ -90,9 +96,10 @@ class CreateVrayROP(plugin.HoudiniCreator): }) else: - filepath = "{}{}".format( - hou.text.expandString("$HIP/pyblish/renders/"), - "{}/{}.$F4.{}".format(subset_name, subset_name, ext) + filepath = "{renders_dir}{subset_name}/{subset_name}.$F4.{ext}".format( + renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), + subset_name=subset_name, + ext=ext, ) parms.update({ "use_render_channels": 0, From 685d5285922eb82540b20442b76e33b18b6983bf Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 27 Apr 2023 21:04:43 +0800 Subject: [PATCH 073/347] hound fix --- .../hosts/houdini/plugins/create/create_karma_rop.py | 7 ++++--- .../hosts/houdini/plugins/create/create_vray_rop.py | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_karma_rop.py b/openpype/hosts/houdini/plugins/create/create_karma_rop.py index 891a6c9794..acf6d25b3e 100644 --- a/openpype/hosts/houdini/plugins/create/create_karma_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_karma_rop.py @@ -38,12 +38,13 @@ class CreateKarmaROP(plugin.HoudiniCreator): ext=ext, ) checkpoint = "{cp_dir}{subset_name}.$F4.checkpoint".format( - cp_dir= hou.text.expandString("$HIP/pyblish/"), - subset_name= subset_name + cp_dir=hou.text.expandString("$HIP/pyblish/"), + subset_name=subset_name ) usd_directory = "{usd_dir}{subset_name}_$RENDERID".format( - usd_dir = hou.text.expandString("$HIP/pyblish/renders/usd_renders/"), + usd_dir=hou.text.expandString( + "$HIP/pyblish/renders/usd_renders/"), subset_name=subset_name ) diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index d9cbea2e53..dcad2ca6b2 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -69,11 +69,11 @@ class CreateVrayROP(plugin.HoudiniCreator): instance_data["RenderElement"] = pre_create_data.get("render_element_enabled") # noqa if pre_create_data.get("render_element_enabled", True): # Vray has its own tag for AOV file output - filepath = "{renders_dir}{subset_name}/{subset_name}.${aov}.$F4.{ext}".format( + filepath = "{renders_dir}{subset_name}/{subset_name}.{fmt}".format( renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), subset_name=subset_name, - aov = "AOV", - ext=ext, + fmt="${aov}.$F4.{ext}".format(aov = "AOV", + ext=ext,) ) filepath = "{}{}".format( hou.text.expandString("$HIP/pyblish/renders/"), @@ -96,10 +96,10 @@ class CreateVrayROP(plugin.HoudiniCreator): }) else: - filepath = "{renders_dir}{subset_name}/{subset_name}.$F4.{ext}".format( + filepath = "{renders_dir}{subset_name}/{subset_name}.{fmt}".format( renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), subset_name=subset_name, - ext=ext, + fmt="$F4.{ext}".format(ext=ext) ) parms.update({ "use_render_channels": 0, From a376db5e5845a5b4384c40cc7b608176afef20b5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 27 Apr 2023 21:08:05 +0800 Subject: [PATCH 074/347] hound fix --- openpype/hosts/houdini/plugins/create/create_karma_rop.py | 3 +-- openpype/hosts/houdini/plugins/create/create_vray_rop.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_karma_rop.py b/openpype/hosts/houdini/plugins/create/create_karma_rop.py index acf6d25b3e..edfb992e1a 100644 --- a/openpype/hosts/houdini/plugins/create/create_karma_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_karma_rop.py @@ -43,8 +43,7 @@ class CreateKarmaROP(plugin.HoudiniCreator): ) usd_directory = "{usd_dir}{subset_name}_$RENDERID".format( - usd_dir=hou.text.expandString( - "$HIP/pyblish/renders/usd_renders/"), + usd_dir=hou.text.expandString("$HIP/pyblish/renders/usd_renders/"), # noqa subset_name=subset_name ) diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index dcad2ca6b2..0a74d93c99 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -72,8 +72,8 @@ class CreateVrayROP(plugin.HoudiniCreator): filepath = "{renders_dir}{subset_name}/{subset_name}.{fmt}".format( renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), subset_name=subset_name, - fmt="${aov}.$F4.{ext}".format(aov = "AOV", - ext=ext,) + fmt="${aov}.$F4.{ext}".format(aov="AOV", + ext=ext) ) filepath = "{}{}".format( hou.text.expandString("$HIP/pyblish/renders/"), From 6ec63c9e6013a0b39f93f370fded8aaaf2be2341 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 27 Apr 2023 21:10:25 +0800 Subject: [PATCH 075/347] add to get ocio color management preference before rendering --- openpype/hosts/houdini/api/lib.py | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 2e58f3dd98..df5c95578b 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import sys import os +import re import uuid import logging from contextlib import contextmanager @@ -581,3 +582,73 @@ def splitext(name, allowed_multidot_extensions): return name[:-len(ext)], ext return os.path.splitext(name) + +def get_top_referenced_parm(parm): + + processed = set() # disallow infinite loop + while True: + if parm.path() in processed: + raise RuntimeError("Parameter references result in cycle.") + + processed.add(parm.path()) + + ref = parm.getReferencedParm() + if ref.path() == parm.path(): + # It returns itself when it doesn't reference + # another parameter + return ref + else: + parm = ref + + +def evalParmNoFrame(node, parm, pad_character="#"): + + parameter = node.parm(parm) + assert parameter, "Parameter does not exist: %s.%s" % (node, parm) + + # If the parameter has a parameter reference, then get that + # parameter instead as otherwise `unexpandedString()` fails. + parameter = get_top_referenced_parm(parameter) + + # Substitute out the frame numbering with padded characters + try: + raw = parameter.unexpandedString() + except hou.Error as exc: + print("Failed: %s" % parameter) + raise RuntimeError(exc) + + def replace(match): + padding = 1 + n = match.group(2) + if n and int(n): + padding = int(n) + return pad_character * padding + + expression = re.sub(r"(\$F([0-9]*))", replace, raw) + + with hou.ScriptEvalContext(parameter): + return hou.expandStringAtFrame(expression, 0) + + +def get_color_management_preferences(): + """Get default OCIO preferences""" + data = { + "config": hou.Color.ocio_configPath() + + } + + # Get default display and view from OCIO + display = hou.Color.ocio_defaultDisplay() + disp_regex = re.compile(r"^(?P.+-)(?P.+)$") + disp_match = disp_regex.match(display) + + view = hou.Color.ocio_defaultView() + view_regex = re.compile(r"^(?P.+- )(?P.+)$") + view_match = view_regex.match(view) + data.update({ + "display": disp_match.group("display"), + "view": view_match.group("view") + + }) + + return data From c1d4ddf0d6b509e5cc8b2210b702d1eb8f067344 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 27 Apr 2023 21:11:44 +0800 Subject: [PATCH 076/347] hound fix --- openpype/hosts/houdini/api/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index df5c95578b..a33ba7aad2 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -583,6 +583,7 @@ def splitext(name, allowed_multidot_extensions): return os.path.splitext(name) + def get_top_referenced_parm(parm): processed = set() # disallow infinite loop @@ -633,7 +634,7 @@ def evalParmNoFrame(node, parm, pad_character="#"): def get_color_management_preferences(): """Get default OCIO preferences""" data = { - "config": hou.Color.ocio_configPath() + "config": hou.Color.ocio_configPath() } From cc4a91b01a26ea3836ab101c62d0907cea56ce27 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 27 Apr 2023 21:22:38 +0800 Subject: [PATCH 077/347] add colorspace parameters for publish job submission --- openpype/hosts/houdini/api/colorspace.py | 55 ++++++++++++++ .../plugins/publish/collect_arnold_rop.py | 67 +++++------------ .../plugins/publish/collect_karma_rop.py | 70 +++++------------ .../plugins/publish/collect_mantra_rop.py | 75 ++++++------------- .../plugins/publish/collect_redshift_rop.py | 72 ++++++------------ .../plugins/publish/collect_vray_rop.py | 72 +++++------------- 6 files changed, 155 insertions(+), 256 deletions(-) create mode 100644 openpype/hosts/houdini/api/colorspace.py diff --git a/openpype/hosts/houdini/api/colorspace.py b/openpype/hosts/houdini/api/colorspace.py new file mode 100644 index 0000000000..b615bc199c --- /dev/null +++ b/openpype/hosts/houdini/api/colorspace.py @@ -0,0 +1,55 @@ +import attr +import hou +from openpype.hosts.houdini.api.lib import get_color_management_preferences + + +@attr.s +class LayerMetadata(object): + """Data class for Render Layer metadata.""" + frameStart = attr.ib() + frameEnd = attr.ib() + + +@attr.s +class RenderProduct(object): + """Getting Colorspace as + Specific Render Product Parameter for submitting + publish job. + + """ + colorspace = attr.ib() # colorspace + view = attr.ib() + productName = attr.ib(default=None) + +class ARenderProduct(object): + + def __init__(self): + """Constructor.""" + # Initialize + self.layer_data = self._get_layer_data() + self.layer_data.products = self.get_colorspace_data() + + def _get_layer_data(self): + return LayerMetadata( + frameStart=int(hou.playbar.frameRange()[0]), + frameEnd=int(hou.playbar.frameRange()[1]), + ) + + def get_colorspace_data(self): + """To be implemented by renderer class. + + This should return a list of RenderProducts. + + Returns: + list: List of RenderProduct + + """ + data = get_color_management_preferences() + colorspace_data = [ + RenderProduct( + colorspace=data["display"], + view=data["view"], + productName="" + ) + ] + return colorspace_data diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py index 4d82b74aa2..7ec4ead968 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -4,52 +4,13 @@ import os import hou import pyblish.api - -def get_top_referenced_parm(parm): - - processed = set() # disallow infinite loop - while True: - if parm.path() in processed: - raise RuntimeError("Parameter references result in cycle.") - - processed.add(parm.path()) - - ref = parm.getReferencedParm() - if ref.path() == parm.path(): - # It returns itself when it doesn't reference - # another parameter - return ref - else: - parm = ref - - -def evalParmNoFrame(node, parm, pad_character="#"): - - parameter = node.parm(parm) - assert parameter, "Parameter does not exist: %s.%s" % (node, parm) - - # If the parameter has a parameter reference, then get that - # parameter instead as otherwise `unexpandedString()` fails. - parameter = get_top_referenced_parm(parameter) - - # Substitute out the frame numbering with padded characters - try: - raw = parameter.unexpandedString() - except hou.Error as exc: - print("Failed: %s" % parameter) - raise RuntimeError(exc) - - def replace(match): - padding = 1 - n = match.group(2) - if n and int(n): - padding = int(n) - return pad_character * padding - - expression = re.sub(r"(\$F([0-9]*))", replace, raw) - - with hou.ScriptEvalContext(parameter): - return hou.expandStringAtFrame(expression, 0) +from openpype.hosts.houdini.api.lib import ( + evalParmNoFrame, + get_color_management_preferences +) +from openpype.hosts.houdini.api import( + colorspace +) class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): @@ -114,6 +75,7 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): filenames = list(render_products) instance.data["files"] = filenames + instance.data["renderProducts"] = colorspace.ARenderProduct() # For now by default do NOT try to publish the rendered output instance.data["publishJobState"] = "Suspended" @@ -123,6 +85,12 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): instance.data["expectedFiles"] = list() instance.data["expectedFiles"].append(files_by_aov) + # update the colorspace data + colorspace_data = get_color_management_preferences() + instance.data["colorspaceConfig"] = colorspace_data["config"] + instance.data["colorspaceDisplay"] = colorspace_data["display"] + instance.data["colorspaceView"] = colorspace_data["view"] + def get_render_product_name(self, prefix, suffix): """Return the output filename using the AOV prefix and suffix""" @@ -157,9 +125,10 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): file = os.path.basename(path) if "#" in file: - pparts = file.split("#") - padding = "%0{}d".format(len(pparts) - 1) - file = pparts[0] + padding + pparts[-1] + def replace(match): + return "%0{}d".format(len(match.group())) + + file = re.sub("#+", replace, file) if "%" not in file: return path diff --git a/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py b/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py index 10c97269fc..11747a4100 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py @@ -4,52 +4,13 @@ import os import hou import pyblish.api - -def get_top_referenced_parm(parm): - - processed = set() # disallow infinite loop - while True: - if parm.path() in processed: - raise RuntimeError("Parameter references result in cycle.") - - processed.add(parm.path()) - - ref = parm.getReferencedParm() - if ref.path() == parm.path(): - # It returns itself when it doesn't reference - # another parameter - return ref - else: - parm = ref - - -def evalParmNoFrame(node, parm, pad_character="#"): - - parameter = node.parm(parm) - assert parameter, "Parameter does not exist: %s.%s" % (node, parm) - - # If the parameter has a parameter reference, then get that - # parameter instead as otherwise `unexpandedString()` fails. - parameter = get_top_referenced_parm(parameter) - - # Substitute out the frame numbering with padded characters - try: - raw = parameter.unexpandedString() - except hou.Error as exc: - print("Failed: %s" % parameter) - raise RuntimeError(exc) - - def replace(match): - padding = 1 - n = match.group(2) - if n and int(n): - padding = int(n) - return pad_character * padding - - expression = re.sub(r"(\$F([0-9]*))", replace, raw) - - with hou.ScriptEvalContext(parameter): - return hou.expandStringAtFrame(expression, 0) +from openpype.hosts.houdini.api.lib import ( + evalParmNoFrame, + get_color_management_preferences +) +from openpype.hosts.houdini.api import( + colorspace +) class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin): @@ -94,6 +55,7 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin): filenames = list(render_products) instance.data["files"] = filenames + instance.data["renderProducts"] = colorspace.ARenderProduct() for product in render_products: self.log.debug("Found render product: %s" % product) @@ -102,13 +64,18 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin): instance.data["expectedFiles"] = list() instance.data["expectedFiles"].append(files_by_aov) + # update the colorspace data + colorspace_data = get_color_management_preferences() + instance.data["colorspaceConfig"] = colorspace_data["config"] + instance.data["colorspaceDisplay"] = colorspace_data["display"] + instance.data["colorspaceView"] = colorspace_data["view"] + def get_render_product_name(self, prefix, suffix): + product_name = prefix if suffix: # Add ".{suffix}" before the extension prefix_base, ext = os.path.splitext(prefix) product_name = prefix_base + "." + suffix + ext - else: - product_name = prefix return product_name @@ -119,9 +86,10 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin): file = os.path.basename(path) if "#" in file: - pparts = file.split("#") - padding = "%0{}d".format(len(pparts) - 1) - file = pparts[0] + padding + pparts[-1] + def replace(match): + return "%0{}d".format(len(match.group())) + + file = re.sub("#+", replace, file) if "%" not in file: return path diff --git a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py index 8f06eb12cf..9c9a15f8f7 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -4,52 +4,13 @@ import os import hou import pyblish.api - -def get_top_referenced_parm(parm): - - processed = set() # disallow infinite loop - while True: - if parm.path() in processed: - raise RuntimeError("Parameter references result in cycle.") - - processed.add(parm.path()) - - ref = parm.getReferencedParm() - if ref.path() == parm.path(): - # It returns itself when it doesn't reference - # another parameter - return ref - else: - parm = ref - - -def evalParmNoFrame(node, parm, pad_character="#"): - - parameter = node.parm(parm) - assert parameter, "Parameter does not exist: %s.%s" % (node, parm) - - # If the parameter has a parameter reference, then get that - # parameter instead as otherwise `unexpandedString()` fails. - parameter = get_top_referenced_parm(parameter) - - # Substitute out the frame numbering with padded characters - try: - raw = parameter.unexpandedString() - except hou.Error as exc: - print("Failed: %s" % parameter) - raise RuntimeError(exc) - - def replace(match): - padding = 1 - n = match.group(2) - if n and int(n): - padding = int(n) - return pad_character * padding - - expression = re.sub(r"(\$F([0-9]*))", replace, raw) - - with hou.ScriptEvalContext(parameter): - return hou.expandStringAtFrame(expression, 0) +from openpype.hosts.houdini.api.lib import ( + evalParmNoFrame, + get_color_management_preferences +) +from openpype.hosts.houdini.api import( + colorspace +) class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): @@ -113,8 +74,10 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): for product in render_products: self.log.debug("Found render product: %s" % product) - filenames = list(render_products) - instance.data["files"] = filenames + + filenames = list(render_products) + instance.data["files"] = filenames + instance.data["renderProducts"] = colorspace.ARenderProduct() # For now by default do NOT try to publish the rendered output instance.data["publishJobState"] = "Suspended" @@ -124,13 +87,18 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): instance.data["expectedFiles"] = list() instance.data["expectedFiles"].append(files_by_aov) + # update the colorspace data + colorspace_data = get_color_management_preferences() + instance.data["colorspaceConfig"] = colorspace_data["config"] + instance.data["colorspaceDisplay"] = colorspace_data["display"] + instance.data["colorspaceView"] = colorspace_data["view"] + def get_render_product_name(self, prefix, suffix): + product_name = prefix if suffix: # Add ".{suffix}" before the extension prefix_base, ext = os.path.splitext(prefix) product_name = prefix_base + "." + suffix + ext - else: - product_name = prefix return product_name @@ -141,9 +109,10 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin): file = os.path.basename(path) if "#" in file: - pparts = file.split("#") - padding = "%0{}d".format(len(pparts) - 1) - file = pparts[0] + padding + pparts[-1] + def replace(match): + return "%0{}d".format(len(match.group())) + + file = re.sub("#+", replace, file) if "%" not in file: return path diff --git a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py index 353a3756db..ab00e3c339 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -4,52 +4,13 @@ import os import hou import pyblish.api - -def get_top_referenced_parm(parm): - - processed = set() # disallow infinite loop - while True: - if parm.path() in processed: - raise RuntimeError("Parameter references result in cycle.") - - processed.add(parm.path()) - - ref = parm.getReferencedParm() - if ref.path() == parm.path(): - # It returns itself when it doesn't reference - # another parameter - return ref - else: - parm = ref - - -def evalParmNoFrame(node, parm, pad_character="#"): - - parameter = node.parm(parm) - assert parameter, "Parameter does not exist: %s.%s" % (node, parm) - - # If the parameter has a parameter reference, then get that - # parameter instead as otherwise `unexpandedString()` fails. - parameter = get_top_referenced_parm(parameter) - - # Substitute out the frame numbering with padded characters - try: - raw = parameter.unexpandedString() - except hou.Error as exc: - print("Failed: %s" % parameter) - raise RuntimeError(exc) - - def replace(match): - padding = 1 - n = match.group(2) - if n and int(n): - padding = int(n) - return pad_character * padding - - expression = re.sub(r"(\$F([0-9]*))", replace, raw) - - with hou.ScriptEvalContext(parameter): - return hou.expandStringAtFrame(expression, 0) +from openpype.hosts.houdini.api.lib import ( + evalParmNoFrame, + get_color_management_preferences +) +from openpype.hosts.houdini.api import( + colorspace +) class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): @@ -112,8 +73,10 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): for product in render_products: self.log.debug("Found render product: %s" % product) - filenames = list(render_products) - instance.data["files"] = filenames + + filenames = list(render_products) + instance.data["files"] = filenames + instance.data["renderProducts"] = colorspace.ARenderProduct() # For now by default do NOT try to publish the rendered output instance.data["publishJobState"] = "Suspended" @@ -123,6 +86,12 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): instance.data["expectedFiles"] = list() instance.data["expectedFiles"].append(files_by_aov) + # update the colorspace data + colorspace_data = get_color_management_preferences() + instance.data["colorspaceConfig"] = colorspace_data["config"] + instance.data["colorspaceDisplay"] = colorspace_data["display"] + instance.data["colorspaceView"] = colorspace_data["view"] + def get_render_product_name(self, prefix, suffix): """Return the output filename using the AOV prefix and suffix""" @@ -154,9 +123,10 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin): file = os.path.basename(path) if "#" in file: - pparts = file.split("#") - padding = "%0{}d".format(len(pparts) - 1) - file = pparts[0] + padding + pparts[-1] + def replace(match): + return "%0{}d".format(len(match.group())) + + file = re.sub("#+", replace, file) if "%" not in file: return path diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index 6ec9e0b37e..aa05cf6736 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -4,52 +4,13 @@ import os import hou import pyblish.api - -def get_top_referenced_parm(parm): - - processed = set() # disallow infinite loop - while True: - if parm.path() in processed: - raise RuntimeError("Parameter references result in cycle.") - - processed.add(parm.path()) - - ref = parm.getReferencedParm() - if ref.path() == parm.path(): - # It returns itself when it doesn't reference - # another parameter - return ref - else: - parm = ref - - -def evalParmNoFrame(node, parm, pad_character="#"): - - parameter = node.parm(parm) - assert parameter, "Parameter does not exist: %s.%s" % (node, parm) - - # If the parameter has a parameter reference, then get that - # parameter instead as otherwise `unexpandedString()` fails. - parameter = get_top_referenced_parm(parameter) - - # Substitute out the frame numbering with padded characters - try: - raw = parameter.unexpandedString() - except hou.Error as exc: - print("Failed: %s" % parameter) - raise RuntimeError(exc) - - def replace(match): - padding = 1 - n = match.group(2) - if n and int(n): - padding = int(n) - return pad_character * padding - - expression = re.sub(r"(\$F([0-9]*))", replace, raw) - - with hou.ScriptEvalContext(parameter): - return hou.expandStringAtFrame(expression, 0) +from openpype.hosts.houdini.api.lib import ( + evalParmNoFrame, + get_color_management_preferences +) +from openpype.hosts.houdini.api import( + colorspace +) class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): @@ -97,9 +58,9 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): for product in render_products: self.log.debug("Found render product: %s" % product) - filenames = list(render_products) - instance.data["files"] = filenames - self.log.debug("files:{}".format(render_products)) + filenames = list(render_products) + instance.data["files"] = filenames + instance.data["renderProducts"] = colorspace.ARenderProduct() # For now by default do NOT try to publish the rendered output instance.data["publishJobState"] = "Suspended" @@ -110,6 +71,12 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): instance.data["expectedFiles"].append(files_by_aov) self.log.debug("expectedFiles:{}".format(files_by_aov)) + # update the colorspace data + colorspace_data = get_color_management_preferences() + instance.data["colorspaceConfig"] = colorspace_data["config"] + instance.data["colorspaceDisplay"] = colorspace_data["display"] + instance.data["colorspaceView"] = colorspace_data["view"] + def get_beauty_render_product(self, prefix, suffix=""): """Return the beauty output filename if render element enabled """ @@ -144,9 +111,10 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin): file = os.path.basename(path) if "#" in file: - pparts = file.split("#") - padding = "%0{}d".format(len(pparts) - 1) - file = pparts[0] + padding + pparts[-1] + def replace(match): + return "%0{}d".format(len(match.group())) + + file = re.sub("#+", replace, file) if "%" not in file: return path From f1aff5bcf2cd5ed167651d8946825b6be4c734a8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 27 Apr 2023 21:24:38 +0800 Subject: [PATCH 078/347] hound fix --- openpype/hosts/houdini/api/colorspace.py | 1 + openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py | 2 +- openpype/hosts/houdini/plugins/publish/collect_karma_rop.py | 2 +- openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py | 2 +- openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py | 2 +- openpype/hosts/houdini/plugins/publish/collect_vray_rop.py | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/api/colorspace.py b/openpype/hosts/houdini/api/colorspace.py index b615bc199c..7047644225 100644 --- a/openpype/hosts/houdini/api/colorspace.py +++ b/openpype/hosts/houdini/api/colorspace.py @@ -21,6 +21,7 @@ class RenderProduct(object): view = attr.ib() productName = attr.ib(default=None) + class ARenderProduct(object): def __init__(self): diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py index 7ec4ead968..2fd419ef9b 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -8,7 +8,7 @@ from openpype.hosts.houdini.api.lib import ( evalParmNoFrame, get_color_management_preferences ) -from openpype.hosts.houdini.api import( +from openpype.hosts.houdini.api import ( colorspace ) diff --git a/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py b/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py index 11747a4100..b87bb06767 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py @@ -8,7 +8,7 @@ from openpype.hosts.houdini.api.lib import ( evalParmNoFrame, get_color_management_preferences ) -from openpype.hosts.houdini.api import( +from openpype.hosts.houdini.api import ( colorspace ) diff --git a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py index 9c9a15f8f7..c4460f5350 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_mantra_rop.py @@ -8,7 +8,7 @@ from openpype.hosts.houdini.api.lib import ( evalParmNoFrame, get_color_management_preferences ) -from openpype.hosts.houdini.api import( +from openpype.hosts.houdini.api import ( colorspace ) diff --git a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py index ab00e3c339..dbb15ab88f 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_redshift_rop.py @@ -8,7 +8,7 @@ from openpype.hosts.houdini.api.lib import ( evalParmNoFrame, get_color_management_preferences ) -from openpype.hosts.houdini.api import( +from openpype.hosts.houdini.api import ( colorspace ) diff --git a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py index aa05cf6736..d4fe37f993 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_vray_rop.py @@ -8,7 +8,7 @@ from openpype.hosts.houdini.api.lib import ( evalParmNoFrame, get_color_management_preferences ) -from openpype.hosts.houdini.api import( +from openpype.hosts.houdini.api import ( colorspace ) From b230da486112d08698a5437ddb2f70c88ece773d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 28 Apr 2023 10:56:32 +0200 Subject: [PATCH 079/347] :recycle: change container to custom attrib modifier --- openpype/hosts/max/api/plugin.py | 93 +++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index b54568b360..213d6c04e0 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """3dsmax specific Avalon/Pyblish plugin definitions.""" from pymxs import runtime as rt +from typing import Union import six from abc import ABCMeta from openpype.pipeline import ( @@ -12,6 +13,59 @@ from openpype.lib import BoolDef from .lib import imprint, read, lsattr +MS_CUSTOM_ATTRIB = """attributes "openPypeData" +( + parameters main rollout:OPparams + ( + all_handles type:#maxObjectTab tabSize:0 tabSizeVariable:on + ) + + rollout OPparams "OP Parameters" + ( + listbox list_node "Node References" items:#() + button button_add "Add Selection" + + fn node_to_name the_node = + ( + handle = the_node.handle + obj_name = the_node.name + handle_name = obj_name + "<" + handle as string + ">" + return handle_name + ) + + on button_add pressed do + ( + current_selection = selectByName title:"Select Objects To Add To Container" buttontext:"Add" + temp_arr = #() + i_node_arr = #() + for c in current_selection do + ( + handle_name = node_to_name c + node_ref = NodeTransformMonitor node:c + append temp_arr handle_name + append i_node_arr node_ref + ) + all_handles = i_node_arr + list_node.items = temp_arr + ) + + on OPparams open do + ( + if all_handles.count != 0 do + ( + temp_arr = #() + for x in all_handles do + ( + print(x.node) + handle_name = node_to_name x.node + append temp_arr handle_name + ) + list_node.items = temp_arr + ) + ) + ) +)""" + class OpenPypeCreatorError(CreatorError): pass @@ -33,15 +87,25 @@ class MaxCreatorBase(object): return shared_data @staticmethod - def create_instance_node(node_name: str, parent: str = ""): - parent_node = rt.getNodeByName(parent) if parent else rt.rootScene - if not parent_node: - raise OpenPypeCreatorError(f"Specified parent {parent} not found") + def create_instance_node(node): + """Create instance node. - container = rt.container(name=node_name) - container.Parent = parent_node + If the supplied node is existing node, it will be used to hold the + instance, otherwise new node of type Dummy will be created. - return container + Args: + node (rt.MXSWrapperBase, str): Node or node name to use. + + Returns: + instance + """ + if isinstance(node, str): + node = rt.dummy(name=node) + + attrs = rt.execute(MS_CUSTOM_ATTRIB) + rt.custAttributes.add(node.baseObject, attrs) + + return node @six.add_metaclass(ABCMeta) @@ -60,8 +124,11 @@ class MaxCreator(Creator, MaxCreatorBase): instance_data, self ) - for node in self.selected_nodes: - node.Parent = instance_node + if pre_create_data.get("use_selection"): + print("adding selection") + print(rt.array(*self.selected_nodes)) + instance_node.openPypeData.all_nodes = rt.array( + *self.selected_nodes) self._add_instance_to_context(instance) imprint(instance_node.name, instance.data_to_store()) @@ -98,11 +165,11 @@ class MaxCreator(Creator, MaxCreatorBase): """ for instance in instances: - instance_node = rt.getNodeByName( - instance.data.get("instance_node")) - if instance_node: + if instance_node := rt.getNodeByName( + instance.data.get("instance_node") + ): rt.select(instance_node) - rt.execute(f'for o in selection do for c in o.children do c.parent = undefined') # noqa + rt.execute("for o in selection do for c in o.children do c.parent = undefined") # noqa rt.delete(instance_node) self._remove_instance_from_context(instance) From 63c7c1c2e9ed8002dff9f703852e2d9b53c08a6e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 28 Apr 2023 12:43:34 +0200 Subject: [PATCH 080/347] :rotating_light: tabs to spaces --- openpype/hosts/max/api/plugin.py | 104 ++++++++++++++++--------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 213d6c04e0..15fcc89c5f 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -12,60 +12,61 @@ from openpype.pipeline import ( from openpype.lib import BoolDef from .lib import imprint, read, lsattr - MS_CUSTOM_ATTRIB = """attributes "openPypeData" ( - parameters main rollout:OPparams - ( - all_handles type:#maxObjectTab tabSize:0 tabSizeVariable:on - ) + parameters main rollout:OPparams + ( + all_handles type:#maxObjectTab tabSize:0 tabSizeVariable:on + ) - rollout OPparams "OP Parameters" - ( - listbox list_node "Node References" items:#() - button button_add "Add Selection" + rollout OPparams "OP Parameters" + ( + listbox list_node "Node References" items:#() + button button_add "Add Selection" - fn node_to_name the_node = - ( - handle = the_node.handle - obj_name = the_node.name - handle_name = obj_name + "<" + handle as string + ">" - return handle_name - ) + fn node_to_name the_node = + ( + handle = the_node.handle + obj_name = the_node.name + handle_name = obj_name + "<" + handle as string + ">" + return handle_name + ) - on button_add pressed do - ( - current_selection = selectByName title:"Select Objects To Add To Container" buttontext:"Add" - temp_arr = #() - i_node_arr = #() - for c in current_selection do - ( - handle_name = node_to_name c - node_ref = NodeTransformMonitor node:c - append temp_arr handle_name - append i_node_arr node_ref - ) - all_handles = i_node_arr - list_node.items = temp_arr - ) + on button_add pressed do + ( + current_selection = selectByName title:"Select Objects To Add To + Container" buttontext:"Add" + temp_arr = #() + i_node_arr = #() + for c in current_selection do + ( + handle_name = node_to_name c + node_ref = NodeTransformMonitor node:c + append temp_arr handle_name + append i_node_arr node_ref + ) + all_handles = i_node_arr + list_node.items = temp_arr + ) - on OPparams open do - ( - if all_handles.count != 0 do - ( - temp_arr = #() - for x in all_handles do - ( - print(x.node) - handle_name = node_to_name x.node - append temp_arr handle_name - ) - list_node.items = temp_arr - ) - ) - ) + on OPparams open do + ( + if all_handles.count != 0 do + ( + temp_arr = #() + for x in all_handles do + ( + print(x.node) + handle_name = node_to_name x.node + append temp_arr handle_name + ) + list_node.items = temp_arr + ) + ) + ) )""" + class OpenPypeCreatorError(CreatorError): pass @@ -83,7 +84,8 @@ class MaxCreatorBase(object): shared_data["max_cached_subsets"][creator_id] = [i.name] else: shared_data[ - "max_cached_subsets"][creator_id].append(i.name) # noqa + "max_cached_subsets"][creator_id].append( + i.name) # noqa return shared_data @staticmethod @@ -138,7 +140,7 @@ class MaxCreator(Creator, MaxCreatorBase): def collect_instances(self): self.cache_subsets(self.collection_shared_data) for instance in self.collection_shared_data[ - "max_cached_subsets"].get(self.identifier, []): + "max_cached_subsets"].get(self.identifier, []): created_instance = CreatedInstance.from_existing( read(rt.getNodeByName(instance)), self ) @@ -166,10 +168,12 @@ class MaxCreator(Creator, MaxCreatorBase): """ for instance in instances: if instance_node := rt.getNodeByName( - instance.data.get("instance_node") + instance.data.get("instance_node") ): rt.select(instance_node) - rt.execute("for o in selection do for c in o.children do c.parent = undefined") # noqa + rt.execute( + "for o in selection do for c in o.children do c.parent = " + "undefined") # noqa rt.delete(instance_node) self._remove_instance_from_context(instance) From 8e456f3f03ed2c976d2e959613b572a1360cbed3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 May 2023 21:19:25 +0200 Subject: [PATCH 081/347] changing structure of colorspace schemas - removing `set_ocio_config` - removing (imageio) from category label - adding global switch to colorspace management - File rules label with ocio v1 compatibility --- .../schema_project_aftereffects.json | 9 ++------- .../projects_schema/schema_project_blender.json | 9 ++------- .../projects_schema/schema_project_celaction.json | 4 ++-- .../projects_schema/schema_project_flame.json | 6 +++--- .../projects_schema/schema_project_fusion.json | 9 ++------- .../projects_schema/schema_project_global.json | 14 ++++++++++++-- .../projects_schema/schema_project_harmony.json | 4 ++-- .../projects_schema/schema_project_hiero.json | 9 ++------- .../projects_schema/schema_project_houdini.json | 9 ++------- .../projects_schema/schema_project_max.json | 9 ++------- .../projects_schema/schema_project_maya.json | 9 ++------- .../projects_schema/schema_project_photoshop.json | 4 ++-- .../projects_schema/schema_project_resolve.json | 4 ++-- .../schema_project_traypublisher.json | 4 ++-- .../projects_schema/schema_project_tvpaint.json | 4 ++-- .../projects_schema/schema_project_unreal.json | 9 ++------- .../schema_project_webpublisher.json | 4 ++-- .../schemas/schema_imageio_file_rules.json | 4 ++-- .../schemas/schema_nuke_imageio.json | 9 ++------- 19 files changed, 49 insertions(+), 84 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 148c1840e5..d9007d6185 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -8,19 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" - }, - { - "type": "boolean", - "key": "set_ocio_config", - "label": "Set OCIO config file in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index fe6ee94654..8997b750ec 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -8,19 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" - }, - { - "type": "boolean", - "key": "set_ocio_config", - "label": "Set OCIO config file in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json index ab3acaf4a2..23268d0d9a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json @@ -8,14 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index 5b96a49679..f18da95065 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -8,14 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" + "label": "Enable Color Management" }, { "type": "schema", @@ -368,7 +368,7 @@ }, { "key": "colorspace_out", - "label": "Output color (imageio)", + "label": "Output color", "type": "text", "default": "linear" }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index f97a3a3a40..b236925c1c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -8,19 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" - }, - { - "type": "boolean", - "key": "set_ocio_config", - "label": "Set OCIO config file in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_global.json b/openpype/settings/entities/schemas/projects_schema/schema_project_global.json index f200c1722f..80ea73267b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_global.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_global.json @@ -8,9 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "is_group": true, "children": [ + { + "type": "boolean", + "key": "activate_global_color_management", + "label": "Enable Color Management" + }, { "key": "ocio_config", "type": "dict", @@ -29,9 +34,14 @@ { "key": "file_rules", "type": "dict", - "label": "File Rules", + "label": "File Rules (OCIO v1 only)", "collapsible": true, "children": [ + { + "type": "boolean", + "key": "activate_global_file_rules", + "label": "Enable File Rules" + }, { "key": "rules", "label": "Rules", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json index 71f8cb4db2..840f1fa4c0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -8,14 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index a46611dc8b..e7bd91dcef 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -8,19 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" - }, - { - "type": "boolean", - "key": "set_ocio_config", - "label": "Set OCIO config file in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index d254b92269..1fb23759cb 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -8,19 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" - }, - { - "type": "boolean", - "key": "set_ocio_config", - "label": "Set OCIO config file in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index 1141cefb40..bc632d4a20 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -8,19 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" - }, - { - "type": "boolean", - "key": "set_ocio_config", - "label": "Set OCIO config file in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index b5366bb0a7..c69ccb3f07 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -48,19 +48,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" - }, - { - "type": "boolean", - "key": "set_ocio_config", - "label": "Set OCIO config file in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 4431e3d95f..c4bb81e7f8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -8,14 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json index 16de175933..ef1880af67 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json @@ -8,14 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index d3faf54ae1..a07c262375 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -8,14 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index a0d94ad7dc..1c1d75c4e6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -8,14 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index 2dedadc6dd..10f562508a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -8,19 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" - }, - { - "type": "boolean", - "key": "set_ocio_config", - "label": "Set OCIO config file in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json index f596c89686..bb4287d4b4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json @@ -8,14 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" + "label": "Enable Color Management" }, { "type": "schema", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json index e76c8a326f..62b72c2518 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json @@ -1,13 +1,13 @@ { "key": "file_rules", "type": "dict", - "label": "File Rules", + "label": "File Rules (OCIO v1 only)", "collapsible": true, "children": [ { "type": "boolean", "key": "override_global_rules", - "label": "Override global file rules" + "label": "Override global File Rules" }, { "key": "rules", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json index c69a4c4f4b..9eed442f25 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json @@ -1,19 +1,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management", "collapsible": true, "is_group": true, "children": [ { "type": "boolean", "key": "activate_host_color_management", - "label": "Enable Color Management in host" - }, - { - "type": "boolean", - "key": "set_ocio_config", - "label": "Set OCIO config file in host" + "label": "Enable Color Management" }, { "type": "schema", From bec1dd775d7be9b0d0051b2654288106ce527d6f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 May 2023 21:20:39 +0200 Subject: [PATCH 082/347] adding default settings --- .../settings/defaults/project_settings/aftereffects.json | 1 - openpype/settings/defaults/project_settings/blender.json | 1 - openpype/settings/defaults/project_settings/fusion.json | 1 - openpype/settings/defaults/project_settings/global.json | 6 +++++- openpype/settings/defaults/project_settings/hiero.json | 1 - openpype/settings/defaults/project_settings/houdini.json | 1 - openpype/settings/defaults/project_settings/max.json | 5 ++--- openpype/settings/defaults/project_settings/maya.json | 1 - openpype/settings/defaults/project_settings/nuke.json | 5 ++--- openpype/settings/defaults/project_settings/unreal.json | 1 - 10 files changed, 9 insertions(+), 14 deletions(-) diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json index c30356335b..74bd519bbd 100644 --- a/openpype/settings/defaults/project_settings/aftereffects.json +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -1,7 +1,6 @@ { "imageio": { "activate_host_color_management": true, - "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 1969cd8346..8328ceeda3 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -1,7 +1,6 @@ { "imageio": { "activate_host_color_management": true, - "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index ba2abd467f..a506f0c182 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -1,7 +1,6 @@ { "imageio": { "activate_host_color_management": true, - "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 3b7b29fe8b..dcc0fdb6b2 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -1,5 +1,6 @@ { "imageio": { + "activate_global_color_management": false, "ocio_config": { "filepath": [ "{OPENPYPE_ROOT}/vendor/bin/ocioconfig/OpenColorIOConfigs/aces_1.2/config.ocio", @@ -7,6 +8,7 @@ ] }, "file_rules": { + "activate_global_file_rules": false, "rules": { "example": { "pattern": ".*(beauty).*", @@ -250,7 +252,9 @@ } }, { - "families": ["review"], + "families": [ + "review" + ], "hosts": [ "maya", "houdini" diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index e876d1727d..01eb15bfbc 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -1,7 +1,6 @@ { "imageio": { "activate_host_color_management": true, - "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index dd3fc87b80..2b7192ff99 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -1,7 +1,6 @@ { "imageio": { "activate_host_color_management": true, - "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index 89ba7a702d..e69b64f6cf 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -1,7 +1,6 @@ { "imageio": { "activate_host_color_management": true, - "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] @@ -17,8 +16,8 @@ "image_format": "exr", "multipass": true }, - "PointCloud":{ - "attribute":{ + "PointCloud": { + "attribute": { "Age": "age", "Radius": "radius", "Position": "position", diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index c1ec473654..a31dffe4c2 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -411,7 +411,6 @@ }, "imageio": { "activate_host_color_management": true, - "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 119a240ad5..a1e2e5c15b 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -10,7 +10,6 @@ }, "imageio": { "activate_host_color_management": true, - "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] @@ -355,12 +354,12 @@ "optional": true, "active": true }, - "ValidateGizmo": { + "ValidateBackdrop": { "enabled": true, "optional": true, "active": true }, - "ValidateBackdrop": { + "ValidateGizmo": { "enabled": true, "optional": true, "active": true diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index eace5a3542..72acf17b9e 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -1,7 +1,6 @@ { "imageio": { "activate_host_color_management": true, - "set_ocio_config": false, "ocio_config": { "override_global_config": false, "filepath": [] From c129a7885170ce395e2399e9ce598aae72b5ba12 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 May 2023 21:32:28 +0200 Subject: [PATCH 083/347] remove set_ocio_config from global hook also clear the checking function from colorspace.py --- openpype/hooks/pre_ocio_hook.py | 15 +-------------- openpype/pipeline/colorspace.py | 22 ---------------------- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py index bcff31fc93..49a042caa8 100644 --- a/openpype/hooks/pre_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -1,8 +1,7 @@ from openpype.lib import PreLaunchHook from openpype.pipeline.colorspace import ( - get_imageio_config, - is_set_ocio_config_activated + get_imageio_config ) from openpype.pipeline.template_data import get_template_data_with_names @@ -42,18 +41,6 @@ class OCIOEnvHook(PreLaunchHook): ) if config_data: - set_config_path = is_set_ocio_config_activated( - project_name=self.data["project_name"], - host_name=self.host_name, - project_settings=self.data["project_settings"] - ) - if not set_config_path: - self.log.info( - "Setting of OCIO environment with " - "config path was not activated..." - ) - return - ocio_path = config_data["path"] self.log.info( diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index a1714bc75e..627d93153c 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -393,28 +393,6 @@ def get_imageio_config( return config_data -def is_set_ocio_config_activated( - project_name, host_name, project_settings=None -): - """Check if host OCIO config path is activated - - Args: - project_name (str): project name - host_name (str): host name - - Returns: - bool: True if activated - """ - project_settings = project_settings or get_project_settings(project_name) - - # get colorspace settings - _, imageio_host = _get_imageio_settings( - project_settings, host_name) - - # check if host settings is having set_ocio_config - return imageio_host.get("set_ocio_config", False) - - def _get_config_data(path_list, anatomy_data): """Return first existing path in path list. From e290d70584c085c0b968ae27687044060e103e52 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 May 2023 21:33:48 +0200 Subject: [PATCH 084/347] recognize global colorspace management switch --- openpype/pipeline/colorspace.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 627d93153c..ec793cd48b 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -346,11 +346,26 @@ def get_imageio_config( formatting_data["platform"] = platform.system().lower() # get colorspace settings + # check if global settings group is having activate_global_color_management + # key at all. If it does't then default it to False + # this is for backward compatibility only + # TODO: in future rewrite this to be more explicit imageio_global, imageio_host = _get_imageio_settings( project_settings, host_name) + activate_color_management = imageio_global.get( + "activate_global_color_management", False) + + if not activate_color_management: + # if global settings are disabled return False because + # it is expected that no colorspace management is needed + log.info( + "Colorspace management is disabled." + ) + return {} + # check if host settings group is having activate_host_color_management - # it it does not have activation key then default it to True so it uses + # if it does not have activation key then default it to True so it uses # global settings # this is for backward compatibility # TODO: in future rewrite this to be more explicit From e7cf7c0fecb8ce73fcf586606af0a817a38ddbd8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 2 May 2023 21:37:41 +0200 Subject: [PATCH 085/347] recognize global file rules switch --- openpype/pipeline/colorspace.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index ec793cd48b..652304ef33 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -357,7 +357,7 @@ def get_imageio_config( "activate_global_color_management", False) if not activate_color_management: - # if global settings are disabled return False because + # if global settings are disabled return empty dict because # it is expected that no colorspace management is needed log.info( "Colorspace management is disabled." @@ -477,6 +477,15 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): # get file rules from global and host_name frules_global = imageio_global["file_rules"] + activate_global_rules = frules_global.get( + "activate_global_file_rules", False) + + if not activate_global_rules: + log.info( + "Global File Rules are disabled." + ) + return {} + # host is optional, some might not have any settings frules_host = imageio_host.get("file_rules", {}) From 459a246948569451a16b00fecd4d1a2e8e9d5f6c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 3 May 2023 17:31:43 +0200 Subject: [PATCH 086/347] Adding labels to settings --- .../projects_schema/schema_project_aftereffects.json | 6 +++++- .../schemas/projects_schema/schema_project_blender.json | 6 +++++- .../schemas/projects_schema/schema_project_celaction.json | 6 +++++- .../schemas/projects_schema/schema_project_flame.json | 6 +++++- .../schemas/projects_schema/schema_project_fusion.json | 6 +++++- .../schemas/projects_schema/schema_project_global.json | 4 ++++ .../schemas/projects_schema/schema_project_harmony.json | 6 +++++- .../schemas/projects_schema/schema_project_hiero.json | 6 +++++- .../schemas/projects_schema/schema_project_houdini.json | 6 +++++- .../schemas/projects_schema/schema_project_max.json | 6 +++++- .../schemas/projects_schema/schema_project_maya.json | 6 +++++- .../schemas/projects_schema/schema_project_photoshop.json | 6 +++++- .../schemas/projects_schema/schema_project_resolve.json | 6 +++++- .../projects_schema/schema_project_traypublisher.json | 6 +++++- .../schemas/projects_schema/schema_project_tvpaint.json | 6 +++++- .../schemas/projects_schema/schema_project_unreal.json | 6 +++++- .../projects_schema/schema_project_webpublisher.json | 6 +++++- .../projects_schema/schemas/schema_nuke_imageio.json | 6 +++++- 18 files changed, 89 insertions(+), 17 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index d9007d6185..5da632a933 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (OCIO managed)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 8997b750ec..b15b508661 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (OCIO managed)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json index 23268d0d9a..5729f70e2f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (derived to OCIO)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index f18da95065..625780a650 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (remapped to OCIO)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index b236925c1c..1e26e7d701 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (OCIO managed)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_global.json b/openpype/settings/entities/schemas/projects_schema/schema_project_global.json index 80ea73267b..d1d7f336e1 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_global.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_global.json @@ -11,6 +11,10 @@ "label": "Color Management", "is_group": true, "children": [ + { + "type": "label", + "label": "It's important to note that once color management is activated on a project, all hosts will be color managed by default.
The OpenColorIO (OCIO) config file is used either from the global settings or from the host's overrides. It's worth
noting that the order of the defined configuration paths matters, with higher priority given to paths listed earlier in
the configuration list.

To avoid potential issues, ensure that the OCIO configuration path is not an absolute path and includes at least
the root token (Anatomy). This helps ensure that the configuration path remains valid across different environments and
avoids any hard-coding of paths that may be specific to one particular system." + }, { "type": "boolean", "key": "activate_global_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json index 840f1fa4c0..0357a79aea 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (OCIO managed)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index e7bd91dcef..8c6be5d6d8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (OCIO managed)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 1fb23759cb..d50ebd948f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (OCIO managed)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index bc632d4a20..10a12dbecc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (OCIO managed)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index c69ccb3f07..49bd1002aa 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -48,10 +48,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (OCIO managed)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index c4bb81e7f8..898c3374d7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (remapped to OCIO)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json index ef1880af67..758cf2a196 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (remapped to OCIO)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures.." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index a07c262375..c234cd1b71 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (derived to OCIO)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index 1c1d75c4e6..6d446b5550 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (derived to OCIO)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index 10f562508a..2d0870f76a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (OCIO managed)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json index bb4287d4b4..e319182e3c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json @@ -8,10 +8,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (derived to OCIO)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing." + }, { "type": "boolean", "key": "activate_host_color_management", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json index 9eed442f25..7aeb3d32db 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json @@ -1,10 +1,14 @@ { "key": "imageio", "type": "dict", - "label": "Color Management", + "label": "Color Management (OCIO managed)", "collapsible": true, "is_group": true, "children": [ + { + "type": "label", + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + }, { "type": "boolean", "key": "activate_host_color_management", From ca3b9f5e3042b4bf25bdcb0ec95815eba3e2a5fb Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 3 May 2023 18:48:41 +0200 Subject: [PATCH 087/347] :art: set references on modifier --- openpype/hosts/max/api/plugin.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 15fcc89c5f..52da23dc0a 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -102,7 +102,7 @@ class MaxCreatorBase(object): instance """ if isinstance(node, str): - node = rt.dummy(name=node) + node = rt.container(name=node) attrs = rt.execute(MS_CUSTOM_ATTRIB) rt.custAttributes.add(node.baseObject, attrs) @@ -127,10 +127,15 @@ class MaxCreator(Creator, MaxCreatorBase): self ) if pre_create_data.get("use_selection"): - print("adding selection") - print(rt.array(*self.selected_nodes)) - instance_node.openPypeData.all_nodes = rt.array( - *self.selected_nodes) + + node_list = [] + for i in self.selected_nodes: + node_ref = rt.NodeTransformMonitor(node=i) + node_list.append(node_ref) + + # Setting the property + rt.setProperty( + instance_node.openPypeData, "all_handles", node_list) self._add_instance_to_context(instance) imprint(instance_node.name, instance.data_to_store()) @@ -171,9 +176,7 @@ class MaxCreator(Creator, MaxCreatorBase): instance.data.get("instance_node") ): rt.select(instance_node) - rt.execute( - "for o in selection do for c in o.children do c.parent = " - "undefined") # noqa + rt.custAttributes.add(instance_node.baseObject, "openPypeData") rt.delete(instance_node) self._remove_instance_from_context(instance) From 37ce7fbc09fc1546dbc3ecc5620e5ba31d17b161 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 3 May 2023 18:49:04 +0200 Subject: [PATCH 088/347] :art: add members collector --- .../max/plugins/publish/collect_members.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 openpype/hosts/max/plugins/publish/collect_members.py diff --git a/openpype/hosts/max/plugins/publish/collect_members.py b/openpype/hosts/max/plugins/publish/collect_members.py new file mode 100644 index 0000000000..0b50ba0d8f --- /dev/null +++ b/openpype/hosts/max/plugins/publish/collect_members.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +"""Collect instance members""" +import pyblish.api +from pymxs import runtime as rt + + +class CollectMembers(pyblish.api.InstancePlugin): + """Collect Render for Deadline""" + + order = pyblish.api.CollectorOrder + 0.01 + label = "Collect Instance Members" + hosts = ['max'] + + def process(self, instance): + + if instance.data.get("instance_node"): + container = rt.GetNodeByName(instance.data["instance_node"]) + instance.data["members"] = [i.node for i in container.openPypeData.all_handles] From b52527b55f48babb3f677bb4534ecdeaf21fd9f6 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 3 May 2023 18:49:43 +0200 Subject: [PATCH 089/347] :recycle: switch the use to collected instance members --- .../max/plugins/publish/extract_camera_abc.py | 15 +- .../max/plugins/publish/extract_camera_fbx.py | 14 +- .../plugins/publish/extract_max_scene_raw.py | 14 +- .../max/plugins/publish/extract_model.py | 15 +- .../max/plugins/publish/extract_model_fbx.py | 15 +- .../max/plugins/publish/extract_model_obj.py | 15 +- .../max/plugins/publish/extract_model_usd.py | 43 ++--- .../max/plugins/publish/extract_pointcache.py | 14 +- .../max/plugins/publish/extract_pointcloud.py | 167 +++++++++++------- .../publish/validate_camera_contents.py | 22 +-- .../publish/validate_model_contents.py | 22 ++- .../publish/validate_no_max_content.py | 3 +- .../plugins/publish/validate_pointcloud.py | 128 ++++++-------- .../plugins/publish/validate_usd_plugin.py | 13 +- 14 files changed, 231 insertions(+), 269 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py index 8c23ff9878..b4f294fbf9 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_abc.py @@ -6,8 +6,7 @@ from openpype.pipeline import ( ) from pymxs import runtime as rt from openpype.hosts.max.api import ( - maintained_selection, - get_all_children + maintained_selection ) @@ -29,8 +28,6 @@ class ExtractCameraAlembic(publish.Extractor, start = float(instance.data.get("frameStartHandle", 1)) end = float(instance.data.get("frameEndHandle", 1)) - container = instance.data["instance_node"] - self.log.info("Extracting Camera ...") stagingdir = self.staging_dir(instance) @@ -38,8 +35,7 @@ class ExtractCameraAlembic(publish.Extractor, path = os.path.join(stagingdir, filename) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (filename, - stagingdir)) + self.log.info(f"Writing alembic '{filename}' to '{stagingdir}'") export_cmd = ( f""" @@ -57,8 +53,8 @@ exportFile @"{path}" #noPrompt selectedOnly:on using:AlembicExport with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(export_cmd) + rt.Select(instance.data["members"]) + rt.Execute(export_cmd) self.log.info("Performing Extraction ...") if "representations" not in instance.data: @@ -71,5 +67,4 @@ exportFile @"{path}" #noPrompt selectedOnly:on using:AlembicExport "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - path)) + self.log.info(f"Extracted instance '{instance.name}' to: {path}") diff --git a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py index 7e92f355ed..ffbd281c67 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py @@ -6,8 +6,7 @@ from openpype.pipeline import ( ) from pymxs import runtime as rt from openpype.hosts.max.api import ( - maintained_selection, - get_all_children + maintained_selection ) @@ -26,15 +25,13 @@ class ExtractCameraFbx(publish.Extractor, def process(self, instance): if not self.is_active(instance.data): return - container = instance.data["instance_node"] self.log.info("Extracting Camera ...") stagingdir = self.staging_dir(instance) filename = "{name}.fbx".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info("Writing fbx file '%s' to '%s'" % (filename, - filepath)) + self.log.info(f"Writing fbx file '{filename}' to '{filepath}'") # Need to export: # Animation = True @@ -57,8 +54,8 @@ exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(fbx_export_cmd) + rt.Select(instance.data["members"]) + rt.Execute(fbx_export_cmd) self.log.info("Performing Extraction ...") if "representations" not in instance.data: @@ -71,5 +68,4 @@ exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info(f"Extracted instance '{instance.name}' to: {filepath}") diff --git a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py index c14fcdbd0b..ed98922462 100644 --- a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py +++ b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py @@ -6,8 +6,7 @@ from openpype.pipeline import ( ) from pymxs import runtime as rt from openpype.hosts.max.api import ( - maintained_selection, - get_all_children + maintained_selection ) @@ -28,7 +27,6 @@ class ExtractMaxSceneRaw(publish.Extractor, def process(self, instance): if not self.is_active(instance.data): return - container = instance.data["instance_node"] # publish the raw scene for camera self.log.info("Extracting Raw Max Scene ...") @@ -37,8 +35,7 @@ class ExtractMaxSceneRaw(publish.Extractor, filename = "{name}.max".format(**instance.data) max_path = os.path.join(stagingdir, filename) - self.log.info("Writing max file '%s' to '%s'" % (filename, - max_path)) + self.log.info(f"Writing max file '{filename}' to '{max_path}'") if "representations" not in instance.data: instance.data["representations"] = [] @@ -46,8 +43,8 @@ class ExtractMaxSceneRaw(publish.Extractor, # saving max scene with maintained_selection(): # need to figure out how to select the camera - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(f'saveNodes selection "{max_path}" quiet:true') + rt.Select(instance.data["members"]) + rt.Execute(f'saveNodes selection "{max_path}" quiet:true') self.log.info("Performing Extraction ...") @@ -58,5 +55,4 @@ class ExtractMaxSceneRaw(publish.Extractor, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - max_path)) + self.log.info(f"Extracted instance '{instance.name}' to: {max_path}") diff --git a/openpype/hosts/max/plugins/publish/extract_model.py b/openpype/hosts/max/plugins/publish/extract_model.py index 710ad5f97d..d0f7bb0410 100644 --- a/openpype/hosts/max/plugins/publish/extract_model.py +++ b/openpype/hosts/max/plugins/publish/extract_model.py @@ -6,8 +6,7 @@ from openpype.pipeline import ( ) from pymxs import runtime as rt from openpype.hosts.max.api import ( - maintained_selection, - get_all_children + maintained_selection ) @@ -27,8 +26,6 @@ class ExtractModel(publish.Extractor, if not self.is_active(instance.data): return - container = instance.data["instance_node"] - self.log.info("Extracting Geometry ...") stagingdir = self.staging_dir(instance) @@ -36,8 +33,7 @@ class ExtractModel(publish.Extractor, filepath = os.path.join(stagingdir, filename) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (filename, - stagingdir)) + self.log.info(f"Writing alembic '{filename}' to '{stagingdir}'") export_cmd = ( f""" @@ -56,8 +52,8 @@ exportFile @"{filepath}" #noPrompt selectedOnly:on using:AlembicExport with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(export_cmd) + rt.Select(instance.data["members"]) + rt.Execute(export_cmd) self.log.info("Performing Extraction ...") if "representations" not in instance.data: @@ -70,5 +66,4 @@ exportFile @"{filepath}" #noPrompt selectedOnly:on using:AlembicExport "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info(f"Extracted instance '{instance.name}' to: {filepath}") diff --git a/openpype/hosts/max/plugins/publish/extract_model_fbx.py b/openpype/hosts/max/plugins/publish/extract_model_fbx.py index ce58e8cc17..696974a703 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_model_fbx.py @@ -6,8 +6,7 @@ from openpype.pipeline import ( ) from pymxs import runtime as rt from openpype.hosts.max.api import ( - maintained_selection, - get_all_children + maintained_selection ) @@ -27,16 +26,13 @@ class ExtractModelFbx(publish.Extractor, if not self.is_active(instance.data): return - container = instance.data["instance_node"] - self.log.info("Extracting Geometry ...") stagingdir = self.staging_dir(instance) filename = "{name}.fbx".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info("Writing FBX '%s' to '%s'" % (filepath, - stagingdir)) + self.log.info(f"Writing FBX '{filepath}' to '{stagingdir}'") export_fbx_cmd = ( f""" @@ -56,8 +52,8 @@ exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(export_fbx_cmd) + rt.Select(instance.data["members"]) + rt.Execute(export_fbx_cmd) self.log.info("Performing Extraction ...") if "representations" not in instance.data: @@ -70,5 +66,4 @@ exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info(f"Extracted instance '{instance.name}' to: {filepath}") diff --git a/openpype/hosts/max/plugins/publish/extract_model_obj.py b/openpype/hosts/max/plugins/publish/extract_model_obj.py index 7bda237880..93896eea02 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_obj.py +++ b/openpype/hosts/max/plugins/publish/extract_model_obj.py @@ -6,8 +6,7 @@ from openpype.pipeline import ( ) from pymxs import runtime as rt from openpype.hosts.max.api import ( - maintained_selection, - get_all_children + maintained_selection ) @@ -27,21 +26,18 @@ class ExtractModelObj(publish.Extractor, if not self.is_active(instance.data): return - container = instance.data["instance_node"] - self.log.info("Extracting Geometry ...") stagingdir = self.staging_dir(instance) filename = "{name}.obj".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info("Writing OBJ '%s' to '%s'" % (filepath, - stagingdir)) + self.log.info(f"Writing OBJ '{filepath}' to '{stagingdir}'") with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(f'exportFile @"{filepath}" #noPrompt selectedOnly:true using:ObjExp') # noqa + rt.Select(instance.data["members"]) + rt.Execute(f'exportFile @"{filepath}" #noPrompt selectedOnly:true using:ObjExp') # noqa self.log.info("Performing Extraction ...") if "representations" not in instance.data: @@ -55,5 +51,4 @@ class ExtractModelObj(publish.Extractor, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info(f"Extracted instance '{instance.name}' to: {filepath}") diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index 0bed2d855e..ae250cae5a 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -26,31 +26,27 @@ class ExtractModelUSD(publish.Extractor, if not self.is_active(instance.data): return - container = instance.data["instance_node"] - self.log.info("Extracting Geometry ...") stagingdir = self.staging_dir(instance) asset_filename = "{name}.usda".format(**instance.data) asset_filepath = os.path.join(stagingdir, asset_filename) - self.log.info("Writing USD '%s' to '%s'" % (asset_filepath, - stagingdir)) + self.log.info(f"Writing USD '{asset_filepath}' to '{stagingdir}'") log_filename = "{name}.txt".format(**instance.data) log_filepath = os.path.join(stagingdir, log_filename) - self.log.info("Writing log '%s' to '%s'" % (log_filepath, - stagingdir)) + self.log.info(f"Writing log '{log_filepath}' to '{stagingdir}'") # get the nodes which need to be exported export_options = self.get_export_options(log_filepath) with maintained_selection(): # select and export - node_list = self.get_node_list(container) + node_list = instance.data["members"] rt.USDExporter.ExportFile(asset_filepath, exportOptions=export_options, - contentSource=rt.name("selected"), + contentSource=rt.Name("selected"), nodeList=node_list) self.log.info("Performing Extraction ...") @@ -73,25 +69,10 @@ class ExtractModelUSD(publish.Extractor, } instance.data["representations"].append(log_representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - asset_filepath)) + self.log.info(f"Extracted instance '{instance.name}' to: {asset_filepath}") - def get_node_list(self, container): - """ - Get the target nodes which are - the children of the container - """ - node_list = [] - - container_node = rt.getNodeByName(container) - target_node = container_node.Children - rt.select(target_node) - for sel in rt.selection: - node_list.append(sel) - - return node_list - - def get_export_options(self, log_path): + @staticmethod + def get_export_options(log_path): """Set Export Options for USD Exporter""" export_options = rt.USDExporter.createOptions() @@ -101,13 +82,13 @@ class ExtractModelUSD(publish.Extractor, export_options.Lights = False export_options.Cameras = False export_options.Materials = False - export_options.MeshFormat = rt.name('fromScene') - export_options.FileFormat = rt.name('ascii') - export_options.UpAxis = rt.name('y') - export_options.LogLevel = rt.name('info') + export_options.MeshFormat = rt.Name('fromScene') + export_options.FileFormat = rt.Name('ascii') + export_options.UpAxis = rt.Name('y') + export_options.LogLevel = rt.Name('info') export_options.LogPath = log_path export_options.PreserveEdgeOrientation = True - export_options.TimeMode = rt.name('current') + export_options.TimeMode = rt.Name('current') rt.USDexporter.UIOptions = export_options diff --git a/openpype/hosts/max/plugins/publish/extract_pointcache.py b/openpype/hosts/max/plugins/publish/extract_pointcache.py index 75d8a7972c..400d55d198 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcache.py @@ -42,8 +42,7 @@ import pyblish.api from openpype.pipeline import publish from pymxs import runtime as rt from openpype.hosts.max.api import ( - maintained_selection, - get_all_children + maintained_selection ) @@ -57,17 +56,14 @@ class ExtractAlembic(publish.Extractor): start = float(instance.data.get("frameStartHandle", 1)) end = float(instance.data.get("frameEndHandle", 1)) - container = instance.data["instance_node"] - self.log.info("Extracting pointcache ...") parent_dir = self.staging_dir(instance) file_name = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, file_name) - # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (file_name, - parent_dir)) + self.log.info( + f"Writing alembic '{file_name}' to '{parent_dir}'") abc_export_cmd = ( f""" @@ -85,8 +81,8 @@ exportFile @"{path}" #noPrompt selectedOnly:on using:AlembicExport with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(abc_export_cmd) + rt.Select(instance.data["members"]) + rt.Execute(abc_export_cmd) if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index e8d58ab713..5a85915967 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -9,13 +9,6 @@ from openpype.settings import get_project_settings from openpype.pipeline import legacy_io -def get_setting(project_setting=None): - project_setting = get_project_settings( - legacy_io.Session["AVALON_PROJECT"] - ) - return (project_setting["max"]["PointCloud"]) - - class ExtractPointCloud(publish.Extractor): """ Extract PRT format with tyFlow operators @@ -24,19 +17,20 @@ class ExtractPointCloud(publish.Extractor): Currently only works for the default partition setting Args: - export_particle(): sets up all job arguments for attributes - to be exported in MAXscript + self.export_particle(): sets up all job arguments for attributes + to be exported in MAXscript - get_operators(): get the export_particle operator + self.get_operators(): get the export_particle operator - get_custom_attr(): get all custom channel attributes from Openpype - setting and sets it as job arguments before exporting + self.get_custom_attr(): get all custom channel attributes from Openpype + setting and sets it as job arguments before exporting - get_files(): get the files with tyFlow naming convention - before publishing + self.get_files(): get the files with tyFlow naming convention + before publishing - partition_output_name(): get the naming with partition settings. - get_partition(): get partition value + self.partition_output_name(): get the naming with partition settings. + + self.get_partition(): get partition value """ @@ -46,6 +40,7 @@ class ExtractPointCloud(publish.Extractor): families = ["pointcloud"] def process(self, instance): + self.settings = self.get_setting(instance) start = int(instance.context.data.get("frameStart")) end = int(instance.context.data.get("frameEnd")) container = instance.data["instance_node"] @@ -56,12 +51,12 @@ class ExtractPointCloud(publish.Extractor): path = os.path.join(stagingdir, filename) with maintained_selection(): - job_args = self.export_particle(container, + job_args = self.export_particle(instance.data["members"], start, end, path) for job in job_args: - rt.execute(job) + rt.Execute(job) self.log.info("Performing Extraction ...") if "representations" not in instance.data: @@ -69,7 +64,7 @@ class ExtractPointCloud(publish.Extractor): self.log.info("Writing PRT with TyFlow Plugin...") filenames = self.get_files(container, path, start, end) - self.log.debug("filenames: {0}".format(filenames)) + self.log.debug(f"filenames: {filenames}") partition = self.partition_output_name(container) @@ -81,67 +76,87 @@ class ExtractPointCloud(publish.Extractor): "outputName": partition # partition value } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - path)) + self.log.info(f"Extracted instance '{instance.name}' to: {path}") def export_particle(self, - container, + members, start, end, filepath): + """Sets up all job arguments for attributes. + + Those attributes are to be exported in MAX Script. + + Args: + members (list): Member nodes of the instance. + start (int): Start frame. + end (int): End frame. + filepath (str): Path to PRT file. + + Returns: + list of arguments for MAX Script. + + """ job_args = [] - opt_list = self.get_operators(container) + opt_list = self.get_operators(members) for operator in opt_list: - start_frame = "{0}.frameStart={1}".format(operator, - start) + start_frame = f"{operator}.frameStart={start}" job_args.append(start_frame) - end_frame = "{0}.frameEnd={1}".format(operator, - end) + end_frame = f"{operator}.frameEnd={end}" job_args.append(end_frame) filepath = filepath.replace("\\", "/") - prt_filename = '{0}.PRTFilename="{1}"'.format(operator, - filepath) + prt_filename = f"{operator}.PRTFilename={filepath}" job_args.append(prt_filename) # Partition - mode = "{0}.PRTPartitionsMode=2".format(operator) + mode = f"{operator}.PRTPartitionsMode=2" job_args.append(mode) additional_args = self.get_custom_attr(operator) - for args in additional_args: - job_args.append(args) - - prt_export = "{0}.exportPRT()".format(operator) + job_args.extend(iter(additional_args)) + prt_export = f"{operator}.exportPRT()" job_args.append(prt_export) return job_args - def get_operators(self, container): - """Get Export Particles Operator""" + @staticmethod + def get_operators(members): + """Get Export Particles Operator. + Args: + members (list): Instance members. + + Returns: + list of particle operators + + """ opt_list = [] - node = rt.getNodebyName(container) - selection_list = list(node.Children) - for sel in selection_list: - obj = sel.baseobject + for member in members: + obj = member.baseobject # TODO: to see if it can be used maxscript instead - anim_names = rt.getsubanimnames(obj) + anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: - sub_anim = rt.getsubanim(obj, anim_name) - boolean = rt.isProperty(sub_anim, "Export_Particles") - event_name = sub_anim.name + sub_anim = rt.GetSubAnim(obj, anim_name) + boolean = rt.IsProperty(sub_anim, "Export_Particles") if boolean: - opt = "${0}.{1}.export_particles".format(sel.name, - event_name) + event_name = sub_anim.Name + opt = f"${member.Name}.{event_name}.export_particles" opt_list.append(opt) return opt_list + @staticmethod + def get_setting(instance): + project_setting = get_project_settings( + instance.context.data["projectName"] + ) + return project_setting["max"]["PointCloud"] + def get_custom_attr(self, operator): """Get Custom Attributes""" custom_attr_list = [] - attr_settings = get_setting()["attribute"] + attr_settings = self.settings["attribute"] for key, value in attr_settings.items(): custom_attr = "{0}.PRTChannels_{1}=True".format(operator, value) @@ -157,14 +172,25 @@ class ExtractPointCloud(publish.Extractor): path, start_frame, end_frame): - """ - Note: - Set the filenames accordingly to the tyFlow file - naming extension for the publishing purpose + """Get file names for tyFlow. - Actual File Output from tyFlow: + Set the filenames accordingly to the tyFlow file + naming extension for the publishing purpose + + Actual File Output from tyFlow:: __partof..prt + e.g. tyFlow_cloth_CCCS_blobbyFill_001__part1of1_00004.prt + + Args: + container: Instance node. + path (str): Output directory. + start_frame (int): Start frame. + end_frame (int): End frame. + + Returns: + list of filenames + """ filenames = [] filename = os.path.basename(path) @@ -181,27 +207,36 @@ class ExtractPointCloud(publish.Extractor): return filenames def partition_output_name(self, container): - """ - Notes: - Partition output name set for mapping - the published file output + """Get partition output name. + + Partition output name set for mapping + the published file output. + + Todo: + Customizes the setting for the output. + + Args: + container: Instance node. + + Returns: + str: Partition name. - todo: - Customizes the setting for the output """ partition_count, partition_start = self.get_partition(container) - partition = "_part{:03}of{}".format(partition_start, - partition_count) - - return partition + return f"_part{partition_start:03}of{partition_count}" def get_partition(self, container): - """ - Get Partition Value + """Get Partition value. + + Args: + container: Instance node. + """ opt_list = self.get_operators(container) + # TODO: This looks strange? Iterating over + # the opt_list but returning from inside? for operator in opt_list: - count = rt.execute(f'{operator}.PRTPartitionsCount') - start = rt.execute(f'{operator}.PRTPartitionsFrom') + count = rt.Execute(f'{operator}.PRTPartitionsCount') + start = rt.Execute(f'{operator}.PRTPartitionsFrom') return count, start diff --git a/openpype/hosts/max/plugins/publish/validate_camera_contents.py b/openpype/hosts/max/plugins/publish/validate_camera_contents.py index c81e28a61f..a858092abd 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_contents.py @@ -18,30 +18,24 @@ class ValidateCameraContent(pyblish.api.InstancePlugin): "$Physical_Camera", "$Target"] def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise PublishValidationError("Camera instance must only include" - "camera (and camera target)") + if invalid := self.get_invalid(instance): + raise PublishValidationError(("Camera instance must only include" + "camera (and camera target). " + f"Invalid content {invalid}")) def get_invalid(self, instance): """ Get invalid nodes if the instance is not camera """ - invalid = list() + invalid = [] container = instance.data["instance_node"] - self.log.info("Validating look content for " - "{}".format(container)) + self.log.info(f"Validating camera content for {container}") - con = rt.getNodeByName(container) - selection_list = list(con.Children) + selection_list = instance.data["members"] for sel in selection_list: # to avoid Attribute Error from pymxs wrapper sel_tmp = str(sel) - found = False - for cam in self.camera_type: - if sel_tmp.startswith(cam): - found = True - break + found = any(sel_tmp.startswith(cam) for cam in self.camera_type) if not found: self.log.error("Camera not found") invalid.append(sel) diff --git a/openpype/hosts/max/plugins/publish/validate_model_contents.py b/openpype/hosts/max/plugins/publish/validate_model_contents.py index dd782674ff..b21c0184b0 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_model_contents.py @@ -17,28 +17,26 @@ class ValidateModelContent(pyblish.api.InstancePlugin): label = "Model Contents" def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise PublishValidationError("Model instance must only include" - "Geometry and Editable Mesh") + if invalid := self.get_invalid(instance): + raise PublishValidationError(("Model instance must only include" + "Geometry and Editable Mesh. " + f"Invalid types on: {invalid}")) def get_invalid(self, instance): """ Get invalid nodes if the instance is not camera """ - invalid = list() + invalid = [] container = instance.data["instance_node"] - self.log.info("Validating look content for " - "{}".format(container)) + self.log.info(f"Validating model content for {container}") - con = rt.getNodeByName(container) - selection_list = list(con.Children) or rt.getCurrentSelection() + selection_list = instance.data["members"] for sel in selection_list: - if rt.classOf(sel) in rt.Camera.classes: + if rt.ClassOf(sel) in rt.Camera.classes: invalid.append(sel) - if rt.classOf(sel) in rt.Light.classes: + if rt.ClassOf(sel) in rt.Light.classes: invalid.append(sel) - if rt.classOf(sel) in rt.Shape.classes: + if rt.ClassOf(sel) in rt.Shape.classes: invalid.append(sel) return invalid diff --git a/openpype/hosts/max/plugins/publish/validate_no_max_content.py b/openpype/hosts/max/plugins/publish/validate_no_max_content.py index c20a1968ed..ba4a6882c2 100644 --- a/openpype/hosts/max/plugins/publish/validate_no_max_content.py +++ b/openpype/hosts/max/plugins/publish/validate_no_max_content.py @@ -18,6 +18,5 @@ class ValidateMaxContents(pyblish.api.InstancePlugin): label = "Max Scene Contents" def process(self, instance): - container = rt.getNodeByName(instance.data["instance_node"]) - if not list(container.Children): + if not instance.data["members"]: raise PublishValidationError("No content found in the container") diff --git a/openpype/hosts/max/plugins/publish/validate_pointcloud.py b/openpype/hosts/max/plugins/publish/validate_pointcloud.py index f654058648..38d8226e41 100644 --- a/openpype/hosts/max/plugins/publish/validate_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/validate_pointcloud.py @@ -9,11 +9,11 @@ def get_setting(project_setting=None): project_setting = get_project_settings( legacy_io.Session["AVALON_PROJECT"] ) - return (project_setting["max"]["PointCloud"]) + return project_setting["max"]["PointCloud"] class ValidatePointCloud(pyblish.api.InstancePlugin): - """Validate that workfile was saved.""" + """Validate that work file was saved.""" order = pyblish.api.ValidatorOrder families = ["pointcloud"] @@ -34,39 +34,37 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): of export_particle operator """ - invalid = self.get_tyFlow_object(instance) - if invalid: - raise PublishValidationError("Non tyFlow object " - "found: {}".format(invalid)) - invalid = self.get_tyFlow_operator(instance) - if invalid: - raise PublishValidationError("tyFlow ExportParticle operator " - "not found: {}".format(invalid)) + report = [] + if invalid := self.get_tyflow_object(instance): + report.append(f"Non tyFlow object found: {invalid}") - invalid = self.validate_export_mode(instance) - if invalid: - raise PublishValidationError("The export mode is not at PRT") + if invalid := self.get_tyflow_operator(instance): + report.append( + f"tyFlow ExportParticle operator not found: {invalid}") - invalid = self.validate_partition_value(instance) - if invalid: - raise PublishValidationError("tyFlow Partition setting is " - "not at the default value") - invalid = self.validate_custom_attribute(instance) - if invalid: - raise PublishValidationError("Custom Attribute not found " - ":{}".format(invalid)) + if self.validate_export_mode(instance): + report.append("The export mode is not at PRT") - def get_tyFlow_object(self, instance): + if self.validate_partition_value(instance): + report.append(("tyFlow Partition setting is " + "not at the default value")) + + if invalid := self.validate_custom_attribute(instance): + report.append(("Custom Attribute not found " + f":{invalid}")) + + if report: + raise PublishValidationError + + def get_tyflow_object(self, instance): invalid = [] container = instance.data["instance_node"] - self.log.info("Validating tyFlow container " - "for {}".format(container)) + self.log.info(f"Validating tyFlow container for {container}") - con = rt.getNodeByName(container) - selection_list = list(con.Children) + selection_list = instance.data["members"] for sel in selection_list: sel_tmp = str(sel) - if rt.classOf(sel) in [rt.tyFlow, + if rt.ClassOf(sel) in [rt.tyFlow, rt.Editable_Mesh]: if "tyFlow" not in sel_tmp: invalid.append(sel) @@ -75,23 +73,20 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): return invalid - def get_tyFlow_operator(self, instance): + def get_tyflow_operator(self, instance): invalid = [] container = instance.data["instance_node"] - self.log.info("Validating tyFlow object " - "for {}".format(container)) - - con = rt.getNodeByName(container) - selection_list = list(con.Children) + self.log.info(f"Validating tyFlow object for {container}") + selection_list = instance.data["members"] bool_list = [] for sel in selection_list: obj = sel.baseobject - anim_names = rt.getsubanimnames(obj) + anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: # get all the names of the related tyFlow nodes - sub_anim = rt.getsubanim(obj, anim_name) + sub_anim = rt.GetSubAnim(obj, anim_name) # check if there is export particle operator - boolean = rt.isProperty(sub_anim, "Export_Particles") + boolean = rt.IsProperty(sub_anim, "Export_Particles") bool_list.append(str(boolean)) # if the export_particles property is not there # it means there is not a "Export Particle" operator @@ -104,21 +99,18 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): def validate_custom_attribute(self, instance): invalid = [] container = instance.data["instance_node"] - self.log.info("Validating tyFlow custom " - "attributes for {}".format(container)) + self.log.info( + f"Validating tyFlow custom attributes for {container}") - con = rt.getNodeByName(container) - selection_list = list(con.Children) + selection_list = instance.data["members"] for sel in selection_list: obj = sel.baseobject - anim_names = rt.getsubanimnames(obj) + anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: # get all the names of the related tyFlow nodes - sub_anim = rt.getsubanim(obj, anim_name) - # check if there is export particle operator - boolean = rt.isProperty(sub_anim, "Export_Particles") - event_name = sub_anim.name - if boolean: + sub_anim = rt.GetSubAnim(obj, anim_name) + if rt.IsProperty(sub_anim, "Export_Particles"): + event_name = sub_anim.name opt = "${0}.{1}.export_particles".format(sel.name, event_name) attributes = get_setting()["attribute"] @@ -126,39 +118,36 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): custom_attr = "{0}.PRTChannels_{1}".format(opt, value) try: - rt.execute(custom_attr) + rt.Execute(custom_attr) except RuntimeError: - invalid.add(key) + invalid.append(key) return invalid def validate_partition_value(self, instance): invalid = [] container = instance.data["instance_node"] - self.log.info("Validating tyFlow partition " - "value for {}".format(container)) + self.log.info( + f"Validating tyFlow partition value for {container}") - con = rt.getNodeByName(container) - selection_list = list(con.Children) + selection_list = instance.data["members"] for sel in selection_list: obj = sel.baseobject - anim_names = rt.getsubanimnames(obj) + anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: # get all the names of the related tyFlow nodes - sub_anim = rt.getsubanim(obj, anim_name) - # check if there is export particle operator - boolean = rt.isProperty(sub_anim, "Export_Particles") - event_name = sub_anim.name - if boolean: + sub_anim = rt.GetSubAnim(obj, anim_name) + if rt.IsProperty(sub_anim, "Export_Particles"): + event_name = sub_anim.name opt = "${0}.{1}.export_particles".format(sel.name, event_name) - count = rt.execute(f'{opt}.PRTPartitionsCount') + count = rt.Execute(f'{opt}.PRTPartitionsCount') if count != 100: invalid.append(count) - start = rt.execute(f'{opt}.PRTPartitionsFrom') + start = rt.Execute(f'{opt}.PRTPartitionsFrom') if start != 1: invalid.append(start) - end = rt.execute(f'{opt}.PRTPartitionsTo') + end = rt.Execute(f'{opt}.PRTPartitionsTo') if end != 1: invalid.append(end) @@ -167,24 +156,23 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): def validate_export_mode(self, instance): invalid = [] container = instance.data["instance_node"] - self.log.info("Validating tyFlow export " - "mode for {}".format(container)) + self.log.info( + f"Validating tyFlow export mode for {container}") - con = rt.getNodeByName(container) + con = rt.GetNodeByName(container) selection_list = list(con.Children) for sel in selection_list: obj = sel.baseobject - anim_names = rt.getsubanimnames(obj) + anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: # get all the names of the related tyFlow nodes - sub_anim = rt.getsubanim(obj, anim_name) + sub_anim = rt.GetSubAnim(obj, anim_name) # check if there is export particle operator - boolean = rt.isProperty(sub_anim, "Export_Particles") + boolean = rt.IsProperty(sub_anim, "Export_Particles") event_name = sub_anim.name if boolean: - opt = "${0}.{1}.export_particles".format(sel.name, - event_name) - export_mode = rt.execute(f'{opt}.exportMode') + opt = f"${sel.name}.{event_name}.export_particles" + export_mode = rt.Execute(f'{opt}.exportMode') if export_mode != 1: invalid.append(export_mode) diff --git a/openpype/hosts/max/plugins/publish/validate_usd_plugin.py b/openpype/hosts/max/plugins/publish/validate_usd_plugin.py index 747147020a..8f11d72567 100644 --- a/openpype/hosts/max/plugins/publish/validate_usd_plugin.py +++ b/openpype/hosts/max/plugins/publish/validate_usd_plugin.py @@ -14,21 +14,20 @@ class ValidateUSDPlugin(pyblish.api.InstancePlugin): label = "USD Plugin" def process(self, instance): - plugin_mgr = rt.pluginManager + plugin_mgr = rt.PluginManager plugin_count = plugin_mgr.pluginDllCount plugin_info = self.get_plugins(plugin_mgr, plugin_count) usd_import = "usdimport.dli" if usd_import not in plugin_info: - raise PublishValidationError("USD Plugin {}" - " not found".format(usd_import)) + raise PublishValidationError(f"USD Plugin {usd_import} not found") usd_export = "usdexport.dle" if usd_export not in plugin_info: - raise PublishValidationError("USD Plugin {}" - " not found".format(usd_export)) + raise PublishValidationError(f"USD Plugin {usd_export} not found") - def get_plugins(self, manager, count): - plugin_info_list = list() + @staticmethod + def get_plugins(manager, count): + plugin_info_list = [] for p in range(1, count + 1): plugin_info = manager.pluginDllName(p) plugin_info_list.append(plugin_info) From 7a0f18e27849b3a67db57f1af4905d58044488fe Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 3 May 2023 18:50:30 +0200 Subject: [PATCH 090/347] :rotating_light: Fix type case to match Python API --- openpype/hosts/max/api/lib.py | 38 +++++++++----------- openpype/hosts/max/api/lib_renderproducts.py | 4 +-- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index ad9a450cad..572d99c1cd 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -3,7 +3,7 @@ import json import six from pymxs import runtime as rt -from typing import Union +from typing import Union, Any, Dict import contextlib from openpype.pipeline.context_tools import ( @@ -16,15 +16,15 @@ JSON_PREFIX = "JSON::" def imprint(node_name: str, data: dict) -> bool: - node = rt.getNodeByName(node_name) + node = rt.GetNodeByName(node_name) if not node: return False for k, v in data.items(): if isinstance(v, (dict, list)): - rt.setUserProp(node, k, f'{JSON_PREFIX}{json.dumps(v)}') + rt.SetUserProp(node, k, f'{JSON_PREFIX}{json.dumps(v)}') else: - rt.setUserProp(node, k, v) + rt.SetUserProp(node, k, v) return True @@ -44,7 +44,7 @@ def lsattr( Returns: list of nodes. """ - root = rt.rootnode if root is None else rt.getNodeByName(root) + root = rt.RootNode if root is None else rt.GetNodeByName(root) def output_node(node, nodes): nodes.append(node) @@ -55,16 +55,16 @@ def lsattr( output_node(root, nodes) return [ n for n in nodes - if rt.getUserProp(n, attr) == value + if rt.GetUserProp(n, attr) == value ] if value else [ n for n in nodes - if rt.getUserProp(n, attr) + if rt.GetUserProp(n, attr) ] def read(container) -> dict: data = {} - props = rt.getUserPropBuffer(container) + props = rt.GetUserPropBuffer(container) # this shouldn't happen but let's guard against it anyway if not props: return data @@ -79,29 +79,25 @@ def read(container) -> dict: value = value.strip() if isinstance(value.strip(), six.string_types) and \ value.startswith(JSON_PREFIX): - try: + with contextlib.suppress(json.JSONDecodeError): value = json.loads(value[len(JSON_PREFIX):]) - except json.JSONDecodeError: - # not a json - pass - data[key.strip()] = value - data["instance_node"] = container.name + data["instance_node"] = container.Name return data @contextlib.contextmanager def maintained_selection(): - previous_selection = rt.getCurrentSelection() + previous_selection = rt.GetCurrentSelection() try: yield finally: if previous_selection: - rt.select(previous_selection) + rt.Select(previous_selection) else: - rt.select() + rt.Select() def get_all_children(parent, node_type=None): @@ -123,7 +119,7 @@ def get_all_children(parent, node_type=None): return children child_list = list_children(parent) - return ([x for x in child_list if rt.superClassOf(x) == node_type] + return ([x for x in child_list if rt.SuperClassOf(x) == node_type] if node_type else child_list) @@ -199,7 +195,7 @@ def reset_scene_resolution(): set_scene_resolution(width, height) -def get_frame_range() -> dict: +def get_frame_range() -> Union[Dict[str, Any], None]: """Get the current assets frame range and handles. Returns: @@ -242,7 +238,7 @@ def reset_frame_range(fps: bool = True): frame_start = frame_range["frameStart"] - int(frame_range["handleStart"]) frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"]) frange_cmd = f"animationRange = interval {frame_start} {frame_end}" - rt.execute(frange_cmd) + rt.Execute(frange_cmd) def set_context_setting(): @@ -270,5 +266,5 @@ def get_max_version(): #(25000, 62, 0, 25, 0, 0, 997, 2023, "") max_info[7] = max version date """ - max_info = rt.maxversion() + max_info = rt.MaxVersion() return max_info[7] diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 350eb97661..e42686020a 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -91,7 +91,7 @@ class RenderProducts(object): """Get all the Arnold AOVs""" aovs = [] - amw = rt.MaxtoAOps.AOVsManagerWindow() + amw = rt.MaxToAOps.AOVsManagerWindow() aov_mgr = rt.renderers.current.AOVManager # Check if there is any aov group set in AOV manager aov_group_num = len(aov_mgr.drivers) @@ -114,7 +114,7 @@ class RenderProducts(object): """Get all the render element output files. """ render_dirname = [] - render_elem = rt.maxOps.GetCurRenderElementMgr() + render_elem = rt.MaxOps.GetCurRenderElementMgr() render_elem_num = render_elem.NumRenderElements() # get render elements from the renders for i in range(render_elem_num): From 6d84969457f930e4020633ab6ecfbfcf47d22afc Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 3 May 2023 19:06:22 +0200 Subject: [PATCH 091/347] :rotating_light: fix Hound :dog: --- openpype/hosts/max/api/plugin.py | 13 +++++-------- .../hosts/max/plugins/publish/collect_members.py | 4 +++- .../hosts/max/plugins/publish/extract_model_usd.py | 3 ++- .../max/plugins/publish/validate_camera_contents.py | 2 +- .../max/plugins/publish/validate_model_contents.py | 2 +- .../max/plugins/publish/validate_pointcloud.py | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 52da23dc0a..39a17c29ef 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -144,10 +144,9 @@ class MaxCreator(Creator, MaxCreatorBase): def collect_instances(self): self.cache_subsets(self.collection_shared_data) - for instance in self.collection_shared_data[ - "max_cached_subsets"].get(self.identifier, []): + for instance in self.collection_shared_data["max_cached_subsets"].get(self.identifier, []): # noqa created_instance = CreatedInstance.from_existing( - read(rt.getNodeByName(instance)), self + read(rt.GetNodeByName(instance)), self ) self._add_instance_to_context(created_instance) @@ -172,12 +171,10 @@ class MaxCreator(Creator, MaxCreatorBase): """ for instance in instances: - if instance_node := rt.getNodeByName( - instance.data.get("instance_node") - ): - rt.select(instance_node) + if instance_node := rt.GetNodeByName(instance.data.get("instance_node")): # noqa + rt.Select(instance_node) rt.custAttributes.add(instance_node.baseObject, "openPypeData") - rt.delete(instance_node) + rt.Delete(instance_node) self._remove_instance_from_context(instance) diff --git a/openpype/hosts/max/plugins/publish/collect_members.py b/openpype/hosts/max/plugins/publish/collect_members.py index 0b50ba0d8f..4ceb6cdadf 100644 --- a/openpype/hosts/max/plugins/publish/collect_members.py +++ b/openpype/hosts/max/plugins/publish/collect_members.py @@ -15,4 +15,6 @@ class CollectMembers(pyblish.api.InstancePlugin): if instance.data.get("instance_node"): container = rt.GetNodeByName(instance.data["instance_node"]) - instance.data["members"] = [i.node for i in container.openPypeData.all_handles] + instance.data["members"] = [ + i.node for i in container.openPypeData.all_handles + ] diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index ae250cae5a..df1e7a4f02 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -69,7 +69,8 @@ class ExtractModelUSD(publish.Extractor, } instance.data["representations"].append(log_representation) - self.log.info(f"Extracted instance '{instance.name}' to: {asset_filepath}") + self.log.info( + f"Extracted instance '{instance.name}' to: {asset_filepath}") @staticmethod def get_export_options(log_path): diff --git a/openpype/hosts/max/plugins/publish/validate_camera_contents.py b/openpype/hosts/max/plugins/publish/validate_camera_contents.py index a858092abd..85be5d59fa 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_contents.py @@ -18,7 +18,7 @@ class ValidateCameraContent(pyblish.api.InstancePlugin): "$Physical_Camera", "$Target"] def process(self, instance): - if invalid := self.get_invalid(instance): + if invalid := self.get_invalid(instance): # noqa raise PublishValidationError(("Camera instance must only include" "camera (and camera target). " f"Invalid content {invalid}")) diff --git a/openpype/hosts/max/plugins/publish/validate_model_contents.py b/openpype/hosts/max/plugins/publish/validate_model_contents.py index b21c0184b0..1d834292b8 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_model_contents.py @@ -17,7 +17,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin): label = "Model Contents" def process(self, instance): - if invalid := self.get_invalid(instance): + if invalid := self.get_invalid(instance): # noqa raise PublishValidationError(("Model instance must only include" "Geometry and Editable Mesh. " f"Invalid types on: {invalid}")) diff --git a/openpype/hosts/max/plugins/publish/validate_pointcloud.py b/openpype/hosts/max/plugins/publish/validate_pointcloud.py index 38d8226e41..e3a6face07 100644 --- a/openpype/hosts/max/plugins/publish/validate_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/validate_pointcloud.py @@ -35,10 +35,10 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): """ report = [] - if invalid := self.get_tyflow_object(instance): + if invalid := self.get_tyflow_object(instance): # noqa report.append(f"Non tyFlow object found: {invalid}") - if invalid := self.get_tyflow_operator(instance): + if invalid := self.get_tyflow_operator(instance): # noqa report.append( f"tyFlow ExportParticle operator not found: {invalid}") @@ -49,7 +49,7 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): report.append(("tyFlow Partition setting is " "not at the default value")) - if invalid := self.validate_custom_attribute(instance): + if invalid := self.validate_custom_attribute(instance): # noqa report.append(("Custom Attribute not found " f":{invalid}")) From eb6f8c1abb68dc42d03635907e163b4a5da8833b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 3 May 2023 22:29:36 +0200 Subject: [PATCH 092/347] fixing typo --- .../schemas/projects_schema/schema_project_aftereffects.json | 2 +- .../schemas/projects_schema/schema_project_blender.json | 2 +- .../entities/schemas/projects_schema/schema_project_fusion.json | 2 +- .../schemas/projects_schema/schema_project_harmony.json | 2 +- .../entities/schemas/projects_schema/schema_project_hiero.json | 2 +- .../schemas/projects_schema/schema_project_houdini.json | 2 +- .../entities/schemas/projects_schema/schema_project_max.json | 2 +- .../entities/schemas/projects_schema/schema_project_maya.json | 2 +- .../entities/schemas/projects_schema/schema_project_unreal.json | 2 +- .../schemas/projects_schema/schemas/schema_nuke_imageio.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 5da632a933..ef09a71bda 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index b15b508661..c3eab6c3f0 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 1e26e7d701..6189da0e19 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json index 0357a79aea..a56f62c6d6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index 8c6be5d6d8..2c82e1a9ac 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index d50ebd948f..588e209718 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index 10a12dbecc..5dac8ee7e9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index 49bd1002aa..55f231e235 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -54,7 +54,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index 2d0870f76a..8c3ff71489 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json index 7aeb3d32db..f691518255 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json @@ -7,7 +7,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) confg path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." }, { "type": "boolean", From 1ec0b2aeac3ab160b96d02706177ccab016c29ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 4 May 2023 11:29:28 +0200 Subject: [PATCH 093/347] Update openpype/hosts/max/plugins/publish/extract_pointcloud.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/max/plugins/publish/extract_pointcloud.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index 5a85915967..1387d47563 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -132,7 +132,10 @@ class ExtractPointCloud(publish.Extractor): """ opt_list = [] for member in members: - obj = member.baseobject + node = rt.getNodeByName(member) + selection_list = list(node.Children) + for sel in selection_list: + obj = sel.baseobject # TODO: to see if it can be used maxscript instead anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: From 5ced9885b1a79ccb8ad068366b186681304dc781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 4 May 2023 11:29:36 +0200 Subject: [PATCH 094/347] Update openpype/hosts/max/plugins/publish/extract_pointcloud.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/max/plugins/publish/extract_pointcloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index 1387d47563..11fcdaaa38 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -137,7 +137,7 @@ class ExtractPointCloud(publish.Extractor): for sel in selection_list: obj = sel.baseobject # TODO: to see if it can be used maxscript instead - anim_names = rt.GetSubAnimNames(obj) + anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: sub_anim = rt.GetSubAnim(obj, anim_name) boolean = rt.IsProperty(sub_anim, "Export_Particles") From 4193b3c277992d6686a3f4fc009465b684a8a2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 4 May 2023 11:29:51 +0200 Subject: [PATCH 095/347] Update openpype/hosts/max/plugins/publish/extract_pointcloud.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/max/plugins/publish/extract_pointcloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index 11fcdaaa38..2063514455 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -138,7 +138,7 @@ class ExtractPointCloud(publish.Extractor): obj = sel.baseobject # TODO: to see if it can be used maxscript instead anim_names = rt.GetSubAnimNames(obj) - for anim_name in anim_names: + for anim_name in anim_names: sub_anim = rt.GetSubAnim(obj, anim_name) boolean = rt.IsProperty(sub_anim, "Export_Particles") if boolean: From 0065dc1056b8e5f548a18297b9f6858c06736d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 4 May 2023 11:29:58 +0200 Subject: [PATCH 096/347] Update openpype/hosts/max/plugins/publish/extract_pointcloud.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/max/plugins/publish/extract_pointcloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index 2063514455..97f0b32eca 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -139,8 +139,8 @@ class ExtractPointCloud(publish.Extractor): # TODO: to see if it can be used maxscript instead anim_names = rt.GetSubAnimNames(obj) for anim_name in anim_names: - sub_anim = rt.GetSubAnim(obj, anim_name) - boolean = rt.IsProperty(sub_anim, "Export_Particles") + sub_anim = rt.GetSubAnim(obj, anim_name) + boolean = rt.IsProperty(sub_anim, "Export_Particles") if boolean: event_name = sub_anim.Name opt = f"${member.Name}.{event_name}.export_particles" From 6923f692057511ef7de985aecce37156e72de50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 4 May 2023 11:30:08 +0200 Subject: [PATCH 097/347] Update openpype/hosts/max/plugins/publish/extract_pointcloud.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/max/plugins/publish/extract_pointcloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index 97f0b32eca..c04eef6604 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -141,7 +141,7 @@ class ExtractPointCloud(publish.Extractor): for anim_name in anim_names: sub_anim = rt.GetSubAnim(obj, anim_name) boolean = rt.IsProperty(sub_anim, "Export_Particles") - if boolean: + if boolean: event_name = sub_anim.Name opt = f"${member.Name}.{event_name}.export_particles" opt_list.append(opt) From 774672a0063e2ba3edd2f2e26492a6607169ce46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 4 May 2023 11:30:17 +0200 Subject: [PATCH 098/347] Update openpype/hosts/max/plugins/publish/extract_pointcloud.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/max/plugins/publish/extract_pointcloud.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index c04eef6604..74265da7fb 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -43,7 +43,6 @@ class ExtractPointCloud(publish.Extractor): self.settings = self.get_setting(instance) start = int(instance.context.data.get("frameStart")) end = int(instance.context.data.get("frameEnd")) - container = instance.data["instance_node"] self.log.info("Extracting PRT...") stagingdir = self.staging_dir(instance) From 8d78288ccd3f3783c228eed131936d845ee0e333 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 11:49:38 +0200 Subject: [PATCH 099/347] :heavy_plus_sign: bump hound flake8 version --- .hound.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.hound.yml b/.hound.yml index df9cdab64a..4e749ac2d8 100644 --- a/.hound.yml +++ b/.hound.yml @@ -1,3 +1,4 @@ flake8: + version: 6.0.0 enabled: true config_file: setup.cfg From fec92a6c34f9e25b01a8d0094b9f90f0080f8e9c Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 11:49:38 +0200 Subject: [PATCH 100/347] Revert ":heavy_plus_sign: bump hound flake8 version" This reverts commit 8d78288ccd3f3783c228eed131936d845ee0e333. --- .hound.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.hound.yml b/.hound.yml index 4e749ac2d8..df9cdab64a 100644 --- a/.hound.yml +++ b/.hound.yml @@ -1,4 +1,3 @@ flake8: - version: 6.0.0 enabled: true config_file: setup.cfg From 49f9822b3fb0bda3dcbf8a7352b2133b361a6f41 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 12:19:49 +0200 Subject: [PATCH 101/347] :recycle: try another linter --- .github/workflows/linting_pr.yml | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/linting_pr.yml diff --git a/.github/workflows/linting_pr.yml b/.github/workflows/linting_pr.yml new file mode 100644 index 0000000000..8cc822df6e --- /dev/null +++ b/.github/workflows/linting_pr.yml @@ -0,0 +1,36 @@ +name: 📇 Linting PR + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +jobs: + linting: + runs-on: ubuntu-latest + steps: + - name: Black + uses: microsoft/action-python@0.6.3 + with: + black: false + + - name: Bandit + uses: microsoft/action-python@0.6.3 + with: + bandit: true + + - name: Pylint + uses: microsoft/action-python@0.6.3 + with: + pylint: true + + - name: Pyright + uses: microsoft/action-python@0.6.3 + with: + pyright: true + + - name: Flake8 + uses: microsoft/action-python@0.6.3 + with: + flake8: true From c1349c6e94296af7e141cfe8f4e26e0df708b279 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 12:19:49 +0200 Subject: [PATCH 102/347] Revert ":recycle: try another linter" This reverts commit 49f9822b3fb0bda3dcbf8a7352b2133b361a6f41. --- .github/workflows/linting_pr.yml | 36 -------------------------------- 1 file changed, 36 deletions(-) delete mode 100644 .github/workflows/linting_pr.yml diff --git a/.github/workflows/linting_pr.yml b/.github/workflows/linting_pr.yml deleted file mode 100644 index 8cc822df6e..0000000000 --- a/.github/workflows/linting_pr.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: 📇 Linting PR - -on: - push: - branches: [ develop ] - pull_request: - branches: [ develop ] - -jobs: - linting: - runs-on: ubuntu-latest - steps: - - name: Black - uses: microsoft/action-python@0.6.3 - with: - black: false - - - name: Bandit - uses: microsoft/action-python@0.6.3 - with: - bandit: true - - - name: Pylint - uses: microsoft/action-python@0.6.3 - with: - pylint: true - - - name: Pyright - uses: microsoft/action-python@0.6.3 - with: - pyright: true - - - name: Flake8 - uses: microsoft/action-python@0.6.3 - with: - flake8: true From 27216b247bda55b9089896fe7494a1e54439201a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 12:43:48 +0200 Subject: [PATCH 103/347] :heavy_plus_sign: Test another linter --- .github/workflows/pr_linting.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/pr_linting.yml diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml new file mode 100644 index 0000000000..418e5d9379 --- /dev/null +++ b/.github/workflows/pr_linting.yml @@ -0,0 +1,13 @@ +name: 📇 Code Linting + +on: + push: + branches: [ develop ] + +jobs: + - linting: + - name: wemake-python-styleguide + uses: wemake-services/wemake-python-styleguide@master + with: + reporter: 'github-pr-review' + GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" From 0f7d552eb3a614ff799ace2aeb49427c7f84da27 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 12:45:00 +0200 Subject: [PATCH 104/347] :recycle: linter events --- .github/workflows/pr_linting.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 418e5d9379..3f53182be0 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -3,6 +3,8 @@ name: 📇 Code Linting on: push: branches: [ develop ] + pull_request: + branches: [ develop ] jobs: - linting: From 98a358b60ac47630b70f6c04e0a62c67fff92929 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 12:49:26 +0200 Subject: [PATCH 105/347] :art: configure linting --- .github/workflows/pr_linting.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 3f53182be0..94574779d1 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -7,9 +7,11 @@ on: branches: [ develop ] jobs: - - linting: - - name: wemake-python-styleguide - uses: wemake-services/wemake-python-styleguide@master + lint: + runs-on: ubuntu-latest + steps: + - name: Code Check + - uses: wemake-services/wemake-python-styleguide@master with: reporter: 'github-pr-review' GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" From 07dd4d0b0ade51aeff2e9f899daede0b0ed85e68 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 12:52:00 +0200 Subject: [PATCH 106/347] :recycle: another hit on GH action configuration --- .github/workflows/pr_linting.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 94574779d1..eb9afffcc2 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -7,11 +7,12 @@ on: branches: [ develop ] jobs: - lint: + linting: runs-on: ubuntu-latest steps: - name: Code Check - - uses: wemake-services/wemake-python-styleguide@master + uses: wemake-services/wemake-python-styleguide@master with: reporter: 'github-pr-review' + env: GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" From 3e495bc88131d4938230ea105a0a8f245e3ccf55 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 13:03:57 +0200 Subject: [PATCH 107/347] :art: set linting permissions and checkout --- .github/workflows/pr_linting.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index eb9afffcc2..1deeef4726 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -6,10 +6,15 @@ on: pull_request: branches: [ develop ] +permissions: + contents: read + pull-requests: write + jobs: linting: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v3 - name: Code Check uses: wemake-services/wemake-python-styleguide@master with: From 00a2db65a12ebc888d09df2d4d8f6ba81a1ea972 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 14:02:02 +0200 Subject: [PATCH 108/347] :recycle: fix container use in extractor --- .../hosts/max/plugins/publish/extract_pointcloud.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index 74265da7fb..ee9e9693e9 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -6,7 +6,6 @@ from openpype.hosts.max.api import ( maintained_selection ) from openpype.settings import get_project_settings -from openpype.pipeline import legacy_io class ExtractPointCloud(publish.Extractor): @@ -62,10 +61,12 @@ class ExtractPointCloud(publish.Extractor): instance.data["representations"] = [] self.log.info("Writing PRT with TyFlow Plugin...") - filenames = self.get_files(container, path, start, end) + filenames = self.get_files( + instance.data["members"][0], path, start, end) self.log.debug(f"filenames: {filenames}") - partition = self.partition_output_name(container) + partition = self.partition_output_name( + instance.data["members"][0]) representation = { 'name': 'prt', @@ -141,9 +142,9 @@ class ExtractPointCloud(publish.Extractor): sub_anim = rt.GetSubAnim(obj, anim_name) boolean = rt.IsProperty(sub_anim, "Export_Particles") if boolean: - event_name = sub_anim.Name - opt = f"${member.Name}.{event_name}.export_particles" - opt_list.append(opt) + event_name = sub_anim.Name + opt = f"${member.Name}.{event_name}.export_particles" + opt_list.append(opt) return opt_list From cb24f2a9ba8c08b27980fa06e2cb83a2599173a9 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 14:07:19 +0200 Subject: [PATCH 109/347] :art: configure linter --- .github/workflows/pr_linting.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/pr_linting.yml diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml new file mode 100644 index 0000000000..1deeef4726 --- /dev/null +++ b/.github/workflows/pr_linting.yml @@ -0,0 +1,23 @@ +name: 📇 Code Linting + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +permissions: + contents: read + pull-requests: write + +jobs: + linting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Code Check + uses: wemake-services/wemake-python-styleguide@master + with: + reporter: 'github-pr-review' + env: + GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" From e608e7c808c22a09c118b4acad14c66543342b7e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 14:57:28 +0200 Subject: [PATCH 110/347] :art: limit to changed files path --- .github/workflows/pr_linting.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 1deeef4726..1f6be64d31 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -15,9 +15,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - run: | + echo "_CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }} -- '*.py' | tr -s '\n' ' ' )" >> ${GITHUB_ENV} - name: Code Check uses: wemake-services/wemake-python-styleguide@master with: reporter: 'github-pr-review' + path: "${{ env._CHANGED_FILES }}" env: GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" From 5829f647e548ab46be77a4caf7d704297419bbf8 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 14:59:41 +0200 Subject: [PATCH 111/347] :art: limit --- .github/workflows/pr_linting.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 1f6be64d31..59ac48aeb8 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -6,6 +6,8 @@ on: pull_request: branches: [ develop ] + workflow_dispatch: + permissions: contents: read pull-requests: write @@ -19,6 +21,7 @@ jobs: echo "_CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }} -- '*.py' | tr -s '\n' ' ' )" >> ${GITHUB_ENV} - name: Code Check uses: wemake-services/wemake-python-styleguide@master + if: ${{ env._CHANGED_FILES }} with: reporter: 'github-pr-review' path: "${{ env._CHANGED_FILES }}" From 6fb6762f34c0b972e5061b5bfcae1755a6bba895 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 15:14:17 +0200 Subject: [PATCH 112/347] :recycle: limit using diff_context --- .github/workflows/pr_linting.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 59ac48aeb8..64b6bc0da5 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -17,13 +17,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - run: | - echo "_CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }} -- '*.py' | tr -s '\n' ' ' )" >> ${GITHUB_ENV} - name: Code Check uses: wemake-services/wemake-python-styleguide@master - if: ${{ env._CHANGED_FILES }} with: reporter: 'github-pr-review' - path: "${{ env._CHANGED_FILES }}" + filter: 'diff_context' env: GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" From da12ac16c56f4510309275ec764b708e1a62bdb0 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 15:32:50 +0200 Subject: [PATCH 113/347] :art: solving changed list --- .github/workflows/pr_linting.yml | 38 +++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 64b6bc0da5..975c83e677 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -8,19 +8,55 @@ on: workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number}} + cancel-in-progress: true + permissions: contents: read pull-requests: write jobs: + files_changed: + runs-on: ubuntu-latest + outputs: + changed_python: ${{ steps.changes.outputs.python }} + steps: + - uses: actions/checkout@master + if: github.event_name == 'push' + - uses: dorny/paths-filter@master + id: changes + with: + filters: | + cpp: + - ["lte/gateway/c/**", "orc8r/gateway/c/**", "lte/gateway/python/**"] + javascript: + - ["nms/**", "**/*.js"] + python: + - ["**/*.py"] + terraform: + - ["**/*.tf"] + linting: + needs: files_changed + if: ${{ needs.files_changed.outputs.changed_python == 'true' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + fetch-depth: 0 + - name: Get changed Python files + id: py-changes + run: | + echo "py_files_list=$(git diff --name-only --diff-filter=ACMRT \ + ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} \ + | grep .py$ | xargs)" >> $GITHUB_OUTPUT - name: Code Check uses: wemake-services/wemake-python-styleguide@master with: reporter: 'github-pr-review' - filter: 'diff_context' + path: ${{ steps.py-changes.outputs.py_files_list }} env: GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" From a3a996d5a56787fa88cf0d92a2c39a6bf0f31fbd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 15:42:04 +0200 Subject: [PATCH 114/347] :bug: fix checkout action --- .github/workflows/pr_linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 975c83e677..4f4f7c2585 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -22,7 +22,7 @@ jobs: outputs: changed_python: ${{ steps.changes.outputs.python }} steps: - - uses: actions/checkout@master + - uses: actions/checkout@v3 if: github.event_name == 'push' - uses: dorny/paths-filter@master id: changes From 65110ded4609f2b6d7a5ccf97bff097f91d370de Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 15:43:32 +0200 Subject: [PATCH 115/347] :bug: fix checkout action --- .github/workflows/pr_linting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 975c83e677..4f4f7c2585 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -22,7 +22,7 @@ jobs: outputs: changed_python: ${{ steps.changes.outputs.python }} steps: - - uses: actions/checkout@master + - uses: actions/checkout@v3 if: github.event_name == 'push' - uses: dorny/paths-filter@master id: changes From 5ef4f4fb131f569da9aa0b5db50c66c0a99f86e8 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 15:47:52 +0200 Subject: [PATCH 116/347] :bug: fix syntax --- .github/workflows/pr_linting.yml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 4f4f7c2585..58f52bb313 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -21,21 +21,15 @@ jobs: runs-on: ubuntu-latest outputs: changed_python: ${{ steps.changes.outputs.python }} - steps: - - uses: actions/checkout@v3 - if: github.event_name == 'push' - - uses: dorny/paths-filter@master - id: changes - with: - filters: | - cpp: - - ["lte/gateway/c/**", "orc8r/gateway/c/**", "lte/gateway/python/**"] - javascript: - - ["nms/**", "**/*.js"] - python: - - ["**/*.py"] - terraform: - - ["**/*.tf"] + steps: + - uses: actions/checkout@v3 + if: github.event_name == 'push' + - uses: dorny/paths-filter@master + id: changes + with: + filters: | + python: + - ["**/*.py"] linting: needs: files_changed From 3497b03b7fc8cffadf597908e7f4bdde60f1ee0e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 15:50:49 +0200 Subject: [PATCH 117/347] :bug: fix syntax --- .github/workflows/pr_linting.yml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 975c83e677..58f52bb313 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -21,21 +21,15 @@ jobs: runs-on: ubuntu-latest outputs: changed_python: ${{ steps.changes.outputs.python }} - steps: - - uses: actions/checkout@master - if: github.event_name == 'push' - - uses: dorny/paths-filter@master - id: changes - with: - filters: | - cpp: - - ["lte/gateway/c/**", "orc8r/gateway/c/**", "lte/gateway/python/**"] - javascript: - - ["nms/**", "**/*.js"] - python: - - ["**/*.py"] - terraform: - - ["**/*.tf"] + steps: + - uses: actions/checkout@v3 + if: github.event_name == 'push' + - uses: dorny/paths-filter@master + id: changes + with: + filters: | + python: + - ["**/*.py"] linting: needs: files_changed From 227075f1a918d3ddc7aea60a57f955558820171b Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 16:07:32 +0200 Subject: [PATCH 118/347] :art: add WPS to pre-commit --- .pre-commit-config.yaml | 9 +++++++++ poetry.lock | 24 +++++++++++++++++++++--- pyproject.toml | 1 + 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eec388924e..fe4c7e3da3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,3 +10,12 @@ repos: - id: check-added-large-files - id: no-commit-to-branch args: [ '--pattern', '^(?!((release|enhancement|feature|bugfix|documentation|tests|local|chore)\/[a-zA-Z0-9\-_]+)$).*' ] +- repo: local + hooks: + - id: flake8 + name: flake8 + description: WPS enforced flake8 + entry: flake8 + args: ["--config=setup.cfg"] + language: python + types: [python] diff --git a/poetry.lock b/poetry.lock index f71611cb6f..cbc4ac7b94 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "acre" @@ -1456,11 +1456,13 @@ python-versions = ">=3.6" files = [ {file = "lief-0.12.3-cp310-cp310-macosx_10_14_arm64.whl", hash = "sha256:66724f337e6a36cea1a9380f13b59923f276c49ca837becae2e7be93a2e245d9"}, {file = "lief-0.12.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:6d18aafa2028587c98f6d4387bec94346e92f2b5a8a5002f70b1cf35b1c045cc"}, + {file = "lief-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4f69d125caaa8d5ddb574f29cc83101e165ebea1a9f18ad042eb3544081a797"}, {file = "lief-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c078d6230279ffd3bca717c79664fb8368666f610b577deb24b374607936e9c1"}, {file = "lief-0.12.3-cp310-cp310-win32.whl", hash = "sha256:e3a6af926532d0aac9e7501946134513d63217bacba666e6f7f5a0b7e15ba236"}, {file = "lief-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:0750b72e3aa161e1fb0e2e7f571121ae05d2428aafd742ff05a7656ad2288447"}, {file = "lief-0.12.3-cp311-cp311-macosx_10_14_arm64.whl", hash = "sha256:b5c123cb99a7879d754c059e299198b34e7e30e3b64cf22e8962013db0099f47"}, {file = "lief-0.12.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8bc58fa26a830df6178e36f112cb2bbdd65deff593f066d2d51434ff78386ba5"}, + {file = "lief-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74ac6143ac6ccd813c9b068d9c5f1f9d55c8813c8b407387eb57de01c3db2d74"}, {file = "lief-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04eb6b70d646fb5bd6183575928ee23715550f161f2832cbcd8c6ff2071fb408"}, {file = "lief-0.12.3-cp311-cp311-win32.whl", hash = "sha256:7e2d0a53c403769b04adcf8df92e83c5e25f9103a052aa7f17b0a9cf057735fb"}, {file = "lief-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:7f6395c12ee1bc4a5162f567cba96d0c72dfb660e7902e84d4f3029daf14fe33"}, @@ -1480,6 +1482,7 @@ files = [ {file = "lief-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:b00667257b43e93d94166c959055b6147d46d302598f3ee55c194b40414c89cc"}, {file = "lief-0.12.3-cp39-cp39-macosx_10_14_arm64.whl", hash = "sha256:e6a1b5b389090d524621c2455795e1262f62dc9381bedd96f0cd72b878c4066d"}, {file = "lief-0.12.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:ae773196df814202c0c51056163a1478941b299512b09660a3c37be3c7fac81e"}, + {file = "lief-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:66ddf88917ec7b00752687c476bb2771dc8ec19bd7e4c0dcff1f8ef774cad4e9"}, {file = "lief-0.12.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:4a47f410032c63ac3be051d963d0337d6b47f0e94bfe8e946ab4b6c428f4d0f8"}, {file = "lief-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbd11367c2259bd1131a6c8755dcde33314324de5ea029227bfbc7d3755871e6"}, {file = "lief-0.12.3-cp39-cp39-win32.whl", hash = "sha256:2ce53e311918c3e5b54c815ef420a747208d2a88200c41cd476f3dd1eb876bcf"}, @@ -2352,7 +2355,7 @@ files = [ cffi = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx_rtd_theme"] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] @@ -3245,6 +3248,21 @@ files = [ [package.dependencies] six = "*" +[[package]] +name = "wemake-python-styleguide" +version = "0.0.1" +description = "Opinionated styleguide that we use in wemake.services projects" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "wemake-python-styleguide-0.0.1.tar.gz", hash = "sha256:e1f47a2be6aa79ca8a1cfbbbffdd67bf4df32b76306f4c3dd2a620a2af78e671"}, + {file = "wemake_python_styleguide-0.0.1-py2.py3-none-any.whl", hash = "sha256:505a19d82f9c4f450c6f06bb8c74d86c99cabcc4d5e6d8ea70e90b13b049f34f"}, +] + +[package.dependencies] +flake8 = "*" + [[package]] name = "wheel" version = "0.38.4" @@ -3462,4 +3480,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = ">=3.9.1,<3.10" -content-hash = "02daca205796a0f29a0d9f50707544e6804f32027eba493cd2aa7f175a00dcea" +content-hash = "c566a959134559e2fccf9b17fc820956207c9d36a58195f48990b986114d7108" diff --git a/pyproject.toml b/pyproject.toml index 2f40d58f56..99e03daea1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,6 +94,7 @@ wheel = "*" enlighten = "*" # cool terminal progress bars toml = "^0.10.2" # for parsing pyproject.toml pre-commit = "*" +wemake-python-styleguide = "*" [tool.poetry.urls] "Bug Tracker" = "https://github.com/pypeclub/openpype/issues" From a6f3bbe0f4f653048e11d7ecbb282d7c7583ef53 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 16:49:09 +0200 Subject: [PATCH 119/347] :wrench: update flake8 configuration --- setup.cfg | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 10cca3eb3f..838c6bd4c5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,6 @@ [flake8] -# ignore = D203 -ignore = BLK100, W504, W503 max-line-length = 79 +strictness = short exclude = .git, __pycache__, @@ -10,8 +9,145 @@ exclude = website, openpype/vendor, *deadline/repository/custom/plugins - max-complexity = 30 +ignore = + # line break before binary operator + W503, + # line break occurred after a binary operator + W504, + # wemake-python-styleguide warnings + # See https://wemake-python-stylegui.de/en/latest/pages/usage/violations/index.html for doc + # Found incorrect module name pattern + WPS102, + # Found wrong variable name + WPS110, + # Found too short name + WPS111, + # Found upper-case constant in a class + WPS115, + # Found module with too many imports + WPS201, + # Found too many module members + WPS202, + # Found overused expression + WPS204, + # Found too many local variables + WPS210, + # Found too many arguments + WPS211, + # Found too many return statements + WPS212, + # Found too many expressions + WPS213, + # Found too many methods + WPS214, + # Found too many await expressions + WPS217, + # Found line with high Jones Complexity + WPS221, + # Found too many `elif` branches + WPS223, + # Found string constant over-use + WPS226, + # Found too long try body length + WPS229, + # Found too many public instance attributes + WPS230, + # Found function with too much cognitive complexity + WPS231, + # Found module cognitive complexity that is too high + WPS232, + # Found too many imported names from a module + WPS235, + # Found too many raises in a function + WPS238, + # Found too deep nesting + WPS220, + # Found `f` string + WPS305, + # Found incorrect multi-line parameters + WPS317, + # Found extra indentation + WPS318, + # Found bracket in wrong position + WPS319, + # Found percent string formatting + WPS323, + # Found implicit string concatenation + WPS326, + # Found variables that are only used for `return` + WPS331, + # Found explicit string concatenation + WPS336, + # Found multiline conditions + WPS337, + # Found incorrect order of methods in a class + WPS338, + # Found line starting with a dot + WPS348, + # Found multiline loop + WPS352, + # Found incorrect unpacking target + WPS414, + # Found wrong keyword + WPS420, + # Found wrong function + WPS421, + # Found statement that has no effect + WPS428, + # Found nested function + WPS430, + # Found magic number + WPS432, + # Found protected attribute usage + WPS437, + # Found block variables overlap + WPS440, + # Found an infinite while loop + WPS457, + # Found a getter without a return value + WPS463, + # Found negated condition + WPS504, + # flake8-quotes warnings + # Remove bad quotes + Q000, + # Remove bad quotes from multiline string + Q001, + # Darglint warnings + # Incorrect indentation + DAR003, + # Excess parameter(s) in Docstring + DAR102, + # Excess exception(s) in Raises section + DAR402, + # pydocstyle warnings + # Missing docstring in __init_ + D107, + # White space formatting for doc strings + D2, + # First line should end with a period + D400, + # Others + # function name + N802, + # Found backslash that is used for line breaking + N400, + E501, + S105, + RST + +[isort] +profile=wemake +src_paths=isort,test +# isort configuration: +# https://github.com/timothycrosley/isort/wiki/isort-Settings +include_trailing_comma = true +use_parentheses = true +# See https://github.com/timothycrosley/isort#multi-line-output-modes +multi_line_output = 3 +# Is the same as 80 in flake8: +line_length = 79 [pylint.'MESSAGES CONTROL'] disable = no-member @@ -28,4 +164,4 @@ omit = /tests directory = ./coverage [tool:pytest] -norecursedirs = repos/* openpype/modules/ftrack/* \ No newline at end of file +norecursedirs = repos/* openpype/modules/ftrack/* From e1384a40b1b94a0f456d6cd4e01a63dd1e8a23e5 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 4 May 2023 17:46:31 +0200 Subject: [PATCH 120/347] :heavy_plus_sign: add isort and fix python black check --- poetry.lock | 2 +- pyproject.toml | 1 + setup.cfg | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index cbc4ac7b94..563f905fad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3480,4 +3480,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = ">=3.9.1,<3.10" -content-hash = "c566a959134559e2fccf9b17fc820956207c9d36a58195f48990b986114d7108" +content-hash = "45e91b47f9e6697b0eb9fdbe76981f691d389ce74bc5a0e98d72e1109b39bc63" diff --git a/pyproject.toml b/pyproject.toml index 4bb80c060b..0d236aedc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,6 +95,7 @@ enlighten = "*" # cool terminal progress bars toml = "^0.10.2" # for parsing pyproject.toml pre-commit = "*" wemake-python-styleguide = "*" +isort="*" [tool.poetry.urls] "Bug Tracker" = "https://github.com/pypeclub/openpype/issues" diff --git a/setup.cfg b/setup.cfg index 838c6bd4c5..7863a74894 100644 --- a/setup.cfg +++ b/setup.cfg @@ -135,7 +135,9 @@ ignore = N400, E501, S105, - RST + RST, + # Black would make changes error + BLK100, [isort] profile=wemake From ecc1ba12f0187c9d9e46e9a59e1749e90e7a6ed1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 5 May 2023 13:22:23 +0800 Subject: [PATCH 121/347] bug fix the validator error --- .../hosts/max/plugins/create/create_camera.py | 8 +++-- .../max/plugins/create/create_maxScene.py | 8 +++-- .../max/plugins/create/create_pointcache.py | 11 ++++-- .../max/plugins/create/create_pointcloud.py | 8 +++-- .../hosts/max/plugins/create/create_render.py | 8 +++-- .../max/plugins/publish/extract_pointcloud.py | 34 +++++++++---------- .../plugins/publish/validate_pointcloud.py | 2 +- 7 files changed, 47 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_camera.py b/openpype/hosts/max/plugins/create/create_camera.py index 91d0d4d3dc..ab5578a201 100644 --- a/openpype/hosts/max/plugins/create/create_camera.py +++ b/openpype/hosts/max/plugins/create/create_camera.py @@ -12,7 +12,6 @@ class CreateCamera(plugin.MaxCreator): def create(self, subset_name, instance_data, pre_create_data): from pymxs import runtime as rt - sel_obj = list(rt.selection) instance = super(CreateCamera, self).create( subset_name, instance_data, @@ -20,7 +19,10 @@ class CreateCamera(plugin.MaxCreator): container = rt.getNodeByName(instance.data.get("instance_node")) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - for obj in sel_obj: - obj.parent = container + sel_obj = None + if self.selected_nodes: + sel_obj = list(self.selected_nodes) + for obj in sel_obj: + obj.parent = container # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_maxScene.py b/openpype/hosts/max/plugins/create/create_maxScene.py index 7900336f32..2d6ea8c27b 100644 --- a/openpype/hosts/max/plugins/create/create_maxScene.py +++ b/openpype/hosts/max/plugins/create/create_maxScene.py @@ -12,7 +12,6 @@ class CreateMaxScene(plugin.MaxCreator): def create(self, subset_name, instance_data, pre_create_data): from pymxs import runtime as rt - sel_obj = list(rt.selection) instance = super(CreateMaxScene, self).create( subset_name, instance_data, @@ -20,7 +19,10 @@ class CreateMaxScene(plugin.MaxCreator): container = rt.getNodeByName(instance.data.get("instance_node")) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - for obj in sel_obj: - obj.parent = container + sel_obj = None + if self.selected_nodes: + sel_obj = list(self.selected_nodes) + for obj in sel_obj: + obj.parent = container # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_pointcache.py b/openpype/hosts/max/plugins/create/create_pointcache.py index 32f0838471..42aa743794 100644 --- a/openpype/hosts/max/plugins/create/create_pointcache.py +++ b/openpype/hosts/max/plugins/create/create_pointcache.py @@ -13,10 +13,17 @@ class CreatePointCache(plugin.MaxCreator): def create(self, subset_name, instance_data, pre_create_data): # from pymxs import runtime as rt - _ = super(CreatePointCache, self).create( + instance = super(CreatePointCache, self).create( subset_name, instance_data, pre_create_data) # type: CreatedInstance - + container = rt.getNodeByName(instance.data.get("instance_node")) + # TODO: Disable "Add to Containers?" Panel + # parent the selected cameras into the container + sel_obj = None + if self.selected_nodes: + sel_obj = list(self.selected_nodes) + for obj in sel_obj: + obj.parent = container # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_pointcloud.py b/openpype/hosts/max/plugins/create/create_pointcloud.py index c83acac3df..e46bf30d1c 100644 --- a/openpype/hosts/max/plugins/create/create_pointcloud.py +++ b/openpype/hosts/max/plugins/create/create_pointcloud.py @@ -12,7 +12,6 @@ class CreatePointCloud(plugin.MaxCreator): def create(self, subset_name, instance_data, pre_create_data): from pymxs import runtime as rt - sel_obj = list(rt.selection) instance = super(CreatePointCloud, self).create( subset_name, instance_data, @@ -20,7 +19,10 @@ class CreatePointCloud(plugin.MaxCreator): container = rt.getNodeByName(instance.data.get("instance_node")) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - for obj in sel_obj: - obj.parent = container + sel_obj = None + if self.selected_nodes: + sel_obj = list(self.selected_nodes) + for obj in sel_obj: + obj.parent = container # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 269fff2e32..43d69fa320 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -13,7 +13,6 @@ class CreateRender(plugin.MaxCreator): def create(self, subset_name, instance_data, pre_create_data): from pymxs import runtime as rt - sel_obj = list(rt.selection) instance = super(CreateRender, self).create( subset_name, instance_data, @@ -22,8 +21,11 @@ class CreateRender(plugin.MaxCreator): container = rt.getNodeByName(container_name) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - for obj in sel_obj: - obj.parent = container + sel_obj = None + if self.selected_nodes: + sel_obj = list(self.selected_nodes) + for obj in sel_obj: + obj.parent = container # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index ee9e9693e9..f85255bbf5 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -53,7 +53,9 @@ class ExtractPointCloud(publish.Extractor): start, end, path) + for job in job_args: + self.log.debug("job:{}".format(job)) rt.Execute(job) self.log.info("Performing Extraction ...") @@ -62,11 +64,11 @@ class ExtractPointCloud(publish.Extractor): self.log.info("Writing PRT with TyFlow Plugin...") filenames = self.get_files( - instance.data["members"][0], path, start, end) + instance.data["members"], path, start, end) self.log.debug(f"filenames: {filenames}") partition = self.partition_output_name( - instance.data["members"][0]) + instance.data["members"]) representation = { 'name': 'prt', @@ -105,7 +107,7 @@ class ExtractPointCloud(publish.Extractor): end_frame = f"{operator}.frameEnd={end}" job_args.append(end_frame) filepath = filepath.replace("\\", "/") - prt_filename = f"{operator}.PRTFilename={filepath}" + prt_filename = f"{operator}.PRTFilename='{filepath}'" job_args.append(prt_filename) # Partition @@ -113,7 +115,8 @@ class ExtractPointCloud(publish.Extractor): job_args.append(mode) additional_args = self.get_custom_attr(operator) - job_args.extend(iter(additional_args)) + for args in additional_args: + job_args.append(args) prt_export = f"{operator}.exportPRT()" job_args.append(prt_export) @@ -132,19 +135,16 @@ class ExtractPointCloud(publish.Extractor): """ opt_list = [] for member in members: - node = rt.getNodeByName(member) - selection_list = list(node.Children) - for sel in selection_list: - obj = sel.baseobject - # TODO: to see if it can be used maxscript instead - anim_names = rt.GetSubAnimNames(obj) - for anim_name in anim_names: - sub_anim = rt.GetSubAnim(obj, anim_name) - boolean = rt.IsProperty(sub_anim, "Export_Particles") - if boolean: - event_name = sub_anim.Name - opt = f"${member.Name}.{event_name}.export_particles" - opt_list.append(opt) + obj = member.baseobject + # TODO: to see if it can be used maxscript instead + anim_names = rt.GetSubAnimNames(obj) + for anim_name in anim_names: + sub_anim = rt.GetSubAnim(obj, anim_name) + boolean = rt.IsProperty(sub_anim, "Export_Particles") + if boolean: + event_name = sub_anim.Name + opt = f"${member.Name}.{event_name}.export_particles" + opt_list.append(opt) return opt_list diff --git a/openpype/hosts/max/plugins/publish/validate_pointcloud.py b/openpype/hosts/max/plugins/publish/validate_pointcloud.py index e3a6face07..e1c2151c9d 100644 --- a/openpype/hosts/max/plugins/publish/validate_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/validate_pointcloud.py @@ -54,7 +54,7 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): f":{invalid}")) if report: - raise PublishValidationError + raise PublishValidationError(f"{report}") def get_tyflow_object(self, instance): invalid = [] From cca37521e9de457e5b4b519f09aaa4d1851f4c3d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 5 May 2023 13:34:18 +0800 Subject: [PATCH 122/347] refractor to the point cloud extractor and all creators --- openpype/hosts/max/plugins/create/create_camera.py | 7 +------ openpype/hosts/max/plugins/create/create_maxScene.py | 7 +------ openpype/hosts/max/plugins/create/create_model.py | 7 +------ openpype/hosts/max/plugins/create/create_pointcache.py | 9 ++------- openpype/hosts/max/plugins/create/create_pointcloud.py | 7 +------ openpype/hosts/max/plugins/publish/extract_pointcloud.py | 4 +--- 6 files changed, 7 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_camera.py b/openpype/hosts/max/plugins/create/create_camera.py index ab5578a201..b949c91857 100644 --- a/openpype/hosts/max/plugins/create/create_camera.py +++ b/openpype/hosts/max/plugins/create/create_camera.py @@ -16,13 +16,8 @@ class CreateCamera(plugin.MaxCreator): subset_name, instance_data, pre_create_data) # type: CreatedInstance - container = rt.getNodeByName(instance.data.get("instance_node")) + _ = rt.getNodeByName(instance.data.get("instance_node")) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - sel_obj = None - if self.selected_nodes: - sel_obj = list(self.selected_nodes) - for obj in sel_obj: - obj.parent = container # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_maxScene.py b/openpype/hosts/max/plugins/create/create_maxScene.py index 2d6ea8c27b..bf03ee8c8a 100644 --- a/openpype/hosts/max/plugins/create/create_maxScene.py +++ b/openpype/hosts/max/plugins/create/create_maxScene.py @@ -16,13 +16,8 @@ class CreateMaxScene(plugin.MaxCreator): subset_name, instance_data, pre_create_data) # type: CreatedInstance - container = rt.getNodeByName(instance.data.get("instance_node")) + _ = rt.getNodeByName(instance.data.get("instance_node")) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - sel_obj = None - if self.selected_nodes: - sel_obj = list(self.selected_nodes) - for obj in sel_obj: - obj.parent = container # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_model.py b/openpype/hosts/max/plugins/create/create_model.py index e7ae3af9db..4f7fd491e4 100644 --- a/openpype/hosts/max/plugins/create/create_model.py +++ b/openpype/hosts/max/plugins/create/create_model.py @@ -16,13 +16,8 @@ class CreateModel(plugin.MaxCreator): subset_name, instance_data, pre_create_data) # type: CreatedInstance - container = rt.getNodeByName(instance.data.get("instance_node")) + _ = rt.getNodeByName(instance.data.get("instance_node")) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - sel_obj = None - if self.selected_nodes: - sel_obj = list(self.selected_nodes) - for obj in sel_obj: - obj.parent = container # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_pointcache.py b/openpype/hosts/max/plugins/create/create_pointcache.py index 42aa743794..aa22a92b25 100644 --- a/openpype/hosts/max/plugins/create/create_pointcache.py +++ b/openpype/hosts/max/plugins/create/create_pointcache.py @@ -11,19 +11,14 @@ class CreatePointCache(plugin.MaxCreator): icon = "gear" def create(self, subset_name, instance_data, pre_create_data): - # from pymxs import runtime as rt + from pymxs import runtime as rt instance = super(CreatePointCache, self).create( subset_name, instance_data, pre_create_data) # type: CreatedInstance - container = rt.getNodeByName(instance.data.get("instance_node")) + _ = rt.getNodeByName(instance.data.get("instance_node")) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - sel_obj = None - if self.selected_nodes: - sel_obj = list(self.selected_nodes) - for obj in sel_obj: - obj.parent = container # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_pointcloud.py b/openpype/hosts/max/plugins/create/create_pointcloud.py index e46bf30d1c..a8564ee118 100644 --- a/openpype/hosts/max/plugins/create/create_pointcloud.py +++ b/openpype/hosts/max/plugins/create/create_pointcloud.py @@ -16,13 +16,8 @@ class CreatePointCloud(plugin.MaxCreator): subset_name, instance_data, pre_create_data) # type: CreatedInstance - container = rt.getNodeByName(instance.data.get("instance_node")) + _ = rt.getNodeByName(instance.data.get("instance_node")) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - sel_obj = None - if self.selected_nodes: - sel_obj = list(self.selected_nodes) - for obj in sel_obj: - obj.parent = container # for additional work on the node: # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index f85255bbf5..c807885859 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -55,7 +55,6 @@ class ExtractPointCloud(publish.Extractor): path) for job in job_args: - self.log.debug("job:{}".format(job)) rt.Execute(job) self.log.info("Performing Extraction ...") @@ -107,8 +106,7 @@ class ExtractPointCloud(publish.Extractor): end_frame = f"{operator}.frameEnd={end}" job_args.append(end_frame) filepath = filepath.replace("\\", "/") - prt_filename = f"{operator}.PRTFilename='{filepath}'" - + prt_filename = f'{operator}.PRTFilename="{filepath}"' job_args.append(prt_filename) # Partition mode = f"{operator}.PRTPartitionsMode=2" From 932c807913e5f7bac4de5d4acffd8ede3c9a0c3d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 5 May 2023 13:53:54 +0800 Subject: [PATCH 123/347] fix the bug shown in removing instance after refractoring --- openpype/hosts/max/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 39a17c29ef..a464d54b33 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -173,7 +173,7 @@ class MaxCreator(Creator, MaxCreatorBase): for instance in instances: if instance_node := rt.GetNodeByName(instance.data.get("instance_node")): # noqa rt.Select(instance_node) - rt.custAttributes.add(instance_node.baseObject, "openPypeData") + rt.execute(f'for o in selection do for c in o.children do c.parent = undefined') # noqa rt.Delete(instance_node) self._remove_instance_from_context(instance) From 2d26cc00ad2c9787b47ede9fc81a6289a6edc911 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 5 May 2023 15:52:47 +0200 Subject: [PATCH 124/347] :rotating_light: some style issues and refactoring --- .pre-commit-config.yaml | 12 ++++-- .../hosts/max/plugins/create/create_camera.py | 14 +------ .../max/plugins/create/create_maxScene.py | 14 +------ .../hosts/max/plugins/create/create_model.py | 14 +------ .../max/plugins/create/create_pointcache.py | 15 +------- .../max/plugins/create/create_pointcloud.py | 14 +------ .../hosts/max/plugins/create/create_render.py | 13 +++---- .../plugins/publish/validate_usd_plugin.py | 38 ++++++++++--------- setup.cfg | 2 + 9 files changed, 41 insertions(+), 95 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fe4c7e3da3..9c03dc8ff0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,11 +10,15 @@ repos: - id: check-added-large-files - id: no-commit-to-branch args: [ '--pattern', '^(?!((release|enhancement|feature|bugfix|documentation|tests|local|chore)\/[a-zA-Z0-9\-_]+)$).*' ] -- repo: local - hooks: - - id: flake8 + +- repo: local + hooks: + - id: flake8 name: flake8 - description: WPS enforced flake8 + additional_dependencies: + - wemake-python-styleguide + - flake8 + description: Python style guide enforcement entry: flake8 args: ["--config=setup.cfg"] language: python diff --git a/openpype/hosts/max/plugins/create/create_camera.py b/openpype/hosts/max/plugins/create/create_camera.py index b949c91857..804d629ec7 100644 --- a/openpype/hosts/max/plugins/create/create_camera.py +++ b/openpype/hosts/max/plugins/create/create_camera.py @@ -1,23 +1,11 @@ # -*- coding: utf-8 -*- """Creator plugin for creating camera.""" from openpype.hosts.max.api import plugin -from openpype.pipeline import CreatedInstance class CreateCamera(plugin.MaxCreator): + """Creator plugin for Camera.""" identifier = "io.openpype.creators.max.camera" label = "Camera" family = "camera" icon = "gear" - - def create(self, subset_name, instance_data, pre_create_data): - from pymxs import runtime as rt - instance = super(CreateCamera, self).create( - subset_name, - instance_data, - pre_create_data) # type: CreatedInstance - _ = rt.getNodeByName(instance.data.get("instance_node")) - # TODO: Disable "Add to Containers?" Panel - # parent the selected cameras into the container - # for additional work on the node: - # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_maxScene.py b/openpype/hosts/max/plugins/create/create_maxScene.py index bf03ee8c8a..851e26dda2 100644 --- a/openpype/hosts/max/plugins/create/create_maxScene.py +++ b/openpype/hosts/max/plugins/create/create_maxScene.py @@ -1,23 +1,11 @@ # -*- coding: utf-8 -*- """Creator plugin for creating raw max scene.""" from openpype.hosts.max.api import plugin -from openpype.pipeline import CreatedInstance class CreateMaxScene(plugin.MaxCreator): + """Creator plugin for 3ds max scenes.""" identifier = "io.openpype.creators.max.maxScene" label = "Max Scene" family = "maxScene" icon = "gear" - - def create(self, subset_name, instance_data, pre_create_data): - from pymxs import runtime as rt - instance = super(CreateMaxScene, self).create( - subset_name, - instance_data, - pre_create_data) # type: CreatedInstance - _ = rt.getNodeByName(instance.data.get("instance_node")) - # TODO: Disable "Add to Containers?" Panel - # parent the selected cameras into the container - # for additional work on the node: - # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_model.py b/openpype/hosts/max/plugins/create/create_model.py index 4f7fd491e4..fc09d475ef 100644 --- a/openpype/hosts/max/plugins/create/create_model.py +++ b/openpype/hosts/max/plugins/create/create_model.py @@ -1,23 +1,11 @@ # -*- coding: utf-8 -*- """Creator plugin for model.""" from openpype.hosts.max.api import plugin -from openpype.pipeline import CreatedInstance class CreateModel(plugin.MaxCreator): + """Creator plugin for Model.""" identifier = "io.openpype.creators.max.model" label = "Model" family = "model" icon = "gear" - - def create(self, subset_name, instance_data, pre_create_data): - from pymxs import runtime as rt - instance = super(CreateModel, self).create( - subset_name, - instance_data, - pre_create_data) # type: CreatedInstance - _ = rt.getNodeByName(instance.data.get("instance_node")) - # TODO: Disable "Add to Containers?" Panel - # parent the selected cameras into the container - # for additional work on the node: - # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_pointcache.py b/openpype/hosts/max/plugins/create/create_pointcache.py index aa22a92b25..c2d11f4c32 100644 --- a/openpype/hosts/max/plugins/create/create_pointcache.py +++ b/openpype/hosts/max/plugins/create/create_pointcache.py @@ -1,24 +1,11 @@ # -*- coding: utf-8 -*- """Creator plugin for creating pointcache alembics.""" from openpype.hosts.max.api import plugin -from openpype.pipeline import CreatedInstance class CreatePointCache(plugin.MaxCreator): + """Creator plugin for Point caches.""" identifier = "io.openpype.creators.max.pointcache" label = "Point Cache" family = "pointcache" icon = "gear" - - def create(self, subset_name, instance_data, pre_create_data): - from pymxs import runtime as rt - - instance = super(CreatePointCache, self).create( - subset_name, - instance_data, - pre_create_data) # type: CreatedInstance - _ = rt.getNodeByName(instance.data.get("instance_node")) - # TODO: Disable "Add to Containers?" Panel - # parent the selected cameras into the container - # for additional work on the node: - # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_pointcloud.py b/openpype/hosts/max/plugins/create/create_pointcloud.py index a8564ee118..bc7706069d 100644 --- a/openpype/hosts/max/plugins/create/create_pointcloud.py +++ b/openpype/hosts/max/plugins/create/create_pointcloud.py @@ -1,23 +1,11 @@ # -*- coding: utf-8 -*- """Creator plugin for creating point cloud.""" from openpype.hosts.max.api import plugin -from openpype.pipeline import CreatedInstance class CreatePointCloud(plugin.MaxCreator): + """Creator plugin for Point Clouds.""" identifier = "io.openpype.creators.max.pointcloud" label = "Point Cloud" family = "pointcloud" icon = "gear" - - def create(self, subset_name, instance_data, pre_create_data): - from pymxs import runtime as rt - instance = super(CreatePointCloud, self).create( - subset_name, - instance_data, - pre_create_data) # type: CreatedInstance - _ = rt.getNodeByName(instance.data.get("instance_node")) - # TODO: Disable "Add to Containers?" Panel - # parent the selected cameras into the container - # for additional work on the node: - # instance_node = rt.getNodeByName(instance.get("instance_node")) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 43d69fa320..e9952c3124 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -1,24 +1,25 @@ # -*- coding: utf-8 -*- """Creator plugin for creating camera.""" from openpype.hosts.max.api import plugin -from openpype.pipeline import CreatedInstance from openpype.hosts.max.api.lib_rendersettings import RenderSettings class CreateRender(plugin.MaxCreator): + """Creator plugin for Renders.""" identifier = "io.openpype.creators.max.render" label = "Render" family = "maxrender" icon = "gear" def create(self, subset_name, instance_data, pre_create_data): - from pymxs import runtime as rt - instance = super(CreateRender, self).create( + """Plugin entry point.""" + from pymxs import runtime as rt # noqa: WPS433,I001 + instance = super().create( subset_name, instance_data, - pre_create_data) # type: CreatedInstance + pre_create_data) container_name = instance.data.get("instance_node") - container = rt.getNodeByName(container_name) + container = rt.GetNodeByName(container_name) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container sel_obj = None @@ -26,8 +27,6 @@ class CreateRender(plugin.MaxCreator): sel_obj = list(self.selected_nodes) for obj in sel_obj: obj.parent = container - # for additional work on the node: - # instance_node = rt.getNodeByName(instance.get("instance_node")) # set viewport camera for rendering(mandatory for deadline) RenderSettings().set_render_camera(sel_obj) diff --git a/openpype/hosts/max/plugins/publish/validate_usd_plugin.py b/openpype/hosts/max/plugins/publish/validate_usd_plugin.py index 8f11d72567..9957e62736 100644 --- a/openpype/hosts/max/plugins/publish/validate_usd_plugin.py +++ b/openpype/hosts/max/plugins/publish/validate_usd_plugin.py @@ -1,35 +1,37 @@ # -*- coding: utf-8 -*- -import pyblish.api +"""Validator for USD plugin.""" from openpype.pipeline import PublishValidationError +from pyblish.api import InstancePlugin, ValidatorOrder from pymxs import runtime as rt -class ValidateUSDPlugin(pyblish.api.InstancePlugin): - """Validates if USD plugin is installed or loaded in Max - """ +def get_plugins() -> list: + """Get plugin list from 3ds max.""" + manager = rt.PluginManager + count = manager.pluginDllCount + plugin_info_list = [] + for p in range(1, count + 1): + plugin_info = manager.pluginDllName(p) + plugin_info_list.append(plugin_info) - order = pyblish.api.ValidatorOrder - 0.01 + return plugin_info_list + + +class ValidateUSDPlugin(InstancePlugin): + """Validates if USD plugin is installed or loaded in 3ds max.""" + + order = ValidatorOrder - 0.01 families = ["model"] hosts = ["max"] label = "USD Plugin" def process(self, instance): - plugin_mgr = rt.PluginManager - plugin_count = plugin_mgr.pluginDllCount - plugin_info = self.get_plugins(plugin_mgr, - plugin_count) + """Plugin entry point.""" + + plugin_info = get_plugins() usd_import = "usdimport.dli" if usd_import not in plugin_info: raise PublishValidationError(f"USD Plugin {usd_import} not found") usd_export = "usdexport.dle" if usd_export not in plugin_info: raise PublishValidationError(f"USD Plugin {usd_export} not found") - - @staticmethod - def get_plugins(manager, count): - plugin_info_list = [] - for p in range(1, count + 1): - plugin_info = manager.pluginDllName(p) - plugin_info_list.append(plugin_info) - - return plugin_info_list diff --git a/setup.cfg b/setup.cfg index 7863a74894..1d57657a19 100644 --- a/setup.cfg +++ b/setup.cfg @@ -138,6 +138,8 @@ ignore = RST, # Black would make changes error BLK100, + # Imperative mood of the first line on docstrings + D401, [isort] profile=wemake From 6b1a801f4c14be4d4c601b7c7267dd01b659dad3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 5 May 2023 15:55:27 +0200 Subject: [PATCH 125/347] adding links to documentation into settings labels --- .../schemas/projects_schema/schema_project_aftereffects.json | 2 +- .../schemas/projects_schema/schema_project_blender.json | 2 +- .../schemas/projects_schema/schema_project_celaction.json | 2 +- .../entities/schemas/projects_schema/schema_project_flame.json | 2 +- .../entities/schemas/projects_schema/schema_project_fusion.json | 2 +- .../entities/schemas/projects_schema/schema_project_global.json | 2 +- .../schemas/projects_schema/schema_project_harmony.json | 2 +- .../entities/schemas/projects_schema/schema_project_hiero.json | 2 +- .../schemas/projects_schema/schema_project_houdini.json | 2 +- .../entities/schemas/projects_schema/schema_project_max.json | 2 +- .../entities/schemas/projects_schema/schema_project_maya.json | 2 +- .../schemas/projects_schema/schema_project_photoshop.json | 2 +- .../schemas/projects_schema/schema_project_resolve.json | 2 +- .../schemas/projects_schema/schema_project_traypublisher.json | 2 +- .../schemas/projects_schema/schema_project_tvpaint.json | 2 +- .../entities/schemas/projects_schema/schema_project_unreal.json | 2 +- .../schemas/projects_schema/schema_project_webpublisher.json | 2 +- .../schemas/projects_schema/schemas/schema_nuke_imageio.json | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index ef09a71bda..d164a8f2c3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index c3eab6c3f0..79eea3f192 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json index 5729f70e2f..915f199b6e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing." + "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index 625780a650..16c9378194 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures." + "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 6189da0e19..65584264c9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_global.json b/openpype/settings/entities/schemas/projects_schema/schema_project_global.json index d1d7f336e1..953361935c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_global.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_global.json @@ -13,7 +13,7 @@ "children": [ { "type": "label", - "label": "It's important to note that once color management is activated on a project, all hosts will be color managed by default.
The OpenColorIO (OCIO) config file is used either from the global settings or from the host's overrides. It's worth
noting that the order of the defined configuration paths matters, with higher priority given to paths listed earlier in
the configuration list.

To avoid potential issues, ensure that the OCIO configuration path is not an absolute path and includes at least
the root token (Anatomy). This helps ensure that the configuration path remains valid across different environments and
avoids any hard-coding of paths that may be specific to one particular system." + "label": "It's important to note that once color management is activated on a project, all hosts will be color managed by default.
The OpenColorIO (OCIO) config file is used either from the global settings or from the host's overrides. It's worth
noting that the order of the defined configuration paths matters, with higher priority given to paths listed earlier in
the configuration list.

To avoid potential issues, ensure that the OCIO configuration path is not an absolute path and includes at least
the root token (Anatomy). This helps ensure that the configuration path remains valid across different environments and
avoids any hard-coding of paths that may be specific to one particular system.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json index a56f62c6d6..276b321b24 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index 2c82e1a9ac..c2339d8200 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index 588e209718..a7032775c1 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index 5dac8ee7e9..d1e8e333cc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index 55f231e235..fe7c262603 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -54,7 +54,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 898c3374d7..7ddd575dde 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures." + "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json index 758cf2a196..aea019b77b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures.." + "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures.

Related documentation.." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index c234cd1b71..6b55837f12 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing." + "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index 6d446b5550..ed8887f93e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing." + "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index 8c3ff71489..aa2fe40b4a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json index e319182e3c..7b65dddda6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json @@ -14,7 +14,7 @@ "children": [ { "type": "label", - "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing." + "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing.

Related documentation." }, { "type": "boolean", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json index f691518255..864e084bde 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json @@ -7,7 +7,7 @@ "children": [ { "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures." + "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." }, { "type": "boolean", From ac154303f3aaf6130492442c1c42496877006213 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 5 May 2023 17:23:42 +0200 Subject: [PATCH 126/347] :heavy_minus_sign: revert WPS tests --- .github/workflows/pr_linting.yml | 56 -------------------------------- .pre-commit-config.yaml | 13 -------- pyproject.toml | 2 -- 3 files changed, 71 deletions(-) delete mode 100644 .github/workflows/pr_linting.yml diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml deleted file mode 100644 index 58f52bb313..0000000000 --- a/.github/workflows/pr_linting.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: 📇 Code Linting - -on: - push: - branches: [ develop ] - pull_request: - branches: [ develop ] - - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number}} - cancel-in-progress: true - -permissions: - contents: read - pull-requests: write - -jobs: - files_changed: - runs-on: ubuntu-latest - outputs: - changed_python: ${{ steps.changes.outputs.python }} - steps: - - uses: actions/checkout@v3 - if: github.event_name == 'push' - - uses: dorny/paths-filter@master - id: changes - with: - filters: | - python: - - ["**/*.py"] - - linting: - needs: files_changed - if: ${{ needs.files_changed.outputs.changed_python == 'true' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - fetch-depth: 0 - - name: Get changed Python files - id: py-changes - run: | - echo "py_files_list=$(git diff --name-only --diff-filter=ACMRT \ - ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} \ - | grep .py$ | xargs)" >> $GITHUB_OUTPUT - - name: Code Check - uses: wemake-services/wemake-python-styleguide@master - with: - reporter: 'github-pr-review' - path: ${{ steps.py-changes.outputs.py_files_list }} - env: - GITHUB_TOKEN: "${{ secrets.YNPUT_BOT_TOKEN }}" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9c03dc8ff0..eec388924e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,16 +10,3 @@ repos: - id: check-added-large-files - id: no-commit-to-branch args: [ '--pattern', '^(?!((release|enhancement|feature|bugfix|documentation|tests|local|chore)\/[a-zA-Z0-9\-_]+)$).*' ] - -- repo: local - hooks: - - id: flake8 - name: flake8 - additional_dependencies: - - wemake-python-styleguide - - flake8 - description: Python style guide enforcement - entry: flake8 - args: ["--config=setup.cfg"] - language: python - types: [python] diff --git a/pyproject.toml b/pyproject.toml index 0d236aedc6..003f6cf2d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,8 +94,6 @@ wheel = "*" enlighten = "*" # cool terminal progress bars toml = "^0.10.2" # for parsing pyproject.toml pre-commit = "*" -wemake-python-styleguide = "*" -isort="*" [tool.poetry.urls] "Bug Tracker" = "https://github.com/pypeclub/openpype/issues" From 3822b8e7f24bbe9754793c77ced8d152fb172a02 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 May 2023 16:29:32 +0800 Subject: [PATCH 127/347] refractor the render creator --- openpype/hosts/max/plugins/create/create_render.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index e9952c3124..136ca028ac 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -22,13 +22,9 @@ class CreateRender(plugin.MaxCreator): container = rt.GetNodeByName(container_name) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container - sel_obj = None - if self.selected_nodes: - sel_obj = list(self.selected_nodes) - for obj in sel_obj: - obj.parent = container - - # set viewport camera for rendering(mandatory for deadline) - RenderSettings().set_render_camera(sel_obj) + sel_obj = self.selected_nodes + if sel_obj: + # set viewport camera for rendering(mandatory for deadline) + RenderSettings().set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) RenderSettings().render_output(container_name) From d485fdbecf432f4097a48c91d6e242b94c80a9a1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 May 2023 16:31:15 +0800 Subject: [PATCH 128/347] hound fix --- openpype/hosts/max/plugins/create/create_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 136ca028ac..2ea993494b 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -19,7 +19,6 @@ class CreateRender(plugin.MaxCreator): instance_data, pre_create_data) container_name = instance.data.get("instance_node") - container = rt.GetNodeByName(container_name) # TODO: Disable "Add to Containers?" Panel # parent the selected cameras into the container sel_obj = self.selected_nodes From d6d022989f5c2e8897abd27c3d80b4b5a23b5e77 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 May 2023 16:32:52 +0800 Subject: [PATCH 129/347] hound fix --- openpype/hosts/max/plugins/create/create_render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 2ea993494b..9b677a615f 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -13,7 +13,6 @@ class CreateRender(plugin.MaxCreator): def create(self, subset_name, instance_data, pre_create_data): """Plugin entry point.""" - from pymxs import runtime as rt # noqa: WPS433,I001 instance = super().create( subset_name, instance_data, From 5e9ae9153b9af411ff2102102d09356d87d8208c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 May 2023 21:45:52 +0800 Subject: [PATCH 130/347] bug fix redshift creators --- .../plugins/create/create_redshift_rop.py | 63 +++++++++++++++---- .../houdini/plugins/create/create_vray_rop.py | 2 - .../plugins/publish/submit_publish_job.py | 3 +- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_rop.py b/openpype/hosts/houdini/plugins/create/create_redshift_rop.py index 1fb9ab2f67..4f9c00b3a3 100644 --- a/openpype/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_redshift_rop.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- """Creator plugin to create Redshift ROP.""" +import hou # noqa + from openpype.hosts.houdini.api import plugin from openpype.pipeline import CreatedInstance +from openpype.lib import EnumDef class CreateRedshiftROP(plugin.HoudiniCreator): @@ -11,9 +14,9 @@ class CreateRedshiftROP(plugin.HoudiniCreator): family = "redshift_rop" icon = "magic" defaults = ["master"] + ext = "exr" def create(self, subset_name, instance_data, pre_create_data): - import hou # noqa instance_data.pop("active", None) instance_data.update({"node_type": "Redshift_ROP"}) @@ -22,12 +25,6 @@ class CreateRedshiftROP(plugin.HoudiniCreator): # Submit for job publishing instance_data["farm"] = True - # Clear the family prefix from the subset - subset = subset_name - subset_no_prefix = subset[len(self.family):] - subset_no_prefix = subset_no_prefix[0].lower() + subset_no_prefix[1:] - subset_name = subset_no_prefix - instance = super(CreateRedshiftROP, self).create( subset_name, instance_data, @@ -36,11 +33,10 @@ class CreateRedshiftROP(plugin.HoudiniCreator): instance_node = hou.node(instance.get("instance_node")) basename = instance_node.name() - instance_node.setName(basename + "_ROP", unique_name=True) # Also create the linked Redshift IPR Rop try: - ipr_rop = self.parent.createNode( + ipr_rop = instance_node.parent().createNode( "Redshift_IPR", node_name=basename + "_IPR" ) except hou.OperationFailed: @@ -52,19 +48,60 @@ class CreateRedshiftROP(plugin.HoudiniCreator): ipr_rop.setPosition(instance_node.position() + hou.Vector2(0, -1)) # Set the linked rop to the Redshift ROP - ipr_rop.parm("linked_rop").set(ipr_rop.relativePathTo(instance)) + ipr_rop.parm("linked_rop").set(instance_node.path()) + + ext = pre_create_data.get("image_format") + filepath ="{renders_dir}{subset_name}/{subset_name}.{fmt}".format( + renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), + subset_name=subset_name, + fmt="${aov}.$F4.{ext}".format(aov="AOV", + ext=ext) + ) - prefix = '${HIP}/render/${HIPNAME}/`chs("subset")`.${AOV}.$F4.exr' parms = { # Render frame range "trange": 1, # Redshift ROP settings - "RS_outputFileNamePrefix": prefix, - "RS_outputMultilayerMode": 0, # no multi-layered exr + "RS_outputFileNamePrefix": filepath, + "RS_outputMultilayerMode": "1", # no multi-layered exr "RS_outputBeautyAOVSuffix": "beauty", } + + if self.selected_nodes: + # set up the render camera from the selected node + camera = None + for node in self.selected_nodes: + if node.type().name() == "cam": + camera = node.path() + parms.update({ + "RS_renderCamera": camera or "" + }) instance_node.setParms(parms) # Lock some Avalon attributes to_lock = ["family", "id"] self.lock_parameters(instance_node, to_lock) + + def remove_instances(self, instances): + for instance in instances: + node = instance.data.get("instance_node") + + ipr_node = hou.node(f"{node}_IPR") + if ipr_node: + ipr_node.destroy() + + return super(CreateRedshiftROP, self).remove_instances(instances) + + def get_pre_create_attr_defs(self): + attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs() + image_format_enum = [ + "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png", + "rad", "rat", "rta", "sgi", "tga", "tif", + ] + + return attrs + [ + EnumDef("image_format", + image_format_enum, + default=self.ext, + label="Image Format Options") + ] diff --git a/openpype/hosts/houdini/plugins/create/create_vray_rop.py b/openpype/hosts/houdini/plugins/create/create_vray_rop.py index 0a74d93c99..1de9be4ed6 100644 --- a/openpype/hosts/houdini/plugins/create/create_vray_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_vray_rop.py @@ -154,5 +154,3 @@ class CreateVrayROP(plugin.HoudiniCreator): "if enabled", default=False) ] - -# ${HIP}/render/${HIPNAME}.${AOV}.$F4.exr diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index c1c9d8c062..afe5c59834 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -125,7 +125,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "renderlayer", "imagesequence", "vrayscene", "maxrender", "arnold_rop", "mantra_rop", - "karma_rop", "vray_rop"] + "karma_rop", "vray_rop", + "redshift_rop"] aov_filter = {"maya": [r".*([Bb]eauty).*"], "aftereffects": [r".*"], # for everything from AE From 708c125433aad8c88b56e86b1a54965d433ebe3d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 May 2023 21:49:20 +0800 Subject: [PATCH 131/347] hound fix --- .../houdini/plugins/create/create_redshift_rop.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_rop.py b/openpype/hosts/houdini/plugins/create/create_redshift_rop.py index 4f9c00b3a3..4ec88fc468 100644 --- a/openpype/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_redshift_rop.py @@ -51,11 +51,10 @@ class CreateRedshiftROP(plugin.HoudiniCreator): ipr_rop.parm("linked_rop").set(instance_node.path()) ext = pre_create_data.get("image_format") - filepath ="{renders_dir}{subset_name}/{subset_name}.{fmt}".format( - renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), + filepath = "{renders_dir}{subset_name}/{subset_name}.{fmt}".format( + renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), subset_name=subset_name, - fmt="${aov}.$F4.{ext}".format(aov="AOV", - ext=ext) + fmt="${aov}.$F4.{ext}".format(aov="AOV", ext=ext) ) parms = { @@ -74,8 +73,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator): if node.type().name() == "cam": camera = node.path() parms.update({ - "RS_renderCamera": camera or "" - }) + "RS_renderCamera": camera or ""}) instance_node.setParms(parms) # Lock some Avalon attributes From 34b387a29395e4bc05d911d151236a77c230c387 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 May 2023 21:51:31 +0800 Subject: [PATCH 132/347] hound fix --- openpype/hosts/houdini/plugins/create/create_redshift_rop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_rop.py b/openpype/hosts/houdini/plugins/create/create_redshift_rop.py index 4ec88fc468..242915ed6d 100644 --- a/openpype/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_redshift_rop.py @@ -53,8 +53,8 @@ class CreateRedshiftROP(plugin.HoudiniCreator): ext = pre_create_data.get("image_format") filepath = "{renders_dir}{subset_name}/{subset_name}.{fmt}".format( renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), - subset_name=subset_name, - fmt="${aov}.$F4.{ext}".format(aov="AOV", ext=ext) + subset_name=subset_name, + fmt="${aov}.$F4.{ext}".format(aov="AOV", ext=ext) ) parms = { From 8d729995791997e9717e1463c04f9e5305e9ecce Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 May 2023 21:52:38 +0800 Subject: [PATCH 133/347] hound fix --- openpype/hosts/houdini/plugins/create/create_redshift_rop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_redshift_rop.py b/openpype/hosts/houdini/plugins/create/create_redshift_rop.py index 242915ed6d..e14ff15bf8 100644 --- a/openpype/hosts/houdini/plugins/create/create_redshift_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_redshift_rop.py @@ -55,7 +55,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator): renders_dir=hou.text.expandString("$HIP/pyblish/renders/"), subset_name=subset_name, fmt="${aov}.$F4.{ext}".format(aov="AOV", ext=ext) - ) + ) parms = { # Render frame range From 8830e63fb010d4cea374bdbbf095d81078e08ac2 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 9 May 2023 10:38:14 +0200 Subject: [PATCH 134/347] :recycle: add/delete action from container --- openpype/hosts/max/api/plugin.py | 37 ++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index a464d54b33..8df620b913 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -22,7 +22,8 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" rollout OPparams "OP Parameters" ( listbox list_node "Node References" items:#() - button button_add "Add Selection" + button button_add "Add to Container" + button button_del "Delete from Container" fn node_to_name the_node = ( @@ -34,8 +35,8 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" on button_add pressed do ( - current_selection = selectByName title:"Select Objects To Add To - Container" buttontext:"Add" + current_selection = selectByName title:"Select Objects to add to + the Container" buttontext:"Add" temp_arr = #() i_node_arr = #() for c in current_selection do @@ -45,8 +46,33 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" append temp_arr handle_name append i_node_arr node_ref ) - all_handles = i_node_arr - list_node.items = temp_arr + all_handles = join i_node_arr all_handles + list_node.items = join temp_arr list_node.items + ) + + on button_del pressed do + ( + current_selection = selectByName title:"Select Objects to remove + from the Container" buttontext:"Remove" + temp_arr = #() + i_node_arr = #() + for c in current_selection do + ( + node_ref = NodeTransformMonitor node:c + handle_name = node_to_name c + idx = finditem all_handles node_ref + if idx do + ( + DeleteItem all_nodes idx + ) + idx = finditem list_node.items handle_name + if idx do + ( + DeleteItem list_node.items idx + ) + ) + all_handles = join i_node_arr all_handles + list_node.items = join temp_arr list_node.items ) on OPparams open do @@ -56,7 +82,6 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" temp_arr = #() for x in all_handles do ( - print(x.node) handle_name = node_to_name x.node append temp_arr handle_name ) From 564da974b19f0c16c88b540d0c1fd5cf7b5864cc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 May 2023 21:47:04 +0800 Subject: [PATCH 135/347] add up-versioning to the max loader --- .../hosts/max/plugins/load/load_camera_fbx.py | 31 ++++++++++++++++--- .../hosts/max/plugins/load/load_model_fbx.py | 8 +++++ .../hosts/max/plugins/load/load_model_obj.py | 3 ++ .../hosts/max/plugins/load/load_pointcache.py | 22 ++++++++++--- .../hosts/max/plugins/load/load_pointcloud.py | 14 ++++++--- 5 files changed, 64 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_camera_fbx.py b/openpype/hosts/max/plugins/load/load_camera_fbx.py index 3a6947798e..183a6f5d45 100644 --- a/openpype/hosts/max/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/max/plugins/load/load_camera_fbx.py @@ -4,7 +4,7 @@ from openpype.pipeline import ( get_representation_path ) from openpype.hosts.max.api.pipeline import containerise -from openpype.hosts.max.api import lib +from openpype.hosts.max.api import lib, maintained_selection class FbxLoader(load.LoaderPlugin): @@ -36,7 +36,13 @@ importFile @"{filepath}" #noPrompt using:FBXIMP self.log.debug(f"Executing command: {fbx_import_cmd}") rt.execute(fbx_import_cmd) - container_name = f"{name}_CON" + # create "missing" container for obj import + container = rt.container() + container.name = f"{name}" + + # get current selection + for selection in rt.getCurrentSelection(): + selection.Parent = container asset = rt.getNodeByName(f"{name}") @@ -48,15 +54,30 @@ importFile @"{filepath}" #noPrompt using:FBXIMP path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) + rt.select(node.Children) + fbx_reimport_cmd = ( + f""" - fbx_objects = self.get_container_children(node) - for fbx_object in fbx_objects: - fbx_object.source = path +FBXImporterSetParam "Animation" true +FBXImporterSetParam "Cameras" true +FBXImporterSetParam "AxisConversionMethod" true +FbxExporterSetParam "UpAxis" "Y" +FbxExporterSetParam "Preserveinstances" true + +importFile @"{path}" #noPrompt using:FBXIMP + """) + rt.execute(fbx_reimport_cmd) + + with maintained_selection(): + rt.select(node) lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) }) + def switch(self, container, representation): + self.update(container, representation) + def remove(self, container): from pymxs import runtime as rt diff --git a/openpype/hosts/max/plugins/load/load_model_fbx.py b/openpype/hosts/max/plugins/load/load_model_fbx.py index 88b8f1ed89..b8485ca333 100644 --- a/openpype/hosts/max/plugins/load/load_model_fbx.py +++ b/openpype/hosts/max/plugins/load/load_model_fbx.py @@ -37,6 +37,14 @@ importFile @"{filepath}" #noPrompt using:FBXIMP self.log.debug(f"Executing command: {fbx_import_cmd}") rt.execute(fbx_import_cmd) + # create "missing" container for obj import + container = rt.container() + container.name = f"{name}" + + # get current selection + for selection in rt.getCurrentSelection(): + selection.Parent = container + asset = rt.getNodeByName(f"{name}") return containerise( diff --git a/openpype/hosts/max/plugins/load/load_model_obj.py b/openpype/hosts/max/plugins/load/load_model_obj.py index c55e462111..ae42e1f3d3 100644 --- a/openpype/hosts/max/plugins/load/load_model_obj.py +++ b/openpype/hosts/max/plugins/load/load_model_obj.py @@ -61,6 +61,9 @@ class ObjLoader(load.LoaderPlugin): "representation": str(representation["_id"]) }) + def switch(self, container, representation): + self.update(container, representation) + def remove(self, container): from pymxs import runtime as rt diff --git a/openpype/hosts/max/plugins/load/load_pointcache.py b/openpype/hosts/max/plugins/load/load_pointcache.py index b3e12adc7b..2001b78aba 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache.py +++ b/openpype/hosts/max/plugins/load/load_pointcache.py @@ -9,7 +9,7 @@ from openpype.pipeline import ( load, get_representation_path ) from openpype.hosts.max.api.pipeline import containerise -from openpype.hosts.max.api import lib +from openpype.hosts.max.api import lib, maintained_selection class AbcLoader(load.LoaderPlugin): @@ -65,14 +65,26 @@ importFile @"{file_path}" #noPrompt path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) - alembic_objects = self.get_container_children(node, "AlembicObject") - for alembic_object in alembic_objects: - alembic_object.source = path - lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) }) + rt.select(node.Children) + + for alembic in rt.selection: + abc = rt.getNodeByName(alembic.name) + rt.select(abc.Children) + for abc_con in rt.selection: + container = rt.getNodeByName(abc_con.name) + container.source = path + rt.select(container.Children) + for abc_obj in rt.selection: + alembic_obj = rt.getNodeByName(abc_obj.name) + alembic_obj.source = path + + with maintained_selection(): + rt.select(node) + def switch(self, container, representation): self.update(container, representation) diff --git a/openpype/hosts/max/plugins/load/load_pointcloud.py b/openpype/hosts/max/plugins/load/load_pointcloud.py index 27bc88b4f3..d4ae721c8a 100644 --- a/openpype/hosts/max/plugins/load/load_pointcloud.py +++ b/openpype/hosts/max/plugins/load/load_pointcloud.py @@ -3,7 +3,7 @@ from openpype.pipeline import ( load, get_representation_path ) from openpype.hosts.max.api.pipeline import containerise -from openpype.hosts.max.api import lib +from openpype.hosts.max.api import lib, maintained_selection class PointCloudLoader(load.LoaderPlugin): @@ -34,15 +34,21 @@ class PointCloudLoader(load.LoaderPlugin): path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) + rt.select(node.Children) + for prt in rt.selection: + prt_object = rt.getNodeByName(prt.name) + prt_object.filename = path - prt_objects = self.get_container_children(node) - for prt_object in prt_objects: - prt_object.source = path + with maintained_selection(): + rt.select(node) lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) }) + def switch(self, container, representation): + self.update(container, representation) + def remove(self, container): """remove the container""" from pymxs import runtime as rt From da56583d198002865bcd9530c7b209c5b56ad66e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 May 2023 21:48:33 +0800 Subject: [PATCH 136/347] select the member data before exporting usd in the usd extractor --- openpype/hosts/max/plugins/publish/extract_model_usd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index df1e7a4f02..2500e6c905 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -44,6 +44,7 @@ class ExtractModelUSD(publish.Extractor, with maintained_selection(): # select and export node_list = instance.data["members"] + rt.select(node_list) rt.USDExporter.ExportFile(asset_filepath, exportOptions=export_options, contentSource=rt.Name("selected"), From 33335a3e89c0c4d96af93c8983c29160f59a6135 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 9 May 2023 23:25:37 +0800 Subject: [PATCH 137/347] fix the update and load function of the max scene loader --- .../hosts/max/plugins/load/load_max_scene.py | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_max_scene.py b/openpype/hosts/max/plugins/load/load_max_scene.py index 4b19cd671f..cffc4ae559 100644 --- a/openpype/hosts/max/plugins/load/load_max_scene.py +++ b/openpype/hosts/max/plugins/load/load_max_scene.py @@ -26,20 +26,16 @@ class MaxSceneLoader(load.LoaderPlugin): merge_before = { c for c in rt.rootNode.Children - if rt.classOf(c) == rt.Container } rt.mergeMaxFile(path) merge_after = { c for c in rt.rootNode.Children - if rt.classOf(c) == rt.Container } - max_containers = merge_after.difference(merge_before) - - if len(max_containers) != 1: - self.log.error("Something failed when loading.") - - max_container = max_containers.pop() + max_objects = merge_after.difference(merge_before) + max_container = rt.container(name=f"{name}") + for max_object in max_objects: + max_object.Parent = max_container return containerise( name, [max_container], context, loader=self.__class__.__name__) @@ -48,15 +44,30 @@ class MaxSceneLoader(load.LoaderPlugin): from pymxs import runtime as rt path = get_representation_path(representation) - node = rt.getNodeByName(container["instance_node"]) - max_objects = node.Children + node_name = container["instance_node"] + instance_name, _ = node_name.split("_") + merge_before = { + c for c in rt.rootNode.Children + } + rt.mergeMaxFile(path, + rt.Name("noRedraw"), + rt.Name("deleteOldDups"), + rt.Name("useSceneMtlDups")) + merge_after = { + c for c in rt.rootNode.Children + } + max_objects = merge_after.difference(merge_before) + container_node = rt.getNodeByName(instance_name) for max_object in max_objects: - max_object.source = path + max_object.Parent = container_node lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) }) + def switch(self, container, representation): + self.update(container, representation) + def remove(self, container): from pymxs import runtime as rt From 0ea7b25c4675f84181cd4635ae5d74e2b18b39fd Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 14:50:12 +0100 Subject: [PATCH 138/347] refactor: rt.execute(saveNodes) replaces with pymxs function - changed the function to no longer use the selection and instead feed it the nodes directly from get_all_children function. - removed maintained_seclection() as we're no longer overriding the selection of the Max scene. - black also used to format. --- .../plugins/publish/extract_max_scene_raw.py | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py index c14fcdbd0b..0f1f6f5b3b 100644 --- a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py +++ b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import get_all_children -class ExtractMaxSceneRaw(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractMaxSceneRaw(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Raw Max Scene with SaveSelected """ @@ -20,9 +13,7 @@ class ExtractMaxSceneRaw(publish.Extractor, order = pyblish.api.ExtractorOrder - 0.2 label = "Extract Max Scene (Raw)" hosts = ["max"] - families = ["camera", - "maxScene", - "model"] + families = ["camera", "maxScene", "model"] optional = True def process(self, instance): @@ -37,26 +28,21 @@ class ExtractMaxSceneRaw(publish.Extractor, filename = "{name}.max".format(**instance.data) max_path = os.path.join(stagingdir, filename) - self.log.info("Writing max file '%s' to '%s'" % (filename, - max_path)) + self.log.info("Writing max file '%s' to '%s'" % (filename, max_path)) if "representations" not in instance.data: instance.data["representations"] = [] - # saving max scene - with maintained_selection(): - # need to figure out how to select the camera - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(f'saveNodes selection "{max_path}" quiet:true') + nodes = get_all_children(rt.getNodeByName(container)) + rt.saveNodes(nodes, max_path, quiet=True) self.log.info("Performing Extraction ...") representation = { - 'name': 'max', - 'ext': 'max', - 'files': filename, + "name": "max", + "ext": "max", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - max_path)) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, max_path)) From 67cd145ce2cca0b0979eb017813713159eb413ed Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 15:05:00 +0100 Subject: [PATCH 139/347] refactor: replaced rt.export string with proper pymxs implementation - black used for formatting - moved the general flow around as each function call is now seperate instead of large string --- .../max/plugins/publish/extract_camera_abc.py | 45 ++++++------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py index 8c23ff9878..3ca72abd88 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_abc.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children -class ExtractCameraAlembic(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Camera with AlembicExport """ @@ -38,38 +31,28 @@ class ExtractCameraAlembic(publish.Extractor, path = os.path.join(stagingdir, filename) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (filename, - stagingdir)) + self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) - export_cmd = ( - f""" -AlembicExport.ArchiveType = #ogawa -AlembicExport.CoordinateSystem = #maya -AlembicExport.StartFrame = {start} -AlembicExport.EndFrame = {end} -AlembicExport.CustomAttributes = true - -exportFile @"{path}" #noPrompt selectedOnly:on using:AlembicExport - - """) - - self.log.debug(f"Executing command: {export_cmd}") + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.StartFrame = start + rt.AlembicExport.EndFrame = end + rt.AlembicExport.CustomAttributes = True with maintained_selection(): # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(export_cmd) + rt.exportFile(path, selectedOnly=True, using="AlembicExport", noPrompt=True) self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': filename, + "name": "abc", + "ext": "abc", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - path)) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) From 8f5b14ad243153953e273a686453a2b50ee4a329 Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 15:13:13 +0100 Subject: [PATCH 140/347] refactor: replaced rt.execute with pymxs implementation --- .../max/plugins/publish/extract_camera_fbx.py | 50 ++++++------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py index 7e92f355ed..c216e726dc 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children -class ExtractCameraFbx(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Camera with FbxExporter """ @@ -33,43 +26,28 @@ class ExtractCameraFbx(publish.Extractor, filename = "{name}.fbx".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info("Writing fbx file '%s' to '%s'" % (filename, - filepath)) + self.log.info("Writing fbx file '%s' to '%s'" % (filename, filepath)) - # Need to export: - # Animation = True - # Cameras = True - # AxisConversionMethod - fbx_export_cmd = ( - f""" - -FBXExporterSetParam "Animation" true -FBXExporterSetParam "Cameras" true -FBXExporterSetParam "AxisConversionMethod" "Animation" -FbxExporterSetParam "UpAxis" "Y" -FbxExporterSetParam "Preserveinstances" true - -exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP - - """) - - self.log.debug(f"Executing command: {fbx_export_cmd}") + rt.FBXExporterSetParam("Animation", True) + rt.FBXExporterSetParam("Cameras", True) + rt.FBXExporterSetParam("AxisConversionMethod", "Animation") + rt.FBXExporterSetParam("UpAxis", "Y") + rt.FBXExporterSetParam("Preserveinstances", True) with maintained_selection(): # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(fbx_export_cmd) + rt.exportFile(filepath, selectedOnly=True, using="FBXEXP", noPrompt=True) self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'fbx', - 'ext': 'fbx', - 'files': filename, + "name": "fbx", + "ext": "fbx", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, filepath)) From 63c463f618cbc8a94053117b02461cc4d67ae838 Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 15:20:59 +0100 Subject: [PATCH 141/347] refactor: replaced rt.execute with proper pymxs --- .../max/plugins/publish/extract_model.py | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_model.py b/openpype/hosts/max/plugins/publish/extract_model.py index 710ad5f97d..23fe59954c 100644 --- a/openpype/hosts/max/plugins/publish/extract_model.py +++ b/openpype/hosts/max/plugins/publish/extract_model.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children -class ExtractModel(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Geometry in Alembic Format """ @@ -36,39 +29,31 @@ class ExtractModel(publish.Extractor, filepath = os.path.join(stagingdir, filename) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (filename, - stagingdir)) + self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) - export_cmd = ( - f""" -AlembicExport.ArchiveType = #ogawa -AlembicExport.CoordinateSystem = #maya -AlembicExport.CustomAttributes = true -AlembicExport.UVs = true -AlembicExport.VertexColors = true -AlembicExport.PreserveInstances = true - -exportFile @"{filepath}" #noPrompt selectedOnly:on using:AlembicExport - - """) - - self.log.debug(f"Executing command: {export_cmd}") + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.CustomAttributes = True + rt.AlembicExport.UVs = True + rt.AlembicExport.VertexColors = True + rt.AlembicExport.PreserveInstances = True with maintained_selection(): # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(export_cmd) + rt.exportFile( + filepath, selectedOnly=True, using="AlembicExport", noPrompt=True + ) self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': filename, + "name": "abc", + "ext": "abc", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, filepath)) From 28078c0508598f7413dd9851a35f3d56f3ce05a1 Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 15:48:55 +0100 Subject: [PATCH 142/347] refactor: replaced rt.execute where possible --- .../max/plugins/publish/extract_model_fbx.py | 55 +++++++------------ 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_model_fbx.py b/openpype/hosts/max/plugins/publish/extract_model_fbx.py index ce58e8cc17..e2bbac4ac2 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_model_fbx.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children -class ExtractModelFbx(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Geometry in FBX Format """ @@ -33,42 +26,32 @@ class ExtractModelFbx(publish.Extractor, stagingdir = self.staging_dir(instance) filename = "{name}.fbx".format(**instance.data) - filepath = os.path.join(stagingdir, - filename) - self.log.info("Writing FBX '%s' to '%s'" % (filepath, - stagingdir)) - - export_fbx_cmd = ( - f""" -FBXExporterSetParam "Animation" false -FBXExporterSetParam "Cameras" false -FBXExporterSetParam "Lights" false -FBXExporterSetParam "PointCache" false -FBXExporterSetParam "AxisConversionMethod" "Animation" -FbxExporterSetParam "UpAxis" "Y" -FbxExporterSetParam "Preserveinstances" true - -exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP - - """) - - self.log.debug(f"Executing command: {export_fbx_cmd}") + filepath = os.path.join(stagingdir, filename) + self.log.info("Writing FBX '%s' to '%s'" % (filepath, stagingdir)) with maintained_selection(): + rt.FBXExporterSetParam("Animation", False) + rt.FBXExporterSetParam("Cameras", False) + rt.FBXExporterSetParam("Lights", False) + rt.FBXExporterSetParam("PointCache", False) + rt.FBXExporterSetParam("AxisConversionMethod", "Animation") + rt.FBXExporterSetParam("UpAxis", "Y") + rt.FBXExporterSetParam("Preserveinstances", True) # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(export_fbx_cmd) + rt.execute( + f'exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP' + ) self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'fbx', - 'ext': 'fbx', - 'files': filename, + "name": "fbx", + "ext": "fbx", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, filepath)) From 1d7edf1fb2982353a2be3ea3405b20c1fb9479a4 Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 16:00:17 +0100 Subject: [PATCH 143/347] fix: pymxs terrible argument handling - noPrompt doesn't seem to work unless you call rt.name and is also positional - using doesn't work as a string you need to feed it the actual rt object --- .../hosts/max/plugins/publish/extract_model.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_model.py b/openpype/hosts/max/plugins/publish/extract_model.py index 23fe59954c..56e791d2e7 100644 --- a/openpype/hosts/max/plugins/publish/extract_model.py +++ b/openpype/hosts/max/plugins/publish/extract_model.py @@ -31,18 +31,17 @@ class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin): # We run the render self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) - rt.AlembicExport.ArchiveType = rt.name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.name("maya") - rt.AlembicExport.CustomAttributes = True - rt.AlembicExport.UVs = True - rt.AlembicExport.VertexColors = True - rt.AlembicExport.PreserveInstances = True - with maintained_selection(): + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.CustomAttributes = True + rt.AlembicExport.UVs = True + rt.AlembicExport.VertexColors = True + rt.AlembicExport.PreserveInstances = True # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( - filepath, selectedOnly=True, using="AlembicExport", noPrompt=True + filepath, rt.name("noPrompt"), selectedOnly=True, using=rt.AlembicExport ) self.log.info("Performing Extraction ...") From b3b07cec7cc772e15bdf03510cb8c1ba2808a5e9 Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 16:04:38 +0100 Subject: [PATCH 144/347] fix: exportFile to use correct arguments --- .../max/plugins/publish/extract_camera_abc.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py index 3ca72abd88..db96470f17 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_abc.py @@ -33,16 +33,17 @@ class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): # We run the render self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) - rt.AlembicExport.ArchiveType = rt.name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.name("maya") - rt.AlembicExport.StartFrame = start - rt.AlembicExport.EndFrame = end - rt.AlembicExport.CustomAttributes = True - with maintained_selection(): + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.StartFrame = start + rt.AlembicExport.EndFrame = end + rt.AlembicExport.CustomAttributes = True # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.exportFile(path, selectedOnly=True, using="AlembicExport", noPrompt=True) + rt.exportFile( + path, rt.name("noPrompt"), selectedOnly=True, using=rt.AlembicExport + ) self.log.info("Performing Extraction ...") if "representations" not in instance.data: From 4f2951b6ec64bacdaec96e20693436aecbd89dad Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 16:07:12 +0100 Subject: [PATCH 145/347] fix: rt.exportfile args --- .../max/plugins/publish/extract_camera_fbx.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py index c216e726dc..16dea0b41e 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py @@ -28,16 +28,17 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) self.log.info("Writing fbx file '%s' to '%s'" % (filename, filepath)) - rt.FBXExporterSetParam("Animation", True) - rt.FBXExporterSetParam("Cameras", True) - rt.FBXExporterSetParam("AxisConversionMethod", "Animation") - rt.FBXExporterSetParam("UpAxis", "Y") - rt.FBXExporterSetParam("Preserveinstances", True) - with maintained_selection(): + rt.FBXExporterSetParam("Animation", True) + rt.FBXExporterSetParam("Cameras", True) + rt.FBXExporterSetParam("AxisConversionMethod", "Animation") + rt.FBXExporterSetParam("UpAxis", "Y") + rt.FBXExporterSetParam("Preserveinstances", True) # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.exportFile(filepath, selectedOnly=True, using="FBXEXP", noPrompt=True) + rt.exportFile( + filepath, rt.name("noPrompt"), selectedOnly=True, using=rt.FBXEXP + ) self.log.info("Performing Extraction ...") if "representations" not in instance.data: From 069bcba72f459245fecb025870f92537a4e6b1f7 Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 16:16:57 +0100 Subject: [PATCH 146/347] fix: exportfile args --- .../max/plugins/publish/extract_model_obj.py | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_model_obj.py b/openpype/hosts/max/plugins/publish/extract_model_obj.py index 7bda237880..3d98f37263 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_obj.py +++ b/openpype/hosts/max/plugins/publish/extract_model_obj.py @@ -1,18 +1,11 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children -class ExtractModelObj(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Geometry in OBJ Format """ @@ -33,27 +26,26 @@ class ExtractModelObj(publish.Extractor, stagingdir = self.staging_dir(instance) filename = "{name}.obj".format(**instance.data) - filepath = os.path.join(stagingdir, - filename) - self.log.info("Writing OBJ '%s' to '%s'" % (filepath, - stagingdir)) + filepath = os.path.join(stagingdir, filename) + self.log.info("Writing OBJ '%s' to '%s'" % (filepath, stagingdir)) with maintained_selection(): # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(f'exportFile @"{filepath}" #noPrompt selectedOnly:true using:ObjExp') # noqa + rt.exportFile( + filepath, rt.name("noPrompt"), selectedOnly=True, using=rt.ObjExp + ) self.log.info("Performing Extraction ...") if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'obj', - 'ext': 'obj', - 'files': filename, + "name": "obj", + "ext": "obj", + "files": filename, "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, - filepath)) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, filepath)) From cc23df7a13cf86074e28fcdcab0559e2506e3012 Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 16:17:11 +0100 Subject: [PATCH 147/347] refactor: replaced rt.execute with proper function --- openpype/hosts/max/plugins/publish/extract_model_fbx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_model_fbx.py b/openpype/hosts/max/plugins/publish/extract_model_fbx.py index e2bbac4ac2..0ffec94a59 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_model_fbx.py @@ -39,8 +39,8 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): rt.FBXExporterSetParam("Preserveinstances", True) # select and export rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute( - f'exportFile @"{filepath}" #noPrompt selectedOnly:true using:FBXEXP' + rt.exportFile( + filepath, rt.name("noPrompt"), selectedOnly=True, using=rt.FBXEXP ) self.log.info("Performing Extraction ...") From dc39fafffd0cd0a5bb3940e9115e7d35e28eaec6 Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 16:23:32 +0100 Subject: [PATCH 148/347] refactor: removed use of rt.execute and replaced with pymxs --- .../max/plugins/publish/extract_pointcache.py | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcache.py b/openpype/hosts/max/plugins/publish/extract_pointcache.py index 75d8a7972c..0936a149f3 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcache.py @@ -41,10 +41,7 @@ import os import pyblish.api from openpype.pipeline import publish from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection, - get_all_children -) +from openpype.hosts.max.api import maintained_selection, get_all_children class ExtractAlembic(publish.Extractor): @@ -66,35 +63,26 @@ class ExtractAlembic(publish.Extractor): path = os.path.join(parent_dir, file_name) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (file_name, - parent_dir)) - - abc_export_cmd = ( - f""" -AlembicExport.ArchiveType = #ogawa -AlembicExport.CoordinateSystem = #maya -AlembicExport.StartFrame = {start} -AlembicExport.EndFrame = {end} - -exportFile @"{path}" #noPrompt selectedOnly:on using:AlembicExport - - """) - - self.log.debug(f"Executing command: {abc_export_cmd}") + self.log.info("Writing alembic '%s' to '%s'" % (file_name, parent_dir)) with maintained_selection(): + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.StartFrame = start + rt.AlembicExport.EndFrame = end # select and export - rt.select(get_all_children(rt.getNodeByName(container))) - rt.execute(abc_export_cmd) + rt.exportFile( + path, rt.name("noPrompt"), selectedOnly=True, using=rt.AlembicExport + ) if "representations" not in instance.data: instance.data["representations"] = [] representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': file_name, + "name": "abc", + "ext": "abc", + "files": file_name, "stagingDir": parent_dir, } instance.data["representations"].append(representation) From b229b992a2a8dcbaa9865be2b3c423d757c30fbb Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 16:29:04 +0100 Subject: [PATCH 149/347] refactor: removed configs from maintained_selection() block --- .../max/plugins/publish/extract_camera_abc.py | 11 ++++++----- .../max/plugins/publish/extract_camera_fbx.py | 11 ++++++----- .../hosts/max/plugins/publish/extract_model.py | 13 +++++++------ .../max/plugins/publish/extract_model_fbx.py | 15 ++++++++------- .../max/plugins/publish/extract_pointcache.py | 9 +++++---- 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py index db96470f17..32d9ab9317 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_abc.py @@ -33,12 +33,13 @@ class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): # We run the render self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.StartFrame = start + rt.AlembicExport.EndFrame = end + rt.AlembicExport.CustomAttributes = True + with maintained_selection(): - rt.AlembicExport.ArchiveType = rt.name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.name("maya") - rt.AlembicExport.StartFrame = start - rt.AlembicExport.EndFrame = end - rt.AlembicExport.CustomAttributes = True # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( diff --git a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py index 16dea0b41e..f865f7ac6a 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py @@ -28,12 +28,13 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) self.log.info("Writing fbx file '%s' to '%s'" % (filename, filepath)) + rt.FBXExporterSetParam("Animation", True) + rt.FBXExporterSetParam("Cameras", True) + rt.FBXExporterSetParam("AxisConversionMethod", "Animation") + rt.FBXExporterSetParam("UpAxis", "Y") + rt.FBXExporterSetParam("Preserveinstances", True) + with maintained_selection(): - rt.FBXExporterSetParam("Animation", True) - rt.FBXExporterSetParam("Cameras", True) - rt.FBXExporterSetParam("AxisConversionMethod", "Animation") - rt.FBXExporterSetParam("UpAxis", "Y") - rt.FBXExporterSetParam("Preserveinstances", True) # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( diff --git a/openpype/hosts/max/plugins/publish/extract_model.py b/openpype/hosts/max/plugins/publish/extract_model.py index 56e791d2e7..d4d59df29c 100644 --- a/openpype/hosts/max/plugins/publish/extract_model.py +++ b/openpype/hosts/max/plugins/publish/extract_model.py @@ -31,13 +31,14 @@ class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin): # We run the render self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.CustomAttributes = True + rt.AlembicExport.UVs = True + rt.AlembicExport.VertexColors = True + rt.AlembicExport.PreserveInstances = True + with maintained_selection(): - rt.AlembicExport.ArchiveType = rt.name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.name("maya") - rt.AlembicExport.CustomAttributes = True - rt.AlembicExport.UVs = True - rt.AlembicExport.VertexColors = True - rt.AlembicExport.PreserveInstances = True # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( diff --git a/openpype/hosts/max/plugins/publish/extract_model_fbx.py b/openpype/hosts/max/plugins/publish/extract_model_fbx.py index 0ffec94a59..eefe5e7e72 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_model_fbx.py @@ -29,14 +29,15 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): filepath = os.path.join(stagingdir, filename) self.log.info("Writing FBX '%s' to '%s'" % (filepath, stagingdir)) + rt.FBXExporterSetParam("Animation", False) + rt.FBXExporterSetParam("Cameras", False) + rt.FBXExporterSetParam("Lights", False) + rt.FBXExporterSetParam("PointCache", False) + rt.FBXExporterSetParam("AxisConversionMethod", "Animation") + rt.FBXExporterSetParam("UpAxis", "Y") + rt.FBXExporterSetParam("Preserveinstances", True) + with maintained_selection(): - rt.FBXExporterSetParam("Animation", False) - rt.FBXExporterSetParam("Cameras", False) - rt.FBXExporterSetParam("Lights", False) - rt.FBXExporterSetParam("PointCache", False) - rt.FBXExporterSetParam("AxisConversionMethod", "Animation") - rt.FBXExporterSetParam("UpAxis", "Y") - rt.FBXExporterSetParam("Preserveinstances", True) # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( diff --git a/openpype/hosts/max/plugins/publish/extract_pointcache.py b/openpype/hosts/max/plugins/publish/extract_pointcache.py index 0936a149f3..84352b489e 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcache.py @@ -65,11 +65,12 @@ class ExtractAlembic(publish.Extractor): # We run the render self.log.info("Writing alembic '%s' to '%s'" % (file_name, parent_dir)) + rt.AlembicExport.ArchiveType = rt.name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.StartFrame = start + rt.AlembicExport.EndFrame = end + with maintained_selection(): - rt.AlembicExport.ArchiveType = rt.name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.name("maya") - rt.AlembicExport.StartFrame = start - rt.AlembicExport.EndFrame = end # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( From 4a128cc59b0f974181805dac69252fa4c559e916 Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 18:06:33 +0100 Subject: [PATCH 150/347] refactor: updated black to be 79 charlines --- openpype/hosts/max/plugins/publish/extract_camera_abc.py | 5 ++++- openpype/hosts/max/plugins/publish/extract_camera_fbx.py | 9 +++++++-- .../hosts/max/plugins/publish/extract_max_scene_raw.py | 4 +++- openpype/hosts/max/plugins/publish/extract_model.py | 9 +++++++-- openpype/hosts/max/plugins/publish/extract_model_fbx.py | 9 +++++++-- openpype/hosts/max/plugins/publish/extract_model_obj.py | 9 +++++++-- openpype/hosts/max/plugins/publish/extract_pointcache.py | 5 ++++- 7 files changed, 39 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py index 32d9ab9317..6b3bb178a3 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_abc.py @@ -43,7 +43,10 @@ class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( - path, rt.name("noPrompt"), selectedOnly=True, using=rt.AlembicExport + path, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.AlembicExport, ) self.log.info("Performing Extraction ...") diff --git a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py index f865f7ac6a..4b4b349e19 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py @@ -38,7 +38,10 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( - filepath, rt.name("noPrompt"), selectedOnly=True, using=rt.FBXEXP + filepath, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.FBXEXP, ) self.log.info("Performing Extraction ...") @@ -52,4 +55,6 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, filepath)) + self.log.info( + "Extracted instance '%s' to: %s" % (instance.name, filepath) + ) diff --git a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py index 0f1f6f5b3b..f0c2aff7f3 100644 --- a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py +++ b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py @@ -45,4 +45,6 @@ class ExtractMaxSceneRaw(publish.Extractor, OptionalPyblishPluginMixin): "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, max_path)) + self.log.info( + "Extracted instance '%s' to: %s" % (instance.name, max_path) + ) diff --git a/openpype/hosts/max/plugins/publish/extract_model.py b/openpype/hosts/max/plugins/publish/extract_model.py index d4d59df29c..4c7c98e2cc 100644 --- a/openpype/hosts/max/plugins/publish/extract_model.py +++ b/openpype/hosts/max/plugins/publish/extract_model.py @@ -42,7 +42,10 @@ class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin): # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( - filepath, rt.name("noPrompt"), selectedOnly=True, using=rt.AlembicExport + filepath, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.AlembicExport, ) self.log.info("Performing Extraction ...") @@ -56,4 +59,6 @@ class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin): "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, filepath)) + self.log.info( + "Extracted instance '%s' to: %s" % (instance.name, filepath) + ) diff --git a/openpype/hosts/max/plugins/publish/extract_model_fbx.py b/openpype/hosts/max/plugins/publish/extract_model_fbx.py index eefe5e7e72..e6ccb24cdd 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_model_fbx.py @@ -41,7 +41,10 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( - filepath, rt.name("noPrompt"), selectedOnly=True, using=rt.FBXEXP + filepath, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.FBXEXP, ) self.log.info("Performing Extraction ...") @@ -55,4 +58,6 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, filepath)) + self.log.info( + "Extracted instance '%s' to: %s" % (instance.name, filepath) + ) diff --git a/openpype/hosts/max/plugins/publish/extract_model_obj.py b/openpype/hosts/max/plugins/publish/extract_model_obj.py index 3d98f37263..ed3d68c990 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_obj.py +++ b/openpype/hosts/max/plugins/publish/extract_model_obj.py @@ -33,7 +33,10 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( - filepath, rt.name("noPrompt"), selectedOnly=True, using=rt.ObjExp + filepath, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.ObjExp, ) self.log.info("Performing Extraction ...") @@ -48,4 +51,6 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, filepath)) + self.log.info( + "Extracted instance '%s' to: %s" % (instance.name, filepath) + ) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcache.py b/openpype/hosts/max/plugins/publish/extract_pointcache.py index 84352b489e..8658cecb1b 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcache.py @@ -74,7 +74,10 @@ class ExtractAlembic(publish.Extractor): # select and export rt.select(get_all_children(rt.getNodeByName(container))) rt.exportFile( - path, rt.name("noPrompt"), selectedOnly=True, using=rt.AlembicExport + path, + rt.name("noPrompt"), + selectedOnly=True, + using=rt.AlembicExport, ) if "representations" not in instance.data: From afb6bd9ba53f6fb030eaed635ae18d456a04e0d5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 23 May 2023 00:57:15 +0800 Subject: [PATCH 151/347] restore the code in abc extractor --- openpype/hosts/max/plugins/publish/extract_camera_abc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py index 0a4f64508a..d53c47fb51 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_abc.py @@ -19,8 +19,8 @@ class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): def process(self, instance): if not self.is_active(instance.data): return - start = float(instance.data.get("frameStart", 1)) - end = float(instance.data.get("frameEnd", 1)) + start = float(instance.data.get("frameStartHandle", 1)) + end = float(instance.data.get("frameEndHandle", 1)) container = instance.data["instance_node"] From 63d820436599fb7f3dfe54b6d86b58cbb1e35639 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 23 May 2023 15:46:16 +0800 Subject: [PATCH 152/347] Jakub's comment --- openpype/hosts/max/plugins/create/create_render.py | 2 +- openpype/hosts/max/plugins/publish/extract_pointcloud.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 9b677a615f..4523d3d411 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -23,6 +23,6 @@ class CreateRender(plugin.MaxCreator): sel_obj = self.selected_nodes if sel_obj: # set viewport camera for rendering(mandatory for deadline) - RenderSettings().set_render_camera(sel_obj) + RenderSettings(self.project_settings).set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) RenderSettings().render_output(container_name) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index c807885859..9e1e7fdc72 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -5,7 +5,6 @@ from pymxs import runtime as rt from openpype.hosts.max.api import ( maintained_selection ) -from openpype.settings import get_project_settings class ExtractPointCloud(publish.Extractor): @@ -148,9 +147,7 @@ class ExtractPointCloud(publish.Extractor): @staticmethod def get_setting(instance): - project_setting = get_project_settings( - instance.context.data["projectName"] - ) + project_setting = instance.context.data["project_settings"] return project_setting["max"]["PointCloud"] def get_custom_attr(self, operator): From 8ac4cb499e9106c6f08b5beb2e933102a6b3ca3e Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 23 May 2023 15:50:07 +0200 Subject: [PATCH 153/347] :rotating_light: style changes --- openpype/hosts/max/api/lib.py | 20 ++- openpype/hosts/max/api/plugin.py | 52 ++++--- .../hosts/max/plugins/create/create_render.py | 5 +- .../hosts/max/plugins/load/load_camera_fbx.py | 30 ++-- .../hosts/max/plugins/load/load_max_scene.py | 37 ++--- openpype/hosts/max/plugins/load/load_model.py | 45 +++--- .../hosts/max/plugins/load/load_model_fbx.py | 34 ++-- .../hosts/max/plugins/load/load_model_obj.py | 40 +++-- .../hosts/max/plugins/load/load_model_usd.py | 28 ++-- .../hosts/max/plugins/load/load_pointcache.py | 52 +++---- .../hosts/max/plugins/load/load_pointcloud.py | 33 ++-- .../max/plugins/publish/collect_members.py | 7 +- .../max/plugins/publish/extract_camera_abc.py | 24 +-- .../max/plugins/publish/extract_camera_fbx.py | 22 ++- .../max/plugins/publish/extract_model_usd.py | 17 +- .../max/plugins/publish/extract_pointcloud.py | 13 +- .../publish/validate_model_contents.py | 3 +- poetry.lock | 24 +-- setup.cfg | 146 +----------------- 19 files changed, 221 insertions(+), 411 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index 132896805f..5718d8f112 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -1,16 +1,13 @@ # -*- coding: utf-8 -*- """Library of functions useful for 3dsmax pipeline.""" -import json -import six -from pymxs import runtime as rt -from typing import Union, Any, Dict import contextlib +import json +from typing import Any, Dict, Union +import six from openpype.pipeline.context_tools import ( - get_current_project_asset, - get_current_project -) - + get_current_project, get_current_project_asset,) +from pymxs import runtime as rt JSON_PREFIX = "JSON::" @@ -22,7 +19,7 @@ def imprint(node_name: str, data: dict) -> bool: for k, v in data.items(): if isinstance(v, (dict, list)): - rt.SetUserProp(node, k, f'{JSON_PREFIX}{json.dumps(v)}') + rt.SetUserProp(node, k, f"{JSON_PREFIX}{json.dumps(v)}") else: rt.SetUserProp(node, k, v) @@ -171,7 +168,7 @@ def set_scene_resolution(width: int, height: int): """ # make sure the render dialog is closed # for the update of resolution - # Changing the Render Setup dialog settingsshould be done + # Changing the Render Setup dialog settings should be done # with the actual Render Setup dialog in a closed state. if rt.renderSceneDialog.isOpen(): rt.renderSceneDialog.close() @@ -179,6 +176,7 @@ def set_scene_resolution(width: int, height: int): rt.renderWidth = width rt.renderHeight = height + def reset_scene_resolution(): """Apply the scene resolution from the project definition @@ -248,7 +246,7 @@ def reset_frame_range(fps: bool = True): frange_cmd = ( f"animationRange = interval {frame_start_handle} {frame_end_handle}" ) - rt.execute(frange_cmd) + rt.Execute(frange_cmd) set_render_frame_range(frame_start_handle, frame_end_handle) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 8df620b913..583e9dc1fb 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -1,16 +1,14 @@ # -*- coding: utf-8 -*- """3dsmax specific Avalon/Pyblish plugin definitions.""" -from pymxs import runtime as rt -from typing import Union -import six from abc import ABCMeta -from openpype.pipeline import ( - CreatorError, - Creator, - CreatedInstance -) + +import six +from pymxs import runtime as rt + from openpype.lib import BoolDef -from .lib import imprint, read, lsattr +from openpype.pipeline import CreatedInstance, Creator, CreatorError + +from .lib import imprint, lsattr, read MS_CUSTOM_ATTRIB = """attributes "openPypeData" ( @@ -100,17 +98,18 @@ class MaxCreatorBase(object): @staticmethod def cache_subsets(shared_data): - if shared_data.get("max_cached_subsets") is None: - shared_data["max_cached_subsets"] = {} - cached_instances = lsattr("id", "pyblish.avalon.instance") - for i in cached_instances: - creator_id = rt.getUserProp(i, "creator_identifier") - if creator_id not in shared_data["max_cached_subsets"]: - shared_data["max_cached_subsets"][creator_id] = [i.name] - else: - shared_data[ - "max_cached_subsets"][creator_id].append( - i.name) # noqa + if shared_data.get("max_cached_subsets"): + return shared_data + + shared_data["max_cached_subsets"] = {} + cached_instances = lsattr("id", "pyblish.avalon.instance") + for i in cached_instances: + creator_id = rt.GetUserProp(i, "creator_identifier") + if creator_id not in shared_data["max_cached_subsets"]: + shared_data["max_cached_subsets"][creator_id] = [i.name] + else: + shared_data[ + "max_cached_subsets"][creator_id].append(i.name) return shared_data @staticmethod @@ -127,9 +126,9 @@ class MaxCreatorBase(object): instance """ if isinstance(node, str): - node = rt.container(name=node) + node = rt.Container(name=node) - attrs = rt.execute(MS_CUSTOM_ATTRIB) + attrs = rt.Execute(MS_CUSTOM_ATTRIB) rt.custAttributes.add(node.baseObject, attrs) return node @@ -141,7 +140,7 @@ class MaxCreator(Creator, MaxCreatorBase): def create(self, subset_name, instance_data, pre_create_data): if pre_create_data.get("use_selection"): - self.selected_nodes = rt.getCurrentSelection() + self.selected_nodes = rt.GetCurrentSelection() instance_node = self.create_instance_node(subset_name) instance_data["instance_node"] = instance_node.name @@ -196,9 +195,12 @@ class MaxCreator(Creator, MaxCreatorBase): """ for instance in instances: - if instance_node := rt.GetNodeByName(instance.data.get("instance_node")): # noqa + if instance_node := rt.GetNodeByName( + instance.data.get("instance_node")): rt.Select(instance_node) - rt.execute(f'for o in selection do for c in o.children do c.parent = undefined') # noqa + rt.Execute( + ("for o in selection do " + "for c in o.children do c.parent = undefined")) rt.Delete(instance_node) self._remove_instance_from_context(instance) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 4523d3d411..5b35453579 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -18,10 +18,7 @@ class CreateRender(plugin.MaxCreator): instance_data, pre_create_data) container_name = instance.data.get("instance_node") - # TODO: Disable "Add to Containers?" Panel - # parent the selected cameras into the container - sel_obj = self.selected_nodes - if sel_obj: + if sel_obj := self.selected_nodes: # set viewport camera for rendering(mandatory for deadline) RenderSettings(self.project_settings).set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) diff --git a/openpype/hosts/max/plugins/load/load_camera_fbx.py b/openpype/hosts/max/plugins/load/load_camera_fbx.py index 35d7f4bad2..c51900dbb7 100644 --- a/openpype/hosts/max/plugins/load/load_camera_fbx.py +++ b/openpype/hosts/max/plugins/load/load_camera_fbx.py @@ -1,14 +1,12 @@ import os -from openpype.pipeline import ( - load, - get_representation_path -) -from openpype.hosts.max.api.pipeline import containerise + from openpype.hosts.max.api import lib, maintained_selection +from openpype.hosts.max.api.pipeline import containerise +from openpype.pipeline import get_representation_path, load class FbxLoader(load.LoaderPlugin): - """Fbx Loader""" + """Fbx Loader.""" families = ["camera"] representations = ["fbx"] @@ -24,17 +22,17 @@ class FbxLoader(load.LoaderPlugin): rt.FBXImporterSetParam("Camera", True) rt.FBXImporterSetParam("AxisConversionMethod", True) rt.FBXImporterSetParam("Preserveinstances", True) - rt.importFile( + rt.ImportFile( filepath, rt.name("noPrompt"), using=rt.FBXIMP) - container = rt.getNodeByName(f"{name}") + container = rt.GetNodeByName(f"{name}") if not container: - container = rt.container() + container = rt.Container() container.name = f"{name}" - for selection in rt.getCurrentSelection(): + for selection in rt.GetCurrentSelection(): selection.Parent = container return containerise( @@ -44,8 +42,8 @@ class FbxLoader(load.LoaderPlugin): from pymxs import runtime as rt path = get_representation_path(representation) - node = rt.getNodeByName(container["instance_node"]) - rt.select(node.Children) + node = rt.GetNodeByName(container["instance_node"]) + rt.Select(node.Children) fbx_reimport_cmd = ( f""" @@ -57,10 +55,10 @@ FbxExporterSetParam "Preserveinstances" true importFile @"{path}" #noPrompt using:FBXIMP """) - rt.execute(fbx_reimport_cmd) + rt.Execute(fbx_reimport_cmd) with maintained_selection(): - rt.select(node) + rt.Select(node) lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) @@ -72,5 +70,5 @@ importFile @"{path}" #noPrompt using:FBXIMP def remove(self, container): from pymxs import runtime as rt - node = rt.getNodeByName(container["instance_node"]) - rt.delete(node) + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) diff --git a/openpype/hosts/max/plugins/load/load_max_scene.py b/openpype/hosts/max/plugins/load/load_max_scene.py index cffc4ae559..4d9367b16f 100644 --- a/openpype/hosts/max/plugins/load/load_max_scene.py +++ b/openpype/hosts/max/plugins/load/load_max_scene.py @@ -1,13 +1,12 @@ import os -from openpype.pipeline import ( - load, get_representation_path -) -from openpype.hosts.max.api.pipeline import containerise + from openpype.hosts.max.api import lib +from openpype.hosts.max.api.pipeline import containerise +from openpype.pipeline import get_representation_path, load class MaxSceneLoader(load.LoaderPlugin): - """Max Scene Loader""" + """Max Scene Loader.""" families = ["camera", "maxScene", @@ -24,16 +23,12 @@ class MaxSceneLoader(load.LoaderPlugin): # import the max scene by using "merge file" path = path.replace('\\', '/') - merge_before = { - c for c in rt.rootNode.Children - } - rt.mergeMaxFile(path) + merge_before = set(rt.RootNode.Children) + rt.MergeMaxFile(path) - merge_after = { - c for c in rt.rootNode.Children - } + merge_after = set(rt.RootNode.Children) max_objects = merge_after.difference(merge_before) - max_container = rt.container(name=f"{name}") + max_container = rt.Container(name=f"{name}") for max_object in max_objects: max_object.Parent = max_container @@ -46,18 +41,14 @@ class MaxSceneLoader(load.LoaderPlugin): path = get_representation_path(representation) node_name = container["instance_node"] instance_name, _ = node_name.split("_") - merge_before = { - c for c in rt.rootNode.Children - } - rt.mergeMaxFile(path, + merge_before = set(rt.RootNode.Children) + rt.MergeMaxFile(path, rt.Name("noRedraw"), rt.Name("deleteOldDups"), rt.Name("useSceneMtlDups")) - merge_after = { - c for c in rt.rootNode.Children - } + merge_after = set(rt.EootNode.Children) max_objects = merge_after.difference(merge_before) - container_node = rt.getNodeByName(instance_name) + container_node = rt.GetNodeByName(instance_name) for max_object in max_objects: max_object.Parent = container_node @@ -71,5 +62,5 @@ class MaxSceneLoader(load.LoaderPlugin): def remove(self, container): from pymxs import runtime as rt - node = rt.getNodeByName(container["instance_node"]) - rt.delete(node) + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) diff --git a/openpype/hosts/max/plugins/load/load_model.py b/openpype/hosts/max/plugins/load/load_model.py index 95ee014e07..662b9fcb87 100644 --- a/openpype/hosts/max/plugins/load/load_model.py +++ b/openpype/hosts/max/plugins/load/load_model.py @@ -1,11 +1,10 @@ import os -from openpype.pipeline import ( - load, get_representation_path -) -from openpype.hosts.max.api.pipeline import containerise + from openpype.hosts.max.api import lib from openpype.hosts.max.api.lib import maintained_selection +from openpype.hosts.max.api.pipeline import containerise +from openpype.pipeline import get_representation_path, load class ModelAbcLoader(load.LoaderPlugin): @@ -24,8 +23,8 @@ class ModelAbcLoader(load.LoaderPlugin): file_path = os.path.normpath(self.fname) abc_before = { - c for c in rt.rootNode.Children - if rt.classOf(c) == rt.AlembicContainer + c for c in rt.RootNode.Children + if rt.ClassOf(c) == rt.AlembicContainer } abc_import_cmd = (f""" @@ -38,11 +37,11 @@ importFile @"{file_path}" #noPrompt """) self.log.debug(f"Executing command: {abc_import_cmd}") - rt.execute(abc_import_cmd) + rt.Execute(abc_import_cmd) abc_after = { - c for c in rt.rootNode.Children - if rt.classOf(c) == rt.AlembicContainer + c for c in rt.RootNode.Children + if rt.ClassOf(c) == rt.AlembicContainer } # This should yield new AlembicContainer node @@ -59,22 +58,22 @@ importFile @"{file_path}" #noPrompt def update(self, container, representation): from pymxs import runtime as rt path = get_representation_path(representation) - node = rt.getNodeByName(container["instance_node"]) - rt.select(node.Children) + node = rt.GetNodeByName(container["instance_node"]) + rt.Select(node.Children) - for alembic in rt.selection: - abc = rt.getNodeByName(alembic.name) - rt.select(abc.Children) - for abc_con in rt.selection: - container = rt.getNodeByName(abc_con.name) + for alembic in rt.Selection: + abc = rt.GetNodeByName(alembic.name) + rt.Select(abc.Children) + for abc_con in rt.Selection: + container = rt.GetNodeByName(abc_con.name) container.source = path - rt.select(container.Children) - for abc_obj in rt.selection: - alembic_obj = rt.getNodeByName(abc_obj.name) + rt.Select(container.Children) + for abc_obj in rt.Selection: + alembic_obj = rt.GetNodeByName(abc_obj.name) alembic_obj.source = path with maintained_selection(): - rt.select(node) + rt.Select(node) lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) @@ -86,8 +85,8 @@ importFile @"{file_path}" #noPrompt def remove(self, container): from pymxs import runtime as rt - node = rt.getNodeByName(container["instance_node"]) - rt.delete(node) + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) @staticmethod def get_container_children(parent, type_name): @@ -102,7 +101,7 @@ importFile @"{file_path}" #noPrompt filtered = [] for child in list_children(parent): - class_type = str(rt.classOf(child.baseObject)) + class_type = str(rt.ClassOf(child.baseObject)) if class_type == type_name: filtered.append(child) diff --git a/openpype/hosts/max/plugins/load/load_model_fbx.py b/openpype/hosts/max/plugins/load/load_model_fbx.py index 01e6acae12..2aef6f02c2 100644 --- a/openpype/hosts/max/plugins/load/load_model_fbx.py +++ b/openpype/hosts/max/plugins/load/load_model_fbx.py @@ -1,15 +1,13 @@ import os -from openpype.pipeline import ( - load, - get_representation_path -) -from openpype.hosts.max.api.pipeline import containerise + from openpype.hosts.max.api import lib from openpype.hosts.max.api.lib import maintained_selection +from openpype.hosts.max.api.pipeline import containerise +from openpype.pipeline import get_representation_path, load class FbxModelLoader(load.LoaderPlugin): - """Fbx Model Loader""" + """Fbx Model Loader.""" families = ["model"] representations = ["fbx"] @@ -24,17 +22,17 @@ class FbxModelLoader(load.LoaderPlugin): rt.FBXImporterSetParam("Animation", False) rt.FBXImporterSetParam("Cameras", False) rt.FBXImporterSetParam("Preserveinstances", True) - rt.importFile( + rt.ImportFile( filepath, - rt.name("noPrompt"), + rt.Name("noPrompt"), using=rt.FBXIMP) - container = rt.getNodeByName(f"{name}") + container = rt.GetNodeByName(name) if not container: - container = rt.container() - container.name = f"{name}" + container = rt.Container() + container.name = name - for selection in rt.getCurrentSelection(): + for selection in rt.GetCurrentSelection(): selection.Parent = container return containerise( @@ -44,8 +42,8 @@ class FbxModelLoader(load.LoaderPlugin): from pymxs import runtime as rt path = get_representation_path(representation) - node = rt.getNodeByName(container["instance_node"]) - rt.select(node.Children) + node = rt.GetNodeByName(container["instance_node"]) + rt.Select(node.Children) fbx_reimport_cmd = ( f""" FBXImporterSetParam "Animation" false @@ -56,10 +54,10 @@ FbxExporterSetParam "Preserveinstances" true importFile @"{path}" #noPrompt using:FBXIMP """) - rt.execute(fbx_reimport_cmd) + rt.Execute(fbx_reimport_cmd) with maintained_selection(): - rt.select(node) + rt.Select(node) lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) @@ -71,5 +69,5 @@ importFile @"{path}" #noPrompt using:FBXIMP def remove(self, container): from pymxs import runtime as rt - node = rt.getNodeByName(container["instance_node"]) - rt.delete(node) + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) diff --git a/openpype/hosts/max/plugins/load/load_model_obj.py b/openpype/hosts/max/plugins/load/load_model_obj.py index ae42e1f3d3..77d4e08cfb 100644 --- a/openpype/hosts/max/plugins/load/load_model_obj.py +++ b/openpype/hosts/max/plugins/load/load_model_obj.py @@ -1,15 +1,13 @@ import os -from openpype.pipeline import ( - load, - get_representation_path -) -from openpype.hosts.max.api.pipeline import containerise + from openpype.hosts.max.api import lib from openpype.hosts.max.api.lib import maintained_selection +from openpype.hosts.max.api.pipeline import containerise +from openpype.pipeline import get_representation_path, load class ObjLoader(load.LoaderPlugin): - """Obj Loader""" + """Obj Loader.""" families = ["model"] representations = ["obj"] @@ -21,18 +19,18 @@ class ObjLoader(load.LoaderPlugin): from pymxs import runtime as rt filepath = os.path.normpath(self.fname) - self.log.debug(f"Executing command to import..") + self.log.debug("Executing command to import..") - rt.execute(f'importFile @"{filepath}" #noPrompt using:ObjImp') + rt.Execute(f'importFile @"{filepath}" #noPrompt using:ObjImp') # create "missing" container for obj import - container = rt.container() - container.name = f"{name}" + container = rt.Container() + container.name = name # get current selection - for selection in rt.getCurrentSelection(): + for selection in rt.GetCurrentSelection(): selection.Parent = container - asset = rt.getNodeByName(f"{name}") + asset = rt.GetNodeByName(name) return containerise( name, [asset], context, loader=self.__class__.__name__) @@ -42,20 +40,20 @@ class ObjLoader(load.LoaderPlugin): path = get_representation_path(representation) node_name = container["instance_node"] - node = rt.getNodeByName(node_name) + node = rt.GetNodeByName(node_name) instance_name, _ = node_name.split("_") - container = rt.getNodeByName(instance_name) - for n in container.Children: - rt.delete(n) + container = rt.GetNodeByName(instance_name) + for child in container.Children: + rt.Delete(child) - rt.execute(f'importFile @"{path}" #noPrompt using:ObjImp') + rt.Execute(f'importFile @"{path}" #noPrompt using:ObjImp') # get current selection - for selection in rt.getCurrentSelection(): + for selection in rt.GetCurrentSelection(): selection.Parent = container with maintained_selection(): - rt.select(node) + rt.Select(node) lib.imprint(node_name, { "representation": str(representation["_id"]) @@ -67,5 +65,5 @@ class ObjLoader(load.LoaderPlugin): def remove(self, container): from pymxs import runtime as rt - node = rt.getNodeByName(container["instance_node"]) - rt.delete(node) + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) diff --git a/openpype/hosts/max/plugins/load/load_model_usd.py b/openpype/hosts/max/plugins/load/load_model_usd.py index 143f91f40b..2b34669278 100644 --- a/openpype/hosts/max/plugins/load/load_model_usd.py +++ b/openpype/hosts/max/plugins/load/load_model_usd.py @@ -1,10 +1,9 @@ import os -from openpype.pipeline import ( - load, get_representation_path -) -from openpype.hosts.max.api.pipeline import containerise + from openpype.hosts.max.api import lib from openpype.hosts.max.api.lib import maintained_selection +from openpype.hosts.max.api.pipeline import containerise +from openpype.pipeline import get_representation_path, load class ModelUSDLoader(load.LoaderPlugin): @@ -19,6 +18,7 @@ class ModelUSDLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): from pymxs import runtime as rt + # asset_filepath filepath = os.path.normpath(self.fname) import_options = rt.USDImporter.CreateOptions() @@ -27,11 +27,11 @@ class ModelUSDLoader(load.LoaderPlugin): log_filepath = filepath.replace(ext, "txt") rt.LogPath = log_filepath - rt.LogLevel = rt.name('info') + rt.LogLevel = rt.Name("info") rt.USDImporter.importFile(filepath, importOptions=import_options) - asset = rt.getNodeByName(f"{name}") + asset = rt.GetNodeByName(name) return containerise( name, [asset], context, loader=self.__class__.__name__) @@ -41,11 +41,11 @@ class ModelUSDLoader(load.LoaderPlugin): path = get_representation_path(representation) node_name = container["instance_node"] - node = rt.getNodeByName(node_name) + node = rt.GetNodeByName(node_name) for n in node.Children: for r in n.Children: - rt.delete(r) - rt.delete(n) + rt.Delete(r) + rt.Delete(n) instance_name, _ = node_name.split("_") import_options = rt.USDImporter.CreateOptions() @@ -54,15 +54,15 @@ class ModelUSDLoader(load.LoaderPlugin): log_filepath = path.replace(ext, "txt") rt.LogPath = log_filepath - rt.LogLevel = rt.name('info') + rt.LogLevel = rt.Name("info") rt.USDImporter.importFile(path, importOptions=import_options) - asset = rt.getNodeByName(f"{instance_name}") + asset = rt.GetNodeByName(instance_name) asset.Parent = node with maintained_selection(): - rt.select(node) + rt.Select(node) lib.imprint(node_name, { "representation": str(representation["_id"]) @@ -74,5 +74,5 @@ class ModelUSDLoader(load.LoaderPlugin): def remove(self, container): from pymxs import runtime as rt - node = rt.getNodeByName(container["instance_node"]) - rt.delete(node) + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) diff --git a/openpype/hosts/max/plugins/load/load_pointcache.py b/openpype/hosts/max/plugins/load/load_pointcache.py index 37cb6791db..4f7773d967 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache.py +++ b/openpype/hosts/max/plugins/load/load_pointcache.py @@ -5,11 +5,10 @@ Because of limited api, alembics can be only loaded, but not easily updated. """ import os -from openpype.pipeline import ( - load, get_representation_path -) -from openpype.hosts.max.api.pipeline import containerise + from openpype.hosts.max.api import lib, maintained_selection +from openpype.hosts.max.api.pipeline import containerise +from openpype.pipeline import get_representation_path, load class AbcLoader(load.LoaderPlugin): @@ -30,29 +29,28 @@ class AbcLoader(load.LoaderPlugin): file_path = os.path.normpath(self.fname) abc_before = { - c for c in rt.rootNode.Children - if rt.classOf(c) == rt.AlembicContainer + c for c in rt.RootNode.Children + if rt.ClassOf(c) == rt.AlembicContainer } rt.AlembicImport.ImportToRoot = False rt.AlembicImport.StartFrame = True rt.AlembicImport.EndFrame = True - rt.importFile(file_path, rt.name("noPrompt")) + rt.ImportFile(file_path, rt.Name("noPrompt")) abc_after = { - c for c in rt.rootNode.Children - if rt.classOf(c) == rt.AlembicContainer + c for c in rt.RootNode.Children + if rt.ClassOf(c) == rt.AlembicContainer } # This should yield new AlembicContainer node abc_containers = abc_after.difference(abc_before) - if len(abc_containers) != 1: self.log.error("Something failed when loading.") abc_container = abc_containers.pop() - for abc in rt.getCurrentSelection(): + for abc in rt.GetCurrentSelection(): for cam_shape in abc.Children: cam_shape.playbackType = 2 @@ -63,27 +61,25 @@ class AbcLoader(load.LoaderPlugin): from pymxs import runtime as rt path = get_representation_path(representation) - node = rt.getNodeByName(container["instance_node"]) + node = rt.GetNodeByName(container["instance_node"]) lib.imprint(container["instance_node"], { "representation": str(representation["_id"]) }) - rt.select(node.Children) - - for alembic in rt.selection: - abc = rt.getNodeByName(alembic.name) - rt.select(abc.Children) - for abc_con in rt.selection: - container = rt.getNodeByName(abc_con.name) - container.source = path - rt.select(container.Children) - for abc_obj in rt.selection: - alembic_obj = rt.getNodeByName(abc_obj.name) - alembic_obj.source = path - with maintained_selection(): - rt.select(node) + rt.Select(node.Children) + + for alembic in rt.Selection: + abc = rt.GetNodeByName(alembic.name) + rt.Select(abc.Children) + for abc_con in rt.Selection: + container = rt.GetNodeByName(abc_con.name) + container.source = path + rt.Select(container.Children) + for abc_obj in rt.Selection: + alembic_obj = rt.GetNodeByName(abc_obj.name) + alembic_obj.source = path def switch(self, container, representation): self.update(container, representation) @@ -91,8 +87,8 @@ class AbcLoader(load.LoaderPlugin): def remove(self, container): from pymxs import runtime as rt - node = rt.getNodeByName(container["instance_node"]) - rt.delete(node) + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) @staticmethod def get_container_children(parent, type_name): diff --git a/openpype/hosts/max/plugins/load/load_pointcloud.py b/openpype/hosts/max/plugins/load/load_pointcloud.py index d4ae721c8a..8634e1d51f 100644 --- a/openpype/hosts/max/plugins/load/load_pointcloud.py +++ b/openpype/hosts/max/plugins/load/load_pointcloud.py @@ -1,13 +1,12 @@ import os -from openpype.pipeline import ( - load, get_representation_path -) -from openpype.hosts.max.api.pipeline import containerise + from openpype.hosts.max.api import lib, maintained_selection +from openpype.hosts.max.api.pipeline import containerise +from openpype.pipeline import get_representation_path, load class PointCloudLoader(load.LoaderPlugin): - """Point Cloud Loader""" + """Point Cloud Loader.""" families = ["pointcloud"] representations = ["prt"] @@ -23,7 +22,7 @@ class PointCloudLoader(load.LoaderPlugin): obj = rt.tyCache() obj.filename = filepath - prt_container = rt.getNodeByName(f"{obj.name}") + prt_container = rt.GetNodeByName(obj.name) return containerise( name, [prt_container], context, loader=self.__class__.__name__) @@ -33,18 +32,16 @@ class PointCloudLoader(load.LoaderPlugin): from pymxs import runtime as rt path = get_representation_path(representation) - node = rt.getNodeByName(container["instance_node"]) - rt.select(node.Children) - for prt in rt.selection: - prt_object = rt.getNodeByName(prt.name) - prt_object.filename = path - + node = rt.GetNodeByName(container["instance_node"]) with maintained_selection(): - rt.select(node) + rt.Select(node.Children) + for prt in rt.Selection: + prt_object = rt.GetNodeByName(prt.name) + prt_object.filename = path - lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) - }) + lib.imprint(container["instance_node"], { + "representation": str(representation["_id"]) + }) def switch(self, container, representation): self.update(container, representation) @@ -53,5 +50,5 @@ class PointCloudLoader(load.LoaderPlugin): """remove the container""" from pymxs import runtime as rt - node = rt.getNodeByName(container["instance_node"]) - rt.delete(node) + node = rt.GetNodeByName(container["instance_node"]) + rt.Delete(node) diff --git a/openpype/hosts/max/plugins/publish/collect_members.py b/openpype/hosts/max/plugins/publish/collect_members.py index 4ceb6cdadf..54020d7dae 100644 --- a/openpype/hosts/max/plugins/publish/collect_members.py +++ b/openpype/hosts/max/plugins/publish/collect_members.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- -"""Collect instance members""" +"""Collect instance members.""" import pyblish.api from pymxs import runtime as rt class CollectMembers(pyblish.api.InstancePlugin): - """Collect Render for Deadline""" + """Collect Render for Deadline.""" order = pyblish.api.CollectorOrder + 0.01 label = "Collect Instance Members" @@ -16,5 +16,6 @@ class CollectMembers(pyblish.api.InstancePlugin): if instance.data.get("instance_node"): container = rt.GetNodeByName(instance.data["instance_node"]) instance.data["members"] = [ - i.node for i in container.openPypeData.all_handles + member.node for member + in container.openPypeData.all_handles ] diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py index d53c47fb51..c526de8960 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_abc.py @@ -1,14 +1,14 @@ import os + import pyblish.api -from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import maintained_selection, get_all_children + +from openpype.hosts.max.api import get_all_children, maintained_selection +from openpype.pipeline import OptionalPyblishPluginMixin, publish class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): - """ - Extract Camera with AlembicExport - """ + """Extract Camera with AlembicExport.""" order = pyblish.api.ExtractorOrder - 0.1 label = "Extract Alembic Camera" @@ -31,20 +31,20 @@ class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): path = os.path.join(stagingdir, filename) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (filename, stagingdir)) + self.log.info(f"Writing alembic '{filename}' to '{stagingdir}'") - rt.AlembicExport.ArchiveType = rt.name("ogawa") - rt.AlembicExport.CoordinateSystem = rt.name("maya") + rt.AlembicExport.ArchiveType = rt.Name("ogawa") + rt.AlembicExport.CoordinateSystem = rt.Name("maya") rt.AlembicExport.StartFrame = start rt.AlembicExport.EndFrame = end rt.AlembicExport.CustomAttributes = True with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) - rt.exportFile( + rt.Select(get_all_children(rt.GetNodeByName(container))) + rt.ExportFile( path, - rt.name("noPrompt"), + rt.Name("noPrompt"), selectedOnly=True, using=rt.AlembicExport, ) @@ -62,4 +62,4 @@ class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): "frameEnd": end, } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) + self.log.info(f"Extracted instance '{instance.name}' to: {path}") diff --git a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py index 4b4b349e19..0c8a82dcaa 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py @@ -1,14 +1,14 @@ import os + import pyblish.api -from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import maintained_selection, get_all_children + +from openpype.hosts.max.api import get_all_children, maintained_selection +from openpype.pipeline import OptionalPyblishPluginMixin, publish class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): - """ - Extract Camera with FbxExporter - """ + """Extract Camera with FbxExporter.""" order = pyblish.api.ExtractorOrder - 0.2 label = "Extract Fbx Camera" @@ -26,7 +26,7 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): filename = "{name}.fbx".format(**instance.data) filepath = os.path.join(stagingdir, filename) - self.log.info("Writing fbx file '%s' to '%s'" % (filename, filepath)) + self.log.info(f"Writing fbx file '{filename}' to '{filepath}'") rt.FBXExporterSetParam("Animation", True) rt.FBXExporterSetParam("Cameras", True) @@ -36,10 +36,10 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) - rt.exportFile( + rt.Select(get_all_children(rt.GetNodeByName(container))) + rt.ExportFile( filepath, - rt.name("noPrompt"), + rt.Name("noPrompt"), selectedOnly=True, using=rt.FBXEXP, ) @@ -55,6 +55,4 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): "stagingDir": stagingdir, } instance.data["representations"].append(representation) - self.log.info( - "Extracted instance '%s' to: %s" % (instance.name, filepath) - ) + self.log.info(f"Extracted instance '{instance.name}' to: {filepath}") diff --git a/openpype/hosts/max/plugins/publish/extract_model_usd.py b/openpype/hosts/max/plugins/publish/extract_model_usd.py index 2500e6c905..da37c77bf7 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_usd.py +++ b/openpype/hosts/max/plugins/publish/extract_model_usd.py @@ -1,20 +1,15 @@ import os + import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection -) + +from openpype.hosts.max.api import maintained_selection +from openpype.pipeline import OptionalPyblishPluginMixin, publish class ExtractModelUSD(publish.Extractor, OptionalPyblishPluginMixin): - """ - Extract Geometry in USDA Format - """ + """Extract Geometry in USDA Format.""" order = pyblish.api.ExtractorOrder - 0.05 label = "Extract Geometry (USD)" @@ -44,7 +39,7 @@ class ExtractModelUSD(publish.Extractor, with maintained_selection(): # select and export node_list = instance.data["members"] - rt.select(node_list) + rt.Select(node_list) rt.USDExporter.ExportFile(asset_filepath, exportOptions=export_options, contentSource=rt.Name("selected"), diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index 9e1e7fdc72..618f9856fd 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -1,15 +1,15 @@ import os + import pyblish.api -from openpype.pipeline import publish from pymxs import runtime as rt -from openpype.hosts.max.api import ( - maintained_selection -) + +from openpype.hosts.max.api import maintained_selection +from openpype.pipeline import publish class ExtractPointCloud(publish.Extractor): """ - Extract PRT format with tyFlow operators + Extract PRT format with tyFlow operators. Notes: Currently only works for the default partition setting @@ -112,8 +112,7 @@ class ExtractPointCloud(publish.Extractor): job_args.append(mode) additional_args = self.get_custom_attr(operator) - for args in additional_args: - job_args.append(args) + job_args.extend(iter(additional_args)) prt_export = f"{operator}.exportPRT()" job_args.append(prt_export) diff --git a/openpype/hosts/max/plugins/publish/validate_model_contents.py b/openpype/hosts/max/plugins/publish/validate_model_contents.py index 1d834292b8..1ec08d9c5f 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_model_contents.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- import pyblish.api -from openpype.pipeline import PublishValidationError from pymxs import runtime as rt +from openpype.pipeline import PublishValidationError + class ValidateModelContent(pyblish.api.InstancePlugin): """Validates Model instance contents. diff --git a/poetry.lock b/poetry.lock index 563f905fad..f71611cb6f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "acre" @@ -1456,13 +1456,11 @@ python-versions = ">=3.6" files = [ {file = "lief-0.12.3-cp310-cp310-macosx_10_14_arm64.whl", hash = "sha256:66724f337e6a36cea1a9380f13b59923f276c49ca837becae2e7be93a2e245d9"}, {file = "lief-0.12.3-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:6d18aafa2028587c98f6d4387bec94346e92f2b5a8a5002f70b1cf35b1c045cc"}, - {file = "lief-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4f69d125caaa8d5ddb574f29cc83101e165ebea1a9f18ad042eb3544081a797"}, {file = "lief-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c078d6230279ffd3bca717c79664fb8368666f610b577deb24b374607936e9c1"}, {file = "lief-0.12.3-cp310-cp310-win32.whl", hash = "sha256:e3a6af926532d0aac9e7501946134513d63217bacba666e6f7f5a0b7e15ba236"}, {file = "lief-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:0750b72e3aa161e1fb0e2e7f571121ae05d2428aafd742ff05a7656ad2288447"}, {file = "lief-0.12.3-cp311-cp311-macosx_10_14_arm64.whl", hash = "sha256:b5c123cb99a7879d754c059e299198b34e7e30e3b64cf22e8962013db0099f47"}, {file = "lief-0.12.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8bc58fa26a830df6178e36f112cb2bbdd65deff593f066d2d51434ff78386ba5"}, - {file = "lief-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74ac6143ac6ccd813c9b068d9c5f1f9d55c8813c8b407387eb57de01c3db2d74"}, {file = "lief-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04eb6b70d646fb5bd6183575928ee23715550f161f2832cbcd8c6ff2071fb408"}, {file = "lief-0.12.3-cp311-cp311-win32.whl", hash = "sha256:7e2d0a53c403769b04adcf8df92e83c5e25f9103a052aa7f17b0a9cf057735fb"}, {file = "lief-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:7f6395c12ee1bc4a5162f567cba96d0c72dfb660e7902e84d4f3029daf14fe33"}, @@ -1482,7 +1480,6 @@ files = [ {file = "lief-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:b00667257b43e93d94166c959055b6147d46d302598f3ee55c194b40414c89cc"}, {file = "lief-0.12.3-cp39-cp39-macosx_10_14_arm64.whl", hash = "sha256:e6a1b5b389090d524621c2455795e1262f62dc9381bedd96f0cd72b878c4066d"}, {file = "lief-0.12.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:ae773196df814202c0c51056163a1478941b299512b09660a3c37be3c7fac81e"}, - {file = "lief-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:66ddf88917ec7b00752687c476bb2771dc8ec19bd7e4c0dcff1f8ef774cad4e9"}, {file = "lief-0.12.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:4a47f410032c63ac3be051d963d0337d6b47f0e94bfe8e946ab4b6c428f4d0f8"}, {file = "lief-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbd11367c2259bd1131a6c8755dcde33314324de5ea029227bfbc7d3755871e6"}, {file = "lief-0.12.3-cp39-cp39-win32.whl", hash = "sha256:2ce53e311918c3e5b54c815ef420a747208d2a88200c41cd476f3dd1eb876bcf"}, @@ -2355,7 +2352,7 @@ files = [ cffi = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5)", "sphinx_rtd_theme"] tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] @@ -3248,21 +3245,6 @@ files = [ [package.dependencies] six = "*" -[[package]] -name = "wemake-python-styleguide" -version = "0.0.1" -description = "Opinionated styleguide that we use in wemake.services projects" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "wemake-python-styleguide-0.0.1.tar.gz", hash = "sha256:e1f47a2be6aa79ca8a1cfbbbffdd67bf4df32b76306f4c3dd2a620a2af78e671"}, - {file = "wemake_python_styleguide-0.0.1-py2.py3-none-any.whl", hash = "sha256:505a19d82f9c4f450c6f06bb8c74d86c99cabcc4d5e6d8ea70e90b13b049f34f"}, -] - -[package.dependencies] -flake8 = "*" - [[package]] name = "wheel" version = "0.38.4" @@ -3480,4 +3462,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "2.0" python-versions = ">=3.9.1,<3.10" -content-hash = "45e91b47f9e6697b0eb9fdbe76981f691d389ce74bc5a0e98d72e1109b39bc63" +content-hash = "02daca205796a0f29a0d9f50707544e6804f32027eba493cd2aa7f175a00dcea" diff --git a/setup.cfg b/setup.cfg index 1d57657a19..42cacdc93c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,7 @@ [flake8] +# ignore = D203 +ignore = BLK100, W504, W503 max-line-length = 79 -strictness = short exclude = .git, __pycache__, @@ -9,149 +10,8 @@ exclude = website, openpype/vendor, *deadline/repository/custom/plugins -max-complexity = 30 -ignore = - # line break before binary operator - W503, - # line break occurred after a binary operator - W504, - # wemake-python-styleguide warnings - # See https://wemake-python-stylegui.de/en/latest/pages/usage/violations/index.html for doc - # Found incorrect module name pattern - WPS102, - # Found wrong variable name - WPS110, - # Found too short name - WPS111, - # Found upper-case constant in a class - WPS115, - # Found module with too many imports - WPS201, - # Found too many module members - WPS202, - # Found overused expression - WPS204, - # Found too many local variables - WPS210, - # Found too many arguments - WPS211, - # Found too many return statements - WPS212, - # Found too many expressions - WPS213, - # Found too many methods - WPS214, - # Found too many await expressions - WPS217, - # Found line with high Jones Complexity - WPS221, - # Found too many `elif` branches - WPS223, - # Found string constant over-use - WPS226, - # Found too long try body length - WPS229, - # Found too many public instance attributes - WPS230, - # Found function with too much cognitive complexity - WPS231, - # Found module cognitive complexity that is too high - WPS232, - # Found too many imported names from a module - WPS235, - # Found too many raises in a function - WPS238, - # Found too deep nesting - WPS220, - # Found `f` string - WPS305, - # Found incorrect multi-line parameters - WPS317, - # Found extra indentation - WPS318, - # Found bracket in wrong position - WPS319, - # Found percent string formatting - WPS323, - # Found implicit string concatenation - WPS326, - # Found variables that are only used for `return` - WPS331, - # Found explicit string concatenation - WPS336, - # Found multiline conditions - WPS337, - # Found incorrect order of methods in a class - WPS338, - # Found line starting with a dot - WPS348, - # Found multiline loop - WPS352, - # Found incorrect unpacking target - WPS414, - # Found wrong keyword - WPS420, - # Found wrong function - WPS421, - # Found statement that has no effect - WPS428, - # Found nested function - WPS430, - # Found magic number - WPS432, - # Found protected attribute usage - WPS437, - # Found block variables overlap - WPS440, - # Found an infinite while loop - WPS457, - # Found a getter without a return value - WPS463, - # Found negated condition - WPS504, - # flake8-quotes warnings - # Remove bad quotes - Q000, - # Remove bad quotes from multiline string - Q001, - # Darglint warnings - # Incorrect indentation - DAR003, - # Excess parameter(s) in Docstring - DAR102, - # Excess exception(s) in Raises section - DAR402, - # pydocstyle warnings - # Missing docstring in __init_ - D107, - # White space formatting for doc strings - D2, - # First line should end with a period - D400, - # Others - # function name - N802, - # Found backslash that is used for line breaking - N400, - E501, - S105, - RST, - # Black would make changes error - BLK100, - # Imperative mood of the first line on docstrings - D401, -[isort] -profile=wemake -src_paths=isort,test -# isort configuration: -# https://github.com/timothycrosley/isort/wiki/isort-Settings -include_trailing_comma = true -use_parentheses = true -# See https://github.com/timothycrosley/isort#multi-line-output-modes -multi_line_output = 3 -# Is the same as 80 in flake8: -line_length = 79 +max-complexity = 30 [pylint.'MESSAGES CONTROL'] disable = no-member From cccc0acd1dc9c6fd9d00fa56abfaebc1de1d327d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 23 May 2023 22:00:40 +0800 Subject: [PATCH 154/347] add docs --- website/docs/artist_hosts_houdini.md | 24 +++++++++++++----- .../assets/houdini_render_publish_creator.png | Bin 0 -> 82356 bytes 2 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 website/docs/assets/houdini_render_publish_creator.png diff --git a/website/docs/artist_hosts_houdini.md b/website/docs/artist_hosts_houdini.md index 8874a0b5cf..0471765365 100644 --- a/website/docs/artist_hosts_houdini.md +++ b/website/docs/artist_hosts_houdini.md @@ -14,7 +14,7 @@ sidebar_label: Houdini - [Library Loader](artist_tools_library-loader) ## Publishing Alembic Cameras -You can publish baked camera in Alembic format. +You can publish baked camera in Alembic format. Select your camera and go **OpenPype -> Create** and select **Camera (abc)**. This will create Alembic ROP in **out** with path and frame range already set. This node will have a name you've @@ -30,7 +30,7 @@ You can use any COP node and publish the image sequence generated from it. For e ![Noise COP](assets/houdini_imagesequence_cop.png) To publish the output of the `radialblur1` go to **OpenPype -> Create** and -select **Composite (Image Sequence)**. If you name the variant *Noise* this will create the `/out/imagesequenceNoise` Composite ROP with the frame range set. +select **Composite (Image Sequence)**. If you name the variant *Noise* this will create the `/out/imagesequenceNoise` Composite ROP with the frame range set. When you hit **Publish** it will render image sequence from selected node. @@ -56,14 +56,14 @@ Now select the `output0` node and go **OpenPype -> Create** and select **Point C Alembic ROP `/out/pointcacheStrange` ## Publishing Reviews (OpenGL) -To generate a review output from Houdini you need to create a **review** instance. +To generate a review output from Houdini you need to create a **review** instance. Go to **OpenPype -> Create** and select **Review**. ![Houdini Create Review](assets/houdini_review_create_attrs.png) -On create, with the **Use Selection** checkbox enabled it will set up the first -camera found in your selection as the camera for the OpenGL ROP node and other -non-cameras are set in **Force Objects**. It will then render those even if +On create, with the **Use Selection** checkbox enabled it will set up the first +camera found in your selection as the camera for the OpenGL ROP node and other +non-cameras are set in **Force Objects**. It will then render those even if their display flag is disabled in your scene. ## Redshift @@ -71,6 +71,18 @@ their display flag is disabled in your scene. This part of documentation is still work in progress. ::: +## Publishing Render to Deadline +Five Renderers(Arnold, Redshift, Mantra, Karma, VRay) are supported for Render Publishing. +They are named with the suffix("_ROP") +To submit render to deadline, you need to create a **Render** instance. +Go to **Openpype -> Create** and select **Publish**. Before clicking **Create** button, +you need select your preferred image rendering format. You can also enable the **Use selection** to +select your render camera. +![Houdini Create Render](assets/houdini_render_publish_creator.png) + +All the render outputs are stored in the pyblish/render directory within your project path.\ +For Karma-specific render, it also outputs the USD render as default. + ## USD (experimental support) ### Publishing USD You can publish your Solaris Stage as USD file. diff --git a/website/docs/assets/houdini_render_publish_creator.png b/website/docs/assets/houdini_render_publish_creator.png new file mode 100644 index 0000000000000000000000000000000000000000..5dd73d296a416a49291c2bb8729541780178ac60 GIT binary patch literal 82356 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 Date: Wed, 24 May 2023 00:42:34 +0800 Subject: [PATCH 155/347] remove custom attributes while removing instance --- openpype/hosts/max/api/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 8df620b913..553031d72e 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -197,8 +197,8 @@ class MaxCreator(Creator, MaxCreatorBase): """ for instance in instances: if instance_node := rt.GetNodeByName(instance.data.get("instance_node")): # noqa - rt.Select(instance_node) - rt.execute(f'for o in selection do for c in o.children do c.parent = undefined') # noqa + count = rt.custAttributes.count(instance_node) + rt.custAttributes.delete(instance_node, count) rt.Delete(instance_node) self._remove_instance_from_context(instance) From 07a969df6ec384af473e3034eb678208aa5dfc77 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 May 2023 10:29:41 +0200 Subject: [PATCH 156/347] adding resolve host to the pre ocio hook --- openpype/hooks/pre_ocio_hook.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py index 49a042caa8..eac7d2696f 100644 --- a/openpype/hooks/pre_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -18,7 +18,8 @@ class OCIOEnvHook(PreLaunchHook): "houdini", "maya", "nuke", - "hiero" + "hiero", + "resolve" ] def execute(self): From ca9defe36975ec373b4c247819e4b9d0bf1fb382 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 May 2023 13:11:13 +0200 Subject: [PATCH 157/347] label improvements --- openpype/pipeline/colorspace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 652304ef33..1cf7e2e192 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -360,7 +360,7 @@ def get_imageio_config( # if global settings are disabled return empty dict because # it is expected that no colorspace management is needed log.info( - "Colorspace management is disabled." + "Colorspace management is disabled globally." ) return {} @@ -482,7 +482,7 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): if not activate_global_rules: log.info( - "Global File Rules are disabled." + "Colorspace global file rules are disabled." ) return {} From 379f838f03160f0b3ae4d24df980c06be06efc59 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 May 2023 14:50:59 +0200 Subject: [PATCH 158/347] distributing settings via template schemas --- .../schema_project_aftereffects.json | 18 +------- .../schema_project_blender.json | 18 +------- .../schema_project_celaction.json | 18 +------- .../projects_schema/schema_project_flame.json | 17 +------- .../schema_project_fusion.json | 17 +------- .../schema_project_harmony.json | 18 +------- .../projects_schema/schema_project_hiero.json | 17 +------- .../schema_project_houdini.json | 18 +------- .../projects_schema/schema_project_max.json | 18 +------- .../projects_schema/schema_project_maya.json | 17 +------- .../schema_project_photoshop.json | 18 +------- .../schema_project_resolve.json | 18 +------- .../schema_project_substancepainter.json | 11 ++--- .../schema_project_traypublisher.json | 18 +------- .../schema_project_tvpaint.json | 18 +------- .../schema_project_unreal.json | 18 +------- .../schema_project_webpublisher.json | 18 +------- .../schemas/schema_imageio_config.json | 20 --------- .../schemas/schema_imageio_file_rules.json | 40 ------------------ .../schemas/schema_nuke_imageio.json | 17 +------- .../template_colorspace_remapping.json | 29 +++++++++++++ ...emplate_host_color_management_derived.json | 19 +++++++++ .../template_host_color_management_ocio.json | 19 +++++++++ ...mplate_host_color_management_remapped.json | 23 ++++++++++ .../schemas/template_imageio_config.json | 22 ++++++++++ .../schemas/template_imageio_file_rules.json | 42 +++++++++++++++++++ 26 files changed, 191 insertions(+), 335 deletions(-) delete mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_config.json delete mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_colorspace_remapping.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_derived.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_ocio.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_remapped.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_config.json create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_file_rules.json diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json index 7262c17dd5..d4f52b50d4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_aftereffects.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json index 79eea3f192..78a1552ac3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_blender.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json index 915f199b6e..9d50e85631 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_derived" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index 16c9378194..102e2bdcc3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -13,21 +13,8 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_remapped" }, { "key": "project", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json index 550e7a3cf4..656c50dd98 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json @@ -13,21 +13,8 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" } ] }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json index 276b321b24..98a815f2d4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index c2339d8200..d80edf902b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -13,21 +13,8 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" }, { "key": "workfile", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index a7032775c1..7f782e3647 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json index ee2bbd4ffa..e314174dff 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json index fe7c262603..dca955dab4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json @@ -53,21 +53,8 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" }, { "key": "workfile", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 7ddd575dde..20d4ff0aa3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_remapped" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json index aea019b77b..2f10cc900a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures.

Related documentation.." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_remapped" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json b/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json index 79a39b8e6e..6be8cecad3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_substancepainter.json @@ -8,18 +8,13 @@ { "key": "imageio", "type": "dict", - "label": "Color Management (ImageIO)", + "label": "Color Management (OCIO managed)", "is_group": true, "children": [ { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 6b55837f12..3703d82856 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_derived" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json index ed8887f93e..45fc13bdde 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_derived" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json index aa2fe40b4a..b23744f406 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json index 7b65dddda6..87de732d69 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_webpublisher.json @@ -13,23 +13,9 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_derived" } - ] }, { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_config.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_config.json deleted file mode 100644 index bc65dd7826..0000000000 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_config.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "key": "ocio_config", - "type": "dict", - "label": "OCIO config", - "collapsible": true, - "children": [ - { - "type": "boolean", - "key": "override_global_config", - "label": "Override global OCIO config" - }, - { - "type": "path", - "key": "filepath", - "label": "Config path", - "multiplatform": false, - "multipath": true - } - ] -} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json deleted file mode 100644 index 62b72c2518..0000000000 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_imageio_file_rules.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "key": "file_rules", - "type": "dict", - "label": "File Rules (OCIO v1 only)", - "collapsible": true, - "children": [ - { - "type": "boolean", - "key": "override_global_rules", - "label": "Override global File Rules" - }, - { - "key": "rules", - "label": "Rules", - "type": "dict-modifiable", - "highlight_content": true, - "collapsible": false, - "object_type": { - "type": "dict", - "children": [ - { - "key": "pattern", - "label": "Regex pattern", - "type": "text" - }, - { - "key": "colorspace", - "label": "Colorspace name", - "type": "text" - }, - { - "key": "ext", - "label": "File extension", - "type": "text" - } - ] - } - } - ] -} diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json index 864e084bde..d4cd332ef8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_imageio.json @@ -6,21 +6,8 @@ "is_group": true, "children": [ { - "type": "label", - "label": "This application's colorspace management can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." - }, - { - "type": "boolean", - "key": "activate_host_color_management", - "label": "Enable Color Management" - }, - { - "type": "schema", - "name": "schema_imageio_config" - }, - { - "type": "schema", - "name": "schema_imageio_file_rules" + "type": "template", + "name": "template_host_color_management_ocio" }, { "key": "viewer", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_colorspace_remapping.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_colorspace_remapping.json new file mode 100644 index 0000000000..9ae504fa81 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_colorspace_remapping.json @@ -0,0 +1,29 @@ +[ + { + "key": "remapping", + "type": "dict", + "label": "Remapping colorspace names", + "collapsible": true, + "children": [ + { + "type": "list", + "key": "inputs", + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "internal_name", + "label": "Internal colorspace name" + }, + { + "type": "text", + "key": "ocio_name", + "label": "OCIO colorspace name" + } + ] + } + } + ] + } +] diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_derived.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_derived.json new file mode 100644 index 0000000000..a129d470c0 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_derived.json @@ -0,0 +1,19 @@ +[ + { + "type": "label", + "label": "The application does not include any built-in color management capabilities, OpenPype offers a solution
to this limitation by deriving valid colorspace names for the OpenColorIO (OCIO) color management
system from file paths, using File Rules feature only during Publishing.

Related documentation." + }, + { + "type": "boolean", + "key": "activate_host_color_management", + "label": "Enable Color Management" + }, + { + "type": "template", + "name": "template_imageio_config" + }, + { + "type": "template", + "name": "template_imageio_file_rules" + } +] diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_ocio.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_ocio.json new file mode 100644 index 0000000000..88c22fa762 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_ocio.json @@ -0,0 +1,19 @@ +[ + { + "type": "label", + "label": "Colorspace management for the application can be controlled through OpenPype settings.
Specifically, the configured OpenColorIO (OCIO) config path is utilized in the application's workfile.
Additionally, the File Rules feature can be leveraged for both publishing and loading procedures.

Related documentation." + }, + { + "type": "boolean", + "key": "activate_host_color_management", + "label": "Enable Color Management" + }, + { + "type": "template", + "name": "template_imageio_config" + }, + { + "type": "template", + "name": "template_imageio_file_rules" + } +] diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_remapped.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_remapped.json new file mode 100644 index 0000000000..780264947f --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_host_color_management_remapped.json @@ -0,0 +1,23 @@ +[ + { + "type": "label", + "label": "The application includes internal color management functionality, but it does not offer external control
over this feature. To address this limitation, OpenPype uses mapping rules to remap the native
colorspace names used in the internal color management system to the OpenColorIO (OCIO)
color management system. Remapping feature is used in Publishing and Loading procedures.

Related documentation.." + }, + { + "type": "boolean", + "key": "activate_host_color_management", + "label": "Enable Color Management" + }, + { + "type": "template", + "name": "template_colorspace_remapping" + }, + { + "type": "template", + "name": "template_imageio_config" + }, + { + "type": "template", + "name": "template_imageio_file_rules" + } +] diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_config.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_config.json new file mode 100644 index 0000000000..0550e5093c --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_config.json @@ -0,0 +1,22 @@ +[ + { + "key": "ocio_config", + "type": "dict", + "label": "OCIO config", + "collapsible": true, + "children": [ + { + "type": "boolean", + "key": "override_global_config", + "label": "Override global OCIO config" + }, + { + "type": "path", + "key": "filepath", + "label": "Config path", + "multiplatform": false, + "multipath": true + } + ] + } +] diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_file_rules.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_file_rules.json new file mode 100644 index 0000000000..829fd02489 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_file_rules.json @@ -0,0 +1,42 @@ +[ + { + "key": "file_rules", + "type": "dict", + "label": "File Rules (OCIO v1 only)", + "collapsible": true, + "children": [ + { + "type": "boolean", + "key": "override_global_rules", + "label": "Override global File Rules" + }, + { + "key": "rules", + "label": "Rules", + "type": "dict-modifiable", + "highlight_content": true, + "collapsible": false, + "object_type": { + "type": "dict", + "children": [ + { + "key": "pattern", + "label": "Regex pattern", + "type": "text" + }, + { + "key": "colorspace", + "label": "Colorspace name", + "type": "text" + }, + { + "key": "ext", + "label": "File extension", + "type": "text" + } + ] + } + } + ] + } +] From c2055622ab98880eeb5a1a3475b7c9303bdcd93d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 May 2023 15:51:29 +0200 Subject: [PATCH 159/347] adding remapping functionality --- openpype/pipeline/colorspace.py | 50 +++++++++++++++++++ .../projects_schema/schema_project_flame.json | 6 ++- .../template_colorspace_remapping.json | 6 +-- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 1cf7e2e192..fa7ad5133c 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -17,6 +17,8 @@ from openpype.pipeline import Anatomy log = Logger.get_logger(__name__) +class cashed_data: + remapping: dict = None @contextlib.contextmanager def _make_temp_json_file(): @@ -497,6 +499,54 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): return frules_global["rules"] +def get_remapped_colorspace_to_native( + ocio_colorspace_name, host_name, imageio_host_settings): + """Return native colorspace name. + + Args: + ocio_colorspace_name (str | None): ocio colorspace name + + Returns: + str: native colorspace name defined in remapping or None + """ + + if not cashed_data.remapping.get(host_name): + remapping_rules = imageio_host_settings["remapping"]["rules"] + cashed_data.remapping[host_name] = { + "to_native": { + rule["ocio_name"]: input["host_native_name"] + for rule in remapping_rules + } + } + + return cashed_data.remapping[host_name]["to_native"].get( + ocio_colorspace_name) + + +def get_remapped_colorspace_from_native( + host_native_colorspace_name, host_name, imageio_host_settings): + """Return ocio colorspace name remapped from host native used name. + + Args: + host_native_colorspace_name (str): host native colorspace name + + Returns: + str: ocio colorspace name defined in remapping or None + """ + + if not cashed_data.remapping.get(host_name): + remapping_rules = imageio_host_settings["remapping"]["rules"] + cashed_data.remapping[host_name] = { + "from_native": { + input["host_native_name"]: rule["ocio_name"] + for rule in remapping_rules + } + } + + return cashed_data.remapping[host_name]["from_native"].get( + host_native_colorspace_name) + + def _get_imageio_settings(project_settings, host_name): """Get ImageIO settings for global and host diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index 102e2bdcc3..06f818966f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -44,10 +44,14 @@ } ] }, + { + "type": "label", + "label": "Profile names mapping settings is deprecated use ./imagio/remapping instead" + }, { "key": "profilesMapping", "type": "dict", - "label": "Profile names mapping", + "label": "Profile names mapping [deprecated]", "collapsible": true, "children": [ { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_colorspace_remapping.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_colorspace_remapping.json index 9ae504fa81..acd36ece9d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/template_colorspace_remapping.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_colorspace_remapping.json @@ -7,14 +7,14 @@ "children": [ { "type": "list", - "key": "inputs", + "key": "rules", "object_type": { "type": "dict", "children": [ { "type": "text", - "key": "internal_name", - "label": "Internal colorspace name" + "key": "host_native_name", + "label": "Application native colorspace name" }, { "type": "text", From 05b0f61e0bad1db6226812c9d77f6dadef49ded5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 May 2023 16:22:33 +0200 Subject: [PATCH 160/347] flame: adding remapping implementation --- openpype/hosts/flame/api/plugin.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index df8c1ac887..3289187fa0 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -10,6 +10,7 @@ from qtpy import QtCore, QtWidgets from openpype import style from openpype.lib import Logger, StringTemplate from openpype.pipeline import LegacyCreator, LoaderPlugin +from openpype.pipeline.colorspace import get_remapped_colorspace_to_native from openpype.settings import get_current_project_settings from . import constants @@ -701,6 +702,7 @@ class ClipLoader(LoaderPlugin): ] _mapping = None + _host_settings = None def apply_settings(cls, project_settings, system_settings): @@ -769,15 +771,26 @@ class ClipLoader(LoaderPlugin): Returns: str: native colorspace name defined in mapping or None """ + # TODO: rewrite to support only pipeline's remapping + if not cls._host_settings: + cls._host_settings = get_current_project_settings()["flame"] + + # [Deprecated] way of remapping if not cls._mapping: - settings = get_current_project_settings()["flame"] - mapping = settings["imageio"]["profilesMapping"]["inputs"] + mapping = ( + cls._host_settings["imageio"]["profilesMapping"]["inputs"]) cls._mapping = { input["ocioName"]: input["flameName"] for input in mapping } - return cls._mapping.get(input_colorspace) + native_name = cls._mapping.get(input_colorspace) + + if not native_name: + native_name = get_remapped_colorspace_to_native( + input_colorspace, "flame", cls._host_settings["imageio"]) + + return native_name class OpenClipSolver(flib.MediaInfoFile): From 6473fac9042e9257c05c2c4020c3352c178da5f5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 May 2023 16:25:34 +0200 Subject: [PATCH 161/347] fixing cashing nested data structure --- openpype/pipeline/colorspace.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index fa7ad5133c..5c449e0a4e 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -17,9 +17,11 @@ from openpype.pipeline import Anatomy log = Logger.get_logger(__name__) + class cashed_data: remapping: dict = None + @contextlib.contextmanager def _make_temp_json_file(): """Wrapping function for json temp file @@ -510,7 +512,7 @@ def get_remapped_colorspace_to_native( str: native colorspace name defined in remapping or None """ - if not cashed_data.remapping.get(host_name): + if not cashed_data.remapping.get(host_name, {}).get("to_native"): remapping_rules = imageio_host_settings["remapping"]["rules"] cashed_data.remapping[host_name] = { "to_native": { @@ -534,7 +536,7 @@ def get_remapped_colorspace_from_native( str: ocio colorspace name defined in remapping or None """ - if not cashed_data.remapping.get(host_name): + if not cashed_data.remapping.get(host_name, {}).get("from_native"): remapping_rules = imageio_host_settings["remapping"]["rules"] cashed_data.remapping[host_name] = { "from_native": { From 31d04e492b8277bcc6c982111bb783632a8c8e1d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 May 2023 16:28:23 +0200 Subject: [PATCH 162/347] cosmetics --- openpype/pipeline/colorspace.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 5c449e0a4e..f0fb7cf7f5 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -18,8 +18,8 @@ from openpype.pipeline import Anatomy log = Logger.get_logger(__name__) -class cashed_data: - remapping: dict = None +class CashedData: + remapping = None @contextlib.contextmanager @@ -512,16 +512,16 @@ def get_remapped_colorspace_to_native( str: native colorspace name defined in remapping or None """ - if not cashed_data.remapping.get(host_name, {}).get("to_native"): + if not CashedData.remapping.get(host_name, {}).get("to_native"): remapping_rules = imageio_host_settings["remapping"]["rules"] - cashed_data.remapping[host_name] = { + CashedData.remapping[host_name] = { "to_native": { rule["ocio_name"]: input["host_native_name"] for rule in remapping_rules } } - return cashed_data.remapping[host_name]["to_native"].get( + return CashedData.remapping[host_name]["to_native"].get( ocio_colorspace_name) @@ -536,16 +536,16 @@ def get_remapped_colorspace_from_native( str: ocio colorspace name defined in remapping or None """ - if not cashed_data.remapping.get(host_name, {}).get("from_native"): + if not CashedData.remapping.get(host_name, {}).get("from_native"): remapping_rules = imageio_host_settings["remapping"]["rules"] - cashed_data.remapping[host_name] = { + CashedData.remapping[host_name] = { "from_native": { input["host_native_name"]: rule["ocio_name"] for rule in remapping_rules } } - return cashed_data.remapping[host_name]["from_native"].get( + return CashedData.remapping[host_name]["from_native"].get( host_native_colorspace_name) From 0f8a998e3aeac42e44c137ca837b75ab1cda15d1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 May 2023 16:30:32 +0200 Subject: [PATCH 163/347] old docstring fix --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index f0fb7cf7f5..5af313c570 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -333,7 +333,7 @@ def get_imageio_config( Defaults to None. Returns: - dict or bool: config path data or None + dict: config path data or empty dict """ project_settings = project_settings or get_project_settings(project_name) anatomy = anatomy or Anatomy(project_name) From e6b301fecbfc6ec8e5bd784808f7654123f07384 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 May 2023 16:45:43 +0200 Subject: [PATCH 164/347] defaults update --- openpype/settings/defaults/project_settings/flame.json | 3 +++ openpype/settings/defaults/project_settings/photoshop.json | 3 +++ openpype/settings/defaults/project_settings/resolve.json | 3 +++ .../settings/defaults/project_settings/substancepainter.json | 5 +++-- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index 64021baeef..19773727ca 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -1,6 +1,9 @@ { "imageio": { "activate_host_color_management": true, + "remapping": { + "rules": [] + }, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 17da5dd738..ffcf87d8a5 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -1,6 +1,9 @@ { "imageio": { "activate_host_color_management": true, + "remapping": { + "rules": [] + }, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/resolve.json b/openpype/settings/defaults/project_settings/resolve.json index 7379e74200..f2d3727be1 100644 --- a/openpype/settings/defaults/project_settings/resolve.json +++ b/openpype/settings/defaults/project_settings/resolve.json @@ -1,6 +1,9 @@ { "imageio": { "activate_host_color_management": true, + "remapping": { + "rules": [] + }, "ocio_config": { "override_global_config": false, "filepath": [] diff --git a/openpype/settings/defaults/project_settings/substancepainter.json b/openpype/settings/defaults/project_settings/substancepainter.json index 60929e85fd..4a1b86f3f4 100644 --- a/openpype/settings/defaults/project_settings/substancepainter.json +++ b/openpype/settings/defaults/project_settings/substancepainter.json @@ -1,11 +1,12 @@ { "imageio": { + "activate_host_color_management": true, "ocio_config": { - "enabled": true, + "override_global_config": true, "filepath": [] }, "file_rules": { - "enabled": true, + "override_global_rules": true, "rules": {} } }, From 1eef778b04e9505e09da3fe3ef7e6ca5d83d5594 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 24 May 2023 17:43:35 +0200 Subject: [PATCH 165/347] Collect frame range for all rop nodes --- .../houdini/plugins/publish/collect_frames.py | 4 +- .../plugins/publish/collect_instances.py | 6 --- .../publish/collect_rop_frame_range.py | 41 +++++++++++++++++++ 3 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 openpype/hosts/houdini/plugins/publish/collect_rop_frame_range.py diff --git a/openpype/hosts/houdini/plugins/publish/collect_frames.py b/openpype/hosts/houdini/plugins/publish/collect_frames.py index 059793e3c5..91a3d9d170 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -11,15 +11,13 @@ from openpype.hosts.houdini.api import lib class CollectFrames(pyblish.api.InstancePlugin): """Collect all frames which would be saved from the ROP nodes""" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder + 0.01 label = "Collect Frames" families = ["vdbcache", "imagesequence", "ass", "redshiftproxy", "review"] def process(self, instance): ropnode = hou.node(instance.data["instance_node"]) - frame_data = lib.get_frame_data(ropnode) - instance.data.update(frame_data) start_frame = instance.data.get("frameStart", None) end_frame = instance.data.get("frameEnd", None) diff --git a/openpype/hosts/houdini/plugins/publish/collect_instances.py b/openpype/hosts/houdini/plugins/publish/collect_instances.py index 5d5347f96e..5fa3e9655e 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_instances.py +++ b/openpype/hosts/houdini/plugins/publish/collect_instances.py @@ -70,16 +70,10 @@ class CollectInstances(pyblish.api.ContextPlugin): if "active" in data: data["publish"] = data["active"] - data.update(self.get_frame_data(node)) - # Create nice name if the instance has a frame range. label = data.get("name", node.name()) label += " (%s)" % data["asset"] # include asset in name - if "frameStart" in data and "frameEnd" in data: - frames = "[{frameStart} - {frameEnd}]".format(**data) - label = "{} {}".format(label, frames) - instance = context.create_instance(label) # Include `families` using `family` data diff --git a/openpype/hosts/houdini/plugins/publish/collect_rop_frame_range.py b/openpype/hosts/houdini/plugins/publish/collect_rop_frame_range.py new file mode 100644 index 0000000000..2a6be6b9f1 --- /dev/null +++ b/openpype/hosts/houdini/plugins/publish/collect_rop_frame_range.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +"""Collector plugin for frames data on ROP instances.""" +import hou # noqa +import pyblish.api +from openpype.hosts.houdini.api import lib + + +class CollectRopFrameRange(pyblish.api.InstancePlugin): + """Collect all frames which would be saved from the ROP nodes""" + + order = pyblish.api.CollectorOrder + label = "Collect RopNode Frame Range" + + def process(self, instance): + + node_path = instance.data.get("instance_node") + if node_path is None: + # Instance without instance node like a workfile instance + return + + ropnode = hou.node(node_path) + frame_data = lib.get_frame_data(ropnode) + + if "frameStart" in frame_data and "frameEnd" in frame_data: + + # Log artist friendly message about the collected frame range + message = ( + "Frame range {0[frameStart]} - {0[frameEnd]}" + ).format(frame_data) + if frame_data.get("step", 1.0) != 1.0: + message += " with step {0[step]}".format(frame_data) + self.log.info(message) + + instance.data.update(frame_data) + + # Add frame range to label if the instance has a frame range. + label = instance.data.get("label", instance.data["name"]) + instance.data["label"] = ( + "{0} [{1[frameStart]} - {1[frameEnd]}]".format(label, + frame_data) + ) From 7820d92dc9fefa090e773e720d3a122989025b5c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 May 2023 09:37:26 +0100 Subject: [PATCH 166/347] Add pools as last attributes --- .../maya/plugins/create/create_render.py | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 387b7321b9..4681175808 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -181,16 +181,34 @@ class CreateRender(plugin.Creator): primary_pool = pool_setting["primary_pool"] sorted_pools = self._set_default_pool(list(pools), primary_pool) - cmds.addAttr(self.instance, longName="primaryPool", - attributeType="enum", - enumName=":".join(sorted_pools)) + cmds.addAttr( + self.instance, + longName="primaryPool", + attributeType="enum", + enumName=":".join(sorted_pools) + ) + cmds.setAttr( + "{}.primaryPool".format(self.instance), + 0, + keyable=False, + channelBox=True + ) pools = ["-"] + pools secondary_pool = pool_setting["secondary_pool"] sorted_pools = self._set_default_pool(list(pools), secondary_pool) - cmds.addAttr("{}.secondaryPool".format(self.instance), - attributeType="enum", - enumName=":".join(sorted_pools)) + cmds.addAttr( + self.instance, + longName="secondaryPool", + attributeType="enum", + enumName=":".join(sorted_pools) + ) + cmds.setAttr( + "{}.secondaryPool".format(self.instance), + 0, + keyable=False, + channelBox=True + ) def _create_render_settings(self): """Create instance settings.""" @@ -260,6 +278,12 @@ class CreateRender(plugin.Creator): default_priority) self.data["tile_priority"] = tile_priority + strict_error_checking = maya_submit_dl.get("strict_error_checking", + True) + self.data["strict_error_checking"] = strict_error_checking + + # Pool attributes should be last since they will be recreated when + # the deadline server changes. pool_setting = (self._project_settings["deadline"] ["publish"] ["CollectDeadlinePools"]) @@ -272,9 +296,6 @@ class CreateRender(plugin.Creator): secondary_pool = pool_setting["secondary_pool"] self.data["secondaryPool"] = self._set_default_pool(pool_names, secondary_pool) - strict_error_checking = maya_submit_dl.get("strict_error_checking", - True) - self.data["strict_error_checking"] = strict_error_checking if muster_enabled: self.log.info(">>> Loading Muster credentials ...") From 1fe89ebbb6096245fdebd59a0f16b150cb33e529 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 May 2023 09:38:04 +0100 Subject: [PATCH 167/347] Fix getting server settings. --- .../maya/plugins/publish/collect_render.py | 2 +- .../collect_deadline_server_from_instance.py | 41 ++++++++++++++----- .../collect_default_deadline_server.py | 3 +- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 7c47f17acb..babd494758 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -336,7 +336,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): context.data["system_settings"]["modules"]["deadline"] ) if deadline_settings["enabled"]: - data["deadlineUrl"] = render_instance.data.get("deadlineUrl") + data["deadlineUrl"] = render_instance.data["deadlineUrl"] if self.sync_workfile_version: data["version"] = context.data["version"] diff --git a/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index 9981bead3e..2de6073e29 100644 --- a/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/openpype/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -5,23 +5,26 @@ This is resolving index of server lists stored in `deadlineServers` instance attribute or using default server if that attribute doesn't exists. """ +from maya import cmds + import pyblish.api class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): """Collect Deadline Webservice URL from instance.""" - order = pyblish.api.CollectorOrder + 0.415 + # Run before collect_render. + order = pyblish.api.CollectorOrder + 0.005 label = "Deadline Webservice from the Instance" families = ["rendering", "renderlayer"] + hosts = ["maya"] def process(self, instance): instance.data["deadlineUrl"] = self._collect_deadline_url(instance) self.log.info( "Using {} for submission.".format(instance.data["deadlineUrl"])) - @staticmethod - def _collect_deadline_url(render_instance): + def _collect_deadline_url(self, render_instance): # type: (pyblish.api.Instance) -> str """Get Deadline Webservice URL from render instance. @@ -49,8 +52,16 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): default_server = render_instance.context.data["defaultDeadline"] instance_server = render_instance.data.get("deadlineServers") if not instance_server: + self.log.debug("Using default server.") return default_server + # Get instance server as sting. + if isinstance(instance_server, int): + instance_server = cmds.getAttr( + "{}.deadlineServers".format(render_instance.data["objset"]), + asString=True + ) + default_servers = deadline_settings["deadline_urls"] project_servers = ( render_instance.context.data @@ -58,15 +69,23 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): ["deadline"] ["deadline_servers"] ) - deadline_servers = { + if not project_servers: + self.log.debug("Not project servers found. Using default servers.") + return default_servers[instance_server] + + project_enabled_servers = { k: default_servers[k] for k in project_servers if k in default_servers } - # This is Maya specific and may not reflect real selection of deadline - # url as dictionary keys in Python 2 are not ordered - return deadline_servers[ - list(deadline_servers.keys())[ - int(render_instance.data.get("deadlineServers")) - ] - ] + + msg = ( + "\"{}\" server on instance is not enabled in project settings." + " Enabled project servers:\n{}".format( + instance_server, project_enabled_servers + ) + ) + assert instance_server in project_enabled_servers, msg + + self.log.debug("Using project approved server.") + return project_enabled_servers[instance_server] diff --git a/openpype/modules/deadline/plugins/publish/collect_default_deadline_server.py b/openpype/modules/deadline/plugins/publish/collect_default_deadline_server.py index cb2b0cf156..1a0d615dc3 100644 --- a/openpype/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/openpype/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -17,7 +17,8 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): `CollectDeadlineServerFromInstance`. """ - order = pyblish.api.CollectorOrder + 0.410 + # Run before collect_deadline_server_instance. + order = pyblish.api.CollectorOrder + 0.0025 label = "Default Deadline Webservice" pass_mongo_url = False From 3ea8425fd5f16450b9beef93413aa69aa98de238 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 May 2023 11:37:13 +0100 Subject: [PATCH 168/347] Support working with single frame renders. --- openpype/plugins/publish/extract_color_transcode.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 45b10620d1..f7c8af9318 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -184,6 +184,11 @@ class ExtractOIIOTranscode(publish.Extractor): if tag == "review": added_review = True + # If there is only 1 file outputted then convert list to + # string, cause that'll indicate that its not a sequence. + if len(new_repre["files"]) == 1: + new_repre["files"] = new_repre["files"][0] + new_representations.append(new_repre) added_representations = True From 71b3242abd277995482a410720a4a84a13818a3a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 May 2023 11:41:10 +0100 Subject: [PATCH 169/347] Missing deadlineUrl on instances metadata. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 68eb0a437d..22370dea14 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -1089,6 +1089,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): deadline_publish_job_id = \ self._submit_deadline_post_job(instance, render_job, instances) + # Inject deadline url to instances. + for inst in instances: + inst["deadlineUrl"] = self.deadline_url + # publish job file publish_job = { "asset": asset, From 2388cf53cb10f0989b57d86ff0963670556e7ebb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 May 2023 11:44:05 +0100 Subject: [PATCH 170/347] Support same attribute names on different node types. --- .../publish/validate_rendersettings.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index ebf7b3138d..0ca7c6f5a7 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -274,16 +274,18 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): # go through definitions and test if such node.attribute exists. # if so, compare its value from the one required. - for attribute, data in cls.get_nodes(instance, renderer).items(): + for data in cls.get_nodes(instance, renderer): for node in data["nodes"]: try: render_value = cmds.getAttr( - "{}.{}".format(node, attribute) + "{}.{}".format(node, data["attribute"]) ) except RuntimeError: invalid = True cls.log.error( - "Cannot get value of {}.{}".format(node, attribute) + "Cannot get value of {}.{}".format( + node, data["attribute"] + ) ) else: if render_value not in data["values"]: @@ -291,7 +293,10 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cls.log.error( "Invalid value {} set on {}.{}. Expecting " "{}".format( - render_value, node, attribute, data["values"] + render_value, + node, + data["attribute"], + data["values"] ) ) @@ -305,7 +310,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): "{}_render_attributes".format(renderer) ) or [] ) - result = {} + result = [] for attr, values in OrderedDict(validation_settings).items(): values = [convert_to_int_or_float(v) for v in values if v] @@ -335,7 +340,13 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): ) continue - result[attribute_name] = {"nodes": nodes, "values": values} + result.append( + { + "attribute": attribute_name, + "nodes": nodes, + "values": values + } + ) return result @@ -350,11 +361,11 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): "{aov_separator}", instance.data.get("aovSeparator", "_") ) - for attribute, data in cls.get_nodes(instance, renderer).items(): + for data in cls.get_nodes(instance, renderer): if not data["values"]: continue for node in data["nodes"]: - lib.set_attribute(attribute, data["values"][0], node) + lib.set_attribute(data["attribute"], data["values"][0], node) with lib.renderlayer(layer_node): default = lib.RENDER_ATTRS['default'] From 83d79b9eb80bc1ffc90f941b4d607e20134065c0 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 May 2023 11:48:08 +0100 Subject: [PATCH 171/347] Repair RenderPass token when merging AOVs. --- .../maya/plugins/publish/validate_rendersettings.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index ebf7b3138d..a5d5ab0c9e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -364,6 +364,17 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cmds.setAttr("defaultRenderGlobals.animation", True) # Repair prefix + if renderer == "arnold": + multipart = cmds.getAttr("defaultArnoldDriver.mergeAOVs") + if multipart: + separator_variations = [ + "_", + "_", + "", + ] + for variant in separator_variations: + default_prefix = default_prefix.replace(variant, "") + if renderer != "renderman": node = render_attrs["node"] prefix_attr = render_attrs["prefix"] From ee41b877e666db4984d7525b05187b56d1e7b2b6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 May 2023 16:10:17 +0200 Subject: [PATCH 172/347] refactor file rules logic to separate host activation This is implementing logic introduced here https://github.com/ynput/OpenPype/pull/4700#discussion_r1193612003 --- openpype/pipeline/colorspace.py | 12 ++++++------ .../defaults/project_settings/aftereffects.json | 2 +- .../settings/defaults/project_settings/blender.json | 2 +- .../defaults/project_settings/celaction.json | 2 +- .../settings/defaults/project_settings/flame.json | 2 +- .../settings/defaults/project_settings/fusion.json | 2 +- .../settings/defaults/project_settings/harmony.json | 2 +- .../settings/defaults/project_settings/hiero.json | 2 +- .../settings/defaults/project_settings/houdini.json | 2 +- openpype/settings/defaults/project_settings/max.json | 2 +- .../settings/defaults/project_settings/maya.json | 2 +- .../settings/defaults/project_settings/nuke.json | 2 +- .../defaults/project_settings/photoshop.json | 2 +- .../settings/defaults/project_settings/resolve.json | 2 +- .../defaults/project_settings/substancepainter.json | 2 +- .../defaults/project_settings/traypublisher.json | 2 +- .../settings/defaults/project_settings/tvpaint.json | 2 +- .../settings/defaults/project_settings/unreal.json | 2 +- .../defaults/project_settings/webpublisher.json | 2 +- .../schemas/template_imageio_file_rules.json | 4 ++-- 20 files changed, 26 insertions(+), 26 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 5af313c570..d4011d32c9 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -483,22 +483,22 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): frules_global = imageio_global["file_rules"] activate_global_rules = frules_global.get( "activate_global_file_rules", False) + global_rules = frules_global["rules"] if not activate_global_rules: log.info( "Colorspace global file rules are disabled." ) - return {} + global_rules = {} # host is optional, some might not have any settings frules_host = imageio_host.get("file_rules", {}) # compile file rules dictionary - override_global_rules = frules_host.get("override_global_rules") - if override_global_rules: - return frules_host["rules"] - else: - return frules_global["rules"] + activate_host_rules = frules_host.get("activate_host_rules") + + # return host rules if activated or global rules + return frules_host["rules"] if activate_host_rules else global_rules def get_remapped_colorspace_to_native( diff --git a/openpype/settings/defaults/project_settings/aftereffects.json b/openpype/settings/defaults/project_settings/aftereffects.json index 1a312c27df..9be8a6e7d5 100644 --- a/openpype/settings/defaults/project_settings/aftereffects.json +++ b/openpype/settings/defaults/project_settings/aftereffects.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 7cdbb0e6fb..eae5b239c8 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -11,7 +11,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/celaction.json b/openpype/settings/defaults/project_settings/celaction.json index 0e8b465118..af56a36649 100644 --- a/openpype/settings/defaults/project_settings/celaction.json +++ b/openpype/settings/defaults/project_settings/celaction.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index 19773727ca..5b4b62c140 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -9,7 +9,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} }, "project": { diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json index 822ec422df..0ee7d6127d 100644 --- a/openpype/settings/defaults/project_settings/fusion.json +++ b/openpype/settings/defaults/project_settings/fusion.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/harmony.json b/openpype/settings/defaults/project_settings/harmony.json index e6fb00a700..02f51d1d2b 100644 --- a/openpype/settings/defaults/project_settings/harmony.json +++ b/openpype/settings/defaults/project_settings/harmony.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index 01eb15bfbc..9c83733b09 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} }, "workfile": { diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 2b7192ff99..a53f1ff202 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json index db203f7f46..bfb1aa4aeb 100644 --- a/openpype/settings/defaults/project_settings/max.json +++ b/openpype/settings/defaults/project_settings/max.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 14d4408138..19c3da13e6 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -416,7 +416,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} }, "workfile": { diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 5262694484..cdfc236d5c 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -15,7 +15,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} }, "viewer": { diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index ffcf87d8a5..71f94f5bfc 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -9,7 +9,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/resolve.json b/openpype/settings/defaults/project_settings/resolve.json index f2d3727be1..da47ae2553 100644 --- a/openpype/settings/defaults/project_settings/resolve.json +++ b/openpype/settings/defaults/project_settings/resolve.json @@ -9,7 +9,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/substancepainter.json b/openpype/settings/defaults/project_settings/substancepainter.json index 4a1b86f3f4..4adeff98ef 100644 --- a/openpype/settings/defaults/project_settings/substancepainter.json +++ b/openpype/settings/defaults/project_settings/substancepainter.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": true, + "activate_host_rules": true, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 6f22f8a6ec..3a42c93515 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index 3c930b84eb..1f4f468656 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json index 5adf1cce60..20e55c74f0 100644 --- a/openpype/settings/defaults/project_settings/unreal.json +++ b/openpype/settings/defaults/project_settings/unreal.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/defaults/project_settings/webpublisher.json b/openpype/settings/defaults/project_settings/webpublisher.json index 17d61ef028..e451bcfc17 100644 --- a/openpype/settings/defaults/project_settings/webpublisher.json +++ b/openpype/settings/defaults/project_settings/webpublisher.json @@ -6,7 +6,7 @@ "filepath": [] }, "file_rules": { - "override_global_rules": false, + "activate_host_rules": false, "rules": {} } }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_file_rules.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_file_rules.json index 829fd02489..5c6c696578 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_file_rules.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_imageio_file_rules.json @@ -7,8 +7,8 @@ "children": [ { "type": "boolean", - "key": "override_global_rules", - "label": "Override global File Rules" + "key": "activate_host_rules", + "label": "Activate Host File Rules" }, { "key": "rules", From 717a2bc81c418a0d77cc7fd730bcb1d4ec18ae90 Mon Sep 17 00:00:00 2001 From: Alexey Bogomolov Date: Thu, 1 Jun 2023 01:04:10 +0300 Subject: [PATCH 173/347] update letterbox docs --- website/docs/project_settings/settings_project_global.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 7bd24a5773..5ddf247d98 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -170,12 +170,10 @@ A profile may generate multiple outputs from a single input. Each output must de - **`Letter Box`** - **Enabled** - Enable letter boxes - - **Ratio** - Ratio of letter boxes - - **Type** - **Letterbox** (horizontal bars) or **Pillarbox** (vertical bars) + - **Ratio** - Ratio of letter boxes. Ratio type is calculated from output image dimensions. If letterbox ratio > image ratio, _letterbox_ is applied. Otherwise _pillarbox_ will be rendered. - **Fill color** - Fill color of boxes (RGBA: 0-255) - **Line Thickness** - Line thickness on the edge of box (set to `0` to turn off) - - **Fill color** - Line color on the edge of box (RGBA: 0-255) - - **Example** + - **Line color** - Line color on the edge of box (RGBA: 0-255) ![global_extract_review_letter_box_settings](assets/global_extract_review_letter_box_settings.png) ![global_extract_review_letter_box](assets/global_extract_review_letter_box.png) From b048a4a40ea11661be95a61e5cf58076f0b404fe Mon Sep 17 00:00:00 2001 From: Alexey Bogomolov Date: Thu, 1 Jun 2023 01:05:02 +0300 Subject: [PATCH 174/347] update letterbox screenshot --- ...bal_extract_review_letter_box_settings.png | Bin 6926 -> 27150 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/website/docs/project_settings/assets/global_extract_review_letter_box_settings.png b/website/docs/project_settings/assets/global_extract_review_letter_box_settings.png index 80e00702e6193dbac34e4976d78133c2ae413d0c..76dd9b372a03142c6849b6ce5347cd780a2415fe 100644 GIT binary patch literal 27150 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 From eeaa79125ce111272e94e3f5c7f2d6c7f3b154f3 Mon Sep 17 00:00:00 2001 From: JackP Date: Thu, 1 Jun 2023 10:06:08 +0100 Subject: [PATCH 175/347] refactor: use actual pymxs implementation --- .../hosts/max/plugins/load/load_model_fbx.py | 36 ++++++++----------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_model_fbx.py b/openpype/hosts/max/plugins/load/load_model_fbx.py index 01e6acae12..61101c482d 100644 --- a/openpype/hosts/max/plugins/load/load_model_fbx.py +++ b/openpype/hosts/max/plugins/load/load_model_fbx.py @@ -1,8 +1,5 @@ import os -from openpype.pipeline import ( - load, - get_representation_path -) +from openpype.pipeline import load, get_representation_path from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib from openpype.hosts.max.api.lib import maintained_selection @@ -24,10 +21,7 @@ class FbxModelLoader(load.LoaderPlugin): rt.FBXImporterSetParam("Animation", False) rt.FBXImporterSetParam("Cameras", False) rt.FBXImporterSetParam("Preserveinstances", True) - rt.importFile( - filepath, - rt.name("noPrompt"), - using=rt.FBXIMP) + rt.importFile(filepath, rt.name("noPrompt"), using=rt.FBXIMP) container = rt.getNodeByName(f"{name}") if not container: @@ -38,7 +32,8 @@ class FbxModelLoader(load.LoaderPlugin): selection.Parent = container return containerise( - name, [container], context, loader=self.__class__.__name__) + name, [container], context, loader=self.__class__.__name__ + ) def update(self, container, representation): from pymxs import runtime as rt @@ -46,24 +41,21 @@ class FbxModelLoader(load.LoaderPlugin): path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) rt.select(node.Children) - fbx_reimport_cmd = ( - f""" -FBXImporterSetParam "Animation" false -FBXImporterSetParam "Cameras" false -FBXImporterSetParam "AxisConversionMethod" true -FbxExporterSetParam "UpAxis" "Y" -FbxExporterSetParam "Preserveinstances" true -importFile @"{path}" #noPrompt using:FBXIMP - """) - rt.execute(fbx_reimport_cmd) + rt.FBXImporterSetParam("Animation", False) + rt.FBXImporterSetParam("Cameras", False) + rt.FBXImporterSetParam("AxisConversionMethod", True) + rt.FBXImporterSetParam("UpAxis", "Y") + rt.FBXImporterSetParam("Preserveinstances", True) + rt.importFile(path, rt.name("noPrompt"), using=rt.FBXIMP) with maintained_selection(): rt.select(node) - lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) - }) + lib.imprint( + container["instance_node"], + {"representation": str(representation["_id"])}, + ) def switch(self, container, representation): self.update(container, representation) From e51967a6596efc2d0464728a4df9ac19d232995b Mon Sep 17 00:00:00 2001 From: JackP Date: Thu, 1 Jun 2023 10:09:50 +0100 Subject: [PATCH 176/347] refactor: use correct pymxs --- .../hosts/max/plugins/load/load_pointcache.py | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_pointcache.py b/openpype/hosts/max/plugins/load/load_pointcache.py index b3e12adc7b..5fb9772f87 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache.py +++ b/openpype/hosts/max/plugins/load/load_pointcache.py @@ -5,9 +5,7 @@ Because of limited api, alembics can be only loaded, but not easily updated. """ import os -from openpype.pipeline import ( - load, get_representation_path -) +from openpype.pipeline import load, get_representation_path from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib @@ -15,9 +13,7 @@ from openpype.hosts.max.api import lib class AbcLoader(load.LoaderPlugin): """Alembic loader.""" - families = ["camera", - "animation", - "pointcache"] + families = ["camera", "animation", "pointcache"] label = "Load Alembic" representations = ["abc"] order = -10 @@ -30,21 +26,17 @@ class AbcLoader(load.LoaderPlugin): file_path = os.path.normpath(self.fname) abc_before = { - c for c in rt.rootNode.Children + c + for c in rt.rootNode.Children if rt.classOf(c) == rt.AlembicContainer } - abc_export_cmd = (f""" -AlembicImport.ImportToRoot = false - -importFile @"{file_path}" #noPrompt - """) - - self.log.debug(f"Executing command: {abc_export_cmd}") - rt.execute(abc_export_cmd) + rt.AlembicImport.ImportToRoot = False + rt.importFile(file_path, rt.name("noPrompt")) abc_after = { - c for c in rt.rootNode.Children + c + for c in rt.rootNode.Children if rt.classOf(c) == rt.AlembicContainer } @@ -57,7 +49,8 @@ importFile @"{file_path}" #noPrompt abc_container = abc_containers.pop() return containerise( - name, [abc_container], context, loader=self.__class__.__name__) + name, [abc_container], context, loader=self.__class__.__name__ + ) def update(self, container, representation): from pymxs import runtime as rt @@ -69,9 +62,10 @@ importFile @"{file_path}" #noPrompt for alembic_object in alembic_objects: alembic_object.source = path - lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) - }) + lib.imprint( + container["instance_node"], + {"representation": str(representation["_id"])}, + ) def switch(self, container, representation): self.update(container, representation) From 31b331811cf20c4a8b750d34d11a463aaf28d7ff Mon Sep 17 00:00:00 2001 From: JackP Date: Thu, 1 Jun 2023 10:15:05 +0100 Subject: [PATCH 177/347] refactor: use proper pymxs --- openpype/hosts/max/plugins/load/load_model.py | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_model.py b/openpype/hosts/max/plugins/load/load_model.py index 95ee014e07..febcaed8be 100644 --- a/openpype/hosts/max/plugins/load/load_model.py +++ b/openpype/hosts/max/plugins/load/load_model.py @@ -1,8 +1,5 @@ - import os -from openpype.pipeline import ( - load, get_representation_path -) +from openpype.pipeline import load, get_representation_path from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib from openpype.hosts.max.api.lib import maintained_selection @@ -24,24 +21,20 @@ class ModelAbcLoader(load.LoaderPlugin): file_path = os.path.normpath(self.fname) abc_before = { - c for c in rt.rootNode.Children + c + for c in rt.rootNode.Children if rt.classOf(c) == rt.AlembicContainer } - abc_import_cmd = (f""" -AlembicImport.ImportToRoot = false -AlembicImport.CustomAttributes = true -AlembicImport.UVs = true -AlembicImport.VertexColors = true - -importFile @"{file_path}" #noPrompt - """) - - self.log.debug(f"Executing command: {abc_import_cmd}") - rt.execute(abc_import_cmd) + rt.AlembicImport.ImportToRoot = False + rt.AlembicImport.CustomAttributes = True + rt.AlembicImport.UVs = True + rt.AlembicImport.VertexColors = True + rt.importFile(filepath, rt.name("noPrompt")) abc_after = { - c for c in rt.rootNode.Children + c + for c in rt.rootNode.Children if rt.classOf(c) == rt.AlembicContainer } @@ -54,10 +47,12 @@ importFile @"{file_path}" #noPrompt abc_container = abc_containers.pop() return containerise( - name, [abc_container], context, loader=self.__class__.__name__) + name, [abc_container], context, loader=self.__class__.__name__ + ) def update(self, container, representation): from pymxs import runtime as rt + path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) rt.select(node.Children) @@ -76,9 +71,10 @@ importFile @"{file_path}" #noPrompt with maintained_selection(): rt.select(node) - lib.imprint(container["instance_node"], { - "representation": str(representation["_id"]) - }) + lib.imprint( + container["instance_node"], + {"representation": str(representation["_id"])}, + ) def switch(self, container, representation): self.update(container, representation) From 3fdbcd3247ad5bbd13230d17bdbec90f65b3985c Mon Sep 17 00:00:00 2001 From: JackP Date: Thu, 1 Jun 2023 10:31:50 +0100 Subject: [PATCH 178/347] fix: incorrect var name --- openpype/hosts/max/plugins/load/load_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/load/load_model.py b/openpype/hosts/max/plugins/load/load_model.py index febcaed8be..5f1ae3378e 100644 --- a/openpype/hosts/max/plugins/load/load_model.py +++ b/openpype/hosts/max/plugins/load/load_model.py @@ -30,7 +30,7 @@ class ModelAbcLoader(load.LoaderPlugin): rt.AlembicImport.CustomAttributes = True rt.AlembicImport.UVs = True rt.AlembicImport.VertexColors = True - rt.importFile(filepath, rt.name("noPrompt")) + rt.importFile(file_path, rt.name("noPrompt")) abc_after = { c From 711dd888e960fb3cdb5ce246fbcefb920ca1b217 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 1 Jun 2023 12:37:39 +0200 Subject: [PATCH 179/347] updating testing data --- tests/unit/openpype/pipeline/publish/test_publish_plugins.py | 2 +- tests/unit/openpype/pipeline/test_colorspace.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/openpype/pipeline/publish/test_publish_plugins.py b/tests/unit/openpype/pipeline/publish/test_publish_plugins.py index bbeab2cc90..aace8cf7e3 100644 --- a/tests/unit/openpype/pipeline/publish/test_publish_plugins.py +++ b/tests/unit/openpype/pipeline/publish/test_publish_plugins.py @@ -37,7 +37,7 @@ class TestPipelinePublishPlugins(TestPipeline): # files are the same as those used in `test_pipeline_colorspace` TEST_FILES = [ ( - "1YinxOToVyAd3-jAMFgVf7EWQa2x8Ma-O", + "1Lf-mFxev7xiwZCWfImlRcw7Fj8XgNQMh", "test_pipeline_colorspace.zip", "" ) diff --git a/tests/unit/openpype/pipeline/test_colorspace.py b/tests/unit/openpype/pipeline/test_colorspace.py index d0981723ad..c22acee2d4 100644 --- a/tests/unit/openpype/pipeline/test_colorspace.py +++ b/tests/unit/openpype/pipeline/test_colorspace.py @@ -31,7 +31,7 @@ class TestPipelineColorspace(TestPipeline): TEST_FILES = [ ( - "1YinxOToVyAd3-jAMFgVf7EWQa2x8Ma-O", + "1Lf-mFxev7xiwZCWfImlRcw7Fj8XgNQMh", "test_pipeline_colorspace.zip", "" ) From aab6e19b5ed0f4f76335cea49342f098ed548319 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Jun 2023 15:48:52 +0200 Subject: [PATCH 180/347] skip roots validation for documents only variant of the functions --- openpype/lib/project_backpack.py | 42 +++++++++++++++++--------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/openpype/lib/project_backpack.py b/openpype/lib/project_backpack.py index 07107ec011..674eaa3b91 100644 --- a/openpype/lib/project_backpack.py +++ b/openpype/lib/project_backpack.py @@ -113,26 +113,29 @@ def pack_project( project_name )) - roots = project_doc["config"]["roots"] - # Determine root directory of project - source_root = None - source_root_name = None - for root_name, root_value in roots.items(): - if source_root is not None: - raise ValueError( - "Packaging is supported only for single root projects" - ) - source_root = root_value - source_root_name = root_name + root_path = None + source_root = {} + project_source_path = None + if not only_documents: + roots = project_doc["config"]["roots"] + # Determine root directory of project + source_root_name = None + for root_name, root_value in roots.items(): + if source_root is not None: + raise ValueError( + "Packaging is supported only for single root projects" + ) + source_root = root_value + source_root_name = root_name - root_path = source_root[platform.system().lower()] - print("Using root \"{}\" with path \"{}\"".format( - source_root_name, root_path - )) + root_path = source_root[platform.system().lower()] + print("Using root \"{}\" with path \"{}\"".format( + source_root_name, root_path + )) - project_source_path = os.path.join(root_path, project_name) - if not os.path.exists(project_source_path): - raise ValueError("Didn't find source of project files") + project_source_path = os.path.join(root_path, project_name) + if not os.path.exists(project_source_path): + raise ValueError("Didn't find source of project files") # Determine zip filepath where data will be stored if not destination_dir: @@ -273,8 +276,7 @@ def unpack_project( low_platform = platform.system().lower() project_name = metadata["project_name"] - source_root = metadata["root"] - root_path = source_root[low_platform] + root_path = metadata["root"].get(low_platform) # Drop existing collection replace_project_documents(project_name, docs, database_name) From bb74019d3e6acd86c536cfc88e43dc78e2ea6652 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 31 May 2023 14:15:21 +0200 Subject: [PATCH 181/347] removing info knob from nuke creators also remove node if instance is removed --- openpype/hosts/nuke/api/lib.py | 2 -- openpype/hosts/nuke/api/pipeline.py | 1 + openpype/hosts/nuke/api/plugin.py | 16 ---------------- .../hosts/nuke/plugins/create/create_backdrop.py | 2 -- .../hosts/nuke/plugins/create/create_camera.py | 2 -- .../hosts/nuke/plugins/create/create_gizmo.py | 2 -- .../hosts/nuke/plugins/create/create_model.py | 2 -- .../hosts/nuke/plugins/create/create_source.py | 2 +- .../nuke/plugins/create/create_write_image.py | 1 - .../plugins/create/create_write_prerender.py | 1 - .../nuke/plugins/create/create_write_render.py | 1 - 11 files changed, 2 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index a439142051..4a57bc3165 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1403,8 +1403,6 @@ def create_write_node( # adding write to read button add_button_clear_rendered(GN, os.path.dirname(fpath)) - GN.addKnob(nuke.Text_Knob('', '')) - # set tile color tile_color = next( iter( diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 75b0f80d21..88f7144542 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -564,6 +564,7 @@ def remove_instance(instance): instance_node = instance.transient_data["node"] instance_knob = instance_node.knobs()[INSTANCE_DATA_KNOB] instance_node.removeKnob(instance_knob) + nuke.delete(instance_node) def select_instance(instance): diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 3566cb64c1..7035da2bb5 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -75,20 +75,6 @@ class NukeCreator(NewCreator): for pass_key in keys: creator_attrs[pass_key] = pre_create_data[pass_key] - def add_info_knob(self, node): - if "OP_info" in node.knobs().keys(): - return - - # add info text - info_knob = nuke.Text_Knob("OP_info", "") - info_knob.setValue(""" - -

This node is maintained by OpenPype Publisher.

-

To remove it use Publisher gui.

-
- """) - node.addKnob(info_knob) - def check_existing_subset(self, subset_name): """Make sure subset name is unique. @@ -153,8 +139,6 @@ class NukeCreator(NewCreator): created_node = nuke.createNode(node_type) created_node["name"].setValue(node_name) - self.add_info_knob(created_node) - for key, values in node_knobs.items(): if key in created_node.knobs(): created_node["key"].setValue(values) diff --git a/openpype/hosts/nuke/plugins/create/create_backdrop.py b/openpype/hosts/nuke/plugins/create/create_backdrop.py index ff415626be..52959bbef2 100644 --- a/openpype/hosts/nuke/plugins/create/create_backdrop.py +++ b/openpype/hosts/nuke/plugins/create/create_backdrop.py @@ -36,8 +36,6 @@ class CreateBackdrop(NukeCreator): created_node["note_font_size"].setValue(24) created_node["label"].setValue("[{}]".format(node_name)) - self.add_info_knob(created_node) - return created_node def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/nuke/plugins/create/create_camera.py b/openpype/hosts/nuke/plugins/create/create_camera.py index 5553645af6..b84280b11b 100644 --- a/openpype/hosts/nuke/plugins/create/create_camera.py +++ b/openpype/hosts/nuke/plugins/create/create_camera.py @@ -39,8 +39,6 @@ class CreateCamera(NukeCreator): created_node["name"].setValue(node_name) - self.add_info_knob(created_node) - return created_node def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/nuke/plugins/create/create_gizmo.py b/openpype/hosts/nuke/plugins/create/create_gizmo.py index e3ce70dd59..cbe2f635c9 100644 --- a/openpype/hosts/nuke/plugins/create/create_gizmo.py +++ b/openpype/hosts/nuke/plugins/create/create_gizmo.py @@ -40,8 +40,6 @@ class CreateGizmo(NukeCreator): created_node["name"].setValue(node_name) - self.add_info_knob(created_node) - return created_node def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/nuke/plugins/create/create_model.py b/openpype/hosts/nuke/plugins/create/create_model.py index 08a53abca2..a94c9f0313 100644 --- a/openpype/hosts/nuke/plugins/create/create_model.py +++ b/openpype/hosts/nuke/plugins/create/create_model.py @@ -40,8 +40,6 @@ class CreateModel(NukeCreator): created_node["name"].setValue(node_name) - self.add_info_knob(created_node) - return created_node def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/nuke/plugins/create/create_source.py b/openpype/hosts/nuke/plugins/create/create_source.py index 57504b5d53..8419c3ef33 100644 --- a/openpype/hosts/nuke/plugins/create/create_source.py +++ b/openpype/hosts/nuke/plugins/create/create_source.py @@ -32,7 +32,7 @@ class CreateSource(NukeCreator): read_node["tile_color"].setValue( int(self.node_color, 16)) read_node["name"].setValue(node_name) - self.add_info_knob(read_node) + return read_node def create(self, subset_name, instance_data, pre_create_data): diff --git a/openpype/hosts/nuke/plugins/create/create_write_image.py b/openpype/hosts/nuke/plugins/create/create_write_image.py index b74cea5dae..0c8adfb75c 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_image.py +++ b/openpype/hosts/nuke/plugins/create/create_write_image.py @@ -86,7 +86,6 @@ class CreateWriteImage(napi.NukeWriteCreator): "frame": nuke.frame() } ) - self.add_info_knob(created_node) self._add_frame_range_limit(created_node, instance_data) diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 387768b1dd..f46dd2d6d5 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -74,7 +74,6 @@ class CreateWritePrerender(napi.NukeWriteCreator): "height": height } ) - self.add_info_knob(created_node) self._add_frame_range_limit(created_node) diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 09257f662e..c24405873a 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -66,7 +66,6 @@ class CreateWriteRender(napi.NukeWriteCreator): "height": height } ) - self.add_info_knob(created_node) self.integrate_links(created_node, outputs=False) From 47e5d3646046041e1cf55e55e51e0a5818360add Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 1 Jun 2023 17:01:25 +0200 Subject: [PATCH 182/347] :recycle: incorporating comments --- .../houdini/plugins/publish/collect_arnold_rop.py | 15 +++++---------- .../houdini/plugins/publish/collect_karma_rop.py | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py index 2fd419ef9b..946eed3301 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -1,16 +1,12 @@ -import re import os +import re import hou import pyblish.api +from openpype.hosts.houdini.api import colorspace from openpype.hosts.houdini.api.lib import ( - evalParmNoFrame, - get_color_management_preferences -) -from openpype.hosts.houdini.api import ( - colorspace -) + evalParmNoFrame, get_color_management_preferences) class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): @@ -52,7 +48,7 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): } num_aovs = rop.evalParm("ar_aovs") - for index in range(num_aovs): + for index in range(1, num_aovs + 1): i = index + 1 # Skip disabled AOVs @@ -97,8 +93,7 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): # When AOV is explicitly defined in prefix we just swap it out # directly with the AOV suffix to embed it. # Note: ${AOV} seems to be evaluated in the parameter as %AOV% - has_aov_in_prefix = "%AOV%" in prefix - if has_aov_in_prefix: + if "%AOV%" in prefix: # It seems that when some special separator characters are present # before the %AOV% token that Redshift will secretly remove it if # there is no suffix for the current product, for example: diff --git a/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py b/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py index b87bb06767..a41e19d93f 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py @@ -75,7 +75,7 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin): if suffix: # Add ".{suffix}" before the extension prefix_base, ext = os.path.splitext(prefix) - product_name = prefix_base + "." + suffix + ext + product_name = "{}.{}{}".format(prefix_base, suffix,ext) return product_name From 6de4ceabd38d84e4b410e5ad2cb795b5e34c1fa0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 1 Jun 2023 23:13:34 +0800 Subject: [PATCH 183/347] custom settings for write node without publish and register --- .../nuke/startup/ops_write_node_no_publish.py | 67 +++++++++++++++++++ .../defaults/project_settings/nuke.json | 7 ++ 2 files changed, 74 insertions(+) create mode 100644 openpype/hosts/nuke/startup/ops_write_node_no_publish.py diff --git a/openpype/hosts/nuke/startup/ops_write_node_no_publish.py b/openpype/hosts/nuke/startup/ops_write_node_no_publish.py new file mode 100644 index 0000000000..74be6c8de9 --- /dev/null +++ b/openpype/hosts/nuke/startup/ops_write_node_no_publish.py @@ -0,0 +1,67 @@ +import os +import nuke +from pathlib import Path +from openpype.client import get_asset_by_name, get_project +from openpype.pipeline import Anatomy, legacy_io +from openpype.pipeline.template_data import get_template_data +from openpype.hosts.nuke.api.lib import ( + get_imageio_node_setting, + set_node_knobs_from_settings) + + +def main(): + project_name = legacy_io.Session["AVALON_PROJECT"] + asset_name = legacy_io.Session["AVALON_ASSET"] + task_name = legacy_io.Session["AVALON_TASK"] + # fetch asset docs + asset_doc = get_asset_by_name(project_name, asset_name) + + # get task type to fill the timer tag + # template = "{root[work]}/{project[name]}/{hierarchy}/{asset}" + anatomy = Anatomy(project_name) + project_doc = get_project(project_name) + template_data = get_template_data(project_doc, asset_doc) + template_data["root"] = anatomy.roots + template_data["task"] = {"name":task_name} + + padding = int( + anatomy.templates["render"]["frame_padding"] + ) + version_int = 0 + version_int += 1 + if version_int: + version_int += 1 + + node_settings = get_imageio_node_setting( + "Write", "CreateWriteRender", subset=None) + + ext = None + for knob in node_settings["knobs"]: + if knob["name"] == "file_type": + ext = knob["value"] + data = { + "asset": asset_name, + "task": task_name, + "subset": "non_publish_render", + "frame": "#" * padding, + "ext": ext + } + + write_selected_nodes = [ + s for s in nuke.selectedNodes() if s.Class() == "Write"] + + for i in range(len(write_selected_nodes)): + data.update({"version": i}) + data.update(template_data) + + anatomy_filled = anatomy.format(data) + folder = anatomy_filled["work"]["folder"] + render_folder = os.path.join(folder, "render_no_publish") + filename = anatomy_filled["render"]["file"] + file_path = os.path.join(render_folder, filename) + file_path = file_path.replace("\\", "/") + + knobs = node_settings["knobs"] + for w in write_selected_nodes: + w["file"].setValue(file_path) + set_node_knobs_from_settings(w, knobs) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index f01bdf7d50..ae37a14494 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -222,6 +222,13 @@ "title": "OpenPype Docs", "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_nuke_tut')", "tooltip": "Open the OpenPype Nuke user doc page" + }, + { + "type": "action", + "sourcetype": "python", + "title": "Set non publish output for Write Node", + "command": "from openpype.hosts.nuke.startup.ops_write_node_no_publish import main;main();", + "tooltip": "Open the OpenPype Nuke user doc page" } ] }, From 3c64fd3b748318cc7bb788d6b7bb2441c7caec29 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 1 Jun 2023 23:17:55 +0800 Subject: [PATCH 184/347] hound fix --- openpype/hosts/nuke/startup/ops_write_node_no_publish.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/startup/ops_write_node_no_publish.py b/openpype/hosts/nuke/startup/ops_write_node_no_publish.py index 74be6c8de9..cc4c6ccd8f 100644 --- a/openpype/hosts/nuke/startup/ops_write_node_no_publish.py +++ b/openpype/hosts/nuke/startup/ops_write_node_no_publish.py @@ -1,6 +1,5 @@ import os import nuke -from pathlib import Path from openpype.client import get_asset_by_name, get_project from openpype.pipeline import Anatomy, legacy_io from openpype.pipeline.template_data import get_template_data @@ -48,11 +47,11 @@ def main(): } write_selected_nodes = [ - s for s in nuke.selectedNodes() if s.Class() == "Write"] + s for s in nuke.selectedNodes() if s.Class() == "Write"] for i in range(len(write_selected_nodes)): - data.update({"version": i}) - data.update(template_data) + data.update({"version": i}) + data.update(template_data) anatomy_filled = anatomy.format(data) folder = anatomy_filled["work"]["folder"] From 5da6e4b8d0cfcaacf17cbe2f1b37b7d2e271cdb6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 1 Jun 2023 23:18:28 +0800 Subject: [PATCH 185/347] hound fix --- openpype/hosts/nuke/startup/ops_write_node_no_publish.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/startup/ops_write_node_no_publish.py b/openpype/hosts/nuke/startup/ops_write_node_no_publish.py index cc4c6ccd8f..a56074e535 100644 --- a/openpype/hosts/nuke/startup/ops_write_node_no_publish.py +++ b/openpype/hosts/nuke/startup/ops_write_node_no_publish.py @@ -21,7 +21,7 @@ def main(): project_doc = get_project(project_name) template_data = get_template_data(project_doc, asset_doc) template_data["root"] = anatomy.roots - template_data["task"] = {"name":task_name} + template_data["task"] = {"name": task_name} padding = int( anatomy.templates["render"]["frame_padding"] From 7b454a92ceaa70ccab5990663055ae63ada38940 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 1 Jun 2023 17:24:56 +0200 Subject: [PATCH 186/347] :bug: show arnold render settings --- openpype/hosts/houdini/plugins/create/create_arnold_rop.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index 9634bf1bd9..3bb736995e 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -33,11 +33,6 @@ class CreateArnoldRop(plugin.HoudiniCreator): instance_node = hou.node(instance.get("instance_node")) - # Hide Properties Tab on Arnold ROP since that's used - # for rendering instead of .ass Archive Export - parm_template_group = instance_node.parmTemplateGroup() - parm_template_group.hideFolder("Properties", True) - instance_node.setParmTemplateGroup(parm_template_group) ext = pre_create_data.get("image_format") From 1818d061df127549bc18a70ba52692bd04983897 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 1 Jun 2023 17:52:03 +0200 Subject: [PATCH 187/347] :rotating_light: fix hound --- openpype/hosts/houdini/plugins/create/create_arnold_rop.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py index 3bb736995e..bddf26dbd5 100644 --- a/openpype/hosts/houdini/plugins/create/create_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/create/create_arnold_rop.py @@ -33,7 +33,6 @@ class CreateArnoldRop(plugin.HoudiniCreator): instance_node = hou.node(instance.get("instance_node")) - ext = pre_create_data.get("image_format") filepath = "{renders_dir}{subset_name}/{subset_name}.$F4.{ext}".format( From 15ce81e267903862ba9ad3cc440ef7cc72da889b Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 1 Jun 2023 17:56:56 +0200 Subject: [PATCH 188/347] :rotating_light: fix linter errors --- openpype/hosts/houdini/plugins/publish/collect_karma_rop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py b/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py index a41e19d93f..eabb1128d8 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_karma_rop.py @@ -75,7 +75,7 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin): if suffix: # Add ".{suffix}" before the extension prefix_base, ext = os.path.splitext(prefix) - product_name = "{}.{}{}".format(prefix_base, suffix,ext) + product_name = "{}.{}{}".format(prefix_base, suffix, ext) return product_name From 4eaeb5682c643b9bc9842f042875b16bd069c84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Fri, 2 Jun 2023 00:17:37 +0200 Subject: [PATCH 189/347] Remove default windowFlags as it makes the publisher window not show minimize/maximize hints --- openpype/tools/publisher/window.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 6ab444109e..006098cb37 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -66,8 +66,7 @@ class PublisherWindow(QtWidgets.QDialog): on_top_flag = QtCore.Qt.Dialog self.setWindowFlags( - self.windowFlags() - | QtCore.Qt.WindowTitleHint + QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowMaximizeButtonHint | QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowCloseButtonHint From 11db326cc52ce24849e851e2e0d73040c764f942 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 2 Jun 2023 13:27:57 +0800 Subject: [PATCH 190/347] clean up unused code --- openpype/hosts/nuke/startup/ops_write_node_no_publish.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/nuke/startup/ops_write_node_no_publish.py b/openpype/hosts/nuke/startup/ops_write_node_no_publish.py index a56074e535..12a69ff378 100644 --- a/openpype/hosts/nuke/startup/ops_write_node_no_publish.py +++ b/openpype/hosts/nuke/startup/ops_write_node_no_publish.py @@ -26,10 +26,6 @@ def main(): padding = int( anatomy.templates["render"]["frame_padding"] ) - version_int = 0 - version_int += 1 - if version_int: - version_int += 1 node_settings = get_imageio_node_setting( "Write", "CreateWriteRender", subset=None) From 27ae0d9f206ce1b36cb243ce409b7ceafe805bea Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 2 Jun 2023 17:11:16 +0800 Subject: [PATCH 191/347] refactor custom write node script --- .../hosts/nuke/startup/custom_write_node.py | 81 +++++++++++++++++++ .../nuke/startup/ops_write_node_no_publish.py | 62 -------------- .../defaults/project_settings/nuke.json | 2 +- 3 files changed, 82 insertions(+), 63 deletions(-) create mode 100644 openpype/hosts/nuke/startup/custom_write_node.py delete mode 100644 openpype/hosts/nuke/startup/ops_write_node_no_publish.py diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py new file mode 100644 index 0000000000..98751c2c7b --- /dev/null +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -0,0 +1,81 @@ +import os +import nuke +from pathlib import Path +from openpype.hosts.nuke.api.lib import set_node_knobs_from_settings + + +frame_padding = 5 +temp_rendering_path_template = ( + "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}") + +knobs_setting = { + "knobs": [ + { + "type": "text", + "name": "file_type", + "value": "exr" + }, + { + "type": "text", + "name": "datatype", + "value": "16 bit half" + }, + { + "type": "text", + "name": "compression", + "value": "Zip (1 scanline)" + }, + { + "type": "bool", + "name": "autocrop", + "value": True + }, + { + "type": "color_gui", + "name": "tile_color", + "value": [ + 186, + 35, + 35, + 255 + ] + }, + { + "type": "text", + "name": "channels", + "value": "rgb" + }, + { + "type": "text", + "name": "colorspace", + "value": "linear" + }, + { + "type": "bool", + "name": "create_directories", + "value": True + } + ] +} + + +def main(): + write_selected_nodes = [ + s for s in nuke.selectedNodes() if s.Class() == "Write"] + + ext = None + knobs = knobs_setting["knobs"] + for knob in knobs: + if knob["name"] == "file_type": + ext = knob["value"] + for w in write_selected_nodes: + data = { + "work": os.getenv("AVALON_WORKDIR"), + "subset": w["name"].value(), + "frame": "#" * frame_padding, + "ext": ext + } + file_path = temp_rendering_path_template.format(**data) + file_path = file_path.replace("\\", "/") + w["file"].setValue(file_path) + set_node_knobs_from_settings(w, knobs) diff --git a/openpype/hosts/nuke/startup/ops_write_node_no_publish.py b/openpype/hosts/nuke/startup/ops_write_node_no_publish.py deleted file mode 100644 index 12a69ff378..0000000000 --- a/openpype/hosts/nuke/startup/ops_write_node_no_publish.py +++ /dev/null @@ -1,62 +0,0 @@ -import os -import nuke -from openpype.client import get_asset_by_name, get_project -from openpype.pipeline import Anatomy, legacy_io -from openpype.pipeline.template_data import get_template_data -from openpype.hosts.nuke.api.lib import ( - get_imageio_node_setting, - set_node_knobs_from_settings) - - -def main(): - project_name = legacy_io.Session["AVALON_PROJECT"] - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] - # fetch asset docs - asset_doc = get_asset_by_name(project_name, asset_name) - - # get task type to fill the timer tag - # template = "{root[work]}/{project[name]}/{hierarchy}/{asset}" - anatomy = Anatomy(project_name) - project_doc = get_project(project_name) - template_data = get_template_data(project_doc, asset_doc) - template_data["root"] = anatomy.roots - template_data["task"] = {"name": task_name} - - padding = int( - anatomy.templates["render"]["frame_padding"] - ) - - node_settings = get_imageio_node_setting( - "Write", "CreateWriteRender", subset=None) - - ext = None - for knob in node_settings["knobs"]: - if knob["name"] == "file_type": - ext = knob["value"] - data = { - "asset": asset_name, - "task": task_name, - "subset": "non_publish_render", - "frame": "#" * padding, - "ext": ext - } - - write_selected_nodes = [ - s for s in nuke.selectedNodes() if s.Class() == "Write"] - - for i in range(len(write_selected_nodes)): - data.update({"version": i}) - data.update(template_data) - - anatomy_filled = anatomy.format(data) - folder = anatomy_filled["work"]["folder"] - render_folder = os.path.join(folder, "render_no_publish") - filename = anatomy_filled["render"]["file"] - file_path = os.path.join(render_folder, filename) - file_path = file_path.replace("\\", "/") - - knobs = node_settings["knobs"] - for w in write_selected_nodes: - w["file"].setValue(file_path) - set_node_knobs_from_settings(w, knobs) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index ae37a14494..c2610591aa 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -227,7 +227,7 @@ "type": "action", "sourcetype": "python", "title": "Set non publish output for Write Node", - "command": "from openpype.hosts.nuke.startup.ops_write_node_no_publish import main;main();", + "command": "from openpype.hosts.nuke.startup.custom_write_node import main;main();", "tooltip": "Open the OpenPype Nuke user doc page" } ] From 22251f4958d1073ff7ba488ac5d2b3a988d284be Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 2 Jun 2023 17:13:08 +0800 Subject: [PATCH 192/347] hound fix --- openpype/hosts/nuke/startup/custom_write_node.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 98751c2c7b..9538c4f4bc 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -1,6 +1,5 @@ import os import nuke -from pathlib import Path from openpype.hosts.nuke.api.lib import set_node_knobs_from_settings @@ -70,10 +69,10 @@ def main(): ext = knob["value"] for w in write_selected_nodes: data = { - "work": os.getenv("AVALON_WORKDIR"), - "subset": w["name"].value(), - "frame": "#" * frame_padding, - "ext": ext + "work": os.getenv("AVALON_WORKDIR"), + "subset": w["name"].value(), + "frame": "#" * frame_padding, + "ext": ext } file_path = temp_rendering_path_template.format(**data) file_path = file_path.replace("\\", "/") From 7075d5c4452dc0ea1c9f5addf3b96a75aef0ae55 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 2 Jun 2023 17:18:13 +0800 Subject: [PATCH 193/347] add some comment --- .../hooks/pre_add_run_python_script_arg.py | 55 ++++++++++++++++ openpype/hosts/max/api/lib.py | 9 ++- .../plugins/create/create_redshift_proxy.py | 18 ++++++ .../max/plugins/load/load_redshift_proxy.py | 63 +++++++++++++++++++ .../plugins/publish/extract_redshift_proxy.py | 62 ++++++++++++++++++ .../validate_renderer_redshift_proxy.py | 54 ++++++++++++++++ openpype/hosts/nuke/startup/__init__.py | 0 .../hosts/nuke/startup/custom_write_node.py | 1 + .../startup/frame_setting_for_read_nodes.py | 47 ++++++++++++++ openpype/hosts/resolve/api/workio.py | 19 +++--- .../hooks/pre_resolve_launch_last_workfile.py | 45 +++++++++++++ openpype/hosts/resolve/startup.py | 62 ++++++++++++++++++ .../openpype_startup.scriptlib | 21 +++++++ openpype/hosts/resolve/utils.py | 11 ++++ .../plugins/publish/collect_frames_fix.py | 62 +++++++++--------- .../defaults/project_settings/nuke.json | 7 +++ .../defaults/project_settings/resolve.json | 1 + .../schema_project_resolve.json | 5 ++ website/docs/artist_hosts_3dsmax.md | 14 ++--- website/docs/dev_blender.md | 61 ++++++++++++++++++ website/sidebars.js | 1 + 21 files changed, 573 insertions(+), 45 deletions(-) create mode 100644 openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py create mode 100644 openpype/hosts/max/plugins/create/create_redshift_proxy.py create mode 100644 openpype/hosts/max/plugins/load/load_redshift_proxy.py create mode 100644 openpype/hosts/max/plugins/publish/extract_redshift_proxy.py create mode 100644 openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py create mode 100644 openpype/hosts/nuke/startup/__init__.py create mode 100644 openpype/hosts/nuke/startup/frame_setting_for_read_nodes.py create mode 100644 openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py create mode 100644 openpype/hosts/resolve/startup.py create mode 100644 openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib create mode 100644 website/docs/dev_blender.md diff --git a/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py b/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py new file mode 100644 index 0000000000..559e9ae0ce --- /dev/null +++ b/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py @@ -0,0 +1,55 @@ +from pathlib import Path + +from openpype.lib import PreLaunchHook + + +class AddPythonScriptToLaunchArgs(PreLaunchHook): + """Add python script to be executed before Blender launch.""" + + # Append after file argument + order = 15 + app_groups = [ + "blender", + ] + + def execute(self): + if not self.launch_context.data.get("python_scripts"): + return + + # Add path to workfile to arguments + for python_script_path in self.launch_context.data["python_scripts"]: + self.log.info( + f"Adding python script {python_script_path} to launch" + ) + # Test script path exists + python_script_path = Path(python_script_path) + if not python_script_path.exists(): + self.log.warning( + f"Python script {python_script_path} doesn't exist. " + "Skipped..." + ) + continue + + if "--" in self.launch_context.launch_args: + # Insert before separator + separator_index = self.launch_context.launch_args.index("--") + self.launch_context.launch_args.insert( + separator_index, + "-P", + ) + self.launch_context.launch_args.insert( + separator_index + 1, + python_script_path.as_posix(), + ) + else: + self.launch_context.launch_args.extend( + ["-P", python_script_path.as_posix()] + ) + + # Ensure separator + if "--" not in self.launch_context.launch_args: + self.launch_context.launch_args.append("--") + + self.launch_context.launch_args.extend( + [*self.launch_context.data.get("script_args", [])] + ) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index d9213863b1..e2af0720ec 100644 --- a/openpype/hosts/max/api/lib.py +++ b/openpype/hosts/max/api/lib.py @@ -128,7 +128,14 @@ def get_all_children(parent, node_type=None): def get_current_renderer(): - """get current renderer""" + """ + Notes: + Get current renderer for Max + + Returns: + "{Current Renderer}:{Current Renderer}" + e.g. "Redshift_Renderer:Redshift_Renderer" + """ return rt.renderers.production diff --git a/openpype/hosts/max/plugins/create/create_redshift_proxy.py b/openpype/hosts/max/plugins/create/create_redshift_proxy.py new file mode 100644 index 0000000000..698ea82b69 --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_redshift_proxy.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +"""Creator plugin for creating camera.""" +from openpype.hosts.max.api import plugin +from openpype.pipeline import CreatedInstance + + +class CreateRedshiftProxy(plugin.MaxCreator): + identifier = "io.openpype.creators.max.redshiftproxy" + label = "Redshift Proxy" + family = "redshiftproxy" + icon = "gear" + + def create(self, subset_name, instance_data, pre_create_data): + + _ = super(CreateRedshiftProxy, self).create( + subset_name, + instance_data, + pre_create_data) # type: CreatedInstance diff --git a/openpype/hosts/max/plugins/load/load_redshift_proxy.py b/openpype/hosts/max/plugins/load/load_redshift_proxy.py new file mode 100644 index 0000000000..31692f6367 --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_redshift_proxy.py @@ -0,0 +1,63 @@ +import os +import clique + +from openpype.pipeline import ( + load, + get_representation_path +) +from openpype.hosts.max.api.pipeline import containerise +from openpype.hosts.max.api import lib + + +class RedshiftProxyLoader(load.LoaderPlugin): + """Load rs files with Redshift Proxy""" + + label = "Load Redshift Proxy" + families = ["redshiftproxy"] + representations = ["rs"] + order = -9 + icon = "code-fork" + color = "white" + + def load(self, context, name=None, namespace=None, data=None): + from pymxs import runtime as rt + + filepath = self.filepath_from_context(context) + rs_proxy = rt.RedshiftProxy() + rs_proxy.file = filepath + files_in_folder = os.listdir(os.path.dirname(filepath)) + collections, remainder = clique.assemble(files_in_folder) + if collections: + rs_proxy.is_sequence = True + + container = rt.container() + container.name = name + rs_proxy.Parent = container + + asset = rt.getNodeByName(name) + + return containerise( + name, [asset], context, loader=self.__class__.__name__) + + def update(self, container, representation): + from pymxs import runtime as rt + + path = get_representation_path(representation) + node = rt.getNodeByName(container["instance_node"]) + for children in node.Children: + children_node = rt.getNodeByName(children.name) + for proxy in children_node.Children: + proxy.file = path + + lib.imprint(container["instance_node"], { + "representation": str(representation["_id"]) + }) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + from pymxs import runtime as rt + + node = rt.getNodeByName(container["instance_node"]) + rt.delete(node) diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py new file mode 100644 index 0000000000..3b44099609 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -0,0 +1,62 @@ +import os +import pyblish.api +from openpype.pipeline import publish +from pymxs import runtime as rt +from openpype.hosts.max.api import maintained_selection + + +class ExtractRedshiftProxy(publish.Extractor): + """ + Extract Redshift Proxy with rsProxy + """ + + order = pyblish.api.ExtractorOrder - 0.1 + label = "Extract RedShift Proxy" + hosts = ["max"] + families = ["redshiftproxy"] + + def process(self, instance): + container = instance.data["instance_node"] + start = int(instance.context.data.get("frameStart")) + end = int(instance.context.data.get("frameEnd")) + + self.log.info("Extracting Redshift Proxy...") + stagingdir = self.staging_dir(instance) + rs_filename = "{name}.rs".format(**instance.data) + rs_filepath = os.path.join(stagingdir, rs_filename) + rs_filepath = rs_filepath.replace("\\", "/") + + rs_filenames = self.get_rsfiles(instance, start, end) + + with maintained_selection(): + # select and export + con = rt.getNodeByName(container) + rt.select(con.Children) + # Redshift rsProxy command + # rsProxy fp selected compress connectivity startFrame endFrame + # camera warnExisting transformPivotToOrigin + rt.rsProxy(rs_filepath, 1, 0, 0, start, end, 0, 1, 1) + + self.log.info("Performing Extraction ...") + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'rs', + 'ext': 'rs', + 'files': rs_filenames if len(rs_filenames) > 1 else rs_filenames[0], # noqa + "stagingDir": stagingdir, + } + instance.data["representations"].append(representation) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, + stagingdir)) + + def get_rsfiles(self, instance, startFrame, endFrame): + rs_filenames = [] + rs_name = instance.data["name"] + for frame in range(startFrame, endFrame + 1): + rs_filename = "%s.%04d.rs" % (rs_name, frame) + rs_filenames.append(rs_filename) + + return rs_filenames diff --git a/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py b/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py new file mode 100644 index 0000000000..bc82f82f3b --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +import pyblish.api +from openpype.pipeline import PublishValidationError +from pymxs import runtime as rt +from openpype.pipeline.publish import RepairAction +from openpype.hosts.max.api.lib import get_current_renderer + + +class ValidateRendererRedshiftProxy(pyblish.api.InstancePlugin): + """ + Validates Redshift as the current renderer for creating + Redshift Proxy + """ + + order = pyblish.api.ValidatorOrder + families = ["redshiftproxy"] + hosts = ["max"] + label = "Redshift Renderer" + actions = [RepairAction] + + def process(self, instance): + invalid = self.get_redshift_renderer(instance) + if invalid: + raise PublishValidationError("Please install Redshift for 3dsMax" + " before using the Redshift proxy instance") # noqa + invalid = self.get_current_renderer(instance) + if invalid: + raise PublishValidationError("The Redshift proxy extraction" + "discontinued since the current renderer is not Redshift") # noqa + + def get_redshift_renderer(self, instance): + invalid = list() + max_renderers_list = str(rt.RendererClass.classes) + if "Redshift_Renderer" not in max_renderers_list: + invalid.append(max_renderers_list) + + return invalid + + def get_current_renderer(self, instance): + invalid = list() + renderer_class = get_current_renderer() + current_renderer = str(renderer_class).split(":")[0] + if current_renderer != "Redshift_Renderer": + invalid.append(current_renderer) + + return invalid + + @classmethod + def repair(cls, instance): + for Renderer in rt.RendererClass.classes: + renderer = Renderer() + if "Redshift_Renderer" in str(renderer): + rt.renderers.production = renderer + break diff --git a/openpype/hosts/nuke/startup/__init__.py b/openpype/hosts/nuke/startup/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 9538c4f4bc..eaf9cf86f7 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -68,6 +68,7 @@ def main(): if knob["name"] == "file_type": ext = knob["value"] for w in write_selected_nodes: + # data for mapping the path data = { "work": os.getenv("AVALON_WORKDIR"), "subset": w["name"].value(), diff --git a/openpype/hosts/nuke/startup/frame_setting_for_read_nodes.py b/openpype/hosts/nuke/startup/frame_setting_for_read_nodes.py new file mode 100644 index 0000000000..f0cbabe20f --- /dev/null +++ b/openpype/hosts/nuke/startup/frame_setting_for_read_nodes.py @@ -0,0 +1,47 @@ +""" OpenPype custom script for resetting read nodes start frame values """ + +import nuke +import nukescripts + + +class FrameSettingsPanel(nukescripts.PythonPanel): + """ Frame Settings Panel """ + def __init__(self): + nukescripts.PythonPanel.__init__(self, "Set Frame Start (Read Node)") + + # create knobs + self.frame = nuke.Int_Knob( + 'frame', 'Frame Number') + self.selected = nuke.Boolean_Knob("selection") + # add knobs to panel + self.addKnob(self.selected) + self.addKnob(self.frame) + + # set values + self.selected.setValue(False) + self.frame.setValue(nuke.root().firstFrame()) + + def process(self): + """ Process the panel values. """ + # get values + frame = self.frame.value() + if self.selected.value(): + # selected nodes processing + if not nuke.selectedNodes(): + return + for rn_ in nuke.selectedNodes(): + if rn_.Class() != "Read": + continue + rn_["frame_mode"].setValue("start_at") + rn_["frame"].setValue(str(frame)) + else: + # all nodes processing + for rn_ in nuke.allNodes(filter="Read"): + rn_["frame_mode"].setValue("start_at") + rn_["frame"].setValue(str(frame)) + + +def main(): + p_ = FrameSettingsPanel() + if p_.showModalDialog(): + print(p_.process()) diff --git a/openpype/hosts/resolve/api/workio.py b/openpype/hosts/resolve/api/workio.py index 5ce73eea53..5966fa6a43 100644 --- a/openpype/hosts/resolve/api/workio.py +++ b/openpype/hosts/resolve/api/workio.py @@ -43,18 +43,22 @@ def open_file(filepath): """ Loading project """ + + from . import bmdvr + pm = get_project_manager() + page = bmdvr.GetCurrentPage() + if page is not None: + # Save current project only if Resolve has an active page, otherwise + # we consider Resolve being in a pre-launch state (no open UI yet) + project = pm.GetCurrentProject() + print(f"Saving current project: {project}") + pm.SaveProject() + file = os.path.basename(filepath) fname, _ = os.path.splitext(file) dname, _ = fname.split("_v") - - # deal with current project - project = pm.GetCurrentProject() - log.info(f"Test `pm`: {pm}") - pm.SaveProject() - try: - log.info(f"Test `dname`: {dname}") if not set_project_manager_to_folder_name(dname): raise # load project from input path @@ -72,6 +76,7 @@ def open_file(filepath): return False return True + def current_file(): pm = get_project_manager() current_dir = os.getenv("AVALON_WORKDIR") diff --git a/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py b/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py new file mode 100644 index 0000000000..0e27ddb8c3 --- /dev/null +++ b/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py @@ -0,0 +1,45 @@ +import os + +from openpype.lib import PreLaunchHook +import openpype.hosts.resolve + + +class ResolveLaunchLastWorkfile(PreLaunchHook): + """Special hook to open last workfile for Resolve. + + Checks 'start_last_workfile', if set to False, it will not open last + workfile. This property is set explicitly in Launcher. + """ + + # Execute after workfile template copy + order = 10 + app_groups = ["resolve"] + + def execute(self): + if not self.data.get("start_last_workfile"): + self.log.info("It is set to not start last workfile on start.") + return + + last_workfile = self.data.get("last_workfile_path") + if not last_workfile: + self.log.warning("Last workfile was not collected.") + return + + if not os.path.exists(last_workfile): + self.log.info("Current context does not have any workfile yet.") + return + + # Add path to launch environment for the startup script to pick up + self.log.info(f"Setting OPENPYPE_RESOLVE_OPEN_ON_LAUNCH to launch " + f"last workfile: {last_workfile}") + key = "OPENPYPE_RESOLVE_OPEN_ON_LAUNCH" + self.launch_context.env[key] = last_workfile + + # Set the openpype prelaunch startup script path for easy access + # in the LUA .scriptlib code + op_resolve_root = os.path.dirname(openpype.hosts.resolve.__file__) + script_path = os.path.join(op_resolve_root, "startup.py") + key = "OPENPYPE_RESOLVE_STARTUP_SCRIPT" + self.launch_context.env[key] = script_path + self.log.info("Setting OPENPYPE_RESOLVE_STARTUP_SCRIPT to: " + f"{script_path}") diff --git a/openpype/hosts/resolve/startup.py b/openpype/hosts/resolve/startup.py new file mode 100644 index 0000000000..79a64e0fbf --- /dev/null +++ b/openpype/hosts/resolve/startup.py @@ -0,0 +1,62 @@ +"""This script is used as a startup script in Resolve through a .scriptlib file + +It triggers directly after the launch of Resolve and it's recommended to keep +it optimized for fast performance since the Resolve UI is actually interactive +while this is running. As such, there's nothing ensuring the user isn't +continuing manually before any of the logic here runs. As such we also try +to delay any imports as much as possible. + +This code runs in a separate process to the main Resolve process. + +""" +import os + +import openpype.hosts.resolve.api + + +def ensure_installed_host(): + """Install resolve host with openpype and return the registered host. + + This function can be called multiple times without triggering an + additional install. + """ + from openpype.pipeline import install_host, registered_host + host = registered_host() + if host: + return host + + install_host(openpype.hosts.resolve.api) + return registered_host() + + +def launch_menu(): + print("Launching Resolve OpenPype menu..") + ensure_installed_host() + openpype.hosts.resolve.api.launch_pype_menu() + + +def open_file(path): + # Avoid the need to "install" the host + host = ensure_installed_host() + host.open_file(path) + + +def main(): + # Open last workfile + workfile_path = os.environ.get("OPENPYPE_RESOLVE_OPEN_ON_LAUNCH") + if workfile_path: + open_file(workfile_path) + else: + print("No last workfile set to open. Skipping..") + + # Launch OpenPype menu + from openpype.settings import get_project_settings + from openpype.pipeline.context_tools import get_current_project_name + project_name = get_current_project_name() + settings = get_project_settings(project_name) + if settings.get("resolve", {}).get("launch_openpype_menu_on_start", True): + launch_menu() + + +if __name__ == "__main__": + main() diff --git a/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib b/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib new file mode 100644 index 0000000000..324c82d6b7 --- /dev/null +++ b/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib @@ -0,0 +1,21 @@ +-- Run OpenPype's Python launch script for resolve +function file_exists(name) + local f = io.open(name, "r") + return f ~= nil and io.close(f) +end + + +openpype_startup_script = os.getenv("OPENPYPE_RESOLVE_STARTUP_SCRIPT") +if openpype_startup_script ~= nil then + script = fusion:MapPath(openpype_startup_script) + + if file_exists(script) then + -- We must use RunScript to ensure it runs in a separate + -- process to Resolve itself to avoid a deadlock for + -- certain imports of OpenPype libraries or Qt + print("Running launch script: " .. script) + fusion:RunScript(script) + else + print("Launch script not found at: " .. script) + end +end diff --git a/openpype/hosts/resolve/utils.py b/openpype/hosts/resolve/utils.py index 9a161f4865..5e3003862f 100644 --- a/openpype/hosts/resolve/utils.py +++ b/openpype/hosts/resolve/utils.py @@ -29,6 +29,9 @@ def setup(env): log.info("Utility Scripts Dir: `{}`".format(util_scripts_paths)) log.info("Utility Scripts: `{}`".format(scripts)) + # Make sure scripts dir exists + os.makedirs(util_scripts_dir, exist_ok=True) + # make sure no script file is in folder for script in os.listdir(util_scripts_dir): path = os.path.join(util_scripts_dir, script) @@ -50,6 +53,14 @@ def setup(env): src = os.path.join(directory, script) dst = os.path.join(util_scripts_dir, script) + + # TODO: Make this a less hacky workaround + if script == "openpype_startup.scriptlib": + # Handle special case for scriptlib that needs to be a folder + # up from the Comp folder in the Fusion scripts + dst = os.path.join(os.path.dirname(util_scripts_dir), + script) + log.info("Copying `{}` to `{}`...".format(src, dst)) if os.path.isdir(src): shutil.copytree( diff --git a/openpype/plugins/publish/collect_frames_fix.py b/openpype/plugins/publish/collect_frames_fix.py index 837738eb06..86e727b053 100644 --- a/openpype/plugins/publish/collect_frames_fix.py +++ b/openpype/plugins/publish/collect_frames_fix.py @@ -35,41 +35,47 @@ class CollectFramesFixDef( rewrite_version = attribute_values.get("rewrite_version") - if frames_to_fix: - instance.data["frames_to_fix"] = frames_to_fix + if not frames_to_fix: + return - subset_name = instance.data["subset"] - asset_name = instance.data["asset"] + instance.data["frames_to_fix"] = frames_to_fix - project_entity = instance.data["projectEntity"] - project_name = project_entity["name"] + subset_name = instance.data["subset"] + asset_name = instance.data["asset"] - version = get_last_version_by_subset_name(project_name, - subset_name, - asset_name=asset_name) - if not version: - self.log.warning("No last version found, " - "re-render not possible") - return + project_entity = instance.data["projectEntity"] + project_name = project_entity["name"] - representations = get_representations(project_name, - version_ids=[version["_id"]]) - published_files = [] - for repre in representations: - if repre["context"]["family"] not in self.families: - continue + version = get_last_version_by_subset_name( + project_name, + subset_name, + asset_name=asset_name + ) + if not version: + self.log.warning( + "No last version found, re-render not possible" + ) + return - for file_info in repre.get("files"): - published_files.append(file_info["path"]) + representations = get_representations( + project_name, version_ids=[version["_id"]] + ) + published_files = [] + for repre in representations: + if repre["context"]["family"] not in self.families: + continue - instance.data["last_version_published_files"] = published_files - self.log.debug("last_version_published_files::{}".format( - instance.data["last_version_published_files"])) + for file_info in repre.get("files"): + published_files.append(file_info["path"]) - if rewrite_version: - instance.data["version"] = version["name"] - # limits triggering version validator - instance.data.pop("latestVersion") + instance.data["last_version_published_files"] = published_files + self.log.debug("last_version_published_files::{}".format( + instance.data["last_version_published_files"])) + + if self.rewrite_version_enable and rewrite_version: + instance.data["version"] = version["name"] + # limits triggering version validator + instance.data.pop("latestVersion") @classmethod def get_attribute_defs(cls): diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index c2610591aa..791e95a9f3 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -223,6 +223,13 @@ "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_nuke_tut')", "tooltip": "Open the OpenPype Nuke user doc page" }, + { + "type": "action", + "sourcetype": "python", + "title": "Set Frame Start (Read Node)", + "command": "from openpype.hosts.nuke.startup.frame_setting_for_read_nodes import main;main();", + "tooltip": "Set frame start for read node(s)" + }, { "type": "action", "sourcetype": "python", diff --git a/openpype/settings/defaults/project_settings/resolve.json b/openpype/settings/defaults/project_settings/resolve.json index 264f3bd902..56efa78e89 100644 --- a/openpype/settings/defaults/project_settings/resolve.json +++ b/openpype/settings/defaults/project_settings/resolve.json @@ -1,4 +1,5 @@ { + "launch_openpype_menu_on_start": false, "imageio": { "ocio_config": { "enabled": false, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json index b326f22394..6f98bdd3bd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json @@ -5,6 +5,11 @@ "label": "DaVinci Resolve", "is_file": true, "children": [ + { + "type": "boolean", + "key": "launch_openpype_menu_on_start", + "label": "Launch OpenPype menu on start of Resolve" + }, { "key": "imageio", "type": "dict", diff --git a/website/docs/artist_hosts_3dsmax.md b/website/docs/artist_hosts_3dsmax.md index 12c1f40181..fffab8ca5d 100644 --- a/website/docs/artist_hosts_3dsmax.md +++ b/website/docs/artist_hosts_3dsmax.md @@ -30,7 +30,7 @@ By clicking the icon ```OpenPype Menu``` rolls out. Choose ```OpenPype Menu > Launcher``` to open the ```Launcher``` window. -When opened you can **choose** the **project** to work in from the list. Then choose the particular **asset** you want to work on then choose **task** +When opened you can **choose** the **project** to work in from the list. Then choose the particular **asset** you want to work on then choose **task** and finally **run 3dsmax by its icon** in the tools. ![Menu OpenPype](assets/3dsmax_tray_OP.png) @@ -65,13 +65,13 @@ If not any workfile present simply hit ```Save As``` and keep ```Subversion``` e ![Save As Dialog](assets/3dsmax_SavingFirstFile_OP.png) -OpenPype correctly names it and add version to the workfile. This basically happens whenever user trigger ```Save As``` action. Resulting into incremental version numbers like +OpenPype correctly names it and add version to the workfile. This basically happens whenever user trigger ```Save As``` action. Resulting into incremental version numbers like ```workfileName_v001``` ```workfileName_v002``` - etc. + etc. Basically meaning user is free of guessing what is the correct naming and other necessities to keep everything in order and managed. @@ -105,13 +105,13 @@ Before proceeding further please check [Glossary](artist_concepts.md) and [What ### Intro -Current OpenPype integration (ver 3.15.0) supports only ```PointCache``` and ```Camera``` families now. +Current OpenPype integration (ver 3.15.0) supports only ```PointCache```, ```Camera```, ```Geometry``` and ```Redshift Proxy``` families now. **Pointcache** family being basically any geometry outputted as Alembic cache (.abc) format **Camera** family being 3dsmax Camera object with/without animation outputted as native .max, FBX, Alembic format - +**Redshift Proxy** family being Redshift Proxy object with/without animation outputted as rs format(Redshift Proxy's very own format) --- :::note Work in progress @@ -119,7 +119,3 @@ This part of documentation is still work in progress. ::: ## ...to be added - - - - diff --git a/website/docs/dev_blender.md b/website/docs/dev_blender.md new file mode 100644 index 0000000000..bed0e4a09d --- /dev/null +++ b/website/docs/dev_blender.md @@ -0,0 +1,61 @@ +--- +id: dev_blender +title: Blender integration +sidebar_label: Blender integration +toc_max_heading_level: 4 +--- + +## Run python script at launch +In case you need to execute a python script when Blender is started (aka [`-P`](https://docs.blender.org/manual/en/latest/advanced/command_line/arguments.html#python-options)), for example to programmatically modify a blender file for conformation, you can create an OpenPype hook as follows: + +```python +from openpype.hosts.blender.hooks import pre_add_run_python_script_arg +from openpype.lib import PreLaunchHook + + +class MyHook(PreLaunchHook): + """Add python script to be executed before Blender launch.""" + + order = pre_add_run_python_script_arg.AddPythonScriptToLaunchArgs.order - 1 + app_groups = [ + "blender", + ] + + def execute(self): + self.launch_context.data.setdefault("python_scripts", []).append( + "/path/to/my_script.py" + ) +``` + +You can write a bare python script, as you could run into the [Text Editor](https://docs.blender.org/manual/en/latest/editors/text_editor.html). + +### Python script with arguments +#### Adding arguments +In case you need to pass arguments to your script, you can append them to `self.launch_context.data["script_args"]`: + +```python +self.launch_context.data.setdefault("script_args", []).append( + "--my-arg", + "value", + ) +``` + +#### Parsing arguments +You can parse arguments in your script using [argparse](https://docs.python.org/3/library/argparse.html) as follows: + +```python +import argparse + +parser = argparse.ArgumentParser( + description="Parsing arguments for my_script.py" +) +parser.add_argument( + "--my-arg", + nargs="?", + help="My argument", +) +args, unknown = arg_parser.parse_known_args( + sys.argv[sys.argv.index("--") + 1 :] +) +print(args.my_arg) +``` diff --git a/website/sidebars.js b/website/sidebars.js index 4874782197..267cc7f6d7 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -180,6 +180,7 @@ module.exports = { ] }, "dev_deadline", + "dev_blender", "dev_colorspace" ] }; From 59b9745deb40e89f8d86251abadf0d614e65ef11 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 2 Jun 2023 17:30:06 +0800 Subject: [PATCH 194/347] Jakub's comment --- openpype/hosts/nuke/startup/custom_write_node.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index eaf9cf86f7..d9313231d8 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -44,11 +44,6 @@ knobs_setting = { "name": "channels", "value": "rgb" }, - { - "type": "text", - "name": "colorspace", - "value": "linear" - }, { "type": "bool", "name": "create_directories", From 69297d0b687693f0b29e751a4e38b562c336e969 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 2 Jun 2023 14:40:20 +0100 Subject: [PATCH 195/347] cmds.ls returns list --- openpype/hosts/maya/plugins/load/load_reference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index f4a4a44344..74ca27ff3c 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -33,7 +33,7 @@ def preserve_modelpanel_cameras(container, log=None): panel_cameras = {} for panel in cmds.getPanel(type="modelPanel"): cam = cmds.ls(cmds.modelPanel(panel, query=True, camera=True), - long=True) + long=True)[0] # Often but not always maya returns the transform from the # modelPanel as opposed to the camera shape, so we convert it From 51a9d6b73ce9d6f7760a8e8265219cb1db2f8b8a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 2 Jun 2023 14:46:30 +0100 Subject: [PATCH 196/347] Improve error feedback when no renderable cameras exist --- .../maya/plugins/publish/collect_arnold_scene_source.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index 0845f653b1..eda7efa244 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -35,6 +35,11 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): # camera. cameras = cmds.ls(type="camera", long=True) renderable = [c for c in cameras if cmds.getAttr("%s.renderable" % c)] + if not renderable: + raise ValueError( + "No renderable cameraes found, which is required for " + "publishing ASS." + ) camera = renderable[0] for node in instance.data["contentMembers"]: camera_shapes = cmds.listRelatives( From aa7dceb79c59f7aafb1909de8ee0c5af56465c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Fri, 2 Jun 2023 18:04:22 +0200 Subject: [PATCH 197/347] Add 'user' details on workfile manager details pane tab for Unix platform --- openpype/tools/workfiles/window.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 31ecf50d3b..34f7f24b02 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -1,6 +1,7 @@ import os import datetime import copy +import platform from qtpy import QtCore, QtWidgets, QtGui from openpype.client import ( @@ -94,6 +95,18 @@ class SidePanelWidget(QtWidgets.QWidget): self._on_note_change() self.save_clicked.emit() + def get_user_name(self, file): + """Get user name from file path""" + # Only run on Unix because pwd module is not available on Windows. + # NOTE: we tried adding "win32security" module but it was not working + # on all hosts so we decided to just support Linux until migration + # to Ayon + if platform.system() != "Windows": + import pwd + + filestat = os.stat(file) + return pwd.getpwuid(filestat.st_uid).pw_name + def set_context(self, asset_id, task_name, filepath, workfile_doc): # Check if asset, task and file are selected # NOTE workfile document is not requirement @@ -134,7 +147,9 @@ class SidePanelWidget(QtWidgets.QWidget): "Created:", creation_time.strftime(datetime_format), "Modified:", - modification_time.strftime(datetime_format) + modification_time.strftime(datetime_format), + "User:", + self.get_user_name(filepath), ) self._details_input.appendHtml("
".join(lines)) From 9c4e1ad4b1cd7938e007a65e7e50f87224e56763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Fri, 2 Jun 2023 18:08:28 +0200 Subject: [PATCH 198/347] Only add User details if platform isn't windows --- openpype/tools/workfiles/window.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 34f7f24b02..afaf7b9967 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -148,9 +148,12 @@ class SidePanelWidget(QtWidgets.QWidget): creation_time.strftime(datetime_format), "Modified:", modification_time.strftime(datetime_format), - "User:", - self.get_user_name(filepath), ) + if platform.system() != "Windows": + lines += ( + "User:", + self.get_user_name(filepath), + ) self._details_input.appendHtml("
".join(lines)) def get_workfile_data(self): From 4b6059339e251218e4c5817551d44c3d28e7c056 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 3 Jun 2023 03:24:55 +0000 Subject: [PATCH 199/347] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index dd23138dee..b55ca42244 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.9" +__version__ = "3.15.10-nightly.1" From 5b662ecd20e65893bbf4dfe6121e5d9b5c60aa29 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 3 Jun 2023 03:25:35 +0000 Subject: [PATCH 200/347] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index aa5b8decdc..3406ca8b65 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.10-nightly.1 - 3.15.9 - 3.15.9-nightly.2 - 3.15.9-nightly.1 @@ -134,7 +135,6 @@ body: - 3.14.3-nightly.2 - 3.14.3-nightly.1 - 3.14.2 - - 3.14.2-nightly.5 validations: required: true - type: dropdown From d37ccb4718e3fa9299d098844ddb0a92c1a09552 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 5 Jun 2023 10:26:42 +0200 Subject: [PATCH 201/347] :recycle: resolve few conversations --- .../houdini/plugins/publish/collect_arnold_rop.py | 15 ++++++--------- .../publish/submit_houdini_render_deadline.py | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py index 946eed3301..614785487f 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py +++ b/openpype/hosts/houdini/plugins/publish/collect_arnold_rop.py @@ -49,16 +49,14 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): num_aovs = rop.evalParm("ar_aovs") for index in range(1, num_aovs + 1): - i = index + 1 - # Skip disabled AOVs - if not rop.evalParm("ar_enable_aov%s" % i): + if not rop.evalParm("ar_enable_aovP{}".format(index)): continue - if rop.evalParm("ar_aov_exr_enable_layer_name%s" % i): - label = rop.evalParm("ar_aov_exr_layer_name%s" % i) + if rop.evalParm("ar_aov_exr_enable_layer_name{}".format(index)): + label = rop.evalParm("ar_aov_exr_layer_name{}".format(index)) else: - label = evalParmNoFrame(rop, "ar_aov_label%s" % i) + label = evalParmNoFrame(rop, "ar_aov_label{}".format(index)) aov_product = self.get_render_product_name(default_prefix, suffix=label) @@ -67,10 +65,9 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin): aov_product) for product in render_products: - self.log.debug("Found render product: %s" % product) + self.log.debug("Found render product: {}".format(product)) - filenames = list(render_products) - instance.data["files"] = filenames + instance.data["files"] = list(render_products) instance.data["renderProducts"] = colorspace.ARenderProduct() # For now by default do NOT try to publish the rendered output diff --git a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 6a62ee0ea8..254914a850 100644 --- a/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -55,7 +55,7 @@ class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): filepath = context.data["currentFile"] filename = os.path.basename(filepath) - job_info.Name = "%s - %s" % (filename, instance.name) + job_info.Name = "{} - {}".format(filename, instance.name) job_info.BatchName = filename job_info.Plugin = "Houdini" job_info.UserName = context.data.get( From d46cee554d929d434859f38daf0660021dff53dc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 5 Jun 2023 17:03:23 +0800 Subject: [PATCH 202/347] fix the bug of not being able to use repair action --- .../maya/plugins/publish/validate_arnold_scene_source_cbid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py index e27723e104..8ce76c8d04 100644 --- a/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py +++ b/openpype/hosts/maya/plugins/publish/validate_arnold_scene_source_cbid.py @@ -70,5 +70,5 @@ class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - for content_node, proxy_node in cls.get_invalid_couples(cls, instance): - lib.set_id(proxy_node, lib.get_id(content_node), overwrite=False) + for content_node, proxy_node in cls.get_invalid_couples(instance): + lib.set_id(proxy_node, lib.get_id(content_node), overwrite=True) From c12e6995dea9e7e2b0929f4790affbe0e8ca62d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 5 Jun 2023 14:49:58 +0200 Subject: [PATCH 203/347] Update openpype/tools/workfiles/window.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/workfiles/window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index afaf7b9967..907598e9df 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -149,10 +149,11 @@ class SidePanelWidget(QtWidgets.QWidget): "Modified:", modification_time.strftime(datetime_format), ) - if platform.system() != "Windows": + username = self.get_user_name(filepath) + if username: lines += ( "User:", - self.get_user_name(filepath), + username, ) self._details_input.appendHtml("
".join(lines)) From 7c70ea968dc604f27cb4f0519fcef5eb297df3a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 5 Jun 2023 15:07:35 +0200 Subject: [PATCH 204/347] Update openpype/tools/workfiles/window.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/workfiles/window.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 907598e9df..5bf6df35ca 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -101,11 +101,12 @@ class SidePanelWidget(QtWidgets.QWidget): # NOTE: we tried adding "win32security" module but it was not working # on all hosts so we decided to just support Linux until migration # to Ayon - if platform.system() != "Windows": - import pwd + if platform.system().lower() == "window": + return None + import pwd - filestat = os.stat(file) - return pwd.getpwuid(filestat.st_uid).pw_name + filestat = os.stat(file) + return pwd.getpwuid(filestat.st_uid).pw_name def set_context(self, asset_id, task_name, filepath, workfile_doc): # Check if asset, task and file are selected From bde15953595e57c30ea8b39becbdb4b81b9e537d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 5 Jun 2023 15:17:10 +0200 Subject: [PATCH 205/347] Update openpype/tools/workfiles/window.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/workfiles/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py index 5bf6df35ca..53f8894665 100644 --- a/openpype/tools/workfiles/window.py +++ b/openpype/tools/workfiles/window.py @@ -101,7 +101,7 @@ class SidePanelWidget(QtWidgets.QWidget): # NOTE: we tried adding "win32security" module but it was not working # on all hosts so we decided to just support Linux until migration # to Ayon - if platform.system().lower() == "window": + if platform.system().lower() == "windows": return None import pwd From a6d5b23fa765dd298b1183a4c0c0a843c5b10b62 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 5 Jun 2023 15:13:37 +0100 Subject: [PATCH 206/347] Update openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py Co-authored-by: Roy Nieterau --- .../hosts/maya/plugins/publish/collect_arnold_scene_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index eda7efa244..d72a428624 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -37,7 +37,7 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): renderable = [c for c in cameras if cmds.getAttr("%s.renderable" % c)] if not renderable: raise ValueError( - "No renderable cameraes found, which is required for " + "No renderable cameras found, which is required for " "publishing ASS." ) camera = renderable[0] From 791dd3ee6eabb4a4cb5ce77c4c366b61c5e92a3b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 5 Jun 2023 15:21:39 +0100 Subject: [PATCH 207/347] Debug logging instead of error --- .../publish/collect_arnold_scene_source.py | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index d72a428624..b7fa9bb6f9 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -35,18 +35,16 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): # camera. cameras = cmds.ls(type="camera", long=True) renderable = [c for c in cameras if cmds.getAttr("%s.renderable" % c)] - if not renderable: - raise ValueError( - "No renderable cameras found, which is required for " - "publishing ASS." - ) - camera = renderable[0] - for node in instance.data["contentMembers"]: - camera_shapes = cmds.listRelatives( - node, shapes=True, type="camera" - ) - if camera_shapes: - camera = node - instance.data["camera"] = camera + if renderable: + camera = renderable[0] + for node in instance.data["contentMembers"]: + camera_shapes = cmds.listRelatives( + node, shapes=True, type="camera" + ) + if camera_shapes: + camera = node + instance.data["camera"] = camera + else: + self.log.debug("No renderable cameraes found.") self.log.debug("data: {}".format(instance.data)) From 4fa079c312bafb037e163c53e7e98e4a80d796ae Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 5 Jun 2023 15:42:06 +0100 Subject: [PATCH 208/347] Update openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- .../hosts/maya/plugins/publish/collect_arnold_scene_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index b7fa9bb6f9..f160a3a0c5 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -45,6 +45,6 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): camera = node instance.data["camera"] = camera else: - self.log.debug("No renderable cameraes found.") + self.log.debug("No renderable cameras found.") self.log.debug("data: {}".format(instance.data)) From 99fbc6dc3537115734b23b718cd04b050723495f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 6 Jun 2023 21:43:22 +0800 Subject: [PATCH 209/347] refactor the redshiftproxy family --- openpype/hosts/max/plugins/create/create_redshift_proxy.py | 7 ------- .../hosts/max/plugins/publish/extract_redshift_proxy.py | 5 ++--- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_redshift_proxy.py b/openpype/hosts/max/plugins/create/create_redshift_proxy.py index 698ea82b69..6eb59f0a73 100644 --- a/openpype/hosts/max/plugins/create/create_redshift_proxy.py +++ b/openpype/hosts/max/plugins/create/create_redshift_proxy.py @@ -9,10 +9,3 @@ class CreateRedshiftProxy(plugin.MaxCreator): label = "Redshift Proxy" family = "redshiftproxy" icon = "gear" - - def create(self, subset_name, instance_data, pre_create_data): - - _ = super(CreateRedshiftProxy, self).create( - subset_name, - instance_data, - pre_create_data) # type: CreatedInstance diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index 3b44099609..8f76eef5ec 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -2,7 +2,7 @@ import os import pyblish.api from openpype.pipeline import publish from pymxs import runtime as rt -from openpype.hosts.max.api import maintained_selection +from openpype.hosts.max.api import get_all_children, maintained_selection class ExtractRedshiftProxy(publish.Extractor): @@ -30,8 +30,7 @@ class ExtractRedshiftProxy(publish.Extractor): with maintained_selection(): # select and export - con = rt.getNodeByName(container) - rt.select(con.Children) + rt.select(get_all_children(rt.getNodeByName(container))) # Redshift rsProxy command # rsProxy fp selected compress connectivity startFrame endFrame # camera warnExisting transformPivotToOrigin From a4e2b18636aa77fbc538fb2493169ce9b19261c1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 6 Jun 2023 21:47:33 +0800 Subject: [PATCH 210/347] remove duplicated imported function --- openpype/hosts/max/plugins/load/load_model.py | 1 - openpype/hosts/max/plugins/load/load_model_fbx.py | 1 - openpype/hosts/max/plugins/load/load_pointcache.py | 2 -- 3 files changed, 4 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_model.py b/openpype/hosts/max/plugins/load/load_model.py index 0ec94ab074..58c6d3c889 100644 --- a/openpype/hosts/max/plugins/load/load_model.py +++ b/openpype/hosts/max/plugins/load/load_model.py @@ -3,7 +3,6 @@ from openpype.pipeline import load, get_representation_path from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib from openpype.hosts.max.api.lib import maintained_selection -from openpype.pipeline import get_representation_path, load class ModelAbcLoader(load.LoaderPlugin): diff --git a/openpype/hosts/max/plugins/load/load_model_fbx.py b/openpype/hosts/max/plugins/load/load_model_fbx.py index ee7d04d5eb..663f79f9f5 100644 --- a/openpype/hosts/max/plugins/load/load_model_fbx.py +++ b/openpype/hosts/max/plugins/load/load_model_fbx.py @@ -3,7 +3,6 @@ from openpype.pipeline import load, get_representation_path from openpype.hosts.max.api.pipeline import containerise from openpype.hosts.max.api import lib from openpype.hosts.max.api.lib import maintained_selection -from openpype.pipeline import get_representation_path, load class FbxModelLoader(load.LoaderPlugin): diff --git a/openpype/hosts/max/plugins/load/load_pointcache.py b/openpype/hosts/max/plugins/load/load_pointcache.py index 8a51e86000..cadbe7cac2 100644 --- a/openpype/hosts/max/plugins/load/load_pointcache.py +++ b/openpype/hosts/max/plugins/load/load_pointcache.py @@ -6,10 +6,8 @@ Because of limited api, alembics can be only loaded, but not easily updated. """ import os from openpype.pipeline import load, get_representation_path - from openpype.hosts.max.api import lib, maintained_selection from openpype.hosts.max.api.pipeline import containerise -from openpype.pipeline import get_representation_path, load class AbcLoader(load.LoaderPlugin): From a98ef68549d090a738615f1cefdbf3cbeac22c95 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Jun 2023 16:49:34 +0200 Subject: [PATCH 211/347] fixing publisher parent --- openpype/hosts/nuke/api/pipeline.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 88f7144542..e7c1c0ba0e 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -240,12 +240,14 @@ def _install_menu(): menu.addCommand( "Create...", lambda: host_tools.show_publisher( + parent=main_window, tab="create" ) ) menu.addCommand( "Publish...", lambda: host_tools.show_publisher( + parent=main_window, tab="publish" ) ) From bb56d5dc16f73f15c502281516361385cc4d06d8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Jun 2023 16:49:48 +0200 Subject: [PATCH 212/347] fixing nuke settings default --- .../settings/defaults/project_settings/nuke.json | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 3f8be4c872..8eca824a6a 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -148,7 +148,7 @@ }, { "plugins": [ - "CreateWriteStill" + "CreateWriteImage" ], "nukeNodeClass": "Write", "knobs": [ @@ -556,15 +556,7 @@ "load": { "LoadImage": { "enabled": true, - "_representations": [ - "exr", - "dpx", - "jpg", - "jpeg", - "png", - "psd", - "tiff" - ], + "_representations": [], "node_name_template": "{class_name}_{ext}" }, "LoadClip": { From 69dc5d85f9fe02a2d8de261ed52eacc87287e7c3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 6 Jun 2023 16:59:42 +0200 Subject: [PATCH 213/347] OP-6145 - make prerender check safer In Nuke is correctly `prerendere.farm` in families, which wasn't handled here. Eventually this query should be simplified only to `prerender.farm`, but this way it is safer for now. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 87b4ca64f4..590acf86c2 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -825,7 +825,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): ).format(source)) family = "render" - if "prerender" in instance.data["families"]: + if ("prerender" in instance.data["families"] or + "prerender.farm" in instance.data["families"]): family = "prerender" families = [family] From fbb6afe5c370101918b77e54ec8aeeec6ab9409a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 6 Jun 2023 17:17:42 +0200 Subject: [PATCH 214/347] adding psd to IMAGE_EXTENSIONS constant --- openpype/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 57968b3700..de6495900e 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -51,7 +51,7 @@ IMAGE_EXTENSIONS = { ".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr", ".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd", ".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf", - ".pictor", ".png", ".psb", ".psp", ".qtvr", ".ras", + ".pictor", ".png", ".psd", ".psb", ".psp", ".qtvr", ".ras", ".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep", ".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf", ".xpm", ".xwd" From 754b48ebe27000105bbb3bf5f9cbd51112b0a9f2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 6 Jun 2023 23:43:59 +0800 Subject: [PATCH 215/347] resolve the conflict --- .../hosts/resolve/utility_scripts/openpype_startup.scriptlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib b/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib index 324c82d6b7..ec9b30a18d 100644 --- a/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib +++ b/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib @@ -18,4 +18,4 @@ if openpype_startup_script ~= nil then else print("Launch script not found at: " .. script) end -end +end \ No newline at end of file From 29c0b8a12a4dcf1ded4c492d63d950766c94b22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Tue, 6 Jun 2023 18:51:44 +0200 Subject: [PATCH 216/347] Sort actions by label if it exists instead of name (#5106) --- openpype/tools/launcher/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 3aa6c5d8cb..63ffcc9365 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -273,7 +273,7 @@ class ActionModel(QtGui.QStandardItemModel): # Sort by order and name return sorted( compatible, - key=lambda action: (action.order, action.name) + key=lambda action: (action.order, lib.get_action_label(action)) ) def update_force_not_open_workfile_settings(self, is_checked, action_id): From e0d95d1348a127d16be69411a916d410dd015824 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 7 Jun 2023 03:27:59 +0000 Subject: [PATCH 217/347] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index b55ca42244..868664c601 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.10-nightly.1" +__version__ = "3.15.10-nightly.2" From 65e5aa40cf87cde18d4ab99598c43486d85ab4b2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 7 Jun 2023 03:28:46 +0000 Subject: [PATCH 218/347] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3406ca8b65..e614d2fa65 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.10-nightly.2 - 3.15.10-nightly.1 - 3.15.9 - 3.15.9-nightly.2 @@ -134,7 +135,6 @@ body: - 3.14.3-nightly.3 - 3.14.3-nightly.2 - 3.14.3-nightly.1 - - 3.14.2 validations: required: true - type: dropdown From b3cab6a88b1fd2c8e3eb3606bfc4d5f8cafa10cb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Jun 2023 11:34:59 +0200 Subject: [PATCH 219/347] adding parent to publisher only if nuke higher then 14 --- openpype/hosts/nuke/api/__init__.py | 4 +++- openpype/hosts/nuke/api/lib.py | 11 +++++++++++ openpype/hosts/nuke/api/pipeline.py | 12 +++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index 1af5ff365d..55c4b8c808 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -43,7 +43,8 @@ from .lib import ( get_node_data, set_node_data, update_node_data, - create_write_node + create_write_node, + get_app_version_info ) from .utils import ( colorspace_exists_on_node, @@ -90,6 +91,7 @@ __all__ = ( "set_node_data", "update_node_data", "create_write_node", + "get_app_version_info", "colorspace_exists_on_node", "get_colorspace_list" diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 4a57bc3165..bf66b9e1a9 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3116,3 +3116,14 @@ def get_viewer_config_from_string(input_string): ).format(input_string)) return (display, viewer) + + +def get_app_version_info(): + """ Return dict of Nuke's version semantic info""" + dot_split = nuke.NUKE_VERSION_STRING.split(".") + v_spit = dot_split[1].split("v") + return { + "major": int(dot_split[0]), + "minor": int(v_spit[0]), + "patch": int(v_spit[1]) + } diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index e7c1c0ba0e..998c03b3dd 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -43,7 +43,8 @@ from .lib import ( add_scripts_menu, add_scripts_gizmo, get_node_data, - set_node_data + set_node_data, + get_app_version_info ) from .workfile_template_builder import ( NukePlaceholderLoadPlugin, @@ -218,6 +219,7 @@ def _install_menu(): main_window = get_main_window() menubar = nuke.menu("Nuke") menu = menubar.addMenu(MENU_LABEL) + app_version = get_app_version_info() if not ASSIST: label = "{0}, {1}".format( @@ -237,17 +239,21 @@ def _install_menu(): menu.addSeparator() if not ASSIST: + # only add parent if nuke version is 14 or higher + # known issue with no solution yet menu.addCommand( "Create...", lambda: host_tools.show_publisher( - parent=main_window, + parent=main_window if app_version["major"] >= 14 else None, tab="create" ) ) + # only add parent if nuke version is 14 or higher + # known issue with no solution yet menu.addCommand( "Publish...", lambda: host_tools.show_publisher( - parent=main_window, + parent=main_window if app_version["major"] >= 14 else None, tab="publish" ) ) From 08084b899d18fd1c34eafc2e93253d72754f478f Mon Sep 17 00:00:00 2001 From: FadyFS Date: Wed, 7 Jun 2023 14:01:05 +0200 Subject: [PATCH 220/347] use frame sequence ok --- .../hosts/maya/plugins/load/load_arnold_standin.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 7c3a732389..4e8941e0ad 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -16,10 +16,13 @@ from openpype.hosts.maya.api.pipeline import containerise def is_sequence(files): sequence = False - collections, remainder = clique.assemble(files) + print("here calling") + if len(files) == 1: + collections, remainder = clique.assemble(files, minimum_items=1) + else: + collections, remainder = clique.assemble(files) if collections: sequence = True - return sequence @@ -84,6 +87,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) + nodes = [root, standin, standin_shape] if operator is not None: nodes.append(operator) @@ -146,6 +150,10 @@ class ArnoldStandinLoader(load.LoaderPlugin): os.path.basename(path), type="string" ) + print("#"*100) + + + # Object name value cmds.connectAttr( string_replace_operator + ".out", From 15766ae910e9fa4414e3512d5a3fe52c95052d90 Mon Sep 17 00:00:00 2001 From: FadyFS Date: Wed, 7 Jun 2023 15:06:56 +0200 Subject: [PATCH 221/347] blanks spaces + correct FPS imported --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 4e8941e0ad..881e6a5643 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -16,7 +16,6 @@ from openpype.hosts.maya.api.pipeline import containerise def is_sequence(files): sequence = False - print("here calling") if len(files) == 1: collections, remainder = clique.assemble(files, minimum_items=1) else: @@ -86,7 +85,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): cmds.setAttr(standin_shape + ".dso", path, type="string") sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) - + cmds.setAttr(standin_shape + ".abcFPS", 30) nodes = [root, standin, standin_shape] if operator is not None: @@ -150,11 +149,8 @@ class ArnoldStandinLoader(load.LoaderPlugin): os.path.basename(path), type="string" ) - print("#"*100) - # Object name value - cmds.connectAttr( string_replace_operator + ".out", "{}.inputs[{}]".format( From 4c4eeb29bed247982edad6d402aaf5903996be01 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Jun 2023 00:04:41 +0800 Subject: [PATCH 222/347] connect custom write node script to the OP setting --- .../hosts/nuke/startup/custom_write_node.py | 99 +++++++++++++++---- 1 file changed, 78 insertions(+), 21 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index d9313231d8..f2244b84c5 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -1,6 +1,11 @@ +""" OpenPype custom script for setting up write nodes for non-publish """ import os import nuke -from openpype.hosts.nuke.api.lib import set_node_knobs_from_settings +import nukescripts +from openpype.hosts.nuke.api.lib import ( + set_node_knobs_from_settings, + get_nuke_imageio_settings +) frame_padding = 5 @@ -53,24 +58,76 @@ knobs_setting = { } -def main(): - write_selected_nodes = [ - s for s in nuke.selectedNodes() if s.Class() == "Write"] +class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): + """ Write Node's Knobs Settings Panel """ + def __init__(self): + nukescripts.PythonPanel.__init__(self, "Set Knobs Value(Write Node)") - ext = None - knobs = knobs_setting["knobs"] - for knob in knobs: - if knob["name"] == "file_type": - ext = knob["value"] - for w in write_selected_nodes: - # data for mapping the path - data = { - "work": os.getenv("AVALON_WORKDIR"), - "subset": w["name"].value(), - "frame": "#" * frame_padding, - "ext": ext - } - file_path = temp_rendering_path_template.format(**data) - file_path = file_path.replace("\\", "/") - w["file"].setValue(file_path) - set_node_knobs_from_settings(w, knobs) + knobs_value = self.get_node_knobs_override() + # create knobs + + self.typeKnob = nuke.Enumeration_Knob( + 'override_subsets', 'override subsets', knobs_value) + # add knobs to panel + self.addKnob(self.typeKnob) + + def process(self): + """ Process the panel values. """ + write_selected_nodes = [ + s for s in nuke.selectedNodes() if s.Class() == "Write"] + + node_knobs = self.typeKnob.value() + ext = None + knobs = None + if node_knobs: + knobs = self.get_node_knobs_setting(node_knobs) + if not knobs: + nuke.message("No knobs value found in subset group..\nDefault setting will be used..") # noqa + knobs = knobs_setting["knobs"] + else: + knobs = knobs_setting["knobs"] + + for knob in knobs: + if knob["name"] == "file_type": + ext = knob["value"] + for w in write_selected_nodes: + # data for mapping the path + data = { + "work": os.getenv("AVALON_WORKDIR"), + "subset": w["name"].value(), + "frame": "#" * frame_padding, + "ext": ext + } + file_path = temp_rendering_path_template.format(**data) + file_path = file_path.replace("\\", "/") + w["file"].setValue(file_path) + set_node_knobs_from_settings(w, knobs) + + def get_node_knobs_setting(self, value): + settings = [ + node for node in get_nuke_imageio_settings()["nodes"]["overrideNodes"] + ] + if not settings: + return + for i, setting in enumerate(settings): + if value in settings[i]["subsets"]: + return settings[i]["knobs"] + + def get_node_knobs_override(self): + knobs_value = [] + settings = [ + node for node in get_nuke_imageio_settings()["nodes"]["overrideNodes"] + ] + if not settings: + return + + for setting in settings: + if setting["nukeNodeClass"] == "Write" and setting["subsets"]: + for knob in setting["subsets"]: + knobs_value.append(knob) + return knobs_value + +def main(): + p_ = WriteNodeKnobSettingPanel() + if p_.showModalDialog(): + print(p_.process()) From 5dd066804bbc42e5fc1f1cc722e6bf69fc2643b4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Jun 2023 00:07:12 +0800 Subject: [PATCH 223/347] hound fix --- openpype/hosts/nuke/startup/custom_write_node.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index f2244b84c5..66095409a6 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -105,18 +105,20 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): def get_node_knobs_setting(self, value): settings = [ - node for node in get_nuke_imageio_settings()["nodes"]["overrideNodes"] + node + for node in get_nuke_imageio_settings()["nodes"]["overrideNodes"] ] if not settings: return - for i, setting in enumerate(settings): + for i, _ in enumerate(settings): if value in settings[i]["subsets"]: return settings[i]["knobs"] def get_node_knobs_override(self): knobs_value = [] settings = [ - node for node in get_nuke_imageio_settings()["nodes"]["overrideNodes"] + node + for node in get_nuke_imageio_settings()["nodes"]["overrideNodes"] ] if not settings: return @@ -127,6 +129,7 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): knobs_value.append(knob) return knobs_value + def main(): p_ = WriteNodeKnobSettingPanel() if p_.showModalDialog(): From c49b59a7eaadd223069fc77601be0047fa212067 Mon Sep 17 00:00:00 2001 From: FadyFS Date: Thu, 8 Jun 2023 11:14:41 +0200 Subject: [PATCH 224/347] getter for fps project --- .../maya/plugins/load/load_arnold_standin.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 881e6a5643..4bc4a1bbe5 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -6,10 +6,11 @@ import maya.cmds as cmds from openpype.settings import get_project_settings from openpype.pipeline import ( load, + legacy_io, get_representation_path ) from openpype.hosts.maya.api.lib import ( - unique_namespace, get_attribute_input, maintained_selection + unique_namespace, get_attribute_input, maintained_selection, convert_to_maya_fps ) from openpype.hosts.maya.api.pipeline import containerise @@ -24,6 +25,17 @@ def is_sequence(files): sequence = True return sequence +#get the fps from the project itself +def get_fps(standin_shape): + + fps = convert_to_maya_fps( + float(legacy_io.Session.get("AVALON_FPS", 25)) + ) + return fps + + + + class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" @@ -85,7 +97,10 @@ class ArnoldStandinLoader(load.LoaderPlugin): cmds.setAttr(standin_shape + ".dso", path, type="string") sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) - cmds.setAttr(standin_shape + ".abcFPS", 30) + + cmds.setAttr(standin_shape + ".dso", path, type="string") + matching_fps = get_fps(os.listdir(os.path.dirname(self.fname))) + cmds.setAttr(standin_shape + ".abcFPS", matching_fps) nodes = [root, standin, standin_shape] if operator is not None: From f423ebcfcbeadacc12a8af5ff5a5e32f13c1a1e2 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Thu, 8 Jun 2023 10:40:46 +0100 Subject: [PATCH 225/347] Keep `publisher.create_widget` variant when creating subsets Whenever a person is creating a subset to publish, the "creator" widget resets (where you choose the variant, product, etc.) so if the person is publishing several images of the a variant which is not the default one, they have to keep selecting the correct one after every "create". This commit resets the original variant upon successful creation of a subset for publishing. --- openpype/tools/publisher/widgets/create_widget.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/publisher/widgets/create_widget.py b/openpype/tools/publisher/widgets/create_widget.py index 30980af03d..b7605b1188 100644 --- a/openpype/tools/publisher/widgets/create_widget.py +++ b/openpype/tools/publisher/widgets/create_widget.py @@ -828,6 +828,7 @@ class CreateWidget(QtWidgets.QWidget): if success: self._set_creator(self._selected_creator) + self.variant_input.setText(variant) self._controller.emit_card_message("Creation finished...") self._last_thumbnail_path = None self._thumbnail_widget.set_current_thumbnails() From 873922d2753cc53d5e396a2236933a844deb61d6 Mon Sep 17 00:00:00 2001 From: FadyFS Date: Thu, 8 Jun 2023 11:48:25 +0200 Subject: [PATCH 226/347] FPS corrected --- .../hosts/maya/plugins/load/load_arnold_standin.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 4bc4a1bbe5..0d76ba9f84 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -10,7 +10,10 @@ from openpype.pipeline import ( get_representation_path ) from openpype.hosts.maya.api.lib import ( - unique_namespace, get_attribute_input, maintained_selection, convert_to_maya_fps + unique_namespace, + get_attribute_input, + maintained_selection, + convert_to_maya_fps ) from openpype.hosts.maya.api.pipeline import containerise @@ -25,7 +28,6 @@ def is_sequence(files): sequence = True return sequence -#get the fps from the project itself def get_fps(standin_shape): fps = convert_to_maya_fps( @@ -33,10 +35,6 @@ def get_fps(standin_shape): ) return fps - - - - class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" From dce5483919f732d2f3055bdc1a267f10406a7a5b Mon Sep 17 00:00:00 2001 From: FadyFS Date: Thu, 8 Jun 2023 11:54:28 +0200 Subject: [PATCH 227/347] no trailing whitespaces --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 0d76ba9f84..27c88869de 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -10,9 +10,9 @@ from openpype.pipeline import ( get_representation_path ) from openpype.hosts.maya.api.lib import ( - unique_namespace, - get_attribute_input, - maintained_selection, + unique_namespace, + get_attribute_input, + maintained_selection, convert_to_maya_fps ) from openpype.hosts.maya.api.pipeline import containerise From b24b4a9d5f6123f7fa096aba81bdc71bff726282 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Jun 2023 11:54:41 +0200 Subject: [PATCH 228/347] Loader: Hide inactive versions in UI (#5100) * Function 'get_last_versions' have active filter * filter in active versions in loader --- openpype/client/entities.py | 20 +++++++++++++++----- openpype/tools/loader/model.py | 1 + openpype/tools/utils/delegates.py | 12 ++++++++---- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/openpype/client/entities.py b/openpype/client/entities.py index 8004dc3019..adbdd7a47c 100644 --- a/openpype/client/entities.py +++ b/openpype/client/entities.py @@ -855,12 +855,13 @@ def get_output_link_versions(project_name, version_id, fields=None): return conn.find(query_filter, _prepare_fields(fields)) -def get_last_versions(project_name, subset_ids, fields=None): +def get_last_versions(project_name, subset_ids, active=None, fields=None): """Latest versions for entered subset_ids. Args: project_name (str): Name of project where to look for queried entities. subset_ids (Iterable[Union[str, ObjectId]]): List of subset ids. + active (Optional[bool]): If True only active versions are returned. fields (Optional[Iterable[str]]): Fields that should be returned. All fields are returned if 'None' is passed. @@ -899,12 +900,21 @@ def get_last_versions(project_name, subset_ids, fields=None): if name_needed: group_item["name"] = {"$last": "$name"} + aggregate_filter = { + "type": "version", + "parent": {"$in": subset_ids} + } + if active is False: + aggregate_filter["data.active"] = active + elif active is True: + aggregate_filter["$or"] = [ + {"data.active": {"$exists": 0}}, + {"data.active": active}, + ] + aggregation_pipeline = [ # Find all versions of those subsets - {"$match": { - "type": "version", - "parent": {"$in": subset_ids} - }}, + {"$match": aggregate_filter}, # Sorting versions all together {"$sort": {"name": 1}}, # Group them by "parent", but only take the last diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index e5d8400031..e58e02f89a 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -446,6 +446,7 @@ class SubsetsModel(BaseRepresentationModel, TreeModel): last_versions_by_subset_id = get_last_versions( project_name, subset_ids, + active=True, fields=["_id", "parent", "name", "type", "data", "schema"] ) diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index fa69113ef1..c71c87f9b0 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -123,10 +123,14 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate): project_name = self.dbcon.active_project() # Add all available versions to the editor parent_id = item["version_document"]["parent"] - version_docs = list(sorted( - get_versions(project_name, subset_ids=[parent_id]), - key=lambda item: item["name"] - )) + version_docs = [ + version_doc + for version_doc in sorted( + get_versions(project_name, subset_ids=[parent_id]), + key=lambda item: item["name"] + ) + if version_doc["data"].get("active", True) + ] hero_versions = list( get_hero_versions( From 29a66eddf014ec68ef4b80b8894a96ea03d2ab5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Thu, 8 Jun 2023 15:12:44 +0200 Subject: [PATCH 229/347] :recycle: Move from deprecated interface (#5117) `INewPublisher` is deprecated, using `IPublishHost` instead --- openpype/hosts/max/api/pipeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/pipeline.py b/openpype/hosts/max/api/pipeline.py index 50fe30b299..03b85a4066 100644 --- a/openpype/hosts/max/api/pipeline.py +++ b/openpype/hosts/max/api/pipeline.py @@ -6,7 +6,7 @@ from operator import attrgetter import json -from openpype.host import HostBase, IWorkfileHost, ILoadHost, INewPublisher +from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost import pyblish.api from openpype.pipeline import ( register_creator_plugin_path, @@ -28,7 +28,7 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") -class MaxHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher): +class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): name = "max" menu = None From 786ee1bf37a145754bc59e95a25de14abbfa9793 Mon Sep 17 00:00:00 2001 From: FadyFS Date: Thu, 8 Jun 2023 16:28:56 +0200 Subject: [PATCH 230/347] Updated method to get correct FPS from abc file --- .../hosts/maya/plugins/load/load_arnold_standin.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 27c88869de..61f093fbf3 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -17,7 +17,6 @@ from openpype.hosts.maya.api.lib import ( ) from openpype.hosts.maya.api.pipeline import containerise - def is_sequence(files): sequence = False if len(files) == 1: @@ -28,13 +27,6 @@ def is_sequence(files): sequence = True return sequence -def get_fps(standin_shape): - - fps = convert_to_maya_fps( - float(legacy_io.Session.get("AVALON_FPS", 25)) - ) - return fps - class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" @@ -96,9 +88,8 @@ class ArnoldStandinLoader(load.LoaderPlugin): sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) - cmds.setAttr(standin_shape + ".dso", path, type="string") - matching_fps = get_fps(os.listdir(os.path.dirname(self.fname))) - cmds.setAttr(standin_shape + ".abcFPS", matching_fps) + fps = float(version["data"]["fps"]) + cmds.setAttr(standin_shape + ".abcFPS", fps) nodes = [root, standin, standin_shape] if operator is not None: From d231558d90447d441ad135bb629836642d93ee06 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Jun 2023 16:35:35 +0200 Subject: [PATCH 231/347] fix video streams collection (#5120) --- .../plugins/create/create_editorial.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/create/create_editorial.py b/openpype/hosts/traypublisher/plugins/create/create_editorial.py index 0630dfb3da..8640500b18 100644 --- a/openpype/hosts/traypublisher/plugins/create/create_editorial.py +++ b/openpype/hosts/traypublisher/plugins/create/create_editorial.py @@ -487,7 +487,22 @@ or updating already created. Publishing will create OTIO file. ) # get video stream data - video_stream = media_data["streams"][0] + video_streams = [] + audio_streams = [] + for stream in media_data["streams"]: + codec_type = stream.get("codec_type") + if codec_type == "audio": + audio_streams.append(stream) + + elif codec_type == "video": + video_streams.append(stream) + + if not video_streams: + raise ValueError( + "Could not find video stream in source file." + ) + + video_stream = video_streams[0] return_data = { "video": True, "start_frame": 0, @@ -500,12 +515,7 @@ or updating already created. Publishing will create OTIO file. } # get audio streams data - audio_stream = [ - stream for stream in media_data["streams"] - if stream["codec_type"] == "audio" - ] - - if audio_stream: + if audio_streams: return_data["audio"] = True except Exception as exc: From bf15830a9a7d464279e92cd4ee45b0d2a1075395 Mon Sep 17 00:00:00 2001 From: FadyFS Date: Thu, 8 Jun 2023 17:42:05 +0200 Subject: [PATCH 232/347] new fps getter --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 61f093fbf3..2b07b1ca51 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -27,6 +27,10 @@ def is_sequence(files): sequence = True return sequence +def get_current_fps(): + session_fps = float(legacy_io.Session.get('AVALON_FPS', 25)) + return convert_to_maya_fps(session_fps) + class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" @@ -88,7 +92,9 @@ class ArnoldStandinLoader(load.LoaderPlugin): sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) - fps = float(version["data"]["fps"]) + fps = float(version["data"].get("fps")) + if fps is None: + fps = get_current_fps() cmds.setAttr(standin_shape + ".abcFPS", fps) nodes = [root, standin, standin_shape] From d2ef628b22dd5bd97e46ce8c82a8381ffcf7ae3d Mon Sep 17 00:00:00 2001 From: FadyFS Date: Thu, 8 Jun 2023 17:44:27 +0200 Subject: [PATCH 233/347] new fps getter updated --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 2b07b1ca51..92b1433bb3 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -27,6 +27,7 @@ def is_sequence(files): sequence = True return sequence + def get_current_fps(): session_fps = float(legacy_io.Session.get('AVALON_FPS', 25)) return convert_to_maya_fps(session_fps) From d345b3e1b178c45cdce4bca315a9d5ce0e69a88f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 14:29:32 +0800 Subject: [PATCH 234/347] select the object from the node references for extractions instead of selecting children of the container --- openpype/hosts/max/plugins/publish/collect_members.py | 1 + openpype/hosts/max/plugins/publish/extract_camera_abc.py | 5 +++-- openpype/hosts/max/plugins/publish/extract_camera_fbx.py | 5 +++-- openpype/hosts/max/plugins/publish/extract_max_scene_raw.py | 3 +-- openpype/hosts/max/plugins/publish/extract_model.py | 5 +++-- openpype/hosts/max/plugins/publish/extract_model_fbx.py | 5 +++-- openpype/hosts/max/plugins/publish/extract_model_obj.py | 5 +++-- openpype/hosts/max/plugins/publish/extract_pointcache.py | 5 +++-- openpype/hosts/max/plugins/publish/extract_redshift_proxy.py | 5 +++-- 9 files changed, 23 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_members.py b/openpype/hosts/max/plugins/publish/collect_members.py index 54020d7dae..0acb4f408d 100644 --- a/openpype/hosts/max/plugins/publish/collect_members.py +++ b/openpype/hosts/max/plugins/publish/collect_members.py @@ -19,3 +19,4 @@ class CollectMembers(pyblish.api.InstancePlugin): member.node for member in container.openPypeData.all_handles ] + self.log.debug("{}".format(instance.data["members"])) diff --git a/openpype/hosts/max/plugins/publish/extract_camera_abc.py b/openpype/hosts/max/plugins/publish/extract_camera_abc.py index c526de8960..b42732e70d 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_abc.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_abc.py @@ -3,7 +3,7 @@ import os import pyblish.api from pymxs import runtime as rt -from openpype.hosts.max.api import get_all_children, maintained_selection +from openpype.hosts.max.api import maintained_selection from openpype.pipeline import OptionalPyblishPluginMixin, publish @@ -41,7 +41,8 @@ class ExtractCameraAlembic(publish.Extractor, OptionalPyblishPluginMixin): with maintained_selection(): # select and export - rt.Select(get_all_children(rt.GetNodeByName(container))) + node_list = instance.data["members"] + rt.Select(node_list) rt.ExportFile( path, rt.Name("noPrompt"), diff --git a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py index 0c8a82dcaa..06ac3da093 100644 --- a/openpype/hosts/max/plugins/publish/extract_camera_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_camera_fbx.py @@ -3,7 +3,7 @@ import os import pyblish.api from pymxs import runtime as rt -from openpype.hosts.max.api import get_all_children, maintained_selection +from openpype.hosts.max.api import maintained_selection from openpype.pipeline import OptionalPyblishPluginMixin, publish @@ -36,7 +36,8 @@ class ExtractCameraFbx(publish.Extractor, OptionalPyblishPluginMixin): with maintained_selection(): # select and export - rt.Select(get_all_children(rt.GetNodeByName(container))) + node_list = instance.data["members"] + rt.Select(node_list) rt.ExportFile( filepath, rt.Name("noPrompt"), diff --git a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py index f0c2aff7f3..de5db9ab56 100644 --- a/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py +++ b/openpype/hosts/max/plugins/publish/extract_max_scene_raw.py @@ -2,7 +2,6 @@ import os import pyblish.api from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import get_all_children class ExtractMaxSceneRaw(publish.Extractor, OptionalPyblishPluginMixin): @@ -33,7 +32,7 @@ class ExtractMaxSceneRaw(publish.Extractor, OptionalPyblishPluginMixin): if "representations" not in instance.data: instance.data["representations"] = [] - nodes = get_all_children(rt.getNodeByName(container)) + nodes = instance.data["members"] rt.saveNodes(nodes, max_path, quiet=True) self.log.info("Performing Extraction ...") diff --git a/openpype/hosts/max/plugins/publish/extract_model.py b/openpype/hosts/max/plugins/publish/extract_model.py index 4c7c98e2cc..c7ecf7efc9 100644 --- a/openpype/hosts/max/plugins/publish/extract_model.py +++ b/openpype/hosts/max/plugins/publish/extract_model.py @@ -2,7 +2,7 @@ import os import pyblish.api from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import maintained_selection, get_all_children +from openpype.hosts.max.api import maintained_selection class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin): @@ -40,7 +40,8 @@ class ExtractModel(publish.Extractor, OptionalPyblishPluginMixin): with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) + node_list = instance.data["members"] + rt.Select(node_list) rt.exportFile( filepath, rt.name("noPrompt"), diff --git a/openpype/hosts/max/plugins/publish/extract_model_fbx.py b/openpype/hosts/max/plugins/publish/extract_model_fbx.py index 815438d378..56c2cadd94 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_fbx.py +++ b/openpype/hosts/max/plugins/publish/extract_model_fbx.py @@ -2,7 +2,7 @@ import os import pyblish.api from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import maintained_selection, get_all_children +from openpype.hosts.max.api import maintained_selection class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): @@ -40,7 +40,8 @@ class ExtractModelFbx(publish.Extractor, OptionalPyblishPluginMixin): with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) + node_list = instance.data["members"] + rt.Select(node_list) rt.exportFile( filepath, rt.name("noPrompt"), diff --git a/openpype/hosts/max/plugins/publish/extract_model_obj.py b/openpype/hosts/max/plugins/publish/extract_model_obj.py index ed3d68c990..4fde65cf22 100644 --- a/openpype/hosts/max/plugins/publish/extract_model_obj.py +++ b/openpype/hosts/max/plugins/publish/extract_model_obj.py @@ -2,7 +2,7 @@ import os import pyblish.api from openpype.pipeline import publish, OptionalPyblishPluginMixin from pymxs import runtime as rt -from openpype.hosts.max.api import maintained_selection, get_all_children +from openpype.hosts.max.api import maintained_selection class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): @@ -31,7 +31,8 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin): with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) + node_list = instance.data["members"] + rt.Select(node_list) rt.exportFile( filepath, rt.name("noPrompt"), diff --git a/openpype/hosts/max/plugins/publish/extract_pointcache.py b/openpype/hosts/max/plugins/publish/extract_pointcache.py index 8658cecb1b..6d1e8d03b4 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcache.py @@ -41,7 +41,7 @@ import os import pyblish.api from openpype.pipeline import publish from pymxs import runtime as rt -from openpype.hosts.max.api import maintained_selection, get_all_children +from openpype.hosts.max.api import maintained_selection class ExtractAlembic(publish.Extractor): @@ -72,7 +72,8 @@ class ExtractAlembic(publish.Extractor): with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) + node_list = instance.data["members"] + rt.Select(node_list) rt.exportFile( path, rt.name("noPrompt"), diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index 8f76eef5ec..ab569ecbcb 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -2,7 +2,7 @@ import os import pyblish.api from openpype.pipeline import publish from pymxs import runtime as rt -from openpype.hosts.max.api import get_all_children, maintained_selection +from openpype.hosts.max.api import maintained_selection class ExtractRedshiftProxy(publish.Extractor): @@ -30,7 +30,8 @@ class ExtractRedshiftProxy(publish.Extractor): with maintained_selection(): # select and export - rt.select(get_all_children(rt.getNodeByName(container))) + node_list = instance.data["members"] + rt.Select(node_list) # Redshift rsProxy command # rsProxy fp selected compress connectivity startFrame endFrame # camera warnExisting transformPivotToOrigin From 8958585da07a7ba07fb2d84e8d26efee6f20d68c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 15:22:51 +0800 Subject: [PATCH 235/347] oscar's comment --- .../hosts/nuke/startup/custom_write_node.py | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 66095409a6..708f40db27 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -63,7 +63,7 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): def __init__(self): nukescripts.PythonPanel.__init__(self, "Set Knobs Value(Write Node)") - knobs_value = self.get_node_knobs_override() + knobs_value, _ = self.get_node_knobs_setting() # create knobs self.typeKnob = nuke.Enumeration_Knob( @@ -78,56 +78,51 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): node_knobs = self.typeKnob.value() ext = None - knobs = None + knobs = knobs_setting["knobs"] + if node_knobs: - knobs = self.get_node_knobs_setting(node_knobs) - if not knobs: + _, node_knobs_settings = self.get_node_knobs_setting(node_knobs) + if not node_knobs_settings: nuke.message("No knobs value found in subset group..\nDefault setting will be used..") # noqa - knobs = knobs_setting["knobs"] - else: - knobs = knobs_setting["knobs"] + else: + knobs = node_knobs_settings for knob in knobs: if knob["name"] == "file_type": ext = knob["value"] - for w in write_selected_nodes: + for write_node in write_selected_nodes: # data for mapping the path data = { "work": os.getenv("AVALON_WORKDIR"), - "subset": w["name"].value(), + "subset": write_node["name"].value(), "frame": "#" * frame_padding, "ext": ext } file_path = temp_rendering_path_template.format(**data) file_path = file_path.replace("\\", "/") - w["file"].setValue(file_path) - set_node_knobs_from_settings(w, knobs) + write_node["file"].setValue(file_path) + set_node_knobs_from_settings(write_node, knobs) - def get_node_knobs_setting(self, value): + def get_node_knobs_setting(self, value=None): + knobs_value = [] + knobs_nodes = [] settings = [ node for node in get_nuke_imageio_settings()["nodes"]["overrideNodes"] ] if not settings: return + for i, _ in enumerate(settings): if value in settings[i]["subsets"]: - return settings[i]["knobs"] - - def get_node_knobs_override(self): - knobs_value = [] - settings = [ - node - for node in get_nuke_imageio_settings()["nodes"]["overrideNodes"] - ] - if not settings: - return + knobs_nodes = settings[i]["knobs"] for setting in settings: if setting["nukeNodeClass"] == "Write" and setting["subsets"]: - for knob in setting["subsets"]: - knobs_value.append(knob) - return knobs_value + for knob in setting["subsets"]: + knobs_value.append(knob) + + return knobs_value, knobs_nodes def main(): From 4b0b06ea4f7201dd274866544de51a750ed30395 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 15:23:48 +0800 Subject: [PATCH 236/347] hound fix --- openpype/hosts/nuke/startup/custom_write_node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 708f40db27..d229b6b268 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -119,8 +119,8 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): for setting in settings: if setting["nukeNodeClass"] == "Write" and setting["subsets"]: - for knob in setting["subsets"]: - knobs_value.append(knob) + for knob in setting["subsets"]: + knobs_value.append(knob) return knobs_value, knobs_nodes From fbf4605417493ed5c0cf4646a9b0645ffc9f7c46 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Jun 2023 12:40:05 +0200 Subject: [PATCH 237/347] removing get_app_version_info --- openpype/hosts/nuke/api/__init__.py | 4 +--- openpype/hosts/nuke/api/lib.py | 11 ----------- openpype/hosts/nuke/api/pipeline.py | 12 +++++++----- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index 55c4b8c808..1af5ff365d 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -43,8 +43,7 @@ from .lib import ( get_node_data, set_node_data, update_node_data, - create_write_node, - get_app_version_info + create_write_node ) from .utils import ( colorspace_exists_on_node, @@ -91,7 +90,6 @@ __all__ = ( "set_node_data", "update_node_data", "create_write_node", - "get_app_version_info", "colorspace_exists_on_node", "get_colorspace_list" diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index bf66b9e1a9..4a57bc3165 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -3116,14 +3116,3 @@ def get_viewer_config_from_string(input_string): ).format(input_string)) return (display, viewer) - - -def get_app_version_info(): - """ Return dict of Nuke's version semantic info""" - dot_split = nuke.NUKE_VERSION_STRING.split(".") - v_spit = dot_split[1].split("v") - return { - "major": int(dot_split[0]), - "minor": int(v_spit[0]), - "patch": int(v_spit[1]) - } diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 998c03b3dd..8406a251e9 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -43,8 +43,7 @@ from .lib import ( add_scripts_menu, add_scripts_gizmo, get_node_data, - set_node_data, - get_app_version_info + set_node_data ) from .workfile_template_builder import ( NukePlaceholderLoadPlugin, @@ -219,7 +218,6 @@ def _install_menu(): main_window = get_main_window() menubar = nuke.menu("Nuke") menu = menubar.addMenu(MENU_LABEL) - app_version = get_app_version_info() if not ASSIST: label = "{0}, {1}".format( @@ -244,7 +242,9 @@ def _install_menu(): menu.addCommand( "Create...", lambda: host_tools.show_publisher( - parent=main_window if app_version["major"] >= 14 else None, + parent=( + main_window if nuke.NUKE_VERSION_RELEASE >= 14 else None + ), tab="create" ) ) @@ -253,7 +253,9 @@ def _install_menu(): menu.addCommand( "Publish...", lambda: host_tools.show_publisher( - parent=main_window if app_version["major"] >= 14 else None, + parent=( + main_window if nuke.NUKE_VERSION_RELEASE >= 14 else None + ), tab="publish" ) ) From a80b1b0f766ad127927575a4e91b7bb48fdb446d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Jun 2023 13:59:27 +0200 Subject: [PATCH 238/347] fixing still to image family conversion --- openpype/hosts/nuke/plugins/publish/extract_render_local.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index e5feda4cd8..e2cf2addc5 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -23,7 +23,7 @@ class NukeRenderLocal(publish.Extractor, order = pyblish.api.ExtractorOrder label = "Render Local" hosts = ["nuke"] - families = ["render.local", "prerender.local", "still.local"] + families = ["render.local", "prerender.local", "image.local"] def process(self, instance): child_nodes = ( @@ -136,9 +136,9 @@ class NukeRenderLocal(publish.Extractor, families.remove('prerender.local') families.insert(0, "prerender") instance.data["anatomyData"]["family"] = "prerender" - elif "still.local" in families: + elif "image.local" in families: instance.data['family'] = 'image' - families.remove('still.local') + families.remove('image.local') instance.data["anatomyData"]["family"] = "image" instance.data["families"] = families From 7c3229db704c8b12529f68e2f6d053a76ae85f51 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 20:19:04 +0800 Subject: [PATCH 239/347] Jakub's comment --- .../hosts/nuke/startup/custom_write_node.py | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index d229b6b268..3edc74ceac 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -2,6 +2,7 @@ import os import nuke import nukescripts +from openpype.pipeline import Anatomy from openpype.hosts.nuke.api.lib import ( set_node_knobs_from_settings, get_nuke_imageio_settings @@ -66,30 +67,43 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): knobs_value, _ = self.get_node_knobs_setting() # create knobs - self.typeKnob = nuke.Enumeration_Knob( - 'override_subsets', 'override subsets', knobs_value) + self.selected_preset_name = nuke.Enumeration_Knob( + 'preset_selector', 'presets', knobs_value) # add knobs to panel - self.addKnob(self.typeKnob) + self.addKnob(self.selected_preset_name ) def process(self): """ Process the panel values. """ write_selected_nodes = [ s for s in nuke.selectedNodes() if s.Class() == "Write"] - node_knobs = self.typeKnob.value() + node_knobs = self.selected_preset_name.value() ext = None knobs = knobs_setting["knobs"] - - if node_knobs: - _, node_knobs_settings = self.get_node_knobs_setting(node_knobs) + knobs_value, node_knobs_settings = self.get_node_knobs_setting(node_knobs) + if node_knobs and knobs_value: if not node_knobs_settings: nuke.message("No knobs value found in subset group..\nDefault setting will be used..") # noqa else: knobs = node_knobs_settings - for knob in knobs: - if knob["name"] == "file_type": + ext_knob_list = [knob for knob in knobs if knob["name"]== "file_type"] + if not ext_knob_list: + for knob in knobs_setting["knobs"]: ext = knob["value"] + nuke.message("No file type found in the subset's knobs.Default file_type value will be used..") + else: + for knob in ext_knob_list: + ext = knob["value"] + + + anatomy = Anatomy() + + frame_padding = int( + anatomy.templates["render"].get( + "frame_padding" + ) + ) for write_node in write_selected_nodes: # data for mapping the path data = { From 399562d950ab277967ded5c02ed52fbd66771436 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 20:20:44 +0800 Subject: [PATCH 240/347] hound fix --- openpype/hosts/nuke/startup/custom_write_node.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 3edc74ceac..d708b277a6 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -67,10 +67,10 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): knobs_value, _ = self.get_node_knobs_setting() # create knobs - self.selected_preset_name = nuke.Enumeration_Knob( + self.selected_preset_name = nuke.Enumeration_Knob( 'preset_selector', 'presets', knobs_value) # add knobs to panel - self.addKnob(self.selected_preset_name ) + self.addKnob(self.selected_preset_name) def process(self): """ Process the panel values. """ @@ -80,18 +80,20 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): node_knobs = self.selected_preset_name.value() ext = None knobs = knobs_setting["knobs"] - knobs_value, node_knobs_settings = self.get_node_knobs_setting(node_knobs) + knobs_value, node_knobs_settings = ( + self.get_node_knobs_setting(node_knobs) + ) if node_knobs and knobs_value: if not node_knobs_settings: nuke.message("No knobs value found in subset group..\nDefault setting will be used..") # noqa else: knobs = node_knobs_settings - ext_knob_list = [knob for knob in knobs if knob["name"]== "file_type"] + ext_knob_list = [knob for knob in knobs if knob["name"] == "file_type"] if not ext_knob_list: for knob in knobs_setting["knobs"]: ext = knob["value"] - nuke.message("No file type found in the subset's knobs.Default file_type value will be used..") + nuke.message("No file type found in the subset's knobs.\nDefault file_type value will be used..") # noqa else: for knob in ext_knob_list: ext = knob["value"] From 1a1afee31957f1c5f12957a313b7c4438de4317c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 20:21:57 +0800 Subject: [PATCH 241/347] hound fix --- openpype/hosts/nuke/startup/custom_write_node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index d708b277a6..3e2a6fa652 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -82,7 +82,8 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): knobs = knobs_setting["knobs"] knobs_value, node_knobs_settings = ( self.get_node_knobs_setting(node_knobs) - ) + ) + if node_knobs and knobs_value: if not node_knobs_settings: nuke.message("No knobs value found in subset group..\nDefault setting will be used..") # noqa @@ -98,7 +99,6 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): for knob in ext_knob_list: ext = knob["value"] - anatomy = Anatomy() frame_padding = int( From feff57de37708f70e30e602ce0fe0edda3e2a3e0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 20:30:50 +0800 Subject: [PATCH 242/347] tell the user no file type in knobs --- openpype/hosts/nuke/startup/custom_write_node.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 3e2a6fa652..08b11ee0fc 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -92,9 +92,8 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): ext_knob_list = [knob for knob in knobs if knob["name"] == "file_type"] if not ext_knob_list: - for knob in knobs_setting["knobs"]: - ext = knob["value"] nuke.message("No file type found in the subset's knobs.\nDefault file_type value will be used..") # noqa + break else: for knob in ext_knob_list: ext = knob["value"] From 704620287c479880ad0205772db96112ca8e8dfb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 20:31:41 +0800 Subject: [PATCH 243/347] tell the user no file type in knobs and ask them to add one --- openpype/hosts/nuke/startup/custom_write_node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 08b11ee0fc..2b326d2a64 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -92,8 +92,8 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): ext_knob_list = [knob for knob in knobs if knob["name"] == "file_type"] if not ext_knob_list: - nuke.message("No file type found in the subset's knobs.\nDefault file_type value will be used..") # noqa - break + nuke.message("No file type found in the subset's knobs.\nPlease add one...") # noqa + return else: for knob in ext_knob_list: ext = knob["value"] From e1b2b723d7bbf2846e1bf5577dc3ed8790187c27 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Jun 2023 20:33:06 +0800 Subject: [PATCH 244/347] tell the user no file type in knobs and ask them to add one --- openpype/hosts/nuke/startup/custom_write_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 2b326d2a64..a221a26424 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -92,7 +92,7 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): ext_knob_list = [knob for knob in knobs if knob["name"] == "file_type"] if not ext_knob_list: - nuke.message("No file type found in the subset's knobs.\nPlease add one...") # noqa + nuke.message("ERROR: No file type found in the subset's knobs.\nPlease add one to complete setting up the node") # noqa return else: for knob in ext_knob_list: From 13efd0eceb3cbeb41e1acb909667b173b363e353 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 9 Jun 2023 16:16:43 +0200 Subject: [PATCH 245/347] :bug: fix 3dsmax host name 3dsmax host name is just `max` --- openpype/hooks/pre_ocio_hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py index eac7d2696f..7af0f4ceef 100644 --- a/openpype/hooks/pre_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -14,7 +14,7 @@ class OCIOEnvHook(PreLaunchHook): "fusion", "blender", "aftereffects", - "3dsmax", + "max", "houdini", "maya", "nuke", From 4d40857ed80757a762ade8c49359c14490eb32f5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Jun 2023 16:24:42 +0200 Subject: [PATCH 246/347] removing redundant hook --- openpype/hooks/pre_host_set_ocio.py | 37 ----------------------------- 1 file changed, 37 deletions(-) delete mode 100644 openpype/hooks/pre_host_set_ocio.py diff --git a/openpype/hooks/pre_host_set_ocio.py b/openpype/hooks/pre_host_set_ocio.py deleted file mode 100644 index 3620d88db6..0000000000 --- a/openpype/hooks/pre_host_set_ocio.py +++ /dev/null @@ -1,37 +0,0 @@ -from openpype.lib import PreLaunchHook - -from openpype.pipeline.colorspace import get_imageio_config -from openpype.pipeline.template_data import get_template_data - - -class PreLaunchHostSetOCIO(PreLaunchHook): - """Set OCIO environment for the host""" - - order = 0 - app_groups = ["substancepainter"] - - def execute(self): - """Hook entry method.""" - - anatomy_data = get_template_data( - project_doc=self.data["project_doc"], - asset_doc=self.data["asset_doc"], - task_name=self.data["task_name"], - host_name=self.host_name, - system_settings=self.data["system_settings"] - ) - - ocio_config = get_imageio_config( - project_name=self.data["project_doc"]["name"], - host_name=self.host_name, - project_settings=self.data["project_settings"], - anatomy_data=anatomy_data, - anatomy=self.data["anatomy"] - ) - - if ocio_config: - ocio_path = ocio_config["path"] - self.log.info(f"Setting OCIO config path: {ocio_path}") - self.launch_context.env["OCIO"] = ocio_path - else: - self.log.debug("OCIO not set or enabled") From 4595813ea6031e7147a3daa2c825fd6b70dd0b02 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Jun 2023 16:25:33 +0200 Subject: [PATCH 247/347] merge from redundant hook --- openpype/hooks/pre_ocio_hook.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hooks/pre_ocio_hook.py b/openpype/hooks/pre_ocio_hook.py index 7af0f4ceef..8f462665bc 100644 --- a/openpype/hooks/pre_ocio_hook.py +++ b/openpype/hooks/pre_ocio_hook.py @@ -11,6 +11,7 @@ class OCIOEnvHook(PreLaunchHook): order = 0 hosts = [ + "substancepainter", "fusion", "blender", "aftereffects", @@ -48,3 +49,5 @@ class OCIOEnvHook(PreLaunchHook): f"Setting OCIO environment to config path: {ocio_path}") self.launch_context.env["OCIO"] = ocio_path + else: + self.log.debug("OCIO not set or enabled") From f0d7876db70b09e5763906d91f9660b7019552e8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Jun 2023 16:26:22 +0200 Subject: [PATCH 248/347] backward compatibility - for hosts which had activated ocio_config in overrides - those are group settings. --- openpype/pipeline/colorspace.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index d4011d32c9..7e6efabf20 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -357,8 +357,12 @@ def get_imageio_config( imageio_global, imageio_host = _get_imageio_settings( project_settings, host_name) - activate_color_management = imageio_global.get( - "activate_global_color_management", False) + activate_color_management = ( + imageio_global.get("activate_global_color_management", False) + # for already saved overrides from previous version + # TODO: remove this in future - backward compatibility + or imageio_host.get("ocio_config").get("enabled") + ) if not activate_color_management: # if global settings are disabled return empty dict because @@ -391,7 +395,12 @@ def get_imageio_config( # depending on override flag # TODO: in future rewrite this to be more explicit config_data = None - override_global_config = config_host.get("override_global_config") + override_global_config = ( + config_host.get("override_global_config") + # for already saved overrides from previous version + # TODO: remove this in future - backward compatibility + or config_host.get("enabled") + ) if override_global_config: config_data = _get_config_data( config_host["filepath"], formatting_data From a71fed7a566579740eb34d4066fa420ccceaf109 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Jun 2023 16:27:06 +0200 Subject: [PATCH 249/347] nuke: backward compatibility for older project settings --- openpype/hosts/nuke/api/lib.py | 67 ++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 912bc5963b..777f4454dc 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -39,6 +39,7 @@ from openpype.settings import ( from openpype.modules import ModulesManager from openpype.pipeline.template_data import get_template_data_with_names from openpype.pipeline import ( + get_current_project_name, discover_legacy_creator_plugins, legacy_io, Anatomy, @@ -2008,37 +2009,65 @@ class WorkfileSettings(object): imageio_host (dict): host colorspace configurations ''' + config_data = get_imageio_config( + project_name=get_current_project_name(), + host_name="nuke" + ) + workfile_settings = imageio_host["workfile"] - # first set OCIO - if self._root_node["colorManagement"].value() \ - not in str(workfile_settings["colorManagement"]): - self._root_node["colorManagement"].setValue( - str(workfile_settings["colorManagement"])) + if not config_data: + # TODO: backward compatibility for old projects - remove later + # perhaps old project overrides is having it set to older version + # with use of `customOCIOConfigPath` + if workfile_settings.get("customOCIOConfigPath"): + unresolved_path = workfile_settings["customOCIOConfigPath"] + ocio_paths = unresolved_path[platform.system().lower()] - # we dont need the key anymore - workfile_settings.pop("colorManagement") + resolved_path = None + for ocio_p in ocio_paths: + resolved_path = str(ocio_p).format(**os.environ) + if not os.path.exists(resolved_path): + continue + if resolved_path: + # set values to root + self._root_node["colorManagement"].setValue("OCIO") + self._root_node["OCIO_config"].setValue("custom") + self._root_node["customOCIOConfigPath"].setValue( + resolved_path) + else: + # no ocio config found and no custom path used + if self._root_node["colorManagement"].value() \ + not in str(workfile_settings["colorManagement"]): + self._root_node["colorManagement"].setValue( + str(workfile_settings["colorManagement"])) - # second set ocio version - if self._root_node["OCIO_config"].value() \ - not in str(workfile_settings["OCIO_config"]): - self._root_node["OCIO_config"].setValue( - str(workfile_settings["OCIO_config"])) + # second set ocio version + if self._root_node["OCIO_config"].value() \ + not in str(workfile_settings["OCIO_config"]): + self._root_node["OCIO_config"].setValue( + str(workfile_settings["OCIO_config"])) - # we dont need the key anymore - workfile_settings.pop("OCIO_config") + else: + # set values to root + self._root_node["colorManagement"].setValue("OCIO") + + # we dont need the key anymore + workfile_settings.pop("customOCIOConfigPath") + workfile_settings.pop("colorManagement") + workfile_settings.pop("OCIO_config") # then set the rest - for knob, value in workfile_settings.items(): + for knob, value_ in workfile_settings.items(): # skip unfilled ocio config path # it will be dict in value - if isinstance(value, dict): + if isinstance(value_, dict): continue - if self._root_node[knob].value() not in value: - self._root_node[knob].setValue(str(value)) + if self._root_node[knob].value() not in value_: + self._root_node[knob].setValue(str(value_)) log.debug("nuke.root()['{}'] changed to: {}".format( - knob, value)) + knob, value_)) def set_writes_colorspace(self): ''' Adds correct colorspace to write node dict From 44bf1dcc8a1111f0b8d10319c0dce48fe24fd733 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Jun 2023 16:33:47 +0200 Subject: [PATCH 250/347] backward combability for file rules --- openpype/pipeline/colorspace.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 7e6efabf20..899b14148b 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -490,8 +490,11 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): # get file rules from global and host_name frules_global = imageio_global["file_rules"] - activate_global_rules = frules_global.get( - "activate_global_file_rules", False) + activate_global_rules = ( + frules_global.get("activate_global_file_rules", False) + # TODO: remove this in future - backward compatibility + or frules_global.get("enabled") + ) global_rules = frules_global["rules"] if not activate_global_rules: @@ -504,7 +507,11 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): frules_host = imageio_host.get("file_rules", {}) # compile file rules dictionary - activate_host_rules = frules_host.get("activate_host_rules") + activate_host_rules = ( + frules_host.get("activate_host_rules") + # TODO: remove this in future - backward compatibility + or frules_host.get("enabled") + ) # return host rules if activated or global rules return frules_host["rules"] if activate_host_rules else global_rules From aaf8756e18008552b89dacaff19ba1d4b0de12f4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Jun 2023 16:57:14 +0200 Subject: [PATCH 251/347] hiero: backward compatibility --- openpype/hosts/hiero/api/lib.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py index 0d4368529f..fa874f9e9d 100644 --- a/openpype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -23,11 +23,17 @@ except ImportError: from openpype.client import get_project from openpype.settings import get_project_settings -from openpype.pipeline import legacy_io, Anatomy +from openpype.pipeline import ( + get_current_project_name, legacy_io, Anatomy +) from openpype.pipeline.load import filter_containers from openpype.lib import Logger from . import tags +from openpype.pipeline.colorspace import ( + get_imageio_config +) + class DeprecatedWarning(DeprecationWarning): pass @@ -1047,6 +1053,18 @@ def apply_colorspace_project(): imageio = get_project_settings(project_name)["hiero"]["imageio"] presets = imageio.get("workfile") + # backward compatibility layer + # TODO: remove this after some time + config_data = get_imageio_config( + project_name=get_current_project_name(), + host_name="hiero" + ) + + if config_data: + presets.update({ + "ocioConfigName": "custom" + }) + # save the workfile as subversion "comment:_colorspaceChange" split_current_file = os.path.splitext(current_file) copy_current_file = current_file From 7be1d97118e2c2b3cb159b2fe192caa233872002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 9 Jun 2023 17:01:05 +0200 Subject: [PATCH 252/347] Update openpype/hosts/flame/hooks/pre_flame_setup.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fabià Serra Arrizabalaga --- openpype/hosts/flame/hooks/pre_flame_setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/flame/hooks/pre_flame_setup.py b/openpype/hosts/flame/hooks/pre_flame_setup.py index 1f01499333..83110bb6b5 100644 --- a/openpype/hosts/flame/hooks/pre_flame_setup.py +++ b/openpype/hosts/flame/hooks/pre_flame_setup.py @@ -47,7 +47,11 @@ class FlamePrelaunch(PreLaunchHook): imageio_flame = project_settings["flame"]["imageio"] - # get host imageio settings enabled key if exists + # Check whether 'enabled' key from host imageio settings exists + # so we can tell if host is using the new colormanagement framework. + # If the 'enabled' isn't found we want 'colormanaged' set to True + # because prior to the key existing we always did colormanagement for + # Flame colormanaged = imageio_flame.get("enabled") # if key was not found, set to True # ensuring backward compatibility From cd7317410be99bdd1d109e5227bd8c5301f84c21 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Fri, 9 Jun 2023 15:28:20 +0000 Subject: [PATCH 253/347] [Automated] Release --- CHANGELOG.md | 393 ++++++++++++++++++++++++++++++++++++++++++++ openpype/version.py | 2 +- pyproject.toml | 2 +- 3 files changed, 395 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec6544e659..882620f26c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,399 @@ # Changelog +## [3.15.10](https://github.com/ynput/OpenPype/tree/3.15.10) + + +[Full Changelog](https://github.com/ynput/OpenPype/compare/3.15.9...3.15.10) + +### **🆕 New features** + + +
+ImageIO: Adding ImageIO activation toggle to all hosts #4700 + +Colorspace management can now be enabled at the project level, although it is disabled by default. Once enabled, all hosts will use the OCIO config file defined in the settings. If settings are disabled, the system switches to DCC's native color space management, and we do not store colorspace information at the representative level. + + +___ + +
+ + +
+Redshift Proxy Support in 3dsMax #4625 + +Redshift Proxy Support for 3dsMax. +- [x] Creator +- [x] Loader +- [x] Extractor +- [x] Validator +- [x] Add documentation + + +___ + +
+ + +
+Houdini farm publishing and rendering #4825 + +Deadline Farm publishing and Rendering for Houdini +- [x] Mantra +- [x] Karma(including usd renders) +- [x] Arnold +- [x] Elaborate Redshift ROP for deadline submission +- [x] fix the existing bug in Redshift ROP +- [x] Vray +- [x] add docs + + +___ + +
+ + +
+Feature: Blender hook to execute python scripts at launch #4905 + +Hook to allow hooks to add path to a python script that will be executed when Blender starts. + + +___ + +
+ + +
+Feature: Resolve: Open last workfile on launch through .scriptlib #5047 + +Added implementation to Resolve integration to open last workfile on launch. + + +___ + +
+ + +
+General: Remove default windowFlags from publisher #5089 + +The default windowFlags is making the publisher window (in Linux at least) only show the close button and it's frustrating as many times you just want to minimize the window and get back to the validation after. Removing that line I get what I'd expect.**Before:****After:** + + +___ + +
+ + +
+General: Show user who created the workfile on the details pane of workfile manager #5093 + +New PR for https://github.com/ynput/OpenPype/pull/5087, which was closed after merging `next-minor` branch and then realizing we don't need to target it as it was decided it's not required to support windows. More info on that PR discussion.Small addition to add name of the `user` who created the workfile on the details pane of the workfile manager: + + +___ + +
+ + +
+Loader: Hide inactive versions in UI #5100 + +Hide versions with `active` set to `False` in Loader UI. + + +___ + +
+ +### **🚀 Enhancements** + + +
+Maya: Repair RenderPass token when merging AOVs. #5055 + +Validator was flagging that `` was in the image prefix, but did not repair the issue. + + +___ + +
+ + +
+Maya: Improve error feedback when no renderable cameras exist for ASS family. #5092 + +When collecting cameras for `ass` family, this improves the error message when no cameras are renderable. + + +___ + +
+ + +
+Nuke: Custom script to set frame range of read nodes #5039 + +Adding option to set frame range specifically for the read nodes in Openpype Panel. User can set up their preferred frame range with the frame range dialog, which can be showed after clicking `Set Frame Range (Read Node)` in Openpype Tools + + +___ + +
+ + +
+Update extract review letterbox docs #5074 + +Update Extract Review - Letter Box section in Docs. Letterbox type description is removed. + + +___ + +
+ + +
+Project pack: Documents only skips roots validation #5082 + +Single roots validation is skipped if only documents are extracted. + + +___ + +
+ + +
+Nuke: custom settings for write node without publish #5084 + +Set Render Output and other settings to write nodes for non-publish purposes. + + +___ + +
+ +### **🐛 Bug fixes** + + +
+Maya: Deadline servers #5052 + +Fix working with multiple Deadline servers in Maya. +- Pools (primary and secondary) attributes were not recreated correctly. +- Order of collector plugins were wrong, so collected data was not injected into render instances. +- Server attribute was not converted to string so comparing with settings was incorrect. +- Improve debug logging for where the webservice url is getting fetched from. + + +___ + +
+ + +
+Maya: Fix Load Reference. #5091 + +Fix bug introduced with https://github.com/ynput/OpenPype/pull/4751 where `cmds.ls` returns a list. + + +___ + +
+ + +
+3dsmax: Publishing Deadline jobs from RedShift #4960 + +Fix the bug of being uable to publish deadline jobs from RedshiftUse Current File instead of Published Scene for just Redshift. +- add save scene before rendering to ensure the scene is saved after the modification. +- add separated aov files option to allow users to choose to have aovs in render output +- add validator for render publish to aovid overriding the previous renders + + +___ + +
+ + +
+Houdini: Fix missing frame range for pointcache and camera exports #5026 + +Fix missing frame range for pointcache and camera exports on published version. + + +___ + +
+ + +
+Global: collect_frame_fix plugin fix and cleanup #5064 + +Previous implementation https://github.com/ynput/OpenPype/pull/5036 was broken this is fixing the issue where attribute is found in instance data although the settings were disabled for the plugin. + + +___ + +
+ + +
+Hiero: Fix apply settings Clip Load #5073 + +Changed `apply_settings` to classmethod which fixes the issue with settings. + + +___ + +
+ + +
+Resolve: Make sure scripts dir exists #5078 + +Make sure the scripts directory exists before looping over it's content. + + +___ + +
+ + +
+removing info knob from nuke creators #5083 + +- removing instance node if removed via publisher +- removing info knob since it is not needed any more (was there only for the transition phase) + + +___ + +
+ + +
+Tray: Fix restart arguments on update #5085 + +Fix arguments on restart. + + +___ + +
+ + +
+Maya: bug fix on repair action in Arnold Scene Source CBID Validator #5096 + +Fix the bug of not being able to use repair action in Arnold Scene Source CBID Validator + + +___ + +
+ + +
+Nuke: batch of small fixes #5103 + +- default settings for `imageio.requiredNodes` **CreateWriteImage** +- default settings for **LoadImage** representations +- **Create** and **Publish** menu items with `parent=main_window` (version > 14) + + +___ + +
+ + +
+Deadline: make prerender check safer #5104 + +Prerender wasn't correctly recognized and was replaced with just 'render' family.In Nuke it is correctly `prerender.farm` in families, which wasn't handled here. It resulted into using `render` in templates even if `render` and `prerender` templates were split. + + +___ + +
+ + +
+General: Sort launcher actions alphabetically #5106 + +The launcher actions weren't being sorted by its label but its name (which on the case of the apps it's the version number) and thus the order wasn't consistent and we kept getting a different order on every launch. From my debugging session, this was the result of what the `actions` variable held after the `filter_compatible_actions` function before these changes: +``` +(Pdb) for p in actions: print(p.order, p.name) +0 14-02 +0 14-02 +0 14-02 +0 14-02 +0 14-02 +0 19-5-493 +0 2023 +0 3-41 +0 6-01 +```This caused already a couple bugs from our artists thinking they had launched Nuke X and instead launched Nuke and telling us their Nuke was missing nodes**Before:****After:** + + +___ + +
+ + +
+TrayPublisher: Editorial video stream discovery #5120 + +Editorial create plugin in traypublisher does not expect that first stream in input is video. + + +___ + +
+ +### **🔀 Refactored code** + + +
+3dsmax: Move from deprecated interface #5117 + +`INewPublisher` interface is deprecated, this PR is changing the use to `IPublishHost` instead. + + +___ + +
+ +### **Merged pull requests** + + +
+add movalex as a contributor for code #5076 + +Adds @movalex as a contributor for code. + +This was requested by mkolar [in this comment](https://github.com/ynput/OpenPype/pull/4916#issuecomment-1571498425) + +[skip ci] +___ + +
+ + +
+3dsmax: refactor load plugins #5079 + + +___ + +
+ + + + ## [3.15.9](https://github.com/ynput/OpenPype/tree/3.15.9) diff --git a/openpype/version.py b/openpype/version.py index 868664c601..cf4e1efc66 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.10-nightly.2" +__version__ = "3.15.10" diff --git a/pyproject.toml b/pyproject.toml index 633899d3a0..56c130982c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.9" # OpenPype +version = "3.15.10" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 333249b4d8ee32116851c6aabad25a9bc29cac16 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 9 Jun 2023 15:29:28 +0000 Subject: [PATCH 254/347] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e614d2fa65..aa5a99f66e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.10 - 3.15.10-nightly.2 - 3.15.10-nightly.1 - 3.15.9 @@ -134,7 +135,6 @@ body: - 3.14.3-nightly.4 - 3.14.3-nightly.3 - 3.14.3-nightly.2 - - 3.14.3-nightly.1 validations: required: true - type: dropdown From 54294abb49e1e1b215aad6d07c3fa50000ccf52a Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 10 Jun 2023 03:25:04 +0000 Subject: [PATCH 255/347] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index cf4e1efc66..300f5546ae 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.10" +__version__ = "3.15.11-nightly.1" From 6f0c5083d310af4291ce14e87dde80dff479fa5d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 10 Jun 2023 03:25:49 +0000 Subject: [PATCH 256/347] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index aa5a99f66e..0b1d0f5087 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.11-nightly.1 - 3.15.10 - 3.15.10-nightly.2 - 3.15.10-nightly.1 @@ -134,7 +135,6 @@ body: - 3.14.3-nightly.5 - 3.14.3-nightly.4 - 3.14.3-nightly.3 - - 3.14.3-nightly.2 validations: required: true - type: dropdown From 8c3d5be711bccb68f4c7897505c0a83d1b9cef37 Mon Sep 17 00:00:00 2001 From: FadyFS Date: Mon, 12 Jun 2023 11:08:12 +0200 Subject: [PATCH 257/347] reviews --- .../hosts/maya/plugins/load/load_arnold_standin.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 92b1433bb3..0b75915cb5 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -19,10 +19,7 @@ from openpype.hosts.maya.api.pipeline import containerise def is_sequence(files): sequence = False - if len(files) == 1: - collections, remainder = clique.assemble(files, minimum_items=1) - else: - collections, remainder = clique.assemble(files) + collections, remainder = clique.assemble(files, minimum_items=1) if collections: sequence = True return sequence @@ -93,9 +90,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) - fps = float(version["data"].get("fps")) - if fps is None: - fps = get_current_fps() + fps = float(version["data"].get("fps"), get_current_fps()) cmds.setAttr(standin_shape + ".abcFPS", fps) nodes = [root, standin, standin_shape] @@ -161,7 +156,6 @@ class ArnoldStandinLoader(load.LoaderPlugin): type="string" ) - # Object name value cmds.connectAttr( string_replace_operator + ".out", "{}.inputs[{}]".format( From ba4ce235e2563da7dc0d90d27c5a129b04b21ca5 Mon Sep 17 00:00:00 2001 From: FadyFS Date: Mon, 12 Jun 2023 11:23:05 +0200 Subject: [PATCH 258/347] update fps attribute --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 0b75915cb5..f62749f587 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -90,7 +90,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) - fps = float(version["data"].get("fps"), get_current_fps()) + fps = float(version["data"].get("fps")) or get_current_fps() cmds.setAttr(standin_shape + ".abcFPS", fps) nodes = [root, standin, standin_shape] From ebc38d6d4fd0e003545dc00d23bdfeb36adaa91d Mon Sep 17 00:00:00 2001 From: FadyFS Date: Mon, 12 Jun 2023 11:34:49 +0200 Subject: [PATCH 259/347] get current session fps --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index f62749f587..74dd54a472 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -25,7 +25,7 @@ def is_sequence(files): return sequence -def get_current_fps(): +def get_current_session_fps(): session_fps = float(legacy_io.Session.get('AVALON_FPS', 25)) return convert_to_maya_fps(session_fps) @@ -90,7 +90,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) - fps = float(version["data"].get("fps")) or get_current_fps() + fps = float(version["data"].get("fps")) or get_current_session_fps() cmds.setAttr(standin_shape + ".abcFPS", fps) nodes = [root, standin, standin_shape] From e9d60c96f50e5a278aa602e80a6a7b677a8decf9 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 12 Jun 2023 12:44:12 +0200 Subject: [PATCH 260/347] :bug: handle cancelled dialog --- openpype/hosts/max/api/plugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index f52e78de70..5eb4eaf391 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -35,6 +35,7 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" ( current_selection = selectByName title:"Select Objects to add to the Container" buttontext:"Add" + if current_selection == undefined then return False temp_arr = #() i_node_arr = #() for c in current_selection do @@ -52,8 +53,10 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" ( current_selection = selectByName title:"Select Objects to remove from the Container" buttontext:"Remove" + if current_selection == undefined then return False temp_arr = #() i_node_arr = #() + for c in current_selection do ( node_ref = NodeTransformMonitor node:c From addec3fd7a65dc67fa9005414b4035a9cd6e3867 Mon Sep 17 00:00:00 2001 From: FadyFS Date: Mon, 12 Jun 2023 13:55:28 +0200 Subject: [PATCH 261/347] line correction --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 74dd54a472..0040baeec3 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -90,7 +90,7 @@ class ArnoldStandinLoader(load.LoaderPlugin): sequence = is_sequence(os.listdir(os.path.dirname(self.fname))) cmds.setAttr(standin_shape + ".useFrameExtension", sequence) - fps = float(version["data"].get("fps")) or get_current_session_fps() + fps = float(version["data"].get("fps"))or get_current_session_fps() cmds.setAttr(standin_shape + ".abcFPS", fps) nodes = [root, standin, standin_shape] From c2a576a39a9a38b67bf3cd4fa3e1d0edc36d6bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 12 Jun 2023 19:47:43 +0200 Subject: [PATCH 262/347] Nuke: Add support for more complex templates in temp_rendering_path_template --- openpype/hosts/nuke/api/lib.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 777f4454dc..bb58bea4e3 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -1302,10 +1302,7 @@ def create_write_node( fdir = str(anatomy_filled["work"]["folder"]).replace("\\", "/") fpath = data["fpath_template"].format( work=fdir, - version=data["version"], - subset=data["subset"], - frame=data["frame"], - ext=ext + **data, ) # create directory From 079f7a935aa3d1a42f39eb8e47c96462ceebe898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A0=20Serra=20Arrizabalaga?= Date: Mon, 12 Jun 2023 19:47:59 +0200 Subject: [PATCH 263/347] Add fallback in case key doesn't exist --- openpype/hosts/nuke/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index bb58bea4e3..860c42ebf3 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2051,7 +2051,7 @@ class WorkfileSettings(object): self._root_node["colorManagement"].setValue("OCIO") # we dont need the key anymore - workfile_settings.pop("customOCIOConfigPath") + workfile_settings.pop("customOCIOConfigPath", None) workfile_settings.pop("colorManagement") workfile_settings.pop("OCIO_config") From 74efe431765272ff640b6478b15ac06e03a5e0a4 Mon Sep 17 00:00:00 2001 From: Alexey Bogomolov Date: Tue, 13 Jun 2023 10:03:05 +0300 Subject: [PATCH 264/347] hide macos dock icon on build --- tools/build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/build.sh b/tools/build.sh index 753a9c55b8..e828cc149e 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -196,6 +196,8 @@ if [ "$disable_submodule_update" == 1 ]; then echo -e "${BIGreen}>>>${RST} Fixing libs ..." mv "$openpype_root/build/OpenPype $openpype_version.app/Contents/MacOS/dependencies/cx_Freeze" "$openpype_root/build/OpenPype $openpype_version.app/Contents/MacOS/lib/" || { echo -e "${BIRed}!!!>${RST} ${BIYellow}Can't move cx_Freeze libs${RST}"; return 1; } + # force hide icon from Dock + defaults write "$openpype_root/build/OpenPype $openpype_version.app/Contents/Info" LSUIElement 1 # fix code signing issue echo -e "${BIGreen}>>>${RST} Fixing code signatures ...\c" From 0a0c374ee1fc874b4f5521a72cd5273b00082ec7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 13 Jun 2023 18:26:31 +0800 Subject: [PATCH 265/347] removing the node references successfully in the parameter --- openpype/hosts/max/api/plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 5eb4eaf391..6bf3756e72 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -72,13 +72,13 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" DeleteItem list_node.items idx ) ) - all_handles = join i_node_arr all_handles - list_node.items = join temp_arr list_node.items + all_handles = i_node_arr + list_node.items = temp_arr ) on OPparams open do ( - if all_handles.count != 0 do + if all_handles.count != 0 then ( temp_arr = #() for x in all_handles do From 15dd1e13b6e37dce8dbc8d1f5d0262ea3639f412 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 13 Jun 2023 19:00:37 +0800 Subject: [PATCH 266/347] removing the node references successfully in the parameter --- openpype/hosts/max/api/plugin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 6bf3756e72..3fa4fa55b1 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -56,6 +56,8 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" if current_selection == undefined then return False temp_arr = #() i_node_arr = #() + new_temp_arr = #() + new_i_node_arr = #() for c in current_selection do ( @@ -65,15 +67,17 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" if idx do ( DeleteItem all_nodes idx + for i in all_nodes do append new_i_node_arr i ) idx = finditem list_node.items handle_name if idx do ( DeleteItem list_node.items idx + for i in list_node.items do append new_temp_arr i ) ) - all_handles = i_node_arr - list_node.items = temp_arr + all_handles = new_i_node_arr + list_node.items = new_temp_arr ) on OPparams open do From fc4c4668d0dca05cd40c4b671a38f8ea8cd78d5b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 13 Jun 2023 20:50:24 +0800 Subject: [PATCH 267/347] removing the node references successfully in the parameter --- openpype/hosts/max/api/plugin.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 3fa4fa55b1..be40a765fb 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -56,7 +56,6 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" if current_selection == undefined then return False temp_arr = #() i_node_arr = #() - new_temp_arr = #() new_i_node_arr = #() for c in current_selection do @@ -73,11 +72,10 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" if idx do ( DeleteItem list_node.items idx - for i in list_node.items do append new_temp_arr i ) ) all_handles = new_i_node_arr - list_node.items = new_temp_arr + list_node.items = temp_arr ) on OPparams open do From 1c77dffc3d45e6ec7a8876b2f19eac572eb04cab Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 14 Jun 2023 03:25:23 +0000 Subject: [PATCH 268/347] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 300f5546ae..c44b1d29fb 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.11-nightly.1" +__version__ = "3.15.11-nightly.2" From fa9d348b321abfc4efa94065925055212dd8e8b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 14 Jun 2023 03:26:11 +0000 Subject: [PATCH 269/347] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0b1d0f5087..2339ec878f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.11-nightly.2 - 3.15.11-nightly.1 - 3.15.10 - 3.15.10-nightly.2 @@ -134,7 +135,6 @@ body: - 3.14.3-nightly.6 - 3.14.3-nightly.5 - 3.14.3-nightly.4 - - 3.14.3-nightly.3 validations: required: true - type: dropdown From bfde7cba51e99fc2de2cb15b831e9c4910c21680 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 14 Jun 2023 14:02:01 +0800 Subject: [PATCH 270/347] fix the bug of not deleting instance with delete button lasted in Openpype attribute --- openpype/hosts/max/api/plugin.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index be40a765fb..3427c85d98 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -57,25 +57,34 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" temp_arr = #() i_node_arr = #() new_i_node_arr = #() + new_temp_arr = #() for c in current_selection do ( - node_ref = NodeTransformMonitor node:c + node_ref = NodeTransformMonitor node:c as string handle_name = node_to_name c - idx = finditem all_handles node_ref + tmp_all_handles = #() + for i in all_handles do + ( + tmp = i as string + append tmp_all_handles tmp + ) + print all_handles + print node_ref + idx = finditem tmp_all_handles node_ref if idx do ( - DeleteItem all_nodes idx - for i in all_nodes do append new_i_node_arr i + new_i_node_arr = DeleteItem all_handles idx + ) idx = finditem list_node.items handle_name if idx do ( - DeleteItem list_node.items idx + new_temp_arr = DeleteItem list_node.items idx ) ) - all_handles = new_i_node_arr - list_node.items = temp_arr + all_handles = join i_node_arr new_i_node_arr + list_node.items = join temp_arr new_temp_arr ) on OPparams open do From cb92481676bd1f9f9720e2489e04eaf3b73a8065 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 14 Jun 2023 14:02:29 +0800 Subject: [PATCH 271/347] fix the docstring --- openpype/hosts/max/plugins/publish/collect_members.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/collect_members.py b/openpype/hosts/max/plugins/publish/collect_members.py index 0acb4f408d..812d82ff26 100644 --- a/openpype/hosts/max/plugins/publish/collect_members.py +++ b/openpype/hosts/max/plugins/publish/collect_members.py @@ -5,7 +5,7 @@ from pymxs import runtime as rt class CollectMembers(pyblish.api.InstancePlugin): - """Collect Render for Deadline.""" + """Collect Set Members.""" order = pyblish.api.CollectorOrder + 0.01 label = "Collect Instance Members" From b736c8c632157facc24cf9ac36bccfff8851d09a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 14 Jun 2023 10:06:45 +0200 Subject: [PATCH 272/347] :recycle: remove debug prints --- openpype/hosts/max/api/plugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 3427c85d98..4fc852e2fe 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -69,8 +69,6 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData" tmp = i as string append tmp_all_handles tmp ) - print all_handles - print node_ref idx = finditem tmp_all_handles node_ref if idx do ( From e769a76ac32ba4d8def490f0d2cca7c2420db284 Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Wed, 14 Jun 2023 09:12:08 -0500 Subject: [PATCH 273/347] Use StringTemplate to allow for optional template keys --- openpype/hosts/nuke/api/lib.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 860c42ebf3..39a0ffeac1 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -30,6 +30,7 @@ from openpype.lib import ( env_value_to_bool, Logger, get_version_from_path, + StringTemplate, ) from openpype.settings import ( @@ -1300,10 +1301,8 @@ def create_write_node( # build file path to workfiles fdir = str(anatomy_filled["work"]["folder"]).replace("\\", "/") - fpath = data["fpath_template"].format( - work=fdir, - **data, - ) + data["work"] = fdir + fpath = StringTemplate(data["fpath_template"]).format_strict(data) # create directory if not os.path.isdir(os.path.dirname(fpath)): From 09c5512e9177e30d65a3ca668cfdc05054ea12cc Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Wed, 14 Jun 2023 09:22:35 -0500 Subject: [PATCH 274/347] Skip empty values --- openpype/hosts/nuke/api/lib.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 39a0ffeac1..14294ed44d 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2060,6 +2060,9 @@ class WorkfileSettings(object): # it will be dict in value if isinstance(value_, dict): continue + # skip empty values + if not value_: + continue if self._root_node[knob].value() not in value_: self._root_node[knob].setValue(str(value_)) log.debug("nuke.root()['{}'] changed to: {}".format( From e128695c428d64b34085b992ca4427f4f84bc096 Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Wed, 14 Jun 2023 09:22:49 -0500 Subject: [PATCH 275/347] Fix typo --- openpype/hosts/nuke/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 14294ed44d..3098d5630a 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2163,7 +2163,7 @@ class WorkfileSettings(object): log.debug(changes) if changes: - msg = "Read nodes are not set to correct colospace:\n\n" + msg = "Read nodes are not set to correct colorspace:\n\n" for nname, knobs in changes.items(): msg += ( " - node: '{0}' is now '{1}' but should be '{2}'\n" From 37e17f3ba03d6b0c101492d9d9a7da41be5fedc9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 15 Jun 2023 14:52:41 +0800 Subject: [PATCH 276/347] bug fix the standin being not loaded when they are first loaded --- .../hosts/maya/plugins/load/load_arnold_standin.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 7c3a732389..dd2e8d0885 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -1,5 +1,6 @@ import os import clique +import time import maya.cmds as cmds @@ -35,9 +36,14 @@ class ArnoldStandinLoader(load.LoaderPlugin): color = "orange" def load(self, context, name, namespace, options): - - # Make sure to load arnold before importing `mtoa.ui.arnoldmenu` - cmds.loadPlugin("mtoa", quiet=True) + # Make sure user has loaded arnold before importing `mtoa.ui.arnoldmenu` + # and getting attribute from defaultArnoldRenderOption.operator + # Otherwises standins will not be loaded successfully for + # every first time using this loader after the build + if not cmds.pluginInfo("mtoa", query=True, loaded=True): + raise RuntimeError("Plugin 'mtoa' must be loaded" + " before using this loader") + # cmds.loadPlugin("mtoa", quiet=True) import mtoa.ui.arnoldmenu version = context['version'] From 9ba54ecace3a5554c737f83f489154a99d597a49 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 15 Jun 2023 15:09:40 +0800 Subject: [PATCH 277/347] hound fix --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index dd2e8d0885..f41afefcf4 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -1,6 +1,5 @@ import os import clique -import time import maya.cmds as cmds @@ -36,7 +35,8 @@ class ArnoldStandinLoader(load.LoaderPlugin): color = "orange" def load(self, context, name, namespace, options): - # Make sure user has loaded arnold before importing `mtoa.ui.arnoldmenu` + # Make sure user has loaded arnold + # before importing `mtoa.ui.arnoldmenu` # and getting attribute from defaultArnoldRenderOption.operator # Otherwises standins will not be loaded successfully for # every first time using this loader after the build From 59b7e265dab01adbd393f9aa934a699d64502aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Hector?= Date: Thu, 15 Jun 2023 10:13:49 +0200 Subject: [PATCH 278/347] add label to matching family (#5128) * add label to matching family * Update openpype/tools/creator/model.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --------- Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/creator/model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/creator/model.py b/openpype/tools/creator/model.py index 7bb2757a11..6e905d0b56 100644 --- a/openpype/tools/creator/model.py +++ b/openpype/tools/creator/model.py @@ -53,6 +53,9 @@ class CreatorsModel(QtGui.QStandardItemModel): index = self.index(row, 0) item_id = index.data(ITEM_ID_ROLE) creator_plugin = self._creators_by_id.get(item_id) - if creator_plugin and creator_plugin.family == family: + if creator_plugin and ( + creator_plugin.label.lower() == family.lower() + or creator_plugin.family.lower() == family.lower() + ): indexes.append(index) return indexes From 22296aeb6e8ae078cde738cea8f3c38d47c3a76b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:32:53 +0200 Subject: [PATCH 279/347] Pack project: Raise exception with reasonable message (#5145) * raise exception with reasonable message * raise errors at better places --- openpype/lib/project_backpack.py | 11 +++++++++++ openpype/pype_commands.py | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/openpype/lib/project_backpack.py b/openpype/lib/project_backpack.py index 674eaa3b91..55a96664d8 100644 --- a/openpype/lib/project_backpack.py +++ b/openpype/lib/project_backpack.py @@ -113,6 +113,12 @@ def pack_project( project_name )) + if only_documents and not destination_dir: + raise ValueError(( + "Destination directory must be defined" + " when only documents should be packed." + )) + root_path = None source_root = {} project_source_path = None @@ -141,6 +147,11 @@ def pack_project( if not destination_dir: destination_dir = root_path + if not destination_dir: + raise ValueError( + "Project {} does not have any roots.".format(project_name) + ) + destination_dir = os.path.normpath(destination_dir) if not os.path.exists(destination_dir): os.makedirs(destination_dir) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 6a24cb0ebc..56a0fe60cd 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -356,6 +356,13 @@ class PypeCommands: def pack_project(self, project_name, dirpath, database_only): from openpype.lib.project_backpack import pack_project + if database_only and not dirpath: + raise ValueError(( + "Destination dir must be defined when using --dbonly." + " Use '--dirpath {output dir path}' flag" + " to specify directory." + )) + pack_project(project_name, dirpath, database_only) def unpack_project(self, zip_filepath, new_root, database_only): From 359d6856442f8d9f3d992ec3af335d8b9aa300f2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Jun 2023 14:35:47 +0200 Subject: [PATCH 280/347] Draft to allow "inventory" actions to be supplied by a Module or Addon. --- openpype/modules/README.md | 3 ++- openpype/modules/base.py | 22 +++++++++++++++++++--- openpype/modules/interfaces.py | 23 +++++++++++++++++++---- openpype/pipeline/context_tools.py | 5 +++++ 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/openpype/modules/README.md b/openpype/modules/README.md index 86afdb9d91..ce3f99b338 100644 --- a/openpype/modules/README.md +++ b/openpype/modules/README.md @@ -138,7 +138,8 @@ class ClockifyModule( "publish": [], "create": [], "load": [], - "actions": [] + "actions": [], + "inventory": [] } ``` diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 732525b6eb..fb9b4e1096 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -740,15 +740,16 @@ class ModulesManager: Unknown keys are logged out. Returns: - dict: Output is dictionary with keys "publish", "create", "load" - and "actions" each containing list of paths. + dict: Output is dictionary with keys "publish", "create", "load", + "actions" and "inventory" each containing list of paths. """ # Output structure output = { "publish": [], "create": [], "load": [], - "actions": [] + "actions": [], + "inventory": [] } unknown_keys_by_module = {} for module in self.get_enabled_modules(): @@ -853,6 +854,21 @@ class ModulesManager: host_name ) + def collect_inventory_action_paths(self, host_name): + """Helper to collect load plugin paths from modules. + + Args: + host_name (str): For which host are load plugins meant. + + Returns: + list: List of pyblish plugin paths. + """ + + return self._collect_plugin_paths( + "get_inventory_action_paths", + host_name + ) + def get_host_module(self, host_name): """Find host module by host name. diff --git a/openpype/modules/interfaces.py b/openpype/modules/interfaces.py index 8c9a6ee1dd..40a42e9290 100644 --- a/openpype/modules/interfaces.py +++ b/openpype/modules/interfaces.py @@ -33,8 +33,8 @@ class OpenPypeInterface: class IPluginPaths(OpenPypeInterface): """Module has plugin paths to return. - Expected result is dictionary with keys "publish", "create", "load" or - "actions" and values as list or string. + Expected result is dictionary with keys "publish", "create", "load", + "actions" or "inventory" and values as list or string. { "publish": ["path/to/publish_plugins"] } @@ -109,6 +109,21 @@ class IPluginPaths(OpenPypeInterface): return self._get_plugin_paths_by_type("publish") + def get_inventory_action_paths(self, host_name): + """Receive inventory action paths. + + Give addons ability to add inventory action plugin paths. + + Notes: + Default implementation uses 'get_plugin_paths' and always return + all publish plugin paths. + + Args: + host_name (str): For which host are the plugins meant. + """ + + return self._get_plugin_paths_by_type("inventory") + class ILaunchHookPaths(OpenPypeInterface): """Module has launch hook paths to return. @@ -397,8 +412,8 @@ class ITrayService(ITrayModule): class ISettingsChangeListener(OpenPypeInterface): """Module has plugin paths to return. - Expected result is dictionary with keys "publish", "create", "load" or - "actions" and values as list or string. + Expected result is dictionary with keys "publish", "create", "load", + "actions" or "inventory" and values as list or string. { "publish": ["path/to/publish_plugins"] } diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py index ada78b989d..97a5c1ba69 100644 --- a/openpype/pipeline/context_tools.py +++ b/openpype/pipeline/context_tools.py @@ -181,6 +181,11 @@ def install_openpype_plugins(project_name=None, host_name=None): for path in load_plugin_paths: register_loader_plugin_path(path) + inventory_action_paths = modules_manager.collect_inventory_action_paths( + host_name) + for path in inventory_action_paths: + register_inventory_action_path(path) + if project_name is None: project_name = os.environ.get("AVALON_PROJECT") From b01424111aa579f95a77b3d0b39bace1e02a8a03 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 15 Jun 2023 21:25:41 +0800 Subject: [PATCH 281/347] roy's comment --- .../maya/plugins/load/load_arnold_standin.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index f41afefcf4..a729e0bb06 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -23,6 +23,20 @@ def is_sequence(files): return sequence +def post_process(): + import maya.utils + from qtpy import QtWidgets + + # I'm not sure this would work, but it'd be the simplest trick to try + cmds.refresh(force=True) + + # I suspect this might work + maya.utils.processIdleEvents() + + # I suspect this one might work too but it's might be a hard to track whether it solves all cases (and whether the events were already submitted to Qt at that time this command starts to run) So I'd always try to avoid this when possible. + QtWidgets.QApplication.instance().processEvents() + + class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" @@ -40,10 +54,13 @@ class ArnoldStandinLoader(load.LoaderPlugin): # and getting attribute from defaultArnoldRenderOption.operator # Otherwises standins will not be loaded successfully for # every first time using this loader after the build + """ if not cmds.pluginInfo("mtoa", query=True, loaded=True): raise RuntimeError("Plugin 'mtoa' must be loaded" " before using this loader") - # cmds.loadPlugin("mtoa", quiet=True) + """ + cmds.loadPlugin("mtoa", quiet=True) + post_process() import mtoa.ui.arnoldmenu version = context['version'] From 5bb476cfa439d168c4e5668c30bc7945f38713a7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 15 Jun 2023 21:27:38 +0800 Subject: [PATCH 282/347] hound fix --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index a729e0bb06..9016359c2f 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -27,13 +27,8 @@ def post_process(): import maya.utils from qtpy import QtWidgets - # I'm not sure this would work, but it'd be the simplest trick to try cmds.refresh(force=True) - - # I suspect this might work maya.utils.processIdleEvents() - - # I suspect this one might work too but it's might be a hard to track whether it solves all cases (and whether the events were already submitted to Qt at that time this command starts to run) So I'd always try to avoid this when possible. QtWidgets.QApplication.instance().processEvents() From d077491617d2c1f8b95b7c86568a88c16ff290b5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 15 Jun 2023 21:28:56 +0800 Subject: [PATCH 283/347] add docstring --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 9016359c2f..f45a070c85 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -24,6 +24,10 @@ def is_sequence(files): def post_process(): + """ + Make sure mtoa script finished loading + before the loader doing any action + """ import maya.utils from qtpy import QtWidgets From 0fe552a01485e52512139a3aa92eb62a513f6885 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 15 Jun 2023 15:35:06 +0200 Subject: [PATCH 284/347] Update invalid docstring --- openpype/modules/interfaces.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/modules/interfaces.py b/openpype/modules/interfaces.py index 40a42e9290..0d73bc35a3 100644 --- a/openpype/modules/interfaces.py +++ b/openpype/modules/interfaces.py @@ -410,13 +410,11 @@ class ITrayService(ITrayModule): class ISettingsChangeListener(OpenPypeInterface): - """Module has plugin paths to return. + """Module tries to listen to settings changes. + + Only settings changes in the current process are propagated. + Changes made in other processes or machines won't trigger the callbacks. - Expected result is dictionary with keys "publish", "create", "load", - "actions" or "inventory" and values as list or string. - { - "publish": ["path/to/publish_plugins"] - } """ @abstractmethod From 6087b27c32d46c0e8c67cd133f031305eb1ae54f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:05:59 +0200 Subject: [PATCH 285/347] Ftrack: Task status during publishing (#5123) * ftrack status is not set in deadline plugin during celaction integration * implemented new logic to set task status during publishing * added missing settings for new plugins * integrate hierarchy ftrack is filling status names for newly created tasks * resaved default settings * change label in settings * Remove leftover docstring Co-authored-by: Roy Nieterau * Use smaller differentiation in order to keep plugin in integration range * formatting changes --------- Co-authored-by: Roy Nieterau --- .../publish/submit_celaction_deadline.py | 1 - .../plugins/publish/integrate_ftrack_api.py | 41 -- .../publish/integrate_ftrack_farm_status.py | 150 ------ .../publish/integrate_ftrack_status.py | 433 ++++++++++++++++++ .../publish/integrate_hierarchy_ftrack.py | 26 +- .../defaults/project_settings/ftrack.json | 24 +- .../schema_project_ftrack.json | 140 +++++- 7 files changed, 609 insertions(+), 206 deletions(-) delete mode 100644 openpype/modules/ftrack/plugins/publish/integrate_ftrack_farm_status.py create mode 100644 openpype/modules/ftrack/plugins/publish/integrate_ftrack_status.py diff --git a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py index bcf0850768..ee28612b44 100644 --- a/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -59,7 +59,6 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): render_path).replace("\\", "/") instance.data["publishJobState"] = "Suspended" - instance.context.data['ftrackStatus'] = "Render" # adding 2d render specific family for version identification in Loader instance.data["families"] = ["render2d"] diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py index cec48ef54f..deb8b414f0 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py @@ -109,8 +109,6 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): for status in asset_version_statuses } - self._set_task_status(instance, project_entity, task_entity, session) - # Prepare AssetTypes asset_types_by_short = self._ensure_asset_types_exists( session, component_list @@ -180,45 +178,6 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): if asset_version not in instance.data[asset_versions_key]: instance.data[asset_versions_key].append(asset_version) - def _set_task_status(self, instance, project_entity, task_entity, session): - if not project_entity: - self.log.info("Task status won't be set, project is not known.") - return - - if not task_entity: - self.log.info("Task status won't be set, task is not known.") - return - - status_name = instance.context.data.get("ftrackStatus") - if not status_name: - self.log.info("Ftrack status name is not set.") - return - - self.log.debug( - "Ftrack status name will be (maybe) set to \"{}\"".format( - status_name - ) - ) - - project_schema = project_entity["project_schema"] - task_statuses = project_schema.get_statuses( - "Task", task_entity["type_id"] - ) - task_statuses_by_low_name = { - status["name"].lower(): status for status in task_statuses - } - status = task_statuses_by_low_name.get(status_name.lower()) - if not status: - self.log.warning(( - "Task status \"{}\" won't be set," - " status is now allowed on task type \"{}\"." - ).format(status_name, task_entity["type"]["name"])) - return - - self.log.info("Setting task status to \"{}\"".format(status_name)) - task_entity["status"] = status - session.commit() - def _fill_component_locations(self, session, component_list): components_by_location_name = collections.defaultdict(list) components_by_location_id = collections.defaultdict(list) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_farm_status.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_farm_status.py deleted file mode 100644 index ab5738c33f..0000000000 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_farm_status.py +++ /dev/null @@ -1,150 +0,0 @@ -import pyblish.api -from openpype.lib import filter_profiles - - -class IntegrateFtrackFarmStatus(pyblish.api.ContextPlugin): - """Change task status when should be published on farm. - - Instance which has set "farm" key in data to 'True' is considered as will - be rendered on farm thus it's status should be changed. - """ - - order = pyblish.api.IntegratorOrder + 0.48 - label = "Integrate Ftrack Farm Status" - - farm_status_profiles = [] - - def process(self, context): - # Quick end - if not self.farm_status_profiles: - project_name = context.data["projectName"] - self.log.info(( - "Status profiles are not filled for project \"{}\". Skipping" - ).format(project_name)) - return - - filtered_instances = self.filter_instances(context) - instances_with_status_names = self.get_instances_with_statuse_names( - context, filtered_instances - ) - if instances_with_status_names: - self.fill_statuses(context, instances_with_status_names) - - def filter_instances(self, context): - filtered_instances = [] - for instance in context: - # Skip disabled instances - if instance.data.get("publish") is False: - continue - subset_name = instance.data["subset"] - msg_start = "Skipping instance {}.".format(subset_name) - if not instance.data.get("farm"): - self.log.debug( - "{} Won't be rendered on farm.".format(msg_start) - ) - continue - - task_entity = instance.data.get("ftrackTask") - if not task_entity: - self.log.debug( - "{} Does not have filled task".format(msg_start) - ) - continue - - filtered_instances.append(instance) - return filtered_instances - - def get_instances_with_statuse_names(self, context, instances): - instances_with_status_names = [] - for instance in instances: - family = instance.data["family"] - subset_name = instance.data["subset"] - task_entity = instance.data["ftrackTask"] - host_name = context.data["hostName"] - task_name = task_entity["name"] - task_type = task_entity["type"]["name"] - status_profile = filter_profiles( - self.farm_status_profiles, - { - "hosts": host_name, - "task_types": task_type, - "task_names": task_name, - "families": family, - "subsets": subset_name, - }, - logger=self.log - ) - if not status_profile: - # There already is log in 'filter_profiles' - continue - - status_name = status_profile["status_name"] - if status_name: - instances_with_status_names.append((instance, status_name)) - return instances_with_status_names - - def fill_statuses(self, context, instances_with_status_names): - # Prepare available task statuses on the project - project_name = context.data["projectName"] - session = context.data["ftrackSession"] - project_entity = session.query(( - "select project_schema from Project where full_name is \"{}\"" - ).format(project_name)).one() - project_schema = project_entity["project_schema"] - - task_type_ids = set() - for item in instances_with_status_names: - instance, _ = item - task_entity = instance.data["ftrackTask"] - task_type_ids.add(task_entity["type"]["id"]) - - task_statuses_by_type_id = { - task_type_id: project_schema.get_statuses("Task", task_type_id) - for task_type_id in task_type_ids - } - - # Keep track if anything has changed - skipped_status_names = set() - status_changed = False - for item in instances_with_status_names: - instance, status_name = item - task_entity = instance.data["ftrackTask"] - task_statuses = task_statuses_by_type_id[task_entity["type"]["id"]] - status_name_low = status_name.lower() - - status_id = None - status_name = None - # Skip if status name was already tried to be found - for status in task_statuses: - if status["name"].lower() == status_name_low: - status_id = status["id"] - status_name = status["name"] - break - - if status_id is None: - if status_name_low not in skipped_status_names: - skipped_status_names.add(status_name_low) - joined_status_names = ", ".join({ - '"{}"'.format(status["name"]) - for status in task_statuses - }) - self.log.warning(( - "Status \"{}\" is not available on project \"{}\"." - " Available statuses are {}" - ).format(status_name, project_name, joined_status_names)) - continue - - # Change task status id - if status_id != task_entity["status_id"]: - task_entity["status_id"] = status_id - status_changed = True - path = "/".join([ - item["name"] - for item in task_entity["link"] - ]) - self.log.debug("Set status \"{}\" to \"{}\"".format( - status_name, path - )) - - if status_changed: - session.commit() diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_status.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_status.py new file mode 100644 index 0000000000..e862dba7fc --- /dev/null +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_status.py @@ -0,0 +1,433 @@ +import copy + +import pyblish.api +from openpype.lib import filter_profiles + + +def create_chunks(iterable, chunk_size=None): + """Separate iterable into multiple chunks by size. + + Args: + iterable(list|tuple|set): Object that will be separated into chunks. + chunk_size(int): Size of one chunk. Default value is 200. + + Returns: + list: Chunked items. + """ + chunks = [] + + tupled_iterable = tuple(iterable) + if not tupled_iterable: + return chunks + iterable_size = len(tupled_iterable) + if chunk_size is None: + chunk_size = 200 + + if chunk_size < 1: + chunk_size = 1 + + for idx in range(0, iterable_size, chunk_size): + chunks.append(tupled_iterable[idx:idx + chunk_size]) + return chunks + + +class CollectFtrackTaskStatuses(pyblish.api.ContextPlugin): + """Collect available task statuses on the project. + + This is preparation for integration of task statuses. + + Requirements: + ftrackSession (ftrack_api.Session): Prepared ftrack session. + + Provides: + ftrackTaskStatuses (dict[str, list[Any]]): Dictionary of available + task statuses on project by task type id. + ftrackStatusByTaskId (dict[str, str]): Empty dictionary of task + statuses by task id. Status on task can be set only once. + Value should be a name of status. + """ + + # After 'CollectFtrackApi' + order = pyblish.api.CollectorOrder + 0.4992 + label = "Collect Ftrack Task Statuses" + settings_category = "ftrack" + + def process(self, context): + ftrack_session = context.data("ftrackSession") + if ftrack_session is None: + self.log.info("Ftrack session is not created.") + return + + # Prepare available task statuses on the project + project_name = context.data["projectName"] + project_entity = ftrack_session.query(( + "select project_schema from Project where full_name is \"{}\"" + ).format(project_name)).one() + project_schema = project_entity["project_schema"] + + task_type_ids = { + task_type["id"] + for task_type in ftrack_session.query("select id from Type").all() + } + task_statuses_by_type_id = { + task_type_id: project_schema.get_statuses("Task", task_type_id) + for task_type_id in task_type_ids + } + context.data["ftrackTaskStatuses"] = task_statuses_by_type_id + context.data["ftrackStatusByTaskId"] = {} + self.log.info("Collected ftrack task statuses.") + + +class IntegrateFtrackStatusBase(pyblish.api.InstancePlugin): + """Base plugin for status collection. + + Requirements: + projectName (str): Name of the project. + hostName (str): Name of the host. + ftrackSession (ftrack_api.Session): Prepared ftrack session. + ftrackTaskStatuses (dict[str, list[Any]]): Dictionary of available + task statuses on project by task type id. + ftrackStatusByTaskId (dict[str, str]): Empty dictionary of task + statuses by task id. Status on task can be set only once. + Value should be a name of status. + """ + + active = False + settings_key = None + status_profiles = [] + + @classmethod + def apply_settings(cls, project_settings): + settings_key = cls.settings_key + if settings_key is None: + settings_key = cls.__name__ + + try: + settings = project_settings["ftrack"]["publish"][settings_key] + except KeyError: + return + + for key, value in settings.items(): + setattr(cls, key, value) + + def process(self, instance): + context = instance.context + # No profiles -> skip + profiles = self.get_status_profiles() + if not profiles: + project_name = context.data["projectName"] + self.log.info(( + "Status profiles are not filled for project \"{}\". Skipping" + ).format(project_name)) + return + + # Task statuses were not collected -> skip + task_statuses_by_type_id = context.data.get("ftrackTaskStatuses") + if not task_statuses_by_type_id: + self.log.info( + "Ftrack task statuses are not collected. Skipping.") + return + + self.prepare_status_names(context, instance, profiles) + + def get_status_profiles(self): + """List of profiles to determine status name. + + Example profile item: + { + "host_names": ["nuke"], + "task_types": ["Compositing"], + "task_names": ["Comp"], + "families": ["render"], + "subset_names": ["renderComp"], + "status_name": "Rendering", + } + + Returns: + list[dict[str, Any]]: List of profiles. + """ + + return self.status_profiles + + def prepare_status_names(self, context, instance, profiles): + if not self.is_valid_instance(context, instance): + return + + filter_data = self.get_profile_filter_data(context, instance) + status_profile = filter_profiles( + profiles, + filter_data, + logger=self.log + ) + if not status_profile: + return + + status_name = status_profile["status_name"] + if status_name: + self.fill_status(context, instance, status_name) + + def get_profile_filter_data(self, context, instance): + task_entity = instance.data["ftrackTask"] + return { + "host_names": context.data["hostName"], + "task_types": task_entity["type"]["name"], + "task_names": task_entity["name"], + "families": instance.data["family"], + "subset_names": instance.data["subset"], + } + + def is_valid_instance(self, context, instance): + """Filter instances that should be processed. + + Ignore instances that are not enabled for publishing or don't have + filled task. Also skip instances with tasks that already have defined + status. + + Plugin should do more filtering which is custom for plugin logic. + + Args: + context (pyblish.api.Context): Pyblish context. + instance (pyblish.api.Instance): Instance to process. + + Returns: + list[pyblish.api.Instance]: List of instances that should be + processed. + """ + + ftrack_status_by_task_id = context.data["ftrackStatusByTaskId"] + # Skip disabled instances + if instance.data.get("publish") is False: + return False + + task_entity = instance.data.get("ftrackTask") + if not task_entity: + self.log.debug( + "Skipping instance Does not have filled task".format( + instance.data["subset"])) + return False + + task_id = task_entity["id"] + if task_id in ftrack_status_by_task_id: + self.log.debug("Status for task {} was already defined".format( + task_entity["name"] + )) + return False + + return True + + def fill_status(self, context, instance, status_name): + """Fill status for instance task. + + If task already had set status, it will be skipped. + + Args: + context (pyblish.api.Context): Pyblish context. + instance (pyblish.api.Instance): Pyblish instance. + status_name (str): Name of status to set. + """ + + task_entity = instance.data["ftrackTask"] + task_id = task_entity["id"] + ftrack_status_by_task_id = context.data["ftrackStatusByTaskId"] + if task_id in ftrack_status_by_task_id: + self.log.debug("Status for task {} was already defined".format( + task_entity["name"] + )) + return + + ftrack_status_by_task_id[task_id] = status_name + self.log.info(( + "Task {} will be set to \"{}\" status." + ).format(task_entity["name"], status_name)) + + +class IntegrateFtrackFarmStatus(IntegrateFtrackStatusBase): + """Collect task status names for instances that are sent to farm. + + Instance which has set "farm" key in data to 'True' is considered as will + be rendered on farm thus it's status should be changed. + + Requirements: + projectName (str): Name of the project. + hostName (str): Name of the host. + ftrackSession (ftrack_api.Session): Prepared ftrack session. + ftrackTaskStatuses (dict[str, list[Any]]): Dictionary of available + task statuses on project by task type id. + ftrackStatusByTaskId (dict[str, str]): Empty dictionary of task + statuses by task id. Status on task can be set only once. + Value should be a name of status. + """ + + order = pyblish.api.IntegratorOrder + 0.48 + label = "Ftrack Task Status To Farm Status" + active = True + + farm_status_profiles = [] + status_profiles = None + + def is_valid_instance(self, context, instance): + if not instance.data.get("farm"): + self.log.debug("{} Won't be rendered on farm.".format( + instance.data["subset"] + )) + return False + return super(IntegrateFtrackFarmStatus, self).is_valid_instance( + context, instance) + + def get_status_profiles(self): + if self.status_profiles is None: + profiles = copy.deepcopy(self.farm_status_profiles) + for profile in profiles: + profile["host_names"] = profile.pop("hosts") + profile["subset_names"] = profile.pop("subsets") + self.status_profiles = profiles + return self.status_profiles + + +class IntegrateFtrackLocalStatus(IntegrateFtrackStatusBase): + """Collect task status names for instances that are published locally. + + Instance which has set "farm" key in data to 'True' is considered as will + be rendered on farm thus it's status should be changed. + + Requirements: + projectName (str): Name of the project. + hostName (str): Name of the host. + ftrackSession (ftrack_api.Session): Prepared ftrack session. + ftrackTaskStatuses (dict[str, list[Any]]): Dictionary of available + task statuses on project by task type id. + ftrackStatusByTaskId (dict[str, str]): Empty dictionary of task + statuses by task id. Status on task can be set only once. + Value should be a name of status. + """ + + order = IntegrateFtrackFarmStatus.order + 0.001 + label = "Ftrack Task Status Local Publish" + active = True + targets = ["local"] + settings_key = "ftrack_task_status_local_publish" + + def is_valid_instance(self, context, instance): + if instance.data.get("farm"): + self.log.debug("{} Will be rendered on farm.".format( + instance.data["subset"] + )) + return False + return super(IntegrateFtrackLocalStatus, self).is_valid_instance( + context, instance) + + +class IntegrateFtrackOnFarmStatus(IntegrateFtrackStatusBase): + """Collect task status names for instances that are published on farm. + + Requirements: + projectName (str): Name of the project. + hostName (str): Name of the host. + ftrackSession (ftrack_api.Session): Prepared ftrack session. + ftrackTaskStatuses (dict[str, list[Any]]): Dictionary of available + task statuses on project by task type id. + ftrackStatusByTaskId (dict[str, str]): Empty dictionary of task + statuses by task id. Status on task can be set only once. + Value should be a name of status. + """ + + order = IntegrateFtrackLocalStatus.order + 0.001 + label = "Ftrack Task Status On Farm Status" + active = True + targets = ["farm"] + settings_key = "ftrack_task_status_on_farm_publish" + + +class IntegrateFtrackTaskStatus(pyblish.api.ContextPlugin): + # Use order of Integrate Ftrack Api plugin and offset it before or after + base_order = pyblish.api.IntegratorOrder + 0.499 + # By default is after Integrate Ftrack Api + order = base_order + 0.0001 + label = "Integrate Ftrack Task Status" + + @classmethod + def apply_settings(cls, project_settings): + """Apply project settings to plugin. + + Args: + project_settings (dict[str, Any]): Project settings. + """ + + settings = ( + project_settings["ftrack"]["publish"]["IntegrateFtrackTaskStatus"] + ) + diff = 0.001 + if not settings["after_version_statuses"]: + diff = -diff + cls.order = cls.base_order + diff + + def process(self, context): + task_statuses_by_type_id = context.data.get("ftrackTaskStatuses") + if not task_statuses_by_type_id: + self.log.info("Ftrack task statuses are not collected. Skipping.") + return + + status_by_task_id = self._get_status_by_task_id(context) + if not status_by_task_id: + self.log.info("No statuses to set. Skipping.") + return + + ftrack_session = context.data["ftrackSession"] + + task_entities = self._get_task_entities( + ftrack_session, status_by_task_id) + + for task_entity in task_entities: + task_path = "/".join([ + item["name"] for item in task_entity["link"] + ]) + task_id = task_entity["id"] + type_id = task_entity["type_id"] + new_status = None + status_name = status_by_task_id[task_id] + self.log.debug( + "Status to set {} on task {}.".format(status_name, task_path)) + status_name_low = status_name.lower() + available_statuses = task_statuses_by_type_id[type_id] + for status in available_statuses: + if status["name"].lower() == status_name_low: + new_status = status + break + + if new_status is None: + joined_statuses = ", ".join([ + "'{}'".format(status["name"]) + for status in available_statuses + ]) + self.log.debug(( + "Status '{}' was not found in available statuses: {}." + ).format(status_name, joined_statuses)) + continue + + if task_entity["status_id"] != new_status["id"]: + task_entity["status_id"] = new_status["id"] + + self.log.debug("Changing status of task '{}' to '{}'".format( + task_path, status_name + )) + ftrack_session.commit() + + def _get_status_by_task_id(self, context): + status_by_task_id = context.data["ftrackStatusByTaskId"] + return { + task_id: status_name + for task_id, status_name in status_by_task_id.items() + if status_name + } + + def _get_task_entities(self, ftrack_session, status_by_task_id): + task_entities = [] + for chunk_ids in create_chunks(status_by_task_id.keys()): + joined_ids = ",".join( + ['"{}"'.format(task_id) for task_id in chunk_ids] + ) + task_entities.extend(ftrack_session.query(( + "select id, type_id, status_id, link from Task" + " where id in ({})" + ).format(joined_ids)).all()) + return task_entities diff --git a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index 6daaea5f18..a1aa7c0daa 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -63,7 +63,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): """ order = pyblish.api.IntegratorOrder - 0.04 - label = 'Integrate Hierarchy To Ftrack' + label = "Integrate Hierarchy To Ftrack" families = ["shot"] hosts = [ "hiero", @@ -94,14 +94,13 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): "Project \"{}\" was not found on ftrack.".format(project_name) ) - self.context = context self.session = session self.ft_project = project self.task_types = self.get_all_task_types(project) self.task_statuses = self.get_task_statuses(project) # import ftrack hierarchy - self.import_to_ftrack(project_name, hierarchy_context) + self.import_to_ftrack(context, project_name, hierarchy_context) def query_ftrack_entitites(self, session, ft_project): project_id = ft_project["id"] @@ -227,7 +226,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): return output - def import_to_ftrack(self, project_name, hierarchy_context): + def import_to_ftrack(self, context, project_name, hierarchy_context): # Prequery hiearchical custom attributes hier_attrs = get_pype_attr(self.session)[1] hier_attr_by_key = { @@ -258,7 +257,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): self.session, matching_entities, hier_attrs) # Get ftrack api module (as they are different per python version) - ftrack_api = self.context.data["ftrackPythonModule"] + ftrack_api = context.data["ftrackPythonModule"] # Use queue of hierarchy items to process import_queue = collections.deque() @@ -292,7 +291,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): # CUSTOM ATTRIBUTES custom_attributes = entity_data.get('custom_attributes', {}) instances = [] - for instance in self.context: + for instance in context: instance_asset_name = instance.data.get("asset") if ( instance_asset_name @@ -369,6 +368,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): if task_name: instances_by_task_name[task_name.lower()].append(instance) + ftrack_status_by_task_id = context.data["ftrackStatusByTaskId"] tasks = entity_data.get('tasks', []) existing_tasks = [] tasks_to_create = [] @@ -389,11 +389,11 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): for task_name, task_type in tasks_to_create: task_entity = self.create_task( - name=task_name, - task_type=task_type, - parent=entity + task_name, + task_type, + entity, + ftrack_status_by_task_id ) - for instance in instances_by_task_name[task_name.lower()]: instance.data["ftrackTask"] = task_entity @@ -481,7 +481,7 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): for status in task_workflow_statuses } - def create_task(self, name, task_type, parent): + def create_task(self, name, task_type, parent, ftrack_status_by_task_id): filter_data = { "task_names": name, "task_types": task_type @@ -491,12 +491,14 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): filter_data ) status_id = None + status_name = None if profile: status_name = profile["status_name"] status_name_low = status_name.lower() for _status_id, status in self.task_statuses.items(): if status["name"].lower() == status_name_low: status_id = _status_id + status_name = status["name"] break if status_id is None: @@ -523,6 +525,8 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): self.session._configure_locations() six.reraise(tp, value, tb) + if status_id is not None: + ftrack_status_by_task_id[task["id"]] = None return task def _get_active_assets(self, context): diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 4ca4a35d1f..b87c45666d 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -493,7 +493,29 @@ "upload_reviewable_with_origin_name": false }, "IntegrateFtrackFarmStatus": { - "farm_status_profiles": [] + "farm_status_profiles": [ + { + "hosts": [ + "celaction" + ], + "task_types": [], + "task_names": [], + "families": [ + "render" + ], + "subsets": [], + "status_name": "Render" + } + ] + }, + "ftrack_task_status_local_publish": { + "status_profiles": [] + }, + "ftrack_task_status_on_farm_publish": { + "status_profiles": [] + }, + "IntegrateFtrackTaskStatus": { + "after_version_statuses": true } } } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json index 7050721742..157a8d297e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json @@ -1058,7 +1058,7 @@ { "type": "dict", "key": "IntegrateFtrackFarmStatus", - "label": "Integrate Ftrack Farm Status", + "label": "Ftrack Status To Farm", "children": [ { "type": "label", @@ -1068,7 +1068,7 @@ "type": "list", "collapsible": true, "key": "farm_status_profiles", - "label": "Farm status profiles", + "label": "Profiles", "use_label_wrap": true, "object_type": { "type": "dict", @@ -1114,6 +1114,142 @@ } } ] + }, + { + "type": "dict", + "key": "ftrack_task_status_local_publish", + "label": "Ftrack Status Local Integration", + "children": [ + { + "type": "label", + "label": "Change status of task when is integrated locally" + }, + { + "type": "list", + "collapsible": true, + "key": "status_profiles", + "label": "Profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "host_names", + "label": "Host names", + "type": "hosts-enum", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "subset_names", + "label": "Subset names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "key": "status_name", + "label": "Status name", + "type": "text" + } + ] + } + } + ] + }, + { + "type": "dict", + "key": "ftrack_task_status_on_farm_publish", + "label": "Ftrack Status On Farm", + "children": [ + { + "type": "label", + "label": "Change status of task when it's subset is integrated on farm" + }, + { + "type": "list", + "collapsible": true, + "key": "status_profiles", + "label": "Profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "host_names", + "label": "Host names", + "type": "hosts-enum", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "subset_names", + "label": "Subset names", + "type": "list", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "key": "status_name", + "label": "Status name", + "type": "text" + } + ] + } + } + ] + }, + { + "type": "dict", + "key": "IntegrateFtrackTaskStatus", + "label": "Integrate Ftrack Task Status", + "children": [ + { + "type": "label", + "label": "Apply collected task statuses. This plugin can run before or after version integration. Some status automations may conflict with status changes on versions because of wrong order." + }, + { + "type": "boolean", + "key": "after_version_statuses", + "label": "After version integration" + } + ] } ] } From 2f95aab31efdd90dbdf0b899059d87416190e329 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 15 Jun 2023 22:31:55 +0800 Subject: [PATCH 286/347] clean up the code --- .../maya/plugins/load/load_arnold_standin.py | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index f45a070c85..1a582647cc 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -2,6 +2,7 @@ import os import clique import maya.cmds as cmds +import maya.utils from openpype.settings import get_project_settings from openpype.pipeline import ( @@ -23,19 +24,6 @@ def is_sequence(files): return sequence -def post_process(): - """ - Make sure mtoa script finished loading - before the loader doing any action - """ - import maya.utils - from qtpy import QtWidgets - - cmds.refresh(force=True) - maya.utils.processIdleEvents() - QtWidgets.QApplication.instance().processEvents() - - class ArnoldStandinLoader(load.LoaderPlugin): """Load as Arnold standin""" @@ -48,18 +36,15 @@ class ArnoldStandinLoader(load.LoaderPlugin): color = "orange" def load(self, context, name, namespace, options): - # Make sure user has loaded arnold - # before importing `mtoa.ui.arnoldmenu` - # and getting attribute from defaultArnoldRenderOption.operator - # Otherwises standins will not be loaded successfully for - # every first time using this loader after the build - """ if not cmds.pluginInfo("mtoa", query=True, loaded=True): - raise RuntimeError("Plugin 'mtoa' must be loaded" - " before using this loader") - """ - cmds.loadPlugin("mtoa", quiet=True) - post_process() + # Allow mtoa plugin load to process all its events + # because otherwise `defaultArnoldRenderOptions.operator` + # does not exist yet and some connections to the standin + # can't be correctly generated on create resulting in an error + cmds.loadPlugin("mtoa") + cmds.refresh(force=True) + maya.utils.processIdleEvents() + import mtoa.ui.arnoldmenu version = context['version'] From f9a64192b37d5c474d2be7803d6f0242415e7539 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 16 Jun 2023 10:50:38 +0200 Subject: [PATCH 287/347] fix match check of save sequence (#5148) --- openpype/tools/publisher/window.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 006098cb37..2bda0c1cfe 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -453,7 +453,11 @@ class PublisherWindow(QtWidgets.QDialog): return save_match = event.matches(QtGui.QKeySequence.Save) - if save_match == QtGui.QKeySequence.ExactMatch: + # PySide2 and PySide6 support + if not isinstance(save_match, bool): + save_match = save_match == QtGui.QKeySequence.ExactMatch + + if save_match: if not self._controller.publish_has_started: self._save_changes(True) event.accept() From 97faf90463773c8d6e8ed8323133df18953cf3b1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Jun 2023 17:01:47 +0800 Subject: [PATCH 288/347] bug fix arnoldExportAss unable to export selected set members --- .../plugins/publish/collect_arnold_scene_source.py | 9 ++++++++- .../plugins/publish/extract_arnold_scene_source.py | 11 +++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index f160a3a0c5..1bacd80f11 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -25,11 +25,18 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): members = cmds.ls(members, long=True) children = get_all_children(members) instance.data["contentMembers"] = children + # TODO: include another instance.data for content members + instance.data["contentMembersTransform"] = members self.log.debug("content members: {}".format(children)) + self.log.debug("content members Transform : {}".format(members)) elif objset.endswith("proxy_SET"): - set_members = get_all_children(cmds.ls(members, long=True)) + proxy_members = cmds.ls(members, long=True) + set_members = get_all_children(proxy_members) instance.data["proxy"] = set_members + instance.data["proxyTransform"] = proxy_members + # TODO: include another instance.data for proxy self.log.debug("proxy members: {}".format(set_members)) + self.log.debug("proxy members Transform: {}".format(proxy_members)) # Use camera in object set if present else default to render globals # camera. diff --git a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py index 14bcc71da6..9ed475e20f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -69,9 +69,10 @@ class ExtractArnoldSceneSource(publish.Extractor): "camera": instance.data["camera"], "mask": mask } - + # TODO: dont use instance.data["contentMembers"] + # but use the new instance.data["contentMemberTransforms"] filenames, nodes_by_id = self._extract( - instance.data["contentMembers"], attribute_data, kwargs + instance.data["contentMembersTransform"], attribute_data, kwargs ) if "representations" not in instance.data: @@ -109,8 +110,10 @@ class ExtractArnoldSceneSource(publish.Extractor): return kwargs["filename"] = file_path.replace(".ass", "_proxy.ass") + # TODO: dont use instance.data["proxy"] + # but use the new instance.data["proxyTransforms"] filenames, _ = self._extract( - instance.data["proxy"], attribute_data, kwargs + instance.data["proxyTransform"], attribute_data, kwargs ) representation = { @@ -187,7 +190,7 @@ class ExtractArnoldSceneSource(publish.Extractor): self.log.info( "Extracting ass sequence with: {}".format(kwargs) ) - + # arnoldExportAss -selected needs an active selection or a list of objects exported_files = cmds.arnoldExportAss(**kwargs) for file in exported_files: From e10b228be2a460f7a606dba19044bfed1459f56d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Jun 2023 17:07:56 +0800 Subject: [PATCH 289/347] hound fix --- igniter/.ass | 212 ++++++++++++++++++ .../publish/collect_arnold_scene_source.py | 6 +- .../publish/extract_arnold_scene_source.py | 2 +- 3 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 igniter/.ass diff --git a/igniter/.ass b/igniter/.ass new file mode 100644 index 0000000000..599a76d732 --- /dev/null +++ b/igniter/.ass @@ -0,0 +1,212 @@ +### exported: Thu Jun 15 17:07:53 2023 +### from: Arnold 7.1.3.2 [2a385cac] windows clang-10.0.1 oiio-2.4.1 osl-1.12.0 vdb-7.1.1 adlsdk-6.3.1.44 clmhub-2.0.0.235 rlm-14.1.3 optix-6.6.0 2022/09/12 08:50:17 +### host app: MtoA 5.2.1.1 6a048927 (fix-5.2.1) Maya 2023 +### bounds: -1.207354 -0.269835 -1.207354 1.207354 2.144874 1.207354 +### user: Kayla +### render_layer: defaultRenderLayer +### scene: D:/dummy_test/test_Project/test/work/test_v002/test_project_test_test_v002_v070.mb +### meters_per_unit: 0.010000 + + + +options +{ + AA_samples 3 + outputs 4 1 STRING + "RGBA RGBA defaultArnoldFilter/gaussian_filter defaultArnoldDriver/driver_exr.RGBA" + "P VECTOR aiAOVFilter1/closest_filter defaultArnoldDriver/driver_exr.RGBA" + "Pref RGB aiAOVFilter2/closest_filter defaultArnoldDriver/driver_exr.RGBA" + "albedo RGB defaultArnoldFilter/gaussian_filter defaultArnoldDriver/driver_exr.RGBA" + xres 1920 + yres 1080 + texture_per_file_stats on + texture_searchpath "[OPENPYPE_PROJECT_ROOT_WORK];D:/dummy_test/test_Project/test/work/test_v002/sourceimages" + texture_automip off + camera "/camera1/cameraShape1" + color_manager "defaultColorMgtGlobals" + operator "aiMerge1" + meters_per_unit 0.00999999978 + frame 1 + fps 25 + procedural_searchpath "[OPENPYPE_PROJECT_ROOT_WORK];D:/dummy_test/test_Project/test/work/test_v002/" + GI_diffuse_depth 1 + GI_specular_depth 1 + GI_transmission_depth 8 + declare render_layer constant STRING + render_layer "defaultRenderLayer" +} + +gaussian_filter +{ + name defaultArnoldFilter/gaussian_filter +} + +driver_exr +{ + name defaultArnoldDriver/driver_exr.RGBA + filename "D:/dummy_test/test_Project/test/work/test_v002/renders/test_project_test_test_v002_v070/masterLayer/masterLayer.exr" + color_space "" +} + +closest_filter +{ + name aiAOVFilter1/closest_filter +} + +closest_filter +{ + name aiAOVFilter2/closest_filter +} + +merge +{ + name aiMerge1 + inputs "test_01_:string_replace_operator" +} + +string_replace +{ + name test_01_:string_replace_operator + selection "*.(@node=='alembic')" + match "resources/test_project_test_modelMain_v067_proxy.abc" + replace "test_project_test_modelMain_v067.abc" +} + +color_manager_ocio +{ + name defaultColorMgtGlobals + config "D:/maya_config/OCIO-configs/Maya2022-default/config.ocio" + color_space_linear "ACEScg" +} + +polymesh +{ + name /proxy/pSphere/pSphereShape + visibility 255 + sidedness 255 + matrix + 2.41470838 0 0 0 + 0 2.41470838 0 0 + 0 0 2.41470838 0 + 0 0.937519372 0 1 + shader "lambert1" + id 2564139679 + nsides 6 1 UINT +4 4 4 4 4 4 + vidxs 24 1 b85UINT +B$ZuK*%:$$-?2$vMr4%:&UObB$w/J=(3BP? + vlist 8 1 b85VECTOR +aDq99aDq9989+]c89+]caDq9989+]caDq99!89+]c$$$$)aDq9989+]caDq9989+]c89+]c!aDq99$$$$(89+]caDq99aDq99 + nlist 24 1 b85VECTOR +zzyzzyzzyzzyzyzzyzzyzzy!$$$$$$$$$'aRT=dzzaRT=dzzaRT=dzzaRT=dzaRT=dzzaRT=dzzaRT=dzzaRT=dzyzzyzzyzzyzzaRT=dzzaRT=dzzaRT=dzzaRT=dzz + uvlist 14 1 b85VECTOR2 +82:0xz80l$K@wi$mMpJ$pnKs$PHvV$PKhS$rXa4$v$<]$USg@$UVY=$wcQs%&/-G$Z^X*$ZaJ'%'nB]%+9s1$_iHi$_l:f%-$3G%0Dcp$dt9S$dw+P%2/$1$h@,s$j**=$j,q:%79ip%:ZED$o4p'$o7b$%j%8uiM%8x[J%[0T+%^Q/T%>+Z7%>.L4%`;Dj%c[u>%C6Jv%C9%mqVg%ML,J%MNsG%o[l(%s'GQ%RVr4%RYd1%tf\g%x28;%5U-2%WdT\&$qMQ&(=)%%\lS]%\oEZ&*'>;&-Gnd%awDG%b%6D&/2/%&2R_N%g-51%g0'.&4RV8&As1a%vM\D%vPNA&C]Fw&$nOd&&XM.&&[?+&Hh7a&L3h5&+c=m&+f/j&Ms(K&Q>Xt&0n.W&0puT&S(n5&VII^&5xtA&6&f>&X3^t&[T:H&;.e+&;1W(&]>O^&`_+2&@9Uj&@x&jta[&JO7>&JR);&l^vq&p*RE&OZ((&O\o%&qig[&u5C/&Tdmg&Tg_d&vtXE'%@3n&Yo^Q&YrPN''*I/'*K$X&_%O;&_(A8',59n'/UjB&d0@%&d31w'1@*X'4`[,&G.Ox&i=wM'6JpB'9kKk&nEvN&nHhK';Ua,'>vb'VriE'Vu[B($*ai('N/L'\(Z/'\+L,()8Db(,Xu6'a3Jn'a6;X'fA-U(3N&6(6nV_'kI,B'kKs?(8Xku(<$GI'pSr,'pVd)(=c\_(A/83'u^bk'uaTh(BnMI(F:(r'X\ri(%lE>(H$>3(KDn\(*tD?(*w6<(M/.r(PO_F(0*5)(0-'&(R9t\(UZP0(55%h(57le(WDeF(Ze@o(:?kR(:B]O(\OV0(_p1Y(?J\<(?MN9(aZFo(e%wC(DUM&(DX>x(fe7Y(Gv@F(I`=e(Ic/b(kp(C(o;Xl(Nk.O(NmuL(q%n-(tFIV(Sut9(Sxf6(v0^l)$Q:@(Y+dx(Y.Vu)&;OV))\+*(^6Ub(^9G_)+F@@).fpi(cAFL(cD8I)0Q1*)3qaS(hL76(hO)3)5Y/Z)9'R=(mW'u(mYnr):fgS)>2C'(ram_(rd_\)?qX=)C=3f(wl^I(woPF)E'I')HH$P)'wO3)(%A0)J29f)MRj:)--?r)-01o)O=*P)R][$)280\)2:wY)TGp:)WhKc(j6@Z)7Eh/)YRa$)\sc`)Kn9C)Kq+@)n(xv)qITJ)Q$*-)Q&q*)s3i`)vTE4)V.ol)V1ai)x>ZJ)YOc7)[9`V)[Z)toi=)tr[:*B*Sp*EK/D*%%Z'*%(L$*G2RK*JUu.**0Jf**3-M*QK&.*TkVW*4F,:*4Hs7*VUkm*YvGA*9Pr$*9Scv*[`\W*_,8+*>[bc*>^T`*`kMA*d7(j*CfSM*CiEJ*ev>+*iAnT*&dcK*Ht5u*k,.j*nL_>*N'4v*N*&s*p6tT*sWP(*S2%`*S4l]*uAe>*xb@g*Xp+/b7Q+3-h%*g]=]*g`/Z+4m(;*k)1(*lh.G*ljuD+9wn%+=CIN*qrt1*quf.+?-^d+BN:8*w(dp*w+Vm+D8ON+GY*w+'3UZ+'6GW+IC@8+Lcpa+,>FD+,A8A+NN0w+QnaK+1I7.+1L)++SXva+W$R5+6T'm+6Vnj+X`u<+Yj%R+Yj1W+Yj=\+YjIa+YjUf+Yjak+Yjmp+Yk$u+Yk1%+Yk=*+YkI/+YkU4+Yka9+Ykm>+Yl$C+Yl0H+Yl$GoW-$IVb>$IVnF$K>Tg$R08V$SlCg$SlOo$UT6;$\Eo*$^-%;$^-1C$_ild$f[PS$$)E7$)3rI$j*)Y$ka82$/uZ&$0%nh$mMp;$plM,$:6;M$:;,-$rXa($uwb%$DKqs$DP>G$wcQj%&-vs$NaSD$NePa%'nBW%+96l$Xw4j$Y%c&%-$3D%0DKe$c7k;$c:u@%2/$1$j$j?$hC+1$hEr-%5R^`%:ZED$o4p'$o7b$%+Z7%>.L4%`;Dj%c[u>%C6Jv%C9%mqVg%ML,J%MNsG%o[l(%s'GQ%RVr4%RYd1%tf\g%x28;%7<8B%YK_m&&XL\&(=)$%\lS\%\oEZ&*'>;&-Gnd%awDG%b%6D&/2/%&2R_N%g-51%g0'.&4RV8&As1a%vM\D%vPNA&C]Fw&&Ufx&$qMw&$t?s&G,,Q&L3h5&+c=m&+f/j&Ms(K&Q>Xt&0n.W&0puT&S(n5&VII^&5xtA&6&f>&X3^t&[T:H&;.e+&;1W(&]>O^&`_+2&@9Uj&@vb'VriE'Vu[B($*mn(%fm9'\(N,'\+@(()8Db(,Xu6'a3Jn'a6;X'fA-U(3N&6(6nV_'kI,B'kKs?(8Xku(<$GI'pSr,'pVd)(=c\_(A/83'u^bk'uaTh(BnMI(F:(r'ZD)$('SPO(I`=>(KDn[(*tD>(*w6<(M/.r(PO_F(0*5)(0-'&(R9t\(UZP0(55%h(57le(WDeF(Ze@o(:?kR(:B]O(\OV0(_p1Y(?J\<(?MN9(aZFo(e%wC(DUM&(DX>x(fe7Y(I]WZ(H$>Y(H'0U(j3r3(o;Xl(Nk.O(NmuL(q%n-(tFIV(Sut9(Sxf6(v0^l)$Q:@(Y+dx(Y.Vu)&;OV))\+*(^6Ub(^9G_)+F@@).fpi(cAFL(cD8I)0Q1*)3qaS(hL76(hO)3)5Y;_)7@;*(mVpr(mYbn):fgS)>2C'(ram_(rd_\)?qX=)C=3f(wl^I(woPF)E'I')HH$P)'wO3)(%A0)J29f)MRj:)--?r)-01o)O=*P)R][$)280\)2:wY)TGp:)WhKc(krKj)9,s@)[9`/)\sc`)Kn9C)Kq+@)n(xv)qITJ)Q$*-)Q&q*)s3i`)vTE4)V.ol)V1ai)x>ZJ)[7%K)YRaJ)YUSF*&b@$*+j&])`DQ@)`GC=*-T;s*0tlG)eOB*)eR4'*2_,]*6*]1)jZ2i)j]$f*7irG*;5Mp)odxS)ogjP*Z)toi=)tr[:*B*Sp*EK/D*%%Z'*%(L$*G2^P*Hn]p**0>c**30_*L@5D*O`em*/;;P*/>-M*QK&.*TkVW*4F,:*4Hs7*VUkm*YvGA*9Pr$*9Scv*[`\W*_,8+*>[bc*>^T`*`kMA*d7(j*CfSM*CiEJ*ev>+*iAnT*(Kn[*J[A1*lh-u*nL_=*N'4u*N*&s*p6tT*sWP(*S2%`*S4l]*uAe>*xb@g*Xp+/b7Q+3-h%*g]=]*g`/Z+4m(;*leH<*k,/;*k.v7+8;bj+=CIN*qrt1*quf.+?-^d+BN:8*w(dp*w+Vm+D8ON+GY*w+'3UZ+'6GW+IC@8+Lcpa+,>FD+,A8A+NN0w+QnaK+1I7.+1L)++SXva+W$R5+6T'm+6Vnj+Xa,A+Yj%R+Yj1Y+YjUg+Yjmq+Yk1&+YkI0+Yka:+Yl$D+YlHN$N^ls$,R7G$,U5H$PI98$Si]]$1](1$1`&2$UT)w$XtNG$6gmp$6jkq$Z^oa$^*?1$;r^Z$;u\[$_i`K$c5/p$A(OD$A+ME$dtQ5$h?uZ$F3@.$GrI?$kfY4$o2(Y$M%H-$M(F.$pqIs$t'W%&2+G%)ROl$\Eo@$\HmA%++Z8%>.X9%aw\)%eC+N%C6Jw%C9Hx%g-Lh%jMq8%HA;a%HD9b%l8=R%oXaw%ML,K%MO*L%qC.<%tcRa%RVr5%RYp6%vMt&&$nCK%Wabt%Wd`u&&Xde&*$45%\lS^%^V\o&-Jld&0k<4%c^[]%caY^&2U]N&5v,s%hiLG%hlJH&7`N8&;+r]%mt=1%mw;2&w&@6cG%s*-p%s-+q&Av/a&EAT1%x4sZ%x7q[&G+uK&JLDp&(?dD&(BbE&L6f5&OW5Z&/1lB&/4jD&S(n4&VI=Y&4<]-&4?[.&X3^s&[T.C&9GMl&9JKm&]>O]&`^t-&>R>V&>U''3'Al'6Gf<&i;0e&i>.f'822V';RW&&nEvO&nHtP'=xwt'?&uu'bp$e'f;I5'Ek*r'En(t'ib,d'm-Q4'Jup]'Jxn^'nlrN'r8As'P+aG'P._H'swc8'wC2]'U6R1'U9P2($-Sw('MxG'ZABp'ZD@q()8Da(,Xi1'_L3Z'_O1[(.C5K(1cYp'dW$D'dYwE(3N&5(8Uao'kI,C'kL*D(:@.4(=`RY'pSr-'pVp.(?Jss(BkCC'u^bl'ua`m(DUd](Gv4-(%iSV(%lQW(I`UG(M,$l(*tD@(*wBA(NkF1(R6jV(0*5*(0-3+(Sv6p(WA[@(55%i(6t/%(Zh>o(^3c?(<'-h(<*+i(_s/Y(c>T)(A1sR(A4qS(e(uC(hIDh(FVl(r_&<(PREe(PUCf(tIGV(wil&(U]6O(U`4P)$T8@)'t\e(\O>M(\R26w(q%VK(q(TL)?qX<)C='a(v0G5(v3E6)E'I&)HGmK)&;7t)&>5u)J29e)O9uJ)--?s)-0=t)Q$Ad)TDf4)280])2;.^)V/2N)YOVs)7BvG)7EtH)[9x8)^ZG])Xp@)s3R()s6P**B*So*EJx?)x>Bh)xA@i*G5DY*JUi)*(I3R*(L1S*L@5C*O`Yh*-T$<*-Vw=*QK&-*TkJR*2^j&*2ah'*VUkl*Yv;<*7iZe*7lXf*[`\V*_,,&*[8*S2%a*S4xb*w)'R+%IKw*X^&+/_-K*bRLt*d1+.%]Z+.([[+Qq_K+U=.p+4leX+4ocZ+XcgJ+\/6o+9wVC+:%TD+]nX4+a:'Y+?-G-+?0E.+c$Hs+fDmC+D87l+D;5m+h/9]+kO^-+IC(V+IF&W+m:*G+pZNl+NMn@+NPlA+rDp1+ue?V+SX_*+S[]++wO`p,%:P.,&vgC,(^)X,*E@m,,,X-,-hoB,/P1W,17Hl,2s`,,4ZwA,6B9V,8)Pk,9eh+,;M*@,=4AU,>pXj,@Wp*,B?2?,D&IT,Eb`i,H'r1,Id4F,KKK[,M2bp,No%0,PVaQ7[Aa/f5)7xuYSaQ7[Aa794h7l?EtaQ7[Aa9['fzaQ7[Aa:L<8`x/vJaQ7[Aa9['ea/f5'aQ7[Aa794fa794faQ7[Aa/f5&a9['caQ7[A`x/vGa:L<5aQ7[Aza9['caQ7[A7l?Eqa794eaQ7[A7xuYOa/f5%aQ7[A8+HY:`x/vGaQ7[A8-jL7]8wuQaQ7[A8.[`^7l?EmaQ7[A8-jL67xuYLaQ7[A8+HY98+HY8aQ7[A7xuYM8-jL6aQ7[A7l?En8.[`]aQ7[Az85BheaOV5ba+:2Y81k?taOV5ba82J)8,AnSaOV5ba=[pI7tIW-aOV5baA3D9zaOV5baBETaa+:2XaOV5baA3D8a82J'aOV5ba=[pFa=[pEaOV5ba82J&aA3D6aOV5ba+:2UaBET^aOV5bzaA3D6aOV5b7tIW*a=[pDaOV5b8,AnOa82J%aOV5b81k?na+:2UaOV5b85Bh^]@q8xaOV5b86U$17tIW(aOV5b85Bh]8,AnMaOV5b81k?m81k?laOV5b8,AnN85Bh\aOV5b7tIW)86U$/aOV5bz8:_aQaMAiaa/f5)87d&JaMAiaa,;a/f5(aMAiaaFP=%a,8aMAiazaFPcW<8'x-gaJT2f8=h8A84LjgaJT2f8;)%18;)%1aJT2f84Lji8=h8@aJT2f8'x-i8>cW;aJT2fz8@J=naG>,6a794h8=>KTaG>,6aCTVt87d&IaG>,6aI/')8+HY,6aL:nBzaG>,6aMAiga794gaG>,6aL:nAaCTVqaG>,6aI/''aI/'&aG>,6aCTVoaL:n?aG>,6a794eaMAidaG>,6zaL:n?aG>,68+HY:aI/'%aG>,687d&CaCTVnaG>,68=>KNa794eaG>,68@J=g]KmN*aG>,68AQ988+HY7aG>,68@J=g87d&@aG>,68=>KM8=>KMaG>,687d&A8@J=faG>,68+HY88AQ96aG>,6z8BUCkaBETZa8YmK8?.MqaBETZaES@889bdbaBETZaJt)F8,iza-&.3aR7`ga::XDa-&.3aPqK=aG-,<8;MPfzaMAii8.[`bzaQ7[IzzaRT=ha:L<8zaQ7[HaG>,:zaMAifaMAiezaG>,9aQ7[Eza:L<5aRT=fzzaQ7[Ez8.[`_aMAidz8;MPbaG>,8z8AQ98a:L<5z8EG*m]Q*w-z8Fcb:8.[`\z8EG*l8;MP`z8AQ978AQ96z8;MPa8EG*kz8.[`]yzz8E+oj7v5R]a::XE8A:.B7v5R]aG-z7v5R]aR7`ga::XD7v5R]aPqK=aG-KT8;MP`aCTVt87d&I8;MP`aI/')8+HY<8;MP`aL:nBz8;MP`aMAiga794g8;MP`aL:nAaCTVq8;MP`aI/''aI/'&8;MP`aCTVoaL:n?8;MP`a794eaMAid8;MP`zaL:n?8;MP`8+HY:aI/'%8;MP`87d&CaCTVn8;MP`8=>KNa794e8;MP`8@J=g]KmN*8;MP`8AQ988+HY78;MP`8@J=g87d&@8;MP`8=>KM8=>KM8;MP`87d&A8@J=f8;MP`8+HY88AQ968;MP`z8=h8G8>cW;a3h^G8;)%78>cW;a@=FF84Ljp8>cW;aFnUa8'x-p8>cW;aIXhqz8>cW;aJT2la3h^F8>cW;aIXhpa@=FC8>cW;aFnU_aFnU^8>cW;a@=FBaIXhn8>cW;a3h^AaJT2i8>cW;zaIXhn8>cW;8'x-kaFnU^8>cW;84Ljja@=F@8>cW;8;)%2a3h^A8>cW;8=h8A]I*l/8>cW;8>cW<8'x-g8>cW;8=h8A84Ljg8>cW;8;)%18;)%18>cW;84Lji8=h8@8>cW;8'x-i8>cW;8>cW;z8:_aQ8AQ96a/f5)87d&J8AQ96a,;a/f5(8AQ96aFP=%a,88AQ96zaFP8EG*ka/f5)7xuYS8EG*ka794h7l?Et8EG*ka9['fz8EG*ka:L<8`x/vJ8EG*ka9['ea/f5'8EG*ka794fa794f8EG*ka/f5&a9['c8EG*k`x/vGa:L<58EG*kza9['c8EG*k7l?Eqa794e8EG*k7xuYOa/f5%8EG*k8+HY:`x/vG8EG*k8-jL7]8wuQ8EG*k8.[`^7l?Em8EG*k8-jL67xuYL8EG*k8+HY98+HY88EG*k7xuYM8-jL68EG*k7l?En8.[`]8EG*kz7uCHt8FG04`jc&P7rt'w8FG04`wC3.7kRWX8FG04a)dXM7^rJx8FG04a,4$Iz8FG04a-&.?`jc&N8FG04a,4$H`wC3,8FG04a)dXKa)dXJ8FG04`wC3*a,4$F8FG04`jc&Ka-&.=8FG04za,4$F8FG047^rJua)dXJ8FG047kRWS`wC3)8FG047rt's`jc&K8FG047uCHo]+QgX8FG047v5Re7^rJq8FG047uCHn7kRWQ8FG047rt'r7rt'r8FG047kRWR7uCHn8FG047^rJr7v5Rd8FG04zzaRT=dzzyz + nlist 382 1 b85VECTOR +8$sL?aR%6>`p]C(7vj/8aR%6>a($j&8,Oq4aQ$=Ea1Ba1j/2]ZEXpaQ$=Ea;iAh`p]BBaR%6?a0d'ta$guxaQ$=Da:rPna($inaR%6>a-Z`,a1B7dlgqa:rPoaQ$=C7mwEMa-Z_iaR%6?7q49%a8@LcaQ$=C8%Qa:a($iiaR%6@7vj/6a1B7dlgx8/,uEaQ$=A7mwE_8&$SXaR%6@5(Kln8/xfEaQ$=D4n,BY82U=faOAqMa8`53867kIaOAqLa+j;W8,oYWaOAqMa>En;7u$`;aOAqNaB(Fp]^u6:aOAqMaC>G8a+j;saOAqNaB(Fta8`55aOAqLa>En?a>En@aOAqMa8`51aB(FxaOAqMa+j;haC>G@aOAqK4Y^O1aB(G%aOAqK7u$`Fa>EnAaOAqK8,oY]a8`50aOAqL82U=ma+j;kaOAqJ867kP4J,])aOAqJ87Mkl7u$`FaOAqK867kM8,oY\aOAqK82U=k82U=jaOAqJ8,oYb867kOaOAqL7u$`@87MkgaOAqM4i*v)88;[taM-K^a%E_aJ@lva4+;Q84c;FaJ@luaG)&O8(:`VaJ@lsaIjv2]_U%E^]2IK3aJ@lr8>v_'8(:`caJ@lt8>%E]84c;QaJ@lq8;8K%8;8K$aJ@lq84c;K8>%E\aJ@lq8(:`W8>v_'aJ@lq5/=Vk8=HJ&aG,t^aCc,n8@V'DaG,t\a7@i]87rQ>aG,t]aI9%T8+P9HaG,t[aLFWk]hcZA7aG,tY8A]V-8+P9PaG,tY8@V'>87rQFaG,tY8=HJ'8=HJ)aG,tY87rQC8@V'@aG,tY8+P9K8A]V.aG,tY50&a>8?43^aB)a>aEWKc8B\)XaB)a?a8^5`89fp4aB)a@aK$d78,mZJaB)aDaNLZ)]Yd;iaB)aDaO]8ja8^6,aB)aDaNLZ'aEWKeaB)aCaK$d1aK$d5aB)aAaEWK_aNLZ(aB)aBa8^6$aO]8jaB)aG]SCosaNLZ'aB)aF8,mZOaK$d3aB)aD89fp8aEWKaaB)a@8?43_a8^6&aB)a>8B\)S4]4BYaB)a;8Cl]?8,mZTaB)a<8B\)S89fp6aB)a;8?43^8?43`aB)a<89fp78B\)TaB)a>8,mZP8Cl]AaB)a<52v)T8@LmMa:8ZwaFR128D3$9a:8Zwa9\x^8:aUXa:8[$aL=I&8-lHLa:8[&aOxT_]2EE>a:8[$aQ:iJa9]$-a:8ZxaOxT^aFR14a:8[%aL=HvaL=I&a:8[$aFR1/aOxT_a:8Zwa9]$&aQ:iJa:8[']Db/faOxT]a:8[&8-lHRaL=I$a:8Zp8:aU\aFR10a:8Zq8@LmPa9]$$a:8Zu8D3$44e9woa:8Zv8EJ8t8-lHUa:8Zx8D3$38:aU[a:8Zw8@LmN8@LmOa:8Zv8:aU[8D3$4a:8Zt8-lHP8EJ8wa:8Zq53XsU8A:e0a,fiLaG-dd8E,[,68EG*p\jrsaa:L;i8;MP]]I`mKaMAib8.[`S]I`mMaQ7[BzzaRT=da:L<;]I`mPaQ7[@aG>,:]H,4aQ7[A3_-C8a:L<2aRT=c\jrsa3leGaaQ7[?\xUx78.[`_aMAi_z8;MPbaG>,5]FmUA8AQ98a:L<.]LT0e8EG*kzz8Fcb88.[`^\jrsc8EG*k8;MPa\xUx<8AQ978AQ96\jrsc8;MP`8EG*kz8.[`_y3_-C453fkZ8A:e07uv8uaG-dd8E,[<7uv9&a:;,o8;=457uv9(aM+@]8.JQ\7uv9(aPr6cz7uv9$aR8OQa:;-?7uv9%aPr6`aG-dg7uv8waM+@WaM+@\7uv8taG-d`aPr6d7uv8ma:;-6aR8OR7uv8b4%`)+aPr6b7uv8a8.JQcaM+@Z7uv8j8;=49aG-db7uv8t8A:e0a:;-47uv8x8E,[74W=-`7uv9$8FGt&8.JQe7uv9$8E,[68;=487uv8v8A:e/8A:e07uv8w8;=488E,[97uv8t8.JQc8FGt&7uv8x53Dx68@LmM8.H*NaFR138D3$98.H*Na9\x^8:aUX8.H*OaL=I'8-lHL8.H*PaOxT_]2EE>8.H*OaQ:iJa9]$,8.H*NaOxT\aFR138.H*MaL=HvaL=I'8.H*MaFR1/aOxT_8.H*La9]$&aQ:iJ8.H*N]:)QhaOxT_8.H*L8-lHRaL=I$8.H*G8:aU\aFR108.H*F8@LmQa9]$$8.H*J8D3$44eYgJ8.H*K8EJ8t8-lHS8.H*M8D3$28:aU[8.H*M8@LmO8@LmO8.H*K8:aU[8D3$48.H*H8-lHP8EJ8w8.H*I52v;@8?43]8690laEWKc8B\)Y8690ka8^5`89fp48690laK$d68,mZL8690naNLZ*]Yd;i8690naO]8ja8^6+8690oaNLZ'aEWKe8690kaK$d1aK$d68690kaEWK_aNLZ*8690ma8^6&aO]8j8690m]OP%E^8>PPPPPPPPPPPPP%E^]2IK38>Pv_'8(:`c8>P%E]84c;N8>PP%E\8>Pv_(8>PEnF7u$`98CQ@vaB(Fs]`=mC8CQ@xaC>G6a+j;s8CQ@xaB(Foa8`548CQ@wa>En:a>En@8CQ@va8`5-aB(G$8CQ@wa+j;naC>G@8CQ@u4Y^O1aB(Fx8CQ@u7u$`Ua>En@8CQ@v8,oY]a8`518CQ@w82U=ia+j;l8CQ@t867kQ4I`qe8CQ@t87Mkp7u$`H8CQ@u867kP8,oY]8CQ@v82U=k82U=l8CQ@u8,oY`867kL8CQ@t7u$`F87Mkg8CQ@w4piDe8,Oq38E3ama1B=/8/,uC8E3ana$guO8%QaB8E3ala8@Lj7mwE<8E3aoa:rPi]ZEXp8E3aoa;iAaa$guw8E3aoa:rPfa1B0s7_[,H7uNfZ7_[,H8%v5I7_[,H8+HY87_[,H8.1k07_[,H80p((7_[,H83Y9u7_[,H86BKm7_[,H89+]d7_[,H8:Jf`7_[,H8;io\7_[,H8=3xX7_[,H8>S,T7_[,H8?r5P7_[,H8A<>L7_[,H8B[GH7_[,H8D%PD7_[,H8EDY@7_[,H8Fcb:7_[,Hz7m>0s7_[,H!7m>0s$$$$'7uNfZ7m>0s8%v5I7m>0s8+HY87m>0s8.1k07m>0s80p((7m>0s83Y9u7m>0s86BKm7m>0s89+]d7m>0s8:Jf`7m>0s8;io\7m>0s8=3xX7m>0s8>S,T7m>0s8?r5P7m>0s8A<>L7m>0s8B[GH7m>0s8D%PD7m>0s8EDY@7m>0s8Fcb:7m>0sz7uNfZ7_[,H7uNfZ7m>0s!7uNfZ$$$$'8%v5I7uNfZ8+HY87uNfZ8.1k07uNfZ80p((7uNfZ83Y9u7uNfZ86BKm7uNfZ89+]d7uNfZ8:Jf`7uNfZ8;io\7uNfZ8=3xX7uNfZ8>S,T7uNfZ8?r5P7uNfZ8A<>L7uNfZ8B[GH7uNfZ8D%PD7uNfZ8EDY@7uNfZ8Fcb:7uNfZz8%v5I7_[,H8%v5I7m>0s8%v5I7uNfZ!8%v5I$$$$'8+HY88%v5I8.1k08%v5I80p((8%v5I83Y9u8%v5I86BKm8%v5I89+]d8%v5I8:Jf`8%v5I8;io\8%v5I8=3xX8%v5I8>S,T8%v5I8?r5P8%v5I8A<>L8%v5I8B[GH8%v5I8D%PD8%v5I8EDY@8%v5I8Fcb:8%v5Iz8+HY87_[,H8+HY87m>0s8+HY87uNfZ8+HY88%v5I!8+HY8$$$$'8.1k08+HY880p((8+HY883Y9u8+HY886BKm8+HY889+]d8+HY88:Jf`8+HY88;io\8+HY88=3xX8+HY88>S,T8+HY88?r5P8+HY88A<>L8+HY88B[GH8+HY88D%PD8+HY88EDY@8+HY88Fcb:8+HY8z8.1k07_[,H8.1k07m>0s8.1k07uNfZ8.1k08%v5I8.1k08+HY8!8.1k0$$$$'80p((8.1k083Y9u8.1k086BKm8.1k089+]d8.1k08:Jf`8.1k08;io\8.1k08=3xX8.1k08>S,T8.1k08?r5P8.1k08A<>L8.1k08B[GH8.1k08D%PD8.1k08EDY@8.1k08Fcb:8.1k0z80p((7_[,H80p((7m>0s80p((7uNfZ80p((8%v5I80p((8+HY880p((8.1k0!80p(($$$$'83Y9u80p((86BKm80p((89+]d80p((8:Jf`80p((8;io\80p((8=3xX80p((8>S,T80p((8?r5P80p((8A<>L80p((8B[GH80p((8D%PD80p((8EDY@80p((8Fcb:80p((z83Y9u7_[,H83Y9u7m>0s83Y9u7uNfZ83Y9u8%v5I83Y9u8+HY883Y9u8.1k083Y9u80p((83Y9u83Y9u83Y9u86BKm83Y9u89+]d83Y9u8:Jf`83Y9u8;io\83Y9u8=3xX83Y9u8>S,T83Y9u8?r5P83Y9u8A<>L83Y9u8B[GH83Y9u8D%PD83Y9u8EDY@83Y9u8Fcb:83Y9uz86BKm7_[,H86BKm7m>0s86BKm7uNfZ86BKm8%v5I86BKm8+HY886BKm8.1k086BKm80p((86BKm83Y9u!86BKm$$$$'89+]d86BKm8:Jf`86BKm8;io\86BKm8=3xX86BKm8>S,T86BKm8?r5P86BKm8A<>L86BKm8B[GH86BKm8D%PD86BKm8EDY@86BKm8Fcb:86BKmz89+]d7_[,H89+]d7m>0s89+]d7uNfZ89+]d8%v5I89+]d8+HY889+]d8.1k089+]d80p((89+]d83Y9u89+]d86BKm!89+]d$$$$'8:Jf`89+]d8;io\89+]d8=3xX89+]d8>S,T89+]d8?r5P89+]d8A<>L89+]d8B[GH89+]d8D%PD89+]d8EDY@89+]d8Fcb:89+]dz8:Jf`7_[,H8:Jf`7m>0s8:Jf`7uNfZ8:Jf`8%v5I8:Jf`8+HY88:Jf`8.1k08:Jf`80p((8:Jf`83Y9u8:Jf`86BKm8:Jf`89+]d!8:Jf`$$$$'8;io\8:Jf`8=3xX8:Jf`8>S,T8:Jf`8?r5P8:Jf`8A<>L8:Jf`8B[GH8:Jf`8D%PD8:Jf`8EDY@8:Jf`8Fcb:8:Jf`z8;io\7_[,H8;io\7m>0s8;io\7uNfZ8;io\8%v5I8;io\8+HY88;io\8.1k08;io\80p((8;io\83Y9u8;io\86BKm8;io\89+]d8;io\8:Jf`!8;io\$$$$'8=3xX8;io\8>S,T8;io\8?r5P8;io\8A<>L8;io\8B[GH8;io\8D%PD8;io\8EDY@8;io\8Fcb:8;io\z8=3xX7_[,H8=3xX7m>0s8=3xX7uNfZ8=3xX8%v5I8=3xX8+HY88=3xX8.1k08=3xX80p((8=3xX83Y9u8=3xX86BKm8=3xX89+]d8=3xX8:Jf`8=3xX8;io\!8=3xX$$$$'8>S,T8=3xX8?r5P8=3xX8A<>L8=3xX8B[GH8=3xX8D%PD8=3xX8EDY@8=3xX8Fcb:8=3xXz8>S,T7_[,H8>S,T7m>0s8>S,T7uNfZ8>S,T8%v5I8>S,T8+HY88>S,T8.1k08>S,T80p((8>S,T83Y9u8>S,T86BKm8>S,T89+]d8>S,T8:Jf`8>S,T8;io\8>S,T8=3xX!8>S,T$$$$'8?r5P8>S,T8A<>L8>S,T8B[GH8>S,T8D%PD8>S,T8EDY@8>S,T8Fcb:8>S,Tz8?r5P7_[,H8?r5P7m>0s8?r5P7uNfZ8?r5P8%v5I8?r5P8+HY88?r5P8.1k08?r5P80p((8?r5P83Y9u8?r5P86BKm8?r5P89+]d8?r5P8:Jf`8?r5P8;io\8?r5P8=3xX8?r5P8>S,T!8?r5P$$$$'8A<>L8?r5P8B[GH8?r5P8D%PD8?r5P8EDY@8?r5P8Fcb:8?r5Pz8A<>L7_[,H8A<>L7m>0s8A<>L7uNfZ8A<>L8%v5I8A<>L8+HY88A<>L8.1k08A<>L80p((8A<>L83Y9u8A<>L86BKm8A<>L89+]d8A<>L8:Jf`8A<>L8;io\8A<>L8=3xX8A<>L8>S,T8A<>L8?r5P!8A<>L$$$$'8B[GH8A<>L8D%PD8A<>L8EDY@8A<>L8Fcb:8A<>Lz8B[GH7_[,H8B[GH7m>0s8B[GH7uNfZ8B[GH8%v5I8B[GH8+HY88B[GH8.1k08B[GH80p((8B[GH83Y9u8B[GH86BKm8B[GH89+]d8B[GH8:Jf`8B[GH8;io\8B[GH8=3xX8B[GH8>S,T8B[GH8?r5P8B[GH8A<>L!8B[GH$$$$'8D%PD8B[GH8EDY@8B[GH8Fcb:8B[GHz8D%PD7_[,H8D%PD7m>0s8D%PD7uNfZ8D%PD8%v5I8D%PD8+HY88D%PD8.1k08D%PD80p((8D%PD83Y9u8D%PD86BKm8D%PD89+]d8D%PD8:Jf`8D%PD8;io\8D%PD8=3xX8D%PD8>S,T8D%PD8?r5P8D%PD8A<>L8D%PD8B[GH!8D%PD$$$$'8EDY@8D%PD8Fcb:8D%PDz8EDY@7_[,H8EDY@7m>0s8EDY@7uNfZ8EDY@8%v5I8EDY@8+HY88EDY@8.1k08EDY@80p((8EDY@83Y9u8EDY@86BKm8EDY@89+]d8EDY@8:Jf`8EDY@8;io\8EDY@8=3xX8EDY@8>S,T8EDY@8?r5P8EDY@8A<>L8EDY@8B[GH8EDY@8D%PD!8EDY@$$$$'8Fcb:8EDY@7Qx'rz7gkb/z7reTbz7x7xRz8(_GAz8,gb4z8/Pt,z82:0xz84xBpz87aThz89eb6z8;/k2z8 Date: Fri, 16 Jun 2023 14:17:08 +0200 Subject: [PATCH 290/347] ImageIO: Minor fixes (#5147) * define variable 'resolved_path' in right scope * fixed missing 'input' variable * make checks for keys more explicit and safe proof * fixed caching of remapped colorspaces * trying to fix indentation issue * use safe keys pop --- openpype/hosts/nuke/api/lib.py | 8 +-- openpype/pipeline/colorspace.py | 107 ++++++++++++++++---------------- 2 files changed, 56 insertions(+), 59 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 777f4454dc..c05182ce97 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2020,11 +2020,11 @@ class WorkfileSettings(object): # TODO: backward compatibility for old projects - remove later # perhaps old project overrides is having it set to older version # with use of `customOCIOConfigPath` + resolved_path = None if workfile_settings.get("customOCIOConfigPath"): unresolved_path = workfile_settings["customOCIOConfigPath"] ocio_paths = unresolved_path[platform.system().lower()] - resolved_path = None for ocio_p in ocio_paths: resolved_path = str(ocio_p).format(**os.environ) if not os.path.exists(resolved_path): @@ -2054,9 +2054,9 @@ class WorkfileSettings(object): self._root_node["colorManagement"].setValue("OCIO") # we dont need the key anymore - workfile_settings.pop("customOCIOConfigPath") - workfile_settings.pop("colorManagement") - workfile_settings.pop("OCIO_config") + workfile_settings.pop("customOCIOConfigPath", None) + workfile_settings.pop("colorManagement", None) + workfile_settings.pop("OCIO_config", None) # then set the rest for knob, value_ in workfile_settings.items(): diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 899b14148b..1999ad3bed 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -312,7 +312,8 @@ def get_views_data_subprocess(config_path): def get_imageio_config( - project_name, host_name, + project_name, + host_name, project_settings=None, anatomy_data=None, anatomy=None @@ -325,12 +326,9 @@ def get_imageio_config( Args: project_name (str): project name host_name (str): host name - project_settings (dict, optional): project settings. - Defaults to None. - anatomy_data (dict, optional): anatomy formatting data. - Defaults to None. - anatomy (lib.Anatomy, optional): Anatomy object. - Defaults to None. + project_settings (Optional[dict]): Project settings. + anatomy_data (Optional[dict]): anatomy formatting data. + anatomy (Optional[Anatomy]): Anatomy object. Returns: dict: config path data or empty dict @@ -345,37 +343,36 @@ def get_imageio_config( formatting_data = deepcopy(anatomy_data) - # add project roots to anatomy data + # Add project roots to anatomy data formatting_data["root"] = anatomy.roots formatting_data["platform"] = platform.system().lower() - # get colorspace settings - # check if global settings group is having activate_global_color_management - # key at all. If it does't then default it to False - # this is for backward compatibility only - # TODO: in future rewrite this to be more explicit + # Get colorspace settings imageio_global, imageio_host = _get_imageio_settings( project_settings, host_name) - activate_color_management = ( - imageio_global.get("activate_global_color_management", False) - # for already saved overrides from previous version - # TODO: remove this in future - backward compatibility - or imageio_host.get("ocio_config").get("enabled") - ) + # Host 'ocio_config' is optional + host_ocio_config = imageio_host.get("ocio_config") or {} + + # Global color management must be enabled to be able to use host settings + activate_color_management = imageio_global.get( + "activate_global_color_management") + # TODO: remove this in future - backward compatibility + # For already saved overrides from previous version look for 'enabled' + # on host settings. + if activate_color_management is None: + activate_color_management = host_ocio_config.get("enabled", False) if not activate_color_management: # if global settings are disabled return empty dict because # it is expected that no colorspace management is needed - log.info( - "Colorspace management is disabled globally." - ) + log.info("Colorspace management is disabled globally.") return {} - # check if host settings group is having activate_host_color_management - # if it does not have activation key then default it to True so it uses - # global settings - # this is for backward compatibility + # Check if host settings group is having 'activate_host_color_management' + # - if it does not have activation key then default it to True so it uses + # global settings + # This is for backward compatibility. # TODO: in future rewrite this to be more explicit activate_host_color_management = imageio_host.get( "activate_host_color_management", True) @@ -389,21 +386,18 @@ def get_imageio_config( ) return {} - config_host = imageio_host.get("ocio_config", {}) - - # get config path from either global or host_name + # get config path from either global or host settings # depending on override flag # TODO: in future rewrite this to be more explicit - config_data = None - override_global_config = ( - config_host.get("override_global_config") + override_global_config = host_ocio_config.get("override_global_config") + if override_global_config is None: # for already saved overrides from previous version # TODO: remove this in future - backward compatibility - or config_host.get("enabled") - ) + override_global_config = host_ocio_config.get("enabled") + if override_global_config: config_data = _get_config_data( - config_host["filepath"], formatting_data + host_ocio_config["filepath"], formatting_data ) else: # get config path from global @@ -507,34 +501,35 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): frules_host = imageio_host.get("file_rules", {}) # compile file rules dictionary - activate_host_rules = ( - frules_host.get("activate_host_rules") + activate_host_rules = frules_host.get("activate_host_rules") + if activate_host_rules is None: # TODO: remove this in future - backward compatibility - or frules_host.get("enabled") - ) + activate_host_rules = frules_host.get("enabled", False) # return host rules if activated or global rules return frules_host["rules"] if activate_host_rules else global_rules def get_remapped_colorspace_to_native( - ocio_colorspace_name, host_name, imageio_host_settings): + ocio_colorspace_name, host_name, imageio_host_settings +): """Return native colorspace name. Args: ocio_colorspace_name (str | None): ocio colorspace name + host_name (str): Host name. + imageio_host_settings (dict[str, Any]): ImageIO host settings. Returns: - str: native colorspace name defined in remapping or None + Union[str, None]: native colorspace name defined in remapping or None """ - if not CashedData.remapping.get(host_name, {}).get("to_native"): + CashedData.remapping.setdefault(host_name, {}) + if CashedData.remapping[host_name].get("to_native") is None: remapping_rules = imageio_host_settings["remapping"]["rules"] - CashedData.remapping[host_name] = { - "to_native": { - rule["ocio_name"]: input["host_native_name"] - for rule in remapping_rules - } + CashedData.remapping[host_name]["to_native"] = { + rule["ocio_name"]: rule["host_native_name"] + for rule in remapping_rules } return CashedData.remapping[host_name]["to_native"].get( @@ -542,23 +537,25 @@ def get_remapped_colorspace_to_native( def get_remapped_colorspace_from_native( - host_native_colorspace_name, host_name, imageio_host_settings): + host_native_colorspace_name, host_name, imageio_host_settings +): """Return ocio colorspace name remapped from host native used name. Args: host_native_colorspace_name (str): host native colorspace name + host_name (str): Host name. + imageio_host_settings (dict[str, Any]): ImageIO host settings. Returns: - str: ocio colorspace name defined in remapping or None + Union[str, None]: Ocio colorspace name defined in remapping or None. """ - if not CashedData.remapping.get(host_name, {}).get("from_native"): + CashedData.remapping.setdefault(host_name, {}) + if CashedData.remapping[host_name].get("from_native") is None: remapping_rules = imageio_host_settings["remapping"]["rules"] - CashedData.remapping[host_name] = { - "from_native": { - input["host_native_name"]: rule["ocio_name"] - for rule in remapping_rules - } + CashedData.remapping[host_name]["from_native"] = { + rule["host_native_name"]: rule["ocio_name"] + for rule in remapping_rules } return CashedData.remapping[host_name]["from_native"].get( From c388ee94636e7eac952d7cff5c067fc03173ecb4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Jun 2023 22:08:21 +0800 Subject: [PATCH 291/347] add collector to tray publisher for getting frame range data --- .../publish/collect_anatomy_frame_range.py | 33 +++++++++++++++++++ .../project_settings/traypublisher.json | 4 +++ .../schema_project_traypublisher.json | 4 +++ 3 files changed, 41 insertions(+) create mode 100644 openpype/plugins/publish/collect_anatomy_frame_range.py diff --git a/openpype/plugins/publish/collect_anatomy_frame_range.py b/openpype/plugins/publish/collect_anatomy_frame_range.py new file mode 100644 index 0000000000..71a5dcfeb0 --- /dev/null +++ b/openpype/plugins/publish/collect_anatomy_frame_range.py @@ -0,0 +1,33 @@ +import pyblish.api + + +class CollectAnatomyFrameRange(pyblish.api.InstancePlugin): + """Collect Frame Range specific Anatomy data. + + Plugin is running for all instances on context even not active instances. + """ + + order = pyblish.api.CollectorOrder + 0.491 + label = "Collect Anatomy Frame Range" + hosts = ["traypublisher"] + + def process(self, instance): + self.log.info("Collecting Anatomy frame range.") + asset_doc = instance.data.get("assetEntity") + if not asset_doc: + self.log.info("Missing required data..") + return + + asset_data = asset_doc["data"] + for key in ( + "fps", + "frameStart", + "frameEnd", + "handleStart", + "handleEnd" + ): + if key not in instance.data and key in asset_data: + value = asset_data[key] + instance.data[key] = value + + self.log.info("Anatomy frame range collection finished.") diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 3a42c93515..6b8bdcfcc5 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -318,6 +318,10 @@ } }, "publish": { + "CollectAnatomyFrameRange": { + "enabled": true, + "active": true + }, "ValidateFrameRange": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 3703d82856..44442a07d4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -343,6 +343,10 @@ "type": "schema_template", "name": "template_validate_plugin", "template_data": [ + { + "key": "CollectAnatomyFrameRange", + "label": "Collect Anatomy frame range" + }, { "key": "ValidateFrameRange", "label": "Validate frame range" From ec8c70db272ae51bb7118d6c7f359dff953efc6d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Jun 2023 22:14:06 +0800 Subject: [PATCH 292/347] delete unrelated code --- .../publish/collect_anatomy_frame_range.py | 33 +++++++++++++++++++ .../project_settings/traypublisher.json | 4 +++ .../schema_project_traypublisher.json | 4 +++ 3 files changed, 41 insertions(+) create mode 100644 openpype/plugins/publish/collect_anatomy_frame_range.py diff --git a/openpype/plugins/publish/collect_anatomy_frame_range.py b/openpype/plugins/publish/collect_anatomy_frame_range.py new file mode 100644 index 0000000000..71a5dcfeb0 --- /dev/null +++ b/openpype/plugins/publish/collect_anatomy_frame_range.py @@ -0,0 +1,33 @@ +import pyblish.api + + +class CollectAnatomyFrameRange(pyblish.api.InstancePlugin): + """Collect Frame Range specific Anatomy data. + + Plugin is running for all instances on context even not active instances. + """ + + order = pyblish.api.CollectorOrder + 0.491 + label = "Collect Anatomy Frame Range" + hosts = ["traypublisher"] + + def process(self, instance): + self.log.info("Collecting Anatomy frame range.") + asset_doc = instance.data.get("assetEntity") + if not asset_doc: + self.log.info("Missing required data..") + return + + asset_data = asset_doc["data"] + for key in ( + "fps", + "frameStart", + "frameEnd", + "handleStart", + "handleEnd" + ): + if key not in instance.data and key in asset_data: + value = asset_data[key] + instance.data[key] = value + + self.log.info("Anatomy frame range collection finished.") diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 3a42c93515..6b8bdcfcc5 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -318,6 +318,10 @@ } }, "publish": { + "CollectAnatomyFrameRange": { + "enabled": true, + "active": true + }, "ValidateFrameRange": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 3703d82856..44442a07d4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -343,6 +343,10 @@ "type": "schema_template", "name": "template_validate_plugin", "template_data": [ + { + "key": "CollectAnatomyFrameRange", + "label": "Collect Anatomy frame range" + }, { "key": "ValidateFrameRange", "label": "Validate frame range" From 00eab724a4dec7df938013349cdd5ecaa9fc5645 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Jun 2023 22:15:58 +0800 Subject: [PATCH 293/347] delete unrelated code --- .../publish/collect_anatomy_frame_range.py | 33 ------------------- .../project_settings/traypublisher.json | 4 --- .../schema_project_traypublisher.json | 4 --- 3 files changed, 41 deletions(-) delete mode 100644 openpype/plugins/publish/collect_anatomy_frame_range.py diff --git a/openpype/plugins/publish/collect_anatomy_frame_range.py b/openpype/plugins/publish/collect_anatomy_frame_range.py deleted file mode 100644 index 71a5dcfeb0..0000000000 --- a/openpype/plugins/publish/collect_anatomy_frame_range.py +++ /dev/null @@ -1,33 +0,0 @@ -import pyblish.api - - -class CollectAnatomyFrameRange(pyblish.api.InstancePlugin): - """Collect Frame Range specific Anatomy data. - - Plugin is running for all instances on context even not active instances. - """ - - order = pyblish.api.CollectorOrder + 0.491 - label = "Collect Anatomy Frame Range" - hosts = ["traypublisher"] - - def process(self, instance): - self.log.info("Collecting Anatomy frame range.") - asset_doc = instance.data.get("assetEntity") - if not asset_doc: - self.log.info("Missing required data..") - return - - asset_data = asset_doc["data"] - for key in ( - "fps", - "frameStart", - "frameEnd", - "handleStart", - "handleEnd" - ): - if key not in instance.data and key in asset_data: - value = asset_data[key] - instance.data[key] = value - - self.log.info("Anatomy frame range collection finished.") diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 6b8bdcfcc5..3a42c93515 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -318,10 +318,6 @@ } }, "publish": { - "CollectAnatomyFrameRange": { - "enabled": true, - "active": true - }, "ValidateFrameRange": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 44442a07d4..3703d82856 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -343,10 +343,6 @@ "type": "schema_template", "name": "template_validate_plugin", "template_data": [ - { - "key": "CollectAnatomyFrameRange", - "label": "Collect Anatomy frame range" - }, { "key": "ValidateFrameRange", "label": "Validate frame range" From 3d41ee6591f554b1b2ad25a208ad1ae8525868a2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 16 Jun 2023 16:26:04 +0200 Subject: [PATCH 294/347] TrayPublisher & StandalonePublisher: Specify version (#5142) * modified simple creator plugin to be able handle version control * added 'allow_version_control' to simple creators * don't remove 'create_context' from pyblish context during publishing * implemented validator for existing version override * actually fill version on collected instances * version can be again changed from standalone publisher * added comment to collector * make sure the version is set always to int * removed unused import * disable validator if is disabled * fix filtered instances loop --- .../plugins/publish/collect_context.py | 9 +- openpype/hosts/traypublisher/api/plugin.py | 182 +++++++++++++++++- .../publish/collect_simple_instances.py | 24 +++ .../help/validate_existing_version.xml | 16 ++ .../publish/validate_existing_version.py | 57 ++++++ openpype/pipeline/create/context.py | 13 ++ .../publish/collect_from_create_context.py | 2 +- .../project_settings/traypublisher.json | 16 ++ .../schema_project_traypublisher.json | 10 + .../widgets/widget_family.py | 3 +- 10 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 openpype/hosts/traypublisher/plugins/publish/help/validate_existing_version.xml create mode 100644 openpype/hosts/traypublisher/plugins/publish/validate_existing_version.py diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py index 96aaae23dc..8fa53f5f48 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py @@ -222,7 +222,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): "label": subset, "name": subset, "family": in_data["family"], - # "version": in_data.get("version", 1), "frameStart": in_data.get("representations", [None])[0].get( "frameStart", None ), @@ -232,6 +231,14 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): "families": instance_families } ) + # Fill version only if 'use_next_available_version' is disabled + # and version is filled in instance data + version = in_data.get("version") + use_next_available_version = in_data.get( + "use_next_available_version", True) + if not use_next_available_version and version is not None: + instance.data["version"] = version + self.log.info("collected instance: {}".format(pformat(instance.data))) self.log.info("parsing data: {}".format(pformat(in_data))) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 75930f0f31..36e041a32c 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,4 +1,14 @@ -from openpype.lib.attribute_definitions import FileDef +from openpype.client import ( + get_assets, + get_subsets, + get_last_versions, +) +from openpype.lib.attribute_definitions import ( + FileDef, + BoolDef, + NumberDef, + UISeparatorDef, +) from openpype.lib.transcoding import IMAGE_EXTENSIONS, VIDEO_EXTENSIONS from openpype.pipeline.create import ( Creator, @@ -94,6 +104,7 @@ class TrayPublishCreator(Creator): class SettingsCreator(TrayPublishCreator): create_allow_context_change = True create_allow_thumbnail = True + allow_version_control = False extensions = [] @@ -101,8 +112,18 @@ class SettingsCreator(TrayPublishCreator): # Pass precreate data to creator attributes thumbnail_path = pre_create_data.pop(PRE_CREATE_THUMBNAIL_KEY, None) + # Fill 'version_to_use' if version control is enabled + if self.allow_version_control: + asset_name = data["asset"] + subset_docs_by_asset_id = self._prepare_next_versions( + [asset_name], [subset_name]) + version = subset_docs_by_asset_id[asset_name].get(subset_name) + pre_create_data["version_to_use"] = version + data["_previous_last_version"] = version + data["creator_attributes"] = pre_create_data data["settings_creator"] = True + # Create new instance new_instance = CreatedInstance(self.family, subset_name, data, self) @@ -111,7 +132,158 @@ class SettingsCreator(TrayPublishCreator): if thumbnail_path: self.set_instance_thumbnail_path(new_instance.id, thumbnail_path) + def _prepare_next_versions(self, asset_names, subset_names): + """Prepare next versions for given asset and subset names. + + Todos: + Expect combination of subset names by asset name to avoid + unnecessary server calls for unused subsets. + + Args: + asset_names (Iterable[str]): Asset names. + subset_names (Iterable[str]): Subset names. + + Returns: + dict[str, dict[str, int]]: Last versions by asset + and subset names. + """ + + # Prepare all versions for all combinations to '1' + subset_docs_by_asset_id = { + asset_name: { + subset_name: 1 + for subset_name in subset_names + } + for asset_name in asset_names + } + if not asset_names or not subset_names: + return subset_docs_by_asset_id + + asset_docs = get_assets( + self.project_name, + asset_names=asset_names, + fields=["_id", "name"] + ) + asset_names_by_id = { + asset_doc["_id"]: asset_doc["name"] + for asset_doc in asset_docs + } + subset_docs = list(get_subsets( + self.project_name, + asset_ids=asset_names_by_id.keys(), + subset_names=subset_names, + fields=["_id", "name", "parent"] + )) + + subset_ids = {subset_doc["_id"] for subset_doc in subset_docs} + last_versions = get_last_versions( + self.project_name, + subset_ids, + fields=["name", "parent"]) + + for subset_doc in subset_docs: + asset_id = subset_doc["parent"] + asset_name = asset_names_by_id[asset_id] + subset_name = subset_doc["name"] + subset_id = subset_doc["_id"] + last_version = last_versions.get(subset_id) + version = 0 + if last_version is not None: + version = last_version["name"] + subset_docs_by_asset_id[asset_name][subset_name] += version + return subset_docs_by_asset_id + + def _fill_next_versions(self, instances_data): + """Fill next version for instances. + + Instances have also stored previous next version to be able to + recognize if user did enter different version. If version was + not changed by user, or user set it to '0' the next version will be + updated by current database state. + """ + + filtered_instance_data = [] + for instance in instances_data: + previous_last_version = instance.get("_previous_last_version") + creator_attributes = instance["creator_attributes"] + use_next_version = creator_attributes.get( + "use_next_version", True) + version = creator_attributes.get("version_to_use", 0) + if ( + use_next_version + or version == 0 + or version == previous_last_version + ): + filtered_instance_data.append(instance) + + asset_names = { + instance["asset"] + for instance in filtered_instance_data} + subset_names = { + instance["subset"] + for instance in filtered_instance_data} + subset_docs_by_asset_id = self._prepare_next_versions( + asset_names, subset_names + ) + for instance in filtered_instance_data: + asset_name = instance["asset"] + subset_name = instance["subset"] + version = subset_docs_by_asset_id[asset_name][subset_name] + instance["creator_attributes"]["version_to_use"] = version + instance["_previous_last_version"] = version + + def collect_instances(self): + """Collect instances from host. + + Overriden to be able to manage version control attributes. If version + control is disabled, the attributes will be removed from instances, + and next versions are filled if is version control enabled. + """ + + instances_by_identifier = cache_and_get_instances( + self, SHARED_DATA_KEY, list_instances + ) + instances = instances_by_identifier[self.identifier] + if not instances: + return + + if self.allow_version_control: + self._fill_next_versions(instances) + + for instance_data in instances: + # Make sure that there are not data related to version control + # if plugin does not support it + if not self.allow_version_control: + instance_data.pop("_previous_last_version", None) + creator_attributes = instance_data["creator_attributes"] + creator_attributes.pop("version_to_use", None) + creator_attributes.pop("use_next_version", None) + + instance = CreatedInstance.from_existing(instance_data, self) + self._add_instance_to_context(instance) + def get_instance_attr_defs(self): + defs = self.get_pre_create_attr_defs() + if self.allow_version_control: + defs += [ + UISeparatorDef(), + BoolDef( + "use_next_version", + default=True, + label="Use next version", + ), + NumberDef( + "version_to_use", + default=1, + minimum=0, + maximum=999, + label="Version to use", + ) + ] + return defs + + def get_pre_create_attr_defs(self): + # Use same attributes as for instance attributes return [ FileDef( "representation_files", @@ -132,10 +304,6 @@ class SettingsCreator(TrayPublishCreator): ) ] - def get_pre_create_attr_defs(self): - # Use same attributes as for instance attrobites - return self.get_instance_attr_defs() - @classmethod def from_settings(cls, item_data): identifier = item_data["identifier"] @@ -155,6 +323,8 @@ class SettingsCreator(TrayPublishCreator): "extensions": item_data["extensions"], "allow_sequences": item_data["allow_sequences"], "allow_multiple_items": item_data["allow_multiple_items"], - "default_variants": item_data["default_variants"] + "allow_version_control": item_data.get( + "allow_version_control", False), + "default_variants": item_data["default_variants"], } ) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py index c081216481..3fa3c3b8c8 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py @@ -47,6 +47,8 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): "Created temp staging directory for instance {}. {}" ).format(instance_label, tmp_folder)) + self._fill_version(instance, instance_label) + # Store filepaths for validation of their existence source_filepaths = [] # Make sure there are no representations with same name @@ -93,6 +95,28 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin): ) ) + def _fill_version(self, instance, instance_label): + """Fill instance version under which will be instance integrated. + + Instance must have set 'use_next_version' to 'False' + and 'version_to_use' to version to use. + + Args: + instance (pyblish.api.Instance): Instance to fill version for. + instance_label (str): Label of instance to fill version for. + """ + + creator_attributes = instance.data["creator_attributes"] + use_next_version = creator_attributes.get("use_next_version", True) + # If 'version_to_use' is '0' it means that next version should be used + version_to_use = creator_attributes.get("version_to_use", 0) + if use_next_version or not version_to_use: + return + instance.data["version"] = version_to_use + self.log.debug( + "Version for instance \"{}\" was set to \"{}\"".format( + instance_label, version_to_use)) + def _create_main_representations( self, instance, diff --git a/openpype/hosts/traypublisher/plugins/publish/help/validate_existing_version.xml b/openpype/hosts/traypublisher/plugins/publish/help/validate_existing_version.xml new file mode 100644 index 0000000000..8a3b8f4d7d --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/help/validate_existing_version.xml @@ -0,0 +1,16 @@ + + + +Version already exists + +## Version already exists + +Version {version} you have set on instance '{subset_name}' under '{asset_name}' already exists. This validation is enabled by default to prevent accidental override of existing versions. + +### How to repair? +- Click on 'Repair' action -> this will change version to next available. +- Disable validation on the instance if you are sure you want to override the version. +- Reset publishing and manually change the version number. + + + diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_existing_version.py b/openpype/hosts/traypublisher/plugins/publish/validate_existing_version.py new file mode 100644 index 0000000000..1fb27acdeb --- /dev/null +++ b/openpype/hosts/traypublisher/plugins/publish/validate_existing_version.py @@ -0,0 +1,57 @@ +import pyblish.api + +from openpype.pipeline.publish import ( + ValidateContentsOrder, + PublishXmlValidationError, + OptionalPyblishPluginMixin, + RepairAction, +) + + +class ValidateExistingVersion( + OptionalPyblishPluginMixin, + pyblish.api.InstancePlugin +): + label = "Validate Existing Version" + order = ValidateContentsOrder + + hosts = ["traypublisher"] + + actions = [RepairAction] + + settings_category = "traypublisher" + optional = True + + def process(self, instance): + if not self.is_active(instance.data): + return + + version = instance.data.get("version") + if version is None: + return + + last_version = instance.data.get("latestVersion") + if last_version is None or last_version < version: + return + + subset_name = instance.data["subset"] + msg = "Version {} already exists for subset {}.".format( + version, subset_name) + + formatting_data = { + "subset_name": subset_name, + "asset_name": instance.data["asset"], + "version": version + } + raise PublishXmlValidationError( + self, msg, formatting_data=formatting_data) + + @classmethod + def repair(cls, instance): + create_context = instance.context.data["create_context"] + created_instance = create_context.get_instance_by_id( + instance.data["instance_id"]) + creator_attributes = created_instance["creator_attributes"] + # Disable version override + creator_attributes["use_next_version"] = True + create_context.save_changes() diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py index 2fc0669732..332e271b0d 100644 --- a/openpype/pipeline/create/context.py +++ b/openpype/pipeline/create/context.py @@ -1441,6 +1441,19 @@ class CreateContext: """Access to global publish attributes.""" return self._publish_attributes + def get_instance_by_id(self, instance_id): + """Receive instance by id. + + Args: + instance_id (str): Instance id. + + Returns: + Union[CreatedInstance, None]: Instance or None if instance with + given id is not available. + """ + + return self._instances_by_id.get(instance_id) + def get_sorted_creators(self, identifiers=None): """Sorted creators by 'order' attribute. diff --git a/openpype/plugins/publish/collect_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py index 4888476fff..8806a13ca0 100644 --- a/openpype/plugins/publish/collect_from_create_context.py +++ b/openpype/plugins/publish/collect_from_create_context.py @@ -16,7 +16,7 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder - 0.5 def process(self, context): - create_context = context.data.pop("create_context", None) + create_context = context.data.get("create_context") if not create_context: host = registered_host() if isinstance(host, IPublishHost): diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json index 3a42c93515..4c2c2f1391 100644 --- a/openpype/settings/defaults/project_settings/traypublisher.json +++ b/openpype/settings/defaults/project_settings/traypublisher.json @@ -23,6 +23,7 @@ "detailed_description": "Workfiles are full scenes from any application that are directly edited by artists. They represent a state of work on a task at a given point and are usually not directly referenced into other scenes.", "allow_sequences": false, "allow_multiple_items": false, + "allow_version_control": false, "extensions": [ ".ma", ".mb", @@ -57,6 +58,7 @@ "detailed_description": "Models should only contain geometry data, without any extras like cameras, locators or bones.\n\nKeep in mind that models published from tray publisher are not validated for correctness. ", "allow_sequences": false, "allow_multiple_items": true, + "allow_version_control": false, "extensions": [ ".ma", ".mb", @@ -82,6 +84,7 @@ "detailed_description": "Alembic or bgeo cache of animated data", "allow_sequences": true, "allow_multiple_items": true, + "allow_version_control": false, "extensions": [ ".abc", ".bgeo", @@ -105,6 +108,7 @@ "detailed_description": "Any type of image seqeuence coming from outside of the studio. Usually camera footage, but could also be animatics used for reference.", "allow_sequences": true, "allow_multiple_items": true, + "allow_version_control": false, "extensions": [ ".exr", ".png", @@ -127,6 +131,7 @@ "detailed_description": "Sequence or single file renders", "allow_sequences": true, "allow_multiple_items": true, + "allow_version_control": false, "extensions": [ ".exr", ".png", @@ -150,6 +155,7 @@ "detailed_description": "Ideally this should be only camera itself with baked animation, however, it can technically also include helper geometry.", "allow_sequences": false, "allow_multiple_items": true, + "allow_version_control": false, "extensions": [ ".abc", ".ma", @@ -174,6 +180,7 @@ "detailed_description": "Any image data can be published as image family. References, textures, concept art, matte paints. This is a fallback 2d family for everything that doesn't fit more specific family.", "allow_sequences": false, "allow_multiple_items": true, + "allow_version_control": false, "extensions": [ ".exr", ".jpg", @@ -197,6 +204,7 @@ "detailed_description": "Hierarchical data structure for the efficient storage and manipulation of sparse volumetric data discretized on three-dimensional grids", "allow_sequences": true, "allow_multiple_items": true, + "allow_version_control": false, "extensions": [ ".vdb" ] @@ -215,6 +223,7 @@ "detailed_description": "Script exported from matchmoving application to be later processed into a tracked camera with additional data", "allow_sequences": false, "allow_multiple_items": true, + "allow_version_control": false, "extensions": [] }, { @@ -227,6 +236,7 @@ "detailed_description": "CG rigged character or prop. Rig should be clean of any extra data and directly loadable into it's respective application\t", "allow_sequences": false, "allow_multiple_items": false, + "allow_version_control": false, "extensions": [ ".ma", ".blend", @@ -244,6 +254,7 @@ "detailed_description": "Texture files with Unreal Engine naming conventions", "allow_sequences": false, "allow_multiple_items": true, + "allow_version_control": false, "extensions": [] } ], @@ -322,6 +333,11 @@ "enabled": true, "optional": true, "active": true + }, + "ValidateExistingVersion": { + "enabled": true, + "optional": true, + "active": true } } } diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json index 3703d82856..e75e2887db 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json @@ -85,6 +85,12 @@ "label": "Allow multiple items", "type": "boolean" }, + { + "type": "boolean", + "key": "allow_version_control", + "label": "Allow version control", + "default": false + }, { "type": "list", "key": "extensions", @@ -346,6 +352,10 @@ { "key": "ValidateFrameRange", "label": "Validate frame range" + }, + { + "key": "ValidateExistingVersion", + "label": "Validate Existing Version" } ] } diff --git a/openpype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py index 11c5ec33b7..8c18a93a00 100644 --- a/openpype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -128,7 +128,8 @@ class FamilyWidget(QtWidgets.QWidget): 'family_preset_key': key, 'family': family, 'subset': self.input_result.text(), - 'version': self.version_spinbox.value() + 'version': self.version_spinbox.value(), + 'use_next_available_version': self.version_checkbox.isChecked(), } return data From 7b19762d5dda46513b38724e8e19cad1c5f70ca0 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 17 Jun 2023 03:25:31 +0000 Subject: [PATCH 295/347] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index c44b1d29fb..9c5a60964b 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.11-nightly.2" +__version__ = "3.15.11-nightly.3" From e3e09e7df9e0c066e5cc77fa4be9631bd910109f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 17 Jun 2023 03:26:12 +0000 Subject: [PATCH 296/347] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 2339ec878f..2fd2780e55 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.11-nightly.3 - 3.15.11-nightly.2 - 3.15.11-nightly.1 - 3.15.10 @@ -134,7 +135,6 @@ body: - 3.14.3-nightly.7 - 3.14.3-nightly.6 - 3.14.3-nightly.5 - - 3.14.3-nightly.4 validations: required: true - type: dropdown From a2525bf9bb3040d3efc5bb222aab6cc2d9794547 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 19 Jun 2023 15:40:44 +0800 Subject: [PATCH 297/347] use getLastMergedNodes() in max_scene loader --- openpype/hosts/max/plugins/load/load_max_scene.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_max_scene.py b/openpype/hosts/max/plugins/load/load_max_scene.py index 4d9367b16f..e3fb34f5bc 100644 --- a/openpype/hosts/max/plugins/load/load_max_scene.py +++ b/openpype/hosts/max/plugins/load/load_max_scene.py @@ -22,12 +22,8 @@ class MaxSceneLoader(load.LoaderPlugin): path = os.path.normpath(self.fname) # import the max scene by using "merge file" path = path.replace('\\', '/') - - merge_before = set(rt.RootNode.Children) rt.MergeMaxFile(path) - - merge_after = set(rt.RootNode.Children) - max_objects = merge_after.difference(merge_before) + max_objects = rt.getLastMergedNodes() max_container = rt.Container(name=f"{name}") for max_object in max_objects: max_object.Parent = max_container @@ -40,15 +36,14 @@ class MaxSceneLoader(load.LoaderPlugin): path = get_representation_path(representation) node_name = container["instance_node"] - instance_name, _ = node_name.split("_") - merge_before = set(rt.RootNode.Children) + rt.MergeMaxFile(path, rt.Name("noRedraw"), rt.Name("deleteOldDups"), rt.Name("useSceneMtlDups")) - merge_after = set(rt.EootNode.Children) - max_objects = merge_after.difference(merge_before) - container_node = rt.GetNodeByName(instance_name) + + max_objects = rt.getLastMergedNodes() + container_node = rt.GetNodeByName(node_name) for max_object in max_objects: max_object.Parent = container_node From 3631cc5f4048edc710f51122d6c91a79e33231db Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:18:55 +0200 Subject: [PATCH 298/347] fix single root packing (#5154) --- openpype/lib/project_backpack.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/lib/project_backpack.py b/openpype/lib/project_backpack.py index 55a96664d8..91a5b76e35 100644 --- a/openpype/lib/project_backpack.py +++ b/openpype/lib/project_backpack.py @@ -125,6 +125,7 @@ def pack_project( if not only_documents: roots = project_doc["config"]["roots"] # Determine root directory of project + source_root = None source_root_name = None for root_name, root_value in roots.items(): if source_root is not None: From 10251eef8626113a74459e4572333c1f9cf35b27 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 19 Jun 2023 11:34:26 +0200 Subject: [PATCH 299/347] :bug: fix review review was depending on objectset no longer collected and index of `displayLights` was converted from index to enum string, but it is directly collected as a string now. --- .../hosts/maya/plugins/publish/collect_review.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index f7d7cfe109..ba5ce9dca0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -30,12 +30,13 @@ class CollectReview(pyblish.api.InstancePlugin): camera = cameras[0] if cameras else None context = instance.context - objectset = context.data['objectsets'] + objectset = { + i.data.get("instance_node") for i in context + } - # Convert enum attribute index to string for Display Lights. - index = instance.data.get("displayLights", 0) - display_lights = lib.DISPLAY_LIGHTS_VALUES[index] - if display_lights == "project_settings": + # Collect display lights. + display_lights = instance.data.get("displayLights", "default") + if instance.data.get("displayLights") == "project_settings": settings = instance.context.data["project_settings"] settings = settings["maya"]["publish"]["ExtractPlayblast"] settings = settings["capture_preset"]["Viewport Options"] @@ -56,7 +57,7 @@ class CollectReview(pyblish.api.InstancePlugin): burninDataMembers["focalLength"] = focal_length # Account for nested instances like model. - reviewable_subsets = list(set(members) & set(objectset)) + reviewable_subsets = list(set(members) & objectset) if reviewable_subsets: if len(reviewable_subsets) > 1: raise KnownPublishError( From 83e23972d741fc9865dbbf93da4844c1bee10def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:50:24 +0200 Subject: [PATCH 300/347] Update openpype/hosts/maya/plugins/publish/collect_review.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/collect_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py index ba5ce9dca0..3820f099c2 100644 --- a/openpype/hosts/maya/plugins/publish/collect_review.py +++ b/openpype/hosts/maya/plugins/publish/collect_review.py @@ -36,7 +36,7 @@ class CollectReview(pyblish.api.InstancePlugin): # Collect display lights. display_lights = instance.data.get("displayLights", "default") - if instance.data.get("displayLights") == "project_settings": + if display_lights == "project_settings": settings = instance.context.data["project_settings"] settings = settings["maya"]["publish"]["ExtractPlayblast"] settings = settings["capture_preset"]["Viewport Options"] From a4c63c12cf0bfb61a4e6005304d40609290132ca Mon Sep 17 00:00:00 2001 From: Alexey Bogomolov <11698866+movalex@users.noreply.github.com> Date: Mon, 19 Jun 2023 14:15:34 +0300 Subject: [PATCH 301/347] Add height, width and fps setup to project manager (#5075) * add width, height and fps setup * add corresponding ui tweaks * update docstring * remove unnecessary fallbacks * remove print * hound * remove whitespace * revert operations change * wip commit project update with new data * formatting * update the project data correctly * Update openpype/tools/project_manager/project_manager/widgets.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * show default settings, use spinbox to validate values add pixel aspec, frame start, frame end * formatting * get default anatomy settings properly * check if singlestep is set Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * not used Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * mindless code copying is evil, removed unnecesary parts Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * Update openpype/tools/project_manager/project_manager/widgets.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * Update openpype/tools/project_manager/project_manager/widgets.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * remove unused import * use integer or float instead of text Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> * import PixmapLabel from 'utils' * fix spinbox field length for macos * set aspect decimals to 2 * remove set size policy * set field growth policy for macos * add newline --------- Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/client/operations.py | 9 +- .../project_manager/widgets.py | 161 ++++++++++++------ 2 files changed, 117 insertions(+), 53 deletions(-) diff --git a/openpype/client/operations.py b/openpype/client/operations.py index ef48f2a1c4..e8c9d28636 100644 --- a/openpype/client/operations.py +++ b/openpype/client/operations.py @@ -220,7 +220,6 @@ def new_representation_doc( "parent": version_id, "name": name, "data": data, - # Imprint shortcut to context for performance reasons. "context": context } @@ -708,7 +707,11 @@ class OperationsSession(object): return operation -def create_project(project_name, project_code, library_project=False): +def create_project( + project_name, + project_code, + library_project=False, +): """Create project using OpenPype settings. This project creation function is not validating project document on @@ -752,7 +755,7 @@ def create_project(project_name, project_code, library_project=False): "name": project_name, "data": { "code": project_code, - "library_project": library_project + "library_project": library_project, }, "schema": CURRENT_PROJECT_SCHEMA } diff --git a/openpype/tools/project_manager/project_manager/widgets.py b/openpype/tools/project_manager/project_manager/widgets.py index 06ae06e4d2..3154f777df 100644 --- a/openpype/tools/project_manager/project_manager/widgets.py +++ b/openpype/tools/project_manager/project_manager/widgets.py @@ -1,4 +1,5 @@ import re +import platform from openpype.client import get_projects, create_project from .constants import ( @@ -8,13 +9,16 @@ from .constants import ( from openpype.client.operations import ( PROJECT_NAME_ALLOWED_SYMBOLS, PROJECT_NAME_REGEX, + OperationsSession, ) from openpype.style import load_stylesheet from openpype.pipeline import AvalonMongoDB from openpype.tools.utils import ( PlaceholderLineEdit, - get_warning_pixmap + get_warning_pixmap, + PixmapLabel, ) +from openpype.settings.lib import get_default_anatomy_settings from qtpy import QtWidgets, QtCore, QtGui @@ -35,7 +39,7 @@ class NameTextEdit(QtWidgets.QLineEdit): sub_regex = "[^{}]+".format(NAME_ALLOWED_SYMBOLS) new_before_text = re.sub(sub_regex, "", before_text) new_after_text = re.sub(sub_regex, "", after_text) - idx -= (len(before_text) - len(new_before_text)) + idx -= len(before_text) - len(new_before_text) self.setText(new_before_text + new_after_text) self.setCursorPosition(idx) @@ -141,13 +145,40 @@ class CreateProjectDialog(QtWidgets.QDialog): inputs_widget = QtWidgets.QWidget(self) project_name_input = QtWidgets.QLineEdit(inputs_widget) project_code_input = QtWidgets.QLineEdit(inputs_widget) + project_width_input = NumScrollWidget(0, 9999999) + project_height_input = NumScrollWidget(0, 9999999) + project_fps_input = FloatScrollWidget(1, 9999999, decimals=3, step=1) + project_aspect_input = FloatScrollWidget( + 0, 9999999, decimals=2, step=0.1 + ) + project_frame_start_input = NumScrollWidget(-9999999, 9999999) + project_frame_end_input = NumScrollWidget(-9999999, 9999999) + + default_project_data = self.get_default_attributes() + project_width_input.setValue(default_project_data["resolutionWidth"]) + project_height_input.setValue(default_project_data["resolutionHeight"]) + project_fps_input.setValue(default_project_data["fps"]) + project_aspect_input.setValue(default_project_data["pixelAspect"]) + project_frame_start_input.setValue(default_project_data["frameStart"]) + project_frame_end_input.setValue(default_project_data["frameEnd"]) + library_project_input = QtWidgets.QCheckBox(inputs_widget) inputs_layout = QtWidgets.QFormLayout(inputs_widget) + if platform.system() == "Darwin": + inputs_layout.setFieldGrowthPolicy( + QtWidgets.QFormLayout.AllNonFixedFieldsGrow + ) inputs_layout.setContentsMargins(0, 0, 0, 0) inputs_layout.addRow("Project name:", project_name_input) inputs_layout.addRow("Project code:", project_code_input) inputs_layout.addRow("Library project:", library_project_input) + inputs_layout.addRow("Width:", project_width_input) + inputs_layout.addRow("Height:", project_height_input) + inputs_layout.addRow("FPS:", project_fps_input) + inputs_layout.addRow("Aspect:", project_aspect_input) + inputs_layout.addRow("Frame Start:", project_frame_start_input) + inputs_layout.addRow("Frame End:", project_frame_end_input) project_name_label = QtWidgets.QLabel(self) project_code_label = QtWidgets.QLabel(self) @@ -183,6 +214,12 @@ class CreateProjectDialog(QtWidgets.QDialog): self.project_name_input = project_name_input self.project_code_input = project_code_input self.library_project_input = library_project_input + self.project_width_input = project_width_input + self.project_height_input = project_height_input + self.project_fps_input = project_fps_input + self.project_aspect_input = project_aspect_input + self.project_frame_start_input = project_frame_start_input + self.project_frame_end_input = project_frame_end_input self.ok_btn = ok_btn @@ -190,6 +227,10 @@ class CreateProjectDialog(QtWidgets.QDialog): def project_name(self): return self.project_name_input.text() + def get_default_attributes(self): + settings = get_default_anatomy_settings() + return settings["attributes"] + def _on_project_name_change(self, value): if self._project_code_value is None: self._ignore_code_change = True @@ -215,12 +256,12 @@ class CreateProjectDialog(QtWidgets.QDialog): is_valid = False elif value in self.invalid_project_names: - message = "Project name \"{}\" already exist".format(value) + message = 'Project name "{}" already exist'.format(value) is_valid = False elif not PROJECT_NAME_REGEX.match(value): message = ( - "Project name \"{}\" contain not supported symbols" + 'Project name "{}" contain not supported symbols' ).format(value) is_valid = False @@ -237,12 +278,12 @@ class CreateProjectDialog(QtWidgets.QDialog): is_valid = False elif value in self.invalid_project_names: - message = "Project code \"{}\" already exist".format(value) + message = 'Project code "{}" already exist'.format(value) is_valid = False elif not PROJECT_NAME_REGEX.match(value): message = ( - "Project code \"{}\" contain not supported symbols" + 'Project code "{}" contain not supported symbols' ).format(value) is_valid = False @@ -264,9 +305,35 @@ class CreateProjectDialog(QtWidgets.QDialog): project_name = self.project_name_input.text() project_code = self.project_code_input.text() - library_project = self.library_project_input.isChecked() - create_project(project_name, project_code, library_project) + project_width = self.project_width_input.value() + project_height = self.project_height_input.value() + project_fps = self.project_fps_input.value() + project_aspect = self.project_aspect_input.value() + project_frame_start = self.project_frame_start_input.value() + project_frame_end = self.project_frame_end_input.value() + library_project = self.library_project_input.isChecked() + project_doc = create_project( + project_name, + project_code, + library_project, + ) + update_data = { + "data.resolutionWidth": project_width, + "data.resolutionHeight": project_height, + "data.fps": project_fps, + "data.pixelAspect": project_aspect, + "data.frameStart": project_frame_start, + "data.frameEnd": project_frame_end, + } + session = OperationsSession() + session.update_entity( + project_name, + project_doc["type"], + project_doc["_id"], + update_data, + ) + session.commit() self.done(1) def _get_existing_projects(self): @@ -288,45 +355,15 @@ class CreateProjectDialog(QtWidgets.QDialog): return project_names, project_codes -# TODO PixmapLabel should be moved to 'utils' in other future PR so should be -# imported from there -class PixmapLabel(QtWidgets.QLabel): - """Label resizing image to height of font.""" - def __init__(self, pixmap, parent): - super(PixmapLabel, self).__init__(parent) - self._empty_pixmap = QtGui.QPixmap(0, 0) - self._source_pixmap = pixmap - - def set_source_pixmap(self, pixmap): - """Change source image.""" - self._source_pixmap = pixmap - self._set_resized_pix() - +class ProjectManagerPixmapLabel(PixmapLabel): def _get_pix_size(self): size = self.fontMetrics().height() * 4 return size, size - def _set_resized_pix(self): - if self._source_pixmap is None: - self.setPixmap(self._empty_pixmap) - return - width, height = self._get_pix_size() - self.setPixmap( - self._source_pixmap.scaled( - width, - height, - QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation - ) - ) - - def resizeEvent(self, event): - self._set_resized_pix() - super(PixmapLabel, self).resizeEvent(event) - class ConfirmProjectDeletion(QtWidgets.QDialog): """Dialog which confirms deletion of a project.""" + def __init__(self, project_name, parent): super(ConfirmProjectDeletion, self).__init__(parent) @@ -335,23 +372,26 @@ class ConfirmProjectDeletion(QtWidgets.QDialog): top_widget = QtWidgets.QWidget(self) warning_pixmap = get_warning_pixmap() - warning_icon_label = PixmapLabel(warning_pixmap, top_widget) + warning_icon_label = ProjectManagerPixmapLabel( + warning_pixmap, top_widget + ) message_label = QtWidgets.QLabel(top_widget) message_label.setWordWrap(True) message_label.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) - message_label.setText(( - "WARNING: This cannot be undone.

" - "Project \"{}\" with all related data will be" - " permanently removed from the database. (This action won't remove" - " any files on disk.)" - ).format(project_name)) + message_label.setText( + ( + "WARNING: This cannot be undone.

" + 'Project "{}" with all related data will be' + " permanently removed from the database." + " (This action won't remove any files on disk.)" + ).format(project_name) + ) top_layout = QtWidgets.QHBoxLayout(top_widget) top_layout.setContentsMargins(0, 0, 0, 0) top_layout.addWidget( - warning_icon_label, 0, - QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter + warning_icon_label, 0, QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter ) top_layout.addWidget(message_label, 1) @@ -359,7 +399,7 @@ class ConfirmProjectDeletion(QtWidgets.QDialog): confirm_input = PlaceholderLineEdit(self) confirm_input.setPlaceholderText( - "Type \"{}\" to confirm...".format(project_name) + 'Type "{}" to confirm...'.format(project_name) ) cancel_btn = QtWidgets.QPushButton("Cancel", self) @@ -429,6 +469,7 @@ class ConfirmProjectDeletion(QtWidgets.QDialog): class SpinBoxScrollFixed(QtWidgets.QSpinBox): """QSpinBox which only allow edits change with scroll wheel when active""" + def __init__(self, *args, **kwargs): super(SpinBoxScrollFixed, self).__init__(*args, **kwargs) self.setFocusPolicy(QtCore.Qt.StrongFocus) @@ -442,6 +483,7 @@ class SpinBoxScrollFixed(QtWidgets.QSpinBox): class DoubleSpinBoxScrollFixed(QtWidgets.QDoubleSpinBox): """QDoubleSpinBox which only allow edits with scroll wheel when active""" + def __init__(self, *args, **kwargs): super(DoubleSpinBoxScrollFixed, self).__init__(*args, **kwargs) self.setFocusPolicy(QtCore.Qt.StrongFocus) @@ -451,3 +493,22 @@ class DoubleSpinBoxScrollFixed(QtWidgets.QDoubleSpinBox): event.ignore() else: super(DoubleSpinBoxScrollFixed, self).wheelEvent(event) + + +class NumScrollWidget(SpinBoxScrollFixed): + def __init__(self, minimum, maximum): + super(NumScrollWidget, self).__init__() + self.setMaximum(maximum) + self.setMinimum(minimum) + self.setButtonSymbols(QtWidgets.QSpinBox.NoButtons) + + +class FloatScrollWidget(DoubleSpinBoxScrollFixed): + def __init__(self, minimum, maximum, decimals, step=None): + super(FloatScrollWidget, self).__init__() + self.setMaximum(maximum) + self.setMinimum(minimum) + self.setDecimals(decimals) + if step is not None: + self.setSingleStep(step) + self.setButtonSymbols(QtWidgets.QSpinBox.NoButtons) From 3314c5f282539f6688ee24ee583e53e95c6c8e04 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 19 Jun 2023 21:57:30 +0800 Subject: [PATCH 302/347] use createOptions() for defaultArnoldRenderOptions --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index 1a582647cc..d71b40e877 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -42,8 +42,10 @@ class ArnoldStandinLoader(load.LoaderPlugin): # does not exist yet and some connections to the standin # can't be correctly generated on create resulting in an error cmds.loadPlugin("mtoa") - cmds.refresh(force=True) - maya.utils.processIdleEvents() + # create defaultArnoldRenderOptions for + # `defaultArnoldRenderOptions.operator`` + from mtoa.core import createOptions + createOptions() import mtoa.ui.arnoldmenu From 92c6cee333d08bb685574f896d33abc253a6d220 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 19 Jun 2023 21:58:31 +0800 Subject: [PATCH 303/347] hound fix --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index d71b40e877..a12ecf8f9f 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -2,7 +2,6 @@ import os import clique import maya.cmds as cmds -import maya.utils from openpype.settings import get_project_settings from openpype.pipeline import ( From 3020ccd90abdf8528052c07eb437e4bd065721f1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 19 Jun 2023 23:00:44 +0800 Subject: [PATCH 304/347] update docstring --- .../hosts/maya/plugins/load/load_arnold_standin.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index a12ecf8f9f..a085f8d575 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -36,13 +36,11 @@ class ArnoldStandinLoader(load.LoaderPlugin): def load(self, context, name, namespace, options): if not cmds.pluginInfo("mtoa", query=True, loaded=True): - # Allow mtoa plugin load to process all its events - # because otherwise `defaultArnoldRenderOptions.operator` - # does not exist yet and some connections to the standin - # can't be correctly generated on create resulting in an error cmds.loadPlugin("mtoa") - # create defaultArnoldRenderOptions for - # `defaultArnoldRenderOptions.operator`` + # create defaultArnoldRenderOptions before creating aiStandin + # which tried to connect it. Since we load the plugin and directly + # create aiStandin without the defaultArnoldRenderOptions, + # here needs to create the render options for aiStandin creation. from mtoa.core import createOptions createOptions() From 83a37eaa76292936e16fddea238ad4ca3045d3de Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 19 Jun 2023 16:11:43 +0100 Subject: [PATCH 305/347] Account for multiple descriptions on single geometry. --- openpype/hosts/maya/plugins/publish/collect_xgen.py | 11 ++++++----- openpype/hosts/maya/plugins/publish/extract_xgen.py | 6 ++---- openpype/hosts/maya/plugins/publish/validate_xgen.py | 4 +--- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_xgen.py b/openpype/hosts/maya/plugins/publish/collect_xgen.py index da0549b2d8..a065b6498a 100644 --- a/openpype/hosts/maya/plugins/publish/collect_xgen.py +++ b/openpype/hosts/maya/plugins/publish/collect_xgen.py @@ -30,12 +30,13 @@ class CollectXgen(pyblish.api.InstancePlugin): if data["xgmPalettes"]: data["xgmPalette"] = data["xgmPalettes"][0] - data["xgenConnections"] = {} + data["xgenConnections"] = [] for node in data["xgmSubdPatches"]: - data["xgenConnections"][node] = {} - for attr in ["transform", "geometry"]: - input = get_attribute_input("{}.{}".format(node, attr)) - data["xgenConnections"][node][attr] = input + connected_transform = get_attribute_input( + node + ".transform" + ).split(".")[0] + if connected_transform not in data["xgenConnections"]: + data["xgenConnections"].append(connected_transform) # Collect all files under palette root as resources. import xgenm diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index fb097ca84a..9b4e5e403d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -51,11 +51,9 @@ class ExtractXgen(publish.Extractor): with delete_after() as delete_bin: duplicate_nodes = [] # Collect nodes to export. - for _, connections in instance.data["xgenConnections"].items(): - transform_name = connections["transform"].split(".")[0] - + for node in instance.data["xgenConnections"]: # Duplicate_transform subd patch geometry. - duplicate_transform = cmds.duplicate(transform_name)[0] + duplicate_transform = cmds.duplicate(node)[0] delete_bin.append(duplicate_transform) # Discard the children. diff --git a/openpype/hosts/maya/plugins/publish/validate_xgen.py b/openpype/hosts/maya/plugins/publish/validate_xgen.py index 47b24e218c..a44fa56308 100644 --- a/openpype/hosts/maya/plugins/publish/validate_xgen.py +++ b/openpype/hosts/maya/plugins/publish/validate_xgen.py @@ -61,9 +61,7 @@ class ValidateXgen(pyblish.api.InstancePlugin): # We need a namespace else there will be a naming conflict when # extracting because of stripping namespaces and parenting to world. node_names = [instance.data["xgmPalette"]] - for _, connections in instance.data["xgenConnections"].items(): - node_names.append(connections["transform"].split(".")[0]) - + node_names.extend(instance.data["xgenConnections"]) non_namespaced_nodes = [n for n in node_names if ":" not in n] if non_namespaced_nodes: raise PublishValidationError( From 4ffab0bb678855d2b963d63cb982c614099cf447 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 19 Jun 2023 23:20:08 +0800 Subject: [PATCH 306/347] update docstring --- openpype/hosts/maya/plugins/load/load_arnold_standin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_arnold_standin.py b/openpype/hosts/maya/plugins/load/load_arnold_standin.py index a085f8d575..38a7adfd7d 100644 --- a/openpype/hosts/maya/plugins/load/load_arnold_standin.py +++ b/openpype/hosts/maya/plugins/load/load_arnold_standin.py @@ -37,10 +37,10 @@ class ArnoldStandinLoader(load.LoaderPlugin): def load(self, context, name, namespace, options): if not cmds.pluginInfo("mtoa", query=True, loaded=True): cmds.loadPlugin("mtoa") - # create defaultArnoldRenderOptions before creating aiStandin - # which tried to connect it. Since we load the plugin and directly + # Create defaultArnoldRenderOptions before creating aiStandin + # which tries to connect it. Since we load the plugin and directly # create aiStandin without the defaultArnoldRenderOptions, - # here needs to create the render options for aiStandin creation. + # we need to create the render options for aiStandin creation. from mtoa.core import createOptions createOptions() From 82ae12695b52ae9f6a3de4ed4b7f99f140170238 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 19 Jun 2023 16:26:57 +0100 Subject: [PATCH 307/347] Update openpype/hosts/maya/plugins/publish/collect_xgen.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/collect_xgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_xgen.py b/openpype/hosts/maya/plugins/publish/collect_xgen.py index a065b6498a..d41b87e25b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_xgen.py +++ b/openpype/hosts/maya/plugins/publish/collect_xgen.py @@ -30,7 +30,7 @@ class CollectXgen(pyblish.api.InstancePlugin): if data["xgmPalettes"]: data["xgmPalette"] = data["xgmPalettes"][0] - data["xgenConnections"] = [] + data["xgenConnections"] = set() for node in data["xgmSubdPatches"]: connected_transform = get_attribute_input( node + ".transform" From d2de6f1c7221f74774f1af0e406e4af1d4ca9f17 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Mon, 19 Jun 2023 16:27:04 +0100 Subject: [PATCH 308/347] Update openpype/hosts/maya/plugins/publish/collect_xgen.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/collect_xgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_xgen.py b/openpype/hosts/maya/plugins/publish/collect_xgen.py index d41b87e25b..e7c730cbf4 100644 --- a/openpype/hosts/maya/plugins/publish/collect_xgen.py +++ b/openpype/hosts/maya/plugins/publish/collect_xgen.py @@ -36,7 +36,7 @@ class CollectXgen(pyblish.api.InstancePlugin): node + ".transform" ).split(".")[0] if connected_transform not in data["xgenConnections"]: - data["xgenConnections"].append(connected_transform) + data["xgenConnections"].add(connected_transform) # Collect all files under palette root as resources. import xgenm From b365fc7534f14dc90ba183eca1e38b650eb7a836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Jun 2023 18:37:33 +0200 Subject: [PATCH 309/347] Update openpype/hosts/max/api/plugin.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/max/api/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 4fc852e2fe..4c1dbb2810 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -110,7 +110,7 @@ class MaxCreatorBase(object): @staticmethod def cache_subsets(shared_data): - if shared_data.get("max_cached_subsets"): + if shared_data.get("max_cached_subsets") is not None: return shared_data shared_data["max_cached_subsets"] = {} From bc33407f28dcd99a22d6239031f52dc375ebeb41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Mon, 19 Jun 2023 18:39:54 +0200 Subject: [PATCH 310/347] Update openpype/hosts/max/plugins/publish/extract_pointcloud.py Co-authored-by: Kayla Man <64118225+moonyuet@users.noreply.github.com> --- openpype/hosts/max/plugins/publish/extract_pointcloud.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_pointcloud.py b/openpype/hosts/max/plugins/publish/extract_pointcloud.py index 618f9856fd..583bbb6dbd 100644 --- a/openpype/hosts/max/plugins/publish/extract_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/extract_pointcloud.py @@ -138,9 +138,9 @@ class ExtractPointCloud(publish.Extractor): sub_anim = rt.GetSubAnim(obj, anim_name) boolean = rt.IsProperty(sub_anim, "Export_Particles") if boolean: - event_name = sub_anim.Name - opt = f"${member.Name}.{event_name}.export_particles" - opt_list.append(opt) + event_name = sub_anim.Name + opt = f"${member.Name}.{event_name}.export_particles" + opt_list.append(opt) return opt_list From 203e29c45117668a4ed0281dc42e3893ae897d3f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 19 Jun 2023 19:03:21 +0200 Subject: [PATCH 311/347] :rewind: in unrelated file --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 42cacdc93c..10cca3eb3f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,4 +28,4 @@ omit = /tests directory = ./coverage [tool:pytest] -norecursedirs = repos/* openpype/modules/ftrack/* +norecursedirs = repos/* openpype/modules/ftrack/* \ No newline at end of file From 33b9e16635fcfad0285d18e6e15fe526cb64df4b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 19 Jun 2023 21:11:47 +0100 Subject: [PATCH 312/347] Ensure workspace xgen file is overwritten and update UI. --- openpype/hosts/maya/plugins/load/load_xgen.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/load_xgen.py b/openpype/hosts/maya/plugins/load/load_xgen.py index 7e6cabc77c..16f2e8e842 100644 --- a/openpype/hosts/maya/plugins/load/load_xgen.py +++ b/openpype/hosts/maya/plugins/load/load_xgen.py @@ -1,4 +1,5 @@ import os +import shutil import maya.cmds as cmds import xgenm @@ -116,8 +117,8 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): def update(self, container, representation): """Workflow for updating Xgen. - - Copy and potentially overwrite the workspace .xgen file. - Export changes to delta file. + - Copy and overwrite the workspace .xgen file. - Set collection attributes to not include delta files. - Update xgen maya file reference. - Apply the delta file changes. @@ -130,6 +131,10 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): There is an implicit increment of the xgen and delta files, due to using the workfile basename. """ + # Storing current description to try and maintain later. + current_description = ( + xgenm.xgGlobal.DescriptionEditor.currentDescription() + ) container_node = container["objectName"] members = get_container_members(container_node) @@ -160,6 +165,7 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): data_path ) data = {"xgProjectPath": project_path, "xgDataPath": data_path} + shutil.copy(new_xgen_file, xgen_file) write_xgen_file(data, xgen_file) attribute_data = { @@ -171,3 +177,11 @@ class XgenLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): super().update(container, representation) xgenm.applyDelta(xgen_palette.replace("|", ""), xgd_file) + + # Restore current selected description if it exists. + if cmds.objExists(current_description): + xgenm.xgGlobal.DescriptionEditor.setCurrentDescription( + current_description + ) + # Full UI refresh. + xgenm.xgGlobal.DescriptionEditor.refresh("Full") From a72050e71c1de86f7cef5b6707858fdc7bf0c4be Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 20 Jun 2023 17:53:42 +0800 Subject: [PATCH 313/347] allows both meshes and groups for assexport --- .../publish/collect_arnold_scene_source.py | 38 +++++++++++++++---- .../publish/extract_arnold_scene_source.py | 4 +- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index 34db3faa2f..1b8ec27509 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -23,22 +23,44 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): continue if objset.endswith("content_SET"): members = cmds.ls(members, long=True) + members_list = [] + for member in members: + shape = cmds.listRelatives( + member, shapes=True, fullPath=True) + if not shape: + continue + members_list = members + shape + group_name = "|{}".format(member) + if group_name in members_list: + members_list.remove(group_name) + children = get_all_children(members) + + if members_list: + children.extend(members_list) instance.data["contentMembers"] = children - # TODO: include another instance.data for content members - instance.data["contentMembersTransform"] = members self.log.debug("content members: {}".format(children)) - self.log.debug( - "content members transform : {}".format(members)) + elif objset.endswith("proxy_SET"): proxy_members = cmds.ls(members, long=True) + proxy_list = [] + for proxy in proxy_members: + shape = cmds.listRelatives( + proxy, shapes=True, fullPath=True) + if not shape: + continue + proxy_list = proxy_members + shape + group_name = "|{}".format(proxy) + if group_name in proxy_list: + proxy_list.remove(group_name) + set_members = get_all_children(proxy_members) + if proxy_list: + set_members.extend(proxy_list) + instance.data["proxy"] = set_members - instance.data["proxyTransform"] = proxy_members - # TODO: include another instance.data for proxy self.log.debug("proxy members: {}".format(set_members)) - self.log.debug( - "proxy members transform: {}".format(proxy_members)) + # Use camera in object set if present else default to render globals # camera. diff --git a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py index f49d8ece57..8696f5a94b 100644 --- a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -72,7 +72,7 @@ class ExtractArnoldSceneSource(publish.Extractor): # TODO: dont use instance.data["contentMembers"] # but use the new instance.data["contentMemberTransforms"] filenames, nodes_by_id = self._extract( - instance.data["contentMembersTransform"], attribute_data, kwargs + instance.data["contentMembers"], attribute_data, kwargs ) if "representations" not in instance.data: @@ -113,7 +113,7 @@ class ExtractArnoldSceneSource(publish.Extractor): # TODO: dont use instance.data["proxy"] # but use the new instance.data["proxyTransforms"] filenames, _ = self._extract( - instance.data["proxyTransform"], attribute_data, kwargs + instance.data["proxy"], attribute_data, kwargs ) representation = { From 9b4641de65a1d20a66d4f78b30a9c1b8448a4d2e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 20 Jun 2023 17:56:16 +0800 Subject: [PATCH 314/347] rename the setting panel --- openpype/hosts/nuke/startup/custom_write_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index a221a26424..ef3923f40a 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -62,7 +62,7 @@ knobs_setting = { class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): """ Write Node's Knobs Settings Panel """ def __init__(self): - nukescripts.PythonPanel.__init__(self, "Set Knobs Value(Write Node)") + nukescripts.PythonPanel.__init__(self, "Set Presets(Write Node)") knobs_value, _ = self.get_node_knobs_setting() # create knobs From f0bf7d8c665b23dbd19ae2cda7da47ef233dd741 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 20 Jun 2023 17:57:45 +0800 Subject: [PATCH 315/347] remove hardcoded frame padding --- openpype/hosts/nuke/startup/custom_write_node.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index ef3923f40a..1db235b71a 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -9,7 +9,6 @@ from openpype.hosts.nuke.api.lib import ( ) -frame_padding = 5 temp_rendering_path_template = ( "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}") @@ -62,7 +61,7 @@ knobs_setting = { class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): """ Write Node's Knobs Settings Panel """ def __init__(self): - nukescripts.PythonPanel.__init__(self, "Set Presets(Write Node)") + nukescripts.PythonPanel.__init__(self, "Set Preset(Write Node)") knobs_value, _ = self.get_node_knobs_setting() # create knobs From 1ae4cbcdad521fcbda19cf868973b0f0ab470322 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 20 Jun 2023 17:59:33 +0800 Subject: [PATCH 316/347] use preset name --- openpype/hosts/nuke/startup/custom_write_node.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 1db235b71a..9628876d0a 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -61,13 +61,13 @@ knobs_setting = { class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): """ Write Node's Knobs Settings Panel """ def __init__(self): - nukescripts.PythonPanel.__init__(self, "Set Preset(Write Node)") + nukescripts.PythonPanel.__init__(self,"Set Knobs Value(Write Node)") - knobs_value, _ = self.get_node_knobs_setting() + preset_name, _ = self.get_node_knobs_setting() # create knobs self.selected_preset_name = nuke.Enumeration_Knob( - 'preset_selector', 'presets', knobs_value) + 'preset_selector', 'presets', preset_name) # add knobs to panel self.addKnob(self.selected_preset_name) @@ -79,11 +79,11 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): node_knobs = self.selected_preset_name.value() ext = None knobs = knobs_setting["knobs"] - knobs_value, node_knobs_settings = ( + preset_name, node_knobs_settings = ( self.get_node_knobs_setting(node_knobs) ) - if node_knobs and knobs_value: + if node_knobs and preset_name: if not node_knobs_settings: nuke.message("No knobs value found in subset group..\nDefault setting will be used..") # noqa else: @@ -118,7 +118,7 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): set_node_knobs_from_settings(write_node, knobs) def get_node_knobs_setting(self, value=None): - knobs_value = [] + preset_name = [] knobs_nodes = [] settings = [ node @@ -134,9 +134,9 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): for setting in settings: if setting["nukeNodeClass"] == "Write" and setting["subsets"]: for knob in setting["subsets"]: - knobs_value.append(knob) + preset_name.append(knob) - return knobs_value, knobs_nodes + return preset_name, knobs_nodes def main(): From 02db5c6792d7e5a89c6243f73e5b11947ee66688 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 20 Jun 2023 18:00:11 +0800 Subject: [PATCH 317/347] hound fix --- openpype/hosts/nuke/startup/custom_write_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 9628876d0a..ff4a3a41eb 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -61,7 +61,7 @@ knobs_setting = { class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): """ Write Node's Knobs Settings Panel """ def __init__(self): - nukescripts.PythonPanel.__init__(self,"Set Knobs Value(Write Node)") + nukescripts.PythonPanel.__init__(self, "Set Knobs Value(Write Node)") preset_name, _ = self.get_node_knobs_setting() # create knobs From c920b0ac5accb84982e0bf61d903c9ddc06b701a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 20 Jun 2023 19:22:28 +0800 Subject: [PATCH 318/347] rename the variable and clean up the code --- .../hosts/nuke/startup/custom_write_node.py | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index ff4a3a41eb..007971fc27 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -74,24 +74,29 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): def process(self): """ Process the panel values. """ write_selected_nodes = [ - s for s in nuke.selectedNodes() if s.Class() == "Write"] + selected_nodes for selected_nodes in nuke.selectedNodes() + if selected_nodes.Class() == "Write"] - node_knobs = self.selected_preset_name.value() + selected_preset = self.selected_preset_name.value() ext = None knobs = knobs_setting["knobs"] - preset_name, node_knobs_settings = ( - self.get_node_knobs_setting(node_knobs) + preset_name, node_knobs_presets = ( + self.get_node_knobs_setting(selected_preset) ) - if node_knobs and preset_name: - if not node_knobs_settings: - nuke.message("No knobs value found in subset group..\nDefault setting will be used..") # noqa + if selected_preset and preset_name: + if not node_knobs_presets: + nuke.message( + "No knobs value found in subset group.." + "\nDefault setting will be used..") else: - knobs = node_knobs_settings + knobs = node_knobs_presets ext_knob_list = [knob for knob in knobs if knob["name"] == "file_type"] if not ext_knob_list: - nuke.message("ERROR: No file type found in the subset's knobs.\nPlease add one to complete setting up the node") # noqa + nuke.message( + "ERROR: No file type found in the subset's knobs." + "\nPlease add one to complete setting up the node") return else: for knob in ext_knob_list: @@ -117,24 +122,24 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): write_node["file"].setValue(file_path) set_node_knobs_from_settings(write_node, knobs) - def get_node_knobs_setting(self, value=None): + def get_node_knobs_setting(self, selected_preset=None): preset_name = [] knobs_nodes = [] settings = [ - node - for node in get_nuke_imageio_settings()["nodes"]["overrideNodes"] + node_settings + for node_settings in get_nuke_imageio_settings()["nodes"]["overrideNodes"] + if node_settings["nukeNodeClass"] == "Write" and node_settings["subsets"] ] if not settings: return for i, _ in enumerate(settings): - if value in settings[i]["subsets"]: + if selected_preset in settings[i]["subsets"]: knobs_nodes = settings[i]["knobs"] for setting in settings: - if setting["nukeNodeClass"] == "Write" and setting["subsets"]: - for knob in setting["subsets"]: - preset_name.append(knob) + for subset in setting["subsets"]: + preset_name.append(subset) return preset_name, knobs_nodes From 3da8dcc7fea93a0b844121acce82ef6803f3f275 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 20 Jun 2023 19:23:32 +0800 Subject: [PATCH 319/347] hound fix --- openpype/hosts/nuke/startup/custom_write_node.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 007971fc27..2052a5a09e 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -126,8 +126,8 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): preset_name = [] knobs_nodes = [] settings = [ - node_settings - for node_settings in get_nuke_imageio_settings()["nodes"]["overrideNodes"] + node_settings for node_settings + in get_nuke_imageio_settings()["nodes"]["overrideNodes"] if node_settings["nukeNodeClass"] == "Write" and node_settings["subsets"] ] if not settings: From 7d99e8c8cf8941e6856e669dfe5134e8a71983fc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 20 Jun 2023 19:24:41 +0800 Subject: [PATCH 320/347] hound fix --- openpype/hosts/nuke/startup/custom_write_node.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/startup/custom_write_node.py b/openpype/hosts/nuke/startup/custom_write_node.py index 2052a5a09e..ea53725834 100644 --- a/openpype/hosts/nuke/startup/custom_write_node.py +++ b/openpype/hosts/nuke/startup/custom_write_node.py @@ -128,7 +128,8 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): settings = [ node_settings for node_settings in get_nuke_imageio_settings()["nodes"]["overrideNodes"] - if node_settings["nukeNodeClass"] == "Write" and node_settings["subsets"] + if node_settings["nukeNodeClass"] == "Write" + and node_settings["subsets"] ] if not settings: return From 9cc22939794fb1710c43ca7df28e16f81b07617d Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 20 Jun 2023 14:04:17 +0200 Subject: [PATCH 321/347] :recycle: make code compatible with 3dsmax 2022 python 3.7 in 3dsmax 2022 is not supporting walrus operator --- openpype/hosts/max/api/plugin.py | 4 +++- .../hosts/max/plugins/create/create_render.py | 3 ++- .../publish/validate_camera_contents.py | 3 ++- .../publish/validate_model_contents.py | 3 ++- .../plugins/publish/validate_pointcloud.py | 19 ++++++++++++------- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/api/plugin.py b/openpype/hosts/max/api/plugin.py index 4c1dbb2810..71a0b94e1f 100644 --- a/openpype/hosts/max/api/plugin.py +++ b/openpype/hosts/max/api/plugin.py @@ -207,7 +207,9 @@ class MaxCreator(Creator, MaxCreatorBase): """ for instance in instances: - if instance_node := rt.GetNodeByName(instance.data.get("instance_node")): # noqa + instance_node = rt.GetNodeByName( + instance.data.get("instance_node")) + if instance_node: count = rt.custAttributes.count(instance_node) rt.custAttributes.delete(instance_node, count) rt.Delete(instance_node) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 41e49f4620..235046684e 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -24,7 +24,8 @@ class CreateRender(plugin.MaxCreator): instance_data, pre_create_data) container_name = instance.data.get("instance_node") - if sel_obj := self.selected_nodes: + sel_obj = self.selected_nodes + if sel_obj: # set viewport camera for rendering(mandatory for deadline) RenderSettings(self.project_settings).set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) diff --git a/openpype/hosts/max/plugins/publish/validate_camera_contents.py b/openpype/hosts/max/plugins/publish/validate_camera_contents.py index 85be5d59fa..ab13e5dc05 100644 --- a/openpype/hosts/max/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_camera_contents.py @@ -18,7 +18,8 @@ class ValidateCameraContent(pyblish.api.InstancePlugin): "$Physical_Camera", "$Target"] def process(self, instance): - if invalid := self.get_invalid(instance): # noqa + invalid = self.get_invalid(instance) + if invalid: raise PublishValidationError(("Camera instance must only include" "camera (and camera target). " f"Invalid content {invalid}")) diff --git a/openpype/hosts/max/plugins/publish/validate_model_contents.py b/openpype/hosts/max/plugins/publish/validate_model_contents.py index 1ec08d9c5f..cfe016f03f 100644 --- a/openpype/hosts/max/plugins/publish/validate_model_contents.py +++ b/openpype/hosts/max/plugins/publish/validate_model_contents.py @@ -18,7 +18,8 @@ class ValidateModelContent(pyblish.api.InstancePlugin): label = "Model Contents" def process(self, instance): - if invalid := self.get_invalid(instance): # noqa + invalid = self.get_invalid(instance) + if invalid: raise PublishValidationError(("Model instance must only include" "Geometry and Editable Mesh. " f"Invalid types on: {invalid}")) diff --git a/openpype/hosts/max/plugins/publish/validate_pointcloud.py b/openpype/hosts/max/plugins/publish/validate_pointcloud.py index e1c2151c9d..1ff6eb126f 100644 --- a/openpype/hosts/max/plugins/publish/validate_pointcloud.py +++ b/openpype/hosts/max/plugins/publish/validate_pointcloud.py @@ -35,12 +35,16 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): """ report = [] - if invalid := self.get_tyflow_object(instance): # noqa - report.append(f"Non tyFlow object found: {invalid}") - if invalid := self.get_tyflow_operator(instance): # noqa - report.append( - f"tyFlow ExportParticle operator not found: {invalid}") + invalid_object = self.get_tyflow_object(instance) + if invalid_object: + report.append(f"Non tyFlow object found: {invalid_object}") + + invalid_operator = self.get_tyflow_operator(instance) + if invalid_operator: + report.append(( + "tyFlow ExportParticle operator not " + f"found: {invalid_operator}")) if self.validate_export_mode(instance): report.append("The export mode is not at PRT") @@ -49,9 +53,10 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): report.append(("tyFlow Partition setting is " "not at the default value")) - if invalid := self.validate_custom_attribute(instance): # noqa + invalid_attribute = self.validate_custom_attribute(instance) + if invalid_attribute: report.append(("Custom Attribute not found " - f":{invalid}")) + f":{invalid_attribute}")) if report: raise PublishValidationError(f"{report}") From b94b7ff5de4af8b27a171bfae5c88c0ea46d4d70 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 20 Jun 2023 16:50:49 +0200 Subject: [PATCH 322/347] backward compatibility fix studio could have set host config paths but those were not resolving to custom config OCIO environment path. Instead it was always falling back to global ocio config paths settings --- openpype/pipeline/colorspace.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 1999ad3bed..13846e9f5c 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -377,6 +377,10 @@ def get_imageio_config( activate_host_color_management = imageio_host.get( "activate_host_color_management", True) + # TODO: remove this in future - backward compatibility + if activate_host_color_management is None: + activate_host_color_management = host_ocio_config.get("enabled", False) + if not activate_host_color_management: # if host settings are disabled return False because # it is expected that no colorspace management is needed From 6f8d47d36bfb948a09bd79891c739d7179579cc3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 20 Jun 2023 16:52:03 +0200 Subject: [PATCH 323/347] nuke: user prompt if config path changes environment variable cannot be changed dynamically and user should now that settings had been changed and the nuke should be reopened. --- openpype/hosts/nuke/api/lib.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index c05182ce97..e3b34222d4 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2069,6 +2069,35 @@ class WorkfileSettings(object): log.debug("nuke.root()['{}'] changed to: {}".format( knob, value_)) + # set ocio config path + if config_data: + current_ocio_path = os.getenv("OCIO") + if current_ocio_path != config_data["path"]: + message = """ +It seems like there's a mismatch between the OCIO config path set in your Nuke +settings and the actual path set in your OCIO environment. + +To resolve this, please follow these steps: +1. Close Nuke if it's currently open. +2. Reopen Nuke. + +Please note the paths for your reference: + +- The OCIO environment path currently set: + `{env_path}` + +- The path in your current Nuke settings: + `{settings_path}` + +Reopening Nuke should synchronize these paths and resolve any discrepancies. +""" + nuke.message( + message.format( + env_path=current_ocio_path, + settings_path=config_data["path"] + ) + ) + def set_writes_colorspace(self): ''' Adds correct colorspace to write node dict From a5b93a9062dc957b06f1b99e10b0298318bda909 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 20 Jun 2023 16:30:44 +0100 Subject: [PATCH 324/347] Copy shading assignments. --- openpype/hosts/maya/plugins/publish/extract_xgen.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen.py b/openpype/hosts/maya/plugins/publish/extract_xgen.py index fb097ca84a..17b60e3108 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen.py @@ -88,6 +88,18 @@ class ExtractXgen(publish.Extractor): delete_bin.append(palette) + # Copy shading assignments. + nodes = ( + instance.data["xgmDescriptions"] + + instance.data["xgmSubdPatches"] + ) + for node in nodes: + target_node = node.split(":")[-1] + shading_engine = cmds.listConnections( + node, type="shadingEngine" + )[0] + cmds.sets(target_node, edit=True, forceElement=shading_engine) + # Export duplicated palettes. xgenm.exportPalette(palette, xgen_path) From 8d621f9fbfb962fcde09c97bdc09080f7f0370cd Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 20 Jun 2023 17:42:34 +0100 Subject: [PATCH 325/347] Fix patches export. --- openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py index 20e1bd37d8..0d2a97bc4b 100644 --- a/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py +++ b/openpype/hosts/maya/plugins/publish/extract_workfile_xgen.py @@ -57,7 +57,7 @@ class ExtractWorkfileXgen(publish.Extractor): continue render_start_frame = instance.data["frameStart"] - render_end_frame = instance.data["frameStart"] + render_end_frame = instance.data["frameEnd"] if start_frame is None: start_frame = render_start_frame From b18b44fd984112e9815811b474205a7923d6e7d9 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 21 Jun 2023 03:25:18 +0000 Subject: [PATCH 326/347] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 9c5a60964b..3a218f3a06 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.11-nightly.3" +__version__ = "3.15.11-nightly.4" From 588148cb9f92b824d15cd055f93e006c931b8559 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Jun 2023 03:26:01 +0000 Subject: [PATCH 327/347] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 2fd2780e55..203ac1df23 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to OpenPype Tray options: + - 3.15.11-nightly.4 - 3.15.11-nightly.3 - 3.15.11-nightly.2 - 3.15.11-nightly.1 @@ -134,7 +135,6 @@ body: - 3.14.3 - 3.14.3-nightly.7 - 3.14.3-nightly.6 - - 3.14.3-nightly.5 validations: required: true - type: dropdown From 7dd30b505d267074af625c1b24175e8e8a51df7e Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Wed, 21 Jun 2023 08:16:53 +0100 Subject: [PATCH 328/347] Update openpype/hosts/maya/plugins/publish/collect_xgen.py Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/collect_xgen.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_xgen.py b/openpype/hosts/maya/plugins/publish/collect_xgen.py index e7c730cbf4..46968f7d1a 100644 --- a/openpype/hosts/maya/plugins/publish/collect_xgen.py +++ b/openpype/hosts/maya/plugins/publish/collect_xgen.py @@ -35,8 +35,7 @@ class CollectXgen(pyblish.api.InstancePlugin): connected_transform = get_attribute_input( node + ".transform" ).split(".")[0] - if connected_transform not in data["xgenConnections"]: - data["xgenConnections"].add(connected_transform) + data["xgenConnections"].add(connected_transform) # Collect all files under palette root as resources. import xgenm From c18afe974bee2e9e8a4bd59c97fda6098b8910e8 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 21 Jun 2023 16:27:17 +0800 Subject: [PATCH 329/347] remove unused text files --- igniter/.ass | 212 --------------------------------------------------- 1 file changed, 212 deletions(-) delete mode 100644 igniter/.ass diff --git a/igniter/.ass b/igniter/.ass deleted file mode 100644 index 599a76d732..0000000000 --- a/igniter/.ass +++ /dev/null @@ -1,212 +0,0 @@ -### exported: Thu Jun 15 17:07:53 2023 -### from: Arnold 7.1.3.2 [2a385cac] windows clang-10.0.1 oiio-2.4.1 osl-1.12.0 vdb-7.1.1 adlsdk-6.3.1.44 clmhub-2.0.0.235 rlm-14.1.3 optix-6.6.0 2022/09/12 08:50:17 -### host app: MtoA 5.2.1.1 6a048927 (fix-5.2.1) Maya 2023 -### bounds: -1.207354 -0.269835 -1.207354 1.207354 2.144874 1.207354 -### user: Kayla -### render_layer: defaultRenderLayer -### scene: D:/dummy_test/test_Project/test/work/test_v002/test_project_test_test_v002_v070.mb -### meters_per_unit: 0.010000 - - - -options -{ - AA_samples 3 - outputs 4 1 STRING - "RGBA RGBA defaultArnoldFilter/gaussian_filter defaultArnoldDriver/driver_exr.RGBA" - "P VECTOR aiAOVFilter1/closest_filter defaultArnoldDriver/driver_exr.RGBA" - "Pref RGB aiAOVFilter2/closest_filter defaultArnoldDriver/driver_exr.RGBA" - "albedo RGB defaultArnoldFilter/gaussian_filter defaultArnoldDriver/driver_exr.RGBA" - xres 1920 - yres 1080 - texture_per_file_stats on - texture_searchpath "[OPENPYPE_PROJECT_ROOT_WORK];D:/dummy_test/test_Project/test/work/test_v002/sourceimages" - texture_automip off - camera "/camera1/cameraShape1" - color_manager "defaultColorMgtGlobals" - operator "aiMerge1" - meters_per_unit 0.00999999978 - frame 1 - fps 25 - procedural_searchpath "[OPENPYPE_PROJECT_ROOT_WORK];D:/dummy_test/test_Project/test/work/test_v002/" - GI_diffuse_depth 1 - GI_specular_depth 1 - GI_transmission_depth 8 - declare render_layer constant STRING - render_layer "defaultRenderLayer" -} - -gaussian_filter -{ - name defaultArnoldFilter/gaussian_filter -} - -driver_exr -{ - name defaultArnoldDriver/driver_exr.RGBA - filename "D:/dummy_test/test_Project/test/work/test_v002/renders/test_project_test_test_v002_v070/masterLayer/masterLayer.exr" - color_space "" -} - -closest_filter -{ - name aiAOVFilter1/closest_filter -} - -closest_filter -{ - name aiAOVFilter2/closest_filter -} - -merge -{ - name aiMerge1 - inputs "test_01_:string_replace_operator" -} - -string_replace -{ - name test_01_:string_replace_operator - selection "*.(@node=='alembic')" - match "resources/test_project_test_modelMain_v067_proxy.abc" - replace "test_project_test_modelMain_v067.abc" -} - -color_manager_ocio -{ - name defaultColorMgtGlobals - config "D:/maya_config/OCIO-configs/Maya2022-default/config.ocio" - color_space_linear "ACEScg" -} - -polymesh -{ - name /proxy/pSphere/pSphereShape - visibility 255 - sidedness 255 - matrix - 2.41470838 0 0 0 - 0 2.41470838 0 0 - 0 0 2.41470838 0 - 0 0.937519372 0 1 - shader "lambert1" - id 2564139679 - nsides 6 1 UINT -4 4 4 4 4 4 - vidxs 24 1 b85UINT -B$ZuK*%:$$-?2$vMr4%:&UObB$w/J=(3BP? - vlist 8 1 b85VECTOR -aDq99aDq9989+]c89+]caDq9989+]caDq99!89+]c$$$$)aDq9989+]caDq9989+]c89+]c!aDq99$$$$(89+]caDq99aDq99 - nlist 24 1 b85VECTOR -zzyzzyzzyzzyzyzzyzzyzzy!$$$$$$$$$'aRT=dzzaRT=dzzaRT=dzzaRT=dzaRT=dzzaRT=dzzaRT=dzzaRT=dzyzzyzzyzzyzzaRT=dzzaRT=dzzaRT=dzzaRT=dzz - uvlist 14 1 b85VECTOR2 -82:0xz80l$K@wi$mMpJ$pnKs$PHvV$PKhS$rXa4$v$<]$USg@$UVY=$wcQs%&/-G$Z^X*$ZaJ'%'nB]%+9s1$_iHi$_l:f%-$3G%0Dcp$dt9S$dw+P%2/$1$h@,s$j**=$j,q:%79ip%:ZED$o4p'$o7b$%j%8uiM%8x[J%[0T+%^Q/T%>+Z7%>.L4%`;Dj%c[u>%C6Jv%C9%mqVg%ML,J%MNsG%o[l(%s'GQ%RVr4%RYd1%tf\g%x28;%5U-2%WdT\&$qMQ&(=)%%\lS]%\oEZ&*'>;&-Gnd%awDG%b%6D&/2/%&2R_N%g-51%g0'.&4RV8&As1a%vM\D%vPNA&C]Fw&$nOd&&XM.&&[?+&Hh7a&L3h5&+c=m&+f/j&Ms(K&Q>Xt&0n.W&0puT&S(n5&VII^&5xtA&6&f>&X3^t&[T:H&;.e+&;1W(&]>O^&`_+2&@9Uj&@x&jta[&JO7>&JR);&l^vq&p*RE&OZ((&O\o%&qig[&u5C/&Tdmg&Tg_d&vtXE'%@3n&Yo^Q&YrPN''*I/'*K$X&_%O;&_(A8',59n'/UjB&d0@%&d31w'1@*X'4`[,&G.Ox&i=wM'6JpB'9kKk&nEvN&nHhK';Ua,'>vb'VriE'Vu[B($*ai('N/L'\(Z/'\+L,()8Db(,Xu6'a3Jn'a6;X'fA-U(3N&6(6nV_'kI,B'kKs?(8Xku(<$GI'pSr,'pVd)(=c\_(A/83'u^bk'uaTh(BnMI(F:(r'X\ri(%lE>(H$>3(KDn\(*tD?(*w6<(M/.r(PO_F(0*5)(0-'&(R9t\(UZP0(55%h(57le(WDeF(Ze@o(:?kR(:B]O(\OV0(_p1Y(?J\<(?MN9(aZFo(e%wC(DUM&(DX>x(fe7Y(Gv@F(I`=e(Ic/b(kp(C(o;Xl(Nk.O(NmuL(q%n-(tFIV(Sut9(Sxf6(v0^l)$Q:@(Y+dx(Y.Vu)&;OV))\+*(^6Ub(^9G_)+F@@).fpi(cAFL(cD8I)0Q1*)3qaS(hL76(hO)3)5Y/Z)9'R=(mW'u(mYnr):fgS)>2C'(ram_(rd_\)?qX=)C=3f(wl^I(woPF)E'I')HH$P)'wO3)(%A0)J29f)MRj:)--?r)-01o)O=*P)R][$)280\)2:wY)TGp:)WhKc(j6@Z)7Eh/)YRa$)\sc`)Kn9C)Kq+@)n(xv)qITJ)Q$*-)Q&q*)s3i`)vTE4)V.ol)V1ai)x>ZJ)YOc7)[9`V)[Z)toi=)tr[:*B*Sp*EK/D*%%Z'*%(L$*G2RK*JUu.**0Jf**3-M*QK&.*TkVW*4F,:*4Hs7*VUkm*YvGA*9Pr$*9Scv*[`\W*_,8+*>[bc*>^T`*`kMA*d7(j*CfSM*CiEJ*ev>+*iAnT*&dcK*Ht5u*k,.j*nL_>*N'4v*N*&s*p6tT*sWP(*S2%`*S4l]*uAe>*xb@g*Xp+/b7Q+3-h%*g]=]*g`/Z+4m(;*k)1(*lh.G*ljuD+9wn%+=CIN*qrt1*quf.+?-^d+BN:8*w(dp*w+Vm+D8ON+GY*w+'3UZ+'6GW+IC@8+Lcpa+,>FD+,A8A+NN0w+QnaK+1I7.+1L)++SXva+W$R5+6T'm+6Vnj+X`u<+Yj%R+Yj1W+Yj=\+YjIa+YjUf+Yjak+Yjmp+Yk$u+Yk1%+Yk=*+YkI/+YkU4+Yka9+Ykm>+Yl$C+Yl0H+Yl$GoW-$IVb>$IVnF$K>Tg$R08V$SlCg$SlOo$UT6;$\Eo*$^-%;$^-1C$_ild$f[PS$$)E7$)3rI$j*)Y$ka82$/uZ&$0%nh$mMp;$plM,$:6;M$:;,-$rXa($uwb%$DKqs$DP>G$wcQj%&-vs$NaSD$NePa%'nBW%+96l$Xw4j$Y%c&%-$3D%0DKe$c7k;$c:u@%2/$1$j$j?$hC+1$hEr-%5R^`%:ZED$o4p'$o7b$%+Z7%>.L4%`;Dj%c[u>%C6Jv%C9%mqVg%ML,J%MNsG%o[l(%s'GQ%RVr4%RYd1%tf\g%x28;%7<8B%YK_m&&XL\&(=)$%\lS\%\oEZ&*'>;&-Gnd%awDG%b%6D&/2/%&2R_N%g-51%g0'.&4RV8&As1a%vM\D%vPNA&C]Fw&&Ufx&$qMw&$t?s&G,,Q&L3h5&+c=m&+f/j&Ms(K&Q>Xt&0n.W&0puT&S(n5&VII^&5xtA&6&f>&X3^t&[T:H&;.e+&;1W(&]>O^&`_+2&@9Uj&@vb'VriE'Vu[B($*mn(%fm9'\(N,'\+@(()8Db(,Xu6'a3Jn'a6;X'fA-U(3N&6(6nV_'kI,B'kKs?(8Xku(<$GI'pSr,'pVd)(=c\_(A/83'u^bk'uaTh(BnMI(F:(r'ZD)$('SPO(I`=>(KDn[(*tD>(*w6<(M/.r(PO_F(0*5)(0-'&(R9t\(UZP0(55%h(57le(WDeF(Ze@o(:?kR(:B]O(\OV0(_p1Y(?J\<(?MN9(aZFo(e%wC(DUM&(DX>x(fe7Y(I]WZ(H$>Y(H'0U(j3r3(o;Xl(Nk.O(NmuL(q%n-(tFIV(Sut9(Sxf6(v0^l)$Q:@(Y+dx(Y.Vu)&;OV))\+*(^6Ub(^9G_)+F@@).fpi(cAFL(cD8I)0Q1*)3qaS(hL76(hO)3)5Y;_)7@;*(mVpr(mYbn):fgS)>2C'(ram_(rd_\)?qX=)C=3f(wl^I(woPF)E'I')HH$P)'wO3)(%A0)J29f)MRj:)--?r)-01o)O=*P)R][$)280\)2:wY)TGp:)WhKc(krKj)9,s@)[9`/)\sc`)Kn9C)Kq+@)n(xv)qITJ)Q$*-)Q&q*)s3i`)vTE4)V.ol)V1ai)x>ZJ)[7%K)YRaJ)YUSF*&b@$*+j&])`DQ@)`GC=*-T;s*0tlG)eOB*)eR4'*2_,]*6*]1)jZ2i)j]$f*7irG*;5Mp)odxS)ogjP*Z)toi=)tr[:*B*Sp*EK/D*%%Z'*%(L$*G2^P*Hn]p**0>c**30_*L@5D*O`em*/;;P*/>-M*QK&.*TkVW*4F,:*4Hs7*VUkm*YvGA*9Pr$*9Scv*[`\W*_,8+*>[bc*>^T`*`kMA*d7(j*CfSM*CiEJ*ev>+*iAnT*(Kn[*J[A1*lh-u*nL_=*N'4u*N*&s*p6tT*sWP(*S2%`*S4l]*uAe>*xb@g*Xp+/b7Q+3-h%*g]=]*g`/Z+4m(;*leH<*k,/;*k.v7+8;bj+=CIN*qrt1*quf.+?-^d+BN:8*w(dp*w+Vm+D8ON+GY*w+'3UZ+'6GW+IC@8+Lcpa+,>FD+,A8A+NN0w+QnaK+1I7.+1L)++SXva+W$R5+6T'm+6Vnj+Xa,A+Yj%R+Yj1Y+YjUg+Yjmq+Yk1&+YkI0+Yka:+Yl$D+YlHN$N^ls$,R7G$,U5H$PI98$Si]]$1](1$1`&2$UT)w$XtNG$6gmp$6jkq$Z^oa$^*?1$;r^Z$;u\[$_i`K$c5/p$A(OD$A+ME$dtQ5$h?uZ$F3@.$GrI?$kfY4$o2(Y$M%H-$M(F.$pqIs$t'W%&2+G%)ROl$\Eo@$\HmA%++Z8%>.X9%aw\)%eC+N%C6Jw%C9Hx%g-Lh%jMq8%HA;a%HD9b%l8=R%oXaw%ML,K%MO*L%qC.<%tcRa%RVr5%RYp6%vMt&&$nCK%Wabt%Wd`u&&Xde&*$45%\lS^%^V\o&-Jld&0k<4%c^[]%caY^&2U]N&5v,s%hiLG%hlJH&7`N8&;+r]%mt=1%mw;2&w&@6cG%s*-p%s-+q&Av/a&EAT1%x4sZ%x7q[&G+uK&JLDp&(?dD&(BbE&L6f5&OW5Z&/1lB&/4jD&S(n4&VI=Y&4<]-&4?[.&X3^s&[T.C&9GMl&9JKm&]>O]&`^t-&>R>V&>U''3'Al'6Gf<&i;0e&i>.f'822V';RW&&nEvO&nHtP'=xwt'?&uu'bp$e'f;I5'Ek*r'En(t'ib,d'm-Q4'Jup]'Jxn^'nlrN'r8As'P+aG'P._H'swc8'wC2]'U6R1'U9P2($-Sw('MxG'ZABp'ZD@q()8Da(,Xi1'_L3Z'_O1[(.C5K(1cYp'dW$D'dYwE(3N&5(8Uao'kI,C'kL*D(:@.4(=`RY'pSr-'pVp.(?Jss(BkCC'u^bl'ua`m(DUd](Gv4-(%iSV(%lQW(I`UG(M,$l(*tD@(*wBA(NkF1(R6jV(0*5*(0-3+(Sv6p(WA[@(55%i(6t/%(Zh>o(^3c?(<'-h(<*+i(_s/Y(c>T)(A1sR(A4qS(e(uC(hIDh(FVl(r_&<(PREe(PUCf(tIGV(wil&(U]6O(U`4P)$T8@)'t\e(\O>M(\R26w(q%VK(q(TL)?qX<)C='a(v0G5(v3E6)E'I&)HGmK)&;7t)&>5u)J29e)O9uJ)--?s)-0=t)Q$Ad)TDf4)280])2;.^)V/2N)YOVs)7BvG)7EtH)[9x8)^ZG])Xp@)s3R()s6P**B*So*EJx?)x>Bh)xA@i*G5DY*JUi)*(I3R*(L1S*L@5C*O`Yh*-T$<*-Vw=*QK&-*TkJR*2^j&*2ah'*VUkl*Yv;<*7iZe*7lXf*[`\V*_,,&*[8*S2%a*S4xb*w)'R+%IKw*X^&+/_-K*bRLt*d1+.%]Z+.([[+Qq_K+U=.p+4leX+4ocZ+XcgJ+\/6o+9wVC+:%TD+]nX4+a:'Y+?-G-+?0E.+c$Hs+fDmC+D87l+D;5m+h/9]+kO^-+IC(V+IF&W+m:*G+pZNl+NMn@+NPlA+rDp1+ue?V+SX_*+S[]++wO`p,%:P.,&vgC,(^)X,*E@m,,,X-,-hoB,/P1W,17Hl,2s`,,4ZwA,6B9V,8)Pk,9eh+,;M*@,=4AU,>pXj,@Wp*,B?2?,D&IT,Eb`i,H'r1,Id4F,KKK[,M2bp,No%0,PVaQ7[Aa/f5)7xuYSaQ7[Aa794h7l?EtaQ7[Aa9['fzaQ7[Aa:L<8`x/vJaQ7[Aa9['ea/f5'aQ7[Aa794fa794faQ7[Aa/f5&a9['caQ7[A`x/vGa:L<5aQ7[Aza9['caQ7[A7l?Eqa794eaQ7[A7xuYOa/f5%aQ7[A8+HY:`x/vGaQ7[A8-jL7]8wuQaQ7[A8.[`^7l?EmaQ7[A8-jL67xuYLaQ7[A8+HY98+HY8aQ7[A7xuYM8-jL6aQ7[A7l?En8.[`]aQ7[Az85BheaOV5ba+:2Y81k?taOV5ba82J)8,AnSaOV5ba=[pI7tIW-aOV5baA3D9zaOV5baBETaa+:2XaOV5baA3D8a82J'aOV5ba=[pFa=[pEaOV5ba82J&aA3D6aOV5ba+:2UaBET^aOV5bzaA3D6aOV5b7tIW*a=[pDaOV5b8,AnOa82J%aOV5b81k?na+:2UaOV5b85Bh^]@q8xaOV5b86U$17tIW(aOV5b85Bh]8,AnMaOV5b81k?m81k?laOV5b8,AnN85Bh\aOV5b7tIW)86U$/aOV5bz8:_aQaMAiaa/f5)87d&JaMAiaa,;a/f5(aMAiaaFP=%a,8aMAiazaFPcW<8'x-gaJT2f8=h8A84LjgaJT2f8;)%18;)%1aJT2f84Lji8=h8@aJT2f8'x-i8>cW;aJT2fz8@J=naG>,6a794h8=>KTaG>,6aCTVt87d&IaG>,6aI/')8+HY,6aL:nBzaG>,6aMAiga794gaG>,6aL:nAaCTVqaG>,6aI/''aI/'&aG>,6aCTVoaL:n?aG>,6a794eaMAidaG>,6zaL:n?aG>,68+HY:aI/'%aG>,687d&CaCTVnaG>,68=>KNa794eaG>,68@J=g]KmN*aG>,68AQ988+HY7aG>,68@J=g87d&@aG>,68=>KM8=>KMaG>,687d&A8@J=faG>,68+HY88AQ96aG>,6z8BUCkaBETZa8YmK8?.MqaBETZaES@889bdbaBETZaJt)F8,iza-&.3aR7`ga::XDa-&.3aPqK=aG-,<8;MPfzaMAii8.[`bzaQ7[IzzaRT=ha:L<8zaQ7[HaG>,:zaMAifaMAiezaG>,9aQ7[Eza:L<5aRT=fzzaQ7[Ez8.[`_aMAidz8;MPbaG>,8z8AQ98a:L<5z8EG*m]Q*w-z8Fcb:8.[`\z8EG*l8;MP`z8AQ978AQ96z8;MPa8EG*kz8.[`]yzz8E+oj7v5R]a::XE8A:.B7v5R]aG-z7v5R]aR7`ga::XD7v5R]aPqK=aG-KT8;MP`aCTVt87d&I8;MP`aI/')8+HY<8;MP`aL:nBz8;MP`aMAiga794g8;MP`aL:nAaCTVq8;MP`aI/''aI/'&8;MP`aCTVoaL:n?8;MP`a794eaMAid8;MP`zaL:n?8;MP`8+HY:aI/'%8;MP`87d&CaCTVn8;MP`8=>KNa794e8;MP`8@J=g]KmN*8;MP`8AQ988+HY78;MP`8@J=g87d&@8;MP`8=>KM8=>KM8;MP`87d&A8@J=f8;MP`8+HY88AQ968;MP`z8=h8G8>cW;a3h^G8;)%78>cW;a@=FF84Ljp8>cW;aFnUa8'x-p8>cW;aIXhqz8>cW;aJT2la3h^F8>cW;aIXhpa@=FC8>cW;aFnU_aFnU^8>cW;a@=FBaIXhn8>cW;a3h^AaJT2i8>cW;zaIXhn8>cW;8'x-kaFnU^8>cW;84Ljja@=F@8>cW;8;)%2a3h^A8>cW;8=h8A]I*l/8>cW;8>cW<8'x-g8>cW;8=h8A84Ljg8>cW;8;)%18;)%18>cW;84Lji8=h8@8>cW;8'x-i8>cW;8>cW;z8:_aQ8AQ96a/f5)87d&J8AQ96a,;a/f5(8AQ96aFP=%a,88AQ96zaFP8EG*ka/f5)7xuYS8EG*ka794h7l?Et8EG*ka9['fz8EG*ka:L<8`x/vJ8EG*ka9['ea/f5'8EG*ka794fa794f8EG*ka/f5&a9['c8EG*k`x/vGa:L<58EG*kza9['c8EG*k7l?Eqa794e8EG*k7xuYOa/f5%8EG*k8+HY:`x/vG8EG*k8-jL7]8wuQ8EG*k8.[`^7l?Em8EG*k8-jL67xuYL8EG*k8+HY98+HY88EG*k7xuYM8-jL68EG*k7l?En8.[`]8EG*kz7uCHt8FG04`jc&P7rt'w8FG04`wC3.7kRWX8FG04a)dXM7^rJx8FG04a,4$Iz8FG04a-&.?`jc&N8FG04a,4$H`wC3,8FG04a)dXKa)dXJ8FG04`wC3*a,4$F8FG04`jc&Ka-&.=8FG04za,4$F8FG047^rJua)dXJ8FG047kRWS`wC3)8FG047rt's`jc&K8FG047uCHo]+QgX8FG047v5Re7^rJq8FG047uCHn7kRWQ8FG047rt'r7rt'r8FG047kRWR7uCHn8FG047^rJr7v5Rd8FG04zzaRT=dzzyz - nlist 382 1 b85VECTOR -8$sL?aR%6>`p]C(7vj/8aR%6>a($j&8,Oq4aQ$=Ea1Ba1j/2]ZEXpaQ$=Ea;iAh`p]BBaR%6?a0d'ta$guxaQ$=Da:rPna($inaR%6>a-Z`,a1B7dlgqa:rPoaQ$=C7mwEMa-Z_iaR%6?7q49%a8@LcaQ$=C8%Qa:a($iiaR%6@7vj/6a1B7dlgx8/,uEaQ$=A7mwE_8&$SXaR%6@5(Kln8/xfEaQ$=D4n,BY82U=faOAqMa8`53867kIaOAqLa+j;W8,oYWaOAqMa>En;7u$`;aOAqNaB(Fp]^u6:aOAqMaC>G8a+j;saOAqNaB(Fta8`55aOAqLa>En?a>En@aOAqMa8`51aB(FxaOAqMa+j;haC>G@aOAqK4Y^O1aB(G%aOAqK7u$`Fa>EnAaOAqK8,oY]a8`50aOAqL82U=ma+j;kaOAqJ867kP4J,])aOAqJ87Mkl7u$`FaOAqK867kM8,oY\aOAqK82U=k82U=jaOAqJ8,oYb867kOaOAqL7u$`@87MkgaOAqM4i*v)88;[taM-K^a%E_aJ@lva4+;Q84c;FaJ@luaG)&O8(:`VaJ@lsaIjv2]_U%E^]2IK3aJ@lr8>v_'8(:`caJ@lt8>%E]84c;QaJ@lq8;8K%8;8K$aJ@lq84c;K8>%E\aJ@lq8(:`W8>v_'aJ@lq5/=Vk8=HJ&aG,t^aCc,n8@V'DaG,t\a7@i]87rQ>aG,t]aI9%T8+P9HaG,t[aLFWk]hcZA7aG,tY8A]V-8+P9PaG,tY8@V'>87rQFaG,tY8=HJ'8=HJ)aG,tY87rQC8@V'@aG,tY8+P9K8A]V.aG,tY50&a>8?43^aB)a>aEWKc8B\)XaB)a?a8^5`89fp4aB)a@aK$d78,mZJaB)aDaNLZ)]Yd;iaB)aDaO]8ja8^6,aB)aDaNLZ'aEWKeaB)aCaK$d1aK$d5aB)aAaEWK_aNLZ(aB)aBa8^6$aO]8jaB)aG]SCosaNLZ'aB)aF8,mZOaK$d3aB)aD89fp8aEWKaaB)a@8?43_a8^6&aB)a>8B\)S4]4BYaB)a;8Cl]?8,mZTaB)a<8B\)S89fp6aB)a;8?43^8?43`aB)a<89fp78B\)TaB)a>8,mZP8Cl]AaB)a<52v)T8@LmMa:8ZwaFR128D3$9a:8Zwa9\x^8:aUXa:8[$aL=I&8-lHLa:8[&aOxT_]2EE>a:8[$aQ:iJa9]$-a:8ZxaOxT^aFR14a:8[%aL=HvaL=I&a:8[$aFR1/aOxT_a:8Zwa9]$&aQ:iJa:8[']Db/faOxT]a:8[&8-lHRaL=I$a:8Zp8:aU\aFR10a:8Zq8@LmPa9]$$a:8Zu8D3$44e9woa:8Zv8EJ8t8-lHUa:8Zx8D3$38:aU[a:8Zw8@LmN8@LmOa:8Zv8:aU[8D3$4a:8Zt8-lHP8EJ8wa:8Zq53XsU8A:e0a,fiLaG-dd8E,[,68EG*p\jrsaa:L;i8;MP]]I`mKaMAib8.[`S]I`mMaQ7[BzzaRT=da:L<;]I`mPaQ7[@aG>,:]H,4aQ7[A3_-C8a:L<2aRT=c\jrsa3leGaaQ7[?\xUx78.[`_aMAi_z8;MPbaG>,5]FmUA8AQ98a:L<.]LT0e8EG*kzz8Fcb88.[`^\jrsc8EG*k8;MPa\xUx<8AQ978AQ96\jrsc8;MP`8EG*kz8.[`_y3_-C453fkZ8A:e07uv8uaG-dd8E,[<7uv9&a:;,o8;=457uv9(aM+@]8.JQ\7uv9(aPr6cz7uv9$aR8OQa:;-?7uv9%aPr6`aG-dg7uv8waM+@WaM+@\7uv8taG-d`aPr6d7uv8ma:;-6aR8OR7uv8b4%`)+aPr6b7uv8a8.JQcaM+@Z7uv8j8;=49aG-db7uv8t8A:e0a:;-47uv8x8E,[74W=-`7uv9$8FGt&8.JQe7uv9$8E,[68;=487uv8v8A:e/8A:e07uv8w8;=488E,[97uv8t8.JQc8FGt&7uv8x53Dx68@LmM8.H*NaFR138D3$98.H*Na9\x^8:aUX8.H*OaL=I'8-lHL8.H*PaOxT_]2EE>8.H*OaQ:iJa9]$,8.H*NaOxT\aFR138.H*MaL=HvaL=I'8.H*MaFR1/aOxT_8.H*La9]$&aQ:iJ8.H*N]:)QhaOxT_8.H*L8-lHRaL=I$8.H*G8:aU\aFR108.H*F8@LmQa9]$$8.H*J8D3$44eYgJ8.H*K8EJ8t8-lHS8.H*M8D3$28:aU[8.H*M8@LmO8@LmO8.H*K8:aU[8D3$48.H*H8-lHP8EJ8w8.H*I52v;@8?43]8690laEWKc8B\)Y8690ka8^5`89fp48690laK$d68,mZL8690naNLZ*]Yd;i8690naO]8ja8^6+8690oaNLZ'aEWKe8690kaK$d1aK$d68690kaEWK_aNLZ*8690ma8^6&aO]8j8690m]OP%E^8>PPPPPPPPPPPPP%E^]2IK38>Pv_'8(:`c8>P%E]84c;N8>PP%E\8>Pv_(8>PEnF7u$`98CQ@vaB(Fs]`=mC8CQ@xaC>G6a+j;s8CQ@xaB(Foa8`548CQ@wa>En:a>En@8CQ@va8`5-aB(G$8CQ@wa+j;naC>G@8CQ@u4Y^O1aB(Fx8CQ@u7u$`Ua>En@8CQ@v8,oY]a8`518CQ@w82U=ia+j;l8CQ@t867kQ4I`qe8CQ@t87Mkp7u$`H8CQ@u867kP8,oY]8CQ@v82U=k82U=l8CQ@u8,oY`867kL8CQ@t7u$`F87Mkg8CQ@w4piDe8,Oq38E3ama1B=/8/,uC8E3ana$guO8%QaB8E3ala8@Lj7mwE<8E3aoa:rPi]ZEXp8E3aoa;iAaa$guw8E3aoa:rPfa1B0s7_[,H7uNfZ7_[,H8%v5I7_[,H8+HY87_[,H8.1k07_[,H80p((7_[,H83Y9u7_[,H86BKm7_[,H89+]d7_[,H8:Jf`7_[,H8;io\7_[,H8=3xX7_[,H8>S,T7_[,H8?r5P7_[,H8A<>L7_[,H8B[GH7_[,H8D%PD7_[,H8EDY@7_[,H8Fcb:7_[,Hz7m>0s7_[,H!7m>0s$$$$'7uNfZ7m>0s8%v5I7m>0s8+HY87m>0s8.1k07m>0s80p((7m>0s83Y9u7m>0s86BKm7m>0s89+]d7m>0s8:Jf`7m>0s8;io\7m>0s8=3xX7m>0s8>S,T7m>0s8?r5P7m>0s8A<>L7m>0s8B[GH7m>0s8D%PD7m>0s8EDY@7m>0s8Fcb:7m>0sz7uNfZ7_[,H7uNfZ7m>0s!7uNfZ$$$$'8%v5I7uNfZ8+HY87uNfZ8.1k07uNfZ80p((7uNfZ83Y9u7uNfZ86BKm7uNfZ89+]d7uNfZ8:Jf`7uNfZ8;io\7uNfZ8=3xX7uNfZ8>S,T7uNfZ8?r5P7uNfZ8A<>L7uNfZ8B[GH7uNfZ8D%PD7uNfZ8EDY@7uNfZ8Fcb:7uNfZz8%v5I7_[,H8%v5I7m>0s8%v5I7uNfZ!8%v5I$$$$'8+HY88%v5I8.1k08%v5I80p((8%v5I83Y9u8%v5I86BKm8%v5I89+]d8%v5I8:Jf`8%v5I8;io\8%v5I8=3xX8%v5I8>S,T8%v5I8?r5P8%v5I8A<>L8%v5I8B[GH8%v5I8D%PD8%v5I8EDY@8%v5I8Fcb:8%v5Iz8+HY87_[,H8+HY87m>0s8+HY87uNfZ8+HY88%v5I!8+HY8$$$$'8.1k08+HY880p((8+HY883Y9u8+HY886BKm8+HY889+]d8+HY88:Jf`8+HY88;io\8+HY88=3xX8+HY88>S,T8+HY88?r5P8+HY88A<>L8+HY88B[GH8+HY88D%PD8+HY88EDY@8+HY88Fcb:8+HY8z8.1k07_[,H8.1k07m>0s8.1k07uNfZ8.1k08%v5I8.1k08+HY8!8.1k0$$$$'80p((8.1k083Y9u8.1k086BKm8.1k089+]d8.1k08:Jf`8.1k08;io\8.1k08=3xX8.1k08>S,T8.1k08?r5P8.1k08A<>L8.1k08B[GH8.1k08D%PD8.1k08EDY@8.1k08Fcb:8.1k0z80p((7_[,H80p((7m>0s80p((7uNfZ80p((8%v5I80p((8+HY880p((8.1k0!80p(($$$$'83Y9u80p((86BKm80p((89+]d80p((8:Jf`80p((8;io\80p((8=3xX80p((8>S,T80p((8?r5P80p((8A<>L80p((8B[GH80p((8D%PD80p((8EDY@80p((8Fcb:80p((z83Y9u7_[,H83Y9u7m>0s83Y9u7uNfZ83Y9u8%v5I83Y9u8+HY883Y9u8.1k083Y9u80p((83Y9u83Y9u83Y9u86BKm83Y9u89+]d83Y9u8:Jf`83Y9u8;io\83Y9u8=3xX83Y9u8>S,T83Y9u8?r5P83Y9u8A<>L83Y9u8B[GH83Y9u8D%PD83Y9u8EDY@83Y9u8Fcb:83Y9uz86BKm7_[,H86BKm7m>0s86BKm7uNfZ86BKm8%v5I86BKm8+HY886BKm8.1k086BKm80p((86BKm83Y9u!86BKm$$$$'89+]d86BKm8:Jf`86BKm8;io\86BKm8=3xX86BKm8>S,T86BKm8?r5P86BKm8A<>L86BKm8B[GH86BKm8D%PD86BKm8EDY@86BKm8Fcb:86BKmz89+]d7_[,H89+]d7m>0s89+]d7uNfZ89+]d8%v5I89+]d8+HY889+]d8.1k089+]d80p((89+]d83Y9u89+]d86BKm!89+]d$$$$'8:Jf`89+]d8;io\89+]d8=3xX89+]d8>S,T89+]d8?r5P89+]d8A<>L89+]d8B[GH89+]d8D%PD89+]d8EDY@89+]d8Fcb:89+]dz8:Jf`7_[,H8:Jf`7m>0s8:Jf`7uNfZ8:Jf`8%v5I8:Jf`8+HY88:Jf`8.1k08:Jf`80p((8:Jf`83Y9u8:Jf`86BKm8:Jf`89+]d!8:Jf`$$$$'8;io\8:Jf`8=3xX8:Jf`8>S,T8:Jf`8?r5P8:Jf`8A<>L8:Jf`8B[GH8:Jf`8D%PD8:Jf`8EDY@8:Jf`8Fcb:8:Jf`z8;io\7_[,H8;io\7m>0s8;io\7uNfZ8;io\8%v5I8;io\8+HY88;io\8.1k08;io\80p((8;io\83Y9u8;io\86BKm8;io\89+]d8;io\8:Jf`!8;io\$$$$'8=3xX8;io\8>S,T8;io\8?r5P8;io\8A<>L8;io\8B[GH8;io\8D%PD8;io\8EDY@8;io\8Fcb:8;io\z8=3xX7_[,H8=3xX7m>0s8=3xX7uNfZ8=3xX8%v5I8=3xX8+HY88=3xX8.1k08=3xX80p((8=3xX83Y9u8=3xX86BKm8=3xX89+]d8=3xX8:Jf`8=3xX8;io\!8=3xX$$$$'8>S,T8=3xX8?r5P8=3xX8A<>L8=3xX8B[GH8=3xX8D%PD8=3xX8EDY@8=3xX8Fcb:8=3xXz8>S,T7_[,H8>S,T7m>0s8>S,T7uNfZ8>S,T8%v5I8>S,T8+HY88>S,T8.1k08>S,T80p((8>S,T83Y9u8>S,T86BKm8>S,T89+]d8>S,T8:Jf`8>S,T8;io\8>S,T8=3xX!8>S,T$$$$'8?r5P8>S,T8A<>L8>S,T8B[GH8>S,T8D%PD8>S,T8EDY@8>S,T8Fcb:8>S,Tz8?r5P7_[,H8?r5P7m>0s8?r5P7uNfZ8?r5P8%v5I8?r5P8+HY88?r5P8.1k08?r5P80p((8?r5P83Y9u8?r5P86BKm8?r5P89+]d8?r5P8:Jf`8?r5P8;io\8?r5P8=3xX8?r5P8>S,T!8?r5P$$$$'8A<>L8?r5P8B[GH8?r5P8D%PD8?r5P8EDY@8?r5P8Fcb:8?r5Pz8A<>L7_[,H8A<>L7m>0s8A<>L7uNfZ8A<>L8%v5I8A<>L8+HY88A<>L8.1k08A<>L80p((8A<>L83Y9u8A<>L86BKm8A<>L89+]d8A<>L8:Jf`8A<>L8;io\8A<>L8=3xX8A<>L8>S,T8A<>L8?r5P!8A<>L$$$$'8B[GH8A<>L8D%PD8A<>L8EDY@8A<>L8Fcb:8A<>Lz8B[GH7_[,H8B[GH7m>0s8B[GH7uNfZ8B[GH8%v5I8B[GH8+HY88B[GH8.1k08B[GH80p((8B[GH83Y9u8B[GH86BKm8B[GH89+]d8B[GH8:Jf`8B[GH8;io\8B[GH8=3xX8B[GH8>S,T8B[GH8?r5P8B[GH8A<>L!8B[GH$$$$'8D%PD8B[GH8EDY@8B[GH8Fcb:8B[GHz8D%PD7_[,H8D%PD7m>0s8D%PD7uNfZ8D%PD8%v5I8D%PD8+HY88D%PD8.1k08D%PD80p((8D%PD83Y9u8D%PD86BKm8D%PD89+]d8D%PD8:Jf`8D%PD8;io\8D%PD8=3xX8D%PD8>S,T8D%PD8?r5P8D%PD8A<>L8D%PD8B[GH!8D%PD$$$$'8EDY@8D%PD8Fcb:8D%PDz8EDY@7_[,H8EDY@7m>0s8EDY@7uNfZ8EDY@8%v5I8EDY@8+HY88EDY@8.1k08EDY@80p((8EDY@83Y9u8EDY@86BKm8EDY@89+]d8EDY@8:Jf`8EDY@8;io\8EDY@8=3xX8EDY@8>S,T8EDY@8?r5P8EDY@8A<>L8EDY@8B[GH8EDY@8D%PD!8EDY@$$$$'8Fcb:8EDY@7Qx'rz7gkb/z7reTbz7x7xRz8(_GAz8,gb4z8/Pt,z82:0xz84xBpz87aThz89eb6z8;/k2z8 Date: Wed, 21 Jun 2023 16:42:52 +0800 Subject: [PATCH 330/347] toke's comment --- .../publish/collect_arnold_scene_source.py | 49 +++++-------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index 1b8ec27509..d93811c23e 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -18,49 +18,24 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): for objset in objsets: objset = str(objset) members = cmds.sets(objset, query=True) + members = cmds.ls(members, long=True) if members is None: self.log.warning("Skipped empty instance: \"%s\" " % objset) continue if objset.endswith("content_SET"): - members = cmds.ls(members, long=True) - members_list = [] - for member in members: - shape = cmds.listRelatives( - member, shapes=True, fullPath=True) - if not shape: - continue - members_list = members + shape - group_name = "|{}".format(member) - if group_name in members_list: - members_list.remove(group_name) - children = get_all_children(members) - - if members_list: - children.extend(members_list) - instance.data["contentMembers"] = children - self.log.debug("content members: {}".format(children)) - + instance.data["contentMembers"] = children + members + self.log.debug( + "content members: {}".format( + instance.data["contentMembers"] + ) + ) elif objset.endswith("proxy_SET"): - proxy_members = cmds.ls(members, long=True) - proxy_list = [] - for proxy in proxy_members: - shape = cmds.listRelatives( - proxy, shapes=True, fullPath=True) - if not shape: - continue - proxy_list = proxy_members + shape - group_name = "|{}".format(proxy) - if group_name in proxy_list: - proxy_list.remove(group_name) - - set_members = get_all_children(proxy_members) - if proxy_list: - set_members.extend(proxy_list) - - instance.data["proxy"] = set_members - self.log.debug("proxy members: {}".format(set_members)) - + children = get_all_children(cmds.ls(members, long=True)) + instance.data["proxy"] = children + members + self.log.debug( + "proxy members: {}".format(instance.data["proxy"]) + ) # Use camera in object set if present else default to render globals # camera. From 1c2955a90aa027b0611197ddaa741dcceb2eaca7 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 21 Jun 2023 16:50:08 +0800 Subject: [PATCH 331/347] remove docstring --- .../maya/plugins/publish/extract_arnold_scene_source.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py index 8696f5a94b..102f0e46a2 100644 --- a/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/extract_arnold_scene_source.py @@ -69,8 +69,7 @@ class ExtractArnoldSceneSource(publish.Extractor): "camera": instance.data["camera"], "mask": mask } - # TODO: dont use instance.data["contentMembers"] - # but use the new instance.data["contentMemberTransforms"] + filenames, nodes_by_id = self._extract( instance.data["contentMembers"], attribute_data, kwargs ) @@ -110,8 +109,7 @@ class ExtractArnoldSceneSource(publish.Extractor): return kwargs["filename"] = file_path.replace(".ass", "_proxy.ass") - # TODO: dont use instance.data["proxy"] - # but use the new instance.data["proxyTransforms"] + filenames, _ = self._extract( instance.data["proxy"], attribute_data, kwargs ) From 29c8cfe99236faf3a0723e7b064c34226d19c925 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 21 Jun 2023 18:48:25 +0800 Subject: [PATCH 332/347] roy's comment --- .../publish/collect_arnold_scene_source.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index d93811c23e..69a07b4aaf 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -23,19 +23,9 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): self.log.warning("Skipped empty instance: \"%s\" " % objset) continue if objset.endswith("content_SET"): - children = get_all_children(members) - instance.data["contentMembers"] = children + members - self.log.debug( - "content members: {}".format( - instance.data["contentMembers"] - ) - ) - elif objset.endswith("proxy_SET"): - children = get_all_children(cmds.ls(members, long=True)) - instance.data["proxy"] = children + members - self.log.debug( - "proxy members: {}".format(instance.data["proxy"]) - ) + instance.data["contentMembers"] = self.get_hierarchy(members) + if objset.endswith("content_SET"): + instance.data["proxy"] = self.get_hierarchy(members) # Use camera in object set if present else default to render globals # camera. @@ -54,3 +44,13 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): self.log.debug("No renderable cameras found.") self.log.debug("data: {}".format(instance.data)) + + def get_hierarchy(self, nodes): + """Return nodes with all their children""" + nodes = cmds.ls(nodes, long=True) + if not nodes: + return [] + children = get_all_children(nodes) + # Make sure nodes merged with children only + # contains unique entries + return list(set(nodes + children)) From 223a3338f3b5a02daf3b7172ab3161a3b95414c4 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Wed, 21 Jun 2023 19:25:47 +0800 Subject: [PATCH 333/347] Update openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py Co-authored-by: Toke Jepsen --- .../hosts/maya/plugins/publish/collect_arnold_scene_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py index 69a07b4aaf..90079c715a 100644 --- a/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/publish/collect_arnold_scene_source.py @@ -24,7 +24,7 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin): continue if objset.endswith("content_SET"): instance.data["contentMembers"] = self.get_hierarchy(members) - if objset.endswith("content_SET"): + if objset.endswith("proxy_SET"): instance.data["proxy"] = self.get_hierarchy(members) # Use camera in object set if present else default to render globals From 2d1fc8e72857a63510fb9e6e27fe2ddea85527e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 21 Jun 2023 14:09:29 +0200 Subject: [PATCH 334/347] Update openpype/pipeline/colorspace.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 13846e9f5c..269825f85f 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -375,7 +375,7 @@ def get_imageio_config( # This is for backward compatibility. # TODO: in future rewrite this to be more explicit activate_host_color_management = imageio_host.get( - "activate_host_color_management", True) + "activate_host_color_management") # TODO: remove this in future - backward compatibility if activate_host_color_management is None: From 43ece88f02bf1b35b22d35d3ceeef82241fe2ef3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Jun 2023 09:40:19 +0100 Subject: [PATCH 335/347] Include workfile family to import. --- openpype/hosts/maya/plugins/load/actions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py index ba69debc40..4855f3eed0 100644 --- a/openpype/hosts/maya/plugins/load/actions.py +++ b/openpype/hosts/maya/plugins/load/actions.py @@ -105,7 +105,8 @@ class ImportMayaLoader(load.LoaderPlugin): "camera", "rig", "camerarig", - "staticMesh" + "staticMesh", + "workfile" ] label = "Import" From 39d2159ed462e3e84236ba4497277d2a7f65016a Mon Sep 17 00:00:00 2001 From: Simone Barbieri Date: Thu, 22 Jun 2023 10:57:23 +0100 Subject: [PATCH 336/347] Blender: Add support for custom path for app templates (#5137) * Add support for custom path for app templates * Use same env variable as scripts and assure correct path exist --- openpype/hosts/blender/api/lib.py | 21 +++++++++++++++++++++ openpype/hosts/blender/api/pipeline.py | 1 + 2 files changed, 22 insertions(+) diff --git a/openpype/hosts/blender/api/lib.py b/openpype/hosts/blender/api/lib.py index 6526f1fb87..9bb560c364 100644 --- a/openpype/hosts/blender/api/lib.py +++ b/openpype/hosts/blender/api/lib.py @@ -134,6 +134,27 @@ def append_user_scripts(): traceback.print_exc() +def set_app_templates_path(): + # Blender requires the app templates to be in `BLENDER_USER_SCRIPTS`. + # After running Blender, we set that variable to our custom path, so + # that the user can use their custom app templates. + + # We look among the scripts paths for one of the paths that contains + # the app templates. The path must contain the subfolder + # `startup/bl_app_templates_user`. + paths = os.environ.get("OPENPYPE_BLENDER_USER_SCRIPTS").split(os.pathsep) + + app_templates_path = None + for path in paths: + if os.path.isdir( + os.path.join(path, "startup", "bl_app_templates_user")): + app_templates_path = path + break + + if app_templates_path and os.path.isdir(app_templates_path): + os.environ["BLENDER_USER_SCRIPTS"] = app_templates_path + + def imprint(node: bpy.types.bpy_struct_meta_idprop, data: Dict): r"""Write `data` to `node` as userDefined attributes diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py index 9cc557c01a..0f756d8cb6 100644 --- a/openpype/hosts/blender/api/pipeline.py +++ b/openpype/hosts/blender/api/pipeline.py @@ -60,6 +60,7 @@ def install(): register_creator_plugin_path(str(CREATE_PATH)) lib.append_user_scripts() + lib.set_app_templates_path() register_event_callback("new", on_new) register_event_callback("open", on_open) From 97f3bdf5ec7483932ba4550bfab7c4a75795a831 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 22 Jun 2023 12:02:23 +0200 Subject: [PATCH 337/347] Color Management- added color management support for simple expected files on Deadline (#5122) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * OP-5864 - added color management support for simple expected files Previously implemented only for AOVs. * OP-5864 - fix colorspace query colorspace might be not on instance.data * OP-5864 - refactore name to more descriptive one It is actually instance.data, not proper `instance`. --------- Co-authored-by: Jakub Ježek --- .../plugins/publish/submit_publish_job.py | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 590acf86c2..69e9fb6449 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -21,6 +21,7 @@ from openpype.pipeline import ( from openpype.tests.lib import is_in_tests from openpype.pipeline.farm.patterning import match_aov_pattern from openpype.lib import is_running_from_build +from openpype.pipeline import publish def get_resources(project_name, version, extension=None): @@ -79,7 +80,8 @@ def get_resource_files(resources, frame_range=None): return list(res_collection) -class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): +class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, + publish.ColormanagedPyblishPluginMixin): """Process Job submitted on farm. These jobs are dependent on a deadline or muster job @@ -598,7 +600,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.debug("instances:{}".format(instances)) return instances - def _get_representations(self, instance, exp_files, do_not_add_review): + def _get_representations(self, instance_data, exp_files, + do_not_add_review): """Create representations for file sequences. This will return representations of expected files if they are not @@ -606,7 +609,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): most cases, but if not - we create representation from each of them. Arguments: - instance (dict): instance data for which we are + instance_data (dict): instance.data for which we are setting representations exp_files (list): list of expected files do_not_add_review (bool): explicitly skip review @@ -628,9 +631,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # expected files contains more explicitly and from what # should be review made. # - "review" tag is never added when is set to 'False' - if instance["useSequenceForReview"]: + if instance_data["useSequenceForReview"]: # toggle preview on if multipart is on - if instance.get("multipartExr", False): + if instance_data.get("multipartExr", False): self.log.debug( "Adding preview tag because its multipartExr" ) @@ -655,8 +658,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): " This may cause issues on farm." ).format(staging)) - frame_start = int(instance.get("frameStartHandle")) - if instance.get("slate"): + frame_start = int(instance_data.get("frameStartHandle")) + if instance_data.get("slate"): frame_start -= 1 preview = preview and not do_not_add_review @@ -665,10 +668,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "ext": ext, "files": [os.path.basename(f) for f in list(collection)], "frameStart": frame_start, - "frameEnd": int(instance.get("frameEndHandle")), + "frameEnd": int(instance_data.get("frameEndHandle")), # If expectedFile are absolute, we need only filenames "stagingDir": staging, - "fps": instance.get("fps"), + "fps": instance_data.get("fps"), "tags": ["review"] if preview else [], } @@ -676,17 +679,17 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if ext in self.skip_integration_repre_list: rep["tags"].append("delete") - if instance.get("multipartExr", False): + if instance_data.get("multipartExr", False): rep["tags"].append("multipartExr") # support conversion from tiled to scanline - if instance.get("convertToScanline"): + if instance_data.get("convertToScanline"): self.log.info("Adding scanline conversion.") rep["tags"].append("toScanline") representations.append(rep) - self._solve_families(instance, preview) + self._solve_families(instance_data, preview) # add remainders as representations for remainder in remainders: @@ -717,13 +720,13 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): preview = preview and not do_not_add_review if preview: rep.update({ - "fps": instance.get("fps"), + "fps": instance_data.get("fps"), "tags": ["review"] }) - self._solve_families(instance, preview) + self._solve_families(instance_data, preview) already_there = False - for repre in instance.get("representations", []): + for repre in instance_data.get("representations", []): # might be added explicitly before by publish_on_farm already_there = repre.get("files") == rep["files"] if already_there: @@ -733,6 +736,13 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if not already_there: representations.append(rep) + for rep in representations: + # inject colorspace data + self.set_representation_colorspace( + rep, self.context, + colorspace=instance_data["colorspace"] + ) + return representations def _solve_families(self, instance, preview=False): @@ -861,7 +871,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "jobBatchName": data.get("jobBatchName", ""), "useSequenceForReview": data.get("useSequenceForReview", True), # map inputVersions `ObjectId` -> `str` so json supports it - "inputVersions": list(map(str, data.get("inputVersions", []))) + "inputVersions": list(map(str, data.get("inputVersions", []))), + "colorspace": instance.data.get("colorspace") } # skip locking version if we are creating v01 From 40cf531cb12bf6a042d4535b08bc3139ed415cac Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 22 Jun 2023 12:55:17 +0100 Subject: [PATCH 338/347] Adding support for excluded families. --- openpype/hosts/maya/plugins/load/actions.py | 17 ++--------------- openpype/pipeline/load/plugins.py | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py index 4855f3eed0..57d1039245 100644 --- a/openpype/hosts/maya/plugins/load/actions.py +++ b/openpype/hosts/maya/plugins/load/actions.py @@ -93,21 +93,8 @@ class ImportMayaLoader(load.LoaderPlugin): """ representations = ["ma", "mb", "obj"] - families = [ - "model", - "pointcache", - "proxyAbc", - "animation", - "mayaAscii", - "mayaScene", - "setdress", - "layout", - "camera", - "rig", - "camerarig", - "staticMesh", - "workfile" - ] + families = ["*"] + excluded_families = ["xgen"] label = "Import" order = 10 diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index e380d65bbe..433d7f671a 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -28,6 +28,7 @@ class LoaderPlugin(list): """ families = [] + excluded_families = [] representations = [] extensions = {"*"} order = 0 @@ -159,7 +160,10 @@ class LoaderPlugin(list): return False plugin_families = set(plugin_families) - if "*" in plugin_families: + + # Return if all families are allowed and no excluded families are + # defined. + if "*" in plugin_families and not cls.excluded_families: return True subset_doc = context["subset"] @@ -175,6 +179,17 @@ class LoaderPlugin(list): if not families: return False + + # Return if there are intersections between the subsets families and + # the loaders excluded families. + if list(set(families) & set(cls.excluded_families)): + return False + + # Return if all families are allowed since excluded families have + # already been considered. + if "*" in plugin_families: + return True + return any(family in plugin_families for family in families) @classmethod From ecc152ffe1efe3689877be9bdd4051d4438539ea Mon Sep 17 00:00:00 2001 From: kaa Date: Thu, 22 Jun 2023 15:56:20 +0200 Subject: [PATCH 339/347] fix ftrack ignore sync filter (#5176) --- openpype/modules/ftrack/lib/avalon_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 8b4c4619a1..7c3ba1a30c 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -890,7 +890,7 @@ class SyncEntitiesFactory: else: parent_dict = self.entities_dict.get(parent_id, {}) - for child_id in parent_dict.get("children", []): + for child_id in list(parent_dict.get("children", [])): # keep original `remove` value for all children _remove = (remove is True) if not _remove: From f76e32940ad67194c37b88c7507386dd90d87b4e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:39:21 +0200 Subject: [PATCH 340/347] Burnins: Refactored burnins script (#5094) * refactored list value for burnins * fix formatting of listed values * formatting fix * separated values preparation into function * added some docstrings and comments * minor changes * handle fps in burnin options * formatting fix * Fix typo Co-authored-by: Roy Nieterau * move lines logic before writing to file * fill 'fps' in burnin options from representation * fix missing 'handles' variable --------- Co-authored-by: Roy Nieterau --- openpype/plugins/publish/extract_burnin.py | 20 +- openpype/scripts/otio_burnin.py | 358 ++++++++++++++------- 2 files changed, 251 insertions(+), 127 deletions(-) diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index 6a8ae958d2..41d6cf81fc 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -266,6 +266,16 @@ class ExtractBurnin(publish.Extractor): first_output = True files_to_delete = [] + + repre_burnin_options = copy.deepcopy(burnin_options) + # Use fps from representation for output in options + fps = repre.get("fps") + if fps is not None: + repre_burnin_options["fps"] = fps + # TODO Should we use fps from source representation to fill + # it in review? + # burnin_data["fps"] = fps + for filename_suffix, burnin_def in repre_burnin_defs.items(): new_repre = copy.deepcopy(repre) new_repre["stagingDir"] = src_repre_staging_dir @@ -308,7 +318,7 @@ class ExtractBurnin(publish.Extractor): "input": temp_data["full_input_path"], "output": temp_data["full_output_path"], "burnin_data": burnin_data, - "options": copy.deepcopy(burnin_options), + "options": repre_burnin_options, "values": burnin_values, "full_input_path": temp_data["full_input_paths"][0], "first_frame": temp_data["first_frame"], @@ -463,15 +473,11 @@ class ExtractBurnin(publish.Extractor): handle_start = instance.data.get("handleStart") if handle_start is None: - handle_start = context.data.get("handleStart") - if handle_start is None: - handle_start = handles + handle_start = context.data.get("handleStart") or 0 handle_end = instance.data.get("handleEnd") if handle_end is None: - handle_end = context.data.get("handleEnd") - if handle_end is None: - handle_end = handles + handle_end = context.data.get("handleEnd") or 0 frame_start_handle = frame_start - handle_start frame_end_handle = frame_end + handle_end diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index d0a4266941..581734a789 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -1,6 +1,5 @@ import os import sys -import re import subprocess import platform import json @@ -13,6 +12,7 @@ from openpype.lib import ( get_ffmpeg_codec_args, get_ffmpeg_format_args, convert_ffprobe_fps_value, + convert_ffprobe_fps_to_float, ) @@ -41,45 +41,6 @@ TIMECODE_KEY = "{timecode}" SOURCE_TIMECODE_KEY = "{source_timecode}" -def convert_list_to_command(list_to_convert, fps, label=""): - """Convert a list of values to a drawtext command file for ffmpeg `sendcmd` - - The list of values is expected to have a value per frame. If the video - file ends up being longer than the amount of samples per frame than the - last value will be held. - - Args: - list_to_convert (list): List of values per frame. - fps (float or int): The expected frame per seconds of the output file. - label (str): Label for the drawtext, if specific drawtext filter is - required - - Returns: - str: Filepath to the temporary drawtext command file. - - """ - - with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: - for i, value in enumerate(list_to_convert): - seconds = i / fps - - # Escape special character - value = str(value).replace(":", "\\:") - - filter = "drawtext" - if label: - filter += "@" + label - - line = ( - "{start} {filter} reinit text='{value}';" - "\n".format(start=seconds, filter=filter, value=value) - ) - - f.write(line) - f.flush() - return f.name - - def _get_ffprobe_data(source): """Reimplemented from otio burnins to be able use full path to ffprobe :param str source: source media file @@ -178,6 +139,7 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): self.ffprobe_data = ffprobe_data self.first_frame = first_frame self.input_args = [] + self.cleanup_paths = [] super().__init__(source, source_streams) @@ -191,7 +153,6 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): frame_start=None, frame_end=None, options=None, - cmd="" ): """ Adding static text to a filter. @@ -212,13 +173,9 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): if frame_end is not None: options["frame_end"] = frame_end - draw_text = DRAWTEXT - if cmd: - draw_text = "{}, {}".format(cmd, DRAWTEXT) options["label"] = align - - self._add_burnin(text, align, options, draw_text) + self._add_burnin(text, align, options, DRAWTEXT) def add_timecode( self, align, frame_start=None, frame_end=None, frame_start_tc=None, @@ -263,6 +220,139 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): self._add_burnin(text, align, options, TIMECODE) + def add_per_frame_text( + self, + text, + align, + frame_start, + frame_end, + listed_keys, + options=None + ): + """Add text that changes per frame. + + Args: + text (str): Template string with unfilled keys that are changed + per frame. + align (str): Alignment of text. + frame_start (int): Starting frame for burnins current frame. + frame_end (int): Ending frame for burnins current frame. + listed_keys (list): List of keys that are changed per frame. + options (Optional[dict]): Options to affect style of burnin. + """ + + if not options: + options = ffmpeg_burnins.TimeCodeOptions(**self.options_init) + + options = options.copy() + if frame_start is None: + frame_start = options["frame_offset"] + + # `frame_end` is only for meassurements of text position + if frame_end is None: + frame_end = options["frame_end"] + + fps = options.get("fps") + if not fps: + fps = self.frame_rate + + text_for_size = text + if CURRENT_FRAME_SPLITTER in text: + expr = self._get_current_frame_expression(frame_start, frame_end) + if expr is None: + expr = MISSING_KEY_VALUE + text_for_size = text_for_size.replace( + CURRENT_FRAME_SPLITTER, MISSING_KEY_VALUE) + text = text.replace(CURRENT_FRAME_SPLITTER, expr) + + # Find longest list with values + longest_list_len = max( + len(item["values"]) for item in listed_keys.values() + ) + # Where to store formatted values per frame by key + new_listed_keys = [{} for _ in range(longest_list_len)] + # Find the longest value per fill key. + # The longest value is used to determine size of burnin box. + longest_value_by_key = {} + for key, item in listed_keys.items(): + values = item["values"] + # Fill the missing values from the longest list with the last + # value to make sure all values have same "frame count" + last_value = values[-1] if values else "" + for _ in range(longest_list_len - len(values)): + values.append(last_value) + + # Prepare dictionary structure for nestes values + # - last key is overriden on each frame loop + item_keys = list(item["keys"]) + fill_data = {} + sub_value = fill_data + last_item_key = item_keys.pop(-1) + for item_key in item_keys: + sub_value[item_key] = {} + sub_value = sub_value[item_key] + + # Fill value per frame + key_max_len = 0 + key_max_value = "" + for value, new_values in zip(values, new_listed_keys): + sub_value[last_item_key] = value + try: + value = key.format(**sub_value) + except (TypeError, KeyError, ValueError): + value = MISSING_KEY_VALUE + new_values[key] = value + + value_len = len(value) + if value_len > key_max_len: + key_max_value = value + key_max_len = value_len + + # Store the longest value + longest_value_by_key[key] = key_max_value + + # Make sure the longest value of each key is replaced for text size + # calculation + for key, value in longest_value_by_key.items(): + text_for_size = text_for_size.replace(key, value) + + # Create temp file with instructions for each frame of text + lines = [] + for frame, value in enumerate(new_listed_keys): + seconds = float(frame) / fps + # Escape special character + new_text = text + for _key, _value in value.items(): + _value = str(_value) + new_text = new_text.replace(_key, str(_value)) + + new_text = ( + str(new_text) + .replace("\\", "\\\\") + .replace(",", "\\,") + .replace(":", "\\:") + ) + lines.append( + f"{seconds} drawtext@{align} reinit text='{new_text}';") + + with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp: + path = temp.name + temp.write("\n".join(lines)) + + self.cleanup_paths.append(path) + self.filters["drawtext"].append("sendcmd=f='{}'".format( + path.replace("\\", "/").replace(":", "\\:") + )) + self.add_text(text_for_size, align, frame_start, frame_end, options) + + def _get_current_frame_expression(self, frame_start, frame_end): + if frame_start is None: + return None + return ( + "%{eif:n+" + str(frame_start) + + ":d:" + str(len(str(frame_end))) + "}" + ) + def _add_burnin(self, text, align, options, draw): """ Generic method for building the filter flags. @@ -276,18 +366,19 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): if CURRENT_FRAME_SPLITTER in text: frame_start = options["frame_offset"] frame_end = options.get("frame_end", frame_start) - if frame_start is None: - replacement_final = replacement_size = str(MISSING_KEY_VALUE) + expr = self._get_current_frame_expression(frame_start, frame_end) + if expr is not None: + max_length = len(str(frame_end)) + # Use number '8' length times for replacement + size_replacement = max_length * "8" else: - replacement_final = "%{eif:n+" + str(frame_start) + ":d:" + \ - str(len(str(frame_end))) + "}" - replacement_size = str(frame_end) + expr = size_replacement = MISSING_KEY_VALUE final_text = final_text.replace( - CURRENT_FRAME_SPLITTER, replacement_final + CURRENT_FRAME_SPLITTER, expr ) text_for_size = text_for_size.replace( - CURRENT_FRAME_SPLITTER, replacement_size + CURRENT_FRAME_SPLITTER, size_replacement ) resolution = self.resolution @@ -314,13 +405,11 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): ffmpeg_burnins._drawtext(align, resolution, text_for_size, options) ) - arg_font_path = font_path - if platform.system().lower() == "windows": - arg_font_path = ( - arg_font_path - .replace(os.sep, r'\\' + os.sep) - .replace(':', r'\:') - ) + arg_font_path = ( + font_path + .replace("\\", "\\\\") + .replace(':', r'\:') + ) data["font"] = arg_font_path self.filters['drawtext'].append(draw % data) @@ -347,9 +436,15 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): if overwrite: output = '-y {}'.format(output) - filters = '' - if self.filter_string: - filters = '-vf "{}"'.format(self.filter_string) + filters = "" + filter_string = self.filter_string + if filter_string: + with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp: + temp.write(filter_string) + filters_path = temp.name + filters = '-filter_script "{}"'.format(filters_path) + print("Filters:", filter_string) + self.cleanup_paths.append(filters_path) if self.first_frame is not None: start_number_arg = "-start_number {}".format(self.first_frame) @@ -420,6 +515,10 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): "Failed to generate this f*cking file '%s'" % output ) + for path in self.cleanup_paths: + if os.path.exists(path): + os.remove(path) + def example(input_path, output_path): options_init = { @@ -440,6 +539,51 @@ def example(input_path, output_path): burnin.render(output_path, overwrite=True) +def prepare_fill_values(burnin_template, data): + """Prepare values that will be filled instead of burnin template. + + Args: + burnin_template (str): Burnin template string. + data (dict[str, Any]): Data that will be used to fill template. + + Returns: + tuple[dict[str, dict[str, Any]], dict[str, Any], set[str]]: Filled + values that can be used as are, listed values that have different + value per frame and missing keys that are not present in data. + """ + + fill_values = {} + listed_keys = {} + missing_keys = set() + for item in Formatter().parse(burnin_template): + _, field_name, format_spec, conversion = item + if not field_name: + continue + # Calculate nested keys '{project[name]}' -> ['project', 'name'] + keys = [key.rstrip("]") for key in field_name.split("[")] + # Calculate original full key for replacement + conversion = "!{}".format(conversion) if conversion else "" + format_spec = ":{}".format(format_spec) if format_spec else "" + orig_key = "{{{}{}{}}}".format( + field_name, conversion, format_spec) + + key_value = data + try: + for key in keys: + key_value = key_value[key] + + if isinstance(key_value, list): + listed_keys[orig_key] = { + "values": key_value, + "keys": keys} + else: + fill_values[orig_key] = orig_key.format(**data) + except (KeyError, TypeError): + missing_keys.add(orig_key) + continue + return fill_values, listed_keys, missing_keys + + def burnins_from_data( input_path, output_path, data, codec_data=None, options=None, burnin_values=None, overwrite=True, @@ -512,17 +656,26 @@ def burnins_from_data( frame_end = data.get("frame_end") frame_start_tc = data.get('frame_start_tc', frame_start) - stream = burnin._streams[0] + video_stream = None + for stream in burnin._streams: + if stream.get("codec_type") == "video": + video_stream = stream + break + + if video_stream is None: + raise ValueError("Source didn't have video stream.") + if "resolution_width" not in data: - data["resolution_width"] = stream.get("width", MISSING_KEY_VALUE) + data["resolution_width"] = video_stream.get( + "width", MISSING_KEY_VALUE) if "resolution_height" not in data: - data["resolution_height"] = stream.get("height", MISSING_KEY_VALUE) + data["resolution_height"] = video_stream.get( + "height", MISSING_KEY_VALUE) + r_frame_rate = video_stream.get("r_frame_rate", "0/0") if "fps" not in data: - data["fps"] = convert_ffprobe_fps_value( - stream.get("r_frame_rate", "0/0") - ) + data["fps"] = convert_ffprobe_fps_value(r_frame_rate) # Check frame start and add expression if is available if frame_start is not None: @@ -531,9 +684,9 @@ def burnins_from_data( if frame_start_tc is not None: data[TIMECODE_KEY[1:-1]] = TIMECODE_KEY - source_timecode = stream.get("timecode") + source_timecode = video_stream.get("timecode") if source_timecode is None: - source_timecode = stream.get("tags", {}).get("timecode") + source_timecode = video_stream.get("tags", {}).get("timecode") # Use "format" key from ffprobe data # - this is used e.g. in mxf extension @@ -589,59 +742,24 @@ def burnins_from_data( print("Source does not have set timecode value.") value = value.replace(SOURCE_TIMECODE_KEY, MISSING_KEY_VALUE) - # Convert lists. - cmd = "" - text = None - keys = [i[1] for i in Formatter().parse(value) if i[1] is not None] - list_to_convert = [] - - # Warn about nested dictionary support for lists. Ei. we dont support - # it. - if "[" in "".join(keys): - print( - "We dont support converting nested dictionaries to lists," - " so skipping {}".format(value) - ) - else: - for key in keys: - data_value = data[key] - - # Multiple lists are not supported. - if isinstance(data_value, list) and list_to_convert: - raise ValueError( - "Found multiple lists to convert, which is not " - "supported: {}".format(value) - ) - - if isinstance(data_value, list): - print("Found list to convert: {}".format(data_value)) - for v in data_value: - data[key] = v - list_to_convert.append(value.format(**data)) - - if list_to_convert: - value = list_to_convert[0] - path = convert_list_to_command( - list_to_convert, data["fps"], label=align - ) - cmd = "sendcmd=f='{}'".format(path) - cmd = cmd.replace("\\", "/") - cmd = cmd.replace(":", "\\:") - clean_up_paths.append(path) - # Failsafe for missing keys. - key_pattern = re.compile(r"(\{.*?[^{0]*\})") - missing_keys = [] - for group in key_pattern.findall(value): - try: - group.format(**data) - except (TypeError, KeyError): - missing_keys.append(group) + fill_values, listed_keys, missing_keys = prepare_fill_values( + value, data + ) - missing_keys = list(set(missing_keys)) for key in missing_keys: value = value.replace(key, MISSING_KEY_VALUE) + if listed_keys: + for key, key_value in fill_values.items(): + if key == CURRENT_FRAME_KEY: + key_value = CURRENT_FRAME_SPLITTER + value = value.replace(key, str(key_value)) + burnin.add_per_frame_text( + value, align, frame_start, frame_end, listed_keys + ) + continue + # Handle timecode differently if has_source_timecode: args = [align, frame_start, frame_end, source_timecode] @@ -665,7 +783,7 @@ def burnins_from_data( text = value.format(**data) - burnin.add_text(text, align, frame_start, frame_end, cmd=cmd) + burnin.add_text(text, align, frame_start, frame_end) ffmpeg_args = [] if codec_data: From cca689e48f3c915e8f760f949e013696cb80f3f2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 22 Jun 2023 20:17:56 +0200 Subject: [PATCH 341/347] Webpublisher - headless publish shouldn't be blocking operation (#5177) * OP-6239 - headless publish from webpublisher shouldn't be blocking subprocess.call is blocking, which resulted in UI non responsiveness as it was waiting for publish to finish. * OP-6239 - revert of typo --- .../hosts/webpublisher/webserver_service/webpublish_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py index 4039d2c8ec..9fe4b4d3c1 100644 --- a/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py +++ b/openpype/hosts/webpublisher/webserver_service/webpublish_routes.py @@ -293,7 +293,7 @@ class BatchPublishEndpoint(WebpublishApiEndpoint): log.debug("Adding to queue") self.resource.studio_task_queue.append(args) else: - subprocess.call(args) + subprocess.Popen(args) return Response( status=200, From 15c7978e2819afc1bcc2e2b49e3b579f84d6607f Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 23 Jun 2023 07:38:35 +0100 Subject: [PATCH 342/347] Revert "Adding support for excluded families." This reverts commit 40cf531cb12bf6a042d4535b08bc3139ed415cac. --- openpype/hosts/maya/plugins/load/actions.py | 17 +++++++++++++++-- openpype/pipeline/load/plugins.py | 17 +---------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py index 57d1039245..4855f3eed0 100644 --- a/openpype/hosts/maya/plugins/load/actions.py +++ b/openpype/hosts/maya/plugins/load/actions.py @@ -93,8 +93,21 @@ class ImportMayaLoader(load.LoaderPlugin): """ representations = ["ma", "mb", "obj"] - families = ["*"] - excluded_families = ["xgen"] + families = [ + "model", + "pointcache", + "proxyAbc", + "animation", + "mayaAscii", + "mayaScene", + "setdress", + "layout", + "camera", + "rig", + "camerarig", + "staticMesh", + "workfile" + ] label = "Import" order = 10 diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index 433d7f671a..e380d65bbe 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -28,7 +28,6 @@ class LoaderPlugin(list): """ families = [] - excluded_families = [] representations = [] extensions = {"*"} order = 0 @@ -160,10 +159,7 @@ class LoaderPlugin(list): return False plugin_families = set(plugin_families) - - # Return if all families are allowed and no excluded families are - # defined. - if "*" in plugin_families and not cls.excluded_families: + if "*" in plugin_families: return True subset_doc = context["subset"] @@ -179,17 +175,6 @@ class LoaderPlugin(list): if not families: return False - - # Return if there are intersections between the subsets families and - # the loaders excluded families. - if list(set(families) & set(cls.excluded_families)): - return False - - # Return if all families are allowed since excluded families have - # already been considered. - if "*" in plugin_families: - return True - return any(family in plugin_families for family in families) @classmethod From 1279acc0900d615a6af2d800f97c79a91e6c8c34 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 23 Jun 2023 16:06:19 +0200 Subject: [PATCH 343/347] :bug: fix ASS creator and validator --- .../create/create_arnold_scene_source.py | 2 +- .../publish/validate_ass_relative_paths.py | 32 ++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_arnold_scene_source.py b/openpype/hosts/maya/plugins/create/create_arnold_scene_source.py index 945074e32f..0c8cf8d2bb 100644 --- a/openpype/hosts/maya/plugins/create/create_arnold_scene_source.py +++ b/openpype/hosts/maya/plugins/create/create_arnold_scene_source.py @@ -98,4 +98,4 @@ class CreateArnoldSceneSource(plugin.MayaCreator): content = cmds.sets(name=instance_node + "_content_SET", empty=True) proxy = cmds.sets(name=instance_node + "_proxy_SET", empty=True) - cmds.sets([content, proxy], forceElement=instance) + cmds.sets([content, proxy], forceElement=instance_node) diff --git a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py index 6975d583bb..4230195a22 100644 --- a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py +++ b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py @@ -23,7 +23,9 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): def process(self, instance): # we cannot ask this until user open render settings as - # `defaultArnoldRenderOptions` doesn't exists + # `defaultArnoldRenderOptions` doesn't exist + errors = [] + try: relative_texture = cmds.getAttr( "defaultArnoldRenderOptions.absolute_texture_paths") @@ -42,10 +44,11 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): scene_dir, scene_basename = os.path.split(cmds.file(q=True, loc=True)) scene_name, _ = os.path.splitext(scene_basename) - assert self.maya_is_true(relative_texture) is not True, \ - ("Texture path is set to be absolute") - assert self.maya_is_true(relative_procedural) is not True, \ - ("Procedural path is set to be absolute") + + if not self.maya_is_true(relative_texture): + errors.append("Texture path is set to be absolute") + if not self.maya_is_true(relative_procedural): + errors.append("Procedural path is set to be absolute") anatomy = instance.context.data["anatomy"] @@ -57,15 +60,20 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): for k in keys: paths.append("[{}]".format(k)) - self.log.info("discovered roots: {}".format(":".join(paths))) + self.log.debug("discovered roots: {}".format(":".join(paths))) - assert ":".join(paths) in texture_search_path, ( - "Project roots are not in texture_search_path" - ) + if ":".join(paths) not in texture_search_path: + errors.append(( + "Project roots {} are not in texture_search_path: {}" + ).format(paths, texture_search_path)) - assert ":".join(paths) in procedural_search_path, ( - "Project roots are not in procedural_search_path" - ) + if ":".join(paths) not in procedural_search_path: + errors.append(( + "Project roots {} are not in procedural_search_path: {}" + ).format(paths, procedural_search_path)) + + if errors: + raise PublishValidationError("\n".join(errors)) @classmethod def repair(cls, instance): From 3ffef444ba3d7cabc34dc47b066edc9d20f6d805 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 23 Jun 2023 16:11:23 +0200 Subject: [PATCH 344/347] Publisher: Edge case fixes (#5165) * do not crash whole controller because of invalid exception * handle missing instance label * fix also list view --- openpype/tools/publisher/control.py | 12 +++++++++++- .../tools/publisher/publish_report_viewer/model.py | 9 +++++++-- .../tools/publisher/widgets/list_view_widgets.py | 7 ++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 89c2343ef7..502e871edd 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -398,12 +398,22 @@ class PublishReportMaker: exception = result.get("error") if exception: fname, line_no, func, exc = exception.traceback + + # Conversion of exception into string may crash + try: + msg = str(exception) + except BaseException: + msg = ( + "Publisher Controller: ERROR" + " - Failed to get exception message" + ) + # Action result does not have 'is_validation_error' is_validation_error = result.get("is_validation_error", False) output.append({ "type": "error", "is_validation_error": is_validation_error, - "msg": str(exception), + "msg": msg, "filename": str(fname), "lineno": str(line_no), "func": str(func), diff --git a/openpype/tools/publisher/publish_report_viewer/model.py b/openpype/tools/publisher/publish_report_viewer/model.py index ff10e091b8..663a67ac70 100644 --- a/openpype/tools/publisher/publish_report_viewer/model.py +++ b/openpype/tools/publisher/publish_report_viewer/model.py @@ -45,8 +45,13 @@ class InstancesModel(QtGui.QStandardItemModel): instance_items = report_item.instance_items_by_family[family] all_removed = True for instance_item in instance_items: - item = QtGui.QStandardItem(instance_item.label) - instance_label = html_escape(instance_item.label) + src_instance_label = instance_item.label + if src_instance_label is None: + # Do not cause UI crash if label is 'None' + src_instance_label = "No label" + instance_label = html_escape(src_instance_label) + + item = QtGui.QStandardItem(src_instance_label) item.setData(instance_label, ITEM_LABEL_ROLE) item.setData(instance_item.errored, ITEM_ERRORED_ROLE) item.setData(instance_item.id, ITEM_ID_ROLE) diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py index 557e6559c8..3370f71701 100644 --- a/openpype/tools/publisher/widgets/list_view_widgets.py +++ b/openpype/tools/publisher/widgets/list_view_widgets.py @@ -116,7 +116,12 @@ class InstanceListItemWidget(QtWidgets.QWidget): self.instance = instance - instance_label = html_escape(instance.label) + instance_label = instance.label + if instance_label is None: + # Do not cause UI crash if label is 'None' + instance_label = "No label" + + instance_label = html_escape(instance_label) subset_name_label = QtWidgets.QLabel(instance_label, self) subset_name_label.setObjectName("ListViewSubsetName") From 7973c5fbe079dc542343cfbe29349fb3395df189 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Jun 2023 16:27:37 +0200 Subject: [PATCH 345/347] Workfile Builder UI: Workfile builder window is not modal (#5131) * Open create placeholder window as regular window instead of modal dialog * Allow to create even as root node without a parent selection * Cosmetics: typo * Keep create dialog open after create so user can directly create another with similar settings * Match functionality in Nuke to the one in Maya --------- Co-authored-by: Roy Nieterau --- .../maya/api/workfile_template_builder.py | 35 ++++++++----------- .../nuke/api/workfile_template_builder.py | 9 +++-- .../tools/workfile_template_build/window.py | 1 - 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/maya/api/workfile_template_builder.py b/openpype/hosts/maya/api/workfile_template_builder.py index 6e6166c2ef..b7e731bbb6 100644 --- a/openpype/hosts/maya/api/workfile_template_builder.py +++ b/openpype/hosts/maya/api/workfile_template_builder.py @@ -14,7 +14,7 @@ from openpype.tools.workfile_template_build import ( WorkfileBuildPlaceholderDialog, ) -from .lib import read, imprint +from .lib import read, imprint, get_main_window PLACEHOLDER_SET = "PLACEHOLDERS_SET" @@ -173,44 +173,37 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): def create_placeholder(self, placeholder_data): selection = cmds.ls(selection=True) - if not selection: - raise ValueError("Nothing is selected") if len(selection) > 1: raise ValueError("More then one item are selected") + parent = selection[0] if selection else None + placeholder_data["plugin_identifier"] = self.identifier placeholder_name = self._create_placeholder_name(placeholder_data) placeholder = cmds.spaceLocator(name=placeholder_name)[0] - # TODO: this can crash if selection can't be used - cmds.parent(placeholder, selection[0]) + if parent: + placeholder = cmds.parent(placeholder, selection[0])[0] - # get the long name of the placeholder (with the groups) - placeholder_full_name = ( - cmds.ls(selection[0], long=True)[0] - + "|" - + placeholder.replace("|", "") - ) - - imprint(placeholder_full_name, placeholder_data) + imprint(placeholder, placeholder_data) # Add helper attributes to keep placeholder info cmds.addAttr( - placeholder_full_name, + placeholder, longName="parent", hidden=True, dataType="string" ) cmds.addAttr( - placeholder_full_name, + placeholder, longName="index", hidden=True, attributeType="short", defaultValue=-1 ) - cmds.setAttr(placeholder_full_name + ".parent", "", type="string") + cmds.setAttr(placeholder + ".parent", "", type="string") def update_placeholder(self, placeholder_item, placeholder_data): node_name = placeholder_item.scene_identifier @@ -233,7 +226,7 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin): if placeholder_data.get("plugin_identifier") != self.identifier: continue - # TODO do data validations and maybe updgrades if are invalid + # TODO do data validations and maybe upgrades if they are invalid output.append( LoadPlaceholderItem(node_name, placeholder_data, self) ) @@ -319,8 +312,9 @@ def update_workfile_template(*args): def create_placeholder(*args): host = registered_host() builder = MayaTemplateBuilder(host) - window = WorkfileBuildPlaceholderDialog(host, builder) - window.exec_() + window = WorkfileBuildPlaceholderDialog(host, builder, + parent=get_main_window()) + window.show() def update_placeholder(*args): @@ -343,6 +337,7 @@ def update_placeholder(*args): raise ValueError("Too many selected nodes") placeholder_item = placeholder_items[0] - window = WorkfileBuildPlaceholderDialog(host, builder) + window = WorkfileBuildPlaceholderDialog(host, builder, + parent=get_main_window()) window.set_update_mode(placeholder_item) window.exec_() diff --git a/openpype/hosts/nuke/api/workfile_template_builder.py b/openpype/hosts/nuke/api/workfile_template_builder.py index 72d4ffb476..766fb0bc47 100644 --- a/openpype/hosts/nuke/api/workfile_template_builder.py +++ b/openpype/hosts/nuke/api/workfile_template_builder.py @@ -25,6 +25,7 @@ from .lib import ( select_nodes, duplicate_node, node_tempfile, + get_main_window ) PLACEHOLDER_SET = "PLACEHOLDERS_SET" @@ -963,8 +964,9 @@ def update_workfile_template(*args): def create_placeholder(*args): host = registered_host() builder = NukeTemplateBuilder(host) - window = WorkfileBuildPlaceholderDialog(host, builder) - window.exec_() + window = WorkfileBuildPlaceholderDialog(host, builder, + parent=get_main_window()) + window.show() def update_placeholder(*args): @@ -988,6 +990,7 @@ def update_placeholder(*args): raise ValueError("Too many selected nodes") placeholder_item = placeholder_items[0] - window = WorkfileBuildPlaceholderDialog(host, builder) + window = WorkfileBuildPlaceholderDialog(host, builder, + parent=get_main_window()) window.set_update_mode(placeholder_item) window.exec_() diff --git a/openpype/tools/workfile_template_build/window.py b/openpype/tools/workfile_template_build/window.py index 24d9105223..df7aedf566 100644 --- a/openpype/tools/workfile_template_build/window.py +++ b/openpype/tools/workfile_template_build/window.py @@ -220,7 +220,6 @@ class WorkfileBuildPlaceholderDialog(QtWidgets.QDialog): # TODO much better error handling try: plugin.create_placeholder(options) - self.accept() except Exception: self.log.warning("Something went wrong", exc_info=True) dialog = QtWidgets.QMessageBox(self) From 97834e32ba49af2b6492b875957d82d628b0001b Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 23 Jun 2023 16:31:29 +0200 Subject: [PATCH 346/347] :recycle: make the validation logic more clear --- .../maya/plugins/publish/validate_ass_relative_paths.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py index 4230195a22..49913fa42b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py +++ b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py @@ -27,9 +27,9 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): errors = [] try: - relative_texture = cmds.getAttr( + absolute_texture = cmds.getAttr( "defaultArnoldRenderOptions.absolute_texture_paths") - relative_procedural = cmds.getAttr( + absolute_procedural = cmds.getAttr( "defaultArnoldRenderOptions.absolute_procedural_paths") texture_search_path = cmds.getAttr( "defaultArnoldRenderOptions.tspath" @@ -45,9 +45,9 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin): scene_dir, scene_basename = os.path.split(cmds.file(q=True, loc=True)) scene_name, _ = os.path.splitext(scene_basename) - if not self.maya_is_true(relative_texture): + if self.maya_is_true(absolute_texture): errors.append("Texture path is set to be absolute") - if not self.maya_is_true(relative_procedural): + if self.maya_is_true(absolute_procedural): errors.append("Procedural path is set to be absolute") anatomy = instance.context.data["anatomy"] From 0de1b2f685789b20c1a91fadde5469c917c10d09 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 23 Jun 2023 17:24:32 +0200 Subject: [PATCH 347/347] :recycle: convert asserts --- .../publish/validate_render_image_rule.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py b/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py index 78bb022785..f9aa7f82d0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py +++ b/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py @@ -1,10 +1,8 @@ +import pyblish.api from maya import cmds -import pyblish.api from openpype.pipeline.publish import ( - RepairAction, - ValidateContentsOrder, -) + PublishValidationError, RepairAction, ValidateContentsOrder) class ValidateRenderImageRule(pyblish.api.InstancePlugin): @@ -27,12 +25,12 @@ class ValidateRenderImageRule(pyblish.api.InstancePlugin): required_images_rule = self.get_default_render_image_folder(instance) current_images_rule = cmds.workspace(fileRuleEntry="images") - assert current_images_rule == required_images_rule, ( - "Invalid workspace `images` file rule value: '{}'. " - "Must be set to: '{}'".format( - current_images_rule, required_images_rule - ) - ) + if current_images_rule != required_images_rule: + raise PublishValidationError( + ( + "Invalid workspace `images` file rule value: '{}'. " + "Must be set to: '{}'" + ).format(current_images_rule, required_images_rule)) @classmethod def repair(cls, instance):