From c104805830c349260b30756b19560836bd9866a6 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Tue, 14 Mar 2023 16:17:17 +0100 Subject: [PATCH 001/105] adding creator, loader for redshift proxy in 3dsmax --- .../plugins/create/create_redshift_proxy.py | 26 +++++++ .../max/plugins/load/load_redshift_proxy.py | 59 ++++++++++++++ .../plugins/publish/extract_redshift_proxy.py | 78 +++++++++++++++++++ 3 files changed, 163 insertions(+) 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 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..83ddc3a193 --- /dev/null +++ b/openpype/hosts/max/plugins/create/create_redshift_proxy.py @@ -0,0 +1,26 @@ +# -*- 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): + from pymxs import runtime as rt + sel_obj = list(rt.selection) + instance = super(CreateRedshiftProxy, 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 + 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/load/load_redshift_proxy.py b/openpype/hosts/max/plugins/load/load_redshift_proxy.py new file mode 100644 index 0000000000..7a5e94158f --- /dev/null +++ b/openpype/hosts/max/plugins/load/load_redshift_proxy.py @@ -0,0 +1,59 @@ +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): + """Redshift Proxy Loader""" + + 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 = os.path.normpath(self.fname) + 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 = f"{name}" + rs_proxy.Parent = container + + asset = rt.getNodeByName(f"{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"]) + + proxy_objects = self.get_container_children(node) + for proxy in proxy_objects: + proxy.source = path + + lib.imprint(container["instance_node"], { + "representation": str(representation["_id"]) + }) + + 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..f9dd726ef4 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -0,0 +1,78 @@ +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 +) + + +class ExtractCameraAlembic(publish.Extractor, + OptionalPyblishPluginMixin): + """ + Extract Camera with AlembicExport + """ + + 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) + + # MaxScript command for export + export_cmd = ( + f""" +fn ProxyExport fp selected:true compress:false connectivity:false startFrame: endFrame: camera:undefined warnExisting:true transformPivotToOrigin:false = ( + if startFrame == unsupplied then ( + startFrame = (currentTime.frame as integer) + ) + + if endFrame == unsupplied then ( + endFrame = (currentTime.frame as integer) + ) + + ret = rsProxy fp selected compress connectivity startFrame endFrame camera warnExisting transformPivotToOrigin + + ret +) +execute = ProxyExport fp selected:true compress:false connectivity:false startFrame:{start} endFrame:{end} warnExisting:false transformPivotToOrigin:bTransformPivotToOrigin + + """) # noqa + + with maintained_selection(): + # select and export + rt.select(container.Children) + rt.execute(export_cmd) + + self.log.info("Performing Extraction ...") + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'rs', + 'ext': 'rs', + # need to count the files + 'files': rs_filename, + "stagingDir": stagingdir, + } + instance.data["representations"].append(representation) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, + rs_filepath)) + + # TODO: set sequence + def get_rsfiles(self, container, startFrame, endFrame): + pass From 5af9867dedda3eb154fedd4aba0c93d8438b80df Mon Sep 17 00:00:00 2001 From: moonyuet Date: Tue, 14 Mar 2023 16:25:00 +0100 Subject: [PATCH 002/105] update fix --- openpype/hosts/max/plugins/publish/extract_redshift_proxy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index f9dd726ef4..85d249b020 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.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 ) From 9cbac449fded786bc033931a07c9e44dd18907e2 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Tue, 14 Mar 2023 16:26:06 +0100 Subject: [PATCH 003/105] change the name --- openpype/hosts/max/plugins/publish/extract_redshift_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index 85d249b020..938a7e8c2c 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -10,7 +10,7 @@ from openpype.hosts.max.api import ( ) -class ExtractCameraAlembic(publish.Extractor, +class ExtractRedshiftProxy(publish.Extractor, OptionalPyblishPluginMixin): """ Extract Camera with AlembicExport From 119bb1a586548e6997f3e2724660c19e0d346a56 Mon Sep 17 00:00:00 2001 From: moonyuet Date: Tue, 14 Mar 2023 16:37:25 +0100 Subject: [PATCH 004/105] update the loader and creator --- openpype/hosts/max/plugins/create/create_redshift_proxy.py | 5 +---- openpype/hosts/max/plugins/load/load_redshift_proxy.py | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_redshift_proxy.py b/openpype/hosts/max/plugins/create/create_redshift_proxy.py index 83ddc3a193..ca0891fc5b 100644 --- a/openpype/hosts/max/plugins/create/create_redshift_proxy.py +++ b/openpype/hosts/max/plugins/create/create_redshift_proxy.py @@ -18,9 +18,6 @@ class CreateRedshiftProxy(plugin.MaxCreator): 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 + 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/load/load_redshift_proxy.py b/openpype/hosts/max/plugins/load/load_redshift_proxy.py index 7a5e94158f..13003d764a 100644 --- a/openpype/hosts/max/plugins/load/load_redshift_proxy.py +++ b/openpype/hosts/max/plugins/load/load_redshift_proxy.py @@ -10,8 +10,8 @@ from openpype.hosts.max.api import lib class RedshiftProxyLoader(load.LoaderPlugin): - """Redshift Proxy Loader""" + label = "Load Redshift Proxy" families = ["redshiftproxy"] representations = ["rs"] order = -9 @@ -21,7 +21,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): def load(self, context, name=None, namespace=None, data=None): from pymxs import runtime as rt - filepath = os.path.normpath(self.fname) + filepath = self.filepath_from_context(context) rs_proxy = rt.RedshiftProxy() rs_proxy.file = filepath files_in_folder = os.listdir(os.path.dirname(filepath)) @@ -30,7 +30,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): rs_proxy.is_sequence = True container = rt.container() - container.name = f"{name}" + container.name = name rs_proxy.Parent = container asset = rt.getNodeByName(f"{name}") From f3bd329d5a40793a6e083198326b22b43a58c621 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Mar 2023 18:48:58 +0800 Subject: [PATCH 005/105] add validator for checking if the current renderer is redshift before the extraction --- .../validate_renderer_redshift_proxy.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py 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..3a921c386e --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py @@ -0,0 +1,50 @@ +# -*- 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_all_renderer(instance) + if invalid: + raise PublishValidationError("Please install Redshift for 3dsMax" + " before using this!") + invalid = self.get_current_renderer(instance) + if invalid: + raise PublishValidationError("Current Renderer is not Redshift") + + def get_all_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): + if "Redshift_Renderer" in str(rt.RendererClass.classes[2]()): + rt.renderers.production = rt.RendererClass.classes[2]() From 91abe54b01ce08856004b7e395735c9508ab8300 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Mar 2023 22:35:02 +0800 Subject: [PATCH 006/105] add the extractor for redshift proxy --- .../plugins/publish/extract_redshift_proxy.py | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index 938a7e8c2c..1616ead0ac 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -6,7 +6,8 @@ from openpype.pipeline import ( ) from pymxs import runtime as rt from openpype.hosts.max.api import ( - maintained_selection + maintained_selection, + get_all_children ) @@ -29,35 +30,22 @@ class ExtractRedshiftProxy(publish.Extractor, 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("\\", "/") - # MaxScript command for export - export_cmd = ( - f""" -fn ProxyExport fp selected:true compress:false connectivity:false startFrame: endFrame: camera:undefined warnExisting:true transformPivotToOrigin:false = ( - if startFrame == unsupplied then ( - startFrame = (currentTime.frame as integer) - ) - - if endFrame == unsupplied then ( - endFrame = (currentTime.frame as integer) - ) - - ret = rsProxy fp selected compress connectivity startFrame endFrame camera warnExisting transformPivotToOrigin - - ret -) -execute = ProxyExport fp selected:true compress:false connectivity:false startFrame:{start} endFrame:{end} warnExisting:false transformPivotToOrigin:bTransformPivotToOrigin - - """) # noqa + rs_filenames = self.get_rsfiles(instance, start, end) with maintained_selection(): # select and export - rt.select(container.Children) - rt.execute(export_cmd) + # con = rt.getNodeByName(container) + rt.select(get_all_children(rt.getNodeByName(container))) + # 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"] = [] @@ -65,13 +53,19 @@ execute = ProxyExport fp selected:true compress:false connectivity:false startFr 'name': 'rs', 'ext': 'rs', # need to count the files - 'files': rs_filename, + 'files': rs_filenames if len(rs_filenames) > 1 else rs_filenames[0], "stagingDir": stagingdir, } instance.data["representations"].append(representation) self.log.info("Extracted instance '%s' to: %s" % (instance.name, - rs_filepath)) + stagingdir)) # TODO: set sequence - def get_rsfiles(self, container, startFrame, endFrame): - pass + 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 From be6813293c605d5b477af72fedcca047b6e7f0c0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Mar 2023 22:36:22 +0800 Subject: [PATCH 007/105] shut hound --- openpype/hosts/max/plugins/publish/extract_redshift_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index 1616ead0ac..8924242a93 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -53,7 +53,7 @@ class ExtractRedshiftProxy(publish.Extractor, 'name': 'rs', 'ext': 'rs', # need to count the files - 'files': rs_filenames if len(rs_filenames) > 1 else rs_filenames[0], + 'files': rs_filenames if len(rs_filenames) > 1 else rs_filenames[0], # noqa "stagingDir": stagingdir, } instance.data["representations"].append(representation) From f25b5d309ad212b273a2ba6ccdfa6b1d950e5a25 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Mar 2023 22:42:28 +0800 Subject: [PATCH 008/105] cleanup --- .../max/plugins/publish/extract_redshift_proxy.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index 8924242a93..bf16c8d4a9 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -1,9 +1,6 @@ import os import pyblish.api -from openpype.pipeline import ( - publish, - OptionalPyblishPluginMixin -) +from openpype.pipeline import publish from pymxs import runtime as rt from openpype.hosts.max.api import ( maintained_selection, @@ -11,10 +8,9 @@ from openpype.hosts.max.api import ( ) -class ExtractRedshiftProxy(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractRedshiftProxy(publish.Extractor): """ - Extract Camera with AlembicExport + Extract Redshift Proxy """ order = pyblish.api.ExtractorOrder - 0.1 From 6d51333a20e13b07d8f32ea69c299163b1c90fc4 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Mar 2023 22:45:52 +0800 Subject: [PATCH 009/105] add docstrings --- openpype/hosts/max/plugins/publish/extract_redshift_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index bf16c8d4a9..c91391429d 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -10,7 +10,7 @@ from openpype.hosts.max.api import ( class ExtractRedshiftProxy(publish.Extractor): """ - Extract Redshift Proxy + Extract Redshift Proxy with rsProxy """ order = pyblish.api.ExtractorOrder - 0.1 From b9ec96fdd64b4d8e7f770dfc722e459f28cd596e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Mar 2023 22:48:21 +0800 Subject: [PATCH 010/105] add docstring for the rs loader --- openpype/hosts/max/plugins/load/load_redshift_proxy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/max/plugins/load/load_redshift_proxy.py b/openpype/hosts/max/plugins/load/load_redshift_proxy.py index 13003d764a..30879bca78 100644 --- a/openpype/hosts/max/plugins/load/load_redshift_proxy.py +++ b/openpype/hosts/max/plugins/load/load_redshift_proxy.py @@ -11,6 +11,8 @@ 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"] From 3ba7b9b1ffc8ba9877024f8175af8adcbd984e08 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Mar 2023 23:05:25 +0800 Subject: [PATCH 011/105] fix selection of children --- openpype/hosts/max/plugins/publish/extract_redshift_proxy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index c91391429d..5aba257443 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -33,8 +33,8 @@ class ExtractRedshiftProxy(publish.Extractor): with maintained_selection(): # select and export - # con = rt.getNodeByName(container) - rt.select(get_all_children(rt.getNodeByName(container))) + con = rt.getNodeByName(container) + rt.select(con.Children) # Redshift rsProxy command # rsProxy fp selected compress connectivity startFrame endFrame # camera warnExisting transformPivotToOrigin From 35448073aba93b3ead99513b0d831accb00c76cb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 28 Mar 2023 23:07:05 +0800 Subject: [PATCH 012/105] hound fix --- openpype/hosts/max/plugins/publish/extract_redshift_proxy.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index 5aba257443..0a3579d687 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -2,10 +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, - get_all_children -) +from openpype.hosts.max.api import maintained_selection class ExtractRedshiftProxy(publish.Extractor): From 14b8139a5cfe92680233343d8e4120ae2253865f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 3 Apr 2023 16:47:15 +0800 Subject: [PATCH 013/105] Roy's comment & fix the loader update --- openpype/hosts/max/plugins/load/load_redshift_proxy.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/load/load_redshift_proxy.py b/openpype/hosts/max/plugins/load/load_redshift_proxy.py index 30879bca78..fd79a2b97c 100644 --- a/openpype/hosts/max/plugins/load/load_redshift_proxy.py +++ b/openpype/hosts/max/plugins/load/load_redshift_proxy.py @@ -35,7 +35,7 @@ class RedshiftProxyLoader(load.LoaderPlugin): container.name = name rs_proxy.Parent = container - asset = rt.getNodeByName(f"{name}") + asset = rt.getNodeByName(name) return containerise( name, [asset], context, loader=self.__class__.__name__) @@ -45,10 +45,10 @@ class RedshiftProxyLoader(load.LoaderPlugin): path = get_representation_path(representation) node = rt.getNodeByName(container["instance_node"]) - - proxy_objects = self.get_container_children(node) - for proxy in proxy_objects: - proxy.source = path + 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"]) From 6423479078a7305d9e73f55243eb42796acb7820 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 3 Apr 2023 16:50:33 +0800 Subject: [PATCH 014/105] add switch version in the loader --- openpype/hosts/max/plugins/load/load_redshift_proxy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/max/plugins/load/load_redshift_proxy.py b/openpype/hosts/max/plugins/load/load_redshift_proxy.py index fd79a2b97c..9451e5299b 100644 --- a/openpype/hosts/max/plugins/load/load_redshift_proxy.py +++ b/openpype/hosts/max/plugins/load/load_redshift_proxy.py @@ -54,6 +54,9 @@ class RedshiftProxyLoader(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 From 442236284bc87cf3a4aff4d3ae622beaaf946c4c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 6 Apr 2023 17:53:42 +0800 Subject: [PATCH 015/105] add docs --- website/docs/artist_hosts_3dsmax.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) 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 - - - - From 7b6df29203ea2b093bc68bc4f9aec0fa6a167b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 26 Apr 2023 12:48:51 +0200 Subject: [PATCH 016/105] Feature: Blender hook to execute python scripts at launch --- .../hooks/pre_add_run_python_script_arg.py | 61 +++++++++++++++++++ website/docs/dev_blender.md | 61 +++++++++++++++++++ website/sidebars.js | 1 + 3 files changed, 123 insertions(+) create mode 100644 openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py 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..7cf7b0f852 --- /dev/null +++ b/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py @@ -0,0 +1,61 @@ +from pathlib import Path + +from openpype.lib import PreLaunchHook +from openpype.settings.lib import get_project_settings + + +class AddPythonScriptToLaunchArgs(PreLaunchHook): + """Add python script to be executed before Blender launch.""" + + # Append after file argument + order = 15 + app_groups = [ + "blender", + ] + + def execute(self): + # Check enabled in settings + project_name = self.data["project_name"] + project_settings = get_project_settings(project_name) + host_name = self.application.host_name + host_settings = project_settings.get(host_name) + if not host_settings: + self.log.info(f"""Host "{host_name}" doesn\'t have settings""") + return None + + # Add path to workfile to arguments + for python_script_path in self.launch_context.data.get( + "python_scripts", [] + ): + self.log.info( + f"Adding python script {python_script_path} to launch" + ) + # Test script path exists + if not Path(python_script_path).exists(): + raise ValueError( + f"Python script {python_script_path} doesn't exist." + ) + + 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, + Path(python_script_path).as_posix(), + ) + else: + self.launch_context.launch_args.extend( + ["-P", Path(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/website/docs/dev_blender.md b/website/docs/dev_blender.md new file mode 100644 index 0000000000..228447fb64 --- /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.pre_add_run_python_script_arg import AddPythonScriptToLaunchArgs +from openpype.lib import PreLaunchHook + + +class MyHook(PreLaunchHook): + """Add python script to be executed before Blender launch.""" + + order = 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 93887e00f6..c204c3fb45 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -179,6 +179,7 @@ module.exports = { ] }, "dev_deadline", + "dev_blender", "dev_colorspace" ] }; From 6de1710810b028949c86276088a988ccb83f06e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 26 Apr 2023 12:53:20 +0200 Subject: [PATCH 017/105] clean Path --- .../hosts/blender/hooks/pre_add_run_python_script_arg.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 index 7cf7b0f852..9ae96327ca 100644 --- a/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py +++ b/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py @@ -31,7 +31,8 @@ class AddPythonScriptToLaunchArgs(PreLaunchHook): f"Adding python script {python_script_path} to launch" ) # Test script path exists - if not Path(python_script_path).exists(): + python_script_path = Path(python_script_path) + if not python_script_path.exists(): raise ValueError( f"Python script {python_script_path} doesn't exist." ) @@ -45,11 +46,11 @@ class AddPythonScriptToLaunchArgs(PreLaunchHook): ) self.launch_context.launch_args.insert( separator_index + 1, - Path(python_script_path).as_posix(), + python_script_path.as_posix(), ) else: self.launch_context.launch_args.extend( - ["-P", Path(python_script_path).as_posix()] + ["-P", python_script_path.as_posix()] ) # Ensure separator From cdf9a10aa19b39c698a57b97abbb5858b6571de6 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 26 Apr 2023 19:10:55 +0800 Subject: [PATCH 018/105] roy's comment --- openpype/hosts/max/api/lib.py | 9 ++++++++- .../max/plugins/create/create_redshift_proxy.py | 7 ++++--- .../max/plugins/publish/extract_redshift_proxy.py | 3 +-- .../publish/validate_renderer_redshift_proxy.py | 14 +++++++++----- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/max/api/lib.py b/openpype/hosts/max/api/lib.py index ad9a450cad..27d4598a3a 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 index ca0891fc5b..1bddbdafae 100644 --- a/openpype/hosts/max/plugins/create/create_redshift_proxy.py +++ b/openpype/hosts/max/plugins/create/create_redshift_proxy.py @@ -18,6 +18,7 @@ class CreateRedshiftProxy(plugin.MaxCreator): instance_data, pre_create_data) # type: CreatedInstance container = rt.getNodeByName(instance.data.get("instance_node")) - - for obj in sel_obj: - obj.parent = container + if self.selected_nodes: + sel_obj = list(self.selected_nodes) + for obj in sel_obj: + obj.parent = container diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index 0a3579d687..eb1673c4fa 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -45,7 +45,6 @@ class ExtractRedshiftProxy(publish.Extractor): representation = { 'name': 'rs', 'ext': 'rs', - # need to count the files 'files': rs_filenames if len(rs_filenames) > 1 else rs_filenames[0], # noqa "stagingDir": stagingdir, } @@ -53,7 +52,7 @@ class ExtractRedshiftProxy(publish.Extractor): self.log.info("Extracted instance '%s' to: %s" % (instance.name, stagingdir)) - # TODO: set sequence + def get_rsfiles(self, instance, startFrame, endFrame): rs_filenames = [] rs_name = instance.data["name"] diff --git a/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py b/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py index 3a921c386e..c834f12ae2 100644 --- a/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py @@ -22,12 +22,13 @@ class ValidateRendererRedshiftProxy(pyblish.api.InstancePlugin): invalid = self.get_all_renderer(instance) if invalid: raise PublishValidationError("Please install Redshift for 3dsMax" - " before using this!") + " before using the Redshift proxy instance") invalid = self.get_current_renderer(instance) if invalid: - raise PublishValidationError("Current Renderer is not Redshift") + raise PublishValidationError("The Redshift proxy extraction discontinued" + "since the current renderer is not Redshift") - def get_all_renderer(self, instance): + def get_redshift_renderer(self, instance): invalid = list() max_renderers_list = str(rt.RendererClass.classes) if "Redshift_Renderer" not in max_renderers_list: @@ -46,5 +47,8 @@ class ValidateRendererRedshiftProxy(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - if "Redshift_Renderer" in str(rt.RendererClass.classes[2]()): - rt.renderers.production = rt.RendererClass.classes[2]() + renderer_count = len(rt.RendererClass.classes) + for r in range(renderer_count): + if "Redshift_Renderer" in str(rt.RendererClass.classes[r]()): + rt.renderers.production = rt.RendererClass.classes[r]() + break From a20d37c68045d73b5f442f503dcbaf31bb8892b5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 26 Apr 2023 19:13:33 +0800 Subject: [PATCH 019/105] hound fix --- .../hosts/max/plugins/publish/extract_redshift_proxy.py | 1 - .../max/plugins/publish/validate_renderer_redshift_proxy.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py index eb1673c4fa..3b44099609 100644 --- a/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/extract_redshift_proxy.py @@ -52,7 +52,6 @@ class ExtractRedshiftProxy(publish.Extractor): 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"] diff --git a/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py b/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py index c834f12ae2..6f8a92a93c 100644 --- a/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py @@ -22,11 +22,11 @@ class ValidateRendererRedshiftProxy(pyblish.api.InstancePlugin): invalid = self.get_all_renderer(instance) if invalid: raise PublishValidationError("Please install Redshift for 3dsMax" - " before using the Redshift proxy instance") + " 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") + raise PublishValidationError("The Redshift proxy extraction" + "discontinued since the current renderer is not Redshift") # noqa def get_redshift_renderer(self, instance): invalid = list() From 610a65420d521b26a3c44792368cbd4b9cec6219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 26 Apr 2023 14:02:18 +0200 Subject: [PATCH 020/105] remove useless settings --- .../hosts/blender/hooks/pre_add_run_python_script_arg.py | 9 --------- 1 file changed, 9 deletions(-) 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 index 9ae96327ca..ff3683baa9 100644 --- a/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py +++ b/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py @@ -14,15 +14,6 @@ class AddPythonScriptToLaunchArgs(PreLaunchHook): ] def execute(self): - # Check enabled in settings - project_name = self.data["project_name"] - project_settings = get_project_settings(project_name) - host_name = self.application.host_name - host_settings = project_settings.get(host_name) - if not host_settings: - self.log.info(f"""Host "{host_name}" doesn\'t have settings""") - return None - # Add path to workfile to arguments for python_script_path in self.launch_context.data.get( "python_scripts", [] From 4fe7ce64a2a834adcdd2763a22b8c93d532c0ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 26 Apr 2023 14:05:39 +0200 Subject: [PATCH 021/105] changes from comments --- .../hosts/blender/hooks/pre_add_run_python_script_arg.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index ff3683baa9..0f959b8f54 100644 --- a/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py +++ b/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py @@ -1,7 +1,6 @@ from pathlib import Path from openpype.lib import PreLaunchHook -from openpype.settings.lib import get_project_settings class AddPythonScriptToLaunchArgs(PreLaunchHook): @@ -14,10 +13,11 @@ class AddPythonScriptToLaunchArgs(PreLaunchHook): ] 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.get( - "python_scripts", [] - ): + for python_script_path in self.launch_context.data["python_scripts"]: self.log.info( f"Adding python script {python_script_path} to launch" ) From edec4a2b1995adbeda6f5b681b9488f7fb3bcb08 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 26 Apr 2023 20:17:36 +0800 Subject: [PATCH 022/105] roy's comment --- .../publish/validate_renderer_redshift_proxy.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py b/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py index 6f8a92a93c..bc82f82f3b 100644 --- a/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py +++ b/openpype/hosts/max/plugins/publish/validate_renderer_redshift_proxy.py @@ -19,7 +19,7 @@ class ValidateRendererRedshiftProxy(pyblish.api.InstancePlugin): actions = [RepairAction] def process(self, instance): - invalid = self.get_all_renderer(instance) + invalid = self.get_redshift_renderer(instance) if invalid: raise PublishValidationError("Please install Redshift for 3dsMax" " before using the Redshift proxy instance") # noqa @@ -47,8 +47,8 @@ class ValidateRendererRedshiftProxy(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - renderer_count = len(rt.RendererClass.classes) - for r in range(renderer_count): - if "Redshift_Renderer" in str(rt.RendererClass.classes[r]()): - rt.renderers.production = rt.RendererClass.classes[r]() + for Renderer in rt.RendererClass.classes: + renderer = Renderer() + if "Redshift_Renderer" in str(renderer): + rt.renderers.production = renderer break From 86fcab6f8d423fd6d719f5ba50f63e72e278e056 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 May 2023 15:20:25 +0800 Subject: [PATCH 023/105] refractor the creator for custom modifiers --- .../hosts/max/plugins/create/create_redshift_proxy.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_redshift_proxy.py b/openpype/hosts/max/plugins/create/create_redshift_proxy.py index 1bddbdafae..8c71feb40f 100644 --- a/openpype/hosts/max/plugins/create/create_redshift_proxy.py +++ b/openpype/hosts/max/plugins/create/create_redshift_proxy.py @@ -12,13 +12,8 @@ class CreateRedshiftProxy(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(CreateRedshiftProxy, self).create( + + _ = super(CreateRedshiftProxy, self).create( subset_name, instance_data, pre_create_data) # type: CreatedInstance - container = rt.getNodeByName(instance.data.get("instance_node")) - if self.selected_nodes: - sel_obj = list(self.selected_nodes) - for obj in sel_obj: - obj.parent = container From e45098c2841c2390c42d7b46f2d1688a5220fa0a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 8 May 2023 15:21:27 +0800 Subject: [PATCH 024/105] hound fix --- openpype/hosts/max/plugins/create/create_redshift_proxy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/create/create_redshift_proxy.py b/openpype/hosts/max/plugins/create/create_redshift_proxy.py index 8c71feb40f..698ea82b69 100644 --- a/openpype/hosts/max/plugins/create/create_redshift_proxy.py +++ b/openpype/hosts/max/plugins/create/create_redshift_proxy.py @@ -11,7 +11,6 @@ class CreateRedshiftProxy(plugin.MaxCreator): icon = "gear" def create(self, subset_name, instance_data, pre_create_data): - from pymxs import runtime as rt _ = super(CreateRedshiftProxy, self).create( subset_name, From 6252e4b6c5c57e2a0f9f60fd0ad53cdcb7d26c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 10 May 2023 10:07:12 +0200 Subject: [PATCH 025/105] skipping if python script doesn't exist --- openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 0f959b8f54..8015a15de8 100644 --- a/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py +++ b/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py @@ -24,8 +24,8 @@ class AddPythonScriptToLaunchArgs(PreLaunchHook): # Test script path exists python_script_path = Path(python_script_path) if not python_script_path.exists(): - raise ValueError( - f"Python script {python_script_path} doesn't exist." + raise self.log.warning( + f"Python script {python_script_path} doesn't exist. Skipped..." ) if "--" in self.launch_context.launch_args: From 52ad442a9cacdd7675bb49c9fbcd144a2400df51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Wed, 10 May 2023 10:10:12 +0200 Subject: [PATCH 026/105] lint --- openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 8015a15de8..2d1b773c5f 100644 --- a/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py +++ b/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py @@ -25,7 +25,8 @@ class AddPythonScriptToLaunchArgs(PreLaunchHook): python_script_path = Path(python_script_path) if not python_script_path.exists(): raise self.log.warning( - f"Python script {python_script_path} doesn't exist. Skipped..." + f"Python script {python_script_path} doesn't exist. " + "Skipped..." ) if "--" in self.launch_context.launch_args: From 9a6ae240e2fbafe693701cb791656e44e3440d74 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 May 2023 17:00:29 +0800 Subject: [PATCH 027/105] using currentfile for redshift renderer --- .../hosts/max/plugins/publish/collect_render.py | 6 ++++-- .../plugins/publish/submit_max_deadline.py | 16 +++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index b040467522..0d4dbc4521 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -5,7 +5,7 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline import get_current_asset_name -from openpype.hosts.max.api.lib import get_max_version +from openpype.hosts.max.api.lib import get_max_version, get_current_renderer from openpype.hosts.max.api.lib_renderproducts import RenderProducts from openpype.client import get_last_version_by_subset_name @@ -38,7 +38,8 @@ class CollectRender(pyblish.api.InstancePlugin): version_doc = get_last_version_by_subset_name(project_name, instance.name, asset_id) - + renderer_class = get_current_renderer() + renderer = str(renderer_class).split(":")[0] self.log.debug("version_doc: {0}".format(version_doc)) version_int = 1 if version_doc: @@ -59,6 +60,7 @@ class CollectRender(pyblish.api.InstancePlugin): "source": filepath, "expectedFiles": render_layer_files, "plugin": "3dsmax", + "renderer": renderer, "frameStart": context.data['frameStart'], "frameEnd": context.data['frameEnd'], "version": version_int, diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index c728b6b9c7..0cf4990428 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -14,7 +14,6 @@ from openpype.pipeline import ( ) from openpype.settings import get_project_settings from openpype.hosts.max.api.lib import ( - get_current_renderer, get_multipass_setting ) from openpype.hosts.max.api.lib_rendersettings import RenderSettings @@ -157,6 +156,12 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, return plugin_payload + def from_published_scene(self, replace_in_path=True): + instance = self._instance + if instance.data["renderer"]== "Redshift_renderer": + file_path = self.scene_path + return file_path + def process_submission(self): instance = self._instance @@ -185,6 +190,8 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, instance = self._instance job_info = copy.deepcopy(self.job_info) plugin_info = copy.deepcopy(self.plugin_info) + if instance.data["renderer"] == "Redshift_Renderer": + self.log.debug("Using Redshift...published scene wont be used..") plugin_data = {} project_setting = get_project_settings( legacy_io.Session["AVALON_PROJECT"] @@ -202,7 +209,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, old_output_dir = os.path.dirname(expected_files[0]) output_beauty = RenderSettings().get_render_output(instance.name, old_output_dir) - filepath = self.from_published_scene() + filepath = self.scene_path def _clean_name(path): return os.path.splitext(os.path.basename(path))[0] @@ -214,9 +221,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, output_beauty = output_beauty.replace("\\", "/") plugin_data["RenderOutput"] = output_beauty - renderer_class = get_current_renderer() - renderer = str(renderer_class).split(":")[0] - if renderer in [ + if instance.data["renderer"] in [ "ART_Renderer", "Redshift_Renderer", "V_Ray_6_Hotfix_3", @@ -227,6 +232,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, render_elem_list = RenderSettings().get_render_element() for i, element in enumerate(render_elem_list): element = element.replace(orig_scene, new_scene) + element = element.replace("\\", "/") plugin_data["RenderElementOutputFilename%d" % i] = element # noqa self.log.debug("plugin data:{}".format(plugin_data)) From be386a36880a7868a4e46ba42fd49bfa08df6905 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 11 May 2023 17:05:56 +0800 Subject: [PATCH 028/105] hound fix --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 0cf4990428..a66d4d630a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -158,7 +158,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, def from_published_scene(self, replace_in_path=True): instance = self._instance - if instance.data["renderer"]== "Redshift_renderer": + if instance.data["renderer"] == "Redshift_renderer": file_path = self.scene_path return file_path From 0ea7b25c4675f84181cd4635ae5d74e2b18b39fd Mon Sep 17 00:00:00 2001 From: JackP Date: Wed, 17 May 2023 14:50:12 +0100 Subject: [PATCH 029/105] 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 030/105] 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 031/105] 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 032/105] 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 033/105] 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 034/105] 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 035/105] 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 036/105] 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 037/105] 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 038/105] 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 039/105] 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 040/105] 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 041/105] 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 2e7b0b9d954c606220f8ad89fdf86549dc38e3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Fri, 19 May 2023 18:20:03 +0200 Subject: [PATCH 042/105] fix raise --- openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 2d1b773c5f..525905f1ab 100644 --- a/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py +++ b/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py @@ -24,7 +24,7 @@ class AddPythonScriptToLaunchArgs(PreLaunchHook): # Test script path exists python_script_path = Path(python_script_path) if not python_script_path.exists(): - raise self.log.warning( + self.log.warning( f"Python script {python_script_path} doesn't exist. " "Skipped..." ) From dc7373408f8d586f854b99da7c9eb799441a3fec Mon Sep 17 00:00:00 2001 From: Felix David Date: Mon, 22 May 2023 10:39:30 +0200 Subject: [PATCH 043/105] continue if not python script path --- openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py | 1 + 1 file changed, 1 insertion(+) 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 index 525905f1ab..559e9ae0ce 100644 --- a/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py +++ b/openpype/hosts/blender/hooks/pre_add_run_python_script_arg.py @@ -28,6 +28,7 @@ class AddPythonScriptToLaunchArgs(PreLaunchHook): f"Python script {python_script_path} doesn't exist. " "Skipped..." ) + continue if "--" in self.launch_context.launch_args: # Insert before separator From 682d8e6b0551935645724e0b632fb5736448979f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 May 2023 12:28:45 +0800 Subject: [PATCH 044/105] custom script for setting frame range for read node --- openpype/hosts/nuke/api/lib.py | 30 ++++++ openpype/hosts/nuke/api/pipeline.py | 5 + openpype/widgets/custom_popup.py | 141 ++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 openpype/widgets/custom_popup.py diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index a439142051..1aa0a95c86 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2358,6 +2358,36 @@ class WorkfileSettings(object): # add colorspace menu item self.set_colorspace() + def reset_frame_range_read_nodes(self): + from openpype.widgets import custom_popup + parent = get_main_window() + dialog = custom_popup.CustomScriptDialog(parent=parent) + dialog.setWindowTitle("Frame Range for Read Node") + dialog.set_name("Frame Range: ") + dialog.set_line_edit("%s - %s" % (nuke.root().firstFrame(), + nuke.root().lastFrame())) + frame = dialog.widgets["line_edit"] + selection = dialog.widgets["selection"] + dialog.on_clicked.connect( + lambda: set_frame_range(frame, selection) + ) + def set_frame_range(frame, selection): + frame_range = frame.text() + selected = selection.isChecked() + for read_node in nuke.allNodes("Read"): + if selected: + if not nuke.selectedNodes(): + return + if read_node in nuke.selectedNodes(): + read_node["frame_mode"].setValue("start_at") + read_node["frame"].setValue(frame_range) + else: + read_node["frame_mode"].setValue("start_at") + read_node["frame"].setValue(frame_range) + dialog.show() + + return False + def set_favorites(self): from .utils import set_context_favorites diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index d649ffae7f..8d6be76e48 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -282,6 +282,11 @@ def _install_menu(): lambda: WorkfileSettings().set_context_settings() ) + menu.addSeparator() + menu.addCommand( + "Set Frame Range(Read Node)", + lambda: WorkfileSettings().reset_frame_range_read_nodes() + ) menu.addSeparator() menu.addCommand( "Build Workfile", diff --git a/openpype/widgets/custom_popup.py b/openpype/widgets/custom_popup.py new file mode 100644 index 0000000000..c4bb1ad43b --- /dev/null +++ b/openpype/widgets/custom_popup.py @@ -0,0 +1,141 @@ +import sys +import contextlib + +from PySide2 import QtCore, QtWidgets + + +class CustomScriptDialog(QtWidgets.QDialog): + """A Popup that moves itself to bottom right of screen on show event. + + The UI contains a message label and a red highlighted button to "show" + or perform another custom action from this pop-up. + + """ + + on_clicked = QtCore.Signal() + on_line_changed = QtCore.Signal(str) + + def __init__(self, parent=None, *args, **kwargs): + super(CustomScriptDialog, self).__init__(parent=parent, *args, **kwargs) + self.setContentsMargins(0, 0, 0, 0) + + # Layout + layout = QtWidgets.QVBoxLayout(self) + line_layout = QtWidgets.QHBoxLayout() + line_layout.setContentsMargins(10, 5, 10, 10) + selection_layout = QtWidgets.QHBoxLayout() + selection_layout.setContentsMargins(10, 5, 10, 10) + button_layout = QtWidgets.QHBoxLayout() + button_layout.setContentsMargins(10, 5, 10, 10) + + # Increase spacing slightly for readability + line_layout.setSpacing(10) + button_layout.setSpacing(8) + name = QtWidgets.QLabel("") + name.setStyleSheet(""" + QLabel { + font-size: 12px; + } + """) + line_edit = QtWidgets.QLineEdit("") + selection_name = QtWidgets.QLabel("Use Selection") + selection_name.setStyleSheet(""" + QLabel { + font-size: 12px; + } + """) + has_selection = QtWidgets.QCheckBox() + button = QtWidgets.QPushButton("Execute") + button.setSizePolicy(QtWidgets.QSizePolicy.Maximum, + QtWidgets.QSizePolicy.Maximum) + cancel = QtWidgets.QPushButton("Cancel") + cancel.setSizePolicy(QtWidgets.QSizePolicy.Maximum, + QtWidgets.QSizePolicy.Maximum) + + line_layout.addWidget(name) + line_layout.addWidget(line_edit) + selection_layout.addWidget(selection_name) + selection_layout.addWidget(has_selection) + button_layout.addWidget(button) + button_layout.addWidget(cancel) + layout.addLayout(line_layout) + layout.addLayout(selection_layout) + layout.addLayout(button_layout) + # Default size + self.resize(100, 30) + + self.widgets = { + "name": name, + "line_edit": line_edit, + "selection": has_selection, + "button": button, + "cancel": cancel + } + + # Signals + has_selection.toggled.connect(self.emit_click_with_state) + line_edit.textChanged.connect(self.on_line_edit_changed) + button.clicked.connect(self._on_clicked) + cancel.clicked.connect(self.close) + self.update_values() + # Set default title + self.setWindowTitle("Custom Popup") + + def update_values(self): + self.widgets["selection"].isChecked() + + def emit_click_with_state(self): + """Emit the on_clicked signal with the toggled state""" + checked = self.widgets["selection"].isChecked() + return checked + + def set_name(self, name): + self.widgets['name'].setText(name) + + def set_line_edit(self, line_edit): + self.widgets['line_edit'].setText(line_edit) + print(line_edit) + + def setButtonText(self, text): + self.widgets["button"].setText(text) + + def setCancelText(self, text): + self.widgets["cancel"].setText(text) + + def on_line_edit_changed(self): + line_edit = self.widgets['line_edit'].text() + self.on_line_changed.emit(line_edit) + return self.set_line_edit(line_edit) + + + def _on_clicked(self): + """Callback for when the 'show' button is clicked. + + Raises the parent (if any) + + """ + + parent = self.parent() + self.close() + + # Trigger the signal + self.on_clicked.emit() + + if parent: + parent.raise_() + + def showEvent(self, event): + + # Position popup based on contents on show event + return super(CustomScriptDialog, self).showEvent(event) + +@contextlib.contextmanager +def application(): + app = QtWidgets.QApplication(sys.argv) + yield + app.exec_() + +if __name__ == "__main__": + with application(): + dialog = CustomScriptDialog() + dialog.show() From 3ece2a8fcf73fa7d0da438ef056447fd7967c6ee Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 May 2023 12:56:11 +0800 Subject: [PATCH 045/105] clean up & cosmetic fix --- openpype/hosts/nuke/api/lib.py | 5 ++++- openpype/widgets/custom_popup.py | 12 +++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 1aa0a95c86..94a0ff15ad 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2362,7 +2362,7 @@ class WorkfileSettings(object): from openpype.widgets import custom_popup parent = get_main_window() dialog = custom_popup.CustomScriptDialog(parent=parent) - dialog.setWindowTitle("Frame Range for Read Node") + dialog.setWindowTitle("Frame Range") dialog.set_name("Frame Range: ") dialog.set_line_edit("%s - %s" % (nuke.root().firstFrame(), nuke.root().lastFrame())) @@ -2371,9 +2371,12 @@ class WorkfileSettings(object): dialog.on_clicked.connect( lambda: set_frame_range(frame, selection) ) + def set_frame_range(frame, selection): frame_range = frame.text() selected = selection.isChecked() + if not nuke.allNodes("Read"): + return for read_node in nuke.allNodes("Read"): if selected: if not nuke.selectedNodes(): diff --git a/openpype/widgets/custom_popup.py b/openpype/widgets/custom_popup.py index c4bb1ad43b..85e31d6ce0 100644 --- a/openpype/widgets/custom_popup.py +++ b/openpype/widgets/custom_popup.py @@ -16,7 +16,9 @@ class CustomScriptDialog(QtWidgets.QDialog): on_line_changed = QtCore.Signal(str) def __init__(self, parent=None, *args, **kwargs): - super(CustomScriptDialog, self).__init__(parent=parent, *args, **kwargs) + super(CustomScriptDialog, self).__init__(parent=parent, + *args, + **kwargs) self.setContentsMargins(0, 0, 0, 0) # Layout @@ -30,7 +32,7 @@ class CustomScriptDialog(QtWidgets.QDialog): # Increase spacing slightly for readability line_layout.setSpacing(10) - button_layout.setSpacing(8) + button_layout.setSpacing(10) name = QtWidgets.QLabel("") name.setStyleSheet(""" QLabel { @@ -47,7 +49,7 @@ class CustomScriptDialog(QtWidgets.QDialog): has_selection = QtWidgets.QCheckBox() button = QtWidgets.QPushButton("Execute") button.setSizePolicy(QtWidgets.QSizePolicy.Maximum, - QtWidgets.QSizePolicy.Maximum) + QtWidgets.QSizePolicy.Maximum) cancel = QtWidgets.QPushButton("Cancel") cancel.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) @@ -62,7 +64,7 @@ class CustomScriptDialog(QtWidgets.QDialog): layout.addLayout(selection_layout) layout.addLayout(button_layout) # Default size - self.resize(100, 30) + self.resize(100, 40) self.widgets = { "name": name, @@ -107,7 +109,6 @@ class CustomScriptDialog(QtWidgets.QDialog): self.on_line_changed.emit(line_edit) return self.set_line_edit(line_edit) - def _on_clicked(self): """Callback for when the 'show' button is clicked. @@ -129,6 +130,7 @@ class CustomScriptDialog(QtWidgets.QDialog): # Position popup based on contents on show event return super(CustomScriptDialog, self).showEvent(event) + @contextlib.contextmanager def application(): app = QtWidgets.QApplication(sys.argv) From 54cccc6a6af7f4d3e04e47173b3e302dbc5d2aa0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 May 2023 12:57:05 +0800 Subject: [PATCH 046/105] hound --- openpype/widgets/custom_popup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/widgets/custom_popup.py b/openpype/widgets/custom_popup.py index 85e31d6ce0..be4b0c32d5 100644 --- a/openpype/widgets/custom_popup.py +++ b/openpype/widgets/custom_popup.py @@ -137,6 +137,7 @@ def application(): yield app.exec_() + if __name__ == "__main__": with application(): dialog = CustomScriptDialog() From 7e02416d30e4d33282a37b27331272701f276d17 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 May 2023 17:35:13 +0800 Subject: [PATCH 047/105] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 1073a0e19e..19c1048496 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -150,7 +150,7 @@ class RenderProducts(object): def get_arnold_product_name(self, folder, startFrame, endFrame, fmt): """Get all the Arnold AOVs""" - aovs + aov_dict = {} amw = rt.MaxtoAOps.AOVsManagerWindow() aov_mgr = rt.renderers.current.AOVManager @@ -187,7 +187,7 @@ class RenderProducts(object): render_element = render_element.replace("\\", "/") render_dict.update({renderpass: render_element}) - return render_dirname + return render_dict def image_format(self): return self._project_settings["max"]["RenderSettings"]["image_format"] # noqa From e633cc7decbcfb8642f7d66ad17fe4219805e834 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 May 2023 18:19:50 +0800 Subject: [PATCH 048/105] expected file can get the aov path --- openpype/hosts/max/api/lib_renderproducts.py | 4 +++- .../max/plugins/publish/collect_render.py | 19 ++----------------- .../plugins/publish/submit_publish_job.py | 2 +- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 19c1048496..ba1ffc3a5e 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -70,7 +70,7 @@ class RenderProducts(object): return rgba_render_list, render_elem_list - def get_aov(self): + def get_aovs(self): folder = rt.maxFilePath folder = folder.replace("\\", "/") setting = self._project_settings @@ -177,6 +177,8 @@ class RenderProducts(object): render_elem = rt.maxOps.GetCurRenderElementMgr() render_elem_num = render_elem.NumRenderElements() + if render_elem_num < 1: + return # get render elements from the renders for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 652c2e1d2c..c4a44a5b11 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -45,23 +45,8 @@ class CollectRender(pyblish.api.InstancePlugin): } folder = folder.replace("\\", "/") - if aov_list: - if renderer in [ - "ART_Renderer", - "V_Ray_6_Hotfix_3", - "V_Ray_GPU_6_Hotfix_3" - "Redshift_Renderer", - "Default_Scanline_Renderer", - "Quicksilver_Hardware_Renderer", - ]: - - render_element = RenderProducts().get_aov() - files_by_aov.update(render_element) - self.log.debug(files_by_aov) - - if renderer == "Arnold": - aovs = RenderProducts().get_aovs() - files_by_aov.update(aovs) + aovs = RenderProducts().get_aovs() + files_by_aov.update(aovs) if "expectedFiles" not in instance.data: instance.data["expectedFiles"] = list() diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 7133cff058..68eb0a437d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -348,7 +348,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info("Submitting Deadline job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10, verify=False) + response = requests.post(url, json=payload, timeout=10) if not response.ok: raise Exception(response.text) From 2d3ba2af0576d5a201fafa0a0957e18d0aa9d00a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 May 2023 19:36:14 +0800 Subject: [PATCH 049/105] add _beauty to subset name --- openpype/hosts/max/api/lib_renderproducts.py | 82 +++++++++++++------ .../max/plugins/publish/collect_render.py | 8 +- .../plugins/publish/submit_publish_job.py | 2 +- 3 files changed, 62 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index ba1ffc3a5e..a93a1d821d 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -70,16 +70,26 @@ class RenderProducts(object): return rgba_render_list, render_elem_list - def get_aovs(self): + def get_aovs(self, container): folder = rt.maxFilePath + file = rt.maxFileName folder = folder.replace("\\", "/") setting = self._project_settings + render_folder = get_default_render_folder(setting) + filename, ext = os.path.splitext(file) + + output_file = os.path.join(folder, + render_folder, + filename, + container) + setting = self._project_settings img_fmt = setting["max"]["RenderSettings"]["image_format"] # noqa startFrame = int(rt.rendStart) endFrame = int(rt.rendEnd) + 1 renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] + render_dict = {} if renderer in [ "ART_Renderer", "Redshift_Renderer", @@ -88,12 +98,22 @@ class RenderProducts(object): "Default_Scanline_Renderer", "Quicksilver_Hardware_Renderer", ]: - render_dict = self.get_render_elements_name( - folder, startFrame, endFrame, img_fmt) + render_name = self.get_render_elements_name() + if render_name: + for name in render_name: + render_dict.update({ + name: self.get_expected_render_elements( + output_file, name, startFrame, endFrame, img_fmt) + }) if renderer == "Arnold": - render_dict = self.get_arnold_product_name( - folder, startFrame, endFrame, img_fmt) + render_name = self.get_arnold_product_name() + if render_name: + for name in render_name: + render_dict.update({ + name: self.get_expected_arnold_product( + output_file, name, startFrame, endFrame, img_fmt) + }) return render_dict @@ -148,9 +168,9 @@ class RenderProducts(object): return render_dirname - def get_arnold_product_name(self, folder, startFrame, endFrame, fmt): - """Get all the Arnold AOVs""" - aov_dict = {} + def get_arnold_product_name(self): + """Get all the Arnold AOVs name""" + aov_name = [] amw = rt.MaxtoAOps.AOVsManagerWindow() aov_mgr = rt.renderers.current.AOVManager @@ -161,20 +181,27 @@ class RenderProducts(object): for i in range(aov_group_num): # get the specific AOV group for aov in aov_mgr.drivers[i].aov_list: - for f in range(startFrame, endFrame): - render_element = f"{folder}_{aov.name}.{f}.{fmt}" - render_element = render_element.replace("\\", "/") - aov = str(aov.name) - aov_dict.update({aov: render_element}) + aov_name.append(aov.name) + # close the AOVs manager window amw.close() - return aov_dict + return aov_name - def get_render_elements_name(self, folder, startFrame, endFrame, fmt): - """Get all the render element output files. """ - render_dict = {} + def get_expected_arnold_product(self, folder, name, + startFrame, endFrame, fmt): + """Get all the expected Arnold AOVs""" + aov_list = [] + for f in range(startFrame, endFrame): + render_element = f"{folder}_{name}.{f}.{fmt}" + render_element = render_element.replace("\\", "/") + aov_list.append(render_element) + return aov_list + + def get_render_elements_name(self): + """Get all the render element names. """ + render_name = [] render_elem = rt.maxOps.GetCurRenderElementMgr() render_elem_num = render_elem.NumRenderElements() if render_elem_num < 1: @@ -182,14 +209,21 @@ class RenderProducts(object): # get render elements from the renders for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) - target, renderpass = str(renderlayer_name).split(":") - if renderlayer_name.enabled: - for f in range(startFrame, endFrame): - render_element = f"{folder}_{renderpass}.{f}.{fmt}" - render_element = render_element.replace("\\", "/") - render_dict.update({renderpass: render_element}) + if renderlayer_name.enabled or "Cryptomatte" in renderlayer_name: + target, renderpass = str(renderlayer_name).split(":") + render_name.append(renderpass) + return render_name - return render_dict + def get_expected_render_elements(self, folder, name, + startFrame, endFrame, fmt): + """Get all the expected render element output files. """ + render_elements = [] + for f in range(startFrame, endFrame): + render_element = f"{folder}_{name}.{f}.{fmt}" + render_element = render_element.replace("\\", "/") + render_elements.append(render_element) + + return render_elements def image_format(self): return self._project_settings["max"]["RenderSettings"]["image_format"] # noqa diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index c4a44a5b11..1282c9b3fe 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -41,11 +41,11 @@ class CollectRender(pyblish.api.InstancePlugin): full_render_list = beauty_list files_by_aov = { - "_": beauty_list + "beauty": beauty_list } folder = folder.replace("\\", "/") - aovs = RenderProducts().get_aovs() + aovs = RenderProducts().get_aovs(instance.name) files_by_aov.update(aovs) if "expectedFiles" not in instance.data: @@ -78,8 +78,8 @@ class CollectRender(pyblish.api.InstancePlugin): instance.data["attachTo"] = [] data = { - "subset": instance.name, "asset": asset, + "subset": str(instance.name), "publish": True, "maxversion": str(get_max_version()), "imageFormat": img_format, @@ -95,5 +95,3 @@ class CollectRender(pyblish.api.InstancePlugin): } instance.data.update(data) self.log.info("data: {0}".format(data)) - files = instance.data["expectedFiles"] - self.log.debug("expectedFiles: {0}".format(files)) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 68eb0a437d..7133cff058 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -348,7 +348,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info("Submitting Deadline job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10) + response = requests.post(url, json=payload, timeout=10, verify=False) if not response.ok: raise Exception(response.text) From 4303b281aab9c157d109e034c68d5d8816fd4450 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 May 2023 19:38:05 +0800 Subject: [PATCH 050/105] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 4 ++-- openpype/hosts/max/plugins/publish/collect_render.py | 4 +--- .../modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index a93a1d821d..b33d0c5751 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -103,7 +103,7 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, startFrame, endFrame, img_fmt) + output_file, name, startFrame, endFrame, img_fmt) }) if renderer == "Arnold": @@ -112,7 +112,7 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_arnold_product( - output_file, name, startFrame, endFrame, img_fmt) + output_file, name, startFrame, endFrame, img_fmt) }) return render_dict diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 1282c9b3fe..a21ccf532e 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -6,7 +6,7 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline import get_current_asset_name from openpype.hosts.max.api import colorspace -from openpype.hosts.max.api.lib import get_max_version, get_current_renderer +from openpype.hosts.max.api.lib import get_max_version from openpype.hosts.max.api.lib_renderproducts import RenderProducts from openpype.client import get_last_version_by_subset_name @@ -29,8 +29,6 @@ class CollectRender(pyblish.api.InstancePlugin): context.data['currentFile'] = current_file asset = get_current_asset_name() - renderer_class = get_current_renderer() - renderer = str(renderer_class).split(":")[0] beauty_list, aov_list = RenderProducts().render_product(instance.name) full_render_list = list() if aov_list: diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 7133cff058..68eb0a437d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -348,7 +348,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info("Submitting Deadline job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10, verify=False) + response = requests.post(url, json=payload, timeout=10) if not response.ok: raise Exception(response.text) From c14525f371aa8b8b2a524022f860cede764f7d0d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 26 May 2023 23:13:17 +0800 Subject: [PATCH 051/105] fix the wrong directory for rendering --- .../max/plugins/publish/collect_render.py | 2 +- .../deadline/abstract_submit_deadline.py | 2 +- .../plugins/publish/submit_max_deadline.py | 40 +++++++++++-------- .../plugins/publish/submit_publish_job.py | 6 ++- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index a21ccf532e..c8e407bbe4 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -39,7 +39,7 @@ class CollectRender(pyblish.api.InstancePlugin): full_render_list = beauty_list files_by_aov = { - "beauty": beauty_list + "max_beauty": beauty_list } folder = folder.replace("\\", "/") diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 558a637e4b..6694f638d6 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -582,7 +582,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): metadata_folder = metadata_folder.replace(orig_scene, new_scene) instance.data["publishRenderMetadataFolder"] = metadata_folder - + self.log.debug(f"MetadataFolder:{metadata_folder}") self.log.info("Scene name was switched {} -> {}".format( orig_scene, new_scene )) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index c678c0fb6e..d2de9160fb 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -132,8 +132,8 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # Add list of expected files to job # --------------------------------- - files = instance.data.get("files") - for filepath in files: + exp = instance.data.get("expectedFiles") + for filepath in self._iter_expected_files(exp): job_info.OutputDirectory += os.path.dirname(filepath) job_info.OutputFilename += os.path.basename(filepath) @@ -162,10 +162,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, instance = self._instance filepath = self.scene_path - files = instance.data["files"] + files = instance.data["expectedFiles"] if not files: raise RuntimeError("No Render Elements found!") - output_dir = os.path.dirname(files[0]) + first_file = next(self._iter_expected_files(files)) + output_dir = os.path.dirname(first_file) instance.data["outputDir"] = output_dir instance.data["toBeRenderedOn"] = "deadline" @@ -202,17 +203,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, old_output_dir = os.path.dirname(files[0]) output_beauty = RenderSettings().get_render_output(instance.name, old_output_dir) - filepath = self.scene_path - - def _clean_name(path): - return os.path.splitext(os.path.basename(path))[0] - - new_scene = _clean_name(filepath) - orig_scene = _clean_name(instance.context.data["currentFile"]) - - output_beauty = output_beauty.replace(orig_scene, new_scene) - output_beauty = output_beauty.replace("\\", "/") - plugin_data["RenderOutput"] = output_beauty + files = instance.data["expectedFiles"] + first_file = next(self._iter_expected_files(files)) + rgb_bname = os.path.basename(output_beauty) + dir = os.path.dirname(first_file) + plugin_data["RenderOutput"] = f"{dir}/{rgb_bname}" renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] @@ -226,14 +221,25 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, ]: render_elem_list = RenderSettings().get_render_element() for i, element in enumerate(render_elem_list): - element = element.replace(orig_scene, new_scene) - plugin_data["RenderElementOutputFilename%d" % i] = element # noqa + elem_bname = os.path.basename(element) + new_elem = f"{dir}/{elem_bname}" + plugin_data["RenderElementOutputFilename%d" % i] = new_elem # noqa self.log.debug("plugin data:{}".format(plugin_data)) plugin_info.update(plugin_data) return job_info, plugin_info + @staticmethod + def _iter_expected_files(exp): + if isinstance(exp[0], dict): + for _aov, files in exp[0].items(): + for file in files: + yield file + else: + for file in exp: + yield file + @classmethod def get_attribute_defs(cls): defs = super(MaxSubmitDeadline, cls).get_attribute_defs() diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 68eb0a437d..fb0608908f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -348,7 +348,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info("Submitting Deadline job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10) + response = requests.post(url, json=payload, timeout=10, verify=False) if not response.ok: raise Exception(response.text) @@ -488,11 +488,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if cam: if aov: subset_name = '{}_{}_{}'.format(group_name, cam, aov) + if aov == "max_beauty": + subset_name = '{}_{}'.format(group_name, cam) else: subset_name = '{}_{}'.format(group_name, cam) else: if aov: subset_name = '{}_{}'.format(group_name, aov) + if aov == "max_beauty": + subset_name = '{}'.format(group_name) else: subset_name = '{}'.format(group_name) From 32562e0b39500996bc25ad2173d401b83d60a48f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 27 May 2023 00:53:40 +0800 Subject: [PATCH 052/105] give beauty name to RGB --- openpype/hosts/max/plugins/publish/collect_render.py | 2 +- .../modules/deadline/plugins/publish/submit_publish_job.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index c8e407bbe4..a21ccf532e 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -39,7 +39,7 @@ class CollectRender(pyblish.api.InstancePlugin): full_render_list = beauty_list files_by_aov = { - "max_beauty": beauty_list + "beauty": beauty_list } folder = folder.replace("\\", "/") diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index fb0608908f..7133cff058 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -488,15 +488,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if cam: if aov: subset_name = '{}_{}_{}'.format(group_name, cam, aov) - if aov == "max_beauty": - subset_name = '{}_{}'.format(group_name, cam) else: subset_name = '{}_{}'.format(group_name, cam) else: if aov: subset_name = '{}_{}'.format(group_name, aov) - if aov == "max_beauty": - subset_name = '{}'.format(group_name) else: subset_name = '{}'.format(group_name) From 271d017bdb368b6cbfdb95087df6da957da43f4a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Sat, 27 May 2023 00:56:11 +0800 Subject: [PATCH 053/105] remove the print function, and set verify to true for payload in publishing job --- openpype/modules/deadline/abstract_submit_deadline.py | 1 - openpype/modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/modules/deadline/abstract_submit_deadline.py b/openpype/modules/deadline/abstract_submit_deadline.py index 6694f638d6..7938c27233 100644 --- a/openpype/modules/deadline/abstract_submit_deadline.py +++ b/openpype/modules/deadline/abstract_submit_deadline.py @@ -582,7 +582,6 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): metadata_folder = metadata_folder.replace(orig_scene, new_scene) instance.data["publishRenderMetadataFolder"] = metadata_folder - self.log.debug(f"MetadataFolder:{metadata_folder}") self.log.info("Scene name was switched {} -> {}".format( orig_scene, new_scene )) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 7133cff058..68eb0a437d 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -348,7 +348,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info("Submitting Deadline job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10, verify=False) + response = requests.post(url, json=payload, timeout=10) if not response.ok: raise Exception(response.text) From b7b8125d70406ef867af53f8a8afe2640e657058 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 26 May 2023 23:23:06 +0200 Subject: [PATCH 054/105] Use .scriptlib for Resolve startup launch script entry point --- .../hooks/pre_resolve_launch_last_workfile.py | 35 ++++++++++++++++ openpype/hosts/resolve/startup.py | 40 +++++++++++++++++++ .../openpype_startup.scriptlib | 22 ++++++++++ openpype/hosts/resolve/utils.py | 8 ++++ 4 files changed, 105 insertions(+) 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 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..6db3cc28b2 --- /dev/null +++ b/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py @@ -0,0 +1,35 @@ +import os + +from openpype.lib import PreLaunchHook + + +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 diff --git a/openpype/hosts/resolve/startup.py b/openpype/hosts/resolve/startup.py new file mode 100644 index 0000000000..4aeb106ef1 --- /dev/null +++ b/openpype/hosts/resolve/startup.py @@ -0,0 +1,40 @@ +import os + +# Importing this takes a little over a second and thus this means +# that we have about 1.5 seconds delay before the workfile will actually +# be opened at the minimum +import openpype.hosts.resolve.api + + +def launch_menu(): + from openpype.pipeline import install_host + print("Launching Resolve OpenPype menu..") + + # Activate resolve from openpype + install_host(openpype.hosts.resolve.api) + + openpype.hosts.resolve.api.launch_pype_menu() + + +def open_file(path): + # Avoid the need to "install" the host + openpype.hosts.resolve.api.bmdvr = resolve # noqa + openpype.hosts.resolve.api.bmdvf = fusion # noqa + openpype.hosts.resolve.api.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 + # TODO: Add a setting to enable/disable this + 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..9fca666d78 --- /dev/null +++ b/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib @@ -0,0 +1,22 @@ +-- 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_root = os.getenv("OPENPYPE_ROOT") +if openpype_root ~= nil then + script = openpype_root .. "/openpype/hosts/resolve/startup.py" + script = fusion:MapPath(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 \ No newline at end of file diff --git a/openpype/hosts/resolve/utils.py b/openpype/hosts/resolve/utils.py index 9a161f4865..e2c8c4a05e 100644 --- a/openpype/hosts/resolve/utils.py +++ b/openpype/hosts/resolve/utils.py @@ -50,6 +50,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( From 00e89719dd75f465411e120fbb2c37dd8e495b7a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 26 May 2023 23:23:33 +0200 Subject: [PATCH 055/105] Do not prompt save project when not in a project (e.g. on Resolve launch) --- openpype/hosts/resolve/api/workio.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) 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") From 56642ac17572ca5c7c12bd97e5e717ce801518f0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 12:37:25 +0800 Subject: [PATCH 056/105] getting the filename from render settings and add save_scene before all the extractors running --- openpype/hosts/max/api/lib_renderproducts.py | 159 ++++++------------ .../hosts/max/plugins/create/create_render.py | 4 + .../max/plugins/publish/collect_render.py | 26 +-- .../hosts/max/plugins/publish/save_scene.py | 26 +++ .../plugins/publish/submit_max_deadline.py | 81 ++++++++- 5 files changed, 171 insertions(+), 125 deletions(-) create mode 100644 openpype/hosts/max/plugins/publish/save_scene.py diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index b33d0c5751..30c3c71cce 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -5,10 +5,8 @@ import os from pymxs import runtime as rt from openpype.hosts.max.api.lib import ( - get_current_renderer, - get_default_render_folder + get_current_renderer ) -from openpype.pipeline.context_tools import get_current_project_asset from openpype.settings import get_project_settings from openpype.pipeline import legacy_io @@ -22,66 +20,30 @@ class RenderProducts(object): legacy_io.Session["AVALON_PROJECT"] ) - def render_product(self, container): - folder = rt.maxFilePath - file = rt.maxFileName - folder = folder.replace("\\", "/") - setting = self._project_settings - render_folder = get_default_render_folder(setting) - filename, ext = os.path.splitext(file) + def get_beauty(self, container): + render_dir = os.path.dirname(rt.rendOutputFilename) - output_file = os.path.join(folder, - render_folder, - filename, + output_file = os.path.join(render_dir, container) - # TODO: change the frame range follows the current render setting + + setting = self._project_settings + img_fmt = setting["max"]["RenderSettings"]["image_format"] # noqa + startFrame = int(rt.rendStart) endFrame = int(rt.rendEnd) + 1 - img_fmt = self._project_settings["max"]["RenderSettings"]["image_format"] # noqa - rgba_render_list = self.beauty_render_product(output_file, - startFrame, - endFrame, - img_fmt) - - renderer_class = get_current_renderer() - renderer = str(renderer_class).split(":")[0] - - render_elem_list = None - - if renderer in [ - "ART_Renderer", - "Redshift_Renderer", - "V_Ray_6_Hotfix_3", - "V_Ray_GPU_6_Hotfix_3", - "Default_Scanline_Renderer", - "Quicksilver_Hardware_Renderer", - ]: - render_elem_list = self.render_elements_product(output_file, - startFrame, - endFrame, - img_fmt) - - if renderer == "Arnold": - render_elem_list = self.arnold_render_product(output_file, - startFrame, - endFrame, - img_fmt) - - return rgba_render_list, render_elem_list + render_dict = { + "beauty": self.get_expected_beauty( + output_file, startFrame, endFrame, img_fmt) + } + return render_dict def get_aovs(self, container): - folder = rt.maxFilePath - file = rt.maxFileName - folder = folder.replace("\\", "/") - setting = self._project_settings - render_folder = get_default_render_folder(setting) - filename, ext = os.path.splitext(file) + render_dir = os.path.dirname(rt.rendOutputFilename) - output_file = os.path.join(folder, - render_folder, - filename, + output_file = os.path.join(render_dir, container) + setting = self._project_settings img_fmt = setting["max"]["RenderSettings"]["image_format"] # noqa @@ -90,9 +52,9 @@ class RenderProducts(object): renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] render_dict = {} + if renderer in [ "ART_Renderer", - "Redshift_Renderer", "V_Ray_6_Hotfix_3", "V_Ray_GPU_6_Hotfix_3", "Default_Scanline_Renderer", @@ -105,6 +67,23 @@ class RenderProducts(object): name: self.get_expected_render_elements( output_file, name, startFrame, endFrame, img_fmt) }) + if renderer == "Redshift_Renderer": + render_name = self.get_render_elements_name() + if render_name: + rs_AovFiles = rt.Redshift_Renderer().SeparateAovFiles + if rs_AovFiles != True and img_fmt == "exr": + for name in render_name: + if name == "RsCryptomatte": + render_dict.update({ + name: self.get_expected_render_elements( + output_file, name, startFrame, endFrame, img_fmt) + }) + else: + for name in render_name: + render_dict.update({ + name: self.get_expected_render_elements( + output_file, name, startFrame, endFrame, img_fmt) + }) if renderer == "Arnold": render_name = self.get_arnold_product_name() @@ -114,60 +93,31 @@ class RenderProducts(object): name: self.get_expected_arnold_product( output_file, name, startFrame, endFrame, img_fmt) }) + if renderer in [ + "V_Ray_6_Hotfix_3", + "V_Ray_GPU_6_Hotfix_3" + ]: + if img_fmt !="exr": + render_name = self.get_render_elements_name() + if render_name: + for name in render_name: + render_dict.update({ + name: self.get_expected_render_elements( + output_file, name, startFrame, endFrame, img_fmt) + }) return render_dict - def beauty_render_product(self, folder, startFrame, endFrame, fmt): + def get_expected_beauty(self, folder, startFrame, endFrame, fmt): beauty_frame_range = [] for f in range(startFrame, endFrame): - beauty_output = f"{folder}.{f}.{fmt}" + frame = "%04d" % f + beauty_output = f"{folder}.{frame}.{fmt}" beauty_output = beauty_output.replace("\\", "/") beauty_frame_range.append(beauty_output) return beauty_frame_range - # TODO: Get the arnold render product - def arnold_render_product(self, folder, startFrame, endFrame, fmt): - """Get all the Arnold AOVs""" - aovs = [] - - 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) - if aov_group_num < 1: - return - for i in range(aov_group_num): - # get the specific AOV group - for aov in aov_mgr.drivers[i].aov_list: - for f in range(startFrame, endFrame): - render_element = f"{folder}_{aov.name}.{f}.{fmt}" - render_element = render_element.replace("\\", "/") - aovs.append(render_element) - - # close the AOVs manager window - amw.close() - - return aovs - - def render_elements_product(self, folder, startFrame, endFrame, fmt): - """Get all the render element output files. """ - render_dirname = [] - - render_elem = rt.maxOps.GetCurRenderElementMgr() - render_elem_num = render_elem.NumRenderElements() - # get render elements from the renders - for i in range(render_elem_num): - renderlayer_name = render_elem.GetRenderElement(i) - target, renderpass = str(renderlayer_name).split(":") - if renderlayer_name.enabled: - for f in range(startFrame, endFrame): - render_element = f"{folder}_{renderpass}.{f}.{fmt}" - render_element = render_element.replace("\\", "/") - render_dirname.append(render_element) - - return render_dirname - def get_arnold_product_name(self): """Get all the Arnold AOVs name""" aov_name = [] @@ -193,14 +143,15 @@ class RenderProducts(object): """Get all the expected Arnold AOVs""" aov_list = [] for f in range(startFrame, endFrame): - render_element = f"{folder}_{name}.{f}.{fmt}" + frame = "%04d" % f + render_element = f"{folder}_{name}.{frame}.{fmt}" render_element = render_element.replace("\\", "/") aov_list.append(render_element) return aov_list def get_render_elements_name(self): - """Get all the render element names. """ + """Get all the render element names for general """ render_name = [] render_elem = rt.maxOps.GetCurRenderElementMgr() render_elem_num = render_elem.NumRenderElements() @@ -209,9 +160,10 @@ class RenderProducts(object): # get render elements from the renders for i in range(render_elem_num): renderlayer_name = render_elem.GetRenderElement(i) - if renderlayer_name.enabled or "Cryptomatte" in renderlayer_name: + if renderlayer_name.enabled: target, renderpass = str(renderlayer_name).split(":") render_name.append(renderpass) + return render_name def get_expected_render_elements(self, folder, name, @@ -219,7 +171,8 @@ class RenderProducts(object): """Get all the expected render element output files. """ render_elements = [] for f in range(startFrame, endFrame): - render_element = f"{folder}_{name}.{f}.{fmt}" + frame = "%04d" % f + render_element = f"{folder}_{name}.{frame}.{fmt}" render_element = render_element.replace("\\", "/") render_elements.append(render_element) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 68ae5eac72..78e9527bdf 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Creator plugin for creating camera.""" +import os from openpype.hosts.max.api import plugin from openpype.pipeline import CreatedInstance from openpype.hosts.max.api.lib_rendersettings import RenderSettings @@ -14,6 +15,9 @@ 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) + file = rt.maxFileName + filename, _ = os.path.splitext(file) + instance_data["AssetName"] = filename instance = super(CreateRender, self).create( subset_name, instance_data, diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index a21ccf532e..5b3f99b2d0 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -29,19 +29,7 @@ class CollectRender(pyblish.api.InstancePlugin): context.data['currentFile'] = current_file asset = get_current_asset_name() - beauty_list, aov_list = RenderProducts().render_product(instance.name) - full_render_list = list() - if aov_list: - full_render_list.extend(iter(beauty_list)) - full_render_list.extend(iter(aov_list)) - - else: - full_render_list = beauty_list - - files_by_aov = { - "beauty": beauty_list - } - + files_by_aov = RenderProducts().get_beauty(instance.name) folder = folder.replace("\\", "/") aovs = RenderProducts().get_aovs(instance.name) files_by_aov.update(aovs) @@ -67,14 +55,14 @@ class CollectRender(pyblish.api.InstancePlugin): # OCIO config not support in # most of the 3dsmax renderers # so this is currently hard coded - setting = instance.context.data["project_settings"] - image_io = setting["global"]["imageio"] - instance.data["colorspaceConfig"] = image_io["ocio_config"]["filepath"][0] # noqa + # TODO: add options for redshift/vray ocio config + instance.data["colorspaceConfig"] = "" instance.data["colorspaceDisplay"] = "sRGB" - instance.data["colorspaceView"] = "ACES 1.0" + instance.data["colorspaceView"] = "ACES 1.0 SDR-video" instance.data["renderProducts"] = colorspace.ARenderProduct() + instance.data["publishJobState"] = "Suspended" instance.data["attachTo"] = [] - + # also need to get the render dir for coversion data = { "asset": asset, "subset": str(instance.name), @@ -84,7 +72,6 @@ class CollectRender(pyblish.api.InstancePlugin): "family": 'maxrender', "families": ['maxrender'], "source": filepath, - "files": full_render_list, "plugin": "3dsmax", "frameStart": int(rt.rendStart), "frameEnd": int(rt.rendEnd), @@ -93,3 +80,4 @@ class CollectRender(pyblish.api.InstancePlugin): } instance.data.update(data) self.log.info("data: {0}".format(data)) + self.log.debug("expectedFiles:{0}".format(instance.data["expectedFiles"])) diff --git a/openpype/hosts/max/plugins/publish/save_scene.py b/openpype/hosts/max/plugins/publish/save_scene.py new file mode 100644 index 0000000000..93d97a3de5 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/save_scene.py @@ -0,0 +1,26 @@ +import pyblish.api +import os + + +class SaveCurrentScene(pyblish.api.ContextPlugin): + """Save current scene + + """ + + label = "Save current file" + order = pyblish.api.ExtractorOrder - 0.49 + hosts = ["max"] + families = ["maxrender", "workfile"] + + def process(self, context): + from pymxs import runtime as rt + folder = rt.maxFilePath + file = rt.maxFileName + current = os.path.join(folder, file) + assert context.data["currentFile"] == current + + if rt.checkForSave(): + self.log.debug("Skipping file save as there " + "are no modifications..") + return + rt.saveMaxFile(current) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index d2de9160fb..15aa521f43 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -78,7 +78,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, job_info.BatchName = src_filename job_info.Plugin = instance.data["plugin"] job_info.UserName = context.data.get("deadlineUser", getpass.getuser()) - + job_info.EnableAutoTimeout = True # Deadline requires integers in frame range frames = "{start}-{end}".format( start=int(instance.data["frameStart"]), @@ -207,9 +207,13 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, first_file = next(self._iter_expected_files(files)) rgb_bname = os.path.basename(output_beauty) dir = os.path.dirname(first_file) - plugin_data["RenderOutput"] = f"{dir}/{rgb_bname}" - + beauty_name = f"{dir}/{rgb_bname}" + beauty_name = beauty_name.replace("\\", "/") + plugin_data["RenderOutput"] = beauty_name + # as 3dsmax has version with different languages + plugin_data["Language"] = "ENU" renderer_class = get_current_renderer() + renderer = str(renderer_class).split(":")[0] if renderer in [ "ART_Renderer", @@ -223,13 +227,84 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, for i, element in enumerate(render_elem_list): elem_bname = os.path.basename(element) new_elem = f"{dir}/{elem_bname}" + new_elem = new_elem.replace("/", "\\") plugin_data["RenderElementOutputFilename%d" % i] = new_elem # noqa + self.log.debug("plugin data:{}".format(plugin_data)) plugin_info.update(plugin_data) return job_info, plugin_info + def from_published_scene(self, replace_in_path=True): + instance = self._instance + workfile_instance = self._get_workfile_instance(instance.context) + if workfile_instance is None: + return + + # determine published path from Anatomy. + template_data = workfile_instance.data.get("anatomyData") + rep = workfile_instance.data["representations"][0] + template_data["representation"] = rep.get("name") + template_data["ext"] = rep.get("ext") + template_data["comment"] = None + + anatomy = instance.context.data['anatomy'] + template_obj = anatomy.templates_obj["publish"]["path"] + template_filled = template_obj.format_strict(template_data) + file_path = os.path.normpath(template_filled) + + self.log.info("Using published scene for render {}".format(file_path)) + + if not os.path.exists(file_path): + self.log.error("published scene does not exist!") + raise + + if not replace_in_path: + return file_path + + # now we need to switch scene in expected files + # because token will now point to published + # scene file and that might differ from current one + def _clean_name(path): + return os.path.splitext(os.path.basename(path))[0] + + new_scene = _clean_name(file_path) + orig_scene = _clean_name(instance.data["AssetName"]) + expected_files = instance.data.get("expectedFiles") + + if isinstance(expected_files[0], dict): + # we have aovs and we need to iterate over them + new_exp = {} + for aov, files in expected_files[0].items(): + replaced_files = [] + for f in files: + replaced_files.append( + str(f).replace(orig_scene, new_scene) + ) + new_exp[aov] = replaced_files + # [] might be too much here, TODO + instance.data["expectedFiles"] = [new_exp] + else: + new_exp = [] + for f in expected_files: + new_exp.append( + str(f).replace(orig_scene, new_scene) + ) + instance.data["expectedFiles"] = new_exp + + metadata_folder = instance.data.get("publishRenderMetadataFolder") + if metadata_folder: + metadata_folder = metadata_folder.replace(orig_scene, + new_scene) + instance.data["publishRenderMetadataFolder"] = metadata_folder + + self.log.info("Scene name was switched {} -> {}".format( + orig_scene, new_scene + )) + + return file_path + @staticmethod def _iter_expected_files(exp): if isinstance(exp[0], dict): From f85051863ccfbd2f54e0f9198c1b3123f08391b3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 12:44:22 +0800 Subject: [PATCH 057/105] hound fix --- openpype/hosts/max/api/lib_renderproducts.py | 15 +++++++++------ .../hosts/max/plugins/publish/collect_render.py | 1 - .../plugins/publish/submit_max_deadline.py | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 30c3c71cce..68090dfefd 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -65,7 +65,8 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, startFrame, endFrame, img_fmt) + output_file, name, startFrame, + endFrame, img_fmt) }) if renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() @@ -76,13 +77,15 @@ class RenderProducts(object): if name == "RsCryptomatte": render_dict.update({ name: self.get_expected_render_elements( - output_file, name, startFrame, endFrame, img_fmt) + output_file, name, startFrame, + endFrame, img_fmt) }) else: for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, startFrame, endFrame, img_fmt) + output_file, name, startFrame, + endFrame, img_fmt) }) if renderer == "Arnold": @@ -95,15 +98,15 @@ class RenderProducts(object): }) if renderer in [ "V_Ray_6_Hotfix_3", - "V_Ray_GPU_6_Hotfix_3" - ]: + "V_Ray_GPU_6_Hotfix_3"]: if img_fmt !="exr": render_name = self.get_render_elements_name() if render_name: for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, startFrame, endFrame, img_fmt) + output_file, name, startFrame, + endFrame, img_fmt) # noqa }) return render_dict diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 5b3f99b2d0..9137f8c854 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -80,4 +80,3 @@ class CollectRender(pyblish.api.InstancePlugin): } instance.data.update(data) self.log.info("data: {0}".format(data)) - self.log.debug("expectedFiles:{0}".format(instance.data["expectedFiles"])) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 15aa521f43..4682cc4487 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -207,7 +207,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, first_file = next(self._iter_expected_files(files)) rgb_bname = os.path.basename(output_beauty) dir = os.path.dirname(first_file) - beauty_name = f"{dir}/{rgb_bname}" + beauty_name = f"{dir}/{rgb_bname}" beauty_name = beauty_name.replace("\\", "/") plugin_data["RenderOutput"] = beauty_name # as 3dsmax has version with different languages From b89bb229a0e7bf4c8d8308776d3d288a65bdd387 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 12:48:09 +0800 Subject: [PATCH 058/105] hound --- openpype/hosts/max/api/lib_renderproducts.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 68090dfefd..21c41446a9 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -72,7 +72,7 @@ class RenderProducts(object): render_name = self.get_render_elements_name() if render_name: rs_AovFiles = rt.Redshift_Renderer().SeparateAovFiles - if rs_AovFiles != True and img_fmt == "exr": + if rs_AovFiles == False and img_fmt == "exr": for name in render_name: if name == "RsCryptomatte": render_dict.update({ @@ -98,7 +98,8 @@ class RenderProducts(object): }) if renderer in [ "V_Ray_6_Hotfix_3", - "V_Ray_GPU_6_Hotfix_3"]: + "V_Ray_GPU_6_Hotfix_3" + ]: if img_fmt !="exr": render_name = self.get_render_elements_name() if render_name: From f030ca17d8a3a7d979f01f3b56b11732d6dcfb85 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 12:50:36 +0800 Subject: [PATCH 059/105] hound --- openpype/hosts/max/api/lib_renderproducts.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 21c41446a9..6bd7e5b7b0 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -72,14 +72,14 @@ class RenderProducts(object): render_name = self.get_render_elements_name() if render_name: rs_AovFiles = rt.Redshift_Renderer().SeparateAovFiles - if rs_AovFiles == False and img_fmt == "exr": + if img_fmt == "exr" and not rs_AovFiles: for name in render_name: if name == "RsCryptomatte": render_dict.update({ - name: self.get_expected_render_elements( - output_file, name, startFrame, - endFrame, img_fmt) - }) + name: self.get_expected_render_elements( + output_file, name, startFrame, + endFrame, img_fmt) + }) else: for name in render_name: render_dict.update({ @@ -99,7 +99,7 @@ class RenderProducts(object): if renderer in [ "V_Ray_6_Hotfix_3", "V_Ray_GPU_6_Hotfix_3" - ]: + ]: if img_fmt !="exr": render_name = self.get_render_elements_name() if render_name: From 44c0f1cf8a52a5eb1b45d43310f2451aa966be01 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 12:51:26 +0800 Subject: [PATCH 060/105] hound --- openpype/hosts/max/api/lib_renderproducts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 6bd7e5b7b0..2fbb7e8ff3 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -100,7 +100,7 @@ class RenderProducts(object): "V_Ray_6_Hotfix_3", "V_Ray_GPU_6_Hotfix_3" ]: - if img_fmt !="exr": + if img_fmt != "exr": render_name = self.get_render_elements_name() if render_name: for name in render_name: From 0fccdaf5f5d46e775863193ff7f3a44e3338eda9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 15:54:55 +0800 Subject: [PATCH 061/105] use expected files instead of files --- .../modules/deadline/plugins/publish/submit_max_deadline.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 4682cc4487..3fde667dfe 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -197,10 +197,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, else: plugin_data["DisableMultipass"] = 1 - files = instance.data.get("files") + files = instance.data.get("expectedFiles") if not files: raise RuntimeError("No render elements found") - old_output_dir = os.path.dirname(files[0]) + first_file = next(self._iter_expected_files(files)) + old_output_dir = os.path.dirname(first_file) output_beauty = RenderSettings().get_render_output(instance.name, old_output_dir) files = instance.data["expectedFiles"] From 7820d92dc9fefa090e773e720d3a122989025b5c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 May 2023 09:37:26 +0100 Subject: [PATCH 062/105] 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 063/105] 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 71b3242abd277995482a410720a4a84a13818a3a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 29 May 2023 11:41:10 +0100 Subject: [PATCH 064/105] 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 9d9eebfe09499474dac3504d7c4b252100e8c57d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 19:25:49 +0800 Subject: [PATCH 065/105] using current file to render and add validator for deadline publish in max hosts --- openpype/hosts/max/api/lib_renderproducts.py | 4 +- .../hosts/max/plugins/create/create_render.py | 14 ++++ .../max/plugins/publish/collect_render.py | 9 ++- .../hosts/max/plugins/publish/save_scene.py | 5 -- .../publish/validate_deadline_publish.py | 41 ++++++++++ .../plugins/publish/submit_max_deadline.py | 76 ++----------------- 6 files changed, 72 insertions(+), 77 deletions(-) create mode 100644 openpype/hosts/max/plugins/publish/validate_deadline_publish.py diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 2fbb7e8ff3..7e016f6f15 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -38,7 +38,7 @@ class RenderProducts(object): } return render_dict - def get_aovs(self, container): + def get_aovs(self, container, instance): render_dir = os.path.dirname(rt.rendOutputFilename) output_file = os.path.join(render_dir, @@ -71,7 +71,7 @@ class RenderProducts(object): if renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() if render_name: - rs_AovFiles = rt.Redshift_Renderer().SeparateAovFiles + rs_AovFiles = instance.data.get("separateAovFiles") if img_fmt == "exr" and not rs_AovFiles: for name in render_name: if name == "RsCryptomatte": diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 78e9527bdf..3d5ed781a4 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -2,6 +2,7 @@ """Creator plugin for creating camera.""" import os from openpype.hosts.max.api import plugin +from openpype.lib import BoolDef from openpype.pipeline import CreatedInstance from openpype.hosts.max.api.lib_rendersettings import RenderSettings @@ -18,6 +19,9 @@ class CreateRender(plugin.MaxCreator): file = rt.maxFileName filename, _ = os.path.splitext(file) instance_data["AssetName"] = filename + instance_data["separateAovFiles"] = ( + pre_create_data.get("separateAovFiles")) + instance = super(CreateRender, self).create( subset_name, instance_data, @@ -40,3 +44,13 @@ class CreateRender(plugin.MaxCreator): RenderSettings().set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) RenderSettings().render_output(container_name) + + def get_pre_create_attr_defs(self): + attrs = super(CreateRender, self).get_pre_create_attr_defs() + + return attrs + [ + BoolDef("separateAovFiles", + label="Separate Aov Files", + default=False), + + ] diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 9137f8c854..14d23c42fc 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -6,7 +6,7 @@ import pyblish.api from pymxs import runtime as rt from openpype.pipeline import get_current_asset_name from openpype.hosts.max.api import colorspace -from openpype.hosts.max.api.lib import get_max_version +from openpype.hosts.max.api.lib import get_max_version, get_current_renderer from openpype.hosts.max.api.lib_renderproducts import RenderProducts from openpype.client import get_last_version_by_subset_name @@ -31,12 +31,14 @@ class CollectRender(pyblish.api.InstancePlugin): files_by_aov = RenderProducts().get_beauty(instance.name) folder = folder.replace("\\", "/") - aovs = RenderProducts().get_aovs(instance.name) + aovs = RenderProducts().get_aovs(instance.name, instance) files_by_aov.update(aovs) if "expectedFiles" not in instance.data: instance.data["expectedFiles"] = list() + instance.data["files"] = list() instance.data["expectedFiles"].append(files_by_aov) + instance.data["files"].append(files_by_aov) img_format = RenderProducts().image_format() project_name = context.data["projectName"] @@ -62,6 +64,8 @@ class CollectRender(pyblish.api.InstancePlugin): instance.data["renderProducts"] = colorspace.ARenderProduct() instance.data["publishJobState"] = "Suspended" instance.data["attachTo"] = [] + renderer_class = get_current_renderer() + renderer = str(renderer_class).split(":")[0] # also need to get the render dir for coversion data = { "asset": asset, @@ -71,6 +75,7 @@ class CollectRender(pyblish.api.InstancePlugin): "imageFormat": img_format, "family": 'maxrender', "families": ['maxrender'], + "renderer": renderer, "source": filepath, "plugin": "3dsmax", "frameStart": int(rt.rendStart), diff --git a/openpype/hosts/max/plugins/publish/save_scene.py b/openpype/hosts/max/plugins/publish/save_scene.py index 93d97a3de5..a40788ab41 100644 --- a/openpype/hosts/max/plugins/publish/save_scene.py +++ b/openpype/hosts/max/plugins/publish/save_scene.py @@ -18,9 +18,4 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): file = rt.maxFileName current = os.path.join(folder, file) assert context.data["currentFile"] == current - - if rt.checkForSave(): - self.log.debug("Skipping file save as there " - "are no modifications..") - return rt.saveMaxFile(current) diff --git a/openpype/hosts/max/plugins/publish/validate_deadline_publish.py b/openpype/hosts/max/plugins/publish/validate_deadline_publish.py new file mode 100644 index 0000000000..f516e09337 --- /dev/null +++ b/openpype/hosts/max/plugins/publish/validate_deadline_publish.py @@ -0,0 +1,41 @@ +import os +import pyblish.api +from pymxs import runtime as rt +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError, + OptionalPyblishPluginMixin +) +from openpype.hosts.max.api.lib_rendersettings import RenderSettings + + +class ValidateDeadlinePublish(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validates Render File Directory is + not the same in every submission + """ + + order = ValidateContentsOrder + families = ["maxrender"] + hosts = ["max"] + label = "Render Output for Deadline" + optional = True + actions = [RepairAction] + + def process(self, instance): + if not self.is_active(instance.data): + return + file = rt.maxFileName + filename, ext = os.path.splitext(file) + if filename not in rt.rendOutputFilename: + raise PublishValidationError( + "Directory of RenderOutput doesn't " + "have with the current Max SceneName " + "please repair to rename the folder!" + ) + + @classmethod + def repair(cls, instance): + container= instance.data.get("instance_node") + RenderSettings().render_output(container) diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index 3fde667dfe..d7ba7107a3 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -133,6 +133,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # Add list of expected files to job # --------------------------------- exp = instance.data.get("expectedFiles") + for filepath in self._iter_expected_files(exp): job_info.OutputDirectory += os.path.dirname(filepath) job_info.OutputFilename += os.path.basename(filepath) @@ -204,8 +205,6 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, old_output_dir = os.path.dirname(first_file) output_beauty = RenderSettings().get_render_output(instance.name, old_output_dir) - files = instance.data["expectedFiles"] - first_file = next(self._iter_expected_files(files)) rgb_bname = os.path.basename(output_beauty) dir = os.path.dirname(first_file) beauty_name = f"{dir}/{rgb_bname}" @@ -231,6 +230,9 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, new_elem = new_elem.replace("/", "\\") plugin_data["RenderElementOutputFilename%d" % i] = new_elem # noqa + if renderer == "Redshift_Renderer": + plugin_data["redshift_SeparateAovFiles"] = instance.data.get( + "separateAovFiles", False) self.log.debug("plugin data:{}".format(plugin_data)) plugin_info.update(plugin_data) @@ -239,72 +241,10 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, def from_published_scene(self, replace_in_path=True): instance = self._instance - workfile_instance = self._get_workfile_instance(instance.context) - if workfile_instance is None: - return - - # determine published path from Anatomy. - template_data = workfile_instance.data.get("anatomyData") - rep = workfile_instance.data["representations"][0] - template_data["representation"] = rep.get("name") - template_data["ext"] = rep.get("ext") - template_data["comment"] = None - - anatomy = instance.context.data['anatomy'] - template_obj = anatomy.templates_obj["publish"]["path"] - template_filled = template_obj.format_strict(template_data) - file_path = os.path.normpath(template_filled) - - self.log.info("Using published scene for render {}".format(file_path)) - - if not os.path.exists(file_path): - self.log.error("published scene does not exist!") - raise - - if not replace_in_path: - return file_path - - # now we need to switch scene in expected files - # because token will now point to published - # scene file and that might differ from current one - def _clean_name(path): - return os.path.splitext(os.path.basename(path))[0] - - new_scene = _clean_name(file_path) - orig_scene = _clean_name(instance.data["AssetName"]) - expected_files = instance.data.get("expectedFiles") - - if isinstance(expected_files[0], dict): - # we have aovs and we need to iterate over them - new_exp = {} - for aov, files in expected_files[0].items(): - replaced_files = [] - for f in files: - replaced_files.append( - str(f).replace(orig_scene, new_scene) - ) - new_exp[aov] = replaced_files - # [] might be too much here, TODO - instance.data["expectedFiles"] = [new_exp] - else: - new_exp = [] - for f in expected_files: - new_exp.append( - str(f).replace(orig_scene, new_scene) - ) - instance.data["expectedFiles"] = new_exp - - metadata_folder = instance.data.get("publishRenderMetadataFolder") - if metadata_folder: - metadata_folder = metadata_folder.replace(orig_scene, - new_scene) - instance.data["publishRenderMetadataFolder"] = metadata_folder - - self.log.info("Scene name was switched {} -> {}".format( - orig_scene, new_scene - )) - - return file_path + if instance.data["renderer"] == "Redshift_Renderer": + self.log.debug("Using Redshift...published scene wont be used..") + replace_in_path = False + return replace_in_path @staticmethod def _iter_expected_files(exp): From dbb175204adf16e763d2c2295d6ad675988453ba Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 19:29:35 +0800 Subject: [PATCH 066/105] hound --- openpype/hosts/max/plugins/publish/validate_deadline_publish.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/max/plugins/publish/validate_deadline_publish.py b/openpype/hosts/max/plugins/publish/validate_deadline_publish.py index f516e09337..c5bc979043 100644 --- a/openpype/hosts/max/plugins/publish/validate_deadline_publish.py +++ b/openpype/hosts/max/plugins/publish/validate_deadline_publish.py @@ -37,5 +37,5 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin, @classmethod def repair(cls, instance): - container= instance.data.get("instance_node") + container = instance.data.get("instance_node") RenderSettings().render_output(container) From e506d88ed3d113f608759e883dcdf17dcb6f5ccc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 20:33:40 +0800 Subject: [PATCH 067/105] style fix --- openpype/hosts/max/plugins/create/create_render.py | 4 +--- .../modules/deadline/plugins/publish/submit_max_deadline.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 3d5ed781a4..6c581cdcf4 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -47,10 +47,8 @@ class CreateRender(plugin.MaxCreator): def get_pre_create_attr_defs(self): attrs = super(CreateRender, self).get_pre_create_attr_defs() - return attrs + [ BoolDef("separateAovFiles", label="Separate Aov Files", - default=False), - + default=False) ] diff --git a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py index d7ba7107a3..b6a30e36b7 100644 --- a/openpype/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_max_deadline.py @@ -232,7 +232,7 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, if renderer == "Redshift_Renderer": plugin_data["redshift_SeparateAovFiles"] = instance.data.get( - "separateAovFiles", False) + "separateAovFiles") self.log.debug("plugin data:{}".format(plugin_data)) plugin_info.update(plugin_data) From 5f763a4e8e65d0aeb8e4515e69ec768f6f2c5f4d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 20:36:38 +0800 Subject: [PATCH 068/105] dont add separate aov as instance data --- openpype/hosts/max/api/lib_renderproducts.py | 4 ++-- openpype/hosts/max/plugins/create/create_render.py | 10 ---------- openpype/hosts/max/plugins/publish/collect_render.py | 2 +- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 7e016f6f15..a6427bf7c5 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -38,7 +38,7 @@ class RenderProducts(object): } return render_dict - def get_aovs(self, container, instance): + def get_aovs(self, container): render_dir = os.path.dirname(rt.rendOutputFilename) output_file = os.path.join(render_dir, @@ -71,7 +71,7 @@ class RenderProducts(object): if renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() if render_name: - rs_AovFiles = instance.data.get("separateAovFiles") + rs_AovFiles = rt.RedShift_Renderer().separateAovFiles if img_fmt == "exr" and not rs_AovFiles: for name in render_name: if name == "RsCryptomatte": diff --git a/openpype/hosts/max/plugins/create/create_render.py b/openpype/hosts/max/plugins/create/create_render.py index 6c581cdcf4..be5dece05b 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -19,8 +19,6 @@ class CreateRender(plugin.MaxCreator): file = rt.maxFileName filename, _ = os.path.splitext(file) instance_data["AssetName"] = filename - instance_data["separateAovFiles"] = ( - pre_create_data.get("separateAovFiles")) instance = super(CreateRender, self).create( subset_name, @@ -44,11 +42,3 @@ class CreateRender(plugin.MaxCreator): RenderSettings().set_render_camera(sel_obj) # set output paths for rendering(mandatory for deadline) RenderSettings().render_output(container_name) - - def get_pre_create_attr_defs(self): - attrs = super(CreateRender, self).get_pre_create_attr_defs() - return attrs + [ - BoolDef("separateAovFiles", - label="Separate Aov Files", - default=False) - ] diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 14d23c42fc..77ab5f654d 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -31,7 +31,7 @@ class CollectRender(pyblish.api.InstancePlugin): files_by_aov = RenderProducts().get_beauty(instance.name) folder = folder.replace("\\", "/") - aovs = RenderProducts().get_aovs(instance.name, instance) + aovs = RenderProducts().get_aovs(instance.name) files_by_aov.update(aovs) if "expectedFiles" not in instance.data: From 982186ffa08618f8d590e894356595f886af0f57 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 20:38:17 +0800 Subject: [PATCH 069/105] remove unused import --- 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 be5dece05b..5ad895b86e 100644 --- a/openpype/hosts/max/plugins/create/create_render.py +++ b/openpype/hosts/max/plugins/create/create_render.py @@ -2,7 +2,6 @@ """Creator plugin for creating camera.""" import os from openpype.hosts.max.api import plugin -from openpype.lib import BoolDef from openpype.pipeline import CreatedInstance from openpype.hosts.max.api.lib_rendersettings import RenderSettings From bafc085ec19e624c163d6a862b9fcc6e0edab983 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 29 May 2023 22:56:47 +0800 Subject: [PATCH 070/105] updating validator's comment --- .../max/plugins/publish/validate_deadline_publish.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/max/plugins/publish/validate_deadline_publish.py b/openpype/hosts/max/plugins/publish/validate_deadline_publish.py index c5bc979043..b2f0e863f4 100644 --- a/openpype/hosts/max/plugins/publish/validate_deadline_publish.py +++ b/openpype/hosts/max/plugins/publish/validate_deadline_publish.py @@ -30,12 +30,14 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin, filename, ext = os.path.splitext(file) if filename not in rt.rendOutputFilename: raise PublishValidationError( - "Directory of RenderOutput doesn't " - "have with the current Max SceneName " - "please repair to rename the folder!" + "Render output folder " + "doesn't match the max scene name! " + "Use Repair action to " + "fix the folder file path.." ) @classmethod def repair(cls, instance): container = instance.data.get("instance_node") RenderSettings().render_output(container) + cls.log.debug("Reset the render output folder...") From a4e9eaf3c38aaa4bb31784067d35d54656ae8333 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 29 May 2023 23:24:48 +0200 Subject: [PATCH 071/105] Update openpype/hosts/max/plugins/load/load_redshift_proxy.py Co-authored-by: Roy Nieterau --- openpype/hosts/max/plugins/load/load_redshift_proxy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/max/plugins/load/load_redshift_proxy.py b/openpype/hosts/max/plugins/load/load_redshift_proxy.py index 9451e5299b..31692f6367 100644 --- a/openpype/hosts/max/plugins/load/load_redshift_proxy.py +++ b/openpype/hosts/max/plugins/load/load_redshift_proxy.py @@ -10,7 +10,6 @@ from openpype.hosts.max.api import lib class RedshiftProxyLoader(load.LoaderPlugin): - """Load rs files with Redshift Proxy""" label = "Load Redshift Proxy" From 279b3dc767fc870a8632abc625f1e2dbf3706add Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 May 2023 12:09:53 +0200 Subject: [PATCH 072/105] Set explicit startup script path --- .../resolve/hooks/pre_resolve_launch_last_workfile.py | 11 +++++++++++ .../utility_scripts/openpype_startup.scriptlib | 7 +++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py b/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py index 6db3cc28b2..2ad4352b82 100644 --- a/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py +++ b/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py @@ -1,6 +1,7 @@ import os from openpype.lib import PreLaunchHook +import openpype.hosts.resolve class ResolveLaunchLastWorkfile(PreLaunchHook): @@ -33,3 +34,13 @@ class ResolveLaunchLastWorkfile(PreLaunchHook): 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/utility_scripts/openpype_startup.scriptlib b/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib index 9fca666d78..ec9b30a18d 100644 --- a/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib +++ b/openpype/hosts/resolve/utility_scripts/openpype_startup.scriptlib @@ -5,10 +5,9 @@ function file_exists(name) end -openpype_root = os.getenv("OPENPYPE_ROOT") -if openpype_root ~= nil then - script = openpype_root .. "/openpype/hosts/resolve/startup.py" - script = fusion:MapPath(script) +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 From 990737623bcfab7c5f7502e3cb4a0f2156f0891c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 May 2023 12:20:17 +0200 Subject: [PATCH 073/105] Cosmetics --- openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py b/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py index 2ad4352b82..0e27ddb8c3 100644 --- a/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py +++ b/openpype/hosts/resolve/hooks/pre_resolve_launch_last_workfile.py @@ -43,4 +43,3 @@ class ResolveLaunchLastWorkfile(PreLaunchHook): self.launch_context.env[key] = script_path self.log.info("Setting OPENPYPE_RESOLVE_STARTUP_SCRIPT to: " f"{script_path}") - From 307eca8c28aeb1afeb68ba1e93e63e993c21a084 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 May 2023 13:24:17 +0200 Subject: [PATCH 074/105] Cleanup Resolve startup script + add setting for launch menu on start --- openpype/hosts/resolve/startup.py | 46 ++++++++++++++----- .../defaults/project_settings/resolve.json | 1 + .../schema_project_resolve.json | 5 ++ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/resolve/startup.py b/openpype/hosts/resolve/startup.py index 4aeb106ef1..79a64e0fbf 100644 --- a/openpype/hosts/resolve/startup.py +++ b/openpype/hosts/resolve/startup.py @@ -1,26 +1,44 @@ +"""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 -# Importing this takes a little over a second and thus this means -# that we have about 1.5 seconds delay before the workfile will actually -# be opened at the minimum import openpype.hosts.resolve.api -def launch_menu(): - from openpype.pipeline import install_host - print("Launching Resolve OpenPype menu..") +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 - # Activate resolve from openpype 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 - openpype.hosts.resolve.api.bmdvr = resolve # noqa - openpype.hosts.resolve.api.bmdvf = fusion # noqa - openpype.hosts.resolve.api.open_file(path) + host = ensure_installed_host() + host.open_file(path) def main(): @@ -32,8 +50,12 @@ def main(): print("No last workfile set to open. Skipping..") # Launch OpenPype menu - # TODO: Add a setting to enable/disable this - launch_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__": 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", From 2e58cd2e1a1a1dd23e0bc59bc1cbc414fa6d1ec9 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 30 May 2023 21:44:26 +0800 Subject: [PATCH 075/105] move the custom popup menu to nuke/startup and add the frame setting to Openpype tool menu --- openpype/hosts/nuke/api/lib.py | 32 --------- openpype/hosts/nuke/api/pipeline.py | 5 -- .../nuke/startup}/custom_popup.py | 71 ++++++++++++++----- .../defaults/project_settings/nuke.json | 7 ++ 4 files changed, 61 insertions(+), 54 deletions(-) rename openpype/{widgets => hosts/nuke/startup}/custom_popup.py (65%) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 94a0ff15ad..59a63d1373 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2358,38 +2358,6 @@ class WorkfileSettings(object): # add colorspace menu item self.set_colorspace() - def reset_frame_range_read_nodes(self): - from openpype.widgets import custom_popup - parent = get_main_window() - dialog = custom_popup.CustomScriptDialog(parent=parent) - dialog.setWindowTitle("Frame Range") - dialog.set_name("Frame Range: ") - dialog.set_line_edit("%s - %s" % (nuke.root().firstFrame(), - nuke.root().lastFrame())) - frame = dialog.widgets["line_edit"] - selection = dialog.widgets["selection"] - dialog.on_clicked.connect( - lambda: set_frame_range(frame, selection) - ) - - def set_frame_range(frame, selection): - frame_range = frame.text() - selected = selection.isChecked() - if not nuke.allNodes("Read"): - return - for read_node in nuke.allNodes("Read"): - if selected: - if not nuke.selectedNodes(): - return - if read_node in nuke.selectedNodes(): - read_node["frame_mode"].setValue("start_at") - read_node["frame"].setValue(frame_range) - else: - read_node["frame_mode"].setValue("start_at") - read_node["frame"].setValue(frame_range) - dialog.show() - - return False def set_favorites(self): from .utils import set_context_favorites diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 33e25d3c81..75b0f80d21 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -286,11 +286,6 @@ def _install_menu(): lambda: WorkfileSettings().set_context_settings() ) - menu.addSeparator() - menu.addCommand( - "Set Frame Range(Read Node)", - lambda: WorkfileSettings().reset_frame_range_read_nodes() - ) menu.addSeparator() menu.addCommand( "Build Workfile", diff --git a/openpype/widgets/custom_popup.py b/openpype/hosts/nuke/startup/custom_popup.py similarity index 65% rename from openpype/widgets/custom_popup.py rename to openpype/hosts/nuke/startup/custom_popup.py index be4b0c32d5..c85577133c 100644 --- a/openpype/widgets/custom_popup.py +++ b/openpype/hosts/nuke/startup/custom_popup.py @@ -1,9 +1,26 @@ import sys import contextlib - +import re +import nuke from PySide2 import QtCore, QtWidgets +def get_main_window(): + """Acquire Nuke's main window""" + main_window = None + if main_window is None: + + top_widgets = QtWidgets.QApplication.topLevelWidgets() + name = "Foundry::UI::DockMainWindow" + for widget in top_widgets: + if ( + widget.inherits("QMainWindow") + and widget.metaObject().className() == name + ): + main_window = widget + break + return main_window + class CustomScriptDialog(QtWidgets.QDialog): """A Popup that moves itself to bottom right of screen on show event. @@ -14,6 +31,9 @@ class CustomScriptDialog(QtWidgets.QDialog): on_clicked = QtCore.Signal() on_line_changed = QtCore.Signal(str) + context = None + + def __init__(self, parent=None, *args, **kwargs): super(CustomScriptDialog, self).__init__(parent=parent, @@ -23,23 +43,25 @@ class CustomScriptDialog(QtWidgets.QDialog): # Layout layout = QtWidgets.QVBoxLayout(self) - line_layout = QtWidgets.QHBoxLayout() - line_layout.setContentsMargins(10, 5, 10, 10) + frame_layout = QtWidgets.QHBoxLayout() + frame_layout.setContentsMargins(10, 5, 10, 10) selection_layout = QtWidgets.QHBoxLayout() selection_layout.setContentsMargins(10, 5, 10, 10) button_layout = QtWidgets.QHBoxLayout() button_layout.setContentsMargins(10, 5, 10, 10) # Increase spacing slightly for readability - line_layout.setSpacing(10) + frame_layout.setSpacing(10) button_layout.setSpacing(10) - name = QtWidgets.QLabel("") + name = QtWidgets.QLabel("Frame Range: ") name.setStyleSheet(""" QLabel { font-size: 12px; } """) - line_edit = QtWidgets.QLineEdit("") + line_edit = QtWidgets.QLineEdit( + "%s-%s" % (nuke.root().firstFrame(), + nuke.root().lastFrame())) selection_name = QtWidgets.QLabel("Use Selection") selection_name.setStyleSheet(""" QLabel { @@ -54,13 +76,13 @@ class CustomScriptDialog(QtWidgets.QDialog): cancel.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) - line_layout.addWidget(name) - line_layout.addWidget(line_edit) + frame_layout.addWidget(name) + frame_layout.addWidget(line_edit) selection_layout.addWidget(selection_name) selection_layout.addWidget(has_selection) button_layout.addWidget(button) button_layout.addWidget(cancel) - layout.addLayout(line_layout) + layout.addLayout(frame_layout) layout.addLayout(selection_layout) layout.addLayout(button_layout) # Default size @@ -73,7 +95,6 @@ class CustomScriptDialog(QtWidgets.QDialog): "button": button, "cancel": cancel } - # Signals has_selection.toggled.connect(self.emit_click_with_state) line_edit.textChanged.connect(self.on_line_edit_changed) @@ -115,18 +136,34 @@ class CustomScriptDialog(QtWidgets.QDialog): Raises the parent (if any) """ + frame_range = self.widgets['line_edit'].text() + selected = self.widgets["selection"].isChecked() + pattern = r"^(?P-?[0-9]+)(?:(?:-+)(?P-?[0-9]+))?$" + match = re.match(pattern, frame_range) + frame_start = int(match.group("start")) + frame_end = int(match.group("end")) + if not nuke.allNodes("Read"): + return + for read_node in nuke.allNodes("Read"): + if selected: + if not nuke.selectedNodes(): + return + if read_node in nuke.selectedNodes(): + read_node["frame_mode"].setValue("start_at") + read_node["frame"].setValue(frame_range) + read_node["first"].setValue(frame_start) + read_node["last"].setValue(frame_end) + else: + read_node["frame_mode"].setValue("start_at") + read_node["frame"].setValue(frame_range) + read_node["first"].setValue(frame_start) + read_node["last"].setValue(frame_end) - parent = self.parent() self.close() - # Trigger the signal - self.on_clicked.emit() - - if parent: - parent.raise_() + return False def showEvent(self, event): - # Position popup based on contents on show event return super(CustomScriptDialog, self).showEvent(event) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index f01bdf7d50..287d13e5c9 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 Frame Range(Read Node)", + "command": "from openpype.hosts.nuke.startup import custom_popup;from openpype.hosts.nuke.startup.custom_popup import get_main_window;custom_popup.CustomScriptDialog(parent=get_main_window()).show();", + "tooltip": "Set Frame Range for Read Node(s)" } ] }, From 3556b58fdc593eef0c22eb3d6b357e0c0ac0be7c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 30 May 2023 21:46:25 +0800 Subject: [PATCH 076/105] hound fix --- openpype/hosts/nuke/api/lib.py | 1 - openpype/hosts/nuke/startup/custom_popup.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 59a63d1373..a439142051 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2358,7 +2358,6 @@ class WorkfileSettings(object): # add colorspace menu item self.set_colorspace() - def set_favorites(self): from .utils import set_context_favorites diff --git a/openpype/hosts/nuke/startup/custom_popup.py b/openpype/hosts/nuke/startup/custom_popup.py index c85577133c..dfbd590e03 100644 --- a/openpype/hosts/nuke/startup/custom_popup.py +++ b/openpype/hosts/nuke/startup/custom_popup.py @@ -21,6 +21,7 @@ def get_main_window(): break return main_window + class CustomScriptDialog(QtWidgets.QDialog): """A Popup that moves itself to bottom right of screen on show event. @@ -34,7 +35,6 @@ class CustomScriptDialog(QtWidgets.QDialog): context = None - def __init__(self, parent=None, *args, **kwargs): super(CustomScriptDialog, self).__init__(parent=parent, *args, From dddfeecceb4c711e1d8b848c8454d529992e6182 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 30 May 2023 21:47:03 +0800 Subject: [PATCH 077/105] hound fix --- openpype/hosts/nuke/startup/custom_popup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/nuke/startup/custom_popup.py b/openpype/hosts/nuke/startup/custom_popup.py index dfbd590e03..d400ed913c 100644 --- a/openpype/hosts/nuke/startup/custom_popup.py +++ b/openpype/hosts/nuke/startup/custom_popup.py @@ -34,7 +34,6 @@ class CustomScriptDialog(QtWidgets.QDialog): on_line_changed = QtCore.Signal(str) context = None - def __init__(self, parent=None, *args, **kwargs): super(CustomScriptDialog, self).__init__(parent=parent, *args, From 8f0821ab327db90a6fba5d95594bdcc461fb33e3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 30 May 2023 22:12:47 +0800 Subject: [PATCH 078/105] the dialog closes as usual by clicking execute button when there is no nuke nodes or no nuke nodes by selection --- openpype/hosts/nuke/startup/custom_popup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/nuke/startup/custom_popup.py b/openpype/hosts/nuke/startup/custom_popup.py index d400ed913c..57d79f99ff 100644 --- a/openpype/hosts/nuke/startup/custom_popup.py +++ b/openpype/hosts/nuke/startup/custom_popup.py @@ -142,10 +142,12 @@ class CustomScriptDialog(QtWidgets.QDialog): frame_start = int(match.group("start")) frame_end = int(match.group("end")) if not nuke.allNodes("Read"): + self.close() return for read_node in nuke.allNodes("Read"): if selected: if not nuke.selectedNodes(): + self.close() return if read_node in nuke.selectedNodes(): read_node["frame_mode"].setValue("start_at") From de72f26f9451f48608229cc85868c1545a201afd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 May 2023 17:09:54 +0200 Subject: [PATCH 079/105] adding check also against class attribute --- openpype/plugins/publish/collect_frames_fix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_frames_fix.py b/openpype/plugins/publish/collect_frames_fix.py index 837738eb06..ca1ccc19fd 100644 --- a/openpype/plugins/publish/collect_frames_fix.py +++ b/openpype/plugins/publish/collect_frames_fix.py @@ -66,7 +66,7 @@ class CollectFramesFixDef( self.log.debug("last_version_published_files::{}".format( instance.data["last_version_published_files"])) - if rewrite_version: + if self.rewrite_version_enable and rewrite_version: instance.data["version"] = version["name"] # limits triggering version validator instance.data.pop("latestVersion") From 04e6f5f4bb844b11dbb93475f5d023c2125651ff Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 30 May 2023 17:16:18 +0200 Subject: [PATCH 080/105] :bug: fix support for separate AOVs and some style issues --- openpype/hosts/max/api/lib_renderproducts.py | 76 +++++++++---------- .../max/plugins/publish/collect_render.py | 10 ++- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index a6427bf7c5..81057db733 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -3,22 +3,20 @@ # arnold # https://help.autodesk.com/view/ARNOL/ENU/?guid=arnold_for_3ds_max_ax_maxscript_commands_ax_renderview_commands_html import os + from pymxs import runtime as rt -from openpype.hosts.max.api.lib import ( - get_current_renderer -) -from openpype.settings import get_project_settings + +from openpype.hosts.max.api.lib import get_current_renderer from openpype.pipeline import legacy_io +from openpype.settings import get_project_settings class RenderProducts(object): def __init__(self, project_settings=None): - self._project_settings = project_settings - if not self._project_settings: - self._project_settings = get_project_settings( - legacy_io.Session["AVALON_PROJECT"] - ) + self._project_settings = project_settings or get_project_settings( + legacy_io.Session["AVALON_PROJECT"] + ) def get_beauty(self, container): render_dir = os.path.dirname(rt.rendOutputFilename) @@ -29,14 +27,14 @@ class RenderProducts(object): setting = self._project_settings img_fmt = setting["max"]["RenderSettings"]["image_format"] # noqa - startFrame = int(rt.rendStart) - endFrame = int(rt.rendEnd) + 1 + start_frame = int(rt.rendStart) + end_frame = int(rt.rendEnd) + 1 - render_dict = { + return { "beauty": self.get_expected_beauty( - output_file, startFrame, endFrame, img_fmt) + output_file, start_frame, end_frame, img_fmt + ) } - return render_dict def get_aovs(self, container): render_dir = os.path.dirname(rt.rendOutputFilename) @@ -47,8 +45,8 @@ class RenderProducts(object): setting = self._project_settings img_fmt = setting["max"]["RenderSettings"]["image_format"] # noqa - startFrame = int(rt.rendStart) - endFrame = int(rt.rendEnd) + 1 + start_frame = int(rt.rendStart) + end_frame = int(rt.rendEnd) + 1 renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] render_dict = {} @@ -65,38 +63,40 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, startFrame, - endFrame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) - if renderer == "Redshift_Renderer": + elif renderer == "Redshift_Renderer": render_name = self.get_render_elements_name() if render_name: - rs_AovFiles = rt.RedShift_Renderer().separateAovFiles - if img_fmt == "exr" and not rs_AovFiles: + rs_aov_files = rt.Execute("renderers.current.separateAovFiles") + # this doesn't work, always returns False + # rs_AovFiles = rt.RedShift_Renderer().separateAovFiles + if img_fmt == "exr" and not rs_aov_files: for name in render_name: if name == "RsCryptomatte": render_dict.update({ name: self.get_expected_render_elements( - output_file, name, startFrame, - endFrame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) else: for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, startFrame, - endFrame, img_fmt) + output_file, name, start_frame, + end_frame, img_fmt) }) - if renderer == "Arnold": + elif renderer == "Arnold": render_name = self.get_arnold_product_name() if render_name: for name in render_name: render_dict.update({ name: self.get_expected_arnold_product( - output_file, name, startFrame, endFrame, img_fmt) + output_file, name, start_frame, end_frame, img_fmt) }) - if renderer in [ + elif renderer in [ "V_Ray_6_Hotfix_3", "V_Ray_GPU_6_Hotfix_3" ]: @@ -106,15 +106,15 @@ class RenderProducts(object): for name in render_name: render_dict.update({ name: self.get_expected_render_elements( - output_file, name, startFrame, - endFrame, img_fmt) # noqa + output_file, name, start_frame, + end_frame, img_fmt) # noqa }) return render_dict - def get_expected_beauty(self, folder, startFrame, endFrame, fmt): + def get_expected_beauty(self, folder, start_frame, end_frame, fmt): beauty_frame_range = [] - for f in range(startFrame, endFrame): + for f in range(start_frame, end_frame): frame = "%04d" % f beauty_output = f"{folder}.{frame}.{fmt}" beauty_output = beauty_output.replace("\\", "/") @@ -134,19 +134,17 @@ class RenderProducts(object): return for i in range(aov_group_num): # get the specific AOV group - for aov in aov_mgr.drivers[i].aov_list: - aov_name.append(aov.name) - + aov_name.extend(aov.name for aov in aov_mgr.drivers[i].aov_list) # close the AOVs manager window amw.close() return aov_name def get_expected_arnold_product(self, folder, name, - startFrame, endFrame, fmt): + start_frame, end_frame, fmt): """Get all the expected Arnold AOVs""" aov_list = [] - for f in range(startFrame, endFrame): + for f in range(start_frame, end_frame): frame = "%04d" % f render_element = f"{folder}_{name}.{frame}.{fmt}" render_element = render_element.replace("\\", "/") @@ -171,10 +169,10 @@ class RenderProducts(object): return render_name def get_expected_render_elements(self, folder, name, - startFrame, endFrame, fmt): + start_frame, end_frame, fmt): """Get all the expected render element output files. """ render_elements = [] - for f in range(startFrame, endFrame): + for f in range(start_frame, end_frame): frame = "%04d" % f render_element = f"{folder}_{name}.{frame}.{fmt}" render_element = render_element.replace("\\", "/") diff --git a/openpype/hosts/max/plugins/publish/collect_render.py b/openpype/hosts/max/plugins/publish/collect_render.py index 77ab5f654d..db5c84fad9 100644 --- a/openpype/hosts/max/plugins/publish/collect_render.py +++ b/openpype/hosts/max/plugins/publish/collect_render.py @@ -66,7 +66,7 @@ class CollectRender(pyblish.api.InstancePlugin): instance.data["attachTo"] = [] renderer_class = get_current_renderer() renderer = str(renderer_class).split(":")[0] - # also need to get the render dir for coversion + # also need to get the render dir for conversion data = { "asset": asset, "subset": str(instance.name), @@ -84,4 +84,12 @@ class CollectRender(pyblish.api.InstancePlugin): "farm": True } instance.data.update(data) + + # TODO: this should be unified with maya and its "multipart" flag + # on instance. + if renderer == "Redshift_Renderer": + instance.data.update( + {"separateAovFiles": rt.Execute( + "renderers.current.separateAovFiles")}) + self.log.info("data: {0}".format(data)) From 4d76c2520f254991da405dc463418d9bb6e2cfc4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 30 May 2023 17:19:00 +0200 Subject: [PATCH 081/105] cleanup --- .../plugins/publish/collect_frames_fix.py | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/openpype/plugins/publish/collect_frames_fix.py b/openpype/plugins/publish/collect_frames_fix.py index ca1ccc19fd..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 self.rewrite_version_enable and 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): From 11728bae0e4f1a539d5959ec3cfe9fe523fe356a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 31 May 2023 20:02:05 +0800 Subject: [PATCH 082/105] roy's comment and uses import instead of from..import --- openpype/hosts/nuke/startup/custom_popup.py | 17 ++++------------- .../defaults/project_settings/nuke.json | 2 +- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/openpype/hosts/nuke/startup/custom_popup.py b/openpype/hosts/nuke/startup/custom_popup.py index 57d79f99ff..7f60fdc70b 100644 --- a/openpype/hosts/nuke/startup/custom_popup.py +++ b/openpype/hosts/nuke/startup/custom_popup.py @@ -23,10 +23,7 @@ def get_main_window(): class CustomScriptDialog(QtWidgets.QDialog): - """A Popup that moves itself to bottom right of screen on show event. - - The UI contains a message label and a red highlighted button to "show" - or perform another custom action from this pop-up. + """A Custom Popup For Nuke Read Node """ @@ -95,7 +92,7 @@ class CustomScriptDialog(QtWidgets.QDialog): "cancel": cancel } # Signals - has_selection.toggled.connect(self.emit_click_with_state) + has_selection.toggled.connect(self.on_checked_changed) line_edit.textChanged.connect(self.on_line_edit_changed) button.clicked.connect(self._on_clicked) cancel.clicked.connect(self.close) @@ -104,10 +101,9 @@ class CustomScriptDialog(QtWidgets.QDialog): self.setWindowTitle("Custom Popup") def update_values(self): - self.widgets["selection"].isChecked() + return self.widgets["selection"].isChecked() - def emit_click_with_state(self): - """Emit the on_clicked signal with the toggled state""" + def on_checked_changed(self): checked = self.widgets["selection"].isChecked() return checked @@ -164,11 +160,6 @@ class CustomScriptDialog(QtWidgets.QDialog): return False - def showEvent(self, event): - # Position popup based on contents on show event - return super(CustomScriptDialog, self).showEvent(event) - - @contextlib.contextmanager def application(): app = QtWidgets.QApplication(sys.argv) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 287d13e5c9..c116540a99 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 Frame Range(Read Node)", - "command": "from openpype.hosts.nuke.startup import custom_popup;from openpype.hosts.nuke.startup.custom_popup import get_main_window;custom_popup.CustomScriptDialog(parent=get_main_window()).show();", + "command": "import openpype.hosts.nuke.startup.custom_popup as popup;popup.CustomScriptDialog(parent=popup.get_main_window()).show();", "tooltip": "Set Frame Range for Read Node(s)" } ] From 803dd565751bb65a6ef5b97e7c6f5447f4534717 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 31 May 2023 20:03:18 +0800 Subject: [PATCH 083/105] hound fix --- openpype/hosts/nuke/startup/custom_popup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/nuke/startup/custom_popup.py b/openpype/hosts/nuke/startup/custom_popup.py index 7f60fdc70b..76d5a25596 100644 --- a/openpype/hosts/nuke/startup/custom_popup.py +++ b/openpype/hosts/nuke/startup/custom_popup.py @@ -160,6 +160,7 @@ class CustomScriptDialog(QtWidgets.QDialog): return False + @contextlib.contextmanager def application(): app = QtWidgets.QApplication(sys.argv) From 183b2866d9e1f1f7b611fa6860c1edc315798b66 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 31 May 2023 20:14:57 +0800 Subject: [PATCH 084/105] style fix --- openpype/settings/defaults/project_settings/nuke.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index c116540a99..35e5b1975c 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -226,7 +226,7 @@ { "type": "action", "sourcetype": "python", - "title": "Set Frame Range(Read Node)", + "title": "Set Frame Range (Read Node)", "command": "import openpype.hosts.nuke.startup.custom_popup as popup;popup.CustomScriptDialog(parent=popup.get_main_window()).show();", "tooltip": "Set Frame Range for Read Node(s)" } From 776dcb5a6fcf65eec08f95655c870d7e43ec4ce9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 31 May 2023 13:44:50 +0000 Subject: [PATCH 085/105] 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 0036e121b7..aa5b8decdc 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.9 - 3.15.9-nightly.2 - 3.15.9-nightly.1 - 3.15.8 @@ -134,7 +135,6 @@ body: - 3.14.3-nightly.1 - 3.14.2 - 3.14.2-nightly.5 - - 3.14.2-nightly.4 validations: required: true - type: dropdown From 3955c466c3a0f01e20bf95bfa594b561884517e5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 31 May 2023 17:17:35 +0200 Subject: [PATCH 086/105] fix apply settings on hiero loader (#5073) --- openpype/hosts/hiero/plugins/load/load_clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index 77844d2448..c9bebfa8b2 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -41,8 +41,8 @@ class LoadClip(phiero.SequenceLoader): clip_name_template = "{asset}_{subset}_{representation}" + @classmethod def apply_settings(cls, project_settings, system_settings): - plugin_type_settings = ( project_settings .get("hiero", {}) From a30ba51508f03c1adbac8c434432a80184d0665b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 1 Jun 2023 00:11:30 +0800 Subject: [PATCH 087/105] use nukescript's panel --- openpype/hosts/nuke/startup/custom_popup.py | 174 ------------------ .../startup/ops_frame_setting_for_read.py | 46 +++++ .../defaults/project_settings/nuke.json | 2 +- 3 files changed, 47 insertions(+), 175 deletions(-) delete mode 100644 openpype/hosts/nuke/startup/custom_popup.py create mode 100644 openpype/hosts/nuke/startup/ops_frame_setting_for_read.py diff --git a/openpype/hosts/nuke/startup/custom_popup.py b/openpype/hosts/nuke/startup/custom_popup.py deleted file mode 100644 index 76d5a25596..0000000000 --- a/openpype/hosts/nuke/startup/custom_popup.py +++ /dev/null @@ -1,174 +0,0 @@ -import sys -import contextlib -import re -import nuke -from PySide2 import QtCore, QtWidgets - - -def get_main_window(): - """Acquire Nuke's main window""" - main_window = None - if main_window is None: - - top_widgets = QtWidgets.QApplication.topLevelWidgets() - name = "Foundry::UI::DockMainWindow" - for widget in top_widgets: - if ( - widget.inherits("QMainWindow") - and widget.metaObject().className() == name - ): - main_window = widget - break - return main_window - - -class CustomScriptDialog(QtWidgets.QDialog): - """A Custom Popup For Nuke Read Node - - """ - - on_clicked = QtCore.Signal() - on_line_changed = QtCore.Signal(str) - context = None - - def __init__(self, parent=None, *args, **kwargs): - super(CustomScriptDialog, self).__init__(parent=parent, - *args, - **kwargs) - self.setContentsMargins(0, 0, 0, 0) - - # Layout - layout = QtWidgets.QVBoxLayout(self) - frame_layout = QtWidgets.QHBoxLayout() - frame_layout.setContentsMargins(10, 5, 10, 10) - selection_layout = QtWidgets.QHBoxLayout() - selection_layout.setContentsMargins(10, 5, 10, 10) - button_layout = QtWidgets.QHBoxLayout() - button_layout.setContentsMargins(10, 5, 10, 10) - - # Increase spacing slightly for readability - frame_layout.setSpacing(10) - button_layout.setSpacing(10) - name = QtWidgets.QLabel("Frame Range: ") - name.setStyleSheet(""" - QLabel { - font-size: 12px; - } - """) - line_edit = QtWidgets.QLineEdit( - "%s-%s" % (nuke.root().firstFrame(), - nuke.root().lastFrame())) - selection_name = QtWidgets.QLabel("Use Selection") - selection_name.setStyleSheet(""" - QLabel { - font-size: 12px; - } - """) - has_selection = QtWidgets.QCheckBox() - button = QtWidgets.QPushButton("Execute") - button.setSizePolicy(QtWidgets.QSizePolicy.Maximum, - QtWidgets.QSizePolicy.Maximum) - cancel = QtWidgets.QPushButton("Cancel") - cancel.setSizePolicy(QtWidgets.QSizePolicy.Maximum, - QtWidgets.QSizePolicy.Maximum) - - frame_layout.addWidget(name) - frame_layout.addWidget(line_edit) - selection_layout.addWidget(selection_name) - selection_layout.addWidget(has_selection) - button_layout.addWidget(button) - button_layout.addWidget(cancel) - layout.addLayout(frame_layout) - layout.addLayout(selection_layout) - layout.addLayout(button_layout) - # Default size - self.resize(100, 40) - - self.widgets = { - "name": name, - "line_edit": line_edit, - "selection": has_selection, - "button": button, - "cancel": cancel - } - # Signals - has_selection.toggled.connect(self.on_checked_changed) - line_edit.textChanged.connect(self.on_line_edit_changed) - button.clicked.connect(self._on_clicked) - cancel.clicked.connect(self.close) - self.update_values() - # Set default title - self.setWindowTitle("Custom Popup") - - def update_values(self): - return self.widgets["selection"].isChecked() - - def on_checked_changed(self): - checked = self.widgets["selection"].isChecked() - return checked - - def set_name(self, name): - self.widgets['name'].setText(name) - - def set_line_edit(self, line_edit): - self.widgets['line_edit'].setText(line_edit) - print(line_edit) - - def setButtonText(self, text): - self.widgets["button"].setText(text) - - def setCancelText(self, text): - self.widgets["cancel"].setText(text) - - def on_line_edit_changed(self): - line_edit = self.widgets['line_edit'].text() - self.on_line_changed.emit(line_edit) - return self.set_line_edit(line_edit) - - def _on_clicked(self): - """Callback for when the 'show' button is clicked. - - Raises the parent (if any) - - """ - frame_range = self.widgets['line_edit'].text() - selected = self.widgets["selection"].isChecked() - pattern = r"^(?P-?[0-9]+)(?:(?:-+)(?P-?[0-9]+))?$" - match = re.match(pattern, frame_range) - frame_start = int(match.group("start")) - frame_end = int(match.group("end")) - if not nuke.allNodes("Read"): - self.close() - return - for read_node in nuke.allNodes("Read"): - if selected: - if not nuke.selectedNodes(): - self.close() - return - if read_node in nuke.selectedNodes(): - read_node["frame_mode"].setValue("start_at") - read_node["frame"].setValue(frame_range) - read_node["first"].setValue(frame_start) - read_node["last"].setValue(frame_end) - else: - read_node["frame_mode"].setValue("start_at") - read_node["frame"].setValue(frame_range) - read_node["first"].setValue(frame_start) - read_node["last"].setValue(frame_end) - - self.close() - - return False - - -@contextlib.contextmanager -def application(): - app = QtWidgets.QApplication(sys.argv) - yield - app.exec_() - - -if __name__ == "__main__": - with application(): - dialog = CustomScriptDialog() - dialog.show() diff --git a/openpype/hosts/nuke/startup/ops_frame_setting_for_read.py b/openpype/hosts/nuke/startup/ops_frame_setting_for_read.py new file mode 100644 index 0000000000..153effc7ad --- /dev/null +++ b/openpype/hosts/nuke/startup/ops_frame_setting_for_read.py @@ -0,0 +1,46 @@ +import nuke +import nukescripts +import re + + +class FrameSettingsPanel(nukescripts.PythonPanel): + def __init__(self, node): + nukescripts.PythonPanel.__init__(self, 'Frame Range') + self.read_node = node + # CREATE KNOBS + self.range = nuke.String_Knob('fRange', 'Frame Range', '%s-%s' % + (nuke.root().firstFrame(), nuke.root().lastFrame())) + self.selected = nuke.Boolean_Knob("selection") + # ADD KNOBS + self.addKnob(self.selected) + self.addKnob(self.range) + self.selected.setValue(False) + + def knobChanged(self, knob): + frame_range = self.range.value() + pattern = r"^(?P-?[0-9]+)(?:(?:-+)(?P-?[0-9]+))?$" + match = re.match(pattern, frame_range) + frame_start = int(match.group("start")) + frame_end = int(match.group("end")) + if not self.read_node: + return + for r in self.read_node: + if self.onchecked(): + if not nuke.selectedNodes(): + return + if r in nuke.selectedNodes(): + r["frame_mode"].setValue("start_at") + r["frame"].setValue(frame_range) + r["first"].setValue(frame_start) + r["last"].setValue(frame_end) + else: + r["frame_mode"].setValue("start_at") + r["frame"].setValue(frame_range) + r["first"].setValue(frame_start) + r["last"].setValue(frame_end) + + def onchecked(self): + if self.selected.value(): + return True + else: + return False diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index 35e5b1975c..cb06ad0a3b 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 Frame Range (Read Node)", - "command": "import openpype.hosts.nuke.startup.custom_popup as popup;popup.CustomScriptDialog(parent=popup.get_main_window()).show();", + "command": "import ops_frame_setting_for_read as popup;import nuke;popup.FrameSettingsPanel(nuke.allNodes('Read')).showModalDialog();", "tooltip": "Set Frame Range for Read Node(s)" } ] From 0655a6222bf3c69f4b4b3e9008c6c7803ffd3f94 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 1 Jun 2023 00:12:22 +0800 Subject: [PATCH 088/105] hound fix --- openpype/hosts/nuke/startup/ops_frame_setting_for_read.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/startup/ops_frame_setting_for_read.py b/openpype/hosts/nuke/startup/ops_frame_setting_for_read.py index 153effc7ad..bf98ef83f6 100644 --- a/openpype/hosts/nuke/startup/ops_frame_setting_for_read.py +++ b/openpype/hosts/nuke/startup/ops_frame_setting_for_read.py @@ -9,11 +9,14 @@ class FrameSettingsPanel(nukescripts.PythonPanel): self.read_node = node # CREATE KNOBS self.range = nuke.String_Knob('fRange', 'Frame Range', '%s-%s' % - (nuke.root().firstFrame(), nuke.root().lastFrame())) + (nuke.root().firstFrame(), + nuke.root().lastFrame())) self.selected = nuke.Boolean_Knob("selection") + self.info = nuke.Help_Knob("Instruction") # ADD KNOBS self.addKnob(self.selected) self.addKnob(self.range) + self.addKnob(self.info) self.selected.setValue(False) def knobChanged(self, knob): From aeaad018c1af7b30c9d1377d27ed91bec65e91cd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 31 May 2023 18:12:48 +0200 Subject: [PATCH 089/105] :rotating_light: make peace with the hound :dog: --- openpype/hosts/max/api/lib_renderproducts.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/max/api/lib_renderproducts.py b/openpype/hosts/max/api/lib_renderproducts.py index 81057db733..94b0aeb913 100644 --- a/openpype/hosts/max/api/lib_renderproducts.py +++ b/openpype/hosts/max/api/lib_renderproducts.py @@ -15,14 +15,12 @@ class RenderProducts(object): def __init__(self, project_settings=None): self._project_settings = project_settings or get_project_settings( - legacy_io.Session["AVALON_PROJECT"] - ) + legacy_io.Session["AVALON_PROJECT"]) def get_beauty(self, container): render_dir = os.path.dirname(rt.rendOutputFilename) - output_file = os.path.join(render_dir, - container) + output_file = os.path.join(render_dir, container) setting = self._project_settings img_fmt = setting["max"]["RenderSettings"]["image_format"] # noqa From 021ea8d6380e439ccddcdbf3ac78542b4329a7e1 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 1 Jun 2023 00:30:19 +0800 Subject: [PATCH 090/105] update the command --- openpype/settings/defaults/project_settings/nuke.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index cb06ad0a3b..a0caa40396 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 Frame Range (Read Node)", - "command": "import ops_frame_setting_for_read as popup;import nuke;popup.FrameSettingsPanel(nuke.allNodes('Read')).showModalDialog();", + "command": "import openpype.hosts.nuke.startup.ops_frame_setting_for_read as popup;import nuke;popup.FrameSettingsPanel(nuke.allNodes('Read')).showModalDialog();", "tooltip": "Set Frame Range for Read Node(s)" } ] From 717a2bc81c418a0d77cc7fd730bcb1d4ec18ae90 Mon Sep 17 00:00:00 2001 From: Alexey Bogomolov Date: Thu, 1 Jun 2023 01:04:10 +0300 Subject: [PATCH 091/105] 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 092/105] 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 5379679f869db99bf169263ca43bb6f068df9dbf Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 07:23:26 +0000 Subject: [PATCH 093/105] update README.md [skip ci] --- README.md | 75 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 514ffb62c0..8757e3db92 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![All Contributors](https://img.shields.io/badge/all_contributors-27-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-28-orange.svg?style=flat-square)](#contributors-) OpenPype ==== @@ -303,41 +303,44 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Milan Kolar

πŸ’» πŸ“– πŸš‡ πŸ’Ό πŸ–‹ πŸ” 🚧 πŸ“† πŸ‘€ πŸ§‘β€πŸ« πŸ’¬

Jakub JeΕΎek

πŸ’» πŸ“– πŸš‡ πŸ–‹ πŸ‘€ 🚧 πŸ§‘β€πŸ« πŸ“† πŸ’¬

OndΕ™ej Samohel

πŸ’» πŸ“– πŸš‡ πŸ–‹ πŸ‘€ 🚧 πŸ§‘β€πŸ« πŸ“† πŸ’¬

Jakub Trllo

πŸ’» πŸ“– πŸš‡ πŸ‘€ 🚧 πŸ’¬

Petr Kalis

πŸ’» πŸ“– πŸš‡ πŸ‘€ 🚧 πŸ’¬

64qam

πŸ’» πŸ‘€ πŸ“– πŸš‡ πŸ“† 🚧 πŸ–‹ πŸ““

Roy Nieterau

πŸ’» πŸ“– πŸ‘€ πŸ§‘β€πŸ« πŸ’¬

Toke Jepsen

πŸ’» πŸ“– πŸ‘€ πŸ§‘β€πŸ« πŸ’¬

Jiri Sindelar

πŸ’» πŸ‘€ πŸ“– πŸ–‹ βœ… πŸ““

Simone Barbieri

πŸ’» πŸ“–

karimmozilla

πŸ’»

Allan I. A.

πŸ’»

murphy

πŸ’» πŸ‘€ πŸ““ πŸ“– πŸ“†

Wijnand Koreman

πŸ’»

Bo Zhou

πŸ’»

ClΓ©ment Hector

πŸ’» πŸ‘€

David Lai

πŸ’» πŸ‘€

Derek

πŸ’» πŸ“–

GΓ‘bor Marinov

πŸ’» πŸ“–

icyvapor

πŸ’» πŸ“–

JΓ©rΓ΄me LORRAIN

πŸ’»

David Morris-Oliveros

πŸ’»

BenoitConnan

πŸ’»

Malthaldar

πŸ’»

Sven Neve

πŸ’»

zafrs

πŸ’»

FΓ©lix David

πŸ’» πŸ“–
Milan Kolar
Milan Kolar

πŸ’» πŸ“– πŸš‡ πŸ’Ό πŸ–‹ πŸ” 🚧 πŸ“† πŸ‘€ πŸ§‘β€πŸ« πŸ’¬
Jakub JeΕΎek
Jakub JeΕΎek

πŸ’» πŸ“– πŸš‡ πŸ–‹ πŸ‘€ 🚧 πŸ§‘β€πŸ« πŸ“† πŸ’¬
OndΕ™ej Samohel
OndΕ™ej Samohel

πŸ’» πŸ“– πŸš‡ πŸ–‹ πŸ‘€ 🚧 πŸ§‘β€πŸ« πŸ“† πŸ’¬
Jakub Trllo
Jakub Trllo

πŸ’» πŸ“– πŸš‡ πŸ‘€ 🚧 πŸ’¬
Petr Kalis
Petr Kalis

πŸ’» πŸ“– πŸš‡ πŸ‘€ 🚧 πŸ’¬
64qam
64qam

πŸ’» πŸ‘€ πŸ“– πŸš‡ πŸ“† 🚧 πŸ–‹ πŸ““
Roy Nieterau
Roy Nieterau

πŸ’» πŸ“– πŸ‘€ πŸ§‘β€πŸ« πŸ’¬
Toke Jepsen
Toke Jepsen

πŸ’» πŸ“– πŸ‘€ πŸ§‘β€πŸ« πŸ’¬
Jiri Sindelar
Jiri Sindelar

πŸ’» πŸ‘€ πŸ“– πŸ–‹ βœ… πŸ““
Simone Barbieri
Simone Barbieri

πŸ’» πŸ“–
karimmozilla
karimmozilla

πŸ’»
Allan I. A.
Allan I. A.

πŸ’»
murphy
murphy

πŸ’» πŸ‘€ πŸ““ πŸ“– πŸ“†
Wijnand Koreman
Wijnand Koreman

πŸ’»
Bo Zhou
Bo Zhou

πŸ’»
ClΓ©ment Hector
ClΓ©ment Hector

πŸ’» πŸ‘€
David Lai
David Lai

πŸ’» πŸ‘€
Derek
Derek

πŸ’» πŸ“–
GΓ‘bor Marinov
GΓ‘bor Marinov

πŸ’» πŸ“–
icyvapor
icyvapor

πŸ’» πŸ“–
JΓ©rΓ΄me LORRAIN
JΓ©rΓ΄me LORRAIN

πŸ’»
David Morris-Oliveros
David Morris-Oliveros

πŸ’»
BenoitConnan
BenoitConnan

πŸ’»
Malthaldar
Malthaldar

πŸ’»
Sven Neve
Sven Neve

πŸ’»
zafrs
zafrs

πŸ’»
FΓ©lix David
FΓ©lix David

πŸ’» πŸ“–
Alexey Bogomolov
Alexey Bogomolov

πŸ’»
From 28e9da8918b86d6ef30f2ffb549483edef3e7a24 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 07:23:27 +0000 Subject: [PATCH 094/105] update .all-contributorsrc [skip ci] --- .all-contributorsrc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index b30f3b2499..60812cdb3c 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1,6 +1,6 @@ { "projectName": "OpenPype", - "projectOwner": "pypeclub", + "projectOwner": "ynput", "repoType": "github", "repoHost": "https://github.com", "files": [ @@ -319,8 +319,18 @@ "code", "doc" ] + }, + { + "login": "movalex", + "name": "Alexey Bogomolov", + "avatar_url": "https://avatars.githubusercontent.com/u/11698866?v=4", + "profile": "http://abogomolov.com", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, - "skipCi": true + "skipCi": true, + "commitType": "docs" } From eeaa79125ce111272e94e3f5c7f2d6c7f3b154f3 Mon Sep 17 00:00:00 2001 From: JackP Date: Thu, 1 Jun 2023 10:06:08 +0100 Subject: [PATCH 095/105] 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 096/105] 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 097/105] 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 d4a807194ebb0971a629f608f3fc2ecf84723394 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 1 Jun 2023 11:24:34 +0200 Subject: [PATCH 098/105] Resolve: Make sure scripts dir exists (#5078) * make sure scripts dir exists * use exist_ok in makedirs --- openpype/hosts/resolve/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/hosts/resolve/utils.py b/openpype/hosts/resolve/utils.py index 9a161f4865..1213fd9e7a 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) From 3fdbcd3247ad5bbd13230d17bdbec90f65b3985c Mon Sep 17 00:00:00 2001 From: JackP Date: Thu, 1 Jun 2023 10:31:50 +0100 Subject: [PATCH 099/105] 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 aab6e19b5ed0f4f76335cea49342f098ed548319 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Jun 2023 15:48:52 +0200 Subject: [PATCH 100/105] 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 abc266e9e5868277b47c2dabdd1697ec13d1a024 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 1 Jun 2023 16:14:29 +0200 Subject: [PATCH 101/105] refactor the script to be frame number rather then frame range --- .../startup/frame_setting_for_read_nodes.py | 47 ++++++++++++++++++ .../startup/ops_frame_setting_for_read.py | 49 ------------------- .../defaults/project_settings/nuke.json | 6 +-- 3 files changed, 50 insertions(+), 52 deletions(-) create mode 100644 openpype/hosts/nuke/startup/frame_setting_for_read_nodes.py delete mode 100644 openpype/hosts/nuke/startup/ops_frame_setting_for_read.py 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/nuke/startup/ops_frame_setting_for_read.py b/openpype/hosts/nuke/startup/ops_frame_setting_for_read.py deleted file mode 100644 index bf98ef83f6..0000000000 --- a/openpype/hosts/nuke/startup/ops_frame_setting_for_read.py +++ /dev/null @@ -1,49 +0,0 @@ -import nuke -import nukescripts -import re - - -class FrameSettingsPanel(nukescripts.PythonPanel): - def __init__(self, node): - nukescripts.PythonPanel.__init__(self, 'Frame Range') - self.read_node = node - # CREATE KNOBS - self.range = nuke.String_Knob('fRange', 'Frame Range', '%s-%s' % - (nuke.root().firstFrame(), - nuke.root().lastFrame())) - self.selected = nuke.Boolean_Knob("selection") - self.info = nuke.Help_Knob("Instruction") - # ADD KNOBS - self.addKnob(self.selected) - self.addKnob(self.range) - self.addKnob(self.info) - self.selected.setValue(False) - - def knobChanged(self, knob): - frame_range = self.range.value() - pattern = r"^(?P-?[0-9]+)(?:(?:-+)(?P-?[0-9]+))?$" - match = re.match(pattern, frame_range) - frame_start = int(match.group("start")) - frame_end = int(match.group("end")) - if not self.read_node: - return - for r in self.read_node: - if self.onchecked(): - if not nuke.selectedNodes(): - return - if r in nuke.selectedNodes(): - r["frame_mode"].setValue("start_at") - r["frame"].setValue(frame_range) - r["first"].setValue(frame_start) - r["last"].setValue(frame_end) - else: - r["frame_mode"].setValue("start_at") - r["frame"].setValue(frame_range) - r["first"].setValue(frame_start) - r["last"].setValue(frame_end) - - def onchecked(self): - if self.selected.value(): - return True - else: - return False diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index a0caa40396..3f8be4c872 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -226,9 +226,9 @@ { "type": "action", "sourcetype": "python", - "title": "Set Frame Range (Read Node)", - "command": "import openpype.hosts.nuke.startup.ops_frame_setting_for_read as popup;import nuke;popup.FrameSettingsPanel(nuke.allNodes('Read')).showModalDialog();", - "tooltip": "Set Frame Range for Read Node(s)" + "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)" } ] }, From 8356dfac7e1150cbe31cc8a63f26cee0c0fe1dd0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 1 Jun 2023 16:41:13 +0200 Subject: [PATCH 102/105] py2 compatibility --- openpype/hosts/nuke/startup/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 openpype/hosts/nuke/startup/__init__.py diff --git a/openpype/hosts/nuke/startup/__init__.py b/openpype/hosts/nuke/startup/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From d0886e43fe8efc8b675d9da5ccc9c37c459408d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20David?= Date: Thu, 1 Jun 2023 16:42:42 +0200 Subject: [PATCH 103/105] fix doc --- website/docs/dev_blender.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/dev_blender.md b/website/docs/dev_blender.md index 228447fb64..bed0e4a09d 100644 --- a/website/docs/dev_blender.md +++ b/website/docs/dev_blender.md @@ -9,14 +9,14 @@ toc_max_heading_level: 4 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.pre_add_run_python_script_arg import AddPythonScriptToLaunchArgs +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 = AddPythonScriptToLaunchArgs.order - 1 + order = pre_add_run_python_script_arg.AddPythonScriptToLaunchArgs.order - 1 app_groups = [ "blender", ] From e64779b3450e66f43bf87a43efb97a177d94360c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:07:55 +0200 Subject: [PATCH 104/105] fix restart arguments in tray (#5085) --- openpype/tools/tray/pype_tray.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py index 2f3b5251f9..fdc0a8094d 100644 --- a/openpype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -633,10 +633,10 @@ class TrayManager: # Create a copy of sys.argv additional_args = list(sys.argv) - # Check last argument from `get_openpype_execute_args` - # - when running from code it is the same as first from sys.argv - if args[-1] == additional_args[0]: - additional_args.pop(0) + # Remove first argument from 'sys.argv' + # - when running from code the first argument is 'start.py' + # - when running from build the first argument is executable + additional_args.pop(0) cleanup_additional_args = False if use_expected_version: @@ -663,7 +663,6 @@ class TrayManager: additional_args = _additional_args args.extend(additional_args) - run_detached_process(args, env=envs) self.exit() From 69297d0b687693f0b29e751a4e38b562c336e969 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 2 Jun 2023 14:40:20 +0100 Subject: [PATCH 105/105] 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